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

550 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-06 08:12 +0000

1import json 

2import datetime 

3 

4from unittest.mock import patch, MagicMock 

5from rest_framework.test import APIRequestFactory, APITestCase 

6from django.conf import settings 

7from django.db import transaction 

8from django.test import TestCase 

9from django.urls import reverse 

10from model_bakery import baker 

11 

12from CIResults.models import ( 

13 Bug, 

14 BugTracker, 

15 Build, 

16 Component, 

17 Issue, 

18 IssueFilter, 

19 Machine, 

20 MachineTag, 

21 RunConfig, 

22 RunConfigTag, 

23 Test, 

24 TestResult, 

25 TestSuite, 

26 TextStatus, 

27 UnknownFailure, 

28) 

29from CIResults.rest_views import CustomPagination, IssueFilterViewSet, RunConfigViewSet, get_obj_by_id_or_name 

30from CIResults.rest_views import BuildViewSet 

31from CIResults.tests.test_views import create_user_and_log_in 

32 

33from shortener.models import Shortener 

34 

35# HACK: Massively speed up the login primitive. We don't care about security in tests 

36settings.PASSWORD_HASHERS = ('django.contrib.auth.hashers.MD5PasswordHasher', ) 

37 

38 

39class UtilsTests(TestCase): 

40 def test_get_obj_by_id_or_name__id(self): 

41 rc = RunConfig.objects.create(name='valid', temporary=True) 

42 self.assertEqual(get_obj_by_id_or_name(RunConfig, 1), rc) 

43 

44 def test_get_obj_by_id_or_name__name(self): 

45 rc = RunConfig.objects.create(name='valid', temporary=True) 

46 self.assertEqual(get_obj_by_id_or_name(RunConfig, "valid"), rc) 

47 

48 def test_get_obj_by_id_or_name__not_exist(self): 

49 self.assertRaises(RunConfig.DoesNotExist, get_obj_by_id_or_name, RunConfig, 1) 

50 

51 

52class CustomPaginationTests(TestCase): 

53 def get_page_size(self, query_params, page_size_query_param='page_size', max_page_size=None): 

54 request = MagicMock(query_params=query_params) 

55 

56 pagination = CustomPagination() 

57 pagination.page_size_query_param = page_size_query_param 

58 pagination.max_page_size = max_page_size 

59 

60 return pagination.get_page_size(request) 

61 

62 def test_default_page_size(self): 

63 self.assertEqual(self.get_page_size({}), CustomPagination.page_size) 

64 

65 def test_default_page_size_without_page_size_field(self): 

66 self.assertEqual(self.get_page_size({}, page_size_query_param=None), CustomPagination.page_size) 

67 

68 def test_invalid_page_size(self): 

69 self.assertEqual(self.get_page_size({'page_size': 'toto'}), CustomPagination.page_size) 

70 

71 def test_negative_page_size(self): 

72 self.assertEqual(self.get_page_size({CustomPagination.page_size_query_param: -0}), None) 

73 

74 def test_page_size_too_big(self): 

75 self.assertEqual(self.get_page_size({'page_size': '1000'}, max_page_size=10), 10) 

76 

77 def test_page_size_big_but_no_limits(self): 

78 self.assertEqual(self.get_page_size({'page_size': '1000'}, max_page_size=None), 1000) 

79 

80 def test_acceptable_page_size(self): 

81 self.assertEqual(self.get_page_size({'page_size2': '42'}, page_size_query_param='page_size2'), 42) 

82 

83 

84class IssueFilterTests(APITestCase): 

85 maxDiff = None 

86 

87 def setUp(self): 

88 self.view = IssueFilterViewSet() 

89 self.user = None 

90 

91 def __post__(self, body_dict, logged_in=True, permissions=['add_issuefilter']): 

92 if logged_in: 

93 self.user = create_user_and_log_in(self.client, permissions=permissions) 

94 

95 return self.client.post(reverse('api:issuefilter-list'), body_dict, format='json') 

96 

97 def test__get_or_None__empty_field(self): 

98 errors = [] 

99 self.assertIsNone(self.view.__get_or_None__(Machine, 'field', {'field': ''}, errors)) 

100 self.assertEqual(errors, []) 

101 

102 def test__get_or_None__invalid_id(self): 

103 errors = [] 

104 self.assertIsNone(self.view.__get_or_None__(Machine, 'field', {'field': '12'}, errors)) 

105 self.assertEqual(errors, ["The object referenced by 'field' does not exist"]) 

106 

107 def test_get_filter_by_description(self): 

108 IssueFilter.objects.create(description="filter one") 

109 IssueFilter.objects.create(description="filter two") 

110 response = self.client.get(reverse('api:issuefilter-list'), {'description': 'one'}) 

111 self.assertEqual(response.status_code, 200) 

112 self.assertEqual(response.data["count"], 1) 

113 self.assertEqual(response.data["results"][0]["description"], "filter one") 

114 

115 def test_create_empty(self): 

116 response = self.__post__({}) 

117 self.assertEqual(response.status_code, 400) 

118 self.assertEqual(response.data, ["The field 'description' cannot be empty"]) 

119 

120 def test_invalid_regexps(self): 

121 response = self.__post__({"description": "Minimal IssueFilter", 

122 "stdout_regex": '(', "stderr_regex": '(', 

123 "dmesg_regex": '('}) 

124 self.assertEqual(response.status_code, 400) 

125 self.assertEqual(response.data, ["The field 'stdout_regex' does not contain a valid regular expression", 

126 "The field 'stderr_regex' does not contain a valid regular expression", 

127 "The field 'dmesg_regex' does not contain a valid regular expression"]) 

128 

129 def test_create_minimal__unauthenticated(self): 

