Coverage for CIResults/tests/test_bugtrackers.py: 100%

1273 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-23 13:11 +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 

7 

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 

11 

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 

22 

23 

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"} 

32 

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 

40 

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]}) 

45 

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" 

49 

50 db_tracker = BugTracker(name="Tracker1", public=True) 

51 common = BugTrackerCommon(db_tracker) 

52 common.accounts_cached = {user_id: MagicMock()} 

53 

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) 

58 

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" 

65 

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) 

69 

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) 

75 

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) 

81 

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) 

87 

88 def test_create_bug(self): 

89 db_tracker = BugTracker.objects.create(name="Tracker1", project="FOO", public=True) 

90 common = BugTrackerCommon(db_tracker) 

91 

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]) 

98 

99 def test_create_bug_existing(self): 

100 db_tracker = BugTracker.objects.create(name="Tracker1", project="FOO", public=True) 

101 common = BugTrackerCommon(db_tracker) 

102 

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) 

107 

108 def test_create_bug_no_project(self): 

109 db_tracker = BugTracker.objects.create(name="Tracker1", public=True) 

110 common = BugTrackerCommon(db_tracker) 

111 

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) 

116 

117 def test__parse_custom_field(self): 

118 db_tracker = BugTracker.objects.create(name="Tracker1", public=True) 

119 common = BugTrackerCommon(db_tracker) 

120 

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)) 

123 

124 

125class SandboxMock(): 

126 @classmethod 

127 def get_or_create_instance(cls, script): 

128 return cls(script) 

129 

130 def __init__(self, script): 

131 self.script = script 

132 

133 def call_user_function(self, fn, kwargs): 

134 code = compile(self.script, "<user script>", 'exec') 

135 exec(code) 

136 return locals()[fn](**kwargs) 

137 

138 

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) 

158 

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") 

164 

165 self.bug = Bug.objects.create(tracker=self.db_tracker, bug_id=2, 

166 title=self.title, description=self.description) 

167 

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 

180 

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 

187 

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": {}} 

192 

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") 

198 

199 def test_tracker_check_replication_update(self): 

200 client = MagicMock() 

201 bug = Bug.objects.create(tracker=self.rep_tracker, parent=self.bug) 

202 

203 client.call_user_function = MagicMock() 

204 client.call_user_function.return_value = {"set_fields": {}} 

205 

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") 

212 

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, []) 

218 

219 bug.parent = self.bug 

220 bug.save() 

221 

222 resp = self.tracker.tracker_check_replication([bug], self.rep_tracker, self.script, client, dryrun=True) 

223 self.assertEqual(resp, []) 

224 

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": {}} 

229 

230 resp = self.tracker.tracker_check_replication([self.bug], self.rep_tracker, self.script, client, dryrun=True) 

231 self.assertEqual(resp, []) 

232 

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}) 

237 

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}] 

262 

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) 

268 

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") 

288 

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) 

303 

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")]) 

308 

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) 

322 

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() 

327 

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') 

355 

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') 

379 

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) 

403 

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) 

410 

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"]) 

419 

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() 

428 

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") 

439 

440 jira_mock.return_value.create_bug_from_json.return_value = 11 

441 gitlab_mock.return_value.create_bug_from_json.return_value = 12 

442 

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)) 

448 

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) 

455 

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, []) 

462 

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 

469 

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) 

475 

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() 

485 

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() 

491 

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)) 

496 

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)) 

501 

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)) 

506 

507 

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' 

524 

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' 

535 

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 } 

579 

580 def __init__(self, url, **kwargs): 

581 self.url = url 

582 self.headers = {} 

583 

584 if url not in self.RESPONSES.keys(): 

585 raise ValueError("unknown URL: {}".format(url)) # pragma: no cover 

586 

587 if kwargs['headers']['PRIVATE-TOKEN'] != self.PRIVATE_TOKEN: 

588 raise ValueError("GitLab needs PRIVATE-TOKEN for querying API") # pragma: no cover 

589 

590 RequestsGetMock.last_URL = url 

591 RequestsGetMock.last_params = kwargs.get('params') 

592 RequestsGetMock.last_headers = kwargs.get('headers') 

593 

594 def raise_for_status(self): 

595 pass 

596 

597 def json(self): 

598 return self.RESPONSES[self.url] 

599 

600 

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/" 

605 

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) 

610 

611 self.bug = Bug.objects.create(tracker=self.db_tracker, bug_id=str(RequestsGetMock.BUG_ID)) 

612 self.gitlab = GitLab(self.db_tracker) 

613 

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)) 

621 

