Skip to content

Commit 2eb1bf7

Browse files
authored
Merge pull request #17 from devops-ia/feat/add-tests-and-examples
add tests and linter
2 parents 52a0ed6 + a807b29 commit 2eb1bf7

22 files changed

+1138
-50
lines changed

.github/workflows/build.yaml

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,6 @@ jobs:
3434
git config user.name "$GITHUB_ACTOR"
3535
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
3636
37-
- name: Semantic Release
38-
uses: cycjimmy/semantic-release-action@v4
39-
id: semantic_release
40-
with:
41-
dry_run: true
42-
branch: main
43-
tag_format: ${version}
44-
env:
45-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46-
4737
- name: "Set Docker metadata"
4838
id: meta
4939
uses: docker/metadata-action@v5
@@ -56,8 +46,10 @@ jobs:
5646
org.opencontainers.image.title=Bitbucket Bot
5747
org.opencontainers.image.description=Bitbucket Bot to Google Chat (Spaces)
5848
org.opencontainers.image.vendor=DevOps IA
49+
flavor: |
50+
latest=auto
5951
tags: |
60-
type=raw,value=${{ github.ref_name }}
52+
type=semver,pattern={{raw}}
6153
type=sha,enable=false
6254
6355
- name: Set up QEMU

.github/workflows/lint-tests.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Linter and tests for Bitbucket Bot
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
types: [opened, synchronize, reopened]
7+
push:
8+
paths:
9+
- app/**
10+
- tests/**
11+
- Pipfile*
12+
13+
jobs:
14+
release:
15+
name: Linter and tests
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
24+
- uses: actions/setup-python@v5
25+
with:
26+
python-version: 3.x
27+
check-latest: true
28+
29+
- name: Install pipenv
30+
run: |
31+
python -m pip install --upgrade pipenv wheel
32+
33+
- id: cache-pipenv
34+
uses: actions/cache@v4
35+
with:
36+
path: ~/.local/share/virtualenvs
37+
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
38+
39+
- name: Install dependencies
40+
if: steps.cache-pipenv.outputs.cache-hit != 'true'
41+
run: |
42+
pipenv install --deploy --dev
43+
44+
- name: pylint
45+
uses: sunnysid3up/python-linter@master
46+
with:
47+
source: "app"
48+
pylint-options: "--rcfile=.pylintrc"
49+
50+
- name: pytest
51+
uses: pavelzw/pytest-action@v2
52+
with:
53+
verbose: false
54+
emoji: false
55+
job-summary: true
56+
report-title: 'pylint report'

.pylintrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[pylint]
2+
max-line-length=120
3+
disable=no-value-for-parameter,no-member
4+
ignore=.git,venv,tests/__pycache__,migrations
5+
max-attributes=10

Pipfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[[source]]
2+
url = "https://pypi.org/simple"
3+
verify_ssl = true
4+
name = "pypi"
5+
6+
[packages]
7+
flask = "==3.0.*"
8+
gunicorn = "==22.0.*"
9+
markupsafe = "==2.1.5"
10+
requests = "==2.32.*"
11+
12+
[dev-packages]
13+
pytest = "==8.3.*"
14+
pytest-flask = "==1.3.*"
15+
pylint = "==3.2.*"
16+
17+
[requires]
18+
python_version = "3.12"

Pipfile.lock

Lines changed: 499 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,102 @@
1+
12
# Bitbucket Bot for Google Chat
23

34
## Introduction
45

5-
Bitbucket Bot to integrate Bitbucket's webhook with Google Chat (Spaces).
6+
The Bitbucket Bot integrates Bitbucket's webhook events with Google Chat (Spaces). This bot listens for specific events from Bitbucket, such as pull requests (PRs), and sends notifications directly to a Google Chat (Spaces). The bot is designed to streamline your development workflow by providing real-time updates on key activities in your repositories.
67

78
## Example
89

10+
When a pull request is opened, the bot sends a notification to your Google Chat space with details about the PR:
11+
912
![Sample open PR](img/sample-pr.png)
1013

11-
## Image details (from dive)
14+
## Quick start!
1215

13-
```text
14-
│ Image Details ├─────────────
16+
### Run container
1517

16-
Total Image size: 178 MB
17-
Potential wasted space: 6.6 MB
18-
Image efficiency score: 97 %
18+
```bash
19+
docker run --name <container-name> \
20+
-p 8080:8080 \
21+
--bind 0.0.0.0:8080 \
22+
--log-level=info \
23+
-e URL=<ENDPOINT-GOOGLE-CHAT-SPACE> \
24+
-e TOKEN=<SECRET-TOKEN> \
25+
devopsiaci/bitbucket-bot:latest
1926
```
2027

21-
You can reproduce this summary with [`dive`](https://github.com/wagoodman/dive):
22-
23-
```command
24-
dive build -t <tag-name> .
28+
### Bitbucket Payloads
29+
30+
Refer to the [Bitbucket payload documentation](https://confluence.atlassian.com/bitbucketserver0721/event-payload-1115665959.html?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash#Eventpayload-pullrequest) for details on the data structure of events.
31+
32+
## Request Payload
33+
34+
### Payload example
35+
36+
Here is an example of a pull request payload that the bot processes. Check [samples folder](./samples):
37+
38+
```json
39+
{
40+
"eventKey": "pr:comment:added",
41+
"date": "2022-09-02T09:24:34+0000",
42+
"actor": {
43+
"name": "sample",
44+
"emailAddress": "mail@example.com",
45+
"id": 86045,
46+
"displayName": "sample",
47+
"active": true,
48+
"slug": "sample",
49+
"type": "NORMAL",
50+
"links": {
51+
"self": [
52+
{
53+
"href": "https://bitbucket.org/bitbucket/users/sample"
54+
}
55+
]
56+
}
57+
},
58+
...
2559
```
2660

27-
## Quick start!
61+
On `samples` folder:
62+
63+
```console
64+
# request
65+
$ curl -XPOST -H "Content-Type: application/json" -d '@sample_pr_add_comment.json' http://<IP>:<PORT>\?token\=<SECRET-TOKEN>
66+
67+
POST / HTTP/1.1
68+
Host: <IP>:<PORT>
69+
User-Agent: python-requests/2.32.3
70+
Accept-Encoding: gzip, deflate
71+
Accept: */*
72+
Connection: keep-alive
73+
Content-Type: application/json; charset=UTF-8
74+
Content-Length: 958
2875

