diff --git a/CHANGELOG.md b/CHANGELOG.md index 31731bb..83c6d96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## v2.1.0 (2020-12-03) + +* Adds Discord support. Now you can send Pullbug messages to a Discord webhook (closes #17) +* Completely rewrote the message module. Messages are now an array of messages built from PR/MR data. This allows messages to be broken up easily into batches for chat services such as Discord which may require multiple batches of messages due to character limit +* The repo name is now included in each message with a link to the repo (closes #16) +* Reworked tests to be more clean and uniform +* Various bug fixes and code refactor for better performance and maintainability + ## v2.0.6 (2020-10-10) * Fixing typo in gitlab message diff --git a/Makefile b/Makefile index 7c31b25..7a356cb 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ clean: ## lint - Lint the project lint: venv/bin/flake8 pullbug/*.py - venv/bin/flake8 test/*.py + venv/bin/flake8 test/unit/*.py ## test - Test the project test: diff --git a/README.md b/README.md index 40d89dc..6917b93 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Pullbug 🐛 -Get bugged via Slack or RocketChat to merge your GitHub pull requests or GitLab merge requests. +Get bugged via Discord, Slack, or RocketChat to merge your GitHub pull requests or GitLab merge requests. [![Build Status](https://travis-ci.com/Justintime50/pullbug.svg?branch=master)](https://travis-ci.com/Justintime50/pullbug) [![Coverage Status](https://coveralls.io/repos/github/Justintime50/pullbug/badge.svg?branch=master)](https://coveralls.io/github/Justintime50/pullbug?branch=master) @@ -13,9 +13,9 @@ Get bugged via Slack or RocketChat to merge your GitHub pull requests or GitLab -Pullbug can notify you on Slack or Rocket.Chat of all open pull and merge requests from GitHub or GitLab. This tool ensures requests never go unnoticed as it can be setup on a schedule to constantly bug you to merge your work. This is perfect for finding old or stale requests and helps you to stay current on new ones. Pass in a few environment variables, setup a [Slackbot](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace) or [Rocket.Chat](https://rocket.chat/docs/developer-guides/rest-api/integration/create/) integration and you're all set to be bugged by Pullbug. +Pullbug can notify you on Discord, Slack, or Rocket.Chat of all open pull and merge requests from GitHub or GitLab. This tool ensures requests never go unnoticed as it can be setup on a schedule to constantly bug you to merge your work. This is perfect for finding old or stale requests and helps you to stay current on new ones. Pass in a few environment variables, setup a [Slackbot](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace) or [Rocket.Chat](https://rocket.chat/docs/developer-guides/rest-api/integration/create/) integration and you're all set to be bugged by Pullbug. -**NOTE:** Pullbug works best if you have link unfurling turned off for GitHub and GitLab on Slack or Rocket.Chat. +**NOTE:** Pullbug works best if you have link unfurling turned off for GitHub and GitLab on Discord, Slack, or Rocket.Chat. **GitLab Users:** If you are not hosting your own GitLab instance and are instead using `gitlab.com`, it's recommended to change the scope to `owner` and provide an owner who has access to all your organizations merge requests. @@ -34,7 +34,7 @@ make help ## Usage -Pullbug works best when run on a schedule. Run one-off reports or setup Pullbug to notify you at whatever interval you'd like to be bugged via Slack or Rocket.Chat about pull or merge requests. +Pullbug works best when run on a schedule. Run one-off reports or setup Pullbug to notify you at whatever interval you'd like to be bugged via Discord, Slack, or Rocket.Chat about pull or merge requests. Pullbug is highly customizable allowing you to mix and match version control software along with messaging platforms to get the right fit. Additionally choose which kinds of pull or merge requests to retrieve. @@ -46,6 +46,7 @@ Options: -h, --help show this help message and exit -gh, --github Get bugged about pull requests from GitHub. -gl, --gitlab Get bugged about merge requests from GitLab. + -d, --discord Send Pullbug messages to Discord. -s, --slack Send Pullbug messages to Slack. -rc, --rocketchat Send Pullbug messages to Rocket.Chat. -w, --wip Include "Work in Progress" pull or merge requests. @@ -64,6 +65,7 @@ Environment Variables: GITHUB_TOKEN The GitHub Token used to authenticate with the GitHub API. GITLAB_API_KEY The GitLab API Key used to authenticate with the GitLab API. GITLAB_API_URL The GitLab API url for your GitLab instance. Default: https://gitlab.com/api/v4. + DISCORD_WEBHOOK_URL The Discord webhook url to send a message to. Will look like: https://discord.com/api/webhooks/channel_id/webhook_id SLACK_BOT_TOKEN The Slackbot Token used to authenticate with Slack. SLACK_CHANNEL The Slack channel to post a message to. ROCKET_CHAT_URL The Rocket.Chat url of the room to post a message to. diff --git a/pullbug/cli.py b/pullbug/cli.py index ccc1fbd..6fa577e 100644 --- a/pullbug/cli.py +++ b/pullbug/cli.py @@ -10,6 +10,7 @@ GITHUB_TOKEN = os.getenv('GITHUB_TOKEN') GITLAB_API_KEY = os.getenv('GITLAB_API_KEY') GITLAB_API_URL = os.getenv('GITLAB_API_URL', 'https://gitlab.com/api/v4') +DISCORD_WEBHOOK_URL = os.getenv('DISCORD_WEBHOOK_URL') SLACK_BOT_TOKEN = os.getenv('SLACK_BOT_TOKEN') SLACK_CHANNEL = os.getenv('SLACK_CHANNEL') ROCKET_CHAT_URL = os.getenv('ROCKET_CHAT_URL') @@ -40,6 +41,14 @@ def __init__(self): default=False, help='Get bugged about merge requests from GitLab.' ) + parser.add_argument( + '-d', + '--discord', + required=False, + action='store_true', + default=False, + help='Send Pullbug messages to Discord.' + ) parser.add_argument( '-s', '--slack', @@ -112,6 +121,7 @@ def run(self): PullBug.run( github=self.github, gitlab=self.gitlab, + discord=self.discord, slack=self.slack, rocketchat=self.rocketchat, wip=self.wip, @@ -125,22 +135,22 @@ def run(self): class PullBug(): @classmethod - def run(cls, github, gitlab, slack, rocketchat, wip, github_owner, + def run(cls, github, gitlab, discord, slack, rocketchat, wip, github_owner, github_state, github_context, gitlab_state, gitlab_scope): """Run Pullbug based on the configuration. """ PullBugLogger._setup_logging(LOGGER) LOGGER.info('Running Pullbug...') load_dotenv() - cls.run_missing_checks(github, gitlab, slack, rocketchat) + cls.run_missing_checks(github, gitlab, discord, slack, rocketchat) if github: - GithubBug.run(github_owner, github_state, github_context, wip, slack, rocketchat) + GithubBug.run(github_owner, github_state, github_context, wip, discord, slack, rocketchat) if gitlab: - GitlabBug.run(gitlab_scope, gitlab_state, wip, slack, rocketchat) + GitlabBug.run(gitlab_scope, gitlab_state, wip, discord, slack, rocketchat) LOGGER.info('Pullbug finished bugging!') @classmethod - def run_missing_checks(cls, github, gitlab, slack, rocketchat): + def run_missing_checks(cls, github, gitlab, discord, slack, rocketchat): """Check that values are set based on configuration before proceeding. """ @@ -152,6 +162,8 @@ def run_missing_checks(cls, github, gitlab, slack, rocketchat): cls.throw_missing_error('GITHUB_TOKEN') if gitlab and not GITLAB_API_KEY: cls.throw_missing_error('GITLAB_API_KEY') + if discord and not DISCORD_WEBHOOK_URL: + cls.throw_missing_error('DISCORD_WEBHOOK_URL') if slack and not SLACK_BOT_TOKEN: cls.throw_missing_error('SLACK_BOT_TOKEN') if slack and not SLACK_CHANNEL: diff --git a/pullbug/github_bug.py b/pullbug/github_bug.py index 4a058d5..01f8018 100644 --- a/pullbug/github_bug.py +++ b/pullbug/github_bug.py @@ -15,26 +15,30 @@ class GithubBug(): @classmethod - def run(cls, github_owner, github_state, github_context, wip, slack, rocketchat): + def run(cls, github_owner, github_state, github_context, wip, discord, slack, rocketchat): """Run the logic to get PR's from GitHub and send that data via message. """ PullBugLogger._setup_logging(LOGGER) repos = cls.get_repos(github_owner, github_context) pull_requests = cls.get_pull_requests(repos, github_owner, github_state) - message_preamble = '' + if pull_requests == []: message = 'No pull requests are available from GitHub.' LOGGER.info(message) return message + message_preamble = '\n:bug: *The following pull requests on GitHub are still open and need your help!*\n' - pull_request_messages = cls.iterate_pull_requests(pull_requests, wip) - final_message = message_preamble + pull_request_messages + messages, discord_messages = cls.iterate_pull_requests(pull_requests, wip, discord, slack, rocketchat) + messages.insert(0, message_preamble) + discord_messages.insert(0, message_preamble) + if discord: + Messages.send_discord_message(discord_messages) if slack: - Messages.slack(final_message) + Messages.send_slack_message(messages) if rocketchat: - Messages.rocketchat(final_message) - LOGGER.info(final_message) + Messages.send_rocketchat_message(messages) + LOGGER.info(messages) @classmethod def get_repos(cls, github_owner, github_context=''): @@ -42,22 +46,22 @@ def get_repos(cls, github_owner, github_context=''): """ LOGGER.info('Bugging GitHub for repos...') try: - repos_response = requests.get( + response = requests.get( f'https://api.github.com/{github_context}/{github_owner}/repos?per_page=100', headers=GITHUB_HEADERS ) - LOGGER.debug(repos_response.text) - if 'Not Found' in repos_response.text: + LOGGER.debug(response.text) + LOGGER.info('GitHub repos retrieved!') + if 'Not Found' in response.text: error = f'Could not retrieve GitHub repos due to bad parameter: {github_owner} | {github_context}.' LOGGER.error(error) raise ValueError(error) - LOGGER.info('GitHub repos retrieved!') except requests.exceptions.RequestException as response_error: LOGGER.error( f'Could not retrieve GitHub repos: {response_error}' ) raise requests.exceptions.RequestException(response_error) - return repos_response.json() + return response.json() @classmethod def get_pull_requests(cls, repos, github_owner, github_state): @@ -90,39 +94,17 @@ def get_pull_requests(cls, repos, github_owner, github_state): return pull_requests @classmethod - def iterate_pull_requests(cls, pull_requests, wip): + def iterate_pull_requests(cls, pull_requests, wip, discord, slack, rocketchat): """Iterate through each pull request of a repo - and send a message to Slack if a PR exists. + and build the message array. """ - final_message = '' + message_array = [] + discord_message_array = [] for pull_request in pull_requests: if not wip and 'WIP' in pull_request['title'].upper(): continue else: - message = cls.prepare_message(pull_request) - final_message += message - return final_message - - @classmethod - def prepare_message(cls, pull_request): - """Prepare the message with pull request data. - """ - # TODO: Check requested_reviewers array also - try: - if pull_request['assignees'][0]['login']: - users = '' - for assignee in pull_request['assignees']: - user = f"<{assignee['html_url']}|{assignee['login']}>" - users += user + ' ' - else: - users = 'No assignee' - except IndexError: - users = 'No assignee' - - # Truncate description after 120 characters - description = (pull_request['body'][:120] + '...') if len(pull_request - ['body']) > 120 else pull_request['body'] - message = f"\n:arrow_heading_up: *Pull Request:* <{pull_request['html_url']}|" + \ - f"{pull_request['title']}>\n*Description:* {description}\n*Waiting on:* {users}\n" - - return message + message, discord_message = Messages.prepare_github_message(pull_request, discord, slack, rocketchat) + message_array.append(message) + discord_message_array.append(discord_message) + return message_array, discord_message_array diff --git a/pullbug/gitlab_bug.py b/pullbug/gitlab_bug.py index 6322f24..60afc7e 100644 --- a/pullbug/gitlab_bug.py +++ b/pullbug/gitlab_bug.py @@ -16,25 +16,29 @@ class GitlabBug(): @classmethod - def run(cls, gitlab_scope, gitlab_state, wip, slack, rocketchat): + def run(cls, gitlab_scope, gitlab_state, wip, discord, slack, rocketchat): """Run the logic to get MR's from GitLab and send that data via message. """ PullBugLogger._setup_logging(LOGGER) merge_requests = cls.get_merge_requests(gitlab_scope, gitlab_state) - message_preamble = '' + if merge_requests == []: message = 'No merge requests are available from GitLab.' LOGGER.info(message) return message + message_preamble = '\n:bug: *The following merge requests on GitLab are still open and need your help!*\n' - merge_request_messages = cls.iterate_merge_requests(merge_requests, wip) - final_message = message_preamble + merge_request_messages + messages, discord_messages = cls.iterate_merge_requests(merge_requests, wip, discord, slack, rocketchat) + messages.insert(0, message_preamble) + discord_messages.insert(0, message_preamble) + if discord: + Messages.send_discord_message(discord_messages) if slack: - Messages.slack(final_message) + Messages.send_slack_message(messages) if rocketchat: - Messages.rocketchat(final_message) - LOGGER.info(final_message) + Messages.send_rocketchat_message(messages) + LOGGER.info(messages) @classmethod def get_merge_requests(cls, gitlab_scope, gitlab_state): @@ -46,7 +50,7 @@ def get_merge_requests(cls, gitlab_scope, gitlab_state): f"{GITLAB_API_URL}/merge_requests?scope={gitlab_scope}&state={gitlab_state}&per_page=100", headers=GITLAB_HEADERS ) - print(response.json()) + LOGGER.debug(response.text) LOGGER.info('GitLab merge requests retrieved!') if 'does not have a valid value' in response.text: error = f'Could not retrieve GitLab merge requests due to bad parameter: {gitlab_scope} | {gitlab_state}.' # noqa @@ -60,40 +64,19 @@ def get_merge_requests(cls, gitlab_scope, gitlab_state): return response.json() @classmethod - def iterate_merge_requests(cls, merge_requests, wip): - """Iterate through each merge request and send - a message to Slack if a PR exists. + def iterate_merge_requests(cls, merge_requests, wip, discord, slack, rocketchat): + """Iterate through each merge request of a repo + and build the message array. """ - final_message = '' + message_array = [] + discord_message_array = [] for merge_request in merge_requests: # TODO: There is a "work_in_progress" key in the response # that could be used? https://docs.gitlab.com/ee/api/merge_requests.html if not wip and 'WIP' in merge_request['title'].upper(): continue else: - message = cls.prepare_message(merge_request) - final_message += message - return final_message - - @classmethod - def prepare_message(cls, merge_request): - """Prepare the message with merge request data. - """ - try: - if merge_request['assignees'][0]['username']: - users = '' - for assignee in merge_request['assignees']: - user = f"<{assignee['web_url']}|{assignee['username']}>" - users += user + ' ' - else: - users = 'No assignee' - except IndexError: - users = 'No assignee' - - # Truncate description after 120 characters - description = (merge_request['description'][:120] + - '...') if len(merge_request['description']) > 120 else merge_request['description'] - message = f"\n:arrow_heading_up: *Merge Request:* <{merge_request['web_url']}|" + \ - f"{merge_request['title']}>\n*Description:* {description}\n*Waiting on:* {users}\n" - - return message + message, discord_message = Messages.prepare_gitlab_message(merge_request, discord, slack, rocketchat) + message_array.append(message) + discord_message_array.append(discord_message) + return message_array, discord_message_array diff --git a/pullbug/messages.py b/pullbug/messages.py index 2129a15..65984f9 100644 --- a/pullbug/messages.py +++ b/pullbug/messages.py @@ -1,36 +1,73 @@ +import re +import math import os import logging import requests import slack +DISCORD_WEBHOOK_URL = os.getenv('DISCORD_WEBHOOK_URL') +ROCKET_CHAT_URL = os.getenv('ROCKET_CHAT_URL') SLACK_BOT_TOKEN = os.getenv('SLACK_BOT_TOKEN') SLACK_CHANNEL = os.getenv('SLACK_CHANNEL') -ROCKET_CHAT_URL = os.getenv('ROCKET_CHAT_URL') +GITLAB_API_URL = os.getenv('GITLAB_API_URL', 'https://gitlab.com/api/v4') LOGGER = logging.getLogger(__name__) class Messages(): @classmethod - def rocketchat(cls, message): + def send_discord_message(cls, message): + """Send a Discord message. + + Discord has a hard limit of 2000 characters per message, + as such, we break up the messages into batches, allow for + breathing room, and send each batch of messages separately. + """ + num_of_messages = len(message) + max_per_batch = 6 + i = 1 + new_cutoff = max_per_batch + old_cutoff = 0 + while i <= math.ceil(num_of_messages / max_per_batch): + i += 1 + batch_message = ''.join(message[old_cutoff:new_cutoff]) + new_cutoff += max_per_batch + old_cutoff += max_per_batch + try: + requests.post(DISCORD_WEBHOOK_URL, json={'content': batch_message}) + LOGGER.info('Discord message sent!') + except requests.exceptions.RequestException as discord_error: + LOGGER.error(f'Could not send Discord message: {discord_error}') + raise requests.exceptions.RequestException(discord_error) + + @ classmethod + def send_rocketchat_message(cls, message): """Send a Rocket Chat message + + We truncate the message at 40,000 characters to match Slack + and improve performance. RC doesn't specify a char limit. """ + rocketchat_message = ''.join(message)[:40000] try: - requests.post(ROCKET_CHAT_URL, data={'text': message}) + requests.post(ROCKET_CHAT_URL, json={'text': rocketchat_message}) LOGGER.info('Rocket Chat message sent!') except requests.exceptions.RequestException as rc_error: LOGGER.error(f'Could not send Rocket Chat message: {rc_error}') raise requests.exceptions.RequestException(rc_error) - @classmethod - def slack(cls, message): - """Send Slack messages via a bot + @ classmethod + def send_slack_message(cls, message): + """Send Slack messages via a bot. + + Slack truncates messages after 40,000 characters so + we truncate there before sending the request. """ + slack_message = ''.join(message)[:40000] slack_client = slack.WebClient(SLACK_BOT_TOKEN) try: slack_client.chat_postMessage( channel=SLACK_CHANNEL, - text=message + text=slack_message ) LOGGER.info('Slack message sent!') except slack.errors.SlackApiError as slack_error: @@ -38,3 +75,78 @@ def slack(cls, message): raise slack.errors.SlackApiError( slack_error.response["ok"], slack_error.response['error'] ) + + @ classmethod + def prepare_github_message(cls, pull_request, discord, slack, rocketchat): + """Prepares a GitHub message with a single pull request's data. + This will then be appended to an array of messages. + + Slack & RocketChat can use the same format while Discord requires + some tweaking. + """ + # TODO: Check requested_reviewers array also + description_max_length = 120 + users = '' + discord_users = '' + try: + if pull_request['assignees'][0]['login']: + for assignee in pull_request['assignees']: + discord_user = f"{assignee['login']} (<{assignee['html_url']}>)" + user = f"<{assignee['html_url']}|{assignee['login']}>" + users += user + ' ' + discord_users += discord_user + ' ' + except IndexError: + pass + + description = (pull_request.get('body')[:description_max_length] + + '...') if len(pull_request['body']) > description_max_length else pull_request.get('body') + message = ( + f"\n:arrow_heading_up: *Pull Request:* <{pull_request['html_url']}|{pull_request['title']}>" + f"\n*Repo:* <{pull_request['base']['repo']['html_url']}|{pull_request['base']['repo']['name']}>" + f"\n*Description:* {description}\n*Waiting on:* {users}\n" + ) + discord_message = ( + f"\n:arrow_heading_up: **Pull Request:** {pull_request['title']} (<{pull_request['html_url']}>)" + f"\n**Repo:** {pull_request['base']['repo']['name']} (<{pull_request['base']['repo']['html_url']}>)" + f"\n**Description:** {description}\n**Waiting on:** {discord_users}\n" + ) + + return message, discord_message + + @ classmethod + def prepare_gitlab_message(cls, merge_request, discord, slack, rocketchat): + """Prepare a GitLab message with a single merge request's data. + This will then be appended to an array of messages. + + Slack & RocketChat can use the same format while Discord requires + some tweaking. + """ + description_max_length = 120 + users = '' + discord_users = '' + re_repo_name = re.compile('(?P[\\w-]+)/(?P[\\w-]+)!(?P\\d+)') + re_match = re_repo_name.search(merge_request['references']['full']) + try: + if merge_request['assignees'][0]['username']: + for assignee in merge_request['assignees']: + user = f"<{assignee['web_url']}|{assignee['username']}>" + discord_user = f"{assignee['username']} (<{assignee['web_url']}>)" + users += user + ' ' + discord_users += discord_user + ' ' + except IndexError: + pass + + description = (merge_request.get('description')[:description_max_length] + + '...') if len(merge_request['description']) > description_max_length else merge_request.get('description') # noqa + message = ( + f"\n:arrow_heading_up: *Merge Request:* <{merge_request['web_url']}|{merge_request['title']}>" + f"\n*Repo:* <{GITLAB_API_URL}/{re_match['group_name']}/{re_match['repo_name']}|{re_match['repo_name']}>" + f"\n*Description:* {description}\n*Waiting on:* {users}\n" + ) + discord_message = ( + f"\n:arrow_heading_up: **Pull Request:** {merge_request['title']} (<{merge_request['web_url']}>)" + f"\n**Repo:** {re_match['repo_name']} (<{GITLAB_API_URL}/{re_match['group_name']}/{re_match['repo_name']}>)" + f"\n**Description:** {description}\n**Waiting on:** {discord_users}\n" + ) + + return message, discord_message diff --git a/setup.py b/setup.py index 956be73..903a113 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,8 @@ setuptools.setup( name='pullbug', - version='2.0.6', - description='Get bugged via Slack or RocketChat to merge your GitHub pull requests or GitLab merge requests.', # noqa + version='2.1.0', + description='Get bugged via Discord, Slack, or RocketChat to merge your GitHub pull requests or GitLab merge requests.', # noqa long_description=long_description, long_description_content_type="text/markdown", url='http://github.com/justintime50/pull-bug', diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..98bae2d --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,86 @@ +import pytest + + +@pytest.fixture +def _mock_github_token(scope='module'): + return '123' + + +@pytest.fixture +def _mock_user(): + return 'mock-user' + + +@pytest.fixture +def _mock_github_context(): + return 'users' + + +@pytest.fixture +def _mock_github_state(): + return 'open' + + +@pytest.fixture +def _mock_repo(): + return { + 'name': 'mock-repo' + } + + +@pytest.fixture +def _mock_pull_request(_mock_user, _mock_repo): + return { + 'title': 'mock-pull-request', + 'description': 'Mock description', + 'assignees': [ + { + 'login': _mock_user, + 'html_url': f'https://github.com/{_mock_user}' + }, + ], + 'body': 'Mock body of a pull request.', + 'html_url': f'https://github.com/{_mock_user}/{_mock_repo}/pull/1', + 'base': { + 'repo': { + 'name': 'mock-repo', + 'html_url': f'https://github.com/{_mock_user}/{_mock_repo}' + } + } + } + + +@pytest.fixture +def _mock_url(): + return 'http://mock-url.com' + + +@pytest.fixture +def _mock_gitlab_scope(): + return 'all' + + +@pytest.fixture +def _mock_gitlab_state(): + return 'opened' + + +@pytest.fixture +def _mock_merge_request(_mock_user, _mock_repo, _mock_url): + return { + 'title': 'mock-pull-request', + 'description': 'Mock description', + 'assignees': [ + { + 'username': _mock_user, + 'web_url': f'https://github.com/{_mock_user}' + } + ], + 'body': 'Mock body of a pull request.', + 'web_url': f'{_mock_url}/{_mock_user}/{_mock_repo}/pull/1', + 'references': { + 'short': '!1', + 'relative': '!1', + 'full': 'my-group/my-project!1' + }, + } diff --git a/test/unit/test_cli.py b/test/unit/test_cli.py index 9765de0..9c8b360 100644 --- a/test/unit/test_cli.py +++ b/test/unit/test_cli.py @@ -15,7 +15,7 @@ def test_throw_missing_error(mock_logger): @mock.patch('pullbug.cli.LOGGER') def test_run_missing_checks_no_github_or_gitlab(mock_logger): with pytest.raises(ValueError): - PullBug.run_missing_checks(False, False, False, False) + PullBug.run_missing_checks(False, False, False, False, False) mock_logger.critical.assert_called_once_with( 'Neither "github" nor "gitlab" flags were passed, one is required. Please correct and try again.' ) @@ -26,8 +26,8 @@ def test_run_missing_checks_no_github_or_gitlab(mock_logger): @mock.patch('pullbug.github_bug.GithubBug.run') @mock.patch('pullbug.cli.LOGGER') def test_run_with_github_arg(mock_logger, mock_github, mock_missing_checks): - PullBug.run(True, False, False, False, False, 'mock-owner', 'open', 'orgs', 'opened', 'all') - mock_missing_checks.assert_called_once_with(True, False, False, False) + PullBug.run(True, False, False, False, False, False, 'mock-owner', 'open', 'orgs', 'opened', 'all') + mock_missing_checks.assert_called_once_with(True, False, False, False, False) mock_github.assert_called_once() mock_logger.info.assert_called() @@ -37,17 +37,26 @@ def test_run_with_github_arg(mock_logger, mock_github, mock_missing_checks): @mock.patch('pullbug.gitlab_bug.GitlabBug.run') @mock.patch('pullbug.cli.LOGGER') def test_run_with_gitlab_arg(mock_logger, mock_gitlab, mock_missing_checks): - PullBug.run(False, True, False, False, False, 'mock-owner', 'open', 'orgs', 'opened', 'all') - mock_missing_checks.assert_called_once_with(False, True, False, False) + PullBug.run(False, True, False, False, False, False, 'mock-owner', 'open', 'orgs', 'opened', 'all') + mock_missing_checks.assert_called_once_with(False, True, False, False, False) mock_gitlab.assert_called_once() mock_logger.info.assert_called() +@mock.patch('pullbug.cli.GITHUB_TOKEN', '123') +@mock.patch('pullbug.cli.LOGGER') +def test_run_missing_checks_no_discord_webhook_url(mock_logger): + message = 'No DISCORD_WEBHOOK_URL set. Please correct and try again.' + with pytest.raises(ValueError): + PullBug.run_missing_checks(True, False, True, False, False) + mock_logger.critical.assert_called_once_with(message) + + @mock.patch('pullbug.cli.LOGGER') def test_run_missing_checks_no_github_token(mock_logger): message = 'No GITHUB_TOKEN set. Please correct and try again.' with pytest.raises(ValueError): - PullBug.run_missing_checks(True, False, False, False) + PullBug.run_missing_checks(True, False, False, False, False) mock_logger.critical.assert_called_once_with(message) @@ -55,7 +64,7 @@ def test_run_missing_checks_no_github_token(mock_logger): def test_run_missing_checks_no_gitlab_api_key(mock_logger): message = 'No GITLAB_API_KEY set. Please correct and try again.' with pytest.raises(ValueError): - PullBug.run_missing_checks(False, True, False, False) + PullBug.run_missing_checks(False, True, False, False, False) mock_logger.critical.assert_called_once_with(message) @@ -64,7 +73,7 @@ def test_run_missing_checks_no_gitlab_api_key(mock_logger): def test_run_missing_checks_no_slack_bot_token(mock_logger): message = 'No SLACK_BOT_TOKEN set. Please correct and try again.' with pytest.raises(ValueError): - PullBug.run_missing_checks(True, False, True, False) + PullBug.run_missing_checks(True, False, False, True, False) mock_logger.critical.assert_called_once_with(message) @@ -74,7 +83,7 @@ def test_run_missing_checks_no_slack_bot_token(mock_logger): def test_run_missing_checks_no_slack_channel(mock_logger): message = 'No SLACK_CHANNEL set. Please correct and try again.' with pytest.raises(ValueError): - PullBug.run_missing_checks(True, False, True, False) + PullBug.run_missing_checks(True, False, False, True, False) mock_logger.critical.assert_called_once_with(message) @@ -83,7 +92,7 @@ def test_run_missing_checks_no_slack_channel(mock_logger): def test_run_missing_checks_no_rocket_chat_url(mock_logger): message = 'No ROCKET_CHAT_URL set. Please correct and try again.' with pytest.raises(ValueError): - PullBug.run_missing_checks(True, False, False, True) + PullBug.run_missing_checks(True, False, False, False, True) mock_logger.critical.assert_called_once_with(message) @@ -93,5 +102,5 @@ def test_run_missing_checks_no_rocket_chat_url(mock_logger): @mock.patch('pullbug.cli.GITHUB_TOKEN', '123') @mock.patch('pullbug.cli.LOGGER') def test_run_missing_checks_no_errors(mock_logger): - PullBug.run_missing_checks(True, False, False, True) + PullBug.run_missing_checks(True, False, False, False, True) mock_logger.critical.assert_not_called() diff --git a/test/unit/test_github_bug.py b/test/unit/test_github_bug.py index b40b1c6..917c156 100644 --- a/test/unit/test_github_bug.py +++ b/test/unit/test_github_bug.py @@ -4,101 +4,72 @@ from pullbug.github_bug import GithubBug -@pytest.fixture -def _mock_github_token(): - return '123' +GITHUB_TOKEN = '123' -@pytest.fixture -def _mock_user(): - return 'mock-user' - - -@pytest.fixture -def _mock_github_context(): - return 'users' - - -@pytest.fixture -def _mock_github_state(): - return 'open' - - -@pytest.fixture -def _mock_repo(): - mock_repo = { - 'name': 'mock-repo' - } - return mock_repo - - -@pytest.fixture -def _mock_pull_request(_mock_user, _mock_repo): - mock_pull_request = { - 'title': 'mock-pull-request', - 'description': 'Mock description', - 'assignees': [ - { - 'login': _mock_user, - 'html_url': f'https://github.com/{_mock_user}' - }, - ], - 'body': 'Mock body of a pull request.', - 'html_url': f'https://github.com/{_mock_user}/{_mock_repo}/pull/1' - } - return mock_pull_request - - -@mock.patch('pullbug.cli.GITHUB_TOKEN', '123') +@mock.patch('pullbug.cli.GITHUB_TOKEN', GITHUB_TOKEN) @mock.patch('pullbug.github_bug.GithubBug.get_pull_requests') @mock.patch('pullbug.github_bug.GithubBug.get_repos') @mock.patch('pullbug.github_bug.LOGGER') def test_run_success(mock_logger, mock_get_repos, mock_pull_request): - GithubBug.run('mock-owner', 'open', 'orgs', False, False, False) + GithubBug.run('mock-owner', 'open', 'orgs', False, False, False, False) mock_get_repos.assert_called_once() mock_pull_request.assert_called_once() mock_logger.info.assert_called() -@mock.patch('pullbug.cli.GITHUB_TOKEN', '123') +@mock.patch('pullbug.cli.GITHUB_TOKEN', GITHUB_TOKEN) @mock.patch('pullbug.github_bug.GithubBug.iterate_pull_requests') @mock.patch('pullbug.github_bug.GithubBug.get_pull_requests', return_value=[]) @mock.patch('pullbug.github_bug.GithubBug.get_repos') @mock.patch('pullbug.github_bug.LOGGER') def test_run_no_pull_requests(mock_logger, mock_get_repos, mock_pull_request, mock_iterate_pull_requests): - GithubBug.run('mock-owner', 'open', 'orgs', False, False, False) + GithubBug.run('mock-owner', 'open', 'orgs', False, False, False, False) mock_get_repos.assert_called_once() mock_iterate_pull_requests.assert_not_called() mock_logger.info.assert_called() -@mock.patch('pullbug.cli.GITHUB_TOKEN', '123') -@mock.patch('pullbug.messages.Messages.slack') +@mock.patch('pullbug.cli.GITHUB_TOKEN', GITHUB_TOKEN) +@mock.patch('pullbug.messages.Messages.send_discord_message') +@mock.patch('pullbug.github_bug.GithubBug.get_pull_requests') +@mock.patch('pullbug.github_bug.GithubBug.get_repos') +@mock.patch('pullbug.github_bug.LOGGER') +def test_run_with_discord(mock_logger, mock_get_repos, mock_pull_request, mock_discord): + GithubBug.run('mock-owner', 'open', 'orgs', False, True, False, False) + mock_get_repos.assert_called_once() + mock_pull_request.assert_called_once() + mock_discord.assert_called_once() + mock_logger.info.assert_called() + + +@mock.patch('pullbug.cli.GITHUB_TOKEN', GITHUB_TOKEN) +@mock.patch('pullbug.messages.Messages.send_slack_message') @mock.patch('pullbug.github_bug.GithubBug.get_pull_requests') @mock.patch('pullbug.github_bug.GithubBug.get_repos') @mock.patch('pullbug.github_bug.LOGGER') def test_run_with_slack(mock_logger, mock_get_repos, mock_pull_request, mock_slack): - GithubBug.run('mock-owner', 'open', 'orgs', False, True, False) + GithubBug.run('mock-owner', 'open', 'orgs', False, False, True, False) mock_get_repos.assert_called_once() mock_pull_request.assert_called_once() mock_slack.assert_called_once() mock_logger.info.assert_called() -@mock.patch('pullbug.cli.GITHUB_TOKEN', '123') -@mock.patch('pullbug.messages.Messages.rocketchat') +@mock.patch('pullbug.cli.GITHUB_TOKEN', GITHUB_TOKEN) +@mock.patch('pullbug.messages.Messages.send_rocketchat_message') @mock.patch('pullbug.github_bug.GithubBug.get_pull_requests') @mock.patch('pullbug.github_bug.GithubBug.get_repos') @mock.patch('pullbug.github_bug.LOGGER') def test_run_with_rocketchat(mock_logger, mock_get_repos, mock_pull_request, mock_rocketchat): - GithubBug.run('mock-owner', 'open', 'orgs', False, False, True) + GithubBug.run('mock-owner', 'open', 'orgs', False, False, False, True) mock_get_repos.assert_called_once() mock_pull_request.assert_called_once() mock_rocketchat.assert_called_once() mock_logger.info.assert_called() -@mock.patch('pullbug.github_bug.GITHUB_TOKEN', _mock_github_token) +@mock.patch('pullbug.github_bug.GITHUB_TOKEN', GITHUB_TOKEN) @mock.patch('pullbug.github_bug.GITHUB_HEADERS') @mock.patch('pullbug.github_bug.LOGGER') @mock.patch('requests.get') @@ -122,11 +93,11 @@ def test_get_repos_exception(mock_request, mock_logger, _mock_user, _mock_github ) -@mock.patch('pullbug.github_bug.GITHUB_TOKEN', _mock_github_token) +@mock.patch('pullbug.github_bug.GITHUB_TOKEN', GITHUB_TOKEN) @mock.patch('pullbug.github_bug.GITHUB_HEADERS') @mock.patch('pullbug.github_bug.LOGGER') @mock.patch('requests.get') -def test_get_pull_requests_success(mock_request, mock_logger, mock_headers, _mock_repo, _mock_github_state): +def test_get_pull_requests_success(mock_request, mock_logger, mock_headers, _mock_repo, _mock_github_state, _mock_user): # TODO: Mock this request better and assert additional values mock_repos = [_mock_repo] result = GithubBug.get_pull_requests(mock_repos, _mock_user, _mock_github_state) @@ -140,7 +111,7 @@ def test_get_pull_requests_success(mock_request, mock_logger, mock_headers, _moc @mock.patch('pullbug.github_bug.LOGGER') @mock.patch('requests.get', side_effect=requests.exceptions.RequestException('mock-error')) -def test_get_pull_requests_request_exception(mock_request, mock_logger, _mock_repo): +def test_get_pull_requests_request_exception(mock_request, mock_logger, _mock_repo, _mock_user, _mock_github_state): mock_repos = [_mock_repo] with pytest.raises(requests.exceptions.RequestException): GithubBug.get_pull_requests(mock_repos, _mock_user, _mock_github_state) @@ -151,7 +122,7 @@ def test_get_pull_requests_request_exception(mock_request, mock_logger, _mock_re @mock.patch('pullbug.github_bug.LOGGER') @mock.patch('requests.get', side_effect=TypeError('mock-error')) -def test_get_pull_requests_type_error_exception(mock_request, mock_logger, _mock_repo): +def test_get_pull_requests_type_error_exception(mock_request, mock_logger, _mock_repo, _mock_user, _mock_github_state): mock_repos = [_mock_repo] with pytest.raises(TypeError): GithubBug.get_pull_requests(mock_repos, _mock_user, _mock_github_state) @@ -160,36 +131,17 @@ def test_get_pull_requests_type_error_exception(mock_request, mock_logger, _mock ) -@mock.patch('pullbug.github_bug.GithubBug.prepare_message') +@mock.patch('pullbug.github_bug.Messages.prepare_github_message', return_value=[['mock-message'], ['mock-message']]) def test_iterate_pull_requests_wip_title(mock_prepare_message, _mock_pull_request): _mock_pull_request['title'] = 'wip: mock-pull-request' mock_pull_requests = [_mock_pull_request] - GithubBug.iterate_pull_requests(mock_pull_requests, True) + GithubBug.iterate_pull_requests(mock_pull_requests, True, False, False, False) mock_prepare_message.assert_called_once() -@mock.patch('pullbug.github_bug.GithubBug.prepare_message') +@mock.patch('pullbug.github_bug.Messages.prepare_github_message') def test_iterate_pull_requests_wip_setting_absent(mock_prepare_message, _mock_pull_request): _mock_pull_request['title'] = 'wip: mock-pull-request' mock_pull_requests = [_mock_pull_request] - GithubBug.iterate_pull_requests(mock_pull_requests, False) + GithubBug.iterate_pull_requests(mock_pull_requests, False, False, False, False) mock_prepare_message.assert_not_called() - - -def test_prepare_message(_mock_pull_request, _mock_user, _mock_repo): - result = GithubBug.prepare_message(_mock_pull_request) - assert 'Pull Request' in result - assert f'{_mock_pull_request["assignees"][0]["html_url"]}|{_mock_pull_request["assignees"][0]["login"]}' in result - assert f'{_mock_pull_request["html_url"]}|{_mock_pull_request["title"]}' in result - - -def test_prepare_message_no_assignees_data(_mock_pull_request): - _mock_pull_request['assignees'][0]['login'] = None - result = GithubBug.prepare_message(_mock_pull_request) - assert '*Waiting on:* No assignee' in result - - -def test_prepare_message_no_assignee(_mock_pull_request): - _mock_pull_request['assignees'] = [] - result = GithubBug.prepare_message(_mock_pull_request) - assert '*Waiting on:* No assignee' in result diff --git a/test/unit/test_gitlab_bug.py b/test/unit/test_gitlab_bug.py index f613bb2..faeac1f 100644 --- a/test/unit/test_gitlab_bug.py +++ b/test/unit/test_gitlab_bug.py @@ -4,68 +4,25 @@ from pullbug.gitlab_bug import GitlabBug -@pytest.fixture -def _mock_user(): - mock_user = { - 'name': 'mock-user' - } - return mock_user - - -@pytest.fixture -def _mock_repo(): - mock_repo = { - 'name': 'mock-repo' - } - return mock_repo - - -@pytest.fixture -def _mock_url(): - return 'http://mock-url.com' - - -@pytest.fixture -def _mock_gitlab_scope(): - return 'all' - - -@pytest.fixture -def _mock_gitlab_state(): - return 'opened' - - -@pytest.fixture -def _mock_merge_request(_mock_user, _mock_repo, _mock_url): - mock_merge_request = { - 'title': 'mock-pull-request', - 'description': 'Mock description', - 'assignees': [ - { - 'username': _mock_user, - 'web_url': f'https://github.com/{_mock_user}' - } - ], - 'body': 'Mock body of a pull request.', - 'web_url': f'{_mock_url}/{_mock_user}/{_mock_repo}/pull/1' - } - return mock_merge_request - - -@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', '123') -@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', _mock_url) -@mock.patch('pullbug.messages.Messages.rocketchat') -@mock.patch('pullbug.messages.Messages.slack') -@mock.patch('pullbug.gitlab_bug.GitlabBug.prepare_message') -@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests') +GITLAB_API_KEY = '123' +MOCK_URL = 'http://mock-url.com' + + +@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', GITLAB_API_KEY) +@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', MOCK_URL) +@mock.patch('pullbug.messages.Messages.send_rocketchat_message') +@mock.patch('pullbug.messages.Messages.send_slack_message') +@mock.patch('pullbug.gitlab_bug.Messages.prepare_gitlab_message') +@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests', return_value=[['mock-message'], ['mock-message']]) @mock.patch('pullbug.gitlab_bug.GitlabBug.get_merge_requests') @mock.patch('pullbug.gitlab_bug.GITLAB_HEADERS') @mock.patch('pullbug.gitlab_bug.LOGGER') @mock.patch('requests.get') def test_run_no_slack_or_rocketchat_messages(mock_request, mock_logger, mock_headers, mock_get_merge_requests, mock_iterate_merge_requests, - mock_prepare_message, mock_slack, mock_rocketchat): - GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, False, False) + mock_prepare_message, mock_slack, mock_rocketchat, + _mock_gitlab_scope, _mock_gitlab_state): + GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, False, False, False) mock_logger.info.assert_called() mock_get_merge_requests.assert_called_once() mock_iterate_merge_requests.assert_called() @@ -73,74 +30,97 @@ def test_run_no_slack_or_rocketchat_messages(mock_request, mock_logger, mock_hea mock_rocketchat.assert_not_called() -@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', '123') -@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', _mock_url) -@mock.patch('pullbug.messages.Messages.rocketchat') -@mock.patch('pullbug.messages.Messages.slack') -@mock.patch('pullbug.gitlab_bug.GitlabBug.prepare_message') -@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests') +@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', GITLAB_API_KEY) +@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', MOCK_URL) +@mock.patch('pullbug.messages.Messages.send_discord_message') +@mock.patch('pullbug.messages.Messages.send_slack_message') +@mock.patch('pullbug.gitlab_bug.Messages.prepare_gitlab_message') +@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests', return_value=[['mock-message'], ['mock-message']]) +@mock.patch('pullbug.gitlab_bug.GitlabBug.get_merge_requests') +@mock.patch('pullbug.gitlab_bug.GITLAB_HEADERS') +@mock.patch('pullbug.gitlab_bug.LOGGER') +@mock.patch('requests.get') +def test_run_discord_message(mock_request, mock_logger, mock_headers, + mock_get_merge_requests, mock_iterate_merge_requests, + mock_prepare_message, mock_slack, mock_discord, + _mock_gitlab_scope, _mock_gitlab_state): + GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, True, False, False) + mock_logger.info.assert_called() + mock_get_merge_requests.assert_called_once() + mock_iterate_merge_requests.assert_called() + mock_discord.assert_called_once() + + +@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', GITLAB_API_KEY) +@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', MOCK_URL) +@mock.patch('pullbug.messages.Messages.send_rocketchat_message') +@mock.patch('pullbug.messages.Messages.send_slack_message') +@mock.patch('pullbug.gitlab_bug.Messages.prepare_gitlab_message') +@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests', return_value=[['mock-message'], ['mock-message']]) @mock.patch('pullbug.gitlab_bug.GitlabBug.get_merge_requests') @mock.patch('pullbug.gitlab_bug.GITLAB_HEADERS') @mock.patch('pullbug.gitlab_bug.LOGGER') @mock.patch('requests.get') def test_run_slack_message(mock_request, mock_logger, mock_headers, mock_get_merge_requests, mock_iterate_merge_requests, - mock_prepare_message, mock_slack, mock_rocketchat): - GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, True, False) + mock_prepare_message, mock_slack, mock_rocketchat, + _mock_gitlab_scope, _mock_gitlab_state): + GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, False, True, False) mock_logger.info.assert_called() mock_get_merge_requests.assert_called_once() mock_iterate_merge_requests.assert_called() mock_slack.assert_called_once() - mock_rocketchat.assert_not_called() -@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', '123') -@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', _mock_url) -@mock.patch('pullbug.messages.Messages.rocketchat') -@mock.patch('pullbug.messages.Messages.slack') -@mock.patch('pullbug.gitlab_bug.GitlabBug.prepare_message') -@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests') +@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', GITLAB_API_KEY) +@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', MOCK_URL) +@mock.patch('pullbug.messages.Messages.send_rocketchat_message') +@mock.patch('pullbug.messages.Messages.send_slack_message') +@mock.patch('pullbug.gitlab_bug.Messages.prepare_gitlab_message') +@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests', return_value=[['mock-message'], ['mock-message']]) @mock.patch('pullbug.gitlab_bug.GitlabBug.get_merge_requests') @mock.patch('pullbug.gitlab_bug.GITLAB_HEADERS') @mock.patch('pullbug.gitlab_bug.LOGGER') @mock.patch('requests.get') def test_run_rocketchat_message(mock_request, mock_logger, mock_headers, mock_get_merge_requests, mock_iterate_merge_requests, - mock_prepare_message, mock_slack, mock_rocketchat): - GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, False, True) + mock_prepare_message, mock_slack, mock_rocketchat, + _mock_gitlab_scope, _mock_gitlab_state): + GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, False, False, True) mock_logger.info.assert_called() mock_get_merge_requests.assert_called_once() mock_iterate_merge_requests.assert_called() mock_slack.assert_not_called() - mock_rocketchat.assert_called_once() -@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', '123') -@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', _mock_url) -@mock.patch('pullbug.messages.Messages.rocketchat') -@mock.patch('pullbug.messages.Messages.slack') -@mock.patch('pullbug.gitlab_bug.GitlabBug.prepare_message') -@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests') +@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', GITLAB_API_KEY) +@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', MOCK_URL) +@mock.patch('pullbug.messages.Messages.send_rocketchat_message') +@mock.patch('pullbug.messages.Messages.send_slack_message') +@mock.patch('pullbug.gitlab_bug.Messages.prepare_gitlab_message') +@mock.patch('pullbug.gitlab_bug.GitlabBug.iterate_merge_requests', return_value=[['mock-message'], ['mock-message']]) @mock.patch('pullbug.gitlab_bug.GitlabBug.get_merge_requests', return_value=[]) @mock.patch('pullbug.gitlab_bug.GITLAB_HEADERS') @mock.patch('pullbug.gitlab_bug.LOGGER') @mock.patch('requests.get') def test_run_no_returned_merge_requests(mock_request, mock_logger, mock_headers, mock_get_merge_requests, mock_iterate_merge_requests, - mock_prepare_message, mock_slack, mock_rocketchat): - GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, False, False) + mock_prepare_message, mock_slack, mock_rocketchat, + _mock_gitlab_scope, _mock_gitlab_state): + GitlabBug.run(_mock_gitlab_scope, _mock_gitlab_state, False, False, False, False) mock_logger.info.assert_called_with('No merge requests are available from GitLab.') mock_iterate_merge_requests.assert_not_called() mock_slack.assert_not_called() mock_rocketchat.assert_not_called() -@ mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', '123') -@ mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', _mock_url) -@ mock.patch('pullbug.gitlab_bug.GITLAB_HEADERS') -@ mock.patch('pullbug.gitlab_bug.LOGGER') -@ mock.patch('requests.get') -def test_get_merge_requests_success(mock_request, mock_logger, mock_headers): +@mock.patch('pullbug.gitlab_bug.GITLAB_API_KEY', GITLAB_API_KEY) +@mock.patch('pullbug.gitlab_bug.GITLAB_API_URL', MOCK_URL) +@mock.patch('pullbug.gitlab_bug.GITLAB_HEADERS') +@mock.patch('pullbug.gitlab_bug.LOGGER') +@mock.patch('requests.get') +def test_get_merge_requests_success(mock_request, mock_logger, mock_headers, _mock_gitlab_scope, + _mock_gitlab_state, _mock_url): # TODO: Mock this request better and assert additional values GitlabBug.get_merge_requests(_mock_gitlab_scope, _mock_gitlab_state) mock_request.assert_called_once_with( @@ -150,9 +130,9 @@ def test_get_merge_requests_success(mock_request, mock_logger, mock_headers): assert mock_logger.info.call_count == 2 -@ mock.patch('pullbug.gitlab_bug.LOGGER') -@ mock.patch('requests.get', side_effect=requests.exceptions.RequestException('mock-error')) -def test_get_repos_exception(mock_request, mock_logger): +@mock.patch('pullbug.gitlab_bug.LOGGER') +@mock.patch('requests.get', side_effect=requests.exceptions.RequestException('mock-error')) +def test_get_repos_exception(mock_request, mock_logger, _mock_gitlab_scope, _mock_gitlab_state): with pytest.raises(requests.exceptions.RequestException): GitlabBug.get_merge_requests(_mock_gitlab_scope, _mock_gitlab_state) mock_logger.error.assert_called_once_with( @@ -160,36 +140,17 @@ def test_get_repos_exception(mock_request, mock_logger): ) -@ mock.patch('pullbug.gitlab_bug.GitlabBug.prepare_message') +@mock.patch('pullbug.gitlab_bug.Messages.prepare_gitlab_message', return_value=[['mock-message'], ['mock-message']]) def test_iterate_merge_requests_wip_title(mock_prepare_message, _mock_merge_request): _mock_merge_request['title'] = 'wip: mock-merge-request' mock_merge_requests = [_mock_merge_request] - GitlabBug.iterate_merge_requests(mock_merge_requests, True) + GitlabBug.iterate_merge_requests(mock_merge_requests, True, False, False, False) mock_prepare_message.assert_called_once() -@ mock.patch('pullbug.gitlab_bug.GitlabBug.prepare_message') +@mock.patch('pullbug.gitlab_bug.Messages.prepare_gitlab_message') def test_iterate_merge_requests_wip_setting_absent(mock_prepare_message, _mock_merge_request): _mock_merge_request['title'] = 'wip: mock-merge-request' mock_merge_requests = [_mock_merge_request] - GitlabBug.iterate_merge_requests(mock_merge_requests, False) + GitlabBug.iterate_merge_requests(mock_merge_requests, False, False, False, False) mock_prepare_message.assert_not_called() - - -def test_prepare_message(_mock_merge_request, _mock_url, _mock_user, _mock_repo): - result = GitlabBug.prepare_message(_mock_merge_request) - assert 'Merge Request' in result - assert f'{_mock_merge_request["assignees"][0]["web_url"]}|{_mock_merge_request["assignees"][0]["username"]}' in result # noqa - assert f'{_mock_merge_request["web_url"]}|{_mock_merge_request["title"]}' in result - - -def test_prepare_message_no_assignees_data(_mock_merge_request): - _mock_merge_request['assignees'][0]['username'] = None - result = GitlabBug.prepare_message(_mock_merge_request) - assert '*Waiting on:* No assignee' in result - - -def test_prepare_message_no_assignee(_mock_merge_request): - _mock_merge_request['assignees'] = [] - result = GitlabBug.prepare_message(_mock_merge_request) - assert '*Waiting on:* No assignee' in result diff --git a/test/unit/test_messages.py b/test/unit/test_messages.py index 8769631..69038d9 100644 --- a/test/unit/test_messages.py +++ b/test/unit/test_messages.py @@ -5,14 +5,37 @@ from pullbug.messages import Messages +@mock.patch('pullbug.messages.DISCORD_WEBHOOK_URL', 'https://discord.com/api/webhooks/channel_id/webhook_id') +@mock.patch('pullbug.messages.LOGGER') +@mock.patch('requests.post') +def test_discord_success(mock_request, mock_logger): + message = 'mock message' + Messages.send_discord_message([message]) + mock_request.assert_called_once_with( + 'https://discord.com/api/webhooks/channel_id/webhook_id', json={'content': message} + ) + mock_logger.info.assert_called_once_with('Discord message sent!') + + +@mock.patch('pullbug.messages.LOGGER') +@mock.patch('requests.post', side_effect=requests.exceptions.RequestException('mock-error')) +def test_discord_exception(mock_request, mock_logger): + message = 'mock message' + with pytest.raises(requests.exceptions.RequestException): + Messages.send_discord_message(message) + mock_logger.error.assert_called_once_with( + 'Could not send Discord message: mock-error' + ) + + @mock.patch('pullbug.messages.ROCKET_CHAT_URL', 'http://mock-url.com') @mock.patch('pullbug.messages.LOGGER') @mock.patch('requests.post') def test_rocket_chat_success(mock_request, mock_logger): message = 'mock message' - Messages.rocketchat(message) + Messages.send_rocketchat_message(message) mock_request.assert_called_once_with( - 'http://mock-url.com', data={'text': message} + 'http://mock-url.com', json={'text': message} ) mock_logger.info.assert_called_once_with('Rocket Chat message sent!') @@ -22,7 +45,7 @@ def test_rocket_chat_success(mock_request, mock_logger): def test_rocket_chat_exception(mock_request, mock_logger): message = 'mock message' with pytest.raises(requests.exceptions.RequestException): - Messages.rocketchat(message) + Messages.send_rocketchat_message(message) mock_logger.error.assert_called_once_with( 'Could not send Rocket Chat message: mock-error' ) @@ -34,7 +57,7 @@ def test_rocket_chat_exception(mock_request, mock_logger): @mock.patch('slack.WebClient.chat_postMessage') def test_slack_success(mock_slack, mock_logger): message = 'mock message' - Messages.slack(message) + Messages.send_slack_message(message) mock_slack.assert_called_once_with(channel='mock-channel', text=message) mock_logger.info.assert_called_once_with('Slack message sent!') @@ -48,7 +71,33 @@ def test_slack_success(mock_slack, mock_logger): def test_slack_exception(mock_slack, mock_logger): message = 'mock message' with pytest.raises(slack.errors.SlackApiError): - Messages.slack(message) + Messages.send_slack_message(message) mock_logger.error.assert_called_once_with( "Could not send Slack message: The request to the Slack API failed.\nThe server responded with: {'ok': False, 'error': 'not_authed'}" # noqa ) + + +def test_prepare_github_message(_mock_pull_request, _mock_user, _mock_repo): + result, discord_result = Messages.prepare_github_message(_mock_pull_request, False, False, False) + assert 'Pull Request' in result + assert f'{_mock_pull_request["assignees"][0]["html_url"]}|{_mock_pull_request["assignees"][0]["login"]}' in result + assert f'{_mock_pull_request["html_url"]}|{_mock_pull_request["title"]}' in result + + +def test_prepare_github_message_no_assignee(_mock_pull_request): + _mock_pull_request['assignees'] = [] + result, discord_result = Messages.prepare_github_message(_mock_pull_request, False, False, False) + assert '*Waiting on:*' in result + + +def test_prepare_gitlab_message(_mock_merge_request, _mock_url, _mock_user, _mock_repo): + result, discord_result = Messages.prepare_gitlab_message(_mock_merge_request, False, False, False) + assert 'Merge Request' in result + assert f'{_mock_merge_request["assignees"][0]["web_url"]}|{_mock_merge_request["assignees"][0]["username"]}' in result # noqa + assert f'{_mock_merge_request["web_url"]}|{_mock_merge_request["title"]}' in result + + +def test_prepare_gitlab_message_no_assignee(_mock_merge_request): + _mock_merge_request['assignees'] = [] + result, discord_result = Messages.prepare_gitlab_message(_mock_merge_request, False, False, False) + assert '*Waiting on:*' in result