130 response = self.__post__({"description": "Minimal IssueFilter"}, logged_in=False) 

131 self.assertEqual(response.status_code, 401) 

132 

133 # FIXME: This is not currently working 

134 # def test_create_minimal__invalid_permissions(self): 

135 # response = self.__post__({"description": "Minimal IssueFilter"}, logged_in=True, permissions=[]) 

136 # self.assertEqual(response.status_code, 403) 

137 

138 def test_create_minimal(self): 

139 response = self.__post__({"description": "Minimal IssueFilter"}) 

140 self.assertEqual(response.status_code, 201) 

141 self.assertEqual(response.data, 

142 {'id': response.data['id'], 'description': 'Minimal IssueFilter', 

143 'tags': [], 'machine_tags': [], 'machines': [], 'tests': [], 

144 'statuses': [], 'stdout_regex': '', 

145 'stderr_regex': '', 'dmesg_regex': '', 

146 'user_query': '', '__str__': 'Minimal IssueFilter'}) 

147 

148 def test_create_invalid(self): 

149 response = self.__post__({ 

150 "description": "Invalid tags, machines, tests, and statuses", 

151 'tags': [1], 'machines': [2], 'tests': [3], 'statuses': [4] 

152 }) 

153 self.assertEqual(response.status_code, 400, response.data) 

154 self.assertEqual(response.data, ['At least one tag does not exist', 

155 'At least one machine does not exist', 

156 'At least one test does not exist', 

157 'At least one status does not exist']) 

158 

159 def test_create_complete(self): 

160 # Create some objects before referencing them 

161 ts = TestSuite.objects.create(name="ts", description="", url="", public=True) 

162 for i in range(1, 6): 

163 RunConfigTag.objects.create(id=i, name="tag{}".format(i), description="", url="", public=True) 

164 Machine.objects.create(id=i, name="machine{}".format(i), public=True) 

165 Test.objects.create(id=i, name="test{}".format(i), testsuite=ts, public=True) 

166 TextStatus.objects.create(id=i, testsuite=ts, name="status{}".format(i)) 

167 MachineTag.objects.create(id=i, name="TAG{}".format(i), public=True) 

168 

169 # Make the request and check that we get the expected output 

170 with transaction.atomic(): 

171 response = self.__post__({ 

172 "description": "Minimal IssueFilter", 

173 'tags': [1, 2], 'machine_tags': [1, 5], 'machines': [2, 3], 'tests': [3, 4], 'statuses': [4, 5], 

174 'stdout_regex': 'stdout', 'stderr_regex': 'stderr', 'dmesg_regex': 'dmesg' 

175 }) 

176 

177 self.assertEqual(response.status_code, 201, response.data) 

178 self.assertEqual(dict(response.data), 

179 {'id': response.data['id'], 'description': 'Minimal IssueFilter', 

180 'tags': [ 

181 {'id': 1, 'description': '', 'url': '', 'public': True, '__str__': 'tag1'}, 

182 {'id': 2, 'description': '', 'url': '', 'public': True, '__str__': 'tag2'} 

183 ], 

184 'machine_tags': [ 

185 {'id': 1, 'name': 'TAG1', 'public': True}, 

186 {'id': 5, 'name': 'TAG5', 'public': True}, 

187 ], 

188 'machines': [ 

189 {'id': 2, 'vetted_on': None, 'public': True, '__str__': 'machine2'}, 

190 {'id': 3, 'vetted_on': None, 'public': True, '__str__': 'machine3'}, 

191 ], 

192 'tests': [ 

193 {'id': 3, 'testsuite': {'id': ts.id, '__str__': 'ts'}, 'public': True, 

194 'vetted_on': None, 'first_runconfig': None, 'name': 'test3', '__str__': 'ts: test3'}, 

195 {'id': 4, 'testsuite': {'id': ts.id, '__str__': 'ts'}, 'public': True, 

196 'vetted_on': None, 'first_runconfig': None, 'name': 'test4', '__str__': 'ts: test4'}, 

197 ], 

198 'statuses': [ 

199 {'id': 4, 'testsuite': {'id': ts.id, '__str__': 'ts'}, 'name': 'status4', 

200 '__str__': 'ts: status4'}, 

201 {'id': 5, 'testsuite': {'id': ts.id, '__str__': 'ts'}, 'name': 'status5', 

202 '__str__': 'ts: status5'}, 

203 ], 

204 'stdout_regex': 'stdout', 'stderr_regex': 'stderr', 

205 'dmesg_regex': 'dmesg', '__str__': 'Minimal IssueFilter', 

206 'user_query': ''}) 

207 

208 def test_edit_invalid(self): 

209 response = self.__post__({"description": "new filter", 

210 "edit_filter": "a", "edit_issue": "b"}) 

211 self.assertEqual(response.status_code, 400, response.data) 

212 self.assertEqual(response.data, ["The field 'edit_filter' needs to be an integer", 

213 "The field 'edit_issue' needs to be an integer"]) 

214 

215 @patch('CIResults.models.IssueFilter.replace') 

216 @patch('CIResults.models.Issue.replace_filter') 

217 def test_edit_all_issues(self, mock_replace_filter, mock_filter_replace): 

218 filter = IssueFilter.objects.create(description="old filter") 

219 

220 with transaction.atomic(): 

221 response = self.__post__({"description": "new filter", "edit_filter": filter.id}) 

222 self.assertEqual(response.status_code, 201, response.data) 

223 self.assertEqual(response.data, 

224 {'id': response.data['id'], 'description': 'new filter', 

225 'tags': [], 'machine_tags': [], 'machines': [], 'tests': [], 

226 'statuses': [], 'stdout_regex': '', 

227 'stderr_regex': '', 'dmesg_regex': '', 

228 'user_query': '', '__str__': 'new filter'}) 