29-
### Pull image
76+
{"cards": [{"header": {"title": "pr:comment:added | (GFP) REPOSITORY", "imageUrl": "https://cdn-icons-png.flaticon.com/512/6125/6125001.png"}, "sections": [{"widgets": [{"keyValue": {"icon": "PERSON", "topLabel": "author", "content": "mail@example.com"}}, {"keyValue": {"iconUrl": "https://cdn-icons-png.flaticon.com/512/7201/7201872.png", "topLabel": "Pull Request", "content": "test 3"}}, {"keyValue": {"iconUrl": "https://cdn-icons-png.flaticon.com/512/6577/6577243.png", "topLabel": "ID Pull Request", "content": "1 (version: 5)"}}, {"keyValue": {"iconUrl": "https://cdn-icons-png.flaticon.com/512/1721/1721936.png", "topLabel": "Status", "content": "OPEN"}}]}, {"widgets": [{"keyValue": {"icon": "DESCRIPTION", "topLabel": "Comment by sample", "content": "Test"}}]}, {"widgets": [{"buttons": [{"textButton": {"text": "Review PR", "onClick": {"openLink": {"url": "https://bitbucket.org/bitbucket/projects/KEY/repos/REPOSITORY/pull-requests/1"}}}}]}]}]}]}
3077

