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
12 from datetime
import datetime
15 from queue
import Empty
17 from dateutil
.relativedelta
import relativedelta
21 from errbot
.backends
.test
import testbot
# pylint: disable=unused-import
23 from plugins
.marge
import deltastr
26 class TestMarge(object):
30 Run 'py.test' from the margebot root directory
33 extra_plugin_dir
= "./plugins/"
34 loglevel
= logging
.INFO
36 @pytest.fixture(autouse
=True)
37 def no_requests(self
, monkeypatch
):
38 monkeypatch
.setattr(requests
.sessions
.Session
, 'request', None)
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'}")
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}}")
47 def mock_get(self
, key
):
49 monkeypatch
.setattr(errbot
.storage
.StoreMixin
, '__getitem__', mock_get
, raising
=False)
53 monkeypatch
.setattr(errbot
.storage
.StoreMixin
, 'keys', mock_keys
, raising
=False)
58 def margebot_no_reviews(self
, margebot
, monkeypatch
):
62 def margebot_one_review(self
, margebot
, monkeypatch
):
64 def mock_get(self
, key
):
65 return {(1001, 2001, 'room1,room2'): True} # [(project, iid, notify_rooms)]
67 monkeypatch
.setattr(errbot
.storage
.StoreMixin
, '__getitem__', mock_get
, raising
=False)
71 def one_wip_review(self
, margebot_one_review
, monkeypatch
):
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',
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
85 def one_waiting_review(self
, margebot_one_review
, monkeypatch
):
87 def mock_getmergerequest(self
, project
, iid
):
88 return {'author': {'id': 2001},
89 'created_at': 'Oct 29, 2017 2:37am',
91 'merge_status': 'can_be_merged',
93 'target_project_id': 1,
95 'web_url': 'http://gitlab.example.com/sample/mr/2001',
96 'work_in_progress': False}
97 monkeypatch
.setattr(gitlab
.Gitlab
, 'getmergerequest', mock_getmergerequest
)
99 def mock_getawardemoji(self
, project
, iid
):
100 return [{'name': 'happycat',
101 'user': {'username': 'ReviewerY'}},
103 'user': {'username': 'ReviewerX'}}]
104 monkeypatch
.setattr(gitlab
.Gitlab
, 'getawardemoji', mock_getawardemoji
)
106 return margebot_one_review
109 def one_conflicted_review(self
, margebot_one_review
, monkeypatch
):
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',
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
123 def one_mergable_review(self
, margebot_one_review
, monkeypatch
):
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',
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
137 def gitlab(self
, monkeypatch
):
139 def mock_getuser(self
, user
):
140 if user
== '(missing)':
143 return {'username': user
+ ' username'}
144 monkeypatch
.setattr(gitlab
.Gitlab
, 'getuser', mock_getuser
)
146 def mock_getusers(self
, search
=None):
149 if val
== "(missing)":
152 return [{'user': val
, 'id': 3001}]
154 return [{'user': 'user1', 'id': 3001}, {'user': 'user2', 'id': 3002}]
155 monkeypatch
.setattr(gitlab
.Gitlab
, 'getusers', mock_getusers
)
157 def mock_getproject(self
, repo
):
161 monkeypatch
.setattr(gitlab
.Gitlab
, 'getproject', mock_getproject
)
163 def mock_getprojecthooks(self
, repo
):
164 if repo
== "(missing)":
167 return [{'id': 'hook_id', 'merge_requests_events': True, 'url': 'url'}]
168 monkeypatch
.setattr(gitlab
.Gitlab
, 'getprojecthooks', mock_getprojecthooks
)
170 # Waiting on Gitlab 8.9 or greater
171 # def mock_getapprovals(id):
173 # monkeypatch.setattr(gitlab.Gitlab, 'getapprovals', mock_getapprovals)
175 def mock_getmergerequests(self
, project
, page
, per_page
, state
=None):
177 monkeypatch
.setattr(gitlab
.Gitlab
, 'getmergerequests', mock_getmergerequests
)
179 def mock_addprojecthook_extra(self
, project
, url
, push
=False, issues
=False, merge_requests
=False, tag_push
=False, extra_data
=None):
181 monkeypatch
.setattr(gitlab
.Gitlab
, 'addprojecthook_extra', mock_addprojecthook_extra
, raising
=False)
183 def mock_editprojecthook_extra(self
, project
, hook_id
, url
, push
=False, issues
=False, merge_requests
=False, tag_push
=False, extra_data
=None):
185 monkeypatch
.setattr(gitlab
.Gitlab
, 'editprojecthook_extra', mock_editprojecthook_extra
, raising
=False)
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):
194 # monkeypatch.setattr(plugins.marge.MargeGitlab, 'addprojecthook_extra')
196 # def mock_editprojecthook_extra(self, project, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
198 # monkeypatch.setattr(plugins.marge.MargeGitlab, 'editprojecthook_extra')
199 # return plugins.marge.MargeGitlab
202 def gitlab_no_reviews(self
, gitlab
, monkeypatch
):
203 def mock_getmergerequest(self
, project
, iid
):
205 monkeypatch
.setattr(gitlab
, 'getuser', mock_getmergerequest
)
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',
216 'web_url': 'http://gitlab.example.com/sample/mr/2001',
217 'work_in_progress': False}
219 def mock_getmergerequest(self
, project
, iid
):
221 monkeypatch
.setattr(gitlab
, 'getmergerequest', mock_getmergerequest
)
223 def mock_getmergerequests(self
, project
, page
, per_page
, state
=None):
224 if state
and state
== 'opened':
228 monkeypatch
.setattr(gitlab
, 'getmergerequests', mock_getmergerequests
)
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
)
238 # ============================================================================
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
):
252 tdelta
= (now
+ rdelta
) - now
253 assert deltastr(tdelta
) == expected
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
262 def test_webstatus(self
, margebot
):
263 margebot
.push_message('!webstatus')
264 assert 'margebot/<rooms>' in margebot
.pop_message()
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
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
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
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
288 def test_gitlab_hook(self
, margebot
, gitlab
):
289 request
= json
.dumps({'object_kind': 'merge_request',
290 'object_attributes': {
292 'work_in_progress': '',
294 'author_id': 'author_id',
295 'target_project_id': 'project_id',
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()
305 def test_gitlab_hook_wip(self
, margebot
, gitlab
):
306 request
= json
.dumps({'object_kind': 'merge_request',
307 'object_attributes': {
309 'work_in_progress': 'true',
311 'author_id': 'author_id',
312 'target_project_id': 'project_id',
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()
322 def test_gitlab_hook_unexpected_user(self
, margebot
, gitlab
):
323 request
= json
.dumps({'object_kind': 'merge_request',
324 'object_attributes': {
326 'work_in_progress': '',
328 'author_id': '(missing)',
329 'target_project_id': 'project_id',
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()
340 def test_gitlab_hook_unexpected_object_kind(self
, margebot
, gitlab
, caplog
):
341 request
= json
.dumps({'object_kind': 'not_merge_request',
342 'object_attributes': {
344 'work_in_progress': '',
346 'author_id': 'author_id',
347 'target_project_id': 'project_id',
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
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()
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
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
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
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
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
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')
402 return [mocker
.MagicMock(node
='room1'), mocker
.MagicMock(node
='room2')]
403 monkeypatch
.setattr(plugin
, 'rooms', mock_rooms
)
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
410 def test_margebot_unknown_command(self
, margebot
):
411 margebot
.push_message('Margebot, alacazam')
412 with pytest
.raises(Empty
):
413 margebot
.pop_message()