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

1274 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-09 12:54 +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 sandbox_globals = {} 

136 exec(code, sandbox_globals) 

137 return sandbox_globals[fn](**kwargs) 

138 

139 

140@patch('CIResults.bugtrackers.Client', SandboxMock) 

141class BugTrackerReplicationTests(TestCase): 

142 def setUp(self): 

143 self.title = "We are the knights who say..." 

144 self.description = "Ni!" 

145 self.script = """\ 

146def replication_check(src_bug, dest_bug): 

147 if int(src_bug['bug_id']) % 2 == 0: 

148 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']}, 

149 "add_comments": ["apple", "pie"]} 

150 else: 

151 return {} 

152 """ 

153 self.db_tracker = BugTracker.objects.create(name="Tracker1", project="TEST", tracker_type="bugzilla", 

154 url="http://bar", public=True) 

155 self.rep_tracker = BugTracker.objects.create(name="Tracker2", tracker_type="jira", url="http://foo", 

156 project="TEST2", public=True) 

157 self.tracker = Untracked(self.db_tracker) 

158 self.tracker2 = Untracked(self.rep_tracker) 

159 

160 self.rp = ReplicationScript.objects.create(source_tracker=self.db_tracker, 

161 destination_tracker=self.rep_tracker, 

162 script=self.script, 

163 enabled=True, 

164 name="BAR") 

165 

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

167 title=self.title, description=self.description) 

168 

169 def get_mirrored_bug_if_created(self, bug, bug_id=4, comments=None, ret_mocks=False): 

170 comments = comments if comments else [] 

171 with patch('CIResults.bugtrackers.Jira.create_bug_from_json', MagicMock()) as create_mock: 

172 with patch('CIResults.bugtrackers.Jira.poll', autospec=True): 

173 with patch('CIResults.bugtrackers.Jira.add_comment', MagicMock()) as add_comm_mock: 

174 create_mock.return_value = bug_id 

175 self.tracker.check_replication(bug, comments) 

176 bug = Bug.objects.filter(parent=bug).first() 

177 if ret_mocks: 

178 return (bug, create_mock, add_comm_mock) 

179 else: 

180 return bug 

181 

182 def get_updated_bug(self, bug): 

183 with patch('CIResults.bugtrackers.Jira.update_bug_from_json', MagicMock()) as upd_mock: 

184 with patch('CIResults.bugtrackers.Jira.poll', autospec=True): 

185 with patch('CIResults.bugtrackers.Jira.add_comment', MagicMock()) as add_comm_mock: 

186 self.tracker.check_replication(bug, []) 

187 return upd_mock, add_comm_mock 

188 

189 def test_tracker_check_replication(self): 

190 client = MagicMock() 

191 client.call_user_function = MagicMock() 

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

193 

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

195 ser_bug = serialize_bug(self.bug) 

196 client.call_user_function.assert_called_with("replication_check", kwargs={"src_bug": ser_bug, 

197 "dest_bug": None}) 

198 self.assertEqual(resp[0]["operation"], "create") 

199 

200 def test_tracker_check_replication_update(self): 

201 client = MagicMock() 

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

203 

204 client.call_user_function = MagicMock() 

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

206 

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

208 ser_bug = serialize_bug(self.bug) 

209 ser_dest_bug = serialize_bug(bug) 

210 client.call_user_function.assert_called_with("replication_check", kwargs={"src_bug": ser_bug, 

211 "dest_bug": ser_dest_bug}) 

212 self.assertEqual(resp[0]["operation"], "update") 

213 

214 def test_tracker_check_replication_invalid_bug(self): 

215 client = MagicMock() 

216 bug = Bug(tracker=self.db_tracker) 

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

218 self.assertEqual(resp, []) 

219 

220 bug.parent = self.bug 

221 bug.save() 

222 

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

224 self.assertEqual(resp, []) 

225 

226 def test_tracker_check_replication_client_error(self): 

227 client = MagicMock() 

228 client.call_user_function = MagicMock(side_effect=Exception()) 

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

230 

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

232 self.assertEqual(resp, []) 

233 

234 def test_check_replication(self): 

235 m_bug, mock, _ = self.get_mirrored_bug_if_created(self.bug, ret_mocks=True) 

236 self.assertIsNotNone(m_bug) 

237 mock.assert_called_with({'description': self.description, 'title': self.title}) 

238 

239 def test_check_replication_comments(self): 

240 name = "Ada" 

241 email = "foo@bar.com" 

242 body = "this is a test" 

243 body2 = "this is also a test" 

244 person = Person.objects.create(full_name=name) 

245 person2 = Person.objects.create(email=email) 

246 account = BugTrackerAccount.objects.create(person=person, user_id="1", 

247 tracker=self.db_tracker, is_developer=True) 

248 account2 = BugTrackerAccount.objects.create(person=person2, user_id="2", 

249 tracker=self.db_tracker, is_developer=True) 

250 created = datetime.datetime.now() 

251 created2 = datetime.datetime.now() + timedelta(days=1) 

252 comment = BugComment.objects.create(bug=self.bug, account=account, 

253 created_on=created, comment_id="1") 

254 comment2 = BugComment.objects.create(bug=self.bug, account=account2, 

255 created_on=created2, comment_id="2") 

256 new_comments = [BugCommentTransport(comment, body), BugCommentTransport(comment2, body2)] 

257 exp_comm = [{'author': name, 

258 'created': str(created), 

259 'body': body}, 

260 {'author': email, 

261 'created': str(created2), 

262 'body': body2}] 

263 

264 with patch('CIResults.bugtrackers.Client.call_user_function') as cuf_mock: 

265 m_bug, mock, _ = self.get_mirrored_bug_if_created(self.bug, comments=new_comments, ret_mocks=True) 

266 self.assertIsNotNone(m_bug) 

267 src_bug_comments = cuf_mock.call_args[1]['kwargs']['src_bug']['new_comments'] 

268 self.assertEqual(src_bug_comments, exp_comm) 

269 

270 def test_check_replication_add_comments_string(self): 

271 self.rp.delete() 

272 script = """\ 

273def replication_check(src_bug, dest_bug): 

274 if int(src_bug['bug_id']) % 2 == 0: 

275 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']}, 

276 "add_comments": "Doh"} 

277 else: 

278 return {} 

279 """ 

280 ReplicationScript.objects.create(source_tracker=self.db_tracker, 

281 destination_tracker=self.rep_tracker, 

282 script=script, 

283 enabled=True, 

284 name="BAR") 

285 m_bug, mock, add_comm_mock = self.get_mirrored_bug_if_created(self.bug, ret_mocks=True) 

286 self.assertIsNotNone(m_bug) 

287 mock.assert_called_with({'description': self.description, 'title': self.title}) 

288 add_comm_mock.assert_called_with(m_bug, "Doh") 

289 

290 def test_check_replication_add_comments_list(self): 

291 self.rp.delete() 

292 script = """\ 

293def replication_check(src_bug, dest_bug): 

294 if int(src_bug['bug_id']) % 2 == 0: 

295 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']}, 

296 "add_comments": ["hello","world","foo"]} 

297 else: 

298 return {} 

299 """ 

300 ReplicationScript.objects.create(source_tracker=self.db_tracker, 

301 destination_tracker=self.rep_tracker, 

302 script=script, 

303 enabled=True) 

304 

305 m_bug, mock, add_comm_mock = self.get_mirrored_bug_if_created(self.bug, ret_mocks=True) 

306 self.assertIsNotNone(m_bug) 

307 mock.assert_called_with({'description': self.description, 'title': self.title}) 

308 add_comm_mock.assert_has_calls([call(m_bug, "hello"), call(m_bug, "world"), call(m_bug, "foo")]) 

309 

310 def test_check_replication_add_comments_no_comment(self): 

311 self.rp.delete() 

312 script = """\ 

313def replication_check(src_bug, dest_bug): 

314 if int(src_bug['bug_id']) % 2 == 0: 

315 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']}} 

316 else: 

317 return {} 

318 """ 

319 ReplicationScript.objects.create(source_tracker=self.db_tracker, 

320 destination_tracker=self.rep_tracker, 

321 script=script, 

322 enabled=True) 

323 

324 m_bug, mock, add_comm_mock = self.get_mirrored_bug_if_created(self.bug, ret_mocks=True) 

325 self.assertIsNotNone(m_bug) 

326 mock.assert_called_with({'description': self.description, 'title': self.title}) 

327 add_comm_mock.assert_not_called() 

328 

329 def test_check_replication_db_fields_update(self): 

330 self.rp.delete() 

331 script = """\ 

332def replication_check(src_bug, dest_bug): 

333 if int(src_bug['bug_id']) % 2 == 0: 

334 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']}, 

335 "add_comments": "Doh", 

336 "db_dest_fields_update": {'severity': 'High'}, 

337 "db_src_fields_update": {'priority': 'Low'}} 

338 else: 

339 return {} 

340 """ 

341 ReplicationScript.objects.create(source_tracker=self.db_tracker, 

342 destination_tracker=self.rep_tracker, 

343 script=script, 

344 enabled=True, 

345 name="BAR") 

346 m_bug = self.get_mirrored_bug_if_created(self.bug) 

347 self.assertEqual(m_bug.severity, 'High') 

348 self.assertEqual(self.bug.priority, 'Low') 

349 m_bug.severity = None 

350 self.bug.priority = None 

351 m_bug.save() 

352 _, _ = self.get_updated_bug(self.bug) 

353 m_bug.refresh_from_db() 

354 self.assertEqual(m_bug.severity, 'High') 

355 self.assertEqual(self.bug.priority, 'Low') 

356 

357 def test_check_replication_db_fields_update_deprecated(self): 

358 self.rp.delete() 

359 script = """\ 

360def replication_check(src_bug, dest_bug): 

361 if int(src_bug['bug_id']) % 2 == 0: 

362 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']}, 

363 "add_comments": "Doh", 

364 "db_fields_update": {'severity': 'High'}} 

365 else: 

366 return {} 

367 """ 

368 ReplicationScript.objects.create(source_tracker=self.db_tracker, 

369 destination_tracker=self.rep_tracker, 

370 script=script, 

371 enabled=True, 

372 name="BAR") 

373 m_bug = self.get_mirrored_bug_if_created(self.bug) 

374 self.assertEqual(m_bug.severity, 'High') 

375 m_bug.severity = None 

376 m_bug.save() 

377 _, _ = self.get_updated_bug(self.bug) 

378 m_bug.refresh_from_db() 

379 self.assertEqual(m_bug.severity, 'High') 

380 

381 def test_check_replication_db_fields_update_empty(self): 

382 self.rp.delete() 

383 script = """\ 

384def replication_check(src_bug, dest_bug): 

385 if int(src_bug['bug_id']) % 2 == 0: 

386 return {"set_fields": {'description': src_bug['description'],'title': src_bug['title']}, 

387 "add_comments": "Doh", 

388 "db_dest_fields_update": {}, 

389 "db_src_fields_update": {}} 

390 else: 

391 return {} 

392 """ 

393 ReplicationScript.objects.create(source_tracker=self.db_tracker, 

394 destination_tracker=self.rep_tracker, 

395 script=script, 

396 enabled=True, 

397 name="BAR") 

398 with patch('CIResults.bugtrackers.Bug.update_from_dict') as upd_mock: 

