Skip to content

Commit 205d532

Browse files
committed
Bugzilla bot
1 parent d469352 commit 205d532

File tree

6 files changed

+233
-0
lines changed

6 files changed

+233
-0
lines changed

zulip_bots/zulip_bots/bots/bugzilla/__init__.py

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[bugzilla]
2+
site = http://bugs.lemoine.tech
3+
api_key = 2BjH9yZxjhe2ZK81wT2loaOOJ3yUvgbgu4akTsXc
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import re
2+
import requests
3+
from typing import Any, Dict
4+
5+
TOPIC_REGEX = re.compile('^Bug (?P<bug_number>.+)$')
6+
7+
HELP_REGEX = re.compile('help$')
8+
9+
HELP_RESPONSE = '''
10+
**help**
11+
12+
`help` returns this short help
13+
14+
**comment**
15+
16+
With no argument, by default, a new comment is added to the bug that is associated to the topic.
17+
For example, on topic Bug 123,
18+
19+
you:
20+
21+
> @**Bugzilla** A new comment
22+
23+
Then `A new comment` is added to bug 123
24+
'''
25+
26+
class BugzillaHandler(object):
27+
'''
28+
A docstring documenting this bot.
29+
'''
30+
31+
def usage(self):
32+
return '''
33+
Bugzilla Bot uses the Bugzilla REST API v1 to interact with Bugzilla. In order to use
34+
Bugzilla Bot, `bugzilla.conf` must be set up. See `doc.md` for more details.
35+
'''
36+
37+
def initialize(self, bot_handler: Any) -> None:
38+
config = bot_handler.get_config_info('bugzilla')
39+
40+
site = config.get('site')
41+
api_key = config.get('api_key')
42+
if not site:
43+
raise KeyError('No `site` was specified')
44+
if not api_key:
45+
raise KeyError('No `api_key` was specified')
46+
47+
self.site = site
48+
self.api_key = api_key
49+
50+
def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None:
51+
content = message.get('content')
52+
topic = message.get('subject')
53+
54+
if HELP_REGEX.match (content):
55+
self.handle_help(message, bot_handler)
56+
return None
57+
58+
try:
59+
bug_number = self.extract_bug_number(topic)
60+
except ValueError:
61+
bot_handler.send_reply(message, 'Unsupported topic: ' + topic)
62+
return None
63+
64+
comment = content
65+
self.handle_comment(bug_number, comment, message, bot_handler)
66+
67+
def handle_help(self, message: Dict[str, str], bot_handler: Any) -> None:
68+
bot_handler.send_reply(message, HELP_RESPONSE)
69+
70+
def handle_comment(self, bug_number: str, comment: str, message: Dict[str, str], bot_handler: Any) -> None:
71+
url = '{}/rest/bug/{}/comment'.format(self.site, bug_number)
72+
requests.post (url,
73+
json=self.make_comment_json(comment))
74+
75+
def make_comment_json(self, comment: str) -> Any:
76+
json = {
77+
'api_key': self.api_key,
78+
'comment': comment
79+
}
80+
return json
81+
82+
def extract_bug_number(self, topic: str) -> Any:
83+
topic_match = TOPIC_REGEX.match(topic)
84+
85+
if not topic_match:
86+
raise ValueError
87+
else:
88+
return topic_match.group('bug_number')
89+
90+
handler_class = BugzillaHandler
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Bugzilla bot
2+
3+
This bot allows to update directly Bugzilla from Zulip
4+
5+
## Setup
6+
7+
To use Bugzilla Bot, first set up `bugzilla.conf`. `bugzilla.conf` takes 2 options:
8+
9+
- site (the site like `https://bugs.xxx.net` that includes both the protocol and the domain), and
10+
- api_key (a Bugzilla API key),
11+
12+
13+
## Usage
14+
15+
Run this bot as described
16+
[here](https://zulipchat.com/api/running-bots#running-a-bot).
17+
18+
Use this bot with the following command
19+
20+
`@mentioned-bot <action>` in a topic that is named `Bug 123` where 123 is the bug number
21+
22+
### comment
23+
24+
With no argument, by default, a new comment is added to the bug that is associated to the topic.
25+
For example, on topic Bug 123,
26+
27+
you:
28+
29+
> @**Bugzilla** A new comment
30+
31+
Then `A new comment` is added to bug 123
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"request": {
3+
"api_url": "https://bugs.net/rest/bug/123/comment",
4+
"method": "POST",
5+
"json": {
6+
"api_key": "kkk",
7+
"comment": "a comment"
8+
}
9+
},
10+
"response": {
11+
"id": 124
12+
},
13+
"response-headers": {
14+
"status": 200,
15+
"content-type": "application/json; charset=utf-8"
16+
}
17+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from typing import Any, Dict
2+
from unittest.mock import patch
3+
from zulip_bots.test_lib import BotTestCase, DefaultTests
4+
5+
class TestBugzillaBot(BotTestCase, DefaultTests):
6+
bot_name = 'bugzilla'
7+
8+
MOCK_CONFIG_INFO = {
9+
'site': 'https://bugs.net',
10+
'api_key': 'kkk'
11+
}
12+
13+
MOCK_HELP_RESPONSE = '''
14+
**help**
15+
16+
`help` returns this short help
17+
18+
**comment**
19+
20+
With no argument, by default, a new comment is added to the bug that is associated to the topic.
21+
For example, on topic Bug 123,
22+
23+
you:
24+
25+
> @**Bugzilla** A new comment
26+
27+
Then `A new comment` is added to bug 123
28+
'''
29+
30+
def make_request_message(self, content: str) -> Dict[str, Any]:
31+
message = super().make_request_message(content)
32+
message['subject'] = "Bug 123"
33+
return message
34+
35+
def handle_message_only(self, request: str) -> Dict[str, Any]:
36+
bot, bot_handler = self._get_handlers()
37+
message = self.make_request_message(request)
38+
bot_handler.reset_transcript()
39+
bot.handle_message(message, bot_handler)
40+
41+
def test_bot_responds_to_empty_message(self) -> None:
42+
pass
43+
44+
def _test_invalid_config(self, invalid_config, error_message) -> None:
45+
with self.mock_config_info(invalid_config), \
46+
self.assertRaisesRegexp(KeyError, error_message):
47+
bot, bot_handler = self._get_handlers()
48+
49+
def test_config_without_site(self) -> None:
50+
config_without_site = {
51+
'api_key': 'kkk',
52+
}
53+
self._test_invalid_config(config_without_site,
54+
'No `site` was specified')
55+
56+
def test_config_without_api_key(self) -> None:
57+
config_without_api_key = {
58+
'site': 'https://bugs.xx',
59+
}
60+
self._test_invalid_config(config_without_api_key,
61+
'No `api_key` was specified')
62+
63+
def test_comment(self) -> None:
64+
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
65+
self.mock_http_conversation('test_comment'):
66+
self.handle_message_only('a comment')
67+
68+
def test_help(self) -> None:
69+
with self.mock_config_info(self.MOCK_CONFIG_INFO):
70+
self.verify_reply('help', self.MOCK_HELP_RESPONSE)
71+
72+
class TestBugzillaBotWrongTopic(BotTestCase, DefaultTests):
73+
bot_name = 'bugzilla'
74+
75+
MOCK_CONFIG_INFO = {
76+
'site': 'https://bugs.net',
77+
'api_key': 'kkk'
78+
}
79+
80+
MOCK_COMMENT_INVALID_TOPIC_RESPONSE = 'Unsupported topic: kqatm2'
81+
82+
def make_request_message(self, content: str) -> Dict[str, Any]:
83+
message = super().make_request_message(content)
84+
message['subject'] = "kqatm2"
85+
return message
86+
87+
def test_bot_responds_to_empty_message(self) -> None:
88+
pass
89+
90+
def test_no_bug_number(self) -> None:
91+
with self.mock_config_info(self.MOCK_CONFIG_INFO):
92+
self.verify_reply('a comment', self.MOCK_COMMENT_INVALID_TOPIC_RESPONSE)

0 commit comments

Comments
 (0)