622 def test_ToTrackerTz(self): 

623 dt = timezone.now() 

624 self.assertEqual(dt, self.gitlab._to_tracker_tz(dt)) 

625 

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() 

631 

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") 

652 

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] 

659 

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'}) 

667 

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()) 

674 

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 

687 

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) 

692 

693 comments = BugComment.objects.filter(bug=self.bug) 

694 self.assertEqual(comments.count(), 2) 

695 

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) 

700 

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) 

706 

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))] 

715 

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) 

723 

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) 

730 

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)) 

737 

738 expected = [(RequestsGetMock.CREATOR_NAME, 

739 RequestsGetMock.NOTE_TWO_BODY, 

740 dateparser.parse(RequestsGetMock.NOTE_TWO_CREATED_AT))] 

741 

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) 

751 

752 comments = BugComment.objects.filter(bug=self.bug) 

753 self.assertEqual(comments.count(), 2) 

754 

755 self.gitlab.poll(self.bug) 

756 comments = BugComment.objects.filter(bug=self.bug) 

757 self.assertEqual(comments.count(), 2) 

758 

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}) 

764 

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 }) 

781 

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 }) 

790 

791 def testSearchWithMoreThanOneStatus(self): 

792 self.assertRaisesMessage(ValueError, "Status has to be a string", 

793 self.gitlab.search_bugs_ids, status=['status1', 'status2']) 

794 

795 def test_open_statuses(self): 

796 self.assertEqual(self.gitlab.open_statuses, ['opened']) 

797 

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) 

802 

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}) 

808 

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"} 

818 

819 post_mock.return_value.raise_for_status.return_value = None 

820 post_mock.return_value.json.return_value = {"iid": test_id} 

821 

822 id = self.gitlab.create_bug_from_json(json_bug) 

823 self.assertEqual(id, test_id) 

824 

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]) 

829 

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} 

837 

838 post_mock.return_value.raise_for_status.return_value = None 

839 post_mock.return_value.json.return_value = {"iid": test_id} 

840 

841 id = self.gitlab.create_bug_from_json(json_bug) 

842 self.assertEqual(id, test_id) 

843 

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) 

850 

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"} 

859 

860 post_mock.return_value.raise_for_status.return_value = None 

861 post_mock.return_value.json.return_value = {"iid": test_id} 

862 

863 id = self.gitlab.create_bug_from_json(json_bug) 

864 self.assertEqual(id, test_id) 

865 

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) 

872 

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"} 

880 

881 post_mock.side_effect = requests.HTTPError 

882 

883 with self.assertRaises(ValueError): 

884 self.gitlab.create_bug_from_json(json_bug) 

885 

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"} 

892 

893 self.gitlab.update_bug_from_json(json_bug, 5678) 

894 

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]) 

899 

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) 

905 

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) 

910 

911 

912class BugzillaProxyMock: 

913 URL = "https://bugzilla.instance.org" 

914 

915 # User.login 

916 LOGIN = "userlogin" 

917 PASSWORD = "password" 

918 TOKEN_ID = '12345' 

919 TOKEN = '12345-kZ5CYMeQGH' 

920 

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' 

927 

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!" 

935 

936 # Update 

937 UPDATE_IDS = 1 

938 

939 PROJECT = "{}/{}".format(PRODUCT, COMPONENT) 

940 

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) 

953 

954 class _Bugzilla: 

955 def time(self): 

956 return {'db_time': datetime.datetime.fromtimestamp(0)} 

957 

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} 

965 

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 

981 

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 } 

1007 

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 

1015 

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 } 

1036 

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] 

1041 

1042 return {"bugs": {"1234": comments, "1235": comments, "1237": comments}} 

1043 

1044 def history(self, params): 

1045 if params.get('ids') != BugzillaProxyMock.BUG_ID: 

1046 raise ValueError('Incorrect or missing bug id') # pragma: no cover 

1047 

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 } 

1069 

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} 

1078 

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 

1086 

1087 return {'id': '1'} 

1088 

1089 def search(self, params): 

1090 self.last_search_request = params 

1091 return {'bugs': [{"id": 10}, {"id": 11}, {"id": 13}]} 

1092 

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 

1096 

1097 # All the other checks are the same as create(), so just call that 

1098 self.create(params) 

1099 

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 

1103 

1104 if not use_builtin_types: 

1105 raise ValueError('use_builtin_types is not True') # pragma: no cover 

1106 

1107 User = _User() 

1108 Bug = _Bug() 

1109 Bugzilla = _Bugzilla() 

1110 

1111 

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) 

1124 

1125 def test__get_tracker_time(self): 