399 self.get_mirrored_bug_if_created(self.bug) 

400 upd_mock.assert_called_with(None) 

401 with patch('CIResults.bugtrackers.Bug.update_from_dict') as upd_mock: 

402 _, _ = self.get_updated_bug(self.bug) 

403 upd_mock.assert_called_with(None) 

404 

405 def test_check_replication_update(self): 

406 new_bug = self.get_mirrored_bug_if_created(self.bug) 

407 self.assertIsNotNone(new_bug) 

408 upd_mock, _ = self.get_updated_bug(self.bug) 

409 upd_mock.assert_called_with({'description': 'Ni!', 'title': 'We are the knights who say...'}, 

410 new_bug.bug_id) 

411 

412 @patch('CIResults.bugtrackers.BugTrackerCommon._replication_add_comments') 

413 def test_check_replication_update_add_comments(self, add_comm_mock): 

414 new_bug = self.get_mirrored_bug_if_created(self.bug) 

415 self.assertIsNotNone(new_bug) 

416 upd_mock, _ = self.get_updated_bug(self.bug) 

417 upd_mock.assert_called_with({'description': 'Ni!', 'title': 'We are the knights who say...'}, 

418 new_bug.bug_id) 

419 add_comm_mock.assert_called_with(new_bug, ["apple", "pie"]) 

420 

421 def test_check_replication_update_error(self): 

422 new_bug = self.get_mirrored_bug_if_created(self.bug) 

423 self.assertIsNotNone(new_bug) 

424 with patch('CIResults.bugtrackers.Jira.update_bug_from_json', MagicMock()) as upd_mock: 

425 with patch('CIResults.bugtrackers.Jira.poll', autospec=True) as poll_mock: 

426 upd_mock.side_effect = ValueError() 

427 self.tracker.check_replication(self.bug, []) 

428 poll_mock.assert_not_called() 

429 

430 @patch('CIResults.bugtrackers.Jira') 

431 @patch('CIResults.bugtrackers.GitLab') 

432 def test_check_replication_two_scripts(self, gitlab_mock, jira_mock): 

433 dest_tracker = BugTracker.objects.create(name="TrackerFoo", tracker_type="gitlab", url="http://foo", 

434 project="TESTFOO", public=True) 

435 ReplicationScript.objects.create(source_tracker=self.db_tracker, 

436 destination_tracker=dest_tracker, 

437 script=self.script, 

438 enabled=True, 

439 name="FOO") 

440 

441 jira_mock.return_value.create_bug_from_json.return_value = 11 

442 gitlab_mock.return_value.create_bug_from_json.return_value = 12 

443 

444 self.tracker.check_replication(self.bug, []) 

445 bugs = Bug.objects.filter(parent=self.bug) 

446 self.assertEqual(len(bugs), 2) 

447 self.assertIsNotNone(bugs.get(tracker=self.rep_tracker)) 

448 self.assertIsNotNone(bugs.get(tracker=dest_tracker)) 

449 

450 def test_check_invalid_replication(self): 

451 with patch('CIResults.bugtrackers.Jira.create_bug_from_json', MagicMock()) as mock: 

452 mock.side_effect = ValueError 

453 self.tracker.check_replication(self.bug, []) 

454 m_bug = Bug.objects.filter(parent=self.bug).first() 

455 self.assertIsNone(m_bug) 

456 

457 def test_check_replication_already_mirrored(self): 

458 new_bug = self.get_mirrored_bug_if_created(self.bug) 

459 with patch('CIResults.bugtrackers.Jira.update_bug_from_json', MagicMock()) as upd_mock: 

460 with patch('CIResults.bugtrackers.Jira.poll', autospec=True): 

461 with patch('CIResults.bugtrackers.Jira.add_comment', MagicMock()): 

462 self.tracker.check_replication(self.bug, []) 

463 

464 upd_mock.assert_called_with({'description': 'Ni!', 'title': 'We are the knights who say...'}, 

465 new_bug.bug_id) 

466 try: 

467 Bug.objects.get(parent=self.bug) 

468 except MultipleObjectsReturned: # pragma: no cover 

469 self.fail("New Bug shouldn't have been created") # pragma: no cover 

470 

471 def test_check_replication_replicated_bug(self): 

472 m_bug = self.get_mirrored_bug_if_created(self.bug) 

473 self.assertIsNotNone(m_bug) 

474 m_bug2 = self.get_mirrored_bug_if_created(m_bug) 

475 self.assertIsNone(m_bug2) 

476 

477 def test_check_replication_disabled(self): 

478 self.rp.delete() 

479 ReplicationScript.objects.create(source_tracker=self.db_tracker, 

480 destination_tracker=self.rep_tracker, 

481 script=self.script, 

482 enabled=False) 

483 with patch('CIResults.bugtrackers.Client.call_user_function', MagicMock()) as chk_mock: 

484 self.assertIsNone(self.get_mirrored_bug_if_created(self.bug)) 

485 chk_mock.assert_not_called() 

486 

487 def test_check_replication_no_script(self): 

488 self.rp.delete() 

489 with patch('CIResults.bugtrackers.Client.call_user_function', MagicMock()) as chk_mock: 

490 self.assertIsNone(self.get_mirrored_bug_if_created(self.bug)) 

491 chk_mock.assert_not_called() 

492 

493 def test_check_replication_no_match(self): 

494 bug = Bug.objects.create(tracker=self.db_tracker, bug_id=1, 

495 title=self.title, description=self.description) 

496 self.assertIsNone(self.get_mirrored_bug_if_created(bug)) 

497 

498 def test_check_replication_fail_save(self): 

499 with patch('CIResults.models.Bug.save', MagicMock()) as val_mock: 

500 val_mock.side_effect = IntegrityError("Non unique bug") 

501 self.assertIsNone(self.get_mirrored_bug_if_created(self.bug)) 

502 

503 def test_check_replication_no_fields(self): 

504 with patch('CIResults.bugtrackers.Client.call_user_function', MagicMock()) as chk_mock: 

505 chk_mock.return_value = None 

506 self.assertIsNone(self.get_mirrored_bug_if_created(self.bug)) 

507 

508 

509class RequestsGetMock(): 

510 PRIVATE_TOKEN = "qwerttyzxcfdsapjdpfa" 

511 BUG_ID = 2 

512 BUG_CREATED_AT = '2018-10-04T11:20:48.531Z' 

513 BUG_UPDATED_AT = '2018-11-28T13:24:13.325Z' 

514 CREATOR_NAME = 'Creator Name' 

515 ASSIGNEE_NAME = 'Assignee Name' 

516 NOTE_ONE_ID = 83161 

517 NOTE_ONE_BODY = "Still Alive" 

518 NOTE_ONE_CREATOR_NAME = 'Note Creator' 

519 NOTE_TWO_CREATED_AT = '2018-11-28T13:24:13.290Z' 

520 NOTE_ONE_CREATED_AT = '2018-10-04T12:35:03.299Z' 

521 NOTE_TWO_BODY = "Oh. Hi. So. How are you holding up? BECAUSE I'M A POTATO!" 

522 BUG_TITLE = 'super bug title' 

523 BUG_STATUS = 'opened' 

524 BUG_DESCRIPTION = 'the cake is a lie' 

525 

526 BUG_PRODUCT = 'PrOdUcT' 

527 BUG_COMPONENT = 'cOmPoNeNt' 

528 BUG_PRIORITY = 'pRiOrItY' 

529 BUG_SEVERITY = 'sEvErItY' 

530 BUG_PLATFORM1 = 'pLaTfOrM 1' 

531 BUG_PLATFORM2 = 'pLaTfOrM 2' 

532 BUG_FEATURE1 = 'fEaTuRe 1' 

533 BUG_FEATURE2 = 'fEaTuRe 2' 

534 BUG_TARGET = 'bug_target' 

535 BUG_MAP_SEVERITY = 'High' 

536 

537 BUG_LABELS = ['FOO', 'BAR', 'pRoDuCt::'+BUG_PRODUCT, 'CoMpOnEnT::'+BUG_COMPONENT, 'PrIoRiTy::'+BUG_PRIORITY, 

538 'SeVeRiTy::'+BUG_SEVERITY, 'PlAtFoRm: '+BUG_PLATFORM1, 'PlAtFoRm: '+BUG_PLATFORM2, 

539 'FeAtUrE: '+BUG_FEATURE1, 'FeAtUrE: '+BUG_FEATURE2] 

540 RESPONSES = { 

541 'https://gitlab.freedesktop.org/api/v4/projects/230/issues/2': 

542 { 

543 'id': 4674, 

544 'iid': BUG_ID, 

545 'project_id': 230, 

546 'title': BUG_TITLE, 

547 'description': BUG_DESCRIPTION, 

548 'state': BUG_STATUS, 

549 'created_at': BUG_CREATED_AT, 

550 'updated_at': BUG_UPDATED_AT, 

551 'closed_at': None, 

552 'closed_by': None, 

553 'labels': BUG_LABELS, 

554 'author': {'id': 1127, 'name': CREATOR_NAME}, 

555 'assignee': {'id': 1128, 'name': ASSIGNEE_NAME}, 

556 'web_url': 'https://gitlab.freedesktop.org/patchwork-fdo/patchwork-fdo/issues/2' 

557 }, 

558 'https://gitlab.freedesktop.org/api/v4/projects/230/issues/2/notes': 

559 [ 

560 { 

561 'id': NOTE_ONE_ID, 

562 'body': NOTE_ONE_BODY, 

563 'author': {'id': 1129, 'name': NOTE_ONE_CREATOR_NAME}, 

564 'created_at': NOTE_ONE_CREATED_AT 

565 }, 

566 { 

567 'id': 41381, 

568 'body': NOTE_TWO_BODY, 

569 'author': {'id': 1127, 'name': CREATOR_NAME}, 

570 'created_at': NOTE_TWO_CREATED_AT 

571 } 

572 ], 

573 'https://gitlab.freedesktop.org/api/v4/projects/230/issues/': 

574 [ 

575 {'id': 4675, 'iid': 3, 'project_id': 230}, 

576 {'id': 4674, 'iid': 2, 'project_id': 230}, 

577 {'id': 4673, 'iid': 1, 'project_id': 230} 

578 ] 

579 } 

580 

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

582 self.url = url 

583 self.headers = {} 

584 

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

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

587 

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

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

590 

591 RequestsGetMock.last_URL = url 

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

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

594 

595 def raise_for_status(self): 

596 pass 

597 

598 def json(self): 

599 return self.RESPONSES[self.url] 

600 

601 

602class BugTrackerGitLabTests(TransactionTestCase): 

603 def setUp(self): 

604 url = "https://gitlab.freedesktop.org" 

605 bug_base_url = "https://gitlab.freedesktop.org/patchwork-fdo/patchwork-fdo/issues/" 

606 

607 self.db_tracker = BugTracker.objects.create(tracker_type="gitlab", public=True, 

608 project="230", 

609 password=RequestsGetMock.PRIVATE_TOKEN, 

610 url=url, bug_base_url=bug_base_url) 

611 

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

613 self.gitlab = GitLab(self.db_tracker) 

614 

615 @patch('requests.get') 

616 def test_GetTrackerTime(self, req_mock): 

617 resp_mock = MagicMock() 

618 resp_mock.headers = {'Date': 'Thu, 07 May 2020 20:23:57 GMT'} 

