There be tests ! And a massive code reorg to support the tests, And pep8 and pylint...
[margebot.git] / tests / test_marge.py
diff --git a/tests/test_marge.py b/tests/test_marge.py
new file mode 100644 (file)
index 0000000..bb53f9d
--- /dev/null
@@ -0,0 +1,322 @@
+"""
+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