--- /dev/null
+"""
+Mrgebot tests
+"""
+# pragma pylint: disable=invalid-name
+# pragma pylint: disable=missing-docstring
+# pragma pylint: disable=no-else-return
+# pragma pylint: disable=protected-access
+# pragma pylint: disable=redefined-outer-name
+# pragma pylint: disable=too-many-public-methods
+# pragma pylint: disable=unused-argument
+
+from datetime import datetime
+import logging
+import json
+
+import pytest
+import errbot
+from errbot.backends.test import testbot # pylint: disable=unused-import
+import gitlab
+from dateutil.relativedelta import relativedelta
+from plugins.marge import deltastr
+
+
+class TestMarge(object):
+ """
+ Margebot Tests
+
+ Run 'py.test' from the margebot root directory
+ """
+
+ extra_plugin_dir = "./plugins/"
+ loglevel = logging.INFO
+
+ @pytest.fixture
+ def margebot(self, testbot, monkeypatch, mocker):
+ testbot.push_message("!plugin config Marge {'CHATROOM_HOST': 'conference.test.com', 'GITLAB_HOST': 'gitlab.test.com', 'GITLAB_ADMIN_TOKEN': 'fake-token', 'CRONTAB': '0 * * * *', 'VERIFY_SSL': True, 'CRONTAB_SOAK_HOURS': 1}")
+ testbot.pop_message()
+ testbot.push_message("!plugin config Webserver {'HOST': '0.0.0.0', 'PORT':3141, 'SSL': {'certificate': '', 'enabled': False, 'host': '0.0.0.0', 'key': '', 'port': 3142}}")
+ testbot.pop_message()
+
+ def mock_keys(self):
+ return ['OPEN_MRS']
+ monkeypatch.setattr(errbot.storage.StoreMixin, 'keys', mock_keys, raising=False)
+
+ return testbot
+
+ @pytest.fixture
+ def margebot_no_reviews(self, margebot, monkeypatch):
+
+ def mock_get(self, key):
+ return {}
+
+ monkeypatch.setattr(errbot.storage.StoreMixin, '__getitem__', mock_get, raising=False)
+ return margebot
+
+ @pytest.fixture
+ def margebot_one_review(self, margebot, monkeypatch):
+
+ def mock_get(self, key):
+ return {(1001, 2001, 'room1,room2'): True} # [(project, iid, notify_rooms)]
+
+ monkeypatch.setattr(errbot.storage.StoreMixin, '__getitem__', mock_get, raising=False)
+ return margebot
+
+ @pytest.fixture
+ def one_wip_review(self, margebot_one_review, monkeypatch):
+
+ def mock_getmergerequest(self, project, iid):
+ return {'author': {'id': 2001},
+ 'created_at': 'Oct 29, 2017 2:37am',
+ 'merge_status': 'can_be_merged',
+ 'state': 'opened',
+ 'upvotes': 0,
+ 'web_url': 'http://gitlab.example.com/sample/mr/2001',
+ 'work_in_progress': True}
+ monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
+ return margebot_one_review
+
+ @pytest.fixture
+ def one_waiting_review(self, margebot_one_review, monkeypatch):
+
+ def mock_getmergerequest(self, project, iid):
+ return {'author': {'id': 2001},
+ 'created_at': 'Oct 29, 2017 2:37am',
+ 'merge_status': 'can_be_merged',
+ 'state': 'opened',
+ 'upvotes': 1,
+ 'web_url': 'http://gitlab.example.com/sample/mr/2001',
+ 'work_in_progress': False}
+ monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
+ return margebot_one_review
+
+ @pytest.fixture
+ def one_conflicted_review(self, margebot_one_review, monkeypatch):
+
+ def mock_getmergerequest(self, project, iid):
+ return {'author': {'id': 2001},
+ 'created_at': 'Oct 29, 2017 2:37am',
+ 'merge_status': 'merge_conflicts',
+ 'state': 'opened',
+ 'upvotes': 2,
+ 'web_url': 'http://gitlab.example.com/sample/mr/2001',
+ 'work_in_progress': False}
+ monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
+ return margebot_one_review
+
+ @pytest.fixture
+ def one_mergable_review(self, margebot_one_review, monkeypatch):
+
+ def mock_getmergerequest(self, project, iid):
+ return {'author': {'id': 2001},
+ 'created_at': 'Oct 29, 2017 2:37am',
+ 'merge_status': 'can_be_merged',
+ 'state': 'opened',
+ 'upvotes': 2,
+ 'web_url': 'http://gitlab.example.com/sample/mr/2001',
+ 'work_in_progress': False}
+ monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
+ return margebot_one_review
+
+ @pytest.fixture
+ def gitlab(self, monkeypatch):
+
+ def mock_getuser(self, user):
+ if user == '(missing)':
+ return False
+ else:
+ return {'username': user + ' username'}
+ monkeypatch.setattr(gitlab.Gitlab, 'getuser', mock_getuser)
+
+ def mock_getusers(self, search=None):
+ if search:
+ ((_, val)) = search
+ if val == "(missing)":
+ return []
+ else:
+ return [{'user': val, 'id': 3001}]
+ else:
+ return [{'user': 'user1', 'id': 3001}, {'user': 'user2', 'id': 3002}]
+ monkeypatch.setattr(gitlab.Gitlab, 'getusers', mock_getusers)
+
+ # Waiting on Gitlab 8.9 or greater
+ # def mock_getapprovals(id):
+ # return []
+ # monkeypatch.setattr(gitlab.Gitlab, 'getapprovals', mock_getapprovals)
+
+ return gitlab.Gitlab
+
+ @pytest.fixture
+ def gitlab_no_reviews(self, gitlab, monkeypatch):
+ def mock_getmergerequest(self, project, iid):
+ return {}
+ monkeypatch.setattr(gitlab, 'getuser', mock_getmergerequest)
+
+ @pytest.fixture
+ def gitlab_one_review(self, gitlab, monkeypatch):
+ def mock_getmergerequest(self, project, iid):
+ return {'author': {'id': 2001},
+ 'created_at': 'Oct 29, 2017 2:37am',
+ 'merge_status': 'can_be_merged',
+ 'state': 'opened',
+ 'upvotes': 0,
+ 'web_url': 'http://gitlab.example.com/sample/mr/2001',
+ 'work_in_progress': False}
+ monkeypatch.setattr(gitlab, 'getmergerequest', mock_getmergerequest)
+
+ # ============================================================================
+
+ @pytest.mark.parametrize("rdelta,expected", [
+ (relativedelta(days=1), "1 day ago"),
+ (relativedelta(days=2), "2 days ago"),
+ (relativedelta(days=1, hours=1), "1 day, 1 hour ago"),
+ (relativedelta(days=2, hours=2), "2 days, 2 hours ago"),
+ (relativedelta(hours=2), "2 hours ago"),
+ (relativedelta(days=1, minutes=23), "1 day, 23 minutes ago"),
+ (relativedelta(minutes=2), "2 minutes ago"),
+ (relativedelta(minutes=1), "1 minute ago"),
+ (relativedelta(minutes=0), "now"), ])
+ def test_deltastr(self, rdelta, expected):
+ now = datetime.now()
+ tdelta = (now + rdelta) - now
+ assert deltastr(tdelta) == expected
+
+ def test_help(self, margebot):
+ margebot.push_message('!help')
+ help_message = margebot.pop_message()
+ assert "Marge" in help_message
+ assert '!reviews' in help_message
+ assert '!hello' in help_message
+ assert '!xyzzy' in help_message
+
+ def test_hello(self, margebot):
+ margebot.push_message('!hello')
+ assert 'Hi there' in margebot.pop_message()
+
+# def test_xyzzy(self, margebot):
+# margebot.push_message('!xyzzy')
+# assert 'All open MRs have been merged into master' in margebot.pop_message()
+# time.sleep(6)
+# assert 'just kidding' in margebot.pop_message()
+
+ def test_webstatus(self, margebot):
+ margebot.push_message('!webstatus')
+ assert 'margebot/<rooms>' in margebot.pop_message()
+
+ def test_gitlab_hook(self, margebot, gitlab):
+ request = json.dumps({'object_kind': 'merge_request',
+ 'object_attributes': {
+ 'state': 'opened',
+ 'work_in_progress': '',
+ 'title': 'title',
+ 'author_id': 'author_id',
+ 'target_project_id': 'project_id',
+ 'iid': 'iid'},
+ 'project': {
+ 'homepage': 'url'}})
+ margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+ assert 'New Review: author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+ margebot.push_message('!reviews')
+ assert 'New Review: author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+
+ def test_gitlab_hook_wip(self, margebot, gitlab):
+ request = json.dumps({'object_kind': 'merge_request',
+ 'object_attributes': {
+ 'state': 'opened',
+ 'work_in_progress': 'true',
+ 'title': 'title',
+ 'author_id': 'author_id',
+ 'target_project_id': 'project_id',
+ 'iid': 'iid'},
+ 'project': {
+ 'homepage': 'url'}})
+ margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+ assert 'New Review: author_id username has opened a new WIP MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+ margebot.push_message('!reviews')
+ assert 'New Review: author_id username has opened a new WIP MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+
+ def test_gitlab_hook_unexpected_user(self, margebot, gitlab):
+ request = json.dumps({'object_kind': 'merge_request',
+ 'object_attributes': {
+ 'state': 'opened',
+ 'work_in_progress': '',
+ 'title': 'title',
+ 'author_id': '(missing)',
+ 'target_project_id': 'project_id',
+ 'iid': 'iid'},
+ 'project': {
+ 'homepage': 'url'}})
+
+ margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+ assert 'New Review: (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+ margebot.push_message('!reviews')
+ assert 'New Review: (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+
+ def test_gitlab_hook_unexpected_object_kind(self, margebot_no_reviews, gitlab, caplog):
+ request = json.dumps({'object_kind': 'not_merge_request',
+ 'object_attributes': {
+ 'state': 'opened',
+ 'work_in_progress': '',
+ 'title': 'title',
+ 'author_id': 'author_id',
+ 'target_project_id': 'project_id',
+ 'iid': 'iid'},
+ 'project': {
+ 'homepage': 'url'}})
+
+ margebot_no_reviews.push_message("!webhook test /margebot/room1,room2 " + request)
+ margebot_no_reviews.pop_message()
+ margebot_no_reviews.push_message('!reviews')
+ assert 'Hi gbin: I found no open MRs for you.' in margebot_no_reviews.pop_message()
+ assert 'unexpecting object_kind: not_merge_request' in caplog.text # Has to be at end of method
+
+ def test_nothing_to_review(self, margebot_no_reviews, gitlab_no_reviews):
+ margebot_no_reviews.push_message('!reviews')
+ assert 'Hi gbin: I found no open MRs for you.' in margebot_no_reviews.pop_message()
+
+ def test_get_one_open_mr(self, margebot_one_review, gitlab_one_review):
+ margebot_one_review.push_message('!reviews')
+ output = margebot_one_review.pop_message()
+ assert 'These MRs need some attention' in output
+ assert 'http://gitlab.example.com/sample/mr/2001' in output
+ assert 'No upvotes, please review.' in output
+
+ def test_get_one_wip_mr(self, one_wip_review, gitlab):
+ one_wip_review.push_message('!reviews')
+ output = one_wip_review.pop_message()
+ assert 'These MRs need some attention' in output
+ assert 'http://gitlab.example.com/sample/mr/2001' in output
+ assert 'No upvotes, please review but still WIP.' in output
+
+ def test_get_one_waiting_mr(self, margebot, one_waiting_review, gitlab):
+ one_waiting_review.push_message('!reviews')
+ output = margebot.pop_message()
+ assert 'These MRs need some attention' in output
+ assert 'http://gitlab.example.com/sample/mr/2001' in output
+ assert 'Waiting for another upvote.' in output
+
+ def test_get_one_mergable_mr(self, margebot, one_mergable_review, gitlab):
+ one_mergable_review.push_message('!reviews')
+ output = margebot.pop_message()
+ assert 'These MRs need some attention' in output
+ assert 'http://gitlab.example.com/sample/mr/2001' in output
+ assert 'Has 2+ upvotes and could be merged in now.' in output
+
+ def test_get_one_conflicted_mr(self, margebot, one_conflicted_review, gitlab):
+ one_conflicted_review.push_message('!reviews')
+ output = margebot.pop_message(timeout=1)
+ assert 'These MRs need some attention' in output
+ assert 'http://gitlab.example.com/sample/mr/2001' in output
+ assert 'Has 2+ upvotes and could be merged in now except there are merge conflicts.' in output
+
+ def test_crontab_hook(self, one_waiting_review, gitlab, monkeypatch, mocker):
+ plugin = one_waiting_review._bot.plugin_manager.get_plugin_obj_by_name('Marge')
+
+ def mock_rooms():
+ return [mocker.MagicMock(node='room1'), mocker.MagicMock(node='room2')]
+ monkeypatch.setattr(plugin, 'rooms', mock_rooms)
+
+ plugin.crontab_hook("unused")
+ output = one_waiting_review.pop_message()
+ assert 'These MRs need some attention' in output
+ assert 'http://gitlab.example.com/sample/mr/2001 (opened' in output