31-
```command
32-
docker build -t <tag-name> .
3378
```
3479

35-
### Run container
80+
## Tests
3681

37-
**IMPORTANT**: `--bind`, `--log-level`, `-e` are runtime Docker options.
82+
### Running Tests
3883

39-
```command
40-
docker run --name <container-name> \
41-
-p 8080:8080 \
42-
<tag-name> \
43-
--bind 0.0.0.0:8080 \
44-
--log-level=info \
45-
-e URL=<ENDPOINT-GOOGLE-CHAT-SPACE> \
46-
-e TOKEN=<SECRET-TOKEN>
84+
The project uses `pytest` for testing. To run the tests, navigate to the project directory and execute the following command:
85+
86+
```bash
87+
pytest
4788
```
4889

49-
* [Bitbucket playloads](https://confluence.atlassian.com/bitbucketserver0721/event-payload-1115665959.html?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash#Eventpayload-pullrequest)
90+
This will run all the tests located in the `tests/` directory. Make sure you have the necessary dependencies installed by running:
91+
92+
```bash
93+
pip install -r requirements-dev.txt
94+
```
95+
96+
### Writing Tests
97+
98+
To write new tests, create a new file in the `tests/` directory with the prefix `test_`. For example, to test a new feature in `app.py`, create a file named `test_app.py` and add your test cases there.
99+
100+
## Contributing
101+
102+
Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file for details on our code of conduct and the process for submitting pull requests.

app/__init__.py

Whitespace-only changes.

app/app.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
"""
2+
Bitbucket Bot to integrate Bitbucket's webhook with Google Chat (Spaces)
3+
"""
4+
15
import html
26
import json
37
import os
@@ -175,6 +179,7 @@ def send_message(self, message={}):
175179
url=self.url,
176180
headers=headers,
177181
data=json.dumps(bot_message),
182+
timeout=10
178183
)
179184

180185
return response.text
@@ -186,10 +191,11 @@ def main():
186191
Main function to deploy
187192
188193
'''
189-
url = os.environ.get('URL', 'http://example.com')
194+
url = os.environ.get('URL', 'http://example.com')
190195
token = os.environ.get('TOKEN')
196+
r = {}
191197

192-
if request.args['token']!= token:
198+
if request.args['token'] != token:
193199
return "Invalid token", 403
194200

195201
event = request.get_json()
@@ -200,18 +206,17 @@ def main():
200206
message = Message(url, event)
201207
if (event['eventKey'] == 'pr:opened' or event['eventKey'] == 'pr:merged' or event['eventKey'] == 'pr:declined'):
202208
r = message.send_message()
203-
elif (event['eventKey'] == 'pr:modified'):
209+
elif event['eventKey'] == 'pr:modified':
204210
r = comment = message.pr_modified(event)
205211
message.send_message(comment)
206-
elif (event['eventKey'] == 'pr:comment:added'):
212+
elif event['eventKey'] == 'pr:comment:added':
207213
comment = message.pr_comment_add(event)
208214
r = message.send_message(comment)
209215
elif (event['eventKey'] == 'pr:reviewer:needs_work' or event['eventKey'] == 'pr:reviewer:approved'):
210216
comment = message.pr_approved(event)
211217
r = message.send_message(comment)
212218

213-
# Mitigate XSS
214-
return html.escape(r)
219+
return html.escape(json.dumps(r))
215220

216221

217222
if __name__ == "__main__":

app/run.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
2+
"""
3+
Starts the Flask application
4+
"""
5+
16
from app import app
27

38
if __name__ == "__main__":
49
app.run()
5-

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
pythonpath = .
3+
testpaths = tests

requirements.txt

Lines changed: 0 additions & 4 deletions
This file was deleted.

samples/sample_declined.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

samples/sample_merged.json

Lines changed: 0 additions & 1 deletion
This file was deleted.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)