619 req_mock.return_value = resp_mock 

620 dt = self.gitlab._get_tracker_time() 

621 self.assertEqual(dt, datetime.datetime(2020, 5, 7, 20, 23, 57, tzinfo=pytz.utc)) 

622 

623 def test_ToTrackerTz(self): 

624 dt = timezone.now() 

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

626 

627 @patch('requests.get', RequestsGetMock) 

628 def testPolledBugShouldSaveJustFine(self): 

629 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now()) 

630 self.gitlab.poll(self.bug) 

631 self.bug.save() 

632 

633 @patch('requests.get', RequestsGetMock) 

634 def testPollingBugShouldPopulateFields(self): 

635 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now()) 

636 self.gitlab.poll(self.bug) 

637 self.assertEqual(self.bug.title, RequestsGetMock.BUG_TITLE) 

638 self.assertEqual(self.bug.status, RequestsGetMock.BUG_STATUS) 

639 self.assertEqual(self.bug.assignee.person.full_name, RequestsGetMock.ASSIGNEE_NAME) 

640 self.assertEqual(self.bug.creator.person.full_name, RequestsGetMock.CREATOR_NAME) 

641 self.assertEqual(self.bug.created, dateparser.parse(RequestsGetMock.BUG_CREATED_AT)) 

642 self.assertEqual(self.bug.updated, dateparser.parse(RequestsGetMock.BUG_UPDATED_AT)) 

643 self.assertEqual(self.bug.description, RequestsGetMock.BUG_DESCRIPTION) 

644 self.assertEqual(self.bug.product, RequestsGetMock.BUG_PRODUCT) 

645 self.assertEqual(self.bug.component, RequestsGetMock.BUG_COMPONENT) 

646 self.assertEqual(self.bug.priority, RequestsGetMock.BUG_PRIORITY) 

647 self.assertEqual(self.bug.severity, RequestsGetMock.BUG_SEVERITY) 

648 self.assertEqual(self.bug.platforms, 

649 "{},{}".format(RequestsGetMock.BUG_PLATFORM1, RequestsGetMock.BUG_PLATFORM2)) 

650 self.assertEqual(self.bug.features, 

651 "{},{}".format(RequestsGetMock.BUG_FEATURE1, RequestsGetMock.BUG_FEATURE2)) 

652 self.assertEqual(self.bug.tags, "FOO,BAR") 

653 

654 @patch('requests.get', RequestsGetMock) 

655 def testPollingCustomFieldMap(self): 

656 url = "https://gitlab.freedesktop.org" 

657 bug_base_url = "https://gitlab.freedesktop.org/patchwork-fdo/patchwork-fdo/issues/" 

658 custom_labels = ['target::'+RequestsGetMock.BUG_TARGET, 

659 'mapped_severity::'+RequestsGetMock.BUG_MAP_SEVERITY] 

660 

661 db_tracker = BugTracker.objects.create(name='blah', tracker_type="gitlab", public=True, 

662 project="230", 

663 password=RequestsGetMock.PRIVATE_TOKEN, 

664 url=url, bug_base_url=bug_base_url, 

665 custom_fields_map={'target::': 'target', 

666 'doesnt_exist': 'foo', 

667 'mapped_severity::': 'severity'}) 

668 

669 orig_labels = RequestsGetMock.BUG_LABELS 

670 RequestsGetMock.BUG_LABELS.extend(custom_labels) 

671 self.bug.tracker = db_tracker 

672 self.bug.save() 

673 gitlab = GitLab(db_tracker) 

674 gitlab._get_tracker_time = MagicMock(return_value=timezone.now()) 

675 

676 gitlab.poll(self.bug) 

677 try: 

678 self.assertEqual(self.bug.tags, "FOO,BAR") # make sure labels are still populated 

679 self.assertEqual(self.bug.severity, RequestsGetMock.BUG_MAP_SEVERITY) 

680 self.assertEqual(self.bug.custom_fields['target'], RequestsGetMock.BUG_TARGET) 

681 self.assertIsNone(self.bug.custom_fields.get('foo')) 

682 except: # pragma: no cover # noqa 

683 raise 

684 finally: 

685 self.bug.tracker = self.db_tracker 

686 self.bug.save() 

687 RequestsGetMock.BUG_LABELS = orig_labels 

688 

689 @patch('requests.get', RequestsGetMock) 

690 def testPollingBugShouldFetchComments(self): 

691 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now()) 

692 self.gitlab.poll(self.bug) 

693 

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

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

696 

697 @patch('requests.get', RequestsGetMock) 

698 def testNoteShouldBePopulatedCorrectly(self): 

699 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now()) 

700 self.gitlab.poll(self.bug) 

701 

702 comment = BugComment.objects.get(bug=self.bug, comment_id=RequestsGetMock.NOTE_ONE_ID) 

703 self.assertEqual(comment.account.person.full_name, RequestsGetMock.NOTE_ONE_CREATOR_NAME) 

704 self.assertEqual(comment.created_on, dateparser.parse(RequestsGetMock.NOTE_ONE_CREATED_AT)) 

705 self.assertTrue("#note_{}".format(comment.comment_id) in comment.url) 

706 self.assertTrue(self.db_tracker.bug_base_url in comment.url) 

707 

708 @patch('requests.get', RequestsGetMock) 

709 def testPollingCreatesCommentList(self): 

710 expected = [(RequestsGetMock.NOTE_ONE_CREATOR_NAME, 

711 RequestsGetMock.NOTE_ONE_BODY, 

712 dateparser.parse(RequestsGetMock.NOTE_ONE_CREATED_AT)), 

713 (RequestsGetMock.CREATOR_NAME, 

714 RequestsGetMock.NOTE_TWO_BODY, 

715 dateparser.parse(RequestsGetMock.NOTE_TWO_CREATED_AT))] 

716 

717 comm_list = self.gitlab._GitLab__poll_comments(self.bug, "http://foo.com") 

718 result = [] 

719 for c in comm_list: 

720 result.append((c.db_object.account.person.full_name, 

721 c.body, 

722 c.db_object.created_on)) 

723 self.assertEqual(result, expected) 

724 

725 @patch('requests.get', RequestsGetMock) 

726 def testPollingBugTwiceShouldNotDuplicateComments(self): 

727 self.gitlab._get_tracker_time = MagicMock(return_value=timezone.now()) 

728 self.bug.comments_polled = dateparser.parse(RequestsGetMock.NOTE_ONE_CREATED_AT) 

729 url = "{}#note_{}".format('https://gitlab.freedesktop.org/api/v4/projects/230/issues/2/notes', 

730 RequestsGetMock.NOTE_ONE_ID) 

731 

732 person = Person.objects.create(full_name=RequestsGetMock.CREATOR_NAME) 

733 account = BugTrackerAccount.objects.create(user_id="1", person=person, 

734 tracker=self.db_tracker, is_developer=True) 

735 BugComment.objects.create(bug=self.bug, account=account, comment_id=RequestsGetMock.NOTE_ONE_ID, 

736 url=url, 

737 created_on=dateparser.parse(RequestsGetMock.NOTE_ONE_CREATED_AT)) 

738 

739 expected = [(RequestsGetMock.CREATOR_NAME, 

740 RequestsGetMock.NOTE_TWO_BODY, 

741 dateparser.parse(RequestsGetMock.NOTE_TWO_CREATED_AT))] 

742 

743 with patch('CIResults.bugtrackers.GitLab.check_replication') as cr_mock: 

744 self.gitlab.poll(self.bug) 

745 comm_list = cr_mock.call_args[0][1] 

746 result = [] 

747 for c in comm_list: 

748 result.append((c.db_object.account.person.full_name, 

749 c.body, 

750 c.db_object.created_on)) 

751 self.assertEqual(result, expected) 

752 

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

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

755 

756 self.gitlab.poll(self.bug) 

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

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

759 

760 @patch('requests.get', RequestsGetMock) 

761 def testSearchNoParams(self): 

762 all_bugs = self.gitlab.search_bugs_ids() 

763 self.assertEqual(all_bugs, set(['1', '2', '3'])) 

764 self.assertEqual(RequestsGetMock.last_params, {'page': 1, 'per_page': 100}) 

765 

766 @patch('requests.get', RequestsGetMock) 

767 def testSearchAllBugIds(self): 

768 created_since = datetime.datetime.fromtimestamp(1000) 

769 updated_since = datetime.datetime.fromtimestamp(1000) 

770 all_bugs = self.gitlab.search_bugs_ids(created_since=created_since, 

771 updated_since=updated_since, 

772 components=['tag1', 'tag2'], 

773 status='status1') 

774 self.assertEqual(all_bugs, set(['1', '2', '3'])) 

775 self.assertEqual(RequestsGetMock.last_params, { 

776 'page': 1, 'per_page': 100, 

777 'created_after': created_since, 

778 'updated_after': updated_since, 

779 "labels": "tag1,tag2", 

780 "state": 'status1' 

781 }) 

782 

783 @patch('requests.get', RequestsGetMock) 

784 def testSearchWithOneStatusInList(self): 

785 all_bugs = self.gitlab.search_bugs_ids(status=['status1']) 

786 self.assertEqual(all_bugs, set(['1', '2', '3'])) 

787 self.assertEqual(RequestsGetMock.last_params, { 

788 'page': 1, 'per_page': 100, 

789 "state": 'status1' 

790 }) 

791 

792 def testSearchWithMoreThanOneStatus(self): 

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

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

795 

796 def test_open_statuses(self): 

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

798 

799 @patch('requests.post') 

800 def testAddComment(self, post_mock): 

801 comment = "Hello world!" 

802 self.gitlab.add_comment(Bug(tracker=self.db_tracker, bug_id=RequestsGetMock.BUG_ID), comment) 

803 

804 # Check that the call was what was expected 

805 args, kwargs = post_mock.call_args_list[0] 

806 self.assertEqual(args[0], 'https://gitlab.freedesktop.org/api/v4/projects/230/issues/2/notes') 

807 self.assertEqual(kwargs['headers'], {'PRIVATE-TOKEN': RequestsGetMock.PRIVATE_TOKEN}) 

808 self.assertEqual(kwargs['params'], {'body': comment}) 

809 

810 @patch('requests.post') 

811 def test_create_bug_from_json(self, post_mock): 

812 summary = "summary" 

813 description = "description" 

814 test_id = 5678 

815 json_bug = {'title': summary, 

816 'description': description, 

817 'labels': "Bug", 

818 'state': "opened"} 

819 

820 post_mock.return_value.raise_for_status.return_value = None 

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

822 

823 id = self.gitlab.create_bug_from_json(json_bug) 

824 self.assertEqual(id, test_id) 

825 

826 args, kwargs = post_mock.call_args_list[0] 

827 request = kwargs['params'] 

828 for field in json_bug: 

829 self.assertEqual(request[field], json_bug[field]) 

830 

831 @patch('requests.post') 

832 def test_create_bug_from_json_no_labels(self, post_mock): 

833 summary = "summary" 

834 description = "description" 

835 test_id = 5678 

836 json_bug = {'title': summary, 

837 'description': description} 

838 

839 post_mock.return_value.raise_for_status.return_value = None 

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

841 

842 id = self.gitlab.create_bug_from_json(json_bug) 

843 self.assertEqual(id, test_id) 

844 