229 

230 new_filter = IssueFilter.objects.get(pk=response.data['id']) 

231 mock_replace_filter.assert_not_called() 

232 mock_filter_replace.assert_called_once_with(new_filter, self.user) 

233 self.assertEqual(mock_filter_replace.call_args_list[0][0][0].id, response.data['id']) 

234 

235 @patch('CIResults.models.IssueFilter.replace') 

236 @patch('CIResults.models.Issue.replace_filter') 

237 def test_edit_one_issue(self, mock_replace_filter, mock_filter_replace): 

238 filter = IssueFilter.objects.create(description="desc") 

239 issue = Issue.objects.create(description="issue") 

240 

241 with transaction.atomic(): 

242 response = self.__post__({"description": "new filter", 

243 "edit_filter": "{}".format(filter.id), 

244 "edit_issue": issue.id}) 

245 

246 self.assertEqual(response.status_code, 201, response.data) 

247 self.assertEqual(response.data, 

248 {'id': response.data['id'], 'description': 'new filter', 

249 'tags': [], 'machine_tags': [], 'machines': [], 'tests': [], 

250 'statuses': [], 'stdout_regex': '', 

251 'stderr_regex': '', 'dmesg_regex': '', 

252 'user_query': '', '__str__': 'new filter'}) 

253 

254 new_filter = IssueFilter.objects.get(pk=response.data['id']) 

255 mock_filter_replace.assert_not_called() 

256 mock_replace_filter.assert_called_once_with(filter, new_filter, self.user) 

257 self.assertEqual(mock_replace_filter.call_args_list[0][0][0].id, filter.id) 

258 self.assertEqual(mock_replace_filter.call_args_list[0][0][1].id, response.data['id']) 

259 

260 

261class RunConfigTests(APITestCase): 

262 def setUp(self): 

263 self.view = RunConfigViewSet() 

264 self.arf = APIRequestFactory() 

265 

266 def test_retrieve__by_id(self): 

267 rc = RunConfig.objects.create(name='valid', temporary=True) 

268 response = self.client.get(reverse("api:runconfig-detail", args=["valid"])) 

269 data = response.json() 

270 self.assertEqual(data["id"], rc.id) 

271 self.assertEqual(data["name"], "valid") 

272 self.assertEqual(data["tags"], []) 

273 self.assertIsNone(data["url"]) 

274 self.assertIsNotNone(data["added_on"]) 

275 

276 def test_list(self): 

277 runcfg1 = RunConfig.objects.create(name='runcfg1', temporary=True, environment="env") 

278 runcfg2 = RunConfig.objects.create(name='runcfg2', temporary=False, url="http://url.com") 

279 RunConfig.objects.create(name='other_name', temporary=False, url="http://url.com") 

280 response = self.client.get(reverse("api:runconfig-list"), {"name__contains": "runcfg"}) 

281 data = response.json() 

282 self.assertEqual(data["count"], 2) 

283 for result in data["results"]: 

284 self.assertIsNotNone(result.pop("added_on")) 

285 for rc in (runcfg1, runcfg2): 

286 self.assertIn( 

287 { 

288 "id": rc.id, "name": rc.name, "tags": list(rc.tags.all()), "temporary": rc.temporary, 

289 "url": rc.url, "builds": list(rc.builds.all()), "environment": rc.environment, 

290 "__str__": str(rc), 

291 }, 

292 data["results"], 

293 ) 

294 

295 def test_known_failures(self): 

296 # TODO: Revisit the test whenever we start generating fixtures 

297 runcfg = RunConfig.objects.create(name='valid', temporary=True) 

298 response = self.client.get(reverse("api:runconfig-known-failures", args=[runcfg.name])) 

299 self.assertEqual(response.data, []) 

300 

301 def test_compare(self): 

302 runcfg1 = RunConfig.objects.create(name='runcfg1', temporary=True) 

303 runcfg2 = RunConfig.objects.create(name='runcfg2', temporary=True) 

304 with patch( 

305 "CIResults.models.RunConfig.compare", return_value=runcfg1.compare(runcfg2, no_compress=False) 

306 ) as compare_mocked: 

307 response = self.client.get(reverse("api:runconfig-compare", args=[runcfg1.name]), {'to': 'runcfg2'}) 

308 compare_mocked.assert_called_once_with(runcfg2, no_compress=False) 

309 self.assertEqual(response.data["runcfg_from"]["name"], "runcfg1") 

310 self.assertEqual(response.data["runcfg_to"]["name"], "runcfg2") 

311 self.assertEqual(response.data["status"], "SUCCESS") 

312 self.assertTrue(len(response.data["text"]) > 0) 

313 

314 def test_compare__no_compress(self): 

315 runcfg1 = RunConfig.objects.create(name='runcfg1', temporary=True) 

316 runcfg2 = RunConfig.objects.create(name='runcfg2', temporary=True) 

317 with patch( 

318 "CIResults.models.RunConfig.compare", return_value=runcfg1.compare(runcfg2, no_compress='1'), 

319 ) as compare_mocked: 

320 response = self.client.get( 

321 reverse("api:runconfig-compare", args=[runcfg1.name]), 

322 {'to': 'runcfg2', "no_compress": "1"}, 

323 ) 

324 compare_mocked.assert_called_once_with(runcfg2, no_compress=True) 

325 self.assertEqual(response.data["runcfg_from"]["name"], "runcfg1") 

326 self.assertEqual(response.data["runcfg_to"]["name"], "runcfg2") 

327 self.assertEqual(response.data["status"], "SUCCESS") 

328 self.assertTrue(len(response.data["text"]) > 0) 

