Checkpoint: 4/9/2019
[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 import gitlab
18 from dateutil.relativedelta import relativedelta
19 import pytest
20 import requests
21 import errbot
22 from errbot.backends.test import testbot # pylint: disable=unused-import
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 'merge_status': 'can_be_merged',
91 'state': 'opened',
92 'upvotes': 1,
93 'web_url': 'http://gitlab.example.com/sample/mr/2001',
94 'work_in_progress': False}
95 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
96 return margebot_one_review
97
98 @pytest.fixture
99 def one_conflicted_review(self, margebot_one_review, monkeypatch):
100
101 def mock_getmergerequest(self, project, iid):
102 return {'author': {'id': 2001},
103 'created_at': 'Oct 29, 2017 2:37am',
104 'merge_status': 'merge_conflicts',
105 'state': 'opened',
106 'upvotes': 2,
107 'web_url': 'http://gitlab.example.com/sample/mr/2001',
108 'work_in_progress': False}
109 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
110 return margebot_one_review
111
112 @pytest.fixture
113 def one_mergable_review(self, margebot_one_review, monkeypatch):
114
115 def mock_getmergerequest(self, project, iid):
116 return {'author': {'id': 2001},
117 'created_at': 'Oct 29, 2017 2:37am',
118 'merge_status': 'can_be_merged',
119 'state': 'opened',
120 'upvotes': 2,
121 'web_url': 'http://gitlab.example.com/sample/mr/2001',
122 'work_in_progress': False}
123 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
124 return margebot_one_review
125
126 @pytest.fixture
127 def gitlab(self, monkeypatch):
128
129 def mock_getuser(self, user):
130 if user == '(missing)':
131 return False
132 else:
133 return {'username': user + ' username'}
134 monkeypatch.setattr(gitlab.Gitlab, 'getuser', mock_getuser)
135
136 def mock_getusers(self, search=None):
137 if search:
138 ((_, val)) = search
139 if val == "(missing)":
140 return []
141 else:
142 return [{'user': val, 'id': 3001}]
143 else:
144 return [{'user': 'user1', 'id': 3001}, {'user': 'user2', 'id': 3002}]
145 monkeypatch.setattr(gitlab.Gitlab, 'getusers', mock_getusers)
146
147 def mock_getproject(self, repo):
148 return {
149 'id': 'projectid'
150 }
151 monkeypatch.setattr(gitlab.Gitlab, 'getproject', mock_getproject)
152
153 def mock_getprojecthooks(self, repo):
154 if repo == "(missing)":
155 return []
156 else:
157 return [{'id': 'hook_id', 'merge_requests_events': True, 'url': 'url'}]
158 monkeypatch.setattr(gitlab.Gitlab, 'getprojecthooks', mock_getprojecthooks)
159
160 # Waiting on Gitlab 8.9 or greater
161 # def mock_getapprovals(id):
162 # return []
163 # monkeypatch.setattr(gitlab.Gitlab, 'getapprovals', mock_getapprovals)
164
165 def mock_getmergerequests(self, project, page, per_page, state=None):
166 return []
167 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequests', mock_getmergerequests)
168
169 def mock_addprojecthook(self, project, url, **kwargs):
170 return True
171 monkeypatch.setattr(gitlab.Gitlab, 'addprojecthook', mock_addprojecthook)
172
173 def mock_editprojecthook(self, project, hook_id, url, **kwargs):
174 return True
175 monkeypatch.setattr(gitlab.Gitlab, 'editprojecthook', mock_editprojecthook)
176 return gitlab.Gitlab
177
178
179 # @pytest.fixture
180 # def MargeGitlab(self, monkeypatch):
181 # def mock_addprojecthook_extra(self, project, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
182 # return True
183 # monkeypatch.setattr('plugins.marge.MargeGitlab.addprojecthook_extra', mock_addprojecthook_extra)
184 #
185 # def mock_editprojecthook_extra(self, project, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
186 # return True
187 # monkeypatch.setattr('plugins.marge.MargeGitlab.editprojecthook_extra', mock_editprojecthook_extra)
188 # return marge.MargeGitlab
189
190 @pytest.fixture
191 def gitlab_no_reviews(self, gitlab, monkeypatch):
192 def mock_getmergerequest(self, project, iid):
193 return {}
194 monkeypatch.setattr(gitlab, 'getuser', mock_getmergerequest)
195 return gitlab
196
197 @pytest.fixture
198 def gitlab_one_review(self, gitlab, monkeypatch):
199 ret_mr = {'id': 'mr_id',
200 'author': {'id': 2001},
201 'created_at': 'Oct 29, 2017 2:37am',
202 'merge_status': 'can_be_merged',
203 'state': 'opened',
204 'upvotes': 0,
205 'web_url': 'http://gitlab.example.com/sample/mr/2001',
206 'work_in_progress': False}
207
208 def mock_getmergerequest(self, project, iid):
209 return ret_mr
210 monkeypatch.setattr(gitlab, 'getmergerequest', mock_getmergerequest)
211
212 def mock_getmergerequests(self, project, page, per_page, state=None):
213 if state and state == 'opened':
214 return [ret_mr]
215 else:
216 return []
217 monkeypatch.setattr(gitlab, 'getmergerequests', mock_getmergerequests)
218 return gitlab
219
220 @pytest.fixture
221 def gitlab_already_watching(self, gitlab, monkeypatch):
222 def mock_getprojecthooks(self, project):
223 return [{'id': 'hook_id', 'merge_requests_events': True, 'url': 'https://webhook.errbot.com:3142/margebot/room1,room2,room3'}]
224 monkeypatch.setattr(gitlab, 'getprojecthooks', mock_getprojecthooks)
225 return gitlab
226
227 # ============================================================================
228
229 @pytest.mark.parametrize("rdelta,expected", [
230 (relativedelta(days=1), "1 day ago"),
231 (relativedelta(days=2), "2 days ago"),
232 (relativedelta(days=1, hours=1), "1 day, 1 hour ago"),
233 (relativedelta(days=2, hours=2), "2 days, 2 hours ago"),
234 (relativedelta(hours=2), "2 hours ago"),
235 (relativedelta(days=1, minutes=23), "1 day, 23 minutes ago"),
236 (relativedelta(minutes=2), "2 minutes ago"),
237 (relativedelta(minutes=1), "1 minute ago"),
238 (relativedelta(minutes=0), "now"), ])
239 def test_deltastr(self, rdelta, expected):
240 now = datetime.now()
241 tdelta = (now + rdelta) - now
242 assert deltastr(tdelta) == expected
243
244 def test_help(self, margebot):
245 margebot.push_message('!help')
246 help_message = margebot.pop_message()
247 assert "Marge" in help_message
248 assert '!reviews' in help_message
249 assert '!watchrepo' in help_message
250
251 def test_good_bot(self, margebot):
252 margebot.push_message('good bot')
253 assert 'Best bot' in margebot.pop_message()
254
255 # def test_xyzzy(self, margebot):
256 # margebot.push_message('!xyzzy')
257 # assert 'All open MRs have been merged into master' in margebot.pop_message()
258 # time.sleep(6)
259 # assert 'just kidding' in margebot.pop_message()
260
261 def test_webstatus(self, margebot):
262 margebot.push_message('!webstatus')
263 assert 'margebot/<rooms>' in margebot.pop_message()
264
265 def test_watchrepo(self, margebot, gitlab):
266 margebot.push_message('!watchrepo group/new_repo room1,room2,room3')
267 pm = margebot.pop_message()
268 assert 'Now watching for new MRs in the group/new_repo repo to the room1,room2,room3 room(s)' in pm
269 assert 'No open MRs were found in the repo.' in pm
270
271 def test_watchrepo_already_watching_repo(self, margebot, gitlab_already_watching):
272 margebot.push_message('!watchrepo group/new_repo room1,room2,room3')
273 pm = margebot.pop_message()
274 assert 'Already reporting group/new_repo MRs to the room1,room2,room3 room(s)' in pm
275
276 def test_watchrepo_updating_roomlist(self, margebot, gitlab_already_watching):
277 margebot.push_message('!watchrepo group/new_repo room4,room5,room6')
278 pm = margebot.pop_message()
279 assert 'Updating room list for group/new_repo MRs from room1,room2,room3 to room4,room5,room6' in pm
280
281 # @pytest.mark.skip(reason='until I figure out how to mock MargeGitlab')
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 '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_dont_blame_margebot(self, margebot):
411 margebot.push_message('I blame margebot')
412 assert "say it ain't so" in margebot.pop_message()
413
414 def test_margefest(self, margebot):
415 margebot.push_message('magfest')
416 assert 'MargeFest' in margebot.pop_message()
417
418 def test_margebot_sucks(self, margebot):
419 margebot.push_message('margebot sucks')
420 assert 'steering committee' in margebot.pop_message()
421
422 def test_margebot_unknown_command(self, margebot):
423 margebot.push_message('Margebot, alacazam')
424 with pytest.raises(Empty):
425 margebot.pop_message()