Backporting production changes / updating tests
[margebot.git] / tests / test_marge.py
index 8e9d450..d96fc60 100644 (file)
@@ -1,5 +1,5 @@
 """
-Mrgebot tests
+Margebot tests
 """
 # pragma pylint: disable=invalid-name
 # pragma pylint: disable=missing-docstring
@@ -12,16 +12,194 @@ Mrgebot tests
 from datetime import datetime
 import logging
 import json
+from queue import Empty
 
-import errbot
-from errbot.backends.test import testbot   # pylint: disable=unused-import
-import gitlab
 from dateutil.relativedelta import relativedelta
+import requests
+from errbot.backends.test import testbot   # pylint: disable=unused-import
+import errbot
 import pytest
+from httmock import urlmatch, HTTMock
 from plugins.marge import deltastr
 
 
-class TestMarge(object):
+# HTTMock urlmatchers
+
+
+@urlmatch(path=r'/api/v4/users/author_id$')
+def mock_users_get_author_id(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps({'username': 'author_id username', 'id': 3001})}
+
+
+@urlmatch(path=r'/api/v4/users/[^/\?]+$')
+def mock_users_get_unexpected(url, request):
+    return {'status_code': 404}
+
+
+@urlmatch(path=r'/api/v4/users', query='username=gbin')
+def mock_users_search_gbin(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps([{'username': 'gbin', 'id': 3002}])}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/hooks$')
+def mock_projects_hooks_list(url, request):
+    if request.method == "GET":
+        return {'status_code': 200,
+                'headers': {'content-type': 'application/json'},
+                'content': '[]'}
+    elif request.method == "POST":
+        return {'status_code': 200,
+                'headers': {'content-type': 'application/json'},
+                'content': json.dumps({"id": "hook_id",
+                                       "merge_requests_events": True,
+                                       "url": "url"})}
+    elif request.method == "PUT":
+        return {'status_code': 200,
+                'headers': {'content-type': 'application/json'},
+                'content': json.dumps({"id": "hook_id",
+                                       "merge_requests_events": True,
+                                       "url": "url"})}
+    else:
+        return {'status-code': 404}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+$')
+def mock_projects_get(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps({"id": "projectid"})}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/merge_requests')
+def mock_projects_mergerequests_list_none(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': '[]'}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/merge_requests/[^/]+')
+def mock_projects_mergerequests_get_unreviewed_review(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps({'iid': 'mr_id',
+                                   'author': {'username': 'ReviewerX', '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})}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/merge_requests/[^/]+')
+def mock_projects_mergerequests_get_wip_review(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps({'iid': 'mr_id',
+                                   'author': {'username': 'ReviewerX', '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})}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/merge_requests/[^/]+')
+def mock_projects_mergerequests_get_waiting_review(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps({'iid': 'mr_id',
+                                   'author': {'username': 'ReviewerX', '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})}
+
+
+@urlmatch(path=r'/api/v4/projects/[^/]+/merge_requests/[^/]+/award_emoji')
+def mock_projects_mergerequests_awardemojis_list(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps([{'id': 'id',
+                                    'name': 'thumbsup',
+                                    'user': {'username': 'ReviewerX'}}])}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/merge_requests/[^/]+')
+def mock_projects_mergerequests_get_mergable_review(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps({'iid': 'mr_id',
+                                   'author': {'username': 'ReviewerX', '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})}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/merge_requests/[^/]+')
+def mock_projects_mergerequests_get_conflicted_review(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps({'iid': 'mr_id',
+                                   'author': {'username': 'ReviewerX', '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})}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/merge_requests$', query='state=opened')
+def mock_projects_mergerequests_list_unreviewed_review(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps([{'iid': 'mr_id',
+                                    'author': {'username': 'ReviewerX', '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_progresso': False}])}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/merge_requests', query='state=reopened')
+def mock_projects_mergerequests_list_reopened(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps([])}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/hooks/[^/]+$')
+def mock_projects_hooks_get_already_watching(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps({'id': 'hook_id',
+                                   'merge_requests_events': True,
+                                   'url': 'https://webhook.errbot.com:3142/margebot/room1,room2,room3'})}
+
+
+@urlmatch(path=r'^/api/v4/projects/[^/]+/hooks$')
+def mock_projects_hooks_list_already_watching(url, request):
+    return {'status_code': 200,
+            'headers': {'content-type': 'application/json'},
+            'content': json.dumps([{'id': 'hook_id',
+                                    'merge_requests_events': True,
+                                    'url': 'https://webhook.errbot.com:3142/margebot/room1,room2,room3'}])}
+
+
+class TestMarge:
     """
     Margebot Tests
 
@@ -31,9 +209,13 @@ class TestMarge(object):
     extra_plugin_dir = "./plugins/"
     loglevel = logging.INFO
 
+    @pytest.fixture(autouse=True)
+    def no_requests(self, monkeypatch):
+        monkeypatch.setattr(requests.sessions.Session, 'request', None)
+
     @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, 'WEBHOOK_URL': 'https://webhood.errbot.com:3142/margebot'}")
+        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, 'WEBHOOK_URL': 'https://webhook.errbot.com:3142/margebot'}")
         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()
@@ -48,10 +230,6 @@ class TestMarge(object):
 
         return testbot
 
-    @pytest.fixture
-    def margebot_no_reviews(self, margebot, monkeypatch):
-        return margebot
-
     @pytest.fixture
     def margebot_one_review(self, margebot, monkeypatch):
 
@@ -61,110 +239,6 @@ class TestMarge(object):
         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)
-        return gitlab
-
-    @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)
-        return gitlab
-
     # ============================================================================
 
     @pytest.mark.parametrize("rdelta,expected", [
@@ -187,25 +261,48 @@ class TestMarge(object):
         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()
+        assert '!watchrepo' in help_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',
+    def test_watchrepo(self, margebot):
+        with HTTMock(mock_projects_get,
+                     mock_projects_hooks_list,
+                     mock_projects_mergerequests_list_none):
+            margebot.push_message('!watchrepo group/new_repo room1,room2,room3')
+            pm = margebot.pop_message()
+            assert 'Now watching for new MRs in the group/new_repo repo to the room1,room2,room3 room(s)' in pm
+            assert 'No open MRs were found in the repo.' in pm
+
+    def test_watchrepo_already_watching_repo(self, margebot):
+        with HTTMock(mock_projects_hooks_list_already_watching,
+                     mock_projects_get):
+            margebot.push_message('!watchrepo group/new_repo room1,room2,room3')
+            pm = margebot.pop_message()
+            assert 'Already reporting group/new_repo MRs to the room1,room2,room3 room(s)' in pm
+
+    def test_watchrepo_updating_roomlist(self, margebot):
+        with HTTMock(mock_projects_hooks_list_already_watching,
+                     mock_projects_hooks_get_already_watching,
+                     mock_projects_get):
+            margebot.push_message('!watchrepo group/new_repo room4,room5,room6')
+            pm = margebot.pop_message()
+            assert 'Updating room list for group/new_repo MRs from room1,room2,room3 to room4,room5,room6' in pm
+
+    def test_watchrepo_existing_mr(self, margebot):
+        with HTTMock(mock_projects_get,
+                     mock_projects_hooks_list,
+                     mock_projects_mergerequests_list_unreviewed_review,
+                     mock_projects_mergerequests_list_reopened):
+            margebot.push_message('!watchrepo sample/mr room1,room2,room3')
+            pm = margebot.pop_message()
+            assert 'Now watching for new MRs in the sample/mr repo to the room1,room2,room3 room(s)' in pm
+            assert '1 open MR was found in the repo.' in pm
+
+    def test_gitlab_hook(self, margebot):
+        request = json.dumps({'event_type': 'merge_request',
                               'object_attributes': {
                                   'state': 'opened',
                                   'work_in_progress': '',
@@ -216,13 +313,52 @@ class TestMarge(object):
                                   'iid': 'iid'},
                               'project': {
                                   'homepage': 'url'}})
-        margebot.push_message("!webhook test /margebot/room1,room2 " + request)
-        assert 'Hi there ! author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
-        margebot.push_message('!reviews')
-        assert 'Hi there ! 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',
+        with HTTMock(mock_users_get_author_id):
+            margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+            assert 'Hi there ! author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+            margebot.push_message('!reviews')
+            assert 'Hi there ! author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+
+    def test_gitlab_hook_never_close(self, margebot):
+        request = json.dumps({'event_type': 'merge_request',
+                              'object_attributes': {
+                                  'state': 'opened',
+                                  'work_in_progress': '',
+                                  'title': 'title',
+                                  'author_id': 'author_id',
+                                  'target_project_id': 'project_id',
+                                  'id': 'id',
+                                  'iid': 'iid'},
+                              'project': {
+                                  'homepage': 'url'},
+                              'labels': [{'title': 'never-close'}]})
+        with HTTMock(mock_users_get_author_id):
+            margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+            assert 'Status code : 200' in margebot.pop_message()
+            margebot.push_message('!reviews')
+            assert 'Hi gbin: I found no open MRs for you.' in margebot.pop_message()
+
+    def test_gitlab_hook_never_abandoned(self, margebot):
+        request = json.dumps({'event_type': 'merge_request',
+                              'object_attributes': {
+                                  'state': 'opened',
+                                  'work_in_progress': '',
+                                  'title': 'title',
+                                  'author_id': 'author_id',
+                                  'target_project_id': 'project_id',
+                                  'id': 'id',
+                                  'iid': 'iid'},
+                              'project': {
+                                  'homepage': 'url'},
+                              'labels': [{'title': 'abandoned'}]})
+        with HTTMock(mock_users_get_author_id):
+            margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+            assert 'Status code : 200' in margebot.pop_message()
+            margebot.push_message('!reviews')
+            assert 'Hi gbin: I found no open MRs for you.' in margebot.pop_message()
+
+    def test_gitlab_hook_wip(self, margebot):
+        request = json.dumps({'event_type': 'merge_request',
                               'object_attributes': {
                                   'state': 'opened',
                                   'work_in_progress': 'true',
@@ -233,13 +369,14 @@ class TestMarge(object):
                                   'iid': 'iid'},
                               'project': {
                                   'homepage': 'url'}})
-        margebot.push_message("!webhook test /margebot/room1,room2 " + request)
-        assert 'Hi there ! author_id username has opened a new WIP MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
-        margebot.push_message('!reviews')
-        assert 'Hi there ! 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',
+        with HTTMock(mock_users_get_author_id):
+            margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+            assert 'Hi there ! author_id username has opened a new WIP MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+            margebot.push_message('!reviews')
+            assert 'Hi there ! 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):
+        request = json.dumps({'event_type': 'merge_request',
                               'object_attributes': {
                                   'state': 'opened',
                                   'work_in_progress': '',
@@ -251,13 +388,14 @@ class TestMarge(object):
                               'project': {
                                   'homepage': 'url'}})
 
-        margebot.push_message("!webhook test /margebot/room1,room2 " + request)
-        assert 'Hi there ! (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
-        margebot.push_message('!reviews')
-        assert 'Hi there ! (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+        with HTTMock(mock_users_get_unexpected):
+            margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+            assert 'Hi there ! (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
+            margebot.push_message('!reviews')
+            assert 'Hi there ! (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
 
-    def test_gitlab_hook_unexpected_object_kind(self, margebot, gitlab, caplog):
-        request = json.dumps({'object_kind': 'not_merge_request',
+    def test_gitlab_hook_unexpected_event_type(self, margebot, caplog):
+        request = json.dumps({'event_type': 'not_merge_request',
                               'object_attributes': {
                                   'state': 'opened',
                                   'work_in_progress': '',
@@ -269,59 +407,84 @@ class TestMarge(object):
                               'project': {
                                   'homepage': 'url'}})
 
-        margebot.push_message("!webhook test /margebot/room1,room2 " + request)
-        margebot.pop_message()
-        margebot.push_message('!reviews')
-        assert 'Hi gbin: I found no open MRs for you.' in margebot.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, gitlab_no_reviews):
-        margebot.push_message('!reviews')
-        assert 'Hi gbin: I found no open MRs for you.' in margebot.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')
+        with HTTMock(mock_users_search_gbin):
+            margebot.push_message("!webhook test /margebot/room1,room2 " + request)
+            margebot.pop_message()
+            margebot.push_message('!reviews')
+            assert 'Hi gbin: I found no open MRs for you.' in margebot.pop_message()
+            assert 'unexpecting event_type: not_merge_request' in caplog.text  # Has to be at end of method
+
+    def test_nothing_to_review(self, margebot):
+        with HTTMock(mock_users_search_gbin):
+            margebot.push_message('!reviews')
+            assert 'Hi gbin: I found no open MRs for you.' in margebot.pop_message()
+
+    def test_get_one_open_mr(self, margebot_one_review):
+        with HTTMock(mock_users_search_gbin,
+                     mock_projects_get,
+                     mock_projects_mergerequests_get_unreviewed_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, margebot_one_review):
+        with HTTMock(mock_users_search_gbin,
+                     mock_projects_get,
+                     mock_projects_mergerequests_get_wip_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 but still WIP.' in output
+
+    def test_get_one_waiting_mr(self, margebot_one_review):
+        with HTTMock(mock_users_search_gbin,
+                     mock_projects_get,
+                     mock_projects_mergerequests_awardemojis_list,
+                     mock_projects_mergerequests_get_waiting_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 'Approved by ReviewerX and waiting for another upvote.' in output
+
+    def test_get_one_mergable_mr(self, margebot_one_review):
+        with HTTMock(mock_users_search_gbin,
+                     mock_projects_get,
+                     mock_projects_mergerequests_get_mergable_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 'Has 2+ upvotes and could be merged in now.' in output
+
+    def test_get_one_conflicted_mr(self, margebot_one_review):
+        with HTTMock(mock_users_search_gbin,
+                     mock_projects_get,
+                     mock_projects_mergerequests_get_conflicted_review):
+            margebot_one_review.push_message('!reviews')
+            output = margebot_one_review.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, margebot_one_review, monkeypatch, mocker):
+        plugin = margebot_one_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
+        with HTTMock(mock_projects_get,
+                     mock_projects_mergerequests_get_unreviewed_review):
+            plugin.crontab_hook("unused")
+            output = margebot_one_review.pop_message()
+            assert 'These MRs need some attention' in output
+            assert 'http://gitlab.example.com/sample/mr/2001 (opened' in output
+
+    def test_margebot_unknown_command(self, margebot):
+        margebot.push_message('Margebot, alacazam')
+        with pytest.raises(Empty):
+            margebot.pop_message()