1126 dt = datetime.datetime.fromtimestamp(0, pytz.utc) 

1127 self.assertEqual(dt, self.bugzilla._get_tracker_time()) 

1128 

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)) 

1133 

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') 

1138 

1139 self.assertEqual(Bugzilla._get_user_id({'creator_detail': {'name': 'John Doe'}}, 

1140 'creator'), 'John Doe') 

1141 

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') 

1145 

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') 

1149 

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')) 

1154 

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') 

1161 

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) 

1167 

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() 

1171 

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] 

1175 

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)) 

1181 

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() 

1200 

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) 

1209 

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 

1228 

1229 with patch('CIResults.bugtrackers.Bugzilla.check_replication'): 

1230 bugzilla.poll(bug) 

1231 

1232 parse_mock.assert_not_called() 

1233 

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)) 

1237 

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)) 

1244 

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) 

1250 

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) 

1255 

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() 

1262 

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) 

1267 

1268 bug.save.assert_not_called() 

1269 

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() 

1276 

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'])) 

1285 

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) 

1295 

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) 

1302 

1303 def test_open_statuses(self): 

1304 self.assertEqual(self.bugzilla.open_statuses, ["NEW", "ASSIGNED", "REOPENED", "NEEDINFO"]) 

1305 

1306 def test_auth_login(self): 

1307 self.assertEqual(self.bugzilla.get_auth_token(), BugzillaProxyMock.TOKEN) 

1308 

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) 

1314 

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) 

1320 

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) 

1324 

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) 

1330 

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) 

1336 

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) 

1342 

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({}) 

1347 

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) 

1353 

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({}) 

1358 

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}) 

1363 

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) 

1369 

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) 

1375 

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) 

1380 

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) 

1385 

1386 

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" 

1394 

1395 ISSUE = MagicMock() 

1396 ISSUE.key = ISSUE_KEY 

1397 

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 

1402 

1403 # User.login 

1404 LOGIN = "userlogin" 

1405 PASSWORD = "password" 

1406 

1407 # Bug.add_comment 

1408 BUG_ID = 1234 

1409 COMMENT = 'my comment' 

1410 

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 } 

1421 

1422 RESPONSES = {REQ_URL: 

1423 {"id": NEW_BUG_ID, 

1424 "key": ISSUE_KEY, 

1425 "self": RESP_URL}} 

1426 

1427 

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'] 

1453 

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') 

1463 

1464 self.issue = issue 

1465 

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) 

1470 

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() 

1474 

1475 self.assertEqual(dt, jt) 

1476 

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) 

1482 

1483 j_mock.server_info = MagicMock(side_effect=JIRAError) 

1484 now_mock = MagicMock() 

1485 tz_mock.now = now_mock 

1486 jira._get_tracker_time() 

1487 

1488 now_mock.assert_called() 

1489 

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) 

1494 

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) 

1498 

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") 

1503 

1504 self.assertEqual(exp_str, jt_str) 

1505 

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) 

1510 

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) 

1514 

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") 

1519 

1520 self.assertEqual(exp_str, jt_str) 

1521 

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}) 

1526 

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')) 

1533 

1534 def test__parse_custom_field(self): 

1535 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', 

1536 public=True) 

1537 jira = Jira(tracker) 

1538 

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') 

1544 

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) 

1550 

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') 

1556 

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") 

1562 

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'} 

1567 

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) 

1576 

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") 

1594 

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) 

1600 

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) 

1604 

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() 

1609 

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)) 

1618 

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() 

1631 

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 

1638 

1639 j = Jira(tracker) 

1640 parse_mock = MagicMock() 

1641 j._parse_custom_field = parse_mock 

1642 j.poll(bug) 

1643 

1644 parse_mock.assert_not_called() 

1645 

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) 

1651 

1652 bug = Bug(bug_id='1234', tracker=tracker) 

1653 bug._save = bug.save 

1654 bug.save = MagicMock() 

1655 

1656 issues = [] 

1657 for i in range(3): 

1658 issues.append(copy.deepcopy(self.issue)) 

1659 

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') 

1663 

1664 connection_mock.issue.side_effect = issues 

1665 

1666 j.poll(bug) 

1667 self.assertEqual(bug.status, "Open") 

1668 

1669 type(issues[2].fields.status).name = PropertyMock(return_value='New State') 

1670 

1671 connection_mock.issue.side_effect = issues 

1672 

1673 j.poll(bug) 

1674 self.assertEqual(bug.status, "New State") 

1675 

1676 def create_issue(self, comm_created, comm2_created): 