845 args, kwargs = post_mock.call_args_list[0] 

846 expected_request = {'title': summary, 

847 'description': description} 

848 request = kwargs['params'] 

849 for field in expected_request: 

850 self.assertEqual(request[field], expected_request[field], field) 

851 

852 @patch('requests.post') 

853 def test_create_bug_from_json_with_status(self, post_mock): 

854 summary = "summary" 

855 description = "description" 

856 test_id = 5678 

857 json_bug = {'title': summary, 

858 'description': description, 

859 'status': "opened"} 

860 

861 post_mock.return_value.raise_for_status.return_value = None 

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

863 

864 id = self.gitlab.create_bug_from_json(json_bug) 

865 self.assertEqual(id, test_id) 

866 

867 args, kwargs = post_mock.call_args_list[0] 

868 expected_request = {'title': summary, 

869 'description': description} 

870 request = kwargs['params'] 

871 for field in expected_request: 

872 self.assertEqual(request[field], expected_request[field], field) 

873 

874 @patch('requests.post') 

875 def test_create_malformed_bug(self, post_mock): 

876 summary = "summary" 

877 description = "description" 

878 json_bug = {'title': summary, 

879 'description': description, 

880 'labels': "Bug"} 

881 

882 post_mock.side_effect = requests.HTTPError 

883 

884 with self.assertRaises(ValueError): 

885 self.gitlab.create_bug_from_json(json_bug) 

886 

887 @patch('requests.put') 

888 def test_update_bug_from_json(self, put_mock): 

889 json_bug = {'title': "summary", 

890 'description': "description", 

891 'labels': "Bug", 

892 'state': "opened"} 

893 

894 self.gitlab.update_bug_from_json(json_bug, 5678) 

895 

896 args, kwargs = put_mock.call_args_list[0] 

897 self.assertEqual(args[0], "https://gitlab.freedesktop.org/api/v4/projects/230/issues/5678") 

898 for field in json_bug: 

899 self.assertEqual(kwargs['params'][field], json_bug[field]) 

900 

901 @patch('requests.put') 

902 def test_update_bug_from_json_error(self, put_mock): 

903 put_mock.side_effect = requests.HTTPError 

904 with self.assertRaises(ValueError): 

905 self.gitlab.update_bug_from_json({}, 5678) 

906 

907 def test_transition(self): 

908 self.gitlab.update_bug_from_json = MagicMock() 

909 self.gitlab.transition(1, "Open") 

910 self.gitlab.update_bug_from_json.assert_called_with({'state_event': "Open"}, 1) 

911 

912 

913class BugzillaProxyMock: 

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

915 

916 # User.login 

917 LOGIN = "userlogin" 

918 PASSWORD = "password" 

919 TOKEN_ID = '12345' 

920 TOKEN = '12345-kZ5CYMeQGH' 

921 

922 # Bug.add_comment 

923 BUG_ID = 1234 

924 BUG_ID_NO_EMAIL = 1235 

925 BUG_ID_NON_EXISTING = 1236 

926 BUG_ID_WRONG_COMMENT_COUNT = 1237 

927 COMMENT = 'my comment' 

928 

929 # Bugzilla.create_bug 

930 NEW_BUG_ID = 5678 

931 PRODUCT = "TEST_PRODUCT" 

932 COMPONENT = "TEST/COMPONENT/WITH/SLASHES" 

933 SUMMARY = "TEST_SUMMARY" 

934 DESCRIPTION = "TEST_DESCRIPTION" 

935 DESCRIPTION_2 = "Some chump has run the data lines right through the power supply!" 

936 

937 # Update 

938 UPDATE_IDS = 1 

939 

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

941 

942 CREATE_REQUEST = { 

943 'token': TOKEN, 

944 'product': PRODUCT, 

945 'component': COMPONENT, 

946 'summary': SUMMARY, 

947 'description': DESCRIPTION 

948 } 

949 # get_comments 

950 COMMENT_CREATOR = "Roy Trenneman" 

951 COMMENT_CREATOR_2 = "Maurice Moss" 

952 COMMENT_CREATION_TIME = datetime.datetime.fromtimestamp(0) 

953 COMMENT_2_CREATION_TIME = COMMENT_CREATION_TIME + timedelta(days=1) 

954 

955 class _Bugzilla: 

956 def time(self): 

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

958 

959 class _User: 

960 def login(self, params): 

961 if params.get('login') != BugzillaProxyMock.LOGIN: 

962 raise ValueError('Incorrect or missing login') # pragma: no cover 

963 if params.get('password') != BugzillaProxyMock.PASSWORD: 

964 raise ValueError('Incorrect or missing password') # pragma: no cover 

965 return {'id': BugzillaProxyMock.TOKEN_ID, 'token': BugzillaProxyMock.TOKEN} 

966 

967 class _Bug: 

968 def get(self, params): 

969 ids = params.get('ids') 

970 if ids == BugzillaProxyMock.BUG_ID or ids == BugzillaProxyMock.BUG_ID_WRONG_COMMENT_COUNT: 

971 creator_detail = {"real_name": "creator", "email": "creator@me.de"} 

972 assigned_to_detail = {"real_name": "assignee", "email": "assignee@me.de"} 

973 is_open = False 

974 elif ids == BugzillaProxyMock.BUG_ID_NO_EMAIL: 

975 creator_detail = {"real_name": "creator", "name": "creator"} 

976 assigned_to_detail = {"real_name": "assignee", "name": "assignee"} 

977 is_open = True 

978 elif ids == BugzillaProxyMock.BUG_ID_NON_EXISTING: 

979 return {'bugs': []} 

980 else: 

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

982 

983 return { 

984 "bugs": [{ 

985 "summary": "summary", 

986 "status": "status", 

987 "severity": "severity", 

988 "is_open": is_open, 

989 "resolution": "resolution", 

990 "creation_time": datetime.datetime.fromtimestamp(0), 

991 "last_change_time": datetime.datetime.fromtimestamp(5), 

992 "creator_detail": creator_detail, 

993 "assigned_to_detail": assigned_to_detail, 

994 "product": "product", 

995 "component": "component", 

996 "custom_features": ["feature1", "feature2"], 

997 "custom_platforms": ["platform1", "platform2"], 

998 "priority": "high", 

999 "a_custom_field": "I'm custom", 

1000 "id": "invalid field", 

1001 "bug_id": "invalid field", 

1002 "tracker_id": "invalid field", 

1003 "tracker": "invalid field", 

1004 "parent_id": "invalid field", 

1005 "parent": "invalid field" 

1006 }] 

1007 } 

1008 

1009 def comments(self, params): 

1010 if params.get('ids') == BugzillaProxyMock.BUG_ID or params.get('ids') == BugzillaProxyMock.BUG_ID_NO_EMAIL: 

1011 count = 0 

1012 elif params.get('ids') == BugzillaProxyMock.BUG_ID_WRONG_COMMENT_COUNT: 

1013 count = 1 

1014 else: 

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

1016 

1017 comments = { 

1018 "comments": [ 

1019 { 

1020 "text": BugzillaProxyMock.DESCRIPTION, 

1021 "creator": BugzillaProxyMock.COMMENT_CREATOR, 

1022 "id": 100, 

1023 "count": count, 

1024 "time": BugzillaProxyMock.COMMENT_CREATION_TIME, 

1025 "creation_time": BugzillaProxyMock.COMMENT_CREATION_TIME 

1026 }, 

1027 { 

1028 "text": BugzillaProxyMock.DESCRIPTION_2, 

1029 "creator": BugzillaProxyMock.COMMENT_CREATOR_2, 

1030 "id": 101, 

1031 "count": count+1, 

1032 "time": BugzillaProxyMock.COMMENT_2_CREATION_TIME, 

1033 "creation_time": BugzillaProxyMock.COMMENT_2_CREATION_TIME 

1034 } 

1035 ] 

1036 } 

1037 

1038 # prune the comments that came after the new_since parameter 

1039 if params.get('new_since') is not None: 

1040 new_since = params['new_since'].replace(tzinfo=None) 

1041 comments['comments'] = [c for c in comments['comments'] if c['time'] > new_since] 

1042 

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

1044 

1045 def history(self, params): 

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

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

1048 

1049 return {"bugs": [ 

1050 {'history': [ 

1051 {'when': datetime.datetime.fromtimestamp(1), 'who': 'someone@toto.de', 

1052 'changes': [{'field_name': 'status', 'removed': 'NEW', 'added': 'RESOLVED'}, 

1053 {'field_name': 'resolution', 'removed': '', 'added': 'FIXED'}] 

1054 }, 

1055 {'who': 'someone@toto.de', 'when': datetime.datetime.fromtimestamp(2), 

1056 'changes': [ 

1057 {'field_name': 'status', 'added': 'NEW', 'removed': 'RESOLVED'}, 

1058 {'field_name': 'resolution', 'added': '', 'removed': 'FIXED'}], 

1059 }, 

1060 {'when': datetime.datetime.fromtimestamp(3), 'who': 'someone@toto.de', 

1061 'changes': [ 

1062 {'field_name': 'status', 'added': 'RESOLVED', 'removed': 'NEW'}, 

1063 {'field_name': 'resolution', 'removed': '', 'added': 'FIXED'}], 

1064 }, 

1065 {'when': datetime.datetime.fromtimestamp(4), 'who': 'someone@toto.de', 

1066 'changes': [{'field_name': 'status', 'added': 'CLOSED', 'removed': 'RESOLVED'}], 

1067 }] 

1068 }] # noqa 

1069 } 

1070 

1071 def add_comment(self, params): 

1072 if params.get('id') != BugzillaProxyMock.BUG_ID: 

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

1074 if params.get('token') != BugzillaProxyMock.TOKEN: 

1075 raise ValueError('Incorrect or missing token') # pragma: no cover 

1076 if params.get('comment') != BugzillaProxyMock.COMMENT: 

1077 raise ValueError('Incorrect or missing comment') # pragma: no cover 

1078 return {'id': 766846} 

1079 

1080 def create(self, params): 

1081 if params.get('token') != BugzillaProxyMock.TOKEN: 

1082 raise xmlrpc.client.Error('Incorrect or missing token') # pragma: no cover 

1083 if params.get('summary') != BugzillaProxyMock.SUMMARY: 

1084 raise xmlrpc.client.Error('Incorrect or missing summary') # pragma: no cover 

1085 if params.get('description') != BugzillaProxyMock.DESCRIPTION: 

1086 raise xmlrpc.client.Error('Incorrect or missing description') # pragma: no cover 

1087 

1088 return {'id': '1'} 

1089 

1090 def search(self, params): 

1091 self.last_search_request = params 

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

1093 

1094 def update(self, params): 

1095 if params.get('ids') != BugzillaProxyMock.UPDATE_IDS: 

1096 raise xmlrpc.client.Error('Incorrect or missing ids') # pragma: no cover 

1097 

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

1099 self.create(params) 

1100 

1101 def __init__(self, url, use_builtin_types=False): 

1102 if url != self.URL + "/xmlrpc.cgi": 

1103 raise ValueError('invalid xmlrpc url') # pragma: no cover 

1104 

1105 if not use_builtin_types: 

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

1107 

1108 User = _User() 

1109 Bug = _Bug() 

1110 Bugzilla = _Bugzilla() 

1111 

1112 

1113class BugTrackerBugzillaTests(TestCase): 