329 

330 def test_compare__only_summary(self): 

331 runcfg1 = RunConfig.objects.create(name='runcfg1', temporary=True) 

332 RunConfig.objects.create(name='runcfg2', temporary=True) 

333 response = self.client.get( 

334 reverse("api:runconfig-compare", args=[runcfg1.name]), 

335 {'to': 'runcfg2', "summary": ""}, 

336 ) 

337 self.assertIn('CI Bug Log', response.content.decode()) 

338 

339 def test_create__no_permissions(self): 

340 response = self.client.post(reverse('api:runconfig-list'), 

341 data={"name": "runcfg", "tags": ["tag1"], "temporary": False}, 

342 content_type="application/json") 

343 self.assertEqual(response.status_code, 401) 

344 

345 def test_create(self): 

346 RunConfigTag.objects.create(id=1, name="tag1", public=True) 

347 create_user_and_log_in(self.client, permissions=['add_runconfig']) 

348 response = self.client.post(reverse('api:runconfig-list'), 

349 data={"name": "runcfg", "tags": ["tag1"], "builds": [], "temporary": False}, 

350 content_type="application/json") 

351 self.assertEqual(response.status_code, 201) 

352 self.assertEqual(response.data["name"], "runcfg") 

353 self.assertEqual(response.data["tags"], ["tag1"]) 

354 

355 def test_create__invalid_data(self): 

356 create_user_and_log_in(self.client, permissions=['add_runconfig']) 

357 response = self.client.post(reverse('api:runconfig-list'), data={"name": "runcfg"}, 

358 content_type="application/json") 

359 self.assertEqual(response.status_code, 400) 

360 

361 def test_create__invalid_data_missing_tag(self): 

362 create_user_and_log_in(self.client, permissions=['add_runconfig']) 

363 response = self.client.post(reverse('api:runconfig-list'), 

364 data={"name": "runcfg", "tags": ["tag1"], "temporary": False}, 

365 content_type="application/json") 

366 self.assertEqual(response.status_code, 400) 

367 

368 def test_import_testsuite_run(self): 

369 RunConfig.objects.create(name='runcfg1', temporary=False) 

370 component = TestSuite.objects.create(id=1, name='testsuite', public=True) 

371 Build.objects.create(name='testsuite-1', component=component) 

372 create_user_and_log_in(self.client, admin=True) 

373 response = self.client.post("/api/runconfig/runcfg1/testsuiterun/", content_type="application/json", data={ 

374 "test_suite_name": "testsuite-1", 

375 "test_results": { 

376 "machine-1": { 

377 "001": [{ 

378 "test_name": "test1", 

379 "status": "pass", 

380 "stdout": "output", 

381 }], 

382 "002": [{ 

383 "test_name": "test2", 

384 "status": "pass", 

385 "stdout": "output", 

386 }], 

387 }, 

388 }, 

389 }, 

390 ) 

391 self.assertEqual(response.status_code, 200) 

392 self.assertIsNotNone(Machine.objects.get(name="machine-1")) 

393 self.assertIsNotNone(Test.objects.get(name="test1")) 

394 self.assertIsNotNone(TextStatus.objects.get(name="pass")) 

395 self.assertEqual(TestResult.objects.count(), 2) 

396 for test_name in ["test1", "test2"]: 

397 result = TestResult.objects.filter(test__name=test_name).first() 

398 self.assertEqual(result.status.name, "pass") 

399 self.assertEqual(result.stdout, "output") 

400 

401 def test_import_testsuite_run__no_permissions(self): 

402 response = self.client.post("/api/runconfig/runcfg1/testsuiterun/", content_type="application/json", data={}) 

403 self.assertEqual(response.status_code, 401) 

404 

405 def test_import_testsuite_run__invalid_data(self): 

406 RunConfig.objects.create(name='runcfg1', temporary=False) 

407 component = TestSuite.objects.create(id=1, name='testsuite', public=True) 

408 Build.objects.create(name='testsuite-1', component=component) 

409 create_user_and_log_in(self.client, admin=True) 

410 response = self.client.post("/api/runconfig/runcfg1/testsuiterun/", content_type="application/json", data={ 

411 "test_suite": "testsuite-1", 

412 "test_results": "invalid_data" 

413 }) 

414 self.assertEqual(response.status_code, 400) 

415 self.assertEqual(response.json()["test_results"], 

416 ['Expected a dictionary of items but got type "str".']) 

417 

418 def test_import_testsuite_run__invalid_data_no_testsuite(self): 

419 RunConfig.objects.create(name='runcfg1', temporary=False) 

420 create_user_and_log_in(self.client, admin=True) 

421 response = self.client.post("/api/runconfig/runcfg1/testsuiterun/", content_type="application/json", data={ 

422 "test_suite": "testsuite-1", 

423 "test_results": { 

424 "machine-1": { 

425 "001": [{ 

426 "test_name": "test1", 

427 "status": "pass", 

428 "stdout": "output", 

429 }], 

430 } 

431 } 

432 }) 

433 self.assertEqual(response.status_code, 400) 

434 self.assertEqual(response.json(), {'test_suite_name': ['This field is required.']}) 

435 

436 @patch("CIResults.serializers.ImportTestSuiteRunSerializer.save", side_effect=Exception("Some exception")) 

437 def test_import_testsuite_run__import_error(self, mock_serializer_save): 

438 runcfg1 = RunConfig.objects.create(name='runcfg1', temporary=False) 

439 component = TestSuite.objects.create(id=1, name='testsuite', public=True) 

440 Build.objects.create(name='testsuite-1', component=component) 

441 create_user_and_log_in(self.client, admin=True) 

