260 lines
9.2 KiB
Python
260 lines
9.2 KiB
Python
from zulip_bots.test_lib import BotTestCase, DefaultTests
|
|
|
|
class TestJiraBot(BotTestCase, DefaultTests):
|
|
bot_name = 'jira'
|
|
|
|
MOCK_CONFIG_INFO = {
|
|
'username': 'example@example.com',
|
|
'password': 'qwerty!123',
|
|
'domain': 'example.atlassian.net'
|
|
}
|
|
|
|
MOCK_SCHEME_CONFIG_INFO = {
|
|
'username': 'example@example.com',
|
|
'password': 'qwerty!123',
|
|
'domain': 'http://example.atlassian.net'
|
|
}
|
|
|
|
MOCK_DISPLAY_CONFIG_INFO = {
|
|
'username': 'example@example.com',
|
|
'password': 'qwerty!123',
|
|
'domain': 'example.atlassian.net',
|
|
'display_url': 'http://test.com'
|
|
}
|
|
|
|
MOCK_GET_RESPONSE = '''\
|
|
**Issue *[TEST-13](https://example.atlassian.net/browse/TEST-13)*: summary**
|
|
|
|
- Type: *Task*
|
|
- Description:
|
|
> description
|
|
- Creator: *admin*
|
|
- Project: *Tests*
|
|
- Priority: *Medium*
|
|
- Status: *To Do*
|
|
'''
|
|
|
|
MOCK_CREATE_RESPONSE = 'Issue *TEST-16* is up! https://example.atlassian.net/browse/TEST-16'
|
|
|
|
MOCK_EDIT_RESPONSE = 'Issue *TEST-16* was edited! https://example.atlassian.net/browse/TEST-16'
|
|
|
|
MOCK_NOTHING_RESPONSE = 'Sorry, I don\'t understand that! Send me `help` for instructions.'
|
|
|
|
MOCK_HELP_RESPONSE = '''
|
|
**get**
|
|
|
|
`get` takes in an issue key and sends back information about that issue. For example,
|
|
|
|
you:
|
|
|
|
> @**Jira Bot** get "BOTS-13"
|
|
|
|
Jira Bot:
|
|
|
|
> **Issue *BOTS-13*: Create Jira Bot**
|
|
>
|
|
> - Type: *Task*
|
|
> - Description:
|
|
> > Jira Bot would connect to Jira.
|
|
> - Creator: *admin*
|
|
> - Project: *Bots*
|
|
> - Priority: *Medium*
|
|
> - Status: *To Do*
|
|
|
|
---
|
|
|
|
**search**
|
|
|
|
`search` takes in a search term and returns issues with matching summaries. For example,
|
|
|
|
you:
|
|
|
|
> @**Jira Bot** search "XSS"
|
|
|
|
Jira Bot:
|
|
|
|
> **Search results for *"XSS"*:**
|
|
>
|
|
> - ***BOTS-5:*** Stored XSS **[Published]**
|
|
> - ***BOTS-6:*** Reflected XSS **[Draft]**
|
|
|
|
---
|
|
|
|
**jql**
|
|
|
|
`jql` takes in a jql search string and returns matching issues. For example,
|
|
|
|
you:
|
|
|
|
> @**Jira Bot** jql "issuetype = Engagement ORDER BY created DESC"
|
|
|
|
Jira Bot:
|
|
|
|
> **Search results for *"issuetype = Engagement ORDER BY created DESC"*:**
|
|
>
|
|
> - ***BOTS-1:*** External Website Test **[In Progress]**
|
|
> - ***BOTS-3:*** Network Vulnerability Scan **[Draft]**
|
|
|
|
---
|
|
|
|
**create**
|
|
|
|
`create` creates an issue using its
|
|
|
|
- summary,
|
|
- project,
|
|
- type,
|
|
- description *(optional)*,
|
|
- assignee *(optional)*,
|
|
- priority *(optional)*,
|
|
- labels *(optional)*, and
|
|
- due date *(optional)*
|
|
|
|
For example, to create an issue with every option,
|
|
|
|
you:
|
|
|
|
> @**Jira Bot** create issue "Make an issue" in project "BOTS"' with type \
|
|
"Task" with description "This is a description" assigned to "skunkmb" with \
|
|
priority "Medium" labeled "issues, testing" due "2017-01-23"
|
|
|
|
Jira Bot:
|
|
|
|
> Issue *BOTS-16* is up! https://example.atlassian.net/browse/BOTS-16
|
|
|
|
---
|
|
|
|
**edit**
|
|
|
|
`edit` is like create, but changes an existing issue using its
|
|
|
|
- summary,
|
|
- project *(optional)*,
|
|
- type *(optional)*,
|
|
- description *(optional)*,
|
|
- assignee *(optional)*,
|
|
- priority *(optional)*,
|
|
- labels *(optional)*, and
|
|
- due date *(optional)*.
|
|
|
|
For example, to change every part of an issue,
|
|
|
|
you:
|
|
|
|
> @**Jira Bot** edit issue "BOTS-16" to use summary "Change the summary" \
|
|
to use project "NEWBOTS" to use type "Bug" to use description "This is \
|
|
a new description" by assigning to "admin" to use priority "Low" by \
|
|
labeling "new, labels" by making due "2018-12-5"
|
|
|
|
Jira Bot:
|
|
|
|
> Issue *BOTS-16* was edited! https://example.atlassian.net/browse/BOTS-16
|
|
'''
|
|
|
|
MOCK_SEARCH_RESPONSE = '**Search results for "TEST"**\n\n*Found 2 results*\n\n\n - TEST-1: [summary test 1](https://example.atlassian.net/browse/TEST-1) **[To Do]**\n - TEST-2: [summary test 2](https://example.atlassian.net/browse/TEST-2) **[To Do]**'
|
|
MOCK_SEARCH_RESPONSE_URL = '**Search results for "TEST"**\n\n*Found 2 results*\n\n\n - TEST-1: [summary test 1](http://test.com/browse/TEST-1) **[To Do]**\n - TEST-2: [summary test 2](http://test.com/browse/TEST-2) **[To Do]**'
|
|
MOCK_SEARCH_RESPONSE_SCHEME = '**Search results for "TEST"**\n\n*Found 2 results*\n\n\n - TEST-1: [summary test 1](http://example.atlassian.net/browse/TEST-1) **[To Do]**\n - TEST-2: [summary test 2](http://example.atlassian.net/browse/TEST-2) **[To Do]**'
|
|
MOCK_JQL_RESPONSE = '**Search results for "summary ~ TEST"**\n\n*Found 2 results*\n\n\n - TEST-1: [summary test 1](https://example.atlassian.net/browse/TEST-1) **[To Do]**\n - TEST-2: [summary test 2](https://example.atlassian.net/browse/TEST-2) **[To Do]**'
|
|
|
|
def _test_invalid_config(self, invalid_config, error_message) -> None:
|
|
with self.mock_config_info(invalid_config), \
|
|
self.assertRaisesRegex(KeyError, error_message):
|
|
bot, bot_handler = self._get_handlers()
|
|
|
|
def test_config_without_username(self) -> None:
|
|
config_without_username = {
|
|
'password': 'qwerty!123',
|
|
'domain': 'example.atlassian.net',
|
|
}
|
|
self._test_invalid_config(config_without_username,
|
|
'No `username` was specified')
|
|
|
|
def test_config_without_password(self) -> None:
|
|
config_without_password = {
|
|
'username': 'example@example.com',
|
|
'domain': 'example.atlassian.net',
|
|
}
|
|
self._test_invalid_config(config_without_password,
|
|
'No `password` was specified')
|
|
|
|
def test_config_without_domain(self) -> None:
|
|
config_without_domain = {
|
|
'username': 'example@example.com',
|
|
'password': 'qwerty!123',
|
|
}
|
|
self._test_invalid_config(config_without_domain,
|
|
'No `domain` was specified')
|
|
|
|
def test_get(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_get'):
|
|
self.verify_reply('get "TEST-13"', self.MOCK_GET_RESPONSE)
|
|
|
|
def test_get_error(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_get_error'):
|
|
self.verify_reply('get "TEST-13"',
|
|
'Oh no! Jira raised an error:\n > error1')
|
|
|
|
def test_create(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_create'):
|
|
self.verify_reply('create issue "Testing" in project "TEST" with type "Task"',
|
|
self.MOCK_CREATE_RESPONSE)
|
|
|
|
def test_create_error(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_create_error'):
|
|
self.verify_reply('create issue "Testing" in project "TEST" with type "Task" '
|
|
'with description "This is a test description" assigned to "testuser" '
|
|
'with priority "Medium" labeled "issues, testing" due "2018-06-11"',
|
|
'Oh no! Jira raised an error:\n > error1')
|
|
|
|
def test_edit(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_edit'):
|
|
self.verify_reply('edit issue "TEST-16" to use description "description"',
|
|
self.MOCK_EDIT_RESPONSE)
|
|
|
|
def test_edit_error(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_edit_error'):
|
|
self.verify_reply('edit issue "TEST-13" to use summary "Change the summary" '
|
|
'to use project "TEST" to use type "Bug" to use description "This is a test description" '
|
|
'by assigning to "testuser" to use priority "Low" by labeling "issues, testing" '
|
|
'by making due "2018-06-11"',
|
|
'Oh no! Jira raised an error:\n > error1')
|
|
|
|
def test_search(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_search'):
|
|
self.verify_reply('search "TEST"', self.MOCK_SEARCH_RESPONSE)
|
|
|
|
def test_jql(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_search'):
|
|
self.verify_reply('jql "summary ~ TEST"', self.MOCK_JQL_RESPONSE)
|
|
|
|
def test_search_url(self) -> None:
|
|
with self.mock_config_info(self.MOCK_DISPLAY_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_search'):
|
|
self.verify_reply('search "TEST"', self.MOCK_SEARCH_RESPONSE_URL)
|
|
|
|
def test_search_scheme(self) -> None:
|
|
with self.mock_config_info(self.MOCK_SCHEME_CONFIG_INFO), \
|
|
self.mock_http_conversation('test_search_scheme'):
|
|
self.verify_reply('search "TEST"', self.MOCK_SEARCH_RESPONSE_SCHEME)
|
|
|
|
def test_help(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO):
|
|
self.verify_reply('help', self.MOCK_HELP_RESPONSE)
|
|
|
|
# This overrides the default one in `BotTestCase`.
|
|
def test_bot_responds_to_empty_message(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO):
|
|
self.verify_reply('', self.MOCK_NOTHING_RESPONSE)
|
|
|
|
def test_no_command(self) -> None:
|
|
with self.mock_config_info(self.MOCK_CONFIG_INFO):
|
|
self.verify_reply('qwertyuiop', self.MOCK_NOTHING_RESPONSE)
|