1114 @patch('xmlrpc.client.ServerProxy', BugzillaProxyMock) 

1115 def setUp(self): 

1116 self.tracker = BugTracker.objects.create(tracker_type="bugzilla", public=True, 

1117 url=BugzillaProxyMock.URL, 

1118 username=BugzillaProxyMock.LOGIN, 

1119 password=BugzillaProxyMock.PASSWORD, 

1120 custom_fields_map={'custom_features': 'features', 

1121 'custom_platforms': 'platforms', 

1122 'a_custom_field': 'my_custom_field'}, 

1123 name='my tracker') 

1124 self.bugzilla = Bugzilla(self.tracker) 

1125 

1126 def test__get_tracker_time(self): 

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

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

1129 

1130 def test__to_tracker_tz(self): 

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

1132 dt_pdt = dt.astimezone(pytz.timezone('America/Vancouver')) 

1133 self.assertEqual(dt, self.bugzilla._to_tracker_tz(dt_pdt)) 

1134 

1135 def test__get_user_id(self): 

1136 self.assertEqual(Bugzilla._get_user_id({'creator_detail': {'email': 'me@email.com', 

1137 'name': 'John Doe'}}, 

1138 'creator'), 'me@email.com') 

1139 

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

1141 'creator'), 'John Doe') 

1142 

1143 self.assertRaisesMessage(ValueError, 

1144 'Cannot find a good identifier for the user of the bug 1234', 

1145 Bugzilla._get_user_id, {'id': '1234'}, 'creator') 

1146 

1147 def test_list_to_str(self): 

1148 self.assertEqual(Bugzilla._list_to_str(['one', 'two', 'three']), 'one,two,three') 

1149 self.assertEqual(Bugzilla._list_to_str('one'), 'one') 

1150 

1151 def test_bug_id_parser(self): 

1152 self.assertEqual(Bugzilla._bug_id_parser(MagicMock(spec=Bug, bug_id='1234')), 1234) 

1153 self.assertRaisesMessage(ValueError, "Bugzilla's IDs should be integers (fdo#1234)", 

1154 Bugzilla._bug_id_parser, MagicMock(spec=Bug, bug_id='fdo#1234')) 

1155 

1156 def test__parse_custom_field(self): 

1157 value1 = ['apple', 'cherry', 'orange'] 

1158 resp1 = self.bugzilla._parse_custom_field(value1, to_str=False) 

1159 resp1_str = self.bugzilla._parse_custom_field(value1, to_str=True) 

1160 self.assertEqual(resp1, value1) 

1161 self.assertEqual(resp1_str, 'apple,cherry,orange') 

1162 

1163 value2 = 'something clever' 

1164 resp2 = self.bugzilla._parse_custom_field(value2, to_str=False) 

1165 resp2_str = self.bugzilla._parse_custom_field(value2, to_str=True) 

1166 self.assertEqual(resp2, value2) 

1167 self.assertEqual(resp2_str, value2) 

1168 

1169 def test_poll__with_emails(self): 

1170 bug = Bug.objects.create(tracker=self.tracker, bug_id=BugzillaProxyMock.BUG_ID, closed=None, description=None) 

1171 bug.save = MagicMock() 

1172 

1173 with patch('CIResults.bugtrackers.Bugzilla.check_replication') as cr_mock: 

1174 self.bugzilla.poll(bug) 

1175 comm_list = cr_mock.call_args[0][1] 

1176 

1177 result = [] 

1178 for c in comm_list: 

1179 result.append((c.db_object.account.person.email, 

1180 c.body, 

1181 c.db_object.created_on)) 

1182 

1183 self.assertEqual(bug.title, "summary") 

1184 self.assertEqual(bug.created, datetime.datetime.fromtimestamp(0, tz=pytz.utc)) 

1185 self.assertEqual(bug.updated, datetime.datetime.fromtimestamp(5, tz=pytz.utc)) 

1186 self.assertEqual(bug.closed, datetime.datetime.fromtimestamp(3, tz=pytz.utc)) 

1187 self.assertEqual(bug.creator.person.full_name, "creator") 

1188 self.assertEqual(bug.creator.person.email, "creator@me.de") 

1189 self.assertEqual(bug.assignee.person.full_name, "assignee") 

1190 self.assertEqual(bug.assignee.person.email, "assignee@me.de") 

1191 self.assertEqual(bug.product, "product") 

1192 self.assertEqual(bug.component, "component") 

1193 self.assertEqual(bug.features, "feature1,feature2") 

1194 self.assertEqual(bug.platforms, "platform1,platform2") 

1195 self.assertEqual(bug.status, "status/resolution") 

1196 self.assertEqual(bug.priority, "high") 

1197 self.assertEqual(bug.severity, "severity") 

1198 self.assertEqual(bug.description, BugzillaProxyMock.DESCRIPTION) 

1199 self.assertEqual(bug.custom_fields['my_custom_field'], "I'm custom") 

1200 bug.save.assert_not_called() 

1201 

1202 expected = [(BugzillaProxyMock.COMMENT_CREATOR, 

1203 BugzillaProxyMock.DESCRIPTION, 

1204 timezone.make_aware(BugzillaProxyMock.COMMENT_CREATION_TIME, pytz.utc)), 

1205 (BugzillaProxyMock.COMMENT_CREATOR_2, 

1206 BugzillaProxyMock.DESCRIPTION_2, 

1207 timezone.make_aware(BugzillaProxyMock.COMMENT_2_CREATION_TIME, pytz.utc))] 

1208 # Check that we polled all the new comments 

1209 self.assertEqual(result, expected) 

1210 

1211 @patch('xmlrpc.client.ServerProxy', BugzillaProxyMock) 

1212 def test_poll_invalid_custom_fields(self): 

1213 tracker = BugTracker.objects.create(tracker_type="bugzilla", public=True, 

1214 url=BugzillaProxyMock.URL, 

1215 username=BugzillaProxyMock.LOGIN, 

1216 password=BugzillaProxyMock.PASSWORD, 

1217 custom_fields_map={'id': 'id', 

1218 'bug_id': 'bug_id', 

1219 'tracker_id': 'tracker_id', 

1220 'tracker': 'tracker', 

1221 'parent_id': 'parent_id', 

1222 'parent': 'parent'}, 

1223 name='super tracker') 

1224 bugzilla = Bugzilla(tracker) 

1225 bug = Bug.objects.create(tracker=tracker, bug_id=BugzillaProxyMock.BUG_ID, closed=None, description=None) 

1226 bug.save = MagicMock() 

1227 parse_mock = MagicMock() 

1228 bugzilla._parse_custom_field = parse_mock 

1229 

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

1231 bugzilla.poll(bug) 

1232 

1233 parse_mock.assert_not_called() 

1234 

1235 def test_poll__new_comments_arrived(self): 

1236 bug = Bug.objects.create(tracker=self.tracker, bug_id=BugzillaProxyMock.BUG_ID, closed=None, description=None, 

1237 comments_polled=timezone.make_aware(BugzillaProxyMock.COMMENT_CREATION_TIME, pytz.utc)) 

1238 

1239 comm_list = self.bugzilla._Bugzilla__poll_comments(bug) 

1240 result = [] 

1241 for c in comm_list: 

1242 result.append((c.db_object.account.person.email, 

1243 c.body, 

1244 c.db_object.created_on)) 

1245 

1246 expected = [(BugzillaProxyMock.COMMENT_CREATOR_2, 

1247 BugzillaProxyMock.DESCRIPTION_2, 

1248 timezone.make_aware(BugzillaProxyMock.COMMENT_2_CREATION_TIME, pytz.utc))] 

1249 # Check that we polled only the new comment 

1250 self.assertEqual(result, expected) 

1251 

1252 def test_poll__no_emails(self): 

1253 bug = MagicMock(spec=Bug, bug_id=BugzillaProxyMock.BUG_ID_NO_EMAIL, comments_polled=None) 

1254 with patch.object(BugComment.objects, "create"): 

1255 self.bugzilla.poll(bug) 

1256 

1257 self.assertEqual(bug.closed, None) 

1258 self.assertEqual(bug.creator.person.full_name, "creator") 

1259 self.assertEqual(bug.creator.person.email, None) 

1260 self.assertEqual(bug.assignee.person.full_name, "assignee") 

1261 self.assertEqual(bug.assignee.person.email, None) 

1262 bug.save.assert_not_called() 

1263 

1264 def test_poll_invalid_bug(self): 

1265 bug = MagicMock(spec=Bug, bug_id=BugzillaProxyMock.BUG_ID_NON_EXISTING, tracker=self.tracker) 

1266 self.assertRaisesMessage(ValueError, "Could not find the bug ID 1236 on my tracker", 

1267 self.bugzilla.poll, bug) 

1268 

1269 bug.save.assert_not_called() 

1270 

1271 def test_poll_wrong_comment_count(self): 

1272 bug = MagicMock(spec=Bug, bug_id=BugzillaProxyMock.BUG_ID_WRONG_COMMENT_COUNT, description=None) 

1273 with patch.object(BugComment.objects, "create"): 

1274 with self.assertRaises(ValueError): 

1275 self.bugzilla.poll(bug) 

1276 bug.save.assert_not_called() 

1277 

1278 def test_search_bugs_ids__full(self): 

1279 # Get the list of open bugs 

1280 updated_since = datetime.datetime.fromtimestamp(1000) 

1281 open_bugs = self.bugzilla.search_bugs_ids(components=["COMPONENT1", "COMPONENT2"], 

1282 created_since=datetime.datetime.fromtimestamp(1000), 

1283 updated_since=updated_since, 

1284 status=['status1', 'status2']) 

1285 self.assertEqual(open_bugs, set(['10', '11', '13'])) 

1286 

1287 # Verify that the request was valid 

1288 expected_request = { 

1289 "component": ["COMPONENT1", "COMPONENT2"], 

1290 "status": ['status1', 'status2'], 

1291 "creation_time": datetime.datetime.fromtimestamp(1000), 

1292 "last_change_time": updated_since, 

1293 "include_fields": ['id'] 

1294 } 

1295 self.assertEqual(BugzillaProxyMock.Bug.last_search_request, expected_request) 

1296 

1297 def test_search_bugs_ids__empty(self): 

1298 self.bugzilla.search_bugs_ids() 

1299 expected_request = { 

1300 "include_fields": ['id'] 

1301 } 

1302 self.assertEqual(BugzillaProxyMock.Bug.last_search_request, expected_request) 

1303 

1304 def test_open_statuses(self): 

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

1306 

1307 def test_auth_login(self): 

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

1309 

1310 def test_auth_login__invalid_username(self): 

1311 for username in [None, ""]: 

1312 self.tracker.username = username 

1313 self.assertRaisesMessage(ValueError, "Invalid credentials", 

1314 self.bugzilla.get_auth_token) 

1315 

1316 def test_auth_login__invalid_password(self): 

1317 for password in [None, ""]: 

1318 self.tracker.password = password 

1319 self.assertRaisesMessage(ValueError, "Invalid credentials", 

1320 self.bugzilla.get_auth_token) 

1321 

1322 def test_add_comment(self): 

1323 bug = Bug(tracker=self.tracker, bug_id=str(BugzillaProxyMock.BUG_ID)) 

1324 self.bugzilla.add_comment(bug, BugzillaProxyMock.COMMENT) 