442 response = self.client.post( 

443 reverse("api:runconfig-import-test-suite-run", args=[runcfg1]), 

444 content_type="application/json", 

445 data={ 

446 "test_suite_name": "testsuite-1", 

447 "test_results": { 

448 "machine-1": { 

449 "001": [{ 

450 "test_name": "test1", 

451 "status": "pass", 

452 "stdout": "output", 

453 }], 

454 "002": [{ 

455 "test_name": "test2", 

456 "status": "pass", 

457 "stdout": "output", 

458 }], 

459 } 

460 } 

461 } 

462 ) 

463 self.assertEqual(response.content.decode(), '"Some exception"') 

464 self.assertEqual(response.status_code, 400) 

465 

466 

467class BuildViewSetTests(APITestCase): 

468 def setUp(self) -> None: 

469 self.component = Component.objects.create(name="component", public=False) 

470 self.view = BuildViewSet() 

471 self.arf = APIRequestFactory() 

472 

473 def test_retrieve(self): 

474 rc = Build.objects.create(name='valid', component=self.component) 

475 data = self.view.retrieve(self.arf.get("/", {}, format="json"), pk="valid").data 

476 self.assertEqual(data["id"], rc.id) 

477 self.assertEqual(data["name"], "valid") 

478 self.assertIsNotNone(data["added_on"]) 

479 

480 def test_create_build_without_permissions(self): 

481 response = self.client.post(reverse('api:build-list'), 

482 data={"name": "test-build", "component": "component", "version": "1"}, 

483 format="json") 

484 self.assertEqual(response.status_code, 401) 

485 

486 def test_create_build(self): 

487 create_user_and_log_in(self.client, permissions=['add_build']) 

488 response = self.client.post(reverse('api:build-list'), 

489 data={"name": "build1", "component": "component", "version": "1", "parents": []}, 

490 format="json") 

491 self.assertEqual(response.status_code, 201) 

492 self.assertEqual(response.data["name"], "build1") 

493 

494 def test_create_build__invalid_data_schema(self): 

495 create_user_and_log_in(self.client, permissions=['add_build']) 

496 response = self.client.post(reverse('api:build-list'), 

497 data={"name": "test-build", "component": "component"}, 

498 format="json") 

499 self.assertEqual(response.status_code, 400) 

500 self.assertEqual(json.loads(response.content)["version"], ["This field is required."]) 

501 

502 def test_create_build__invalid_data(self): 

503 create_user_and_log_in(self.client, permissions=['add_build']) 

504 response = self.client.post(reverse('api:build-list'), 

505 data={"name": "test-build", "component": "no-component", "version": "1"}, 

506 format="json") 

507 self.assertEqual(response.status_code, 400) 

508 

509 

510class BugViewSetTests(TestCase): 

511 def setUp(self): 

512 self.arf = APIRequestFactory() 

513 

514 @classmethod 

515 def setUpTestData(cls): 

516 cls.bt1 = BugTracker.objects.create(name='Test Suite 1', short_name='ts1', public=True) 

517 cls.bt2 = BugTracker.objects.create(name='Test Suite 2', short_name='ts2', public=True) 

518 

519 cls.bugs = dict() 

520 for bt in (cls.bt1, cls.bt2): 

521 cls.bugs[bt] = [] 

522 for i in range(2): 

523 cls.bugs[bt].append(Bug.objects.create(tracker=bt, bug_id='b_'+str(i))) 

524 

525 def test_retrieving_by_tracker_id(self): 

526 tracker = self.bt2 

527 bug = self.bugs[self.bt2][1] 

528 response = self.client.get(reverse('api-bugtracker-get-bug', 

529 kwargs={'tracker': tracker.id, 'bug_id': bug.bug_id}), format='json') 

530 self.assertEqual(response.status_code, 200) 

531 self.assertEqual(response.data['bug_id'], bug.bug_id) 

532 self.assertEqual(response.data['tracker']['short_name'], tracker.short_name) 

533 

534 def test_retrieving_by_tracker_name(self): 

535 tracker = self.bt1 

536 bug = self.bugs[self.bt2][0] 

537 response = self.client.get(reverse('api-bugtracker-get-bug', 

538 kwargs={'tracker': tracker.name, 'bug_id': bug.bug_id}), format='json') 

539 self.assertEqual(response.status_code, 200) 

540 self.assertEqual(response.data['bug_id'], bug.bug_id) 

541 self.assertEqual(response.data['tracker']['short_name'], tracker.short_name) 

542 

543 def test_retrieving_by_tracker_short_name(self): 

544 tracker = self.bt2 

545 bug = self.bugs[self.bt2][0] 

546 response = self.client.get(reverse('api-bugtracker-get-bug', 

547 kwargs={'tracker': tracker.short_name, 'bug_id': bug.bug_id}), format='json') 

548 self.assertEqual(response.status_code, 200) 

549 self.assertEqual(response.data['bug_id'], bug.bug_id) 

550 self.assertEqual(response.data['tracker']['short_name'], tracker.short_name) 

551 

552 

553class ShortenerViewSetTests(TestCase): 

554 def setUp(self): 

555 self.arf = APIRequestFactory() 

556 

557 @classmethod 

558 def setUpTestData(cls): 

559 cls.short1 = Shortener.get_or_create("Some sort of long query!") 

560 

561 def test_create_invalid_request(self): 

562 with self.assertRaisesMessage(ValueError, "Only JSON POST requests are supported"): 

563 self.client.post(reverse('api:shortener-list'), data={}) 

564 

565 def test_create_empty(self): 

566 with self.assertRaisesMessage(ValueError, 

567 "Missing the field 'full' which should contain the full text to be shortened"): 

568 self.client.post(reverse('api:shortener-list'), data={}, content_type="application/json") 

