Coverage for CIResults/tests/test_bugtrackers.py: 100%
1273 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-19 09:20 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-19 09:20 +0000
1from unittest.mock import call, patch, MagicMock, PropertyMock
2from django.test import TestCase, TransactionTestCase
3from django.core.exceptions import MultipleObjectsReturned
4from django.db import IntegrityError
5from dateutil import parser as dateparser
6from django.utils import timezone
8from CIResults.models import BugTracker, Bug, Person, BugTrackerAccount, BugComment, ReplicationScript
9from CIResults.bugtrackers import BugTrackerCommon, Bugzilla, Untracked, Jira, GitLab, BugCommentTransport
10from CIResults.serializers import serialize_bug
12from collections import namedtuple
13from jira.exceptions import JIRAError
14from jira.client import ResultList
15import urllib.parse
16import xmlrpc.client
17import requests
18import datetime
19from datetime import timedelta
20import pytz
21import copy
24class BugTrackerCommonTests(TestCase):
25 fields = {'title': "Kwyjibo",
26 'status': "D'oh",
27 'description': "A big, dumb, balding North American ape.",
28 'product': "The Simpsons",
29 'platforms': "TV",
30 'priority': "High",
31 'component': "Homer"}
33 @patch('CIResults.models.BugTrackerAccount.objects.filter', return_value=[BugTrackerAccount(user_id="1"),
34 BugTrackerAccount(user_id="2"),
35 BugTrackerAccount(user_id="3")])
36 def test_account_cached(self, filter_mocked):
37 db_tracker = BugTracker(name="Tracker1", public=True)
38 common = BugTrackerCommon(db_tracker)
39 accounts = common.accounts_cached
41 filter_mocked.assert_called_with(tracker=db_tracker)
42 self.assertEqual(accounts, {"1": filter_mocked.return_value[0],
43 "2": filter_mocked.return_value[1],
44 "3": filter_mocked.return_value[2]})
46 # Check that the corresponding account is returned if it exists
47 def test_find_or_create_account__existing(self):
48 user_id = "my id"
50 db_tracker = BugTracker(name="Tracker1", public=True)
51 common = BugTrackerCommon(db_tracker)
52 common.accounts_cached = {user_id: MagicMock()}
54 account = common.find_or_create_account(user_id)
55 self.assertEqual(account, common.accounts_cached[user_id])
56 self.assertEqual(Person.objects.all().count(), 0)
57 self.assertEqual(BugTrackerAccount.objects.all().count(), 0)
59 # Check that a new account is created when it does not exist yet, then
60 # that subsequent changes get registered
61 def test_find_or_create_account(self):
62 user_id = "my id"
63 name = "John Doe"
64 email = "me@email.com"
66 db_tracker = BugTracker.objects.create(name="Tracker1", public=True)
67 common = BugTrackerCommon(db_tracker)
68 account = common.find_or_create_account(user_id, email=email, full_name=name)
70 self.assertEqual(account.tracker, db_tracker)
71 self.assertEqual(account.user_id, user_id)
72 self.assertEqual(account.is_developer, False)
73 self.assertEqual(account.person.full_name, name)
74 self.assertEqual(account.person.email, email)
76 name2 = "John Doe 2"
77 common.find_or_create_account(user_id, email=email, full_name=name2)
78 account = BugTrackerAccount.objects.get(user_id=user_id)
79 self.assertEqual(account.person.full_name, name2)
80 self.assertEqual(account.person.email, email)
82 email2 = "me2@email.com"
83 common.find_or_create_account(user_id, email=email2, full_name=name)
84 account = BugTrackerAccount.objects.get(user_id=user_id)
85 self.assertEqual(account.person.full_name, name)
86 self.assertEqual(account.person.email, email2)
88 def test_create_bug(self):
89 db_tracker = BugTracker.objects.create(name="Tracker1", project="FOO", public=True)
90 common = BugTrackerCommon(db_tracker)
92 bug = Bug.objects.create(tracker=db_tracker, **self.fields)
93 common.create_bug_from_json = MagicMock()
94 common.create_bug(bug)
95 out_fields = common.create_bug_from_json.call_args[0][0]
96 for field in self.fields:
97 self.assertEqual(self.fields[field], out_fields[field])
99 def test_create_bug_existing(self):
100 db_tracker = BugTracker.objects.create(name="Tracker1", project="FOO", public=True)
101 common = BugTrackerCommon(db_tracker)
103 bug = Bug.objects.create(tracker=db_tracker, bug_id=10, **self.fields)
104 common.create_bug_from_json = MagicMock()
105 with self.assertRaises(ValueError):
106 common.create_bug(bug)
108 def test_create_bug_no_project(self):
109 db_tracker = BugTracker.objects.create(name="Tracker1", public=True)
110 common = BugTrackerCommon(db_tracker)
112 bug = Bug.objects.create(tracker=db_tracker, **self.fields)
113 common.create_bug_from_json = MagicMock()
114 with self.assertRaises(ValueError):
115 common.create_bug(bug)
117 def test__parse_custom_field(self):
118 db_tracker = BugTracker.objects.create(name="Tracker1", public=True)
119 common = BugTrackerCommon(db_tracker)
121 self.assertEqual('[1, 2, 3]', common._parse_custom_field([1, 2, 3], to_str=True))
122 self.assertEqual([1, 2, 3], common._parse_custom_field([1, 2, 3], to_str=False))
125class SandboxMock():
126 @classmethod
127 def get_or_create_instance(cls, script):
128 return cls(script)
130 def __init__(self, script):
131 self.script = script
133 def call_user_function(self, fn, kwargs):
134 code = compile(self.script, "<user script>", 'exec')
135 exec(code)
136 return locals()[fn](**kwargs)
139@patch('CIResults.bugtrackers.Client', SandboxMock)
140class BugTrackerReplicationTests(TestCase):
141 def setUp(self):
142 self.title = "We are the knights who say..."
143 self.description = "Ni!"
144 self.script = """\
145def replication_check(src_bug, dest_bug):
146 if int(src_bug['bug_id']) % 2 == 0:
147 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']},
148 "add_comments": ["apple", "pie"]}
149 else:
150 return {}
151 """
152 self.db_tracker = BugTracker.objects.create(name="Tracker1", project="TEST", tracker_type="bugzilla",
153 url="http://bar", public=True)
154 self.rep_tracker = BugTracker.objects.create(name="Tracker2", tracker_type="jira", url="http://foo",
155 project="TEST2", public=True)
156 self.tracker = Untracked(self.db_tracker)
157 self.tracker2 = Untracked(self.rep_tracker)
159 self.rp = ReplicationScript.objects.create(source_tracker=self.db_tracker,
160 destination_tracker=self.rep_tracker,
161 script=self.script,
162 enabled=True,
163 name="BAR")
165 self.bug = Bug.objects.create(tracker=self.db_tracker, bug_id=2,
166 title=self.title, description=self.description)
168 def get_mirrored_bug_if_created(self, bug, bug_id=4, comments=None, ret_mocks=False):
169 comments = comments if comments else []
170 with patch('CIResults.bugtrackers.Jira.create_bug_from_json', MagicMock()) as create_mock:
171 with patch('CIResults.bugtrackers.Jira.poll', autospec=True):
172 with patch('CIResults.bugtrackers.Jira.add_comment', MagicMock()) as add_comm_mock:
173 create_mock.return_value = bug_id
174 self.tracker.check_replication(bug, comments)
175 bug = Bug.objects.filter(parent=bug).first()
176 if ret_mocks:
177 return (bug, create_mock, add_comm_mock)
178 else:
179 return bug
181 def get_updated_bug(self, bug):
182 with patch('CIResults.bugtrackers.Jira.update_bug_from_json', MagicMock()) as upd_mock:
183 with patch('CIResults.bugtrackers.Jira.poll', autospec=True):
184 with patch('CIResults.bugtrackers.Jira.add_comment', MagicMock()) as add_comm_mock:
185 self.tracker.check_replication(bug, [])
186 return upd_mock, add_comm_mock
188 def test_tracker_check_replication(self):
189 client = MagicMock()
190 client.call_user_function = MagicMock()
191 client.call_user_function.return_value = {"set_fields": {}}
193 resp = self.tracker.tracker_check_replication([self.bug], self.rep_tracker, self.script, client, dryrun=True)
194 ser_bug = serialize_bug(self.bug)
195 client.call_user_function.assert_called_with("replication_check", kwargs={"src_bug": ser_bug,
196 "dest_bug": None})
197 self.assertEqual(resp[0]["operation"], "create")
199 def test_tracker_check_replication_update(self):
200 client = MagicMock()
201 bug = Bug.objects.create(tracker=self.rep_tracker, parent=self.bug)
203 client.call_user_function = MagicMock()
204 client.call_user_function.return_value = {"set_fields": {}}
206 resp = self.tracker.tracker_check_replication([self.bug], self.rep_tracker, self.script, client, dryrun=True)
207 ser_bug = serialize_bug(self.bug)
208 ser_dest_bug = serialize_bug(bug)
209 client.call_user_function.assert_called_with("replication_check", kwargs={"src_bug": ser_bug,
210 "dest_bug": ser_dest_bug})
211 self.assertEqual(resp[0]["operation"], "update")
213 def test_tracker_check_replication_invalid_bug(self):
214 client = MagicMock()
215 bug = Bug(tracker=self.db_tracker)
216 resp = self.tracker.tracker_check_replication([bug], self.rep_tracker, self.script, client, dryrun=True)
217 self.assertEqual(resp, [])
219 bug.parent = self.bug
220 bug.save()
222 resp = self.tracker.tracker_check_replication([bug], self.rep_tracker, self.script, client, dryrun=True)
223 self.assertEqual(resp, [])
225 def test_tracker_check_replication_client_error(self):
226 client = MagicMock()
227 client.call_user_function = MagicMock(side_effect=Exception())
228 client.call_user_function.return_value = {"set_fields": {}}
230 resp = self.tracker.tracker_check_replication([self.bug], self.rep_tracker, self.script, client, dryrun=True)
231 self.assertEqual(resp, [])
233 def test_check_replication(self):
234 m_bug, mock, _ = self.get_mirrored_bug_if_created(self.bug, ret_mocks=True)
235 self.assertIsNotNone(m_bug)
236 mock.assert_called_with({'description': self.description, 'title': self.title})
238 def test_check_replication_comments(self):
239 name = "Ada"
240 email = "foo@bar.com"
241 body = "this is a test"
242 body2 = "this is also a test"
243 person = Person.objects.create(full_name=name)
244 person2 = Person.objects.create(email=email)
245 account = BugTrackerAccount.objects.create(person=person, user_id="1",
246 tracker=self.db_tracker, is_developer=True)
247 account2 = BugTrackerAccount.objects.create(person=person2, user_id="2",
248 tracker=self.db_tracker, is_developer=True)
249 created = datetime.datetime.now()
250 created2 = datetime.datetime.now() + timedelta(days=1)
251 comment = BugComment.objects.create(bug=self.bug, account=account,
252 created_on=created, comment_id="1")
253 comment2 = BugComment.objects.create(bug=self.bug, account=account2,
254 created_on=created2, comment_id="2")
255 new_comments = [BugCommentTransport(comment, body), BugCommentTransport(comment2, body2)]
256 exp_comm = [{'author': name,
257 'created': str(created),
258 'body': body},
259 {'author': email,
260 'created': str(created2),
261 'body': body2}]
263 with patch('CIResults.bugtrackers.Client.call_user_function') as cuf_mock:
264 m_bug, mock, _ = self.get_mirrored_bug_if_created(self.bug, comments=new_comments, ret_mocks=True)
265 self.assertIsNotNone(m_bug)
266 src_bug_comments = cuf_mock.call_args[1]['kwargs']['src_bug']['new_comments']
267 self.assertEqual(src_bug_comments, exp_comm)
269 def test_check_replication_add_comments_string(self):
270 self.rp.delete()
271 script = """\
272def replication_check(src_bug, dest_bug):
273 if int(src_bug['bug_id']) % 2 == 0:
274 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']},
275 "add_comments": "Doh"}
276 else:
277 return {}
278 """
279 ReplicationScript.objects.create(source_tracker=self.db_tracker,
280 destination_tracker=self.rep_tracker,
281 script=script,
282 enabled=True,
283 name="BAR")
284 m_bug, mock, add_comm_mock = self.get_mirrored_bug_if_created(self.bug, ret_mocks=True)
285 self.assertIsNotNone(m_bug)
286 mock.assert_called_with({'description': self.description, 'title': self.title})
287 add_comm_mock.assert_called_with(m_bug, "Doh")
289 def test_check_replication_add_comments_list(self):
290 self.rp.delete()
291 script = """\
292def replication_check(src_bug, dest_bug):
293 if int(src_bug['bug_id']) % 2 == 0:
294 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']},
295 "add_comments": ["hello","world","foo"]}
296 else:
297 return {}
298 """
299 ReplicationScript.objects.create(source_tracker=self.db_tracker,
300 destination_tracker=self.rep_tracker,
301 script=script,
302 enabled=True)
304 m_bug, mock, add_comm_mock = self.get_mirrored_bug_if_created(self.bug, ret_mocks=True)
305 self.assertIsNotNone(m_bug)
306 mock.assert_called_with({'description': self.description, 'title': self.title})
307 add_comm_mock.assert_has_calls([call(m_bug, "hello"), call(m_bug, "world"), call(m_bug, "foo")])
309 def test_check_replication_add_comments_no_comment(self):
310 self.rp.delete()
311 script = """\
312def replication_check(src_bug, dest_bug):
313 if int(src_bug['bug_id']) % 2 == 0:
314 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']}}
315 else:
316 return {}
317 """
318 ReplicationScript.objects.create(source_tracker=self.db_tracker,
319 destination_tracker=self.rep_tracker,
320 script=script,
321 enabled=True)
323 m_bug, mock, add_comm_mock = self.get_mirrored_bug_if_created(self.bug, ret_mocks=True)
324 self.assertIsNotNone(m_bug)
325 mock.assert_called_with({'description': self.description, 'title': self.title})
326 add_comm_mock.assert_not_called()
328 def test_check_replication_db_fields_update(self):
329 self.rp.delete()
330 script = """\
331def replication_check(src_bug, dest_bug):
332 if int(src_bug['bug_id']) % 2 == 0:
333 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']},
334 "add_comments": "Doh",
335 "db_dest_fields_update": {'severity': 'High'},
336 "db_src_fields_update": {'priority': 'Low'}}
337 else:
338 return {}
339 """
340 ReplicationScript.objects.create(source_tracker=self.db_tracker,
341 destination_tracker=self.rep_tracker,
342 script=script,
343 enabled=True,
344 name="BAR")
345 m_bug = self.get_mirrored_bug_if_created(self.bug)
346 self.assertEqual(m_bug.severity, 'High')
347 self.assertEqual(self.bug.priority, 'Low')
348 m_bug.severity = None
349 self.bug.priority = None
350 m_bug.save()
351 _, _ = self.get_updated_bug(self.bug)
352 m_bug.refresh_from_db()
353 self.assertEqual(m_bug.severity, 'High')
354 self.assertEqual(self.bug.priority, 'Low')
356 def test_check_replication_db_fields_update_deprecated(self):
357 self.rp.delete()
358 script = """\
359def replication_check(src_bug, dest_bug):
360 if int(src_bug['bug_id']) % 2 == 0:
361 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']},
362 "add_comments": "Doh",
363 "db_fields_update": {'severity': 'High'}}
364 else:
365 return {}
366 """
367 ReplicationScript.objects.create(source_tracker=self.db_tracker,
368 destination_tracker=self.rep_tracker,
369 script=script,
370 enabled=True,
371 name="BAR")
372 m_bug = self.get_mirrored_bug_if_created(self.bug)
373 self.assertEqual(m_bug.severity, 'High')
374 m_bug.severity = None
375 m_bug.save()
376 _, _ = self.get_updated_bug(self.bug)
377 m_bug.refresh_from_db()
378 self.assertEqual(m_bug.severity, 'High')
380 def test_check_replication_db_fields_update_empty(self):
381 self.rp.delete()
382 script = """\
383def replication_check(src_bug, dest_bug):
384 if int(src_bug['bug_id']) % 2 == 0:
385 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']},
386 "add_comments": "Doh",
387 "db_dest_fields_update": {},
388 "db_src_fields_update": {}}
389 else:
390 return {}
391 """
392 ReplicationScript.objects.create(source_tracker=self.db_tracker,
393 destination_tracker=self.rep_tracker,
394 script=script,
395 enabled=True,
396 name="BAR")
397 with patch('CIResults.bugtrackers.Bug.update_from_dict') as upd_mock:
398 self.get_mirrored_bug_if_created(self.bug)
399 upd_mock.assert_called_with(None)
400 with patch('CIResults.bugtrackers.Bug.update_from_dict') as upd_mock:
401 _, _ = self.get_updated_bug(self.bug)
402 upd_mock.assert_called_with(None)
404 def test_check_replication_update(self):
405 new_bug = self.get_mirrored_bug_if_created(self.bug)
406 self.assertIsNotNone(new_bug)
407 upd_mock, _ = self.get_updated_bug(self.bug)
408 upd_mock.assert_called_with({'description': 'Ni!', 'title': 'We are the knights who say...'},
409 new_bug.bug_id)
411 @patch('CIResults.bugtrackers.BugTrackerCommon._replication_add_comments')
412 def test_check_replication_update_add_comments(self, add_comm_mock):
413 new_bug = self.get_mirrored_bug_if_created(self.bug)
414 self.assertIsNotNone(new_bug)
415 upd_mock, _ = self.get_updated_bug(self.bug)
416 upd_mock.assert_called_with({'description': 'Ni!', 'title': 'We are the knights who say...'},
417 new_bug.bug_id)
418 add_comm_mock.assert_called_with(new_bug, ["apple", "pie"])
420 def test_check_replication_update_error(self):
421 new_bug = self.get_mirrored_bug_if_created(self.bug)
422 self.assertIsNotNone(new_bug)
423 with patch('CIResults.bugtrackers.Jira.update_bug_from_json', MagicMock()) as upd_mock:
424 with patch('CIResults.bugtrackers.Jira.poll', autospec=True) as poll_mock:
425 upd_mock.side_effect = ValueError()
426 self.tracker.check_replication(self.bug, [])
427 poll_mock.assert_not_called()
429 @patch('CIResults.bugtrackers.Jira')
430 @patch('CIResults.bugtrackers.GitLab')
431 def test_check_replication_two_scripts(self, gitlab_mock, jira_mock):
432 dest_tracker = BugTracker.objects.create(name="TrackerFoo", tracker_type="gitlab", url="http://foo",
433 project="TESTFOO", public=True)
434 ReplicationScript.objects.create(source_tracker=self.db_tracker,
435 destination_tracker=dest_tracker,
436 script=self.script,
437 enabled=True,
438 name="FOO")
440 jira_mock.return_value.create_bug_from_json.return_value = 11
441 gitlab_mock.return_value.create_bug_from_json.return_value = 12
443 self.tracker.check_replication(self.bug, [])
444 bugs = Bug.objects.filter(parent=self.bug)
445 self.assertEqual(len(bugs), 2)
446 self.assertIsNotNone(bugs.get(tracker=self.rep_tracker))
447 self.assertIsNotNone(bugs.get(tracker=dest_tracker))
449 def test_check_invalid_replication(self):
450 with patch('CIResults.bugtrackers.Jira.create_bug_from_json', MagicMock()) as mock:
451 mock.side_effect = ValueError
452 self.tracker.check_replication(self.bug, [])
453 m_bug = Bug.objects.filter(parent=self.bug).first()
454 self.assertIsNone(m_bug)
456 def test_check_replication_already_mirrored(self):
457 new_bug = self.get_mirrored_bug_if_created(self.bug)
458 with patch('CIResults.bugtrackers.Jira.update_bug_from_json', MagicMock()) as upd_mock:
459 with patch('CIResults.bugtrackers.Jira.poll', autospec=True):
460 with patch('CIResults.bugtrackers.Jira.add_comment', MagicMock()):
461 self.tracker.check_replication(self.bug, [])
463 upd_mock.assert_called_with({'description': 'Ni!', 'title': 'We are the knights who say...'},
464 new_bug.bug_id)
465 try:
466 Bug.objects.get(parent=self.bug)
467 except MultipleObjectsReturned: # pragma: no cover
468 self.fail("New Bug shouldn't have been created") # pragma: no cover
470 def test_check_replication_replicated_bug(self):
471 m_bug = self.get_mirrored_bug_if_created(self.bug)
472 self.assertIsNotNone(m_bug)
473 m_bug2 = self.get_mirrored_bug_if_created(m_bug)
474 self.assertIsNone(m_bug2)
476 def test_check_replication_disabled(self):
477 self.rp.delete()
478 ReplicationScript.objects.create(source_tracker=self.db_tracker,
479 destination_tracker=self.rep_tracker,
480 script=self.script,
481 enabled=False)
482 with patch('CIResults.bugtrackers.Client.call_user_function', MagicMock()) as chk_mock:
483 self.assertIsNone(self.get_mirrored_bug_if_created(self.bug))
484 chk_mock.assert_not_called()
486 def test_check_replication_no_script(self):
487 self.rp.delete()
488 with patch('CIResults.bugtrackers.Client.call_user_function', MagicMock()) as chk_mock:
489 self.assertIsNone(self.get_mirrored_bug_if_created(self.bug))
490 chk_mock.assert_not_called()
492 def test_check_replication_no_match(self):
493 bug = Bug.objects.create(tracker=self.db_tracker, bug_id=1,
494 title=self.title, description=self.description)
495 self.assertIsNone(self.get_mirrored_bug_if_created(bug))
497 def test_check_replication_fail_save(self):
498 with patch('CIResults.models.Bug.save', MagicMock()) as val_mock:
499 val_mock.side_effect = IntegrityError("Non unique bug")
500 self.assertIsNone(self.get_mirrored_bug_if_created(self.bug))
502 def test_check_replication_no_fields(self):
503 with patch('CIResults.bugtrackers.Client.call_user_function', MagicMock()) as chk_mock:
504 chk_mock.return_value = None
505 self.assertIsNone(self.get_mirrored_bug_if_created(self.bug))
508class RequestsGetMock():
509 PRIVATE_TOKEN = "qwerttyzxcfdsapjdpfa"
510 BUG_ID = 2
511 BUG_CREATED_AT = '2018-10-04T11:20:48.531Z'
512 BUG_UPDATED_AT = '2018-11-28T13:24:13.325Z'
513 CREATOR_NAME = 'Creator Name'
514 ASSIGNEE_NAME = 'Assignee Name'
515 NOTE_ONE_ID = 83161
516 NOTE_ONE_BODY = "Still Alive"
517 NOTE_ONE_CREATOR_NAME = 'Note Creator'
518 NOTE_TWO_CREATED_AT = '2018-11-28T13:24:13.290Z'
519 NOTE_ONE_CREATED_AT = '2018-10-04T12:35:03.299Z'
520 NOTE_TWO_BODY = "Oh. Hi. So. How are you holding up? BECAUSE I'M A POTATO!"
521 BUG_TITLE = 'super bug title'
522 BUG_STATUS = 'opened'
523 BUG_DESCRIPTION = 'the cake is a lie'
525 BUG_PRODUCT = 'PrOdUcT'
526 BUG_COMPONENT = 'cOmPoNeNt'
527 BUG_PRIORITY = 'pRiOrItY'
528 BUG_SEVERITY = 'sEvErItY'
529 BUG_PLATFORM1 = 'pLaTfOrM 1'
530 BUG_PLATFORM2 = 'pLaTfOrM 2'
531 BUG_FEATURE1 = 'fEaTuRe 1'
532 BUG_FEATURE2 = 'fEaTuRe 2'
533 BUG_TARGET = 'bug_target'
534 BUG_MAP_SEVERITY = 'High'
536 BUG_LABELS = ['FOO', 'BAR', 'pRoDuCt::'+BUG_PRODUCT, 'CoMpOnEnT::'+BUG_COMPONENT, 'PrIoRiTy::'+BUG_PRIORITY,
537 'SeVeRiTy::'+BUG_SEVERITY, 'PlAtFoRm: '+BUG_PLATFORM1, 'PlAtFoRm: '+BUG_PLATFORM2,
538 'FeAtUrE: '+BUG_FEATURE1, 'FeAtUrE: '+BUG_FEATURE2]
539 RESPONSES = {
540 'https://gitlab.freedesktop.org/api/v4/projects/230/issues/2':
541 {
542 'id': 4674,
543 'iid': BUG_ID,
544 'project_id': 230,
545 'title': BUG_TITLE,
546 'description': BUG_DESCRIPTION,
547 'state': BUG_STATUS,
548 'created_at': BUG_CREATED_AT,
549 'updated_at': BUG_UPDATED_AT,
550 'closed_at': None,
551 'closed_by': None,
552 'labels': BUG_LABELS,
553 'author': {'id': 1127, 'name': CREATOR_NAME},
554 'assignee': {'id': 1128, 'name': ASSIGNEE_NAME},
555 'web_url': 'https://gitlab.freedesktop.org/patchwork-fdo/patchwork-fdo/issues/2'
556 },
557 'https://gitlab.freedesktop.org/api/v4/projects/230/issues/2/notes':
558 [
559 {
560 'id': NOTE_ONE_ID,
561 'body': NOTE_ONE_BODY,
562 'author': {'id': 1129, 'name': NOTE_ONE_CREATOR_NAME},
563 'created_at': NOTE_ONE_CREATED_AT
564 },
565 {
566 'id': 41381,
567 'body': NOTE_TWO_BODY,
568 'author': {'id': 1127, 'name': CREATOR_NAME},
569 'created_at': NOTE_TWO_CREATED_AT
570 }
571 ],
572 'https://gitlab.freedesktop.org/api/v4/projects/230/issues/':
573 [
574 {'id': 4675, 'iid': 3, 'project_id': 230},
575 {'id': 4674, 'iid': 2, 'project_id': 230},
576 {'id': 4673, 'iid': 1, 'project_id': 230}
577 ]
578 }
580 def __init__(self, url, **kwargs):
581 self.url = url
582 self.headers = {}
584 if url not in self.RESPONSES.keys():
585 raise ValueError("unknown URL: {}".format(url)) # pragma: no cover
587 if kwargs['headers']['PRIVATE-TOKEN'] != self.PRIVATE_TOKEN:
588 raise ValueError("GitLab needs PRIVATE-TOKEN for querying API") # pragma: no cover
590 RequestsGetMock.last_URL = url
591 RequestsGetMock.last_params = kwargs.get('params')
592 RequestsGetMock.last_headers = kwargs.get('headers')
594 def raise_for_status(self):
595 pass
597 def json(self):
598 return self.RESPONSES[self.url]
601class BugTrackerGitLabTests(TransactionTestCase):
602 def setUp(self):
603 url = "https://gitlab.freedesktop.org"
604 bug_base_url = "https://gitlab.freedesktop.org/patchwork-fdo/patchwork-fdo/issues/"
606 self.db_tracker = BugTracker.objects.create(tracker_type="gitlab", public=True,
607 project="230",
608 password=RequestsGetMock.PRIVATE_TOKEN,
609 url=url, bug_base_url=bug_base_url)
611 self.bug = Bug.objects.create(tracker=self.db_tracker, bug_id=str(RequestsGetMock.BUG_ID))
612 self.gitlab = GitLab(self.db_tracker)
614 @patch('requests.get')
615 def test_GetTrackerTime(self, req_mock):
616 resp_mock = MagicMock()
617 resp_mock.headers = {'Date': 'Thu, 07 May 2020 20:23:57 GMT'}
618 req_mock.return_value = resp_mock
619 dt = self.gitlab._get_tracker_time()
620 self.assertEqual(dt, datetime.datetime(2020, 5, 7, 20, 23, 57, tzinfo=pytz.utc))
622 def test_ToTrackerTz(self):
623 dt = timezone.now()
624 self.assertEqual(dt, self.gitlab._to_tracker_tz(dt))
626 @patch('requests.get', RequestsGetMock)
627 def testPolledBugShouldSaveJustFine(self):
628 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now())
629 self.gitlab.poll(self.bug)
630 self.bug.save()
632 @patch('requests.get', RequestsGetMock)
633 def testPollingBugShouldPopulateFields(self):
634 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now())
635 self.gitlab.poll(self.bug)
636 self.assertEqual(self.bug.title, RequestsGetMock.BUG_TITLE)
637 self.assertEqual(self.bug.status, RequestsGetMock.BUG_STATUS)
638 self.assertEqual(self.bug.assignee.person.full_name, RequestsGetMock.ASSIGNEE_NAME)
639 self.assertEqual(self.bug.creator.person.full_name, RequestsGetMock.CREATOR_NAME)
640 self.assertEqual(self.bug.created, dateparser.parse(RequestsGetMock.BUG_CREATED_AT))
641 self.assertEqual(self.bug.updated, dateparser.parse(RequestsGetMock.BUG_UPDATED_AT))
642 self.assertEqual(self.bug.description, RequestsGetMock.BUG_DESCRIPTION)
643 self.assertEqual(self.bug.product, RequestsGetMock.BUG_PRODUCT)
644 self.assertEqual(self.bug.component, RequestsGetMock.BUG_COMPONENT)
645 self.assertEqual(self.bug.priority, RequestsGetMock.BUG_PRIORITY)
646 self.assertEqual(self.bug.severity, RequestsGetMock.BUG_SEVERITY)
647 self.assertEqual(self.bug.platforms,
648 "{},{}".format(RequestsGetMock.BUG_PLATFORM1, RequestsGetMock.BUG_PLATFORM2))
649 self.assertEqual(self.bug.features,
650 "{},{}".format(RequestsGetMock.BUG_FEATURE1, RequestsGetMock.BUG_FEATURE2))
651 self.assertEqual(self.bug.tags, "FOO,BAR")
653 @patch('requests.get', RequestsGetMock)
654 def testPollingCustomFieldMap(self):
655 url = "https://gitlab.freedesktop.org"
656 bug_base_url = "https://gitlab.freedesktop.org/patchwork-fdo/patchwork-fdo/issues/"
657 custom_labels = ['target::'+RequestsGetMock.BUG_TARGET,
658 'mapped_severity::'+RequestsGetMock.BUG_MAP_SEVERITY]
660 db_tracker = BugTracker.objects.create(name='blah', tracker_type="gitlab", public=True,
661 project="230",
662 password=RequestsGetMock.PRIVATE_TOKEN,
663 url=url, bug_base_url=bug_base_url,
664 custom_fields_map={'target::': 'target',
665 'doesnt_exist': 'foo',
666 'mapped_severity::': 'severity'})
668 orig_labels = RequestsGetMock.BUG_LABELS
669 RequestsGetMock.BUG_LABELS.extend(custom_labels)
670 self.bug.tracker = db_tracker
671 self.bug.save()
672 gitlab = GitLab(db_tracker)
673 gitlab._get_tracker_time = MagicMock(return_value=timezone.now())
675 gitlab.poll(self.bug)
676 try:
677 self.assertEqual(self.bug.tags, "FOO,BAR") # make sure labels are still populated
678 self.assertEqual(self.bug.severity, RequestsGetMock.BUG_MAP_SEVERITY)
679 self.assertEqual(self.bug.custom_fields['target'], RequestsGetMock.BUG_TARGET)
680 self.assertIsNone(self.bug.custom_fields.get('foo'))
681 except: # pragma: no cover # noqa
682 raise
683 finally:
684 self.bug.tracker = self.db_tracker
685 self.bug.save()
686 RequestsGetMock.BUG_LABELS = orig_labels
688 @patch('requests.get', RequestsGetMock)
689 def testPollingBugShouldFetchComments(self):
690 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now())
691 self.gitlab.poll(self.bug)
693 comments = BugComment.objects.filter(bug=self.bug)
694 self.assertEqual(comments.count(), 2)
696 @patch('requests.get', RequestsGetMock)
697 def testNoteShouldBePopulatedCorrectly(self):
698 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now())
699 self.gitlab.poll(self.bug)
701 comment = BugComment.objects.get(bug=self.bug, comment_id=RequestsGetMock.NOTE_ONE_ID)
702 self.assertEqual(comment.account.person.full_name, RequestsGetMock.NOTE_ONE_CREATOR_NAME)
703 self.assertEqual(comment.created_on, dateparser.parse(RequestsGetMock.NOTE_ONE_CREATED_AT))
704 self.assertTrue("#note_{}".format(comment.comment_id) in comment.url)
705 self.assertTrue(self.db_tracker.bug_base_url in comment.url)
707 @patch('requests.get', RequestsGetMock)
708 def testPollingCreatesCommentList(self):
709 expected = [(RequestsGetMock.NOTE_ONE_CREATOR_NAME,
710 RequestsGetMock.NOTE_ONE_BODY,
711 dateparser.parse(RequestsGetMock.NOTE_ONE_CREATED_AT)),
712 (RequestsGetMock.CREATOR_NAME,
713 RequestsGetMock.NOTE_TWO_BODY,
714 dateparser.parse(RequestsGetMock.NOTE_TWO_CREATED_AT))]
716 comm_list = self.gitlab._GitLab__poll_comments(self.bug, "http://foo.com")
717 result = []
718 for c in comm_list:
719 result.append((c.db_object.account.person.full_name,
720 c.body,
721 c.db_object.created_on))
722 self.assertEqual(result, expected)
724 @patch('requests.get', RequestsGetMock)
725 def testPollingBugTwiceShouldNotDuplicateComments(self):
726 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now())
727 self.bug.comments_polled = dateparser.parse(RequestsGetMock.NOTE_ONE_CREATED_AT)
728 url = "{}#note_{}".format('https://gitlab.freedesktop.org/api/v4/projects/230/issues/2/notes',
729 RequestsGetMock.NOTE_ONE_ID)
731 person = Person.objects.create(full_name=RequestsGetMock.CREATOR_NAME)
732 account = BugTrackerAccount.objects.create(user_id="1", person=person,
733 tracker=self.db_tracker, is_developer=True)
734 BugComment.objects.create(bug=self.bug, account=account, comment_id=RequestsGetMock.NOTE_ONE_ID,
735 url=url,
736 created_on=dateparser.parse(RequestsGetMock.NOTE_ONE_CREATED_AT))
738 expected = [(RequestsGetMock.CREATOR_NAME,
739 RequestsGetMock.NOTE_TWO_BODY,
740 dateparser.parse(RequestsGetMock.NOTE_TWO_CREATED_AT))]
742 with patch('CIResults.bugtrackers.GitLab.check_replication') as cr_mock:
743 self.gitlab.poll(self.bug)
744 comm_list = cr_mock.call_args[0][1]
745 result = []
746 for c in comm_list:
747 result.append((c.db_object.account.person.full_name,
748 c.body,
749 c.db_object.created_on))
750 self.assertEqual(result, expected)
752 comments = BugComment.objects.filter(bug=self.bug)
753 self.assertEqual(comments.count(), 2)
755 self.gitlab.poll(self.bug)
756 comments = BugComment.objects.filter(bug=self.bug)
757 self.assertEqual(comments.count(), 2)
759 @patch('requests.get', RequestsGetMock)
760 def testSearchNoParams(self):
761 all_bugs = self.gitlab.search_bugs_ids()
762 self.assertEqual(all_bugs, set(['1', '2', '3']))
763 self.assertEqual(RequestsGetMock.last_params, {'page': 1, 'per_page': 100})
765 @patch('requests.get', RequestsGetMock)
766 def testSearchAllBugIds(self):
767 created_since = datetime.datetime.fromtimestamp(1000)
768 updated_since = datetime.datetime.fromtimestamp(1000)
769 all_bugs = self.gitlab.search_bugs_ids(created_since=created_since,
770 updated_since=updated_since,
771 components=['tag1', 'tag2'],
772 status='status1')
773 self.assertEqual(all_bugs, set(['1', '2', '3']))
774 self.assertEqual(RequestsGetMock.last_params, {
775 'page': 1, 'per_page': 100,
776 'created_after': created_since,
777 'updated_after': updated_since,
778 "labels": "tag1,tag2",
779 "state": 'status1'
780 })
782 @patch('requests.get', RequestsGetMock)
783 def testSearchWithOneStatusInList(self):
784 all_bugs = self.gitlab.search_bugs_ids(status=['status1'])
785 self.assertEqual(all_bugs, set(['1', '2', '3']))
786 self.assertEqual(RequestsGetMock.last_params, {
787 'page': 1, 'per_page': 100,
788 "state": 'status1'
789 })
791 def testSearchWithMoreThanOneStatus(self):
792 self.assertRaisesMessage(ValueError, "Status has to be a string",
793 self.gitlab.search_bugs_ids, status=['status1', 'status2'])
795 def test_open_statuses(self):
796 self.assertEqual(self.gitlab.open_statuses, ['opened'])
798 @patch('requests.post')
799 def testAddComment(self, post_mock):
800 comment = "Hello world!"
801 self.gitlab.add_comment(Bug(tracker=self.db_tracker, bug_id=RequestsGetMock.BUG_ID), comment)
803 # Check that the call was what was expected
804 args, kwargs = post_mock.call_args_list[0]
805 self.assertEqual(args[0], 'https://gitlab.freedesktop.org/api/v4/projects/230/issues/2/notes')
806 self.assertEqual(kwargs['headers'], {'PRIVATE-TOKEN': RequestsGetMock.PRIVATE_TOKEN})
807 self.assertEqual(kwargs['params'], {'body': comment})
809 @patch('requests.post')
810 def test_create_bug_from_json(self, post_mock):
811 summary = "summary"
812 description = "description"
813 test_id = 5678
814 json_bug = {'title': summary,
815 'description': description,
816 'labels': "Bug",
817 'state': "opened"}
819 post_mock.return_value.raise_for_status.return_value = None
820 post_mock.return_value.json.return_value = {"iid": test_id}
822 id = self.gitlab.create_bug_from_json(json_bug)
823 self.assertEqual(id, test_id)
825 args, kwargs = post_mock.call_args_list[0]
826 request = kwargs['params']
827 for field in json_bug:
828 self.assertEqual(request[field], json_bug[field])
830 @patch('requests.post')
831 def test_create_bug_from_json_no_labels(self, post_mock):
832 summary = "summary"
833 description = "description"
834 test_id = 5678
835 json_bug = {'title': summary,
836 'description': description}
838 post_mock.return_value.raise_for_status.return_value = None
839 post_mock.return_value.json.return_value = {"iid": test_id}
841 id = self.gitlab.create_bug_from_json(json_bug)
842 self.assertEqual(id, test_id)
844 args, kwargs = post_mock.call_args_list[0]
845 expected_request = {'title': summary,
846 'description': description}
847 request = kwargs['params']
848 for field in expected_request:
849 self.assertEqual(request[field], expected_request[field], field)
851 @patch('requests.post')
852 def test_create_bug_from_json_with_status(self, post_mock):
853 summary = "summary"
854 description = "description"
855 test_id = 5678
856 json_bug = {'title': summary,
857 'description': description,
858 'status': "opened"}
860 post_mock.return_value.raise_for_status.return_value = None
861 post_mock.return_value.json.return_value = {"iid": test_id}
863 id = self.gitlab.create_bug_from_json(json_bug)
864 self.assertEqual(id, test_id)
866 args, kwargs = post_mock.call_args_list[0]
867 expected_request = {'title': summary,
868 'description': description}
869 request = kwargs['params']
870 for field in expected_request:
871 self.assertEqual(request[field], expected_request[field], field)
873 @patch('requests.post')
874 def test_create_malformed_bug(self, post_mock):
875 summary = "summary"
876 description = "description"
877 json_bug = {'title': summary,
878 'description': description,
879 'labels': "Bug"}
881 post_mock.side_effect = requests.HTTPError
883 with self.assertRaises(ValueError):
884 self.gitlab.create_bug_from_json(json_bug)
886 @patch('requests.put')
887 def test_update_bug_from_json(self, put_mock):
888 json_bug = {'title': "summary",
889 'description': "description",
890 'labels': "Bug",
891 'state': "opened"}
893 self.gitlab.update_bug_from_json(json_bug, 5678)
895 args, kwargs = put_mock.call_args_list[0]
896 self.assertEqual(args[0], "https://gitlab.freedesktop.org/api/v4/projects/230/issues/5678")
897 for field in json_bug:
898 self.assertEqual(kwargs['params'][field], json_bug[field])
900 @patch('requests.put')
901 def test_update_bug_from_json_error(self, put_mock):
902 put_mock.side_effect = requests.HTTPError
903 with self.assertRaises(ValueError):
904 self.gitlab.update_bug_from_json({}, 5678)
906 def test_transition(self):
907 self.gitlab.update_bug_from_json = MagicMock()
908 self.gitlab.transition(1, "Open")
909 self.gitlab.update_bug_from_json.assert_called_with({'state_event': "Open"}, 1)
912class BugzillaProxyMock:
913 URL = "https://bugzilla.instance.org"
915 # User.login
916 LOGIN = "userlogin"
917 PASSWORD = "password"
918 TOKEN_ID = '12345'
919 TOKEN = '12345-kZ5CYMeQGH'
921 # Bug.add_comment
922 BUG_ID = 1234
923 BUG_ID_NO_EMAIL = 1235
924 BUG_ID_NON_EXISTING = 1236
925 BUG_ID_WRONG_COMMENT_COUNT = 1237
926 COMMENT = 'my comment'
928 # Bugzilla.create_bug
929 NEW_BUG_ID = 5678
930 PRODUCT = "TEST_PRODUCT"
931 COMPONENT = "TEST/COMPONENT/WITH/SLASHES"
932 SUMMARY = "TEST_SUMMARY"
933 DESCRIPTION = "TEST_DESCRIPTION"
934 DESCRIPTION_2 = "Some chump has run the data lines right through the power supply!"
936 # Update
937 UPDATE_IDS = 1
939 PROJECT = "{}/{}".format(PRODUCT, COMPONENT)
941 CREATE_REQUEST = {
942 'token': TOKEN,
943 'product': PRODUCT,
944 'component': COMPONENT,
945 'summary': SUMMARY,
946 'description': DESCRIPTION
947 }
948 # get_comments
949 COMMENT_CREATOR = "Roy Trenneman"
950 COMMENT_CREATOR_2 = "Maurice Moss"
951 COMMENT_CREATION_TIME = datetime.datetime.fromtimestamp(0)
952 COMMENT_2_CREATION_TIME = COMMENT_CREATION_TIME + timedelta(days=1)
954 class _Bugzilla:
955 def time(self):
956 return {'db_time': datetime.datetime.fromtimestamp(0)}
958 class _User:
959 def login(self, params):
960 if params.get('login') != BugzillaProxyMock.LOGIN:
961 raise ValueError('Incorrect or missing login') # pragma: no cover
962 if params.get('password') != BugzillaProxyMock.PASSWORD:
963 raise ValueError('Incorrect or missing password') # pragma: no cover
964 return {'id': BugzillaProxyMock.TOKEN_ID, 'token': BugzillaProxyMock.TOKEN}
966 class _Bug:
967 def get(self, params):
968 ids = params.get('ids')
969 if ids == BugzillaProxyMock.BUG_ID or ids == BugzillaProxyMock.BUG_ID_WRONG_COMMENT_COUNT:
970 creator_detail = {"real_name": "creator", "email": "creator@me.de"}
971 assigned_to_detail = {"real_name": "assignee", "email": "assignee@me.de"}
972 is_open = False
973 elif ids == BugzillaProxyMock.BUG_ID_NO_EMAIL:
974 creator_detail = {"real_name": "creator", "name": "creator"}
975 assigned_to_detail = {"real_name": "assignee", "name": "assignee"}
976 is_open = True
977 elif ids == BugzillaProxyMock.BUG_ID_NON_EXISTING:
978 return {'bugs': []}
979 else:
980 raise ValueError('Incorrect or missing bug id') # pragma: no cover
982 return {
983 "bugs": [{
984 "summary": "summary",
985 "status": "status",
986 "severity": "severity",
987 "is_open": is_open,
988 "resolution": "resolution",
989 "creation_time": datetime.datetime.fromtimestamp(0),
990 "last_change_time": datetime.datetime.fromtimestamp(5),
991 "creator_detail": creator_detail,
992 "assigned_to_detail": assigned_to_detail,
993 "product": "product",
994 "component": "component",
995 "custom_features": ["feature1", "feature2"],
996 "custom_platforms": ["platform1", "platform2"],
997 "priority": "high",
998 "a_custom_field": "I'm custom",
999 "id": "invalid field",
1000 "bug_id": "invalid field",
1001 "tracker_id": "invalid field",
1002 "tracker": "invalid field",
1003 "parent_id": "invalid field",
1004 "parent": "invalid field"
1005 }]
1006 }
1008 def comments(self, params):
1009 if params.get('ids') == BugzillaProxyMock.BUG_ID or params.get('ids') == BugzillaProxyMock.BUG_ID_NO_EMAIL:
1010 count = 0
1011 elif params.get('ids') == BugzillaProxyMock.BUG_ID_WRONG_COMMENT_COUNT:
1012 count = 1
1013 else:
1014 raise ValueError('Incorrect or missing bug id') # pragma: no cover
1016 comments = {
1017 "comments": [
1018 {
1019 "text": BugzillaProxyMock.DESCRIPTION,
1020 "creator": BugzillaProxyMock.COMMENT_CREATOR,
1021 "id": 100,
1022 "count": count,
1023 "time": BugzillaProxyMock.COMMENT_CREATION_TIME,
1024 "creation_time": BugzillaProxyMock.COMMENT_CREATION_TIME
1025 },
1026 {
1027 "text": BugzillaProxyMock.DESCRIPTION_2,
1028 "creator": BugzillaProxyMock.COMMENT_CREATOR_2,
1029 "id": 101,
1030 "count": count+1,
1031 "time": BugzillaProxyMock.COMMENT_2_CREATION_TIME,
1032 "creation_time": BugzillaProxyMock.COMMENT_2_CREATION_TIME
1033 }
1034 ]
1035 }
1037 # prune the comments that came after the new_since parameter
1038 if params.get('new_since') is not None:
1039 new_since = params['new_since'].replace(tzinfo=None)
1040 comments['comments'] = [c for c in comments['comments'] if c['time'] > new_since]
1042 return {"bugs": {"1234": comments, "1235": comments, "1237": comments}}
1044 def history(self, params):
1045 if params.get('ids') != BugzillaProxyMock.BUG_ID:
1046 raise ValueError('Incorrect or missing bug id') # pragma: no cover
1048 return {"bugs": [
1049 {'history': [
1050 {'when': datetime.datetime.fromtimestamp(1), 'who': 'someone@toto.de',
1051 'changes': [{'field_name': 'status', 'removed': 'NEW', 'added': 'RESOLVED'},
1052 {'field_name': 'resolution', 'removed': '', 'added': 'FIXED'}]
1053 },
1054 {'who': 'someone@toto.de', 'when': datetime.datetime.fromtimestamp(2),
1055 'changes': [
1056 {'field_name': 'status', 'added': 'NEW', 'removed': 'RESOLVED'},
1057 {'field_name': 'resolution', 'added': '', 'removed': 'FIXED'}],
1058 },
1059 {'when': datetime.datetime.fromtimestamp(3), 'who': 'someone@toto.de',
1060 'changes': [
1061 {'field_name': 'status', 'added': 'RESOLVED', 'removed': 'NEW'},
1062 {'field_name': 'resolution', 'removed': '', 'added': 'FIXED'}],
1063 },
1064 {'when': datetime.datetime.fromtimestamp(4), 'who': 'someone@toto.de',
1065 'changes': [{'field_name': 'status', 'added': 'CLOSED', 'removed': 'RESOLVED'}],
1066 }]
1067 }] # noqa
1068 }
1070 def add_comment(self, params):
1071 if params.get('id') != BugzillaProxyMock.BUG_ID:
1072 raise ValueError('Incorrect or missing bug id') # pragma: no cover
1073 if params.get('token') != BugzillaProxyMock.TOKEN:
1074 raise ValueError('Incorrect or missing token') # pragma: no cover
1075 if params.get('comment') != BugzillaProxyMock.COMMENT:
1076 raise ValueError('Incorrect or missing comment') # pragma: no cover
1077 return {'id': 766846}
1079 def create(self, params):
1080 if params.get('token') != BugzillaProxyMock.TOKEN:
1081 raise xmlrpc.client.Error('Incorrect or missing token') # pragma: no cover
1082 if params.get('summary') != BugzillaProxyMock.SUMMARY:
1083 raise xmlrpc.client.Error('Incorrect or missing summary') # pragma: no cover
1084 if params.get('description') != BugzillaProxyMock.DESCRIPTION:
1085 raise xmlrpc.client.Error('Incorrect or missing description') # pragma: no cover
1087 return {'id': '1'}
1089 def search(self, params):
1090 self.last_search_request = params
1091 return {'bugs': [{"id": 10}, {"id": 11}, {"id": 13}]}
1093 def update(self, params):
1094 if params.get('ids') != BugzillaProxyMock.UPDATE_IDS:
1095 raise xmlrpc.client.Error('Incorrect or missing ids') # pragma: no cover
1097 # All the other checks are the same as create(), so just call that
1098 self.create(params)
1100 def __init__(self, url, use_builtin_types=False):
1101 if url != self.URL + "/xmlrpc.cgi":
1102 raise ValueError('invalid xmlrpc url') # pragma: no cover
1104 if not use_builtin_types:
1105 raise ValueError('use_builtin_types is not True') # pragma: no cover
1107 User = _User()
1108 Bug = _Bug()
1109 Bugzilla = _Bugzilla()
1112class BugTrackerBugzillaTests(TestCase):
1113 @patch('xmlrpc.client.ServerProxy', BugzillaProxyMock)
1114 def setUp(self):
1115 self.tracker = BugTracker.objects.create(tracker_type="bugzilla", public=True,
1116 url=BugzillaProxyMock.URL,
1117 username=BugzillaProxyMock.LOGIN,
1118 password=BugzillaProxyMock.PASSWORD,
1119 custom_fields_map={'custom_features': 'features',
1120 'custom_platforms': 'platforms',
1121 'a_custom_field': 'my_custom_field'},
1122 name='my tracker')
1123 self.bugzilla = Bugzilla(self.tracker)
1125 def test__get_tracker_time(self):
1126 dt = datetime.datetime.fromtimestamp(0, pytz.utc)
1127 self.assertEqual(dt, self.bugzilla._get_tracker_time())
1129 def test__to_tracker_tz(self):
1130 dt = datetime.datetime.fromtimestamp(0, pytz.utc)
1131 dt_pdt = dt.astimezone(pytz.timezone('America/Vancouver'))
1132 self.assertEqual(dt, self.bugzilla._to_tracker_tz(dt_pdt))
1134 def test__get_user_id(self):
1135 self.assertEqual(Bugzilla._get_user_id({'creator_detail': {'email': 'me@email.com',
1136 'name': 'John Doe'}},
1137 'creator'), 'me@email.com')
1139 self.assertEqual(Bugzilla._get_user_id({'creator_detail': {'name': 'John Doe'}},
1140 'creator'), 'John Doe')
1142 self.assertRaisesMessage(ValueError,
1143 'Cannot find a good identifier for the user of the bug 1234',
1144 Bugzilla._get_user_id, {'id': '1234'}, 'creator')
1146 def test_list_to_str(self):
1147 self.assertEqual(Bugzilla._list_to_str(['one', 'two', 'three']), 'one,two,three')
1148 self.assertEqual(Bugzilla._list_to_str('one'), 'one')
1150 def test_bug_id_parser(self):
1151 self.assertEqual(Bugzilla._bug_id_parser(MagicMock(spec=Bug, bug_id='1234')), 1234)
1152 self.assertRaisesMessage(ValueError, "Bugzilla's IDs should be integers (fdo#1234)",
1153 Bugzilla._bug_id_parser, MagicMock(spec=Bug, bug_id='fdo#1234'))
1155 def test__parse_custom_field(self):
1156 value1 = ['apple', 'cherry', 'orange']
1157 resp1 = self.bugzilla._parse_custom_field(value1, to_str=False)
1158 resp1_str = self.bugzilla._parse_custom_field(value1, to_str=True)
1159 self.assertEqual(resp1, value1)
1160 self.assertEqual(resp1_str, 'apple,cherry,orange')
1162 value2 = 'something clever'
1163 resp2 = self.bugzilla._parse_custom_field(value2, to_str=False)
1164 resp2_str = self.bugzilla._parse_custom_field(value2, to_str=True)
1165 self.assertEqual(resp2, value2)
1166 self.assertEqual(resp2_str, value2)
1168 def test_poll__with_emails(self):
1169 bug = Bug.objects.create(tracker=self.tracker, bug_id=BugzillaProxyMock.BUG_ID, closed=None, description=None)
1170 bug.save = MagicMock()
1172 with patch('CIResults.bugtrackers.Bugzilla.check_replication') as cr_mock:
1173 self.bugzilla.poll(bug)
1174 comm_list = cr_mock.call_args[0][1]
1176 result = []
1177 for c in comm_list:
1178 result.append((c.db_object.account.person.email,
1179 c.body,
1180 c.db_object.created_on))
1182 self.assertEqual(bug.title, "summary")
1183 self.assertEqual(bug.created, datetime.datetime.fromtimestamp(0, tz=pytz.utc))
1184 self.assertEqual(bug.updated, datetime.datetime.fromtimestamp(5, tz=pytz.utc))
1185 self.assertEqual(bug.closed, datetime.datetime.fromtimestamp(3, tz=pytz.utc))
1186 self.assertEqual(bug.creator.person.full_name, "creator")
1187 self.assertEqual(bug.creator.person.email, "creator@me.de")
1188 self.assertEqual(bug.assignee.person.full_name, "assignee")
1189 self.assertEqual(bug.assignee.person.email, "assignee@me.de")
1190 self.assertEqual(bug.product, "product")
1191 self.assertEqual(bug.component, "component")
1192 self.assertEqual(bug.features, "feature1,feature2")
1193 self.assertEqual(bug.platforms, "platform1,platform2")
1194 self.assertEqual(bug.status, "status/resolution")
1195 self.assertEqual(bug.priority, "high")
1196 self.assertEqual(bug.severity, "severity")
1197 self.assertEqual(bug.description, BugzillaProxyMock.DESCRIPTION)
1198 self.assertEqual(bug.custom_fields['my_custom_field'], "I'm custom")
1199 bug.save.assert_not_called()
1201 expected = [(BugzillaProxyMock.COMMENT_CREATOR,
1202 BugzillaProxyMock.DESCRIPTION,
1203 timezone.make_aware(BugzillaProxyMock.COMMENT_CREATION_TIME, pytz.utc)),
1204 (BugzillaProxyMock.COMMENT_CREATOR_2,
1205 BugzillaProxyMock.DESCRIPTION_2,
1206 timezone.make_aware(BugzillaProxyMock.COMMENT_2_CREATION_TIME, pytz.utc))]
1207 # Check that we polled all the new comments
1208 self.assertEqual(result, expected)
1210 @patch('xmlrpc.client.ServerProxy', BugzillaProxyMock)
1211 def test_poll_invalid_custom_fields(self):
1212 tracker = BugTracker.objects.create(tracker_type="bugzilla", public=True,
1213 url=BugzillaProxyMock.URL,
1214 username=BugzillaProxyMock.LOGIN,
1215 password=BugzillaProxyMock.PASSWORD,
1216 custom_fields_map={'id': 'id',
1217 'bug_id': 'bug_id',
1218 'tracker_id': 'tracker_id',
1219 'tracker': 'tracker',
1220 'parent_id': 'parent_id',
1221 'parent': 'parent'},
1222 name='super tracker')
1223 bugzilla = Bugzilla(tracker)
1224 bug = Bug.objects.create(tracker=tracker, bug_id=BugzillaProxyMock.BUG_ID, closed=None, description=None)
1225 bug.save = MagicMock()
1226 parse_mock = MagicMock()
1227 bugzilla._parse_custom_field = parse_mock
1229 with patch('CIResults.bugtrackers.Bugzilla.check_replication'):
1230 bugzilla.poll(bug)
1232 parse_mock.assert_not_called()
1234 def test_poll__new_comments_arrived(self):
1235 bug = Bug.objects.create(tracker=self.tracker, bug_id=BugzillaProxyMock.BUG_ID, closed=None, description=None,
1236 comments_polled=timezone.make_aware(BugzillaProxyMock.COMMENT_CREATION_TIME, pytz.utc))
1238 comm_list = self.bugzilla._Bugzilla__poll_comments(bug)
1239 result = []
1240 for c in comm_list:
1241 result.append((c.db_object.account.person.email,
1242 c.body,
1243 c.db_object.created_on))
1245 expected = [(BugzillaProxyMock.COMMENT_CREATOR_2,
1246 BugzillaProxyMock.DESCRIPTION_2,
1247 timezone.make_aware(BugzillaProxyMock.COMMENT_2_CREATION_TIME, pytz.utc))]
1248 # Check that we polled only the new comment
1249 self.assertEqual(result, expected)
1251 def test_poll__no_emails(self):
1252 bug = MagicMock(spec=Bug, bug_id=BugzillaProxyMock.BUG_ID_NO_EMAIL, comments_polled=None)
1253 with patch.object(BugComment.objects, "create"):
1254 self.bugzilla.poll(bug)
1256 self.assertEqual(bug.closed, None)
1257 self.assertEqual(bug.creator.person.full_name, "creator")
1258 self.assertEqual(bug.creator.person.email, None)
1259 self.assertEqual(bug.assignee.person.full_name, "assignee")
1260 self.assertEqual(bug.assignee.person.email, None)
1261 bug.save.assert_not_called()
1263 def test_poll_invalid_bug(self):
1264 bug = MagicMock(spec=Bug, bug_id=BugzillaProxyMock.BUG_ID_NON_EXISTING, tracker=self.tracker)
1265 self.assertRaisesMessage(ValueError, "Could not find the bug ID 1236 on my tracker",
1266 self.bugzilla.poll, bug)
1268 bug.save.assert_not_called()
1270 def test_poll_wrong_comment_count(self):
1271 bug = MagicMock(spec=Bug, bug_id=BugzillaProxyMock.BUG_ID_WRONG_COMMENT_COUNT, description=None)
1272 with patch.object(BugComment.objects, "create"):
1273 with self.assertRaises(ValueError):
1274 self.bugzilla.poll(bug)
1275 bug.save.assert_not_called()
1277 def test_search_bugs_ids__full(self):
1278 # Get the list of open bugs
1279 updated_since = datetime.datetime.fromtimestamp(1000)
1280 open_bugs = self.bugzilla.search_bugs_ids(components=["COMPONENT1", "COMPONENT2"],
1281 created_since=datetime.datetime.fromtimestamp(1000),
1282 updated_since=updated_since,
1283 status=['status1', 'status2'])
1284 self.assertEqual(open_bugs, set(['10', '11', '13']))
1286 # Verify that the request was valid
1287 expected_request = {
1288 "component": ["COMPONENT1", "COMPONENT2"],
1289 "status": ['status1', 'status2'],
1290 "creation_time": datetime.datetime.fromtimestamp(1000),
1291 "last_change_time": updated_since,
1292 "include_fields": ['id']
1293 }
1294 self.assertEqual(BugzillaProxyMock.Bug.last_search_request, expected_request)
1296 def test_search_bugs_ids__empty(self):
1297 self.bugzilla.search_bugs_ids()
1298 expected_request = {
1299 "include_fields": ['id']
1300 }
1301 self.assertEqual(BugzillaProxyMock.Bug.last_search_request, expected_request)
1303 def test_open_statuses(self):
1304 self.assertEqual(self.bugzilla.open_statuses, ["NEW", "ASSIGNED", "REOPENED", "NEEDINFO"])
1306 def test_auth_login(self):
1307 self.assertEqual(self.bugzilla.get_auth_token(), BugzillaProxyMock.TOKEN)
1309 def test_auth_login__invalid_username(self):
1310 for username in [None, ""]:
1311 self.tracker.username = username
1312 self.assertRaisesMessage(ValueError, "Invalid credentials",
1313 self.bugzilla.get_auth_token)
1315 def test_auth_login__invalid_password(self):
1316 for password in [None, ""]:
1317 self.tracker.password = password
1318 self.assertRaisesMessage(ValueError, "Invalid credentials",
1319 self.bugzilla.get_auth_token)
1321 def test_add_comment(self):
1322 bug = Bug(tracker=self.tracker, bug_id=str(BugzillaProxyMock.BUG_ID))
1323 self.bugzilla.add_comment(bug, BugzillaProxyMock.COMMENT)
1325 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=None)
1326 def test_add_comment__invalid_credentials(self, auth_token_mocked):
1327 bug = Bug(tracker=self.tracker, bug_id=str(BugzillaProxyMock.BUG_ID))
1328 self.assertRaisesMessage(ValueError, "Authentication failed. Can't post a comment",
1329 self.bugzilla.add_comment, bug, BugzillaProxyMock.COMMENT)
1331 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN)
1332 def test_create_bug_from_json(self, auth_mock):
1333 json_bug = {'summary': BugzillaProxyMock.SUMMARY,
1334 'description': BugzillaProxyMock.DESCRIPTION}
1335 self.bugzilla.create_bug_from_json(json_bug)
1337 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN)
1338 def test_create_bug_from_json__with_title_insteaf_of_summary(self, auth_mock):
1339 json_bug = {'title': BugzillaProxyMock.SUMMARY,
1340 'description': BugzillaProxyMock.DESCRIPTION}
1341 self.bugzilla.create_bug_from_json(json_bug)
1343 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN)
1344 def test_create_bug_from_json__no_summary_nor_title(self, auth_mock):
1345 with self.assertRaises(KeyError):
1346 self.bugzilla.create_bug_from_json({})
1348 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN)
1349 def test_create_bug_from_json__missing_description(self, auth_mock):
1350 json_bug = {'title': BugzillaProxyMock.SUMMARY}
1351 with self.assertRaises(ValueError):
1352 self.bugzilla.create_bug_from_json(json_bug)
1354 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=None)
1355 def test_create_bug_from_json_invalid_token(self, auth_mock):
1356 with self.assertRaises(ValueError):
1357 self.bugzilla.create_bug_from_json({})
1359 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=None)
1360 def test_create_bug_from_json_missing_required(self, auth_mock):
1361 with self.assertRaises(ValueError):
1362 self.bugzilla.create_bug_from_json({'summary': BugzillaProxyMock.SUMMARY})
1364 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN)
1365 def test_update_bug_from_json(self, auth_mock):
1366 json_bug = {'summary': BugzillaProxyMock.SUMMARY,
1367 'description': BugzillaProxyMock.DESCRIPTION}
1368 self.bugzilla.update_bug_from_json(json_bug, BugzillaProxyMock.UPDATE_IDS)
1370 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN)
1371 def test_update_bug_from_json_error(self, auth_mock):
1372 self.bugzilla._proxy.Bug.update = MagicMock(side_effect=xmlrpc.client.Error())
1373 with self.assertRaises(ValueError):
1374 self.bugzilla.update_bug_from_json({}, 1)
1376 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=None)
1377 def test_update_bug_from_json_invalid_token(self, auth_mock):
1378 with self.assertRaises(ValueError):
1379 self.bugzilla.update_bug_from_json({}, 1)
1381 def test_transition(self):
1382 self.bugzilla.update_bug_from_json = MagicMock()
1383 self.bugzilla.transition(1, "Open")
1384 self.bugzilla.update_bug_from_json.assert_called_with({'status': "Open"}, 1)
1387class JiraMock:
1388 # New Bug
1389 NEW_BUG_ID = 5678
1390 PROJECT_KEY = "TEST"
1391 ISSUE_KEY = "TEST-101"
1392 SUMMARY = "This is a test bug"
1393 DESCRIPTION = "This is a description"
1395 ISSUE = MagicMock()
1396 ISSUE.key = ISSUE_KEY
1398 # URLs
1399 URL = "https://jira.instance.com/rest/api/2/issue"
1400 RESP_URL = urllib.parse.urljoin(URL, str(NEW_BUG_ID))
1401 REQ_URL = URL
1403 # User.login
1404 LOGIN = "userlogin"
1405 PASSWORD = "password"
1407 # Bug.add_comment
1408 BUG_ID = 1234
1409 COMMENT = 'my comment'
1411 # Bug.create_bug
1412 REQUEST_DATA = {"project": {
1413 "key": PROJECT_KEY
1414 },
1415 "summary": SUMMARY,
1416 "description": DESCRIPTION,
1417 "issuetype": {
1418 "name": "Bug"
1419 }
1420 }
1422 RESPONSES = {REQ_URL:
1423 {"id": NEW_BUG_ID,
1424 "key": ISSUE_KEY,
1425 "self": RESP_URL}}
1428class BugTrackerJiraTests(TransactionTestCase):
1429 def setUp(self):
1430 issue = MagicMock(fields=MagicMock(summary="summary", status=MagicMock(),
1431 description="description",
1432 created=datetime.datetime.fromtimestamp(0, tz=pytz.utc).isoformat(),
1433 updated=datetime.datetime.fromtimestamp(1, tz=pytz.utc).isoformat(),
1434 resolutiondate=datetime.datetime.fromtimestamp(42, tz=pytz.utc).isoformat(),
1435 creator=MagicMock(displayName="creator", key="creator_key",
1436 emailAddress="creator@email"),
1437 assignee=MagicMock(displayName="assignee", key="assignee_key",
1438 emailAddress="assigne@email"),
1439 components=[MagicMock(), MagicMock()],
1440 comment=MagicMock(comments=[])))
1441 type(issue.fields.status).name = PropertyMock(return_value='status')
1442 type(issue.fields.priority).name = PropertyMock(return_value='Low')
1443 type(issue.fields.components[0]).name = PropertyMock(return_value='component1')
1444 type(issue.fields.components[1]).name = PropertyMock(return_value='component2')
1445 feature1 = MagicMock(value='Lasers')
1446 feature2 = MagicMock(value='Sharks')
1447 issue.fields.cool_features = [feature1, feature2]
1448 platform1 = MagicMock(value='Platform A')
1449 platform2 = MagicMock(value='Platform B')
1450 issue.fields.fancy_platforms = [platform1, platform2]
1451 issue.fields.a_custom_field = MagicMock(value="tacos")
1452 issue.fields.labels = ['label1', 'label2']
1454 comment_created = datetime.datetime.fromtimestamp(47, tz=pytz.utc).isoformat()
1455 issue.fields.comment.comments.append(MagicMock(author=MagicMock(displayName='Last, First',
1456 emailAddress='comments@email'),
1457 id='12345', created=comment_created))
1458 type(issue.fields.comment.comments[0].author).name = PropertyMock(return_value='flast')
1459 issue.fields.comment.comments.append(MagicMock(author=MagicMock(displayName='Last, First2',
1460 emailAddress='comments@email2'),
1461 id='12346', created=comment_created,))
1462 type(issue.fields.comment.comments[1].author).name = PropertyMock(return_value='flast2')
1464 self.issue = issue
1466 @patch('CIResults.bugtrackers.Jira.jira')
1467 def test__get_tracker_time(self, j_mock):
1468 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', public=True)
1469 jira = Jira(tracker)
1471 j_mock.server_info.return_value = {'serverTime': '2020-05-07T10:36:28.772-0700'}
1472 dt = datetime.datetime(2020, 5, 7, 17, 36, 28, 772000, tzinfo=pytz.utc)
1473 jt = jira._get_tracker_time()
1475 self.assertEqual(dt, jt)
1477 @patch('CIResults.bugtrackers.timezone')
1478 @patch('CIResults.bugtrackers.Jira.jira')
1479 def test__get_tracker_time_error(self, j_mock, tz_mock):
1480 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', public=True)
1481 jira = Jira(tracker)
1483 j_mock.server_info = MagicMock(side_effect=JIRAError)
1484 now_mock = MagicMock()
1485 tz_mock.now = now_mock
1486 jira._get_tracker_time()
1488 now_mock.assert_called()
1490 @patch('CIResults.bugtrackers.Jira.jira')
1491 def test__to_tracker_tz(self, j_mock):
1492 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', public=True)
1493 jira = Jira(tracker)
1495 j_mock.myself.return_value = {'timeZone': 'America/Vancouver'}
1496 dt = datetime.datetime(2020, 5, 7, 17, 36, 28, 772000, tzinfo=pytz.utc)
1497 j_dt = jira._to_tracker_tz(dt)
1499 # NOTE: datetime comparison considers TZ, so format to str to
1500 # make sure right datetime adjusted for right TZ.
1501 exp_str = "2020/05/07 10:36"
1502 jt_str = j_dt.strftime("%Y/%m/%d %H:%M")
1504 self.assertEqual(exp_str, jt_str)
1506 @patch('CIResults.bugtrackers.Jira.jira')
1507 def test__to_tracker_tz_error(self, j_mock):
1508 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', public=True)
1509 jira = Jira(tracker)
1511 j_mock.myself = MagicMock(side_effect=JIRAError)
1512 dt = datetime.datetime(2020, 5, 7, 17, 36, 28, 772000, tzinfo=pytz.utc)
1513 j_dt = jira._to_tracker_tz(dt)
1515 # NOTE: datetime comparison considers TZ, so format to str to
1516 # make sure right datetime adjusted for right TZ.
1517 exp_str = "2020/05/07 17:36"
1518 jt_str = j_dt.strftime("%Y/%m/%d %H:%M")
1520 self.assertEqual(exp_str, jt_str)
1522 @patch('jira.JIRA.__init__', return_value=None)
1523 def test_jira__no_auth(self, JIRA_mocked):
1524 Jira(BugTracker(tracker_type="jira", url='https://jira.com', public=True)).jira
1525 JIRA_mocked.assert_called_with({'server': 'https://jira.com', 'verify': False})
1527 @patch('jira.JIRA.__init__', return_value=None)
1528 def test_jira__with_auth(self, JIRA_mocked):
1529 Jira(BugTracker(tracker_type="jira", url='https://jira.com', public=True,
1530 username='user', password='password')).jira
1531 JIRA_mocked.assert_called_with({'server': 'https://jira.com', 'verify': False},
1532 basic_auth=('user', 'password'))
1534 def test__parse_custom_field(self):
1535 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT',
1536 public=True)
1537 jira = Jira(tracker)
1539 value1 = ['apple', 'cherry', 'orange']
1540 resp1 = jira._parse_custom_field(value1, to_str=False)
1541 resp1_str = jira._parse_custom_field(value1, to_str=True)
1542 self.assertEqual(resp1, value1)
1543 self.assertEqual(resp1_str, 'apple,cherry,orange')
1545 value2 = 'something clever'
1546 resp2 = jira._parse_custom_field(value2, to_str=False)
1547 resp2_str = jira._parse_custom_field(value2, to_str=True)
1548 self.assertEqual(resp2, value2)
1549 self.assertEqual(resp2_str, value2)
1551 value3 = [MagicMock(value="apple"), MagicMock(value="cherry"), MagicMock(value="orange")]
1552 resp3 = jira._parse_custom_field(value3, to_str=False)
1553 resp3_str = jira._parse_custom_field(value3, to_str=True)
1554 self.assertEqual(resp3, ['apple', 'cherry', 'orange'])
1555 self.assertEqual(resp3_str, 'apple,cherry,orange')
1557 value4 = MagicMock(value=25)
1558 resp4 = jira._parse_custom_field(value4, to_str=False)
1559 resp4_str = jira._parse_custom_field(value4, to_str=True)
1560 self.assertEqual(resp4, value4.value)
1561 self.assertEqual(resp4_str, "25")
1563 @patch('CIResults.bugtrackers.Jira.jira')
1564 def test_poll(self, connection_mock):
1565 connection_mock.issue.return_value = self.issue
1566 connection_mock.server_info.return_value = {'serverTime': '2020-05-07T10:36:28.772-0700'}
1568 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/',
1569 public=True, custom_fields_map={'cool_features': 'features',
1570 'fancy_platforms': 'platforms',
1571 'a_custom_field': 'my_custom_field'})
1572 bug = Bug(bug_id='1234', tracker=tracker)
1573 bug._save = bug.save
1574 bug.save = MagicMock()
1575 Jira(tracker).poll(bug)
1577 fields = connection_mock.issue.call_args[1]['fields']
1578 self.assertIn('fancy_platforms', fields)
1579 self.assertIn('cool_features', fields)
1580 self.assertEqual(bug.title, "summary")
1581 self.assertEqual(bug.created, datetime.datetime.fromtimestamp(0, tz=pytz.utc))
1582 self.assertEqual(bug.updated, datetime.datetime.fromtimestamp(1, tz=pytz.utc))
1583 self.assertEqual(bug.creator.person.full_name, "creator")
1584 self.assertEqual(bug.assignee.person.full_name, "assignee")
1585 self.assertEqual(bug.component, "component1,component2")
1586 self.assertEqual(bug.status, "status")
1587 self.assertEqual(bug.priority, "Low")
1588 self.assertEqual(bug.platforms, "Platform A,Platform B")
1589 self.assertEqual(bug.features, "Lasers,Sharks")
1590 self.assertEqual(bug.description, "description")
1591 self.assertEqual(bug.closed, datetime.datetime.fromtimestamp(42, tz=pytz.utc))
1592 self.assertEqual(bug.custom_fields['my_custom_field'], "tacos")
1593 self.assertEqual(bug.tags, "label1,label2")
1595 del (tracker.custom_fields_map['cool_features'])
1596 del (tracker.custom_fields_map['fancy_platforms'])
1597 Jira(tracker).poll(bug)
1598 self.assertIsNone(bug.platforms)
1599 self.assertIsNone(bug.features)
1601 # Check that the bug does not exist and no bugs have been polled
1602 bug.save.assert_not_called()
1603 self.assertEqual(len(BugComment.objects.all()), 0)
1605 # Save the bug, then poll again to check that the comments get created
1606 bug._save()
1607 bug.poll()
1608 bug.save.assert_not_called()
1610 # Check that the comments got created
1611 for c_id in ['12345', '12346']:
1612 comment = BugComment.objects.get(comment_id=c_id)
1613 self.assertEqual(comment.bug, bug)
1614 self.assertIn("flast", comment.account.user_id)
1615 self.assertIn("Last, First", comment.account.person.full_name)
1616 self.assertEqual(comment.url, "https://jira.com/browse/1234#comment-{}".format(c_id))
1617 self.assertEqual(comment.created_on, datetime.datetime.fromtimestamp(47, tz=pytz.utc))
1619 @patch('CIResults.bugtrackers.Jira.jira')
1620 def test_poll_invalid_custom_fields(self, connection_mock):
1621 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/',
1622 public=True, custom_fields_map={'id': 'id',
1623 'bug_id': 'bug_id',
1624 'tracker_id': 'tracker_id',
1625 'tracker': 'tracker',
1626 'parent_id': 'parent_id',
1627 'parent': 'parent'})
1628 bug = Bug(bug_id='1234', tracker=tracker)
1629 bug._save = bug.save
1630 bug.save = MagicMock()
1632 self.issue.fields.id = 9000
1633 self.issue.fields.bug_id = 4000
1634 self.issue.fields.tracker_id = 12341234
1635 self.issue.fields.parent_id = 9999999
1636 self.issue.fields.parent = 102030102
1637 connection_mock.issue.return_value = self.issue
1639 j = Jira(tracker)
1640 parse_mock = MagicMock()
1641 j._parse_custom_field = parse_mock
1642 j.poll(bug)
1644 parse_mock.assert_not_called()
1646 @patch('CIResults.bugtrackers.Jira.jira')
1647 def test_poll_invalid_status(self, connection_mock):
1648 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/',
1649 public=True)
1650 j = Jira(tracker)
1652 bug = Bug(bug_id='1234', tracker=tracker)
1653 bug._save = bug.save
1654 bug.save = MagicMock()
1656 issues = []
1657 for i in range(3):
1658 issues.append(copy.deepcopy(self.issue))
1660 type(issues[0].fields.status).name = PropertyMock(return_value='New State')
1661 type(issues[1].fields.status).name = PropertyMock(return_value='New State')
1662 type(issues[2].fields.status).name = PropertyMock(return_value='Open')
1664 connection_mock.issue.side_effect = issues
1666 j.poll(bug)
1667 self.assertEqual(bug.status, "Open")
1669 type(issues[2].fields.status).name = PropertyMock(return_value='New State')
1671 connection_mock.issue.side_effect = issues
1673 j.poll(bug)
1674 self.assertEqual(bug.status, "New State")
1676 def create_issue(self, comm_created, comm2_created):
1677 issue = MagicMock(fields=MagicMock(comment=MagicMock(comments=[])))
1679 name_1 = 'Pat'
1680 name_2 = 'Geno'
1681 email_1 = 'Pat@Pat'
1682 email_2 = 'Geno@Geno'
1683 comm_body_1 = "Steak wiz wit'"
1684 comm_body_2 = "Steak prov witout"
1685 issue.fields.comment.comments.append(MagicMock(author=MagicMock(displayName=name_1, emailAddress=email_1),
1686 id='12345', created=str(comm_created), body=comm_body_1))
1687 type(issue.fields.comment.comments[0].author).name = PropertyMock(return_value=name_1)
1688 issue.fields.comment.comments.append(MagicMock(author=MagicMock(displayName=name_2, emailAddress=email_2),
1689 id='12346', created=str(comm2_created), body=comm_body_2))
1690 type(issue.fields.comment.comments[1].author).name = PropertyMock(return_value=name_2)
1692 JiraMock.issue = MagicMock()
1693 JiraMock.issue.return_value = issue
1695 comments = [(name_1,
1696 comm_body_1,
1697 comm_created),
1698 (name_2,
1699 comm_body_2,
1700 comm2_created)]
1701 return (issue, comments)
1703 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1704 def test__poll_comments(self):
1705 comment_created = timezone.make_aware(datetime.datetime.now(), pytz.utc)
1706 comment2_created = timezone.make_aware(datetime.datetime.now(), pytz.utc)
1707 issue, exp_resp = self.create_issue(comment_created, comment2_created)
1709 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/',
1710 public=True)
1711 bug = Bug(bug_id='1234', tracker=tracker)
1712 bug.save()
1714 j = Jira(tracker)
1715 comm_list = j._Jira__poll_comments(bug, issue)
1716 result = []
1717 for c in comm_list:
1718 result.append((c.db_object.account.person.full_name,
1719 c.body,
1720 c.db_object.created_on))
1722 self.assertEqual(result, exp_resp)
1724 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1725 def test__poll_comments_after_polled(self):
1726 comment_created = timezone.make_aware(datetime.datetime.now(), pytz.utc)
1727 comment2_created = comment_created + timedelta(days=2)
1728 comment_polled = comment_created + timedelta(days=1)
1729 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/',
1730 public=True)
1731 j = Jira(tracker)
1732 bug = Bug(bug_id='1234', tracker=tracker, comments_polled=comment_polled)
1733 bug.save()
1735 issue, exp_resp = self.create_issue(comment_created, comment2_created)
1736 exp_resp = [exp_resp[1]]
1737 person = Person.objects.create(full_name=exp_resp[0])
1738 account = BugTrackerAccount.objects.create(user_id="1", person=person, tracker=tracker, is_developer=True)
1739 BugComment.objects.create(bug=bug, account=account, comment_id="12345", url='https://jira.com/browse/12345',
1740 created_on=comment_created)
1741 comm_list = j._Jira__poll_comments(bug, issue)
1742 result = []
1743 for c in comm_list:
1744 result.append((c.db_object.account.person.full_name,
1745 c.body,
1746 c.db_object.created_on))
1748 self.assertEqual(result, exp_resp)
1750 @patch('CIResults.bugtrackers.Jira.jira')
1751 def test_search_bugs_ids__full(self, connection_mock):
1752 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT',
1753 public=True)
1754 jira = Jira(tracker)
1756 # Mock the return value of the search command
1757 JiraBug = namedtuple('JiraBug', ('key', ))
1759 bugs1 = []
1760 for i in range(1000):
1761 bugs1.append(JiraBug(key=f"PRODUCT-{i}"))
1763 bugs2 = []
1764 for i in range(1000, 2000):
1765 bugs2.append(JiraBug(key=f"PRODUCT-{i}"))
1767 bugs3 = []
1768 for i in range(1000, 2000):
1769 bugs3.append(JiraBug(key=f"PRODUCT-{i}"))
1771 connection_mock.search_issues.side_effect = [ResultList(bugs1, _total=2500),
1772 ResultList(bugs2, _total=2500),
1773 ResultList(bugs3, _total=2500)]
1775 # Get the list of open bugs
1776 updated_since = datetime.datetime.fromtimestamp(125)
1777 open_bugs = jira.search_bugs_ids(components=["COMPONENT1", "COMPONENT2"],
1778 created_since=datetime.datetime.fromtimestamp(125),
1779 updated_since=updated_since,
1780 status=['status1', 'status2'])
1781 self.assertEqual(open_bugs, set([b.key for b in bugs1+bugs2+bugs3]))
1783 # Verify that the request was valid
1784 connection_mock.search_issues.assert_any_call('project = \'PRODUCT\' '
1785 'AND component in ("COMPONENT1", "COMPONENT2") '
1786 'AND created > "1970/01/01 00:02" '
1787 'AND updated > "1970/01/01 00:02" '
1788 'AND status in ("status1", "status2")',
1789 fields=['key'], maxResults=1000)
1790 connection_mock.search_issues.assert_any_call('project = \'PRODUCT\' '
1791 'AND component in ("COMPONENT1", "COMPONENT2") '
1792 'AND created > "1970/01/01 00:02" '
1793 'AND updated > "1970/01/01 00:02" '
1794 'AND status in ("status1", "status2")',
1795 fields=['key'], maxResults=1000, startAt=1000)
1796 connection_mock.search_issues.assert_any_call('project = \'PRODUCT\' '
1797 'AND component in ("COMPONENT1", "COMPONENT2") '
1798 'AND created > "1970/01/01 00:02" '
1799 'AND updated > "1970/01/01 00:02" '
1800 'AND status in ("status1", "status2")',
1801 fields=['key'], maxResults=1000, startAt=2000)
1803 @patch('CIResults.bugtrackers.Jira.jira')
1804 def test_open_statuses(self, j_mock):
1805 def statuses():
1806 stat1_cat = MagicMock()
1807 stat1_cat.name = "To Do"
1808 stat1 = MagicMock(statusCategory=stat1_cat)
1809 stat1.name = "Open"
1811 stat2_cat = MagicMock()
1812 stat2_cat.name = "In Progress"
1813 stat2 = MagicMock(statusCategory=stat2_cat)
1814 stat2.name = "Scoping"
1816 stat3_cat = MagicMock()
1817 stat3_cat.name = "Done"
1818 stat3 = MagicMock(statusCategory=stat3_cat)
1819 stat3.name = "Closed"
1821 return [stat1, stat2, stat3]
1823 j_mock.statuses = statuses
1824 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT',
1825 public=True)
1826 jira = Jira(tracker)
1827 self.assertEqual(jira.open_statuses, ['Open', 'Scoping'])
1829 @patch('CIResults.bugtrackers.Jira.jira')
1830 def test_add_comment(self, connection_mock):
1831 tracker = BugTracker.objects.create(tracker_type="jira", public=True)
1832 jira = Jira(tracker)
1834 jira.add_comment(Bug(tracker=tracker, bug_id="JIRA-123"), "My comment")
1836 connection_mock.add_comment.assert_called_with("JIRA-123", "My comment")
1838 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1839 def test_create_bug_from_json(self):
1840 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
1841 project=JiraMock.PROJECT_KEY,
1842 public=True)
1843 j = Jira(tracker)
1844 resp = MagicMock(key=1)
1845 JiraMock.create_issue = MagicMock(return_value=resp)
1846 summary = "summary"
1847 description = "description"
1848 status = {"name": "FOO"}
1849 components = [{"name": "BAR"}, {"name": "Blah"}]
1850 json_bug = {'summary': summary,
1851 'description': description,
1852 'status': status,
1853 'components': components,
1854 'issue_type': {'name': "Bug"}}
1856 with patch('CIResults.bugtrackers.Jira.transition') as t_mock:
1857 j.create_bug_from_json(json_bug)
1858 t_mock.assert_not_called()
1859 args, kwargs = JiraMock.create_issue.call_args_list[0]
1860 request = kwargs['fields']
1861 self.assertEqual(request['project'], {'key': JiraMock.PROJECT_KEY})
1862 del (request['project'])
1863 self.assertEqual(request['issuetype'], {'name': "Bug"})
1864 del (request['issuetype'])
1865 for field in json_bug:
1866 self.assertEqual(request[field], json_bug[field])
1868 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1869 def test_create_bug_from_json_title(self):
1870 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
1871 project=JiraMock.PROJECT_KEY,
1872 public=True)
1873 j = Jira(tracker)
1874 resp = MagicMock(key=1)
1875 JiraMock.create_issue = MagicMock(return_value=resp)
1876 summary = "summary"
1877 description = "description"
1878 status = {"name": "FOO"}
1879 components = [{"name": "BAR"}, {"name": "Blah"}]
1880 json_bug = {'title': summary,
1881 'description': description,
1882 'status': status,
1883 'components': components}
1885 j.create_bug_from_json(json_bug)
1886 args, kwargs = JiraMock.create_issue.call_args_list[0]
1887 request = kwargs['fields']
1888 self.assertEqual(request['summary'], summary)
1890 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1891 def test_create_bug_from_json_issuetype(self):
1892 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
1893 project=JiraMock.PROJECT_KEY,
1894 public=True)
1895 j = Jira(tracker)
1896 resp = MagicMock(key=1)
1897 JiraMock.create_issue = MagicMock(return_value=resp)
1898 summary = "summary"
1899 description = "description"
1900 status = {"name": "FOO"}
1901 components = [{"name": "BAR"}, {"name": "Blah"}]
1902 json_bug = {'title': summary,
1903 'description': description,
1904 'status': status,
1905 'components': components}
1907 j.create_bug_from_json(json_bug)
1908 args, kwargs = JiraMock.create_issue.call_args_list[0]
1909 request = kwargs['fields']
1910 self.assertEqual(request['issuetype'], {'name': "Bug"})
1912 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1913 def test_create_bug_from_json_error(self):
1914 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
1915 project=JiraMock.PROJECT_KEY,
1916 public=True)
1917 j = Jira(tracker)
1918 JiraMock.create_issue = MagicMock(side_effect=JIRAError)
1919 summary = "summary"
1920 description = "description"
1921 status = {"name": "FOO"}
1922 components = [{"name": "BAR"}, {"name": "Blah"}]
1923 json_bug = {'title': summary,
1924 'description': description,
1925 'status': status,
1926 'components': components}
1928 with self.assertRaises(ValueError):
1929 j.create_bug_from_json(json_bug)
1931 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1932 def test_update_bug_from_json(self):
1933 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
1934 project=JiraMock.PROJECT_KEY,
1935 public=True)
1936 j = Jira(tracker)
1937 JiraMock.issue = MagicMock()
1938 JiraMock.issue.return_value.update = MagicMock()
1939 JiraMock.transition_issue = MagicMock()
1940 summary = "summary"
1941 status = {"name": "FOO"}
1942 json_bug = {'summary': summary,
1943 'status': status}
1945 j.update_bug_from_json(json_bug, 1)
1946 args, kwargs = JiraMock.issue.return_value.update.call_args_list[0]
1947 request = kwargs['fields']
1948 self.assertEqual(request['project'], {'key': JiraMock.PROJECT_KEY})
1950 del (request['project'])
1951 for field in json_bug:
1952 self.assertEqual(request[field], json_bug[field])
1953 JiraMock.transition_issue.assert_not_called()
1955 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1956 def test_update_bug_from_json_error(self):
1957 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
1958 project=JiraMock.PROJECT_KEY,
1959 public=True)
1960 j = Jira(tracker)
1961 JiraMock.issue = MagicMock()
1962 JiraMock.issue.return_value.update = MagicMock(side_effect=JIRAError)
1964 with self.assertRaises(ValueError):
1965 j.update_bug_from_json({"foo": "bar"}, 1)
1967 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1968 def test_update_bug_from_json_transition(self):
1969 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
1970 project=JiraMock.PROJECT_KEY,
1971 public=True)
1972 j = Jira(tracker)
1973 issue = MagicMock()
1974 JiraMock.issue = MagicMock()
1975 JiraMock.issue.return_value = issue
1976 issue.update = MagicMock()
1977 j.transition = MagicMock()
1978 json_bug = {'transition': {'status': "Open", 'fields': {'resolution': 'In Progress'}}}
1980 j.update_bug_from_json(json_bug, 1)
1981 j.transition.assert_called_with(1, "Open", {'resolution': "In Progress"})
1983 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
1984 def test_update_bug_from_json_update_field(self):
1985 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
1986 project=JiraMock.PROJECT_KEY,
1987 public=True)
1988 j = Jira(tracker)
1989 issue = MagicMock()
1990 JiraMock.issue = MagicMock()
1991 JiraMock.issue.return_value = issue
1992 issue.update = MagicMock()
1993 json_bug = {'update': {'labels': [{'add': 'foo'}]}, 'summary': "Foo"}
1995 j.update_bug_from_json(json_bug, 1)
1996 issue.update.assert_called_with(update={'labels': [{'add': 'foo'}]},
1997 fields={'summary': "Foo", 'project': {'key': "TEST"}})
1999 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
2000 def test_transition(self):
2001 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
2002 project=JiraMock.PROJECT_KEY,
2003 public=True)
2004 j = Jira(tracker)
2005 issue = MagicMock()
2006 JiraMock.issue = MagicMock()
2007 JiraMock.issue.return_value = issue
2008 JiraMock.transition_issue = MagicMock()
2009 status = "Open"
2010 fields = {'resolution': 'In Progress'}
2012 j.transition(1, status, fields)
2013 JiraMock.transition_issue.assert_called_with(1, "Open", fields={'resolution': "In Progress"})
2015 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
2016 def test_transition_error(self):
2017 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
2018 project=JiraMock.PROJECT_KEY,
2019 public=True)
2020 j = Jira(tracker)
2021 issue = MagicMock()
2022 JiraMock.issue = MagicMock()
2023 JiraMock.issue.return_value = issue
2024 JiraMock.transition_issue = MagicMock(side_effect=JIRAError)
2025 status = "Open"
2026 fields = {'resolution': 'In Progress'}
2028 with self.assertRaises(ValueError):
2029 j.transition(1, status, fields)
2031 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
2032 def test_transition_create_bug_from_json(self):
2033 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
2034 project=JiraMock.PROJECT_KEY,
2035 public=True)
2036 j = Jira(tracker)
2037 resp = MagicMock(key=1)
2038 issue = MagicMock()
2039 JiraMock.issue = MagicMock()
2040 JiraMock.issue.return_value = issue
2041 JiraMock.create_issue = MagicMock(return_value=resp)
2042 j.transition = MagicMock()
2043 json_bug = {'summary': "FOO", 'transition': {'status': "Closed", 'fields': {'resolution': 'Fixed'}}}
2045 bug_id = j.create_bug_from_json(json_bug)
2046 self.assertEqual(bug_id, 1)
2047 j.transition.assert_called_with(1, "Closed", {'resolution': "Fixed"})
2049 @patch('CIResults.bugtrackers.Jira.jira', JiraMock)
2050 def test_transition_create_bug_from_json_error(self):
2051 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo",
2052 project=JiraMock.PROJECT_KEY,
2053 public=True)
2054 j = Jira(tracker)
2055 resp = MagicMock(key=1)
2056 JiraMock.create_issue = MagicMock(return_value=resp)
2057 j.transition = MagicMock(side_effect=ValueError)
2058 json_bug = {"title": "bar", "transition": {"status": "Open"}}
2060 bug_id = j.create_bug_from_json(json_bug)
2061 self.assertEqual(bug_id, 1)
2064class BugTrackerJiraUntrackedTests(TestCase):
2065 def setUp(self):
2066 self.db_tracker = BugTracker.objects.create(tracker_type="jira_untracked",
2067 project='PROJECT', public=True)
2069 def test_poll(self):
2070 bug = MagicMock(spec=Bug)
2071 Untracked(self.db_tracker).poll(bug)
2073 self.assertEqual(bug.title, "UNKNOWN")
2074 self.assertEqual(bug.status, "UNKNOWN")
2075 bug.save.assert_not_called()
2077 def test_search_bugs_ids(self):
2078 self.assertEqual(Untracked(None).search_bugs_ids(), set())
2080 def test_open_statuses(self):
2081 self.assertEqual(Untracked(None).open_statuses, [])
2083 def test_add_comment(self):
2084 self.assertEqual(Untracked(None).add_comment(Bug(bug_id="1234"), "Hello World"), None)
2086 def test_create_bug(self):
2087 self.assertEqual(Untracked(self.db_tracker).create_bug(Bug(tracker=self.db_tracker)), None)