1325 

1326 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=None) 

1327 def test_add_comment__invalid_credentials(self, auth_token_mocked): 

1328 bug = Bug(tracker=self.tracker, bug_id=str(BugzillaProxyMock.BUG_ID)) 

1329 self.assertRaisesMessage(ValueError, "Authentication failed. Can't post a comment", 

1330 self.bugzilla.add_comment, bug, BugzillaProxyMock.COMMENT) 

1331 

1332 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN) 

1333 def test_create_bug_from_json(self, auth_mock): 

1334 json_bug = {'summary': BugzillaProxyMock.SUMMARY, 

1335 'description': BugzillaProxyMock.DESCRIPTION} 

1336 self.bugzilla.create_bug_from_json(json_bug) 

1337 

1338 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN) 

1339 def test_create_bug_from_json__with_title_insteaf_of_summary(self, auth_mock): 

1340 json_bug = {'title': BugzillaProxyMock.SUMMARY, 

1341 'description': BugzillaProxyMock.DESCRIPTION} 

1342 self.bugzilla.create_bug_from_json(json_bug) 

1343 

1344 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN) 

1345 def test_create_bug_from_json__no_summary_nor_title(self, auth_mock): 

1346 with self.assertRaises(KeyError): 

1347 self.bugzilla.create_bug_from_json({}) 

1348 

1349 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN) 

1350 def test_create_bug_from_json__missing_description(self, auth_mock): 

1351 json_bug = {'title': BugzillaProxyMock.SUMMARY} 

1352 with self.assertRaises(ValueError): 

1353 self.bugzilla.create_bug_from_json(json_bug) 

1354 

1355 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=None) 

1356 def test_create_bug_from_json_invalid_token(self, auth_mock): 

1357 with self.assertRaises(ValueError): 

1358 self.bugzilla.create_bug_from_json({}) 

1359 

1360 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=None) 

1361 def test_create_bug_from_json_missing_required(self, auth_mock): 

1362 with self.assertRaises(ValueError): 

1363 self.bugzilla.create_bug_from_json({'summary': BugzillaProxyMock.SUMMARY}) 

1364 

1365 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN) 

1366 def test_update_bug_from_json(self, auth_mock): 

1367 json_bug = {'summary': BugzillaProxyMock.SUMMARY, 

1368 'description': BugzillaProxyMock.DESCRIPTION} 

1369 self.bugzilla.update_bug_from_json(json_bug, BugzillaProxyMock.UPDATE_IDS) 

1370 

1371 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=BugzillaProxyMock.TOKEN) 

1372 def test_update_bug_from_json_error(self, auth_mock): 

1373 self.bugzilla._proxy.Bug.update = MagicMock(side_effect=xmlrpc.client.Error()) 

1374 with self.assertRaises(ValueError): 

1375 self.bugzilla.update_bug_from_json({}, 1) 

1376 

1377 @patch('CIResults.bugtrackers.Bugzilla.get_auth_token', return_value=None) 

1378 def test_update_bug_from_json_invalid_token(self, auth_mock): 

1379 with self.assertRaises(ValueError): 

1380 self.bugzilla.update_bug_from_json({}, 1) 

1381 

1382 def test_transition(self): 

1383 self.bugzilla.update_bug_from_json = MagicMock() 

1384 self.bugzilla.transition(1, "Open") 

1385 self.bugzilla.update_bug_from_json.assert_called_with({'status': "Open"}, 1) 

1386 

1387 

1388class JiraMock: 

1389 # New Bug 

1390 NEW_BUG_ID = 5678 

1391 PROJECT_KEY = "TEST" 

1392 ISSUE_KEY = "TEST-101" 

1393 SUMMARY = "This is a test bug" 

1394 DESCRIPTION = "This is a description" 

1395 

1396 ISSUE = MagicMock() 

1397 ISSUE.key = ISSUE_KEY 

1398 

1399 # URLs 

1400 URL = "https://jira.instance.com/rest/api/2/issue" 

1401 RESP_URL = urllib.parse.urljoin(URL, str(NEW_BUG_ID)) 

1402 REQ_URL = URL 

1403 

1404 # User.login 

1405 LOGIN = "userlogin" 

1406 PASSWORD = "password" 

1407 

1408 # Bug.add_comment 

1409 BUG_ID = 1234 

1410 COMMENT = 'my comment' 

1411 

1412 # Bug.create_bug 

1413 REQUEST_DATA = {"project": { 

1414 "key": PROJECT_KEY 

1415 }, 

1416 "summary": SUMMARY, 

1417 "description": DESCRIPTION, 

1418 "issuetype": { 

1419 "name": "Bug" 

1420 } 

1421 } 

1422 

1423 RESPONSES = {REQ_URL: 

1424 {"id": NEW_BUG_ID, 

1425 "key": ISSUE_KEY, 

1426 "self": RESP_URL}} 

1427 

1428 

1429class BugTrackerJiraTests(TransactionTestCase): 

1430 def setUp(self): 

1431 issue = MagicMock(fields=MagicMock(summary="summary", status=MagicMock(), 

1432 description="description", 

1433 created=datetime.datetime.fromtimestamp(0, tz=pytz.utc).isoformat(), 

1434 updated=datetime.datetime.fromtimestamp(1, tz=pytz.utc).isoformat(), 

1435 resolutiondate=datetime.datetime.fromtimestamp(42, tz=pytz.utc).isoformat(), 

1436 creator=MagicMock(displayName="creator", key="creator_key", 

1437 emailAddress="creator@email"), 

1438 assignee=MagicMock(displayName="assignee", key="assignee_key", 

1439 emailAddress="assigne@email"), 

1440 components=[MagicMock(), MagicMock()], 

1441 comment=MagicMock(comments=[]))) 

1442 type(issue.fields.status).name = PropertyMock(return_value='status') 

1443 type(issue.fields.priority).name = PropertyMock(return_value='Low') 

1444 type(issue.fields.components[0]).name = PropertyMock(return_value='component1') 

1445 type(issue.fields.components[1]).name = PropertyMock(return_value='component2') 

1446 feature1 = MagicMock(value='Lasers') 

1447 feature2 = MagicMock(value='Sharks') 

1448 issue.fields.cool_features = [feature1, feature2] 

1449 platform1 = MagicMock(value='Platform A') 

1450 platform2 = MagicMock(value='Platform B') 

1451 issue.fields.fancy_platforms = [platform1, platform2] 

1452 issue.fields.a_custom_field = MagicMock(value="tacos") 

1453 issue.fields.labels = ['label1', 'label2'] 

1454 

1455 comment_created = datetime.datetime.fromtimestamp(47, tz=pytz.utc).isoformat() 

1456 issue.fields.comment.comments.append(MagicMock(author=MagicMock(displayName='Last, First', 

1457 emailAddress='comments@email'), 

1458 id='12345', created=comment_created)) 

1459 type(issue.fields.comment.comments[0].author).name = PropertyMock(return_value='flast') 

1460 issue.fields.comment.comments.append(MagicMock(author=MagicMock(displayName='Last, First2', 

1461 emailAddress='comments@email2'), 

1462 id='12346', created=comment_created,)) 

1463 type(issue.fields.comment.comments[1].author).name = PropertyMock(return_value='flast2') 

1464 

1465 self.issue = issue 

1466 

1467 @patch('CIResults.bugtrackers.Jira.jira') 

1468 def test__get_tracker_time(self, j_mock): 

1469 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', public=True) 

1470 jira = Jira(tracker) 

1471 

1472 j_mock.server_info.return_value = {'serverTime': '2020-05-07T10:36:28.772-0700'} 

1473 dt = datetime.datetime(2020, 5, 7, 17, 36, 28, 772000, tzinfo=pytz.utc) 

1474 jt = jira._get_tracker_time() 

1475 

1476 self.assertEqual(dt, jt) 

1477 

1478 @patch('CIResults.bugtrackers.timezone') 

1479 @patch('CIResults.bugtrackers.Jira.jira') 

1480 def test__get_tracker_time_error(self, j_mock, tz_mock): 

1481 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', public=True) 

1482 jira = Jira(tracker) 

1483 

1484 j_mock.server_info = MagicMock(side_effect=JIRAError) 

1485 now_mock = MagicMock() 

1486 tz_mock.now = now_mock 

1487 jira._get_tracker_time() 

1488 

1489 now_mock.assert_called() 

1490 

1491 @patch('CIResults.bugtrackers.Jira.jira') 

1492 def test__to_tracker_tz(self, j_mock): 

1493 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', public=True) 

1494 jira = Jira(tracker) 

1495 

1496 j_mock.myself.return_value = {'timeZone': 'America/Vancouver'} 

1497 dt = datetime.datetime(2020, 5, 7, 17, 36, 28, 772000, tzinfo=pytz.utc) 

1498 j_dt = jira._to_tracker_tz(dt) 

1499 

1500 # NOTE: datetime comparison considers TZ, so format to str to 

1501 # make sure right datetime adjusted for right TZ. 

1502 exp_str = "2020/05/07 10:36" 

1503 jt_str = j_dt.strftime("%Y/%m/%d %H:%M") 

1504 

1505 self.assertEqual(exp_str, jt_str) 

1506 

1507 @patch('CIResults.bugtrackers.Jira.jira') 

1508 def test__to_tracker_tz_error(self, j_mock): 

1509 tracker = BugTracker.objects.create(tracker_type="jira", project='PRODUCT', public=True) 

1510 jira = Jira(tracker) 

1511 

1512 j_mock.myself = MagicMock(side_effect=JIRAError) 

1513 dt = datetime.datetime(2020, 5, 7, 17, 36, 28, 772000, tzinfo=pytz.utc) 

1514 j_dt = jira._to_tracker_tz(dt) 

1515 

1516 # NOTE: datetime comparison considers TZ, so format to str to 

1517 # make sure right datetime adjusted for right TZ. 

1518 exp_str = "2020/05/07 17:36" 

1519 jt_str = j_dt.strftime("%Y/%m/%d %H:%M") 

1520 

1521 self.assertEqual(exp_str, jt_str) 

1522 

1523 @patch('jira.JIRA.__init__', return_value=None) 

1524 def test_jira__no_auth(self, JIRA_mocked): 

1525 Jira(BugTracker(tracker_type="jira", url='https://jira.com', public=True)).jira 

1526 JIRA_mocked.assert_called_with({'server': 'https://jira.com', 'verify': False}) 

1527 

1528 @patch('jira.JIRA.__init__', return_value=None) 

1529 def test_jira__with_auth(self, JIRA_mocked): 

1530 Jira(BugTracker(tracker_type="jira", url='https://jira.com', public=True, 

1531 username='user', password='password')).jira 

1532 JIRA_mocked.assert_called_with({'server': 'https://jira.com', 'verify': False}, 

1533 basic_auth=('user', 'password')) 

1534 

1535 def test__parse_custom_field(self): 

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

1537 public=True) 

1538 jira = Jira(tracker) 

1539 

1540 value1 = ['apple', 'cherry', 'orange'] 

1541 resp1 = jira._parse_custom_field(value1, to_str=False) 

1542 resp1_str = jira._parse_custom_field(value1, to_str=True) 

1543 self.assertEqual(resp1, value1) 

1544 self.assertEqual(resp1_str, 'apple,cherry,orange') 

1545 

1546 value2 = 'something clever' 