569 

570 def test_create_single(self): 

571 full = 'Some sort of long query, but different!' 

572 

573 response = self.client.post(reverse('api:shortener-list'), data={'full': full}, content_type="application/json") 

574 self.assertEqual(response.status_code, 200) 

575 

576 data = response.json() 

577 self.assertNotEqual(data['id'], self.short1.id) 

578 self.assertEqual(data['full'], full) 

579 

580 def test_create_multiple(self): 

581 fulls = ["query1", "query2"] 

582 

583 response = self.client.post(reverse('api:shortener-list'), data={'full': fulls}, 

584 content_type="application/json") 

585 self.assertEqual(response.status_code, 200) 

586 

587 data = response.json() 

588 self.assertEqual(data[0]['full'], fulls[0]) 

589 self.assertEqual(data[1]['full'], fulls[1]) 

590 

591 def test_retrieving_existing(self): 

592 response = self.client.post(reverse('api:shortener-list'), data={'full': self.short1.full}, 

593 content_type="application/json") 

594 self.assertEqual(response.status_code, 200) 

595 

596 data = response.json() 

597 self.assertEqual(data['id'], self.short1.id) 

598 

599 

600class MachineViewSetTests(APITestCase): 

601 def setUp(self): 

602 self.arf = APIRequestFactory() 

603 

604 def test_retrieve(self): 

605 machine = Machine.objects.create(name="machine_1", public=True) 

606 response = self.client.get(reverse("api:machine-detail", args=[machine.id]), content_type="application/json") 

607 self.assertEqual(response.status_code, 200) 

608 self.assertEqual( 

609 response.json(), 

610 { 

611 'id': machine.id, 

612 'name': machine.name, 

613 'description': machine.description, 

614 'public': machine.public, 

615 'vetted_on': machine.vetted_on, 

616 'aliases': machine.aliases, 

617 'tags': list(machine.tags.all()), 

618 } 

619 ) 

620 

621 def test_list_machines(self): 

622 Machine.objects.create(name="machine_1", public=True) 

623 response = self.client.get(reverse('api:machine-list'), content_type="application/json") 

624 self.assertEqual(response.status_code, 200) 

625 self.assertEqual(json.loads(response.content)["results"], 

626 [{'id': 1, 

627 'name': 'machine_1', 

628 'description': None, 

629 'public': True, 

630 'vetted_on': None, 

631 'aliases': None, 

632 'tags': []}]) 

633 

634 def test_create_machine_without_permission(self): 

635 response = self.client.post(reverse('api:machine-list'), data={'name': 'machine_1', 'tags': ['tag1']}, 

636 content_type="application/json") 

637 self.assertEqual(response.status_code, 401) 

638 

639 def test_create_machine(self): 

640 self.user = create_user_and_log_in(self.client, permissions=['add_machine']) 

641 response = self.client.post(reverse('api:machine-list'), data={'name': 'machine_1', 'tags': ['tag1']}, 

642 content_type="application/json") 

643 self.assertEqual(response.status_code, 201) 

644 self.assertEqual(response.json(), { 

645 "id": 1, 

646 "name": "machine_1", 

647 "description": None, 

648 "public": False, 

649 "vetted_on": None, 

650 "aliases": None, 

651 "tags": ["tag1"], 

652 }) 

653 

654 def test_create_machine_invalid_data(self): 

655 self.user = create_user_and_log_in(self.client, permissions=['add_machine']) 

656 response = self.client.post(reverse('api:machine-list'), data={'name': True}, 

657 content_type="application/json") 

658 self.assertEqual(response.status_code, 400) 

659 self.assertEqual(json.loads(response.content), { 

660 "name": ["Not a valid string."], 

661 }) 

662 

663 def test_create_machine_import_error(self): 

664 self.user = create_user_and_log_in(self.client, permissions=['add_machine']) 

665 response = self.client.post(reverse('api:machine-list'), data={'name': 'machine_1', 'aliases': 'machine_2'}, 

666 content_type="application/json") 

667 self.assertEqual(response.status_code, 400) 

668 self.assertEqual(response.json(), {'aliases': ['Object with name=machine_2 does not exist.']}) 

669 

670 def test_vet_machine_without_permission(self): 

671 machine = baker.make(Machine) 

672 self.user = create_user_and_log_in(self.client, admin=False) 

673 response = self.client.post(reverse('api:machine-vet', kwargs={"pk": machine.pk})) 

674 self.assertEqual(response.status_code, 403) 

675 

676 @patch("django.utils.timezone.now") 

677 def test_vet_machine(self, now_mocked): 

678 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc) 

679 now_mocked.return_value = date 

680 machine = baker.make(Machine) 

681 self.user = create_user_and_log_in(self.client, admin=True) 

682 response = self.client.post(reverse("api:machine-vet", kwargs={"pk": machine.pk})) 

683 machine.refresh_from_db() 

684 self.assertEqual(machine.vetted_on, date) 

685 self.assertEqual(response.status_code, 200) 

686 

687 def test_vet_already_vetted(self): 

688 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc) 

689 machine = baker.make(Machine, vetted_on=date) 

690 self.user = create_user_and_log_in(self.client, admin=True) 

691 response = self.client.post(reverse('api:machine-vet', kwargs={"pk": machine.pk})) 

692 machine.refresh_from_db() 

693 self.assertEqual(response.status_code, 200) 

694 self.assertEqual(machine.vetted_on, date) 

695 

696 def test_suppress_machine_without_permission(self): 

697 machine = baker.make(Machine) 

698 self.user = create_user_and_log_in(self.client, admin=False) 

699 response = self.client.post(reverse('api:machine-suppress', kwargs={"pk": machine.pk})) 

