There be tests ! And a massive code reorg to support the tests, And pep8 and pylint...
[margebot.git] / tests / test_marge.py
1 """
2 Mrgebot 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
16 import pytest
17 import errbot
18 from errbot.backends.test import testbot # pylint: disable=unused-import
19 import gitlab
20 from dateutil.relativedelta import relativedelta
21 from plugins.marge import deltastr
22
23
24 class TestMarge(object):
25 """
26 Margebot Tests
27
28 Run 'py.test' from the margebot root directory
29 """
30
31 extra_plugin_dir = "./plugins/"
32 loglevel = logging.INFO
33
34 @pytest.fixture
35 def margebot(self, testbot, monkeypatch, mocker):
36 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}")
37 testbot.pop_message()
38 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}}")
39 testbot.pop_message()
40
41 def mock_keys(self):
42 return ['OPEN_MRS']
43 monkeypatch.setattr(errbot.storage.StoreMixin, 'keys', mock_keys, raising=False)
44
45 return testbot
46
47 @pytest.fixture
48 def margebot_no_reviews(self, margebot, monkeypatch):
49
50 def mock_get(self, key):
51 return {}
52
53 monkeypatch.setattr(errbot.storage.StoreMixin, '__getitem__', mock_get, raising=False)
54 return margebot
55
56 @pytest.fixture
57 def margebot_one_review(self, margebot, monkeypatch):
58
59 def mock_get(self, key):
60 return {(1001, 2001, 'room1,room2'): True} # [(project, iid, notify_rooms)]
61
62 monkeypatch.setattr(errbot.storage.StoreMixin, '__getitem__', mock_get, raising=False)
63 return margebot
64
65 @pytest.fixture
66 def one_wip_review(self, margebot_one_review, monkeypatch):
67
68 def mock_getmergerequest(self, project, iid):
69 return {'author': {'id': 2001},
70 'created_at': 'Oct 29, 2017 2:37am',
71 'merge_status': 'can_be_merged',
72 'state': 'opened',
73 'upvotes': 0,
74 'web_url': 'http://gitlab.example.com/sample/mr/2001',
75 'work_in_progress': True}
76 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
77 return margebot_one_review
78
79 @pytest.fixture
80 def one_waiting_review(self, margebot_one_review, monkeypatch):
81
82 def mock_getmergerequest(self, project, iid):
83 return {'author': {'id': 2001},
84 'created_at': 'Oct 29, 2017 2:37am',
85 'merge_status': 'can_be_merged',
86 'state': 'opened',
87 'upvotes': 1,
88 'web_url': 'http://gitlab.example.com/sample/mr/2001',
89 'work_in_progress': False}
90 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
91 return margebot_one_review
92
93 @pytest.fixture
94 def one_conflicted_review(self, margebot_one_review, monkeypatch):
95
96 def mock_getmergerequest(self, project, iid):
97 return {'author': {'id': 2001},
98 'created_at': 'Oct 29, 2017 2:37am',
99 'merge_status': 'merge_conflicts',
100 'state': 'opened',
101 'upvotes': 2,
102 'web_url': 'http://gitlab.example.com/sample/mr/2001',
103 'work_in_progress': False}
104 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
105 return margebot_one_review
106
107 @pytest.fixture
108 def one_mergable_review(self, margebot_one_review, monkeypatch):
109
110 def mock_getmergerequest(self, project, iid):
111 return {'author': {'id': 2001},
112 'created_at': 'Oct 29, 2017 2:37am',
113 'merge_status': 'can_be_merged',
114 'state': 'opened',
115 'upvotes': 2,
116 'web_url': 'http://gitlab.example.com/sample/mr/2001',
117 'work_in_progress': False}
118 monkeypatch.setattr(gitlab.Gitlab, 'getmergerequest', mock_getmergerequest)
119 return margebot_one_review
120
121 @pytest.fixture
122 def gitlab(self, monkeypatch):
123
124 def mock_getuser(self, user):
125 if user == '(missing)':
126 return False
127 else:
128 return {'username': user + ' username'}
129 monkeypatch.setattr(gitlab.Gitlab, 'getuser', mock_getuser)
130
131 def mock_getusers(self, search=None):
132 if search:
133 ((_, val)) = search
134 if val == "(missing)":
135 return []
136 else:
137 return [{'user': val, 'id': 3001}]
138 else:
139 return [{'user': 'user1', 'id': 3001}, {'user': 'user2', 'id': 3002}]
140 monkeypatch.setattr(gitlab.Gitlab, 'getusers', mock_getusers)
141
142 # Waiting on Gitlab 8.9 or greater
143 # def mock_getapprovals(id):
144 # return []
145 # monkeypatch.setattr(gitlab.Gitlab, 'getapprovals', mock_getapprovals)
146
147 return gitlab.Gitlab
148
149 @pytest.fixture
150 def gitlab_no_reviews(self, gitlab, monkeypatch):
151 def mock_getmergerequest(self, project, iid):
152 return {}
153 monkeypatch.setattr(gitlab, 'getuser', mock_getmergerequest)
154
155 @pytest.fixture
156 def gitlab_one_review(self, gitlab, monkeypatch):
157 def mock_getmergerequest(self, project, iid):
158 return {'author': {'id': 2001},
159 'created_at': 'Oct 29, 2017 2:37am',
160 'merge_status': 'can_be_merged',
161 'state': 'opened',
162 'upvotes': 0,
163 'web_url': 'http://gitlab.example.com/sample/mr/2001',
164 'work_in_progress': False}
165 monkeypatch.setattr(gitlab, 'getmergerequest', mock_getmergerequest)
166
167 # ============================================================================
168
169 @pytest.mark.parametrize("rdelta,expected", [
170 (relativedelta(days=1), "1 day ago"),
171 (relativedelta(days=2), "2 days ago"),
172 (relativedelta(days=1, hours=1), "1 day, 1 hour ago"),
173 (relativedelta(days=2, hours=2), "2 days, 2 hours ago"),
174 (relativedelta(hours=2), "2 hours ago"),
175 (relativedelta(days=1, minutes=23), "1 day, 23 minutes ago"),
176 (relativedelta(minutes=2), "2 minutes ago"),
177 (relativedelta(minutes=1), "1 minute ago"),
178 (relativedelta(minutes=0), "now"), ])
179 def test_deltastr(self, rdelta, expected):
180 now = datetime.now()
181 tdelta = (now + rdelta) - now
182 assert deltastr(tdelta) == expected
183
184 def test_help(self, margebot):
185 margebot.push_message('!help')
186 help_message = margebot.pop_message()
187 assert "Marge" in help_message
188 assert '!reviews' in help_message
189 assert '!hello' in help_message
190 assert '!xyzzy' in help_message
191
192 def test_hello(self, margebot):
193 margebot.push_message('!hello')
194 assert 'Hi there' in margebot.pop_message()
195
196 # def test_xyzzy(self, margebot):
197 # margebot.push_message('!xyzzy')
198 # assert 'All open MRs have been merged into master' in margebot.pop_message()
199 # time.sleep(6)
200 # assert 'just kidding' in margebot.pop_message()
201
202 def test_webstatus(self, margebot):
203 margebot.push_message('!webstatus')
204 assert 'margebot/<rooms>' in margebot.pop_message()
205
206 def test_gitlab_hook(self, margebot, gitlab):
207 request = json.dumps({'object_kind': 'merge_request',
208 'object_attributes': {
209 'state': 'opened',
210 'work_in_progress': '',
211 'title': 'title',
212 'author_id': 'author_id',
213 'target_project_id': 'project_id',
214 'iid': 'iid'},
215 'project': {
216 'homepage': 'url'}})
217 margebot.push_message("!webhook test /margebot/room1,room2 " + request)
218 assert 'New Review: author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
219 margebot.push_message('!reviews')
220 assert 'New Review: author_id username has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
221
222 def test_gitlab_hook_wip(self, margebot, gitlab):
223 request = json.dumps({'object_kind': 'merge_request',
224 'object_attributes': {
225 'state': 'opened',
226 'work_in_progress': 'true',
227 'title': 'title',
228 'author_id': 'author_id',
229 'target_project_id': 'project_id',
230 'iid': 'iid'},
231 'project': {
232 'homepage': 'url'}})
233 margebot.push_message("!webhook test /margebot/room1,room2 " + request)
234 assert 'New Review: author_id username has opened a new WIP MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
235 margebot.push_message('!reviews')
236 assert 'New Review: author_id username has opened a new WIP MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
237
238 def test_gitlab_hook_unexpected_user(self, margebot, gitlab):
239 request = json.dumps({'object_kind': 'merge_request',
240 'object_attributes': {
241 'state': 'opened',
242 'work_in_progress': '',
243 'title': 'title',
244 'author_id': '(missing)',
245 'target_project_id': 'project_id',
246 'iid': 'iid'},
247 'project': {
248 'homepage': 'url'}})
249
250 margebot.push_message("!webhook test /margebot/room1,room2 " + request)
251 assert 'New Review: (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
252 margebot.push_message('!reviews')
253 assert 'New Review: (missing) has opened a new MR: \"title\"\nurl/merge_requests/iid' in margebot.pop_message()
254
255 def test_gitlab_hook_unexpected_object_kind(self, margebot_no_reviews, gitlab, caplog):
256 request = json.dumps({'object_kind': 'not_merge_request',
257 'object_attributes': {
258 'state': 'opened',
259 'work_in_progress': '',
260 'title': 'title',
261 'author_id': 'author_id',
262 'target_project_id': 'project_id',
263 'iid': 'iid'},
264 'project': {
265 'homepage': 'url'}})
266
267 margebot_no_reviews.push_message("!webhook test /margebot/room1,room2 " + request)
268 margebot_no_reviews.pop_message()
269 margebot_no_reviews.push_message('!reviews')
270 assert 'Hi gbin: I found no open MRs for you.' in margebot_no_reviews.pop_message()
271 assert 'unexpecting object_kind: not_merge_request' in caplog.text # Has to be at end of method
272
273 def test_nothing_to_review(self, margebot_no_reviews, gitlab_no_reviews):
274 margebot_no_reviews.push_message('!reviews')
275 assert 'Hi gbin: I found no open MRs for you.' in margebot_no_reviews.pop_message()
276
277 def test_get_one_open_mr(self, margebot_one_review, gitlab_one_review):
278 margebot_one_review.push_message('!reviews')
279 output = margebot_one_review.pop_message()
280 assert 'These MRs need some attention' in output
281 assert 'http://gitlab.example.com/sample/mr/2001' in output
282 assert 'No upvotes, please review.' in output
283
284 def test_get_one_wip_mr(self, one_wip_review, gitlab):
285 one_wip_review.push_message('!reviews')
286 output = one_wip_review.pop_message()
287 assert 'These MRs need some attention' in output
288 assert 'http://gitlab.example.com/sample/mr/2001' in output
289 assert 'No upvotes, please review but still WIP.' in output
290
291 def test_get_one_waiting_mr(self, margebot, one_waiting_review, gitlab):
292 one_waiting_review.push_message('!reviews')
293 output = margebot.pop_message()
294 assert 'These MRs need some attention' in output
295 assert 'http://gitlab.example.com/sample/mr/2001' in output
296 assert 'Waiting for another upvote.' in output
297
298 def test_get_one_mergable_mr(self, margebot, one_mergable_review, gitlab):
299 one_mergable_review.push_message('!reviews')
300 output = margebot.pop_message()
301 assert 'These MRs need some attention' in output
302 assert 'http://gitlab.example.com/sample/mr/2001' in output
303 assert 'Has 2+ upvotes and could be merged in now.' in output
304
305 def test_get_one_conflicted_mr(self, margebot, one_conflicted_review, gitlab):
306 one_conflicted_review.push_message('!reviews')
307 output = margebot.pop_message(timeout=1)
308 assert 'These MRs need some attention' in output
309 assert 'http://gitlab.example.com/sample/mr/2001' in output
310 assert 'Has 2+ upvotes and could be merged in now except there are merge conflicts.' in output
311
312 def test_crontab_hook(self, one_waiting_review, gitlab, monkeypatch, mocker):
313 plugin = one_waiting_review._bot.plugin_manager.get_plugin_obj_by_name('Marge')
314
315 def mock_rooms():
316 return [mocker.MagicMock(node='room1'), mocker.MagicMock(node='room2')]
317 monkeypatch.setattr(plugin, 'rooms', mock_rooms)
318
319 plugin.crontab_hook("unused")
320 output = one_waiting_review.pop_message()
321 assert 'These MRs need some attention' in output
322 assert 'http://gitlab.example.com/sample/mr/2001 (opened' in output