1677 issue = MagicMock(fields=MagicMock(comment=MagicMock(comments=[]))) 

1678 

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) 

1691 

1692 JiraMock.issue = MagicMock() 

1693 JiraMock.issue.return_value = issue 

1694 

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) 

1702 

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) 

1708 

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() 

1713 

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)) 

1721 

1722 self.assertEqual(result, exp_resp) 

1723 

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() 

1734 

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)) 

1747 

1748 self.assertEqual(result, exp_resp) 

1749 

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) 

1755 

1756 # Mock the return value of the search command 

1757 JiraBug = namedtuple('JiraBug', ('key', )) 

1758 

1759 bugs1 = [] 

1760 for i in range(1000): 

1761 bugs1.append(JiraBug(key=f"PRODUCT-{i}")) 

1762 

1763 bugs2 = [] 

1764 for i in range(1000, 2000): 

1765 bugs2.append(JiraBug(key=f"PRODUCT-{i}")) 

1766 

1767 bugs3 = [] 

1768 for i in range(1000, 2000): 

1769 bugs3.append(JiraBug(key=f"PRODUCT-{i}")) 

1770 

1771 connection_mock.search_issues.side_effect = [ResultList(bugs1, _total=2500), 

1772 ResultList(bugs2, _total=2500), 

1773 ResultList(bugs3, _total=2500)] 

1774 

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])) 

1782 

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) 

1802 

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" 

1810 

1811 stat2_cat = MagicMock() 

1812 stat2_cat.name = "In Progress" 

1813 stat2 = MagicMock(statusCategory=stat2_cat) 

1814 stat2.name = "Scoping" 

1815 

1816 stat3_cat = MagicMock() 

1817 stat3_cat.name = "Done" 

1818 stat3 = MagicMock(statusCategory=stat3_cat) 

1819 stat3.name = "Closed" 

1820 

1821 return [stat1, stat2, stat3] 

1822 

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']) 

1828 

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) 

1833 

1834 jira.add_comment(Bug(tracker=tracker, bug_id="JIRA-123"), "My comment") 

1835 

1836 connection_mock.add_comment.assert_called_with("JIRA-123", "My comment") 

1837 

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"}} 

1855 

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]) 

1867 

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} 

1884 

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) 

1889 

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} 

1906 

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"}) 

1911 

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} 

1927 

1928 with self.assertRaises(ValueError): 

1929 j.create_bug_from_json(json_bug) 

1930 

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} 

1944 

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}) 

1949 

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() 

1954 

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) 

1963 

1964 with self.assertRaises(ValueError): 

1965 j.update_bug_from_json({"foo": "bar"}, 1) 

1966 

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'}}} 

1979 

1980 j.update_bug_from_json(json_bug, 1) 

1981 j.transition.assert_called_with(1, "Open", {'resolution': "In Progress"}) 

1982 

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"} 

1994 

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"}}) 

1998 

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'} 

2011 

2012 j.transition(1, status, fields) 

2013 JiraMock.transition_issue.assert_called_with(1, "Open", fields={'resolution': "In Progress"}) 

2014 

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'} 

2027 

2028 with self.assertRaises(ValueError): 

2029 j.transition(1, status, fields) 

2030 

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'}}} 

2044 

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"}) 

2048 

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"}} 

2059 

2060 bug_id = j.create_bug_from_json(json_bug) 

2061 self.assertEqual(bug_id, 1) 

2062 

2063 

2064class BugTrackerJiraUntrackedTests(TestCase): 

2065 def setUp(self): 

2066 self.db_tracker = BugTracker.objects.create(tracker_type="jira_untracked", 

2067 project='PROJECT', public=True) 

2068 

2069 def test_poll(self): 

2070 bug = MagicMock(spec=Bug) 

2071 Untracked(self.db_tracker).poll(bug) 

2072 

2073 self.assertEqual(bug.title, "UNKNOWN") 

2074 self.assertEqual(bug.status, "UNKNOWN") 

2075 bug.save.assert_not_called() 

2076 

2077 def test_search_bugs_ids(self): 

2078 self.assertEqual(Untracked(None).search_bugs_ids(), set()) 

2079 

2080 def test_open_statuses(self): 

2081 self.assertEqual(Untracked(None).open_statuses, []) 

2082 

2083 def test_add_comment(self): 

2084 self.assertEqual(Untracked(None).add_comment(Bug(bug_id="1234"), "Hello World"), None) 

2085 

2086 def test_create_bug(self): 

2087 self.assertEqual(Untracked(self.db_tracker).create_bug(Bug(tracker=self.db_tracker)), None)