700 self.assertEqual(response.status_code, 403) 

701 

702 def test_suppress_machine(self): 

703 machine = baker.make(Machine, vetted_on="2021-01-01") 

704 self.user = create_user_and_log_in(self.client, admin=True) 

705 response = self.client.post(reverse('api:machine-suppress', kwargs={"pk": machine.pk})) 

706 self.assertEqual(response.status_code, 200) 

707 

708 

709class IssueViewSetTests(TestCase): 

710 def setUp(self): 

711 self.arf = APIRequestFactory() 

712 

713 def test_archive_issue_without_permission(self): 

714 response = self.client.get(reverse('api:issue-archive', kwargs={"pk": 1})) 

715 self.assertEqual(response.status_code, 401) 

716 self.assertEqual(response.json(), {"message": "User AnonymousUser doesn't have sufficient permissions"}) 

717 

718 def test_archive_issue(self): 

719 Issue.objects.create() 

720 self.user = create_user_and_log_in(self.client, admin=True) 

721 response = self.client.get(reverse('api:issue-archive', kwargs={"pk": 1})) 

722 self.assertEqual(response.status_code, 200) 

723 

724 def test_archive_archived_issue(self): 

725 self.user = create_user_and_log_in(self.client, admin=True) 

726 Issue.objects.create().archive(self.user) 

727 response = self.client.get(reverse('api:issue-archive', kwargs={"pk": 1})) 

728 self.assertEqual(response.status_code, 400) 

729 self.assertEqual(response.json(), {"message": "The issue is already archived"}) 

730 

731 def test_restore_issue_without_permission(self): 

732 response = self.client.get(reverse('api:issue-restore', kwargs={"pk": 1})) 

733 self.assertEqual(response.status_code, 401) 

734 self.assertEqual(response.json(), {"message": "User AnonymousUser doesn't have sufficient permissions"}) 

735 

736 def test_restore_issue(self): 

737 self.user = create_user_and_log_in(self.client, admin=True) 

738 Issue.objects.create().archive(self.user) 

739 response = self.client.get(reverse('api:issue-restore', kwargs={"pk": 1})) 

740 self.assertEqual(response.status_code, 200) 

741 

742 def test_restore_not_archived_issue(self): 

743 Issue.objects.create() 

744 self.user = create_user_and_log_in(self.client, admin=True) 

745 response = self.client.get(reverse('api:issue-restore', kwargs={"pk": 1})) 

746 self.assertEqual(response.status_code, 400) 

747 self.assertEqual(response.json(), {"message": "The issue is not currently archived"}) 

748 

749 def test_update_to_expected(self): 

750 Issue.objects.create(expected=False) 

751 self.user = create_user_and_log_in(self.client, admin=True) 

752 response = self.client.patch(reverse('api:issue-detail', kwargs={"pk": 1}), data={"id": 1, "expected": True}, 

753 content_type="application/json") 

754 self.assertEqual(response.status_code, 200) 

755 self.assertEqual(response.json()["expected"], True) 

756 self.assertEqual(Issue.objects.get(pk=1).expected, True) 

757 

758 def test_update_to_expected_wrong_value(self): 

759 Issue.objects.create(expected=False) 

760 self.user = create_user_and_log_in(self.client, admin=True) 

761 response = self.client.patch(reverse('api:issue-detail', kwargs={"pk": 1}), data={"id": 1, "expected": "Str"}, 

762 content_type="application/json") 

763 self.assertEqual(response.status_code, 400) 

764 self.assertEqual(response.json()["expected"], ["Must be a valid boolean."]) 

765 

766 def test_try_to_update_read_only_field(self): 

767 Issue.objects.create(runconfigs_covered_count=100) 

768 self.user = create_user_and_log_in(self.client, admin=True) 

769 response = self.client.patch(reverse('api:issue-detail', kwargs={"pk": 1}), 

770 data={"id": 1, "runconfigs_covered_count": 99}, content_type="application/json") 

771 self.assertEqual(response.status_code, 200) 

772 self.assertEqual(response.json()["runconfigs_covered_count"], 100) 

773 

774 

775class UnknownFailureViewSetTests(TestCase): 

776 def test_retrieve_by_id(self): 

777 unknown_failure = baker.make(UnknownFailure) 

778 response = self.client.get( 

779 reverse("api:unknownfailure-detail", kwargs={"pk": unknown_failure.id}), content_type="application/json" 

780 ) 

781 self.assertEqual(response.status_code, 200) 

782 data = response.json() 

783 self.assertEqual(data["id"], unknown_failure.id) 

784 self.assertEqual(data["test"], unknown_failure.result.test.name) 

785 self.assertEqual(data["status"], unknown_failure.result.status.name) 

786 self.assertRaises(KeyError, lambda: data["dmesg"]) 

787 self.assertRaises(KeyError, lambda: data["stdout"]) 

788 self.assertRaises(KeyError, lambda: data["stderr"]) 

789 self.assertEqual(data["testsuite"], unknown_failure.result.ts_run.testsuite.name) 

790 self.assertEqual(data["runconfig"], { 

791 "name": unknown_failure.result.ts_run.runconfig.name, 

792 "tags": list(unknown_failure.result.ts_run.runconfig.tags.values_list('name', flat=True)) 

793 }) 

794 self.assertEqual(data["machine"], { 

795 "name": unknown_failure.result.ts_run.machine.name, 

796 "tags": list(unknown_failure.result.ts_run.machine.tags.values_list('name', flat=True)) 

797 }) 

798 

799 def test_retrieve_by_id__extra_fields(self): 

800 unknown_failure = baker.make(UnknownFailure) 

