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
18 from dateutil
.relativedelta
import relativedelta
22 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',
90 'merge_status': 'can_be_merged',
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
99 def one_conflicted_review(self
, margebot_one_review
, monkeypatch
):
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',
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
113 def one_mergable_review(self
, margebot_one_review
, monkeypatch
):
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',
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
127 def gitlab(self
, monkeypatch
):
129 def mock_getuser(self
, user
):
130 if user
== '(missing)':
133 return {'username': user
+ ' username'}
134 monkeypatch
.setattr(gitlab
.Gitlab
, 'getuser', mock_getuser
)
136 def mock_getusers(self
, search
=None):
139 if val
== "(missing)":
142 return [{'user': val
, 'id': 3001}]
144 return [{'user': 'user1', 'id': 3001}, {'user': 'user2', 'id': 3002}]
145 monkeypatch
.setattr(gitlab
.Gitlab
, 'getusers', mock_getusers
)
147 def mock_getproject(self
, repo
):
151 monkeypatch
.setattr(gitlab
.Gitlab
, 'getproject', mock_getproject
)
153 def mock_getprojecthooks(self
, repo
):
154 if repo
== "(missing)":
157 return [{'id': 'hook_id', 'merge_requests_events': True, 'url': 'url'}]
158 monkeypatch
.setattr(gitlab
.Gitlab
, 'getprojecthooks', mock_getprojecthooks
)
160 # Waiting on Gitlab 8.9 or greater
161 # def mock_getapprovals(id):
163 # monkeypatch.setattr(gitlab.Gitlab, 'getapprovals', mock_getapprovals)
165 def mock_getmergerequests(self
, project
, page
, per_page
, state
=None):
167 monkeypatch
.setattr(gitlab
.Gitlab
, 'getmergerequests', mock_getmergerequests
)
169 def mock_addprojecthook(self
, project
, url
, **kwargs
):
171 monkeypatch
.setattr(gitlab
.Gitlab
, 'addprojecthook', mock_addprojecthook
)
173 def mock_editprojecthook(self
, project
, hook_id
, url
, **kwargs
):
175 monkeypatch
.setattr(gitlab
.Gitlab
, 'editprojecthook', mock_editprojecthook
)
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):
183 # monkeypatch.setattr('plugins.marge.MargeGitlab.addprojecthook_extra', mock_addprojecthook_extra)
185 # def mock_editprojecthook_extra(self, project, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
187 # monkeypatch.setattr('plugins.marge.MargeGitlab.editprojecthook_extra', mock_editprojecthook_extra)
188 # return marge.MargeGitlab
191 def gitlab_no_reviews(self
, gitlab
, monkeypatch
):
192 def mock_getmergerequest(self
, project
, iid
):
194 monkeypatch
.setattr(gitlab
, 'getuser', mock_getmergerequest
)
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',
205 'web_url': 'http://gitlab.example.com/sample/mr/2001',
206 'work_in_progress': False}
208 def mock_getmergerequest(self
, project
, iid
):
210 monkeypatch
.setattr(gitlab
, 'getmergerequest', mock_getmergerequest
)
212 def mock_getmergerequests(self
, project
, page
, per_page
, state
=None):
213 if state
and state
== 'opened':
217 monkeypatch
.setattr(gitlab
, 'getmergerequests', mock_getmergerequests
)
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
)
227 # ============================================================================
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
):
241 tdelta
= (now
+ rdelta
) - now
242 assert deltastr(tdelta
) == expected
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
251 def test_good_bot(self
, margebot
):
252 margebot
.push_message('good bot')
253 assert 'Best bot' in margebot
.pop_message()
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()
259 # assert 'just kidding' in margebot.pop_message()
261 def test_webstatus(self
, margebot
):
262 margebot
.push_message('!webstatus')
263 assert 'margebot/<rooms>' in margebot
.pop_message()
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
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
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
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
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 '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_dont_blame_margebot(self
, margebot
):
411 margebot
.push_message('I blame margebot')
412 assert "say it ain't so" in margebot
.pop_message()
414 def test_margefest(self
, margebot
):
415 margebot
.push_message('magfest')
416 assert 'MargeFest' in margebot
.pop_message()
418 def test_margebot_sucks(self
, margebot
):
419 margebot
.push_message('margebot sucks')
420 assert 'steering committee' in margebot
.pop_message()
422 def test_margebot_unknown_command(self
, margebot
):
423 margebot
.push_message('Margebot, alacazam')
424 with pytest
.raises(Empty
):
425 margebot
.pop_message()