1547 resp2 = jira._parse_custom_field(value2, to_str=False) 

1548 resp2_str = jira._parse_custom_field(value2, to_str=True) 

1549 self.assertEqual(resp2, value2) 

1550 self.assertEqual(resp2_str, value2) 

1551 

1552 value3 = [MagicMock(value="apple"), MagicMock(value="cherry"), MagicMock(value="orange")] 

1553 resp3 = jira._parse_custom_field(value3, to_str=False) 

1554 resp3_str = jira._parse_custom_field(value3, to_str=True) 

1555 self.assertEqual(resp3, ['apple', 'cherry', 'orange']) 

1556 self.assertEqual(resp3_str, 'apple,cherry,orange') 

1557 

1558 value4 = MagicMock(value=25) 

1559 resp4 = jira._parse_custom_field(value4, to_str=False) 

1560 resp4_str = jira._parse_custom_field(value4, to_str=True) 

1561 self.assertEqual(resp4, value4.value) 

1562 self.assertEqual(resp4_str, "25") 

1563 

1564 @patch('CIResults.bugtrackers.Jira.jira') 

1565 def test_poll(self, connection_mock): 

1566 connection_mock.issue.return_value = self.issue 

1567 connection_mock.server_info.return_value = {'serverTime': '2020-05-07T10:36:28.772-0700'} 

1568 

1569 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/', 

1570 public=True, custom_fields_map={'cool_features': 'features', 

1571 'fancy_platforms': 'platforms', 

1572 'a_custom_field': 'my_custom_field'}) 

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

1574 bug._save = bug.save 

1575 bug.save = MagicMock() 

1576 Jira(tracker).poll(bug) 

1577 

1578 fields = connection_mock.issue.call_args[1]['fields'] 

1579 self.assertIn('fancy_platforms', fields) 

1580 self.assertIn('cool_features', fields) 

1581 self.assertEqual(bug.title, "summary") 

1582 self.assertEqual(bug.created, datetime.datetime.fromtimestamp(0, tz=pytz.utc)) 

1583 self.assertEqual(bug.updated, datetime.datetime.fromtimestamp(1, tz=pytz.utc)) 

1584 self.assertEqual(bug.creator.person.full_name, "creator") 

1585 self.assertEqual(bug.assignee.person.full_name, "assignee") 

1586 self.assertEqual(bug.component, "component1,component2") 

1587 self.assertEqual(bug.status, "status") 

1588 self.assertEqual(bug.priority, "Low") 

1589 self.assertEqual(bug.platforms, "Platform A,Platform B") 

1590 self.assertEqual(bug.features, "Lasers,Sharks") 

1591 self.assertEqual(bug.description, "description") 

1592 self.assertEqual(bug.closed, datetime.datetime.fromtimestamp(42, tz=pytz.utc)) 

1593 self.assertEqual(bug.custom_fields['my_custom_field'], "tacos") 

1594 self.assertEqual(bug.tags, "label1,label2") 

1595 

1596 del (tracker.custom_fields_map['cool_features']) 

1597 del (tracker.custom_fields_map['fancy_platforms']) 

1598 Jira(tracker).poll(bug) 

1599 self.assertIsNone(bug.platforms) 

1600 self.assertIsNone(bug.features) 

1601 

1602 # Check that the bug does not exist and no bugs have been polled 

1603 bug.save.assert_not_called() 

1604 self.assertEqual(len(BugComment.objects.all()), 0) 

1605 

1606 # Save the bug, then poll again to check that the comments get created 

1607 bug._save() 

1608 bug.poll() 

1609 bug.save.assert_not_called() 

1610 

1611 # Check that the comments got created 

1612 for c_id in ['12345', '12346']: 

1613 comment = BugComment.objects.get(comment_id=c_id) 

1614 self.assertEqual(comment.bug, bug) 

1615 self.assertIn("flast", comment.account.user_id) 

1616 self.assertIn("Last, First", comment.account.person.full_name) 

1617 self.assertEqual(comment.url, "https://jira.com/browse/1234#comment-{}".format(c_id)) 

1618 self.assertEqual(comment.created_on, datetime.datetime.fromtimestamp(47, tz=pytz.utc)) 

1619 

1620 @patch('CIResults.bugtrackers.Jira.jira') 

1621 def test_poll_invalid_custom_fields(self, connection_mock): 

1622 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/', 

1623 public=True, custom_fields_map={'id': 'id', 

1624 'bug_id': 'bug_id', 

1625 'tracker_id': 'tracker_id', 

1626 'tracker': 'tracker', 

1627 'parent_id': 'parent_id', 

1628 'parent': 'parent'}) 

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

1630 bug._save = bug.save 

1631 bug.save = MagicMock() 

1632 

1633 self.issue.fields.id = 9000 

1634 self.issue.fields.bug_id = 4000 

1635 self.issue.fields.tracker_id = 12341234 

1636 self.issue.fields.parent_id = 9999999 

1637 self.issue.fields.parent = 102030102 

1638 connection_mock.issue.return_value = self.issue 

1639 

1640 j = Jira(tracker) 

1641 parse_mock = MagicMock() 

1642 j._parse_custom_field = parse_mock 

1643 j.poll(bug) 

1644 

1645 parse_mock.assert_not_called() 

1646 

1647 @patch('CIResults.bugtrackers.Jira.jira') 

1648 def test_poll_invalid_status(self, connection_mock): 

1649 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/', 

1650 public=True) 

1651 j = Jira(tracker) 

1652 

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

1654 bug._save = bug.save 

1655 bug.save = MagicMock() 

1656 

1657 issues = [] 

1658 for i in range(3): 

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

1660 

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

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

1663 type(issues[2].fields.status).name = PropertyMock(return_value='Open') 

1664 

1665 connection_mock.issue.side_effect = issues 

1666 

1667 j.poll(bug) 

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

1669 

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

1671 

1672 connection_mock.issue.side_effect = issues 

1673 

1674 j.poll(bug) 

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

1676 

1677 def create_issue(self, comm_created, comm2_created): 

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

1679 

1680 name_1 = 'Pat' 

1681 name_2 = 'Geno' 

1682 email_1 = 'Pat@Pat' 

1683 email_2 = 'Geno@Geno' 

1684 comm_body_1 = "Steak wiz wit'" 

1685 comm_body_2 = "Steak prov witout" 

1686 issue.fields.comment.comments.append(MagicMock(author=MagicMock(displayName=name_1, emailAddress=email_1), 

1687 id='12345', created=str(comm_created), body=comm_body_1)) 

1688 type(issue.fields.comment.comments[0].author).name = PropertyMock(return_value=name_1) 

1689 issue.fields.comment.comments.append(MagicMock(author=MagicMock(displayName=name_2, emailAddress=email_2), 

1690 id='12346', created=str(comm2_created), body=comm_body_2)) 

1691 type(issue.fields.comment.comments[1].author).name = PropertyMock(return_value=name_2) 

1692 

1693 JiraMock.issue = MagicMock() 

1694 JiraMock.issue.return_value = issue 

1695 

1696 comments = [(name_1, 

1697 comm_body_1, 

1698 comm_created), 

1699 (name_2, 

1700 comm_body_2, 

1701 comm2_created)] 

1702 return (issue, comments) 

1703 

1704 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1705 def test__poll_comments(self): 

1706 comment_created = timezone.make_aware(datetime.datetime.now(), pytz.utc) 

1707 comment2_created = timezone.make_aware(datetime.datetime.now(), pytz.utc) 

1708 issue, exp_resp = self.create_issue(comment_created, comment2_created) 

1709 

1710 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/', 

1711 public=True) 

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

1713 bug.save() 

1714 

1715 j = Jira(tracker) 

1716 comm_list = j._Jira__poll_comments(bug, issue) 

1717 result = [] 

1718 for c in comm_list: 

1719 result.append((c.db_object.account.person.full_name, 

1720 c.body, 

1721 c.db_object.created_on)) 

1722 

1723 self.assertEqual(result, exp_resp) 

1724 

1725 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1726 def test__poll_comments_after_polled(self): 

1727 comment_created = timezone.make_aware(datetime.datetime.now(), pytz.utc) 

1728 comment2_created = comment_created + timedelta(days=2) 

1729 comment_polled = comment_created + timedelta(days=1) 

1730 tracker = BugTracker.objects.create(tracker_type="jira", bug_base_url='https://jira.com/browse/', 

1731 public=True) 

1732 j = Jira(tracker) 

1733 bug = Bug(bug_id='1234', tracker=tracker, comments_polled=comment_polled) 

1734 bug.save() 

1735 

1736 issue, exp_resp = self.create_issue(comment_created, comment2_created) 

1737 exp_resp = [exp_resp[1]] 

1738 person = Person.objects.create(full_name=exp_resp[0]) 

1739 account = BugTrackerAccount.objects.create(user_id="1", person=person, tracker=tracker, is_developer=True) 

1740 BugComment.objects.create(bug=bug, account=account, comment_id="12345", url='https://jira.com/browse/12345', 

1741 created_on=comment_created) 

1742 comm_list = j._Jira__poll_comments(bug, issue) 

1743 result = [] 

1744 for c in comm_list: 

1745 result.append((c.db_object.account.person.full_name, 

1746 c.body, 

1747 c.db_object.created_on)) 

1748 

1749 self.assertEqual(result, exp_resp) 

1750 

1751 @patch('CIResults.bugtrackers.Jira.jira') 

1752 def test_search_bugs_ids__full(self, connection_mock): 

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

1754 public=True) 

1755 jira = Jira(tracker) 

1756 

1757 # Mock the return value of the search command 

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

1759 

1760 bugs1 = [] 

1761 for i in range(1000): 

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

1763 

1764 bugs2 = [] 

1765 for i in range(1000, 2000): 

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

1767 

1768 bugs3 = [] 

1769 for i in range(1000, 2000): 

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

1771 

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

1773 ResultList(bugs2, _total=2500), 

1774 ResultList(bugs3, _total=2500)] 

1775 

1776 # Get the list of open bugs 

1777 updated_since = datetime.datetime.fromtimestamp(125) 

1778 open_bugs = jira.search_bugs_ids(components=["COMPONENT1", "COMPONENT2"], 

1779 created_since=datetime.datetime.fromtimestamp(125), 

1780 updated_since=updated_since, 

1781 status=['status1', 'status2']) 

1782 self.assertEqual(open_bugs, set([b.key for b in bugs1+bugs2+bugs3])) 

1783 

1784 # Verify that the request was valid 

1785 connection_mock.search_issues.assert_any_call('project = \'PRODUCT\' ' 

1786 'AND component in ("COMPONENT1", "COMPONENT2") ' 

1787 'AND created > "1970/01/01 00:02" ' 

1788 'AND updated > "1970/01/01 00:02" ' 

1789 'AND status in ("status1", "status2")', 

1790 fields=['key'], maxResults=1000) 

1791 connection_mock.search_issues.assert_any_call('project = \'PRODUCT\' ' 

1792 'AND component in ("COMPONENT1", "COMPONENT2") ' 

1793 'AND created > "1970/01/01 00:02" ' 

1794 'AND updated > "1970/01/01 00:02" ' 

1795 'AND status in ("status1", "status2")', 

1796 fields=['key'], maxResults=1000, startAt=1000) 