801 response = self.client.get( 

802 reverse("api:unknownfailure-detail", kwargs={"pk": unknown_failure.id}), 

803 content_type="application/json", 

804 data={"extra_fields": "stdout"}, 

805 ) 

806 self.assertEqual(response.status_code, 200) 

807 data = response.json() 

808 self.assertRaises(KeyError, lambda: data["dmesg"]) 

809 self.assertRaises(KeyError, lambda: data["stderr"]) 

810 self.assertEqual(data["stdout"], unknown_failure.result.stdout) 

811 

812 def test_list(self): 

813 unknown_failures = baker.make(UnknownFailure, _quantity=3) 

814 response = self.client.get( 

815 reverse("api:unknownfailure-list"), 

816 content_type="application/json", 

817 ) 

818 data = response.json() 

819 self.assertEqual(response.status_code, 200) 

820 self.assertEqual(data["count"], 3) 

821 for unknown_failure in unknown_failures: 

822 self.assertIn( 

823 { 

824 "id": unknown_failure.id, 

825 "test": unknown_failure.result.test.name, 

826 "status": unknown_failure.result.status.name, 

827 "testsuite": unknown_failure.result.ts_run.testsuite.name, 

828 "runconfig": { 

829 "name": unknown_failure.result.ts_run.runconfig.name, 

830 "tags": list(unknown_failure.result.ts_run.runconfig.tags.values_list('name', flat=True)) 

831 }, 

832 "machine": { 

833 "name": unknown_failure.result.ts_run.machine.name, 

834 "tags": list(unknown_failure.result.ts_run.machine.tags.values_list('name', flat=True)) 

835 }, 

836 }, 

837 data["results"], 

838 ) 

839 

840 

841class TestSetTests(TestCase): 

842 def setUp(self): 

843 self.arf = APIRequestFactory() 

844 ts = TestSuite.objects.create(name="ts", description="", url="", public=True) 

845 Test.objects.create(id=1, name="test1", testsuite=ts, public=True) 

846 

847 def test_vet_test_without_permission(self): 

848 self.user = create_user_and_log_in(self.client, admin=False) 

849 response = self.client.post(reverse('api:test-vet', kwargs={"pk": 1})) 

850 self.assertEqual(response.status_code, 403) 

851 

852 @patch("django.utils.timezone.now") 

853 def test_vet_test(self, now_mocked): 

854 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc) 

855 now_mocked.return_value = date 

856 test = baker.make(Test, vetted_on=None) 

857 self.user = create_user_and_log_in(self.client, admin=True) 

858 response = self.client.post(reverse('api:test-vet', kwargs={"pk": test.pk})) 

859 test.refresh_from_db() 

860 self.assertEqual(response.status_code, 200) 

861 self.assertEqual(test.vetted_on, date) 

862 

863 def test_vet_already_vetted(self): 

864 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc) 

865 test = baker.make(Test, vetted_on=date) 

866 self.user = create_user_and_log_in(self.client, admin=True) 

867 response = self.client.post(reverse('api:test-vet', kwargs={"pk": test.pk})) 

868 test.refresh_from_db() 

869 self.assertEqual(response.status_code, 200) 

870 self.assertEqual(test.vetted_on, date) 

871 

872 def test_suppress_test_without_permission(self): 

873 self.user = create_user_and_log_in(self.client, admin=False) 

874 response = self.client.post(reverse('api:test-suppress', kwargs={"pk": 1})) 

875 self.assertEqual(response.status_code, 403) 

876 

877 def test_suppress_test(self): 

878 test = baker.make(Test, vetted_on=datetime.datetime(2021, 1, 1)) 

879 self.user = create_user_and_log_in(self.client, admin=True) 

880 response = self.client.post(reverse('api:test-suppress', kwargs={"pk": test.pk})) 

881 test.refresh_from_db() 

882 self.assertEqual(response.status_code, 200) 

883 self.assertIsNone(test.vetted_on) 

884 

885 

886class TextStatusViewSetTests(TestCase): 

887 def setUp(self): 

888 self.arf = APIRequestFactory() 

889 self.user = create_user_and_log_in(self.client, admin=True) 

890 

891 @patch("django.utils.timezone.now") 

892 def test_vet(self, now_mocked): 

893 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc) 

894 now_mocked.return_value = date 

895 ts = baker.make(TextStatus, vetted_on=None) 

896 response = self.client.get(reverse('api:textstatus-vet', kwargs={"pk": ts.pk})) 

897 ts.refresh_from_db() 

898 self.assertEqual(response.status_code, 200) 

899 self.assertEqual(ts.vetted_on, date) 

900 

901 def test_suppress(self): 

902 ts = baker.make(TextStatus, vetted_on="2021-01-01") 

903 response = self.client.get(reverse('api:textstatus-suppress', kwargs={"pk": ts.pk})) 

904 ts.refresh_from_db() 

905 self.assertEqual(response.status_code, 200) 

906 self.assertIsNone(ts.vetted_on) 

907 

908 

909class metrics_passrate_trend_viewTests(TestCase): 

910 def setUp(self): 

911 self.arf = APIRequestFactory() 

912 

913 def test_basic(self): 

914 # TODO: Add a fixture that would return useful data here 

915 response = self.client.get(reverse('api-metrics-passrate-per-runconfig'), 

916 {'query': ""}, format='json') 

917 self.assertEqual(response.status_code, 200) 

918 

919 

920class metrics_passrate_viewTests(TestCase): 

921 def setUp(self): 

922 self.arf = APIRequestFactory() 

923 

924 def test_basic(self): 

925 # TODO: Add a fixture that would return useful data here 

926 response = self.client.get(reverse('api-metrics-passrate-per-test'), 

927 {'query': ""}, format='json') 

928 self.assertEqual(response.status_code, 200)