Removed the jokes / added the reviewer name to the single +1 response.
[margebot.git] / tests / test_marge.py
1 """
2 Margebot tests
3 """
4 # pragma pylint: disable=invalid-name
5 # pragma pylint: disable=missing-docstring
6 # pragma pylint: disable=no-else-return
7 # pragma pylint: disable=protected-access
8 # pragma pylint: disable=redefined-outer-name
9 # pragma pylint: disable=too-many-public-methods
10 # pragma pylint: disable=unused-argument
11
12 from datetime import datetime
13 import logging
14 import json
15 from queue import Empty
16
17 from dateutil.relativedelta import relativedelta
18 import pytest
19 import requests
20 import errbot
21 from errbot.backends.test import testbot # pylint: disable=unused-import
22 import gitlab
23 from plugins.marge import deltastr
24
25
26 class TestMarge(object):
27 """
28 Margebot Tests
29
30 Run 'py.test' from the margebot root directory
31 """
32
33 extra_plugin_dir = "./plugins/"
34 loglevel = logging.INFO
35
36 @pytest.fixture(autouse=True)
37 def no_requests(self, monkeypatch):
38 monkeypatch.setattr(requests.sessions.Session, 'request', None)
39
40 @pytest.fixture
41 def margebot(self, testbot, monkeypatch, mocker):
42 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'}")
43 testbot.pop_message()
44 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}}")
45 testbot.pop_message()
46
47 def mock_get(self, key):
48 return {}
49 monkeypatch.setattr(errbot.storage.StoreMixin, '__getitem__', mock_get, raising=False)
50
51 def mock_keys(self):
52 return ['OPEN_MRS']
53 monkeypatch.setattr(errbot.storage.StoreMixin, 'keys', mock_keys, raising=False)
54
55 return testbot
56
57 @pytest.fixture
58 def margebot_no_reviews(self, margebot, monkeypatch):
59 return margebot
60
61 @pytest.fixture
62 def margebot_one_review(self, margebot, monkeypatch):
63
64 def mock_get(self, key):
65 return {(1001, 2001, 'room1,room2'): True} # [(project, iid, notify_rooms)]
66
67 monkeypatch.setattr(errbot.storage.StoreMixin, '__getitem__', mock_get, raising=False)
68 return margebot
69
70 @pytest.fixture
71 def one_wip_review(self, margebot_one_review, monkeypatch):
72
73 def mock_getmergerequest(self, project, iid):
74 return {'author': {'id': 2001},
75 'created_at': 'Oct 29, 2017 2:37am',
76 'merge_status': 'can_be_merged',
77 'state': 'opened',
78 'upvotes': 0,
79 'web_url': 'http://gitlab.example.com/sample/mr/2001',
80 'work_in_progress': True}
81 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
82 return margebot_one_review
83
84 @pytest.fixture
85 def one_waiting_review(self, margebot_one_review, monkeypatch):
86
87 def mock_getmergerequest(self, project, iid):
88 return {'author': {'id': 2001},
89 'created_at': 'Oct 29, 2017 2:37am',
90 'iid': 1,
91 'merge_status': 'can_be_merged',
92 'state': 'opened',
93 'target_project_id': 1,
94 'upvotes': 1,
95 'web_url': 'http://gitlab.example.com/sample/mr/2001',
96 'work_in_progress': False}
97 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
98
99 def mock_getawardemoji(self, project, iid):
100 return [{'name': 'happycat',
101 'user': {'username': 'ReviewerY'}},
102 {'name': 'thumbsup',
103 'user': {'username': 'ReviewerX'}}]
104 monkeypatch.setattr(gitlab.Gitlab, 'getawardemoji', mock_getawardemoji)
105
106 return margebot_one_review
107
108 @pytest.fixture
109 def one_conflicted_review(self, margebot_one_review, monkeypatch):
110
111 def mock_getmergerequest(self, project, iid):
112 return {'author': {'id': 2001},
113 'created_at': 'Oct 29, 2017 2:37am',
114 'merge_status': 'merge_conflicts',
115 'state': 'opened',
116 'upvotes': 2,
117 'web_url': 'http://gitlab.example.com/sample/mr/2001',
118 'work_in_progress': False}
119 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
120 return margebot_one_review
121
122 @pytest.fixture
123 def one_mergable_review(self, margebot_one_review, monkeypatch):
124
125 def mock_getmergerequest(self, project, iid):
126 return {'author': {'id': 2001},
127 'created_at': 'Oct 29, 2017 2:37am',
128 'merge_status': 'can_be_merged',
129 'state': 'opened',
130 'upvotes': 2,
131 'web_url': 'http://gitlab.example.com/sample/mr/2001',
132 'work_in_progress': False}
133 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
134 return margebot_one_review
135
136 @pytest.fixture
137 def gitlab(self, monkeypatch):
138
139 def mock_getuser(self, user):
140 if user == '(missing)':
141 return False
142 else:
143 return {'username': user + ' username'}
144 monkeypatch.setattr(gitlab.Gitlab, 'getuser', mock_getuser)
145
146 def mock_getusers(self, search=None):
147 if search:
148 ((_, val)) = search
149 if val == "(missing)":
150 return []
151 else:
152 return [{'user': val, 'id': 3001}]
153 else:
154 return [{'user': 'user1', 'id': 3001}, {'user': 'user2', 'id': 3002}]
155 monkeypatch.setattr(gitlab.Gitlab, 'getusers', mock_getusers)
156
157 def mock_getproject(self, repo):
158 return {
159 'id': 'projectid'
160 }
161 monkeypatch.setattr(gitlab.Gitlab, 'getproject', mock_getproject)
162
163 def mock_getprojecthooks(self, repo):
164 if repo == "(missing)":
165 return []
166 else:
167 return [{'id': 'hook_id', 'merge_requests_events': True, 'url': 'url'}]
168 monkeypatch.setattr(gitlab.Gitlab, 'getprojecthooks', mock_getprojecthooks)
169
170 # Waiting on Gitlab 8.9 or greater
171 # def mock_getapprovals(id):
172 # return []
173 # monkeypatch.setattr(gitlab.Gitlab, 'getapprovals', mock_getapprovals)
174
175 def mock_getmergerequests(self, project, page, per_page, state=None):
176 return []
177 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequests', mock_getmergerequests)
178
179 def mock_addprojecthook_extra(self, project, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
180 return True
181 monkeypatch.setattr(gitlab.Gitlab, 'addprojecthook_extra', mock_addprojecthook_extra, raising=False)
182
183 def mock_editprojecthook_extra(self, project, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
184 return True
185 monkeypatch.setattr(gitlab.Gitlab, 'editprojecthook_extra', mock_editprojecthook_extra, raising=False)
186
187 return gitlab.Gitlab
188
189
190 # @pytest.fixture
191 # def MargeGitlab(self, monkeypatch):
192 # def mock_addprojecthook_extra(self, project, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
193 # return True
194 # monkeypatch.setattr(plugins.marge.MargeGitlab, 'addprojecthook_extra')
195 #
196 # def mock_editprojecthook_extra(self, project, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
197 # return True
198 # monkeypatch.setattr(plugins.marge.MargeGitlab, 'editprojecthook_extra')
199 # return plugins.marge.MargeGitlab
200
201 @pytest.fixture
202 def gitlab_no_reviews(self, gitlab, monkeypatch):
203 def mock_getmergerequest(self, project, iid):
204 return {}
205 monkeypatch.setattr(gitlab, 'getuser', mock_getmergerequest)
206 return gitlab
207
208 @pytest.fixture
209 def gitlab_one_review(self, gitlab, monkeypatch):
210 ret_mr = {'iid': 'mr_id',
211 'author': {'id': 2001},
212 'created_at': 'Oct 29, 2017 2:37am',
213 'merge_status': 'can_be_merged',
214 'state': 'opened',
215 'upvotes': 0,
216 'web_url': 'http://gitlab.example.com/sample/mr/2001',
217 'work_in_progress': False}
218
219 def mock_getmergerequest(self, project, iid):
220 return ret_mr
221 monkeypatch.setattr(gitlab, 'getmergerequest', mock_getmergerequest)
222
223 def mock_getmergerequests(self, project, page, per_page, state=None):
224 if state and state == 'opened':
225 return [ret_mr]
226 else:
227 return []
228 monkeypatch.setattr(gitlab, 'getmergerequests', mock_getmergerequests)
229 return gitlab
230
231 @pytest.fixture
232 def gitlab_already_watching(self, gitlab, monkeypatch):
233 def mock_getprojecthooks(self, project):
234 return [{'id': 'hook_id', 'merge_requests_events': True, 'url': 'https://webhook.errbot.com:3142/margebot/room1,room2,room3'}]
235 monkeypatch.setattr(gitlab, 'getprojecthooks', mock_getprojecthooks)
236 return gitlab
237
238 # ============================================================================
239
240 @pytest.mark.parametrize("rdelta,expected", [
241 (relativedelta(days=1), "1 day ago"),
242 (relativedelta(days=2), "2 days ago"),
243 (relativedelta(days=1, hours=1), "1 day, 1 hour ago"),
244 (relativedelta(days=2, hours=2), "2 days, 2 hours ago"),
245 (relativedelta(hours=2), "2 hours ago"),
246 (relativedelta(days=1, minutes=23), "1 day, 23 minutes ago"),
247 (relativedelta(minutes=2), "2 minutes ago"),
248 (relativedelta(minutes=1), "1 minute ago"),
249 (relativedelta(minutes=0), "now"), ])
250 def test_deltastr(self, rdelta, expected):
251 now = datetime.now()
252 tdelta = (now + rdelta) - now
253 assert deltastr(tdelta) == expected
254
255 def test_help(self, margebot):
256 margebot.push_message('!help')
257 help_message = margebot.pop_message()
258 assert "Marge" in help_message
259 assert '!reviews' in help_message
260 assert '!watchrepo' in help_message
261
262 def test_webstatus(self, margebot):
263 margebot.push_message('!webstatus')
264 assert 'margebot/<rooms>' in margebot.pop_message()
265
266 def test_watchrepo(self, margebot, gitlab):
267 margebot.push_message('!watchrepo group/new_repo room1,room2,room3')
268 pm = margebot.pop_message()
269 assert 'Now watching for new MRs in the group/new_repo repo to the room1,room2,room3 room(s)' in pm
270 assert 'No open MRs were found in the repo.' in pm
271
272 def test_watchrepo_already_watching_repo(self, margebot, gitlab_already_watching):
273 margebot.push_message('!watchrepo group/new_repo room1,room2,room3')
274 pm = margebot.pop_message()
275 assert 'Already reporting group/new_repo MRs to the room1,room2,room3 room(s)' in pm
276
277 def test_watchrepo_updating_roomlist(self, margebot, gitlab_already_watching):
278 margebot.push_message('!watchrepo group/new_repo room4,room5,room6')
279 pm = margebot.pop_message()
280 assert 'Updating room list for group/new_repo MRs from room1,room2,room3 to room4,room5,room6' in pm
281
282 def test_watchrepo_existing_mr(self, margebot, gitlab_one_review):
283 margebot.push_message('!watchrepo sample/mr room1,room2,room3')
284 pm = margebot.pop_message()
285 assert 'Now watching for new MRs in the sample/mr repo to the room1,room2,room3 room(s)' in pm
286 assert '1 open MR was found in the repo.' in pm
287
288 def test_gitlab_hook(self, margebot, gitlab):
289 request = json.dumps({'object_kind': 'merge_request',
290 'object_attributes': {
291 'state': 'opened',
292 'work_in_progress': '',
293 'title': 'title',
294 'author_id': 'author_id',
295 'target_project_id': 'project_id',
296 'id': 'id',
297 'iid': 'iid'},
298 'project': {
299 'homepage': 'url'}})
300 margebot.push_message("!webhook test /margebot/room1,room2 " + request)
301 assert 'Hi there ! author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
302 margebot.push_message('!reviews')
303 assert 'Hi there ! author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
304
305 def test_gitlab_hook_wip(self, margebot, gitlab):
306 request = json.dumps({'object_kind': 'merge_request',
307 'object_attributes': {
308 'state': 'opened',
309 'work_in_progress': 'true',
310 'title': 'title',
311 'author_id': 'author_id',
312 'target_project_id': 'project_id',
313 'id': 'id',
314 'iid': 'iid'},
315 'project': {
316 'homepage': 'url'}})
317 margebot.push_message("!webhook test /margebot/room1,room2 " + request)
318 assert 'Hi there ! author_id username has opened a new WIP MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
319 margebot.push_message('!reviews')
320 assert 'Hi there ! author_id username has opened a new WIP MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
321
322 def test_gitlab_hook_unexpected_user(self, margebot, gitlab):
323 request = json.dumps({'object_kind': 'merge_request',
324 'object_attributes': {
325 'state': 'opened',
326 'work_in_progress': '',
327 'title': 'title',
328 'author_id': '(missing)',
329 'target_project_id': 'project_id',
330 'id': 'id',
331 'iid': 'iid'},
332 'project': {
333 'homepage': 'url'}})
334
335 margebot.push_message("!webhook test /margebot/room1,room2 " + request)
336 assert 'Hi there ! (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
337 margebot.push_message('!reviews')
338 assert 'Hi there ! (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
339
340 def test_gitlab_hook_unexpected_object_kind(self, margebot, gitlab, caplog):
341 request = json.dumps({'object_kind': 'not_merge_request',
342 'object_attributes': {
343 'state': 'opened',
344 'work_in_progress': '',
345 'title': 'title',
346 'author_id': 'author_id',
347 'target_project_id': 'project_id',
348 'id': 'id',
349 'iid': 'iid'},
350 'project': {
351 'homepage': 'url'}})
352
353 margebot.push_message("!webhook test /margebot/room1,room2 " + request)
354 margebot.pop_message()
355 margebot.push_message('!reviews')
356 assert 'Hi gbin: I found no open MRs for you.' in margebot.pop_message()
357 assert 'unexpecting object_kind: not_merge_request' in caplog.text # Has to be at end of method
358
359 def test_nothing_to_review(self, margebot, gitlab_no_reviews):
360 margebot.push_message('!reviews')
361 assert 'Hi gbin: I found no open MRs for you.' in margebot.pop_message()
362
363 def test_get_one_open_mr(self, margebot_one_review, gitlab_one_review):
364 margebot_one_review.push_message('!reviews')
365 output = margebot_one_review.pop_message()
366 assert 'These MRs need some attention' in output
367 assert 'http://gitlab.example.com/sample/mr/2001' in output
368 assert 'No upvotes, please review.' in output
369
370 def test_get_one_wip_mr(self, one_wip_review, gitlab):
371 one_wip_review.push_message('!reviews')
372 output = one_wip_review.pop_message()
373 assert 'These MRs need some attention' in output
374 assert 'http://gitlab.example.com/sample/mr/2001' in output
375 assert 'No upvotes, please review but still WIP.' in output
376
377 def test_get_one_waiting_mr(self, margebot, one_waiting_review, gitlab):
378 one_waiting_review.push_message('!reviews')
379 output = margebot.pop_message()
380 assert 'These MRs need some attention' in output
381 assert 'http://gitlab.example.com/sample/mr/2001' in output
382 assert 'Approved by ReviewerX and waiting for another upvote.' in output
383
384 def test_get_one_mergable_mr(self, margebot, one_mergable_review, gitlab):
385 one_mergable_review.push_message('!reviews')
386 output = margebot.pop_message()
387 assert 'These MRs need some attention' in output
388 assert 'http://gitlab.example.com/sample/mr/2001' in output
389 assert 'Has 2+ upvotes and could be merged in now.' in output
390
391 def test_get_one_conflicted_mr(self, margebot, one_conflicted_review, gitlab):
392 one_conflicted_review.push_message('!reviews')
393 output = margebot.pop_message(timeout=1)
394 assert 'These MRs need some attention' in output
395 assert 'http://gitlab.example.com/sample/mr/2001' in output
396 assert 'Has 2+ upvotes and could be merged in now except there are merge conflicts.' in output
397
398 def test_crontab_hook(self, one_waiting_review, gitlab, monkeypatch, mocker):
399 plugin = one_waiting_review._bot.plugin_manager.get_plugin_obj_by_name('Marge')
400
401 def mock_rooms():
402 return [mocker.MagicMock(node='room1'), mocker.MagicMock(node='room2')]
403 monkeypatch.setattr(plugin, 'rooms', mock_rooms)
404
405 plugin.crontab_hook("unused")
406 output = one_waiting_review.pop_message()
407 assert 'These MRs need some attention' in output
408 assert 'http://gitlab.example.com/sample/mr/2001 (opened' in output
409
410 def test_margebot_unknown_command(self, margebot):
411 margebot.push_message('Margebot, alacazam')
412 with pytest.raises(Empty):
413 margebot.pop_message()