1797 connection_mock.search_issues.assert_any_call('project = \'PRODUCT\' ' 

1798 'AND component in ("COMPONENT1", "COMPONENT2") ' 

1799 'AND created > "1970/01/01 00:02" ' 

1800 'AND updated > "1970/01/01 00:02" ' 

1801 'AND status in ("status1", "status2")', 

1802 fields=['key'], maxResults=1000, startAt=2000) 

1803 

1804 @patch('CIResults.bugtrackers.Jira.jira') 

1805 def test_open_statuses(self, j_mock): 

1806 def statuses(): 

1807 stat1_cat = MagicMock() 

1808 stat1_cat.name = "To Do" 

1809 stat1 = MagicMock(statusCategory=stat1_cat) 

1810 stat1.name = "Open" 

1811 

1812 stat2_cat = MagicMock() 

1813 stat2_cat.name = "In Progress" 

1814 stat2 = MagicMock(statusCategory=stat2_cat) 

1815 stat2.name = "Scoping" 

1816 

1817 stat3_cat = MagicMock() 

1818 stat3_cat.name = "Done" 

1819 stat3 = MagicMock(statusCategory=stat3_cat) 

1820 stat3.name = "Closed" 

1821 

1822 return [stat1, stat2, stat3] 

1823 

1824 j_mock.statuses = statuses 

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

1826 public=True) 

1827 jira = Jira(tracker) 

1828 self.assertEqual(jira.open_statuses, ['Open', 'Scoping']) 

1829 

1830 @patch('CIResults.bugtrackers.Jira.jira') 

1831 def test_add_comment(self, connection_mock): 

1832 tracker = BugTracker.objects.create(tracker_type="jira", public=True) 

1833 jira = Jira(tracker) 

1834 

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

1836 

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

1838 

1839 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1840 def test_create_bug_from_json(self): 

1841 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

1842 project=JiraMock.PROJECT_KEY, 

1843 public=True) 

1844 j = Jira(tracker) 

1845 resp = MagicMock(key=1) 

1846 JiraMock.create_issue = MagicMock(return_value=resp) 

1847 summary = "summary" 

1848 description = "description" 

1849 status = {"name": "FOO"} 

1850 components = [{"name": "BAR"}, {"name": "Blah"}] 

1851 json_bug = {'summary': summary, 

1852 'description': description, 

1853 'status': status, 

1854 'components': components, 

1855 'issue_type': {'name': "Bug"}} 

1856 

1857 with patch('CIResults.bugtrackers.Jira.transition') as t_mock: 

1858 j.create_bug_from_json(json_bug) 

1859 t_mock.assert_not_called() 

1860 args, kwargs = JiraMock.create_issue.call_args_list[0] 

1861 request = kwargs['fields'] 

1862 self.assertEqual(request['project'], {'key': JiraMock.PROJECT_KEY}) 

1863 del (request['project']) 

1864 self.assertEqual(request['issuetype'], {'name': "Bug"}) 

1865 del (request['issuetype']) 

1866 for field in json_bug: 

1867 self.assertEqual(request[field], json_bug[field]) 

1868 

1869 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1870 def test_create_bug_from_json_title(self): 

1871 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

1872 project=JiraMock.PROJECT_KEY, 

1873 public=True) 

1874 j = Jira(tracker) 

1875 resp = MagicMock(key=1) 

1876 JiraMock.create_issue = MagicMock(return_value=resp) 

1877 summary = "summary" 

1878 description = "description" 

1879 status = {"name": "FOO"} 

1880 components = [{"name": "BAR"}, {"name": "Blah"}] 

1881 json_bug = {'title': summary, 

1882 'description': description, 

1883 'status': status, 

1884 'components': components} 

1885 

1886 j.create_bug_from_json(json_bug) 

1887 args, kwargs = JiraMock.create_issue.call_args_list[0] 

1888 request = kwargs['fields'] 

1889 self.assertEqual(request['summary'], summary) 

1890 

1891 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1892 def test_create_bug_from_json_issuetype(self): 

1893 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

1894 project=JiraMock.PROJECT_KEY, 

1895 public=True) 

1896 j = Jira(tracker) 

1897 resp = MagicMock(key=1) 

1898 JiraMock.create_issue = MagicMock(return_value=resp) 

1899 summary = "summary" 

1900 description = "description" 

1901 status = {"name": "FOO"} 

1902 components = [{"name": "BAR"}, {"name": "Blah"}] 

1903 json_bug = {'title': summary, 

1904 'description': description, 

1905 'status': status, 

1906 'components': components} 

1907 

1908 j.create_bug_from_json(json_bug) 

1909 args, kwargs = JiraMock.create_issue.call_args_list[0] 

1910 request = kwargs['fields'] 

1911 self.assertEqual(request['issuetype'], {'name': "Bug"}) 

1912 

1913 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1914 def test_create_bug_from_json_error(self): 

1915 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

1916 project=JiraMock.PROJECT_KEY, 

1917 public=True) 

1918 j = Jira(tracker) 

1919 JiraMock.create_issue = MagicMock(side_effect=JIRAError) 

1920 summary = "summary" 

1921 description = "description" 

1922 status = {"name": "FOO"} 

1923 components = [{"name": "BAR"}, {"name": "Blah"}] 

1924 json_bug = {'title': summary, 

1925 'description': description, 

1926 'status': status, 

1927 'components': components} 

1928 

1929 with self.assertRaises(ValueError): 

1930 j.create_bug_from_json(json_bug) 

1931 

1932 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1933 def test_update_bug_from_json(self): 

1934 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

1935 project=JiraMock.PROJECT_KEY, 

1936 public=True) 

1937 j = Jira(tracker) 

1938 JiraMock.issue = MagicMock() 

1939 JiraMock.issue.return_value.update = MagicMock() 

1940 JiraMock.transition_issue = MagicMock() 

1941 summary = "summary" 

1942 status = {"name": "FOO"} 

1943 json_bug = {'summary': summary, 

1944 'status': status} 

1945 

1946 j.update_bug_from_json(json_bug, 1) 

1947 args, kwargs = JiraMock.issue.return_value.update.call_args_list[0] 

1948 request = kwargs['fields'] 

1949 self.assertEqual(request['project'], {'key': JiraMock.PROJECT_KEY}) 

1950 

1951 del (request['project']) 

1952 for field in json_bug: 

1953 self.assertEqual(request[field], json_bug[field]) 

1954 JiraMock.transition_issue.assert_not_called() 

1955 

1956 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1957 def test_update_bug_from_json_error(self): 

1958 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

1959 project=JiraMock.PROJECT_KEY, 

1960 public=True) 

1961 j = Jira(tracker) 

1962 JiraMock.issue = MagicMock() 

1963 JiraMock.issue.return_value.update = MagicMock(side_effect=JIRAError) 

1964 

1965 with self.assertRaises(ValueError): 

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

1967 

1968 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1969 def test_update_bug_from_json_transition(self): 

1970 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

1971 project=JiraMock.PROJECT_KEY, 

1972 public=True) 

1973 j = Jira(tracker) 

1974 issue = MagicMock() 

1975 JiraMock.issue = MagicMock() 

1976 JiraMock.issue.return_value = issue 

1977 issue.update = MagicMock() 

1978 j.transition = MagicMock() 

1979 json_bug = {'transition': {'status': "Open", 'fields': {'resolution': 'In Progress'}}} 

1980 

1981 j.update_bug_from_json(json_bug, 1) 

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

1983 

1984 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

1985 def test_update_bug_from_json_update_field(self): 

1986 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

1987 project=JiraMock.PROJECT_KEY, 

1988 public=True) 

1989 j = Jira(tracker) 

1990 issue = MagicMock() 

1991 JiraMock.issue = MagicMock() 

1992 JiraMock.issue.return_value = issue 

1993 issue.update = MagicMock() 

1994 json_bug = {'update': {'labels': [{'add': 'foo'}]}, 'summary': "Foo"} 

1995 

1996 j.update_bug_from_json(json_bug, 1) 

1997 issue.update.assert_called_with(update={'labels': [{'add': 'foo'}]}, 

1998 fields={'summary': "Foo", 'project': {'key': "TEST"}}) 

1999 

2000 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

2001 def test_transition(self): 

2002 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

2003 project=JiraMock.PROJECT_KEY, 

2004 public=True) 

2005 j = Jira(tracker) 

2006 issue = MagicMock() 

2007 JiraMock.issue = MagicMock() 

2008 JiraMock.issue.return_value = issue 

2009 JiraMock.transition_issue = MagicMock() 

2010 status = "Open" 

2011 fields = {'resolution': 'In Progress'} 

2012 

2013 j.transition(1, status, fields) 

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

2015 

2016 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

2017 def test_transition_error(self): 

2018 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

2019 project=JiraMock.PROJECT_KEY, 

2020 public=True) 

2021 j = Jira(tracker) 

2022 issue = MagicMock() 

2023 JiraMock.issue = MagicMock() 

2024 JiraMock.issue.return_value = issue 

2025 JiraMock.transition_issue = MagicMock(side_effect=JIRAError) 

2026 status = "Open" 

2027 fields = {'resolution': 'In Progress'} 

2028 

2029 with self.assertRaises(ValueError): 

2030 j.transition(1, status, fields) 

2031 

2032 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

2033 def test_transition_create_bug_from_json(self): 

2034 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

2035 project=JiraMock.PROJECT_KEY, 

2036 public=True) 

2037 j = Jira(tracker) 

2038 resp = MagicMock(key=1) 

2039 issue = MagicMock() 

2040 JiraMock.issue = MagicMock() 

2041 JiraMock.issue.return_value = issue 

2042 JiraMock.create_issue = MagicMock(return_value=resp) 

2043 j.transition = MagicMock() 

2044 json_bug = {'summary': "FOO", 'transition': {'status': "Closed", 'fields': {'resolution': 'Fixed'}}} 

2045 

2046 bug_id = j.create_bug_from_json(json_bug) 

2047 self.assertEqual(bug_id, 1) 

2048 j.transition.assert_called_with(1, "Closed", {'resolution': "Fixed"}) 

2049 

2050 @patch('CIResults.bugtrackers.Jira.jira', JiraMock) 

2051 def test_transition_create_bug_from_json_error(self): 

2052 tracker = BugTracker.objects.create(tracker_type="jira", url="https://foo", 

2053 project=JiraMock.PROJECT_KEY, 

2054 public=True) 

2055 j = Jira(tracker) 

2056 resp = MagicMock(key=1) 

2057 JiraMock.create_issue = MagicMock(return_value=resp) 

2058 j.transition = MagicMock(side_effect=ValueError) 

2059 json_bug = {"title": "bar", "transition": {"status": "Open"}} 

2060 

2061 bug_id = j.create_bug_from_json(json_bug) 

2062 self.assertEqual(bug_id, 1) 

2063 

2064 

2065class BugTrackerJiraUntrackedTests(TestCase): 

2066 def setUp(self): 

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

2068 project='PROJECT', public=True) 

2069 

2070 def test_poll(self): 

2071 bug = MagicMock(spec=Bug) 

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

2073 

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

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

2076 bug.save.assert_not_called() 

2077 

2078 def test_search_bugs_ids(self): 

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

2080 

2081 def test_open_statuses(self): 

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

2083 

2084 def test_add_comment(self): 

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

2086 

2087 def test_create_bug(self): 

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