From 205d532d910ed053f32456ac1b3869966bb04a48 Mon Sep 17 00:00:00 2001 From: Nicolas Relange Date: Wed, 1 Apr 2020 23:42:10 +0200 Subject: [PATCH 1/4] Bugzilla bot --- .../zulip_bots/bots/bugzilla/__init__.py | 0 .../zulip_bots/bots/bugzilla/bugzilla.conf | 3 + .../zulip_bots/bots/bugzilla/bugzilla.py | 90 ++++++++++++++++++ zulip_bots/zulip_bots/bots/bugzilla/doc.md | 31 +++++++ .../bots/bugzilla/fixtures/test_comment.json | 17 ++++ .../zulip_bots/bots/bugzilla/test_bugzilla.py | 92 +++++++++++++++++++ 6 files changed, 233 insertions(+) create mode 100644 zulip_bots/zulip_bots/bots/bugzilla/__init__.py create mode 100644 zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf create mode 100644 zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py create mode 100644 zulip_bots/zulip_bots/bots/bugzilla/doc.md create mode 100644 zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json create mode 100644 zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py diff --git a/zulip_bots/zulip_bots/bots/bugzilla/__init__.py b/zulip_bots/zulip_bots/bots/bugzilla/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf b/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf new file mode 100644 index 000000000..f3e8ac8e0 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf @@ -0,0 +1,3 @@ +[bugzilla] +site = http://bugs.lemoine.tech +api_key = 2BjH9yZxjhe2ZK81wT2loaOOJ3yUvgbgu4akTsXc diff --git a/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py b/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py new file mode 100644 index 000000000..ab8630870 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py @@ -0,0 +1,90 @@ +import re +import requests +from typing import Any, Dict + +TOPIC_REGEX = re.compile('^Bug (?P.+)$') + +HELP_REGEX = re.compile('help$') + +HELP_RESPONSE = ''' +**help** + +`help` returns this short help + +**comment** + +With no argument, by default, a new comment is added to the bug that is associated to the topic. +For example, on topic Bug 123, + +you: + + > @**Bugzilla** A new comment + +Then `A new comment` is added to bug 123 +''' + +class BugzillaHandler(object): + ''' + A docstring documenting this bot. + ''' + + def usage(self): + return ''' + Bugzilla Bot uses the Bugzilla REST API v1 to interact with Bugzilla. In order to use + Bugzilla Bot, `bugzilla.conf` must be set up. See `doc.md` for more details. + ''' + + def initialize(self, bot_handler: Any) -> None: + config = bot_handler.get_config_info('bugzilla') + + site = config.get('site') + api_key = config.get('api_key') + if not site: + raise KeyError('No `site` was specified') + if not api_key: + raise KeyError('No `api_key` was specified') + + self.site = site + self.api_key = api_key + + def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None: + content = message.get('content') + topic = message.get('subject') + + if HELP_REGEX.match (content): + self.handle_help(message, bot_handler) + return None + + try: + bug_number = self.extract_bug_number(topic) + except ValueError: + bot_handler.send_reply(message, 'Unsupported topic: ' + topic) + return None + + comment = content + self.handle_comment(bug_number, comment, message, bot_handler) + + def handle_help(self, message: Dict[str, str], bot_handler: Any) -> None: + bot_handler.send_reply(message, HELP_RESPONSE) + + def handle_comment(self, bug_number: str, comment: str, message: Dict[str, str], bot_handler: Any) -> None: + url = '{}/rest/bug/{}/comment'.format(self.site, bug_number) + requests.post (url, + json=self.make_comment_json(comment)) + + def make_comment_json(self, comment: str) -> Any: + json = { + 'api_key': self.api_key, + 'comment': comment + } + return json + + def extract_bug_number(self, topic: str) -> Any: + topic_match = TOPIC_REGEX.match(topic) + + if not topic_match: + raise ValueError + else: + return topic_match.group('bug_number') + +handler_class = BugzillaHandler diff --git a/zulip_bots/zulip_bots/bots/bugzilla/doc.md b/zulip_bots/zulip_bots/bots/bugzilla/doc.md new file mode 100644 index 000000000..8b83d801d --- /dev/null +++ b/zulip_bots/zulip_bots/bots/bugzilla/doc.md @@ -0,0 +1,31 @@ +# Bugzilla bot + +This bot allows to update directly Bugzilla from Zulip + +## Setup + +To use Bugzilla Bot, first set up `bugzilla.conf`. `bugzilla.conf` takes 2 options: + + - site (the site like `https://bugs.xxx.net` that includes both the protocol and the domain), and + - api_key (a Bugzilla API key), + + +## Usage + +Run this bot as described +[here](https://zulipchat.com/api/running-bots#running-a-bot). + +Use this bot with the following command + +`@mentioned-bot ` in a topic that is named `Bug 123` where 123 is the bug number + +### comment + +With no argument, by default, a new comment is added to the bug that is associated to the topic. +For example, on topic Bug 123, + +you: + + > @**Bugzilla** A new comment + +Then `A new comment` is added to bug 123 diff --git a/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json b/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json new file mode 100644 index 000000000..fe29e01ef --- /dev/null +++ b/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json @@ -0,0 +1,17 @@ +{ + "request": { + "api_url": "https://bugs.net/rest/bug/123/comment", + "method": "POST", + "json": { + "api_key": "kkk", + "comment": "a comment" + } + }, + "response": { + "id": 124 + }, + "response-headers": { + "status": 200, + "content-type": "application/json; charset=utf-8" + } +} diff --git a/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py b/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py new file mode 100644 index 000000000..d270edb9a --- /dev/null +++ b/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py @@ -0,0 +1,92 @@ +from typing import Any, Dict +from unittest.mock import patch +from zulip_bots.test_lib import BotTestCase, DefaultTests + +class TestBugzillaBot(BotTestCase, DefaultTests): + bot_name = 'bugzilla' + + MOCK_CONFIG_INFO = { + 'site': 'https://bugs.net', + 'api_key': 'kkk' + } + + MOCK_HELP_RESPONSE = ''' +**help** + +`help` returns this short help + +**comment** + +With no argument, by default, a new comment is added to the bug that is associated to the topic. +For example, on topic Bug 123, + +you: + + > @**Bugzilla** A new comment + +Then `A new comment` is added to bug 123 +''' + + def make_request_message(self, content: str) -> Dict[str, Any]: + message = super().make_request_message(content) + message['subject'] = "Bug 123" + return message + + def handle_message_only(self, request: str) -> Dict[str, Any]: + bot, bot_handler = self._get_handlers() + message = self.make_request_message(request) + bot_handler.reset_transcript() + bot.handle_message(message, bot_handler) + + def test_bot_responds_to_empty_message(self) -> None: + pass + + def _test_invalid_config(self, invalid_config, error_message) -> None: + with self.mock_config_info(invalid_config), \ + self.assertRaisesRegexp(KeyError, error_message): + bot, bot_handler = self._get_handlers() + + def test_config_without_site(self) -> None: + config_without_site = { + 'api_key': 'kkk', + } + self._test_invalid_config(config_without_site, + 'No `site` was specified') + + def test_config_without_api_key(self) -> None: + config_without_api_key = { + 'site': 'https://bugs.xx', + } + self._test_invalid_config(config_without_api_key, + 'No `api_key` was specified') + + def test_comment(self) -> None: + with self.mock_config_info(self.MOCK_CONFIG_INFO), \ + self.mock_http_conversation('test_comment'): + self.handle_message_only('a comment') + + def test_help(self) -> None: + with self.mock_config_info(self.MOCK_CONFIG_INFO): + self.verify_reply('help', self.MOCK_HELP_RESPONSE) + +class TestBugzillaBotWrongTopic(BotTestCase, DefaultTests): + bot_name = 'bugzilla' + + MOCK_CONFIG_INFO = { + 'site': 'https://bugs.net', + 'api_key': 'kkk' + } + + MOCK_COMMENT_INVALID_TOPIC_RESPONSE = 'Unsupported topic: kqatm2' + + def make_request_message(self, content: str) -> Dict[str, Any]: + message = super().make_request_message(content) + message['subject'] = "kqatm2" + return message + + def test_bot_responds_to_empty_message(self) -> None: + pass + + def test_no_bug_number(self) -> None: + with self.mock_config_info(self.MOCK_CONFIG_INFO): + self.verify_reply('a comment', self.MOCK_COMMENT_INVALID_TOPIC_RESPONSE) From 20830f4782d09354d86b8f7d048659a0c93b7e28 Mon Sep 17 00:00:00 2001 From: Nicolas Relange Date: Wed, 1 Apr 2020 23:59:04 +0200 Subject: [PATCH 2/4] Format the code --- zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py | 14 ++++++++------ .../bots/bugzilla/fixtures/test_comment.json | 2 +- .../zulip_bots/bots/bugzilla/test_bugzilla.py | 5 ++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py b/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py index ab8630870..b6856a452 100644 --- a/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py +++ b/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.py @@ -23,6 +23,7 @@ Then `A new comment` is added to bug 123 ''' + class BugzillaHandler(object): ''' A docstring documenting this bot. @@ -51,7 +52,7 @@ def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None: content = message.get('content') topic = message.get('subject') - if HELP_REGEX.match (content): + if HELP_REGEX.match(content): self.handle_help(message, bot_handler) return None @@ -63,19 +64,19 @@ def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None: comment = content self.handle_comment(bug_number, comment, message, bot_handler) - + def handle_help(self, message: Dict[str, str], bot_handler: Any) -> None: bot_handler.send_reply(message, HELP_RESPONSE) def handle_comment(self, bug_number: str, comment: str, message: Dict[str, str], bot_handler: Any) -> None: url = '{}/rest/bug/{}/comment'.format(self.site, bug_number) - requests.post (url, - json=self.make_comment_json(comment)) + requests.post(url, + json=self.make_comment_json(comment)) def make_comment_json(self, comment: str) -> Any: json = { - 'api_key': self.api_key, - 'comment': comment + 'api_key': self.api_key, + 'comment': comment } return json @@ -87,4 +88,5 @@ def extract_bug_number(self, topic: str) -> Any: else: return topic_match.group('bug_number') + handler_class = BugzillaHandler diff --git a/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json b/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json index fe29e01ef..998ec7550 100644 --- a/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json +++ b/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json @@ -14,4 +14,4 @@ "status": 200, "content-type": "application/json; charset=utf-8" } -} +} \ No newline at end of file diff --git a/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py b/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py index d270edb9a..25892bf0c 100644 --- a/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py +++ b/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py @@ -2,6 +2,7 @@ from unittest.mock import patch from zulip_bots.test_lib import BotTestCase, DefaultTests + class TestBugzillaBot(BotTestCase, DefaultTests): bot_name = 'bugzilla' @@ -69,6 +70,7 @@ def test_help(self) -> None: with self.mock_config_info(self.MOCK_CONFIG_INFO): self.verify_reply('help', self.MOCK_HELP_RESPONSE) + class TestBugzillaBotWrongTopic(BotTestCase, DefaultTests): bot_name = 'bugzilla' @@ -89,4 +91,5 @@ def test_bot_responds_to_empty_message(self) -> None: def test_no_bug_number(self) -> None: with self.mock_config_info(self.MOCK_CONFIG_INFO): - self.verify_reply('a comment', self.MOCK_COMMENT_INVALID_TOPIC_RESPONSE) + self.verify_reply( + 'a comment', self.MOCK_COMMENT_INVALID_TOPIC_RESPONSE) From f3b36ea6333f340655b386e7510de2fe7f0d979c Mon Sep 17 00:00:00 2001 From: Nicolas Relange Date: Thu, 2 Apr 2020 00:04:05 +0200 Subject: [PATCH 3/4] Reset the configuration of the bugzilla bot and remove the unused import unittest.mock.patch --- zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf | 4 ++-- zulip_bots/zulip_bots/bots/bugzilla/doc.md | 5 ++--- .../zulip_bots/bots/bugzilla/fixtures/test_comment.json | 2 +- zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py | 1 - 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf b/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf index f3e8ac8e0..0dff8efcb 100644 --- a/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf +++ b/zulip_bots/zulip_bots/bots/bugzilla/bugzilla.conf @@ -1,3 +1,3 @@ [bugzilla] -site = http://bugs.lemoine.tech -api_key = 2BjH9yZxjhe2ZK81wT2loaOOJ3yUvgbgu4akTsXc +site = https://bugs.site.net +api_key = xxxx diff --git a/zulip_bots/zulip_bots/bots/bugzilla/doc.md b/zulip_bots/zulip_bots/bots/bugzilla/doc.md index 8b83d801d..1b9f08e2b 100644 --- a/zulip_bots/zulip_bots/bots/bugzilla/doc.md +++ b/zulip_bots/zulip_bots/bots/bugzilla/doc.md @@ -5,9 +5,8 @@ This bot allows to update directly Bugzilla from Zulip ## Setup To use Bugzilla Bot, first set up `bugzilla.conf`. `bugzilla.conf` takes 2 options: - - - site (the site like `https://bugs.xxx.net` that includes both the protocol and the domain), and - - api_key (a Bugzilla API key), + - site (the site like `https://bugs.xxx.net` that includes both the protocol and the domain) + - api_key (a Bugzilla API key) ## Usage diff --git a/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json b/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json index 998ec7550..fe29e01ef 100644 --- a/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json +++ b/zulip_bots/zulip_bots/bots/bugzilla/fixtures/test_comment.json @@ -14,4 +14,4 @@ "status": 200, "content-type": "application/json; charset=utf-8" } -} \ No newline at end of file +} diff --git a/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py b/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py index 25892bf0c..6cfb1d028 100644 --- a/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py +++ b/zulip_bots/zulip_bots/bots/bugzilla/test_bugzilla.py @@ -1,5 +1,4 @@ from typing import Any, Dict -from unittest.mock import patch from zulip_bots.test_lib import BotTestCase, DefaultTests From c562cfe86bcd8c319ee6b35944a9b291d8a54ce7 Mon Sep 17 00:00:00 2001 From: Nicolas Relange Date: Tue, 1 Sep 2020 08:51:59 +0200 Subject: [PATCH 4/4] Pull request #619, first review: ignore vs code settings, add an example of the configuration file in doc.md --- .gitignore | 4 ++++ zulip_bots/zulip_bots/bots/bugzilla/doc.md | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index 910ff9029..f88e6cf14 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,7 @@ botserverrc # Pycharm \.DS_Store \.idea/ + +# VS Code +.vscode/settings.json + diff --git a/zulip_bots/zulip_bots/bots/bugzilla/doc.md b/zulip_bots/zulip_bots/bots/bugzilla/doc.md index 1b9f08e2b..7a625c165 100644 --- a/zulip_bots/zulip_bots/bots/bugzilla/doc.md +++ b/zulip_bots/zulip_bots/bots/bugzilla/doc.md @@ -8,6 +8,13 @@ To use Bugzilla Bot, first set up `bugzilla.conf`. `bugzilla.conf` takes 2 optio - site (the site like `https://bugs.xxx.net` that includes both the protocol and the domain) - api_key (a Bugzilla API key) + Example: + ``` + [bugzilla] +site = https://bugs.site.net +api_key = xxxx + ``` + ## Usage