Coverage for CIResults/tests/test_rest_views.py: 100%
614 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 07:21 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 07:21 +0000
1import json
2import datetime
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
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
33from shortener.models import Shortener
35# HACK: Massively speed up the login primitive. We don't care about security in tests
36settings.PASSWORD_HASHERS = ('django.contrib.auth.hashers.MD5PasswordHasher', )
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)
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)
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)
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)
56 pagination = CustomPagination()
57 pagination.page_size_query_param = page_size_query_param
58 pagination.max_page_size = max_page_size
60 return pagination.get_page_size(request)
62 def test_default_page_size(self):
63 self.assertEqual(self.get_page_size({}), CustomPagination.page_size)
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)
68 def test_invalid_page_size(self):
69 self.assertEqual(self.get_page_size({'page_size': 'toto'}), CustomPagination.page_size)
71 def test_negative_page_size(self):
72 self.assertEqual(self.get_page_size({CustomPagination.page_size_query_param: -0}), None)
74 def test_page_size_too_big(self):
75 self.assertEqual(self.get_page_size({'page_size': '1000'}, max_page_size=10), 10)
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)
80 def test_acceptable_page_size(self):
81 self.assertEqual(self.get_page_size({'page_size2': '42'}, page_size_query_param='page_size2'), 42)
84class IssueFilterTests(APITestCase):
85 maxDiff = None
87 def setUp(self):
88 self.view = IssueFilterViewSet()
89 self.user = None
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)
95 return self.client.post(reverse('api:issuefilter-list'), body_dict, format='json')
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, [])
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"])
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")
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"])
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"])
129 def test_create_minimal__unauthenticated(self):
130 response = self.__post__({"description": "Minimal IssueFilter"}, logged_in=False)
131 self.assertEqual(response.status_code, 401)
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)
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'})
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'])
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)
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 })
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': ''})
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"])
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")
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'})
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'])
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")
241 with transaction.atomic():
242 response = self.__post__({"description": "new filter",
243 "edit_filter": "{}".format(filter.id),
244 "edit_issue": issue.id})
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'})
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'])
261class RunConfigTests(APITestCase):
262 def setUp(self):
263 self.view = RunConfigViewSet()
264 self.arf = APIRequestFactory()
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"])
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 )
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, [])
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)
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)
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())
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)
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"])
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)
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)
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 url = reverse("api:runconfig-import-test-suite-run", args=["runcfg1"])
374 response = self.client.post(url, content_type="application/json", data={
375 "test_suite_name": "testsuite-1",
376 "test_results": {
377 "machine-1": {
378 "001": [{
379 "test_name": "test1",
380 "status": "pass",
381 "stdout": "output",
382 }],
383 "002": [{
384 "test_name": "test2",
385 "status": "pass",
386 "stdout": "output",
387 }],
388 },
389 },
390 },
391 )
392 self.assertEqual(response.status_code, 200)
393 self.assertIsNotNone(Machine.objects.get(name="machine-1"))
394 self.assertIsNotNone(Test.objects.get(name="test1"))
395 self.assertIsNotNone(TextStatus.objects.get(name="pass"))
396 self.assertEqual(TestResult.objects.count(), 2)
397 for test_name in ["test1", "test2"]:
398 result = TestResult.objects.filter(test__name=test_name).first()
399 self.assertEqual(result.status.name, "pass")
400 self.assertEqual(result.stdout, "output")
402 def test_import_testsuite_run__no_permissions(self):
403 url = reverse("api:runconfig-import-test-suite-run", args=["runcfg1"])
404 response = self.client.post(url, content_type="application/json", data={})
405 self.assertEqual(response.status_code, 401)
407 def test_import_testsuite_run__invalid_data(self):
408 RunConfig.objects.create(name='runcfg1', temporary=False)
409 component = TestSuite.objects.create(id=1, name='testsuite', public=True)
410 Build.objects.create(name='testsuite-1', component=component)
411 create_user_and_log_in(self.client, admin=True)
412 url = reverse("api:runconfig-import-test-suite-run", args=["runcfg1"])
413 response = self.client.post(url, content_type="application/json", data={
414 "test_suite": "testsuite-1",
415 "test_results": "invalid_data"
416 })
417 self.assertEqual(response.status_code, 400)
418 self.assertEqual(response.json()["test_results"],
419 ['Expected a dictionary of items but got type "str".'])
421 def test_import_testsuite_run__invalid_data_no_testsuite(self):
422 RunConfig.objects.create(name='runcfg1', temporary=False)
423 create_user_and_log_in(self.client, admin=True)
424 url = reverse("api:runconfig-import-test-suite-run", args=["runcfg1"])
425 response = self.client.post(url, content_type="application/json", data={
426 "test_suite": "testsuite-1",
427 "test_results": {
428 "machine-1": {
429 "001": [{
430 "test_name": "test1",
431 "status": "pass",
432 "stdout": "output",
433 }],
434 }
435 }
436 })
437 self.assertEqual(response.status_code, 400)
438 self.assertEqual(response.json(), {'test_suite_name': ['This field is required.']})
440 @patch("CIResults.serializers.ImportTestSuiteRunSerializer.save", side_effect=Exception("Some exception"))
441 def test_import_testsuite_run__import_error(self, mock_serializer_save):
442 runcfg1 = RunConfig.objects.create(name='runcfg1', temporary=False)
443 component = TestSuite.objects.create(id=1, name='testsuite', public=True)
444 Build.objects.create(name='testsuite-1', component=component)
445 create_user_and_log_in(self.client, admin=True)
446 response = self.client.post(
447 reverse("api:runconfig-import-test-suite-run", args=[runcfg1]),
448 content_type="application/json",
449 data={
450 "test_suite_name": "testsuite-1",
451 "test_results": {
452 "machine-1": {
453 "001": [{
454 "test_name": "test1",
455 "status": "pass",
456 "stdout": "output",
457 }],
458 "002": [{
459 "test_name": "test2",
460 "status": "pass",
461 "stdout": "output",
462 }],
463 }
464 }
465 }
466 )
467 self.assertEqual(response.content.decode(), '"Some exception"')
468 self.assertEqual(response.status_code, 400)
471class BuildViewSetTests(APITestCase):
472 def setUp(self) -> None:
473 self.component = Component.objects.create(name="component", public=False)
474 self.view = BuildViewSet()
475 self.arf = APIRequestFactory()
477 def test_retrieve(self):
478 rc = Build.objects.create(name='valid', component=self.component)
479 data = self.view.retrieve(self.arf.get("/", {}, format="json"), pk="valid").data
480 self.assertEqual(data["id"], rc.id)
481 self.assertEqual(data["name"], "valid")
482 self.assertIsNotNone(data["added_on"])
484 def test_create_build_without_permissions(self):
485 response = self.client.post(reverse('api:build-list'),
486 data={"name": "test-build", "component": "component", "version": "1"},
487 format="json")
488 self.assertEqual(response.status_code, 401)
490 def test_create_build(self):
491 create_user_and_log_in(self.client, permissions=['add_build'])
492 response = self.client.post(reverse('api:build-list'),
493 data={"name": "build1", "component": "component", "version": "1", "parents": []},
494 format="json")
495 self.assertEqual(response.status_code, 201)
496 self.assertEqual(response.data["name"], "build1")
498 def test_create_build__invalid_data_schema(self):
499 create_user_and_log_in(self.client, permissions=['add_build'])
500 response = self.client.post(reverse('api:build-list'),
501 data={"name": "test-build", "component": "component"},
502 format="json")
503 self.assertEqual(response.status_code, 400)
504 self.assertEqual(json.loads(response.content)["version"], ["This field is required."])
506 def test_create_build__invalid_data(self):
507 create_user_and_log_in(self.client, permissions=['add_build'])
508 response = self.client.post(reverse('api:build-list'),
509 data={"name": "test-build", "component": "no-component", "version": "1"},
510 format="json")
511 self.assertEqual(response.status_code, 400)
514class BugViewSetTests(APITestCase):
515 def setUp(self):
516 self.arf = APIRequestFactory()
518 @classmethod
519 def setUpTestData(cls):
520 cls.bt1 = BugTracker.objects.create(name='Test Suite 1', short_name='ts1', public=True)
521 cls.bt2 = BugTracker.objects.create(name='Test Suite 2', short_name='ts2', public=True)
523 cls.bugs = dict()
524 for bt in (cls.bt1, cls.bt2):
525 cls.bugs[bt] = []
526 for i in range(2):
527 cls.bugs[bt].append(Bug.objects.create(tracker=bt, bug_id='b_'+str(i)))
529 def test_retrieving_by_tracker_id(self):
530 tracker = self.bt2
531 bug = self.bugs[self.bt2][1]
532 response = self.client.get(reverse('api:bug-detail',
533 kwargs={'tracker': tracker.id, 'bug_id': bug.bug_id}), format='json')
534 self.assertEqual(response.status_code, 200)
535 self.assertEqual(response.data['bug_id'], bug.bug_id)
536 self.assertEqual(response.data['tracker']['short_name'], tracker.short_name)
538 def test_retrieving_by_tracker_name(self):
539 tracker = self.bt1
540 bug = self.bugs[self.bt2][0]
541 response = self.client.get(reverse('api:bug-detail',
542 kwargs={'tracker': tracker.name, 'bug_id': bug.bug_id}), format='json')
543 self.assertEqual(response.status_code, 200)
544 self.assertEqual(response.data['bug_id'], bug.bug_id)
545 self.assertEqual(response.data['tracker']['short_name'], tracker.short_name)
547 def test_retrieving_by_tracker_short_name(self):
548 tracker = self.bt2
549 bug = self.bugs[self.bt2][0]
550 response = self.client.get(reverse('api:bug-detail',
551 kwargs={'tracker': tracker.short_name, 'bug_id': bug.bug_id}), format='json')
552 self.assertEqual(response.status_code, 200)
553 self.assertEqual(response.data['bug_id'], bug.bug_id)
554 self.assertEqual(response.data['tracker']['short_name'], tracker.short_name)
556 def test_creating_bug_without_permission(self):
557 response = self.client.post(reverse('api:bug-list', kwargs={'tracker': self.bt1.id}),
558 data={'tracker': self.bt1.id, 'bug_id': 'new_bug'}, format='json')
559 self.assertEqual(response.status_code, 401)
561 def test_creating_bug(self):
562 create_user_and_log_in(self.client, permissions=['add_bug'])
563 response = self.client.post(reverse('api:bug-list', kwargs={'tracker': self.bt1.id}),
564 data={'tracker': self.bt1.id, 'bug_id': 'new_bug'}, format='json')
565 self.assertEqual(response.status_code, 201)
566 self.assertEqual(response.data['bug_id'], 'new_bug')
567 self.assertEqual(response.data['tracker']['id'], self.bt1.id)
569 def test_creating_bug_invalid_tracker(self):
570 create_user_and_log_in(self.client, permissions=['add_bug'])
571 response = self.client.post(reverse('api:bug-list', kwargs={'tracker': 999}),
572 data={'tracker': 999, 'bug_id': 'new_bug'}, format='json')
573 self.assertEqual(response.status_code, 404)
575 def test_updating_bug_without_permission(self):
576 bug = self.bugs[self.bt1][0]
577 response = self.client.put(reverse('api:bug-detail',
578 kwargs={'tracker': self.bt1.id, 'bug_id': bug.bug_id}),
579 data={'tracker': self.bt1.id, 'bug_id': bug.bug_id}, format='json')
580 self.assertEqual(response.status_code, 401)
582 def test_updating_bug(self):
583 create_user_and_log_in(self.client, permissions=['change_bug'])
584 bug = self.bugs[self.bt1][0]
585 response = self.client.put(reverse('api:bug-detail',
586 kwargs={'tracker': self.bt1.id, 'bug_id': bug.bug_id}),
587 data={'status': 'updated'}, format='json')
588 self.assertEqual(response.status_code, 200)
589 self.assertEqual(response.data['status'], 'updated')
590 self.assertEqual(response.data['tracker']['id'], self.bt1.id)
593class ShortenerViewSetTests(TestCase):
594 def setUp(self):
595 self.arf = APIRequestFactory()
597 @classmethod
598 def setUpTestData(cls):
599 cls.short1 = Shortener.get_or_create("Some sort of long query!")
601 def test_create_invalid_request(self):
602 with self.assertRaisesMessage(ValueError, "Only JSON POST requests are supported"):
603 self.client.post(reverse('api:shortener-list'), data={})
605 def test_create_empty(self):
606 with self.assertRaisesMessage(ValueError,
607 "Missing the field 'full' which should contain the full text to be shortened"):
608 self.client.post(reverse('api:shortener-list'), data={}, content_type="application/json")
610 def test_create_single(self):
611 full = 'Some sort of long query, but different!'
613 response = self.client.post(reverse('api:shortener-list'), data={'full': full}, content_type="application/json")
614 self.assertEqual(response.status_code, 200)
616 data = response.json()
617 self.assertNotEqual(data['id'], self.short1.id)
618 self.assertEqual(data['full'], full)
620 def test_create_multiple(self):
621 fulls = ["query1", "query2"]
623 response = self.client.post(reverse('api:shortener-list'), data={'full': fulls},
624 content_type="application/json")
625 self.assertEqual(response.status_code, 200)
627 data = response.json()
628 self.assertEqual(data[0]['full'], fulls[0])
629 self.assertEqual(data[1]['full'], fulls[1])
631 def test_retrieving_existing(self):
632 response = self.client.post(reverse('api:shortener-list'), data={'full': self.short1.full},
633 content_type="application/json")
634 self.assertEqual(response.status_code, 200)
636 data = response.json()
637 self.assertEqual(data['id'], self.short1.id)
640class MachineViewSetTests(APITestCase):
641 def setUp(self):
642 self.arf = APIRequestFactory()
644 def test_retrieve(self):
645 machine = Machine.objects.create(name="machine_1", public=True)
646 response = self.client.get(reverse("api:machine-detail", args=[machine.id]), content_type="application/json")
647 self.assertEqual(response.status_code, 200)
648 self.assertEqual(
649 response.json(),
650 {
651 'id': machine.id,
652 'name': machine.name,
653 'description': machine.description,
654 'public': machine.public,
655 'vetted_on': machine.vetted_on,
656 'aliases': machine.aliases,
657 'tags': list(machine.tags.all()),
658 }
659 )
661 def test_list_machines(self):
662 Machine.objects.create(name="machine_1", public=True)
663 response = self.client.get(reverse('api:machine-list'), content_type="application/json")
664 self.assertEqual(response.status_code, 200)
665 self.assertEqual(json.loads(response.content)["results"],
666 [{'id': 1,
667 'name': 'machine_1',
668 'description': None,
669 'public': True,
670 'vetted_on': None,
671 'aliases': None,
672 'tags': []}])
674 def test_create_machine_without_permission(self):
675 response = self.client.post(reverse('api:machine-list'), data={'name': 'machine_1', 'tags': ['tag1']},
676 content_type="application/json")
677 self.assertEqual(response.status_code, 401)
679 def test_create_machine(self):
680 self.user = create_user_and_log_in(self.client, permissions=['add_machine'])
681 response = self.client.post(reverse('api:machine-list'), data={'name': 'machine_1', 'tags': ['tag1']},
682 content_type="application/json")
683 self.assertEqual(response.status_code, 201)
684 self.assertEqual(response.json(), {
685 "id": 1,
686 "name": "machine_1",
687 "description": None,
688 "public": False,
689 "vetted_on": None,
690 "aliases": None,
691 "tags": ["tag1"],
692 })
694 def test_create_machine_invalid_data(self):
695 self.user = create_user_and_log_in(self.client, permissions=['add_machine'])
696 response = self.client.post(reverse('api:machine-list'), data={'name': True},
697 content_type="application/json")
698 self.assertEqual(response.status_code, 400)
699 self.assertEqual(json.loads(response.content), {
700 "name": ["Not a valid string."],
701 })
703 def test_create_machine_import_error(self):
704 self.user = create_user_and_log_in(self.client, permissions=['add_machine'])
705 response = self.client.post(reverse('api:machine-list'), data={'name': 'machine_1', 'aliases': 'machine_2'},
706 content_type="application/json")
707 self.assertEqual(response.status_code, 400)
708 self.assertEqual(response.json(), {'aliases': ['Object with name=machine_2 does not exist.']})
710 def test_vet_machine_without_permission(self):
711 machine = baker.make(Machine)
712 self.user = create_user_and_log_in(self.client, admin=False)
713 response = self.client.post(reverse('api:machine-vet', kwargs={"pk": machine.pk}))
714 self.assertEqual(response.status_code, 403)
716 @patch("django.utils.timezone.now")
717 def test_vet_machine(self, now_mocked):
718 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc)
719 now_mocked.return_value = date
720 machine = baker.make(Machine)
721 self.user = create_user_and_log_in(self.client, admin=True)
722 response = self.client.post(reverse("api:machine-vet", kwargs={"pk": machine.pk}))
723 machine.refresh_from_db()
724 self.assertEqual(machine.vetted_on, date)
725 self.assertEqual(response.status_code, 200)
727 def test_vet_already_vetted(self):
728 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc)
729 machine = baker.make(Machine, vetted_on=date)
730 self.user = create_user_and_log_in(self.client, admin=True)
731 response = self.client.post(reverse('api:machine-vet', kwargs={"pk": machine.pk}))
732 machine.refresh_from_db()
733 self.assertEqual(response.status_code, 200)
734 self.assertEqual(machine.vetted_on, date)
736 def test_suppress_machine_without_permission(self):
737 machine = baker.make(Machine)
738 self.user = create_user_and_log_in(self.client, admin=False)
739 response = self.client.post(reverse('api:machine-suppress', kwargs={"pk": machine.pk}))
740 self.assertEqual(response.status_code, 403)
742 def test_suppress_machine(self):
743 machine = baker.make(Machine, vetted_on="2021-01-01")
744 self.user = create_user_and_log_in(self.client, admin=True)
745 response = self.client.post(reverse('api:machine-suppress', kwargs={"pk": machine.pk}))
746 self.assertEqual(response.status_code, 200)
749class IssueViewSetTests(TestCase):
750 def setUp(self):
751 self.arf = APIRequestFactory()
753 def test_archive_issue_without_permission(self):
754 response = self.client.get(reverse('api:issue-archive', kwargs={"pk": 1}))
755 self.assertEqual(response.status_code, 401)
756 self.assertEqual(response.json(), {"message": "User AnonymousUser doesn't have sufficient permissions"})
758 def test_archive_issue(self):
759 Issue.objects.create()
760 self.user = create_user_and_log_in(self.client, admin=True)
761 response = self.client.get(reverse('api:issue-archive', kwargs={"pk": 1}))
762 self.assertEqual(response.status_code, 200)
764 def test_archive_archived_issue(self):
765 self.user = create_user_and_log_in(self.client, admin=True)
766 Issue.objects.create().archive(self.user)
767 response = self.client.get(reverse('api:issue-archive', kwargs={"pk": 1}))
768 self.assertEqual(response.status_code, 400)
769 self.assertEqual(response.json(), {"message": "The issue is already archived"})
771 def test_restore_issue_without_permission(self):
772 response = self.client.get(reverse('api:issue-restore', kwargs={"pk": 1}))
773 self.assertEqual(response.status_code, 401)
774 self.assertEqual(response.json(), {"message": "User AnonymousUser doesn't have sufficient permissions"})
776 def test_restore_issue(self):
777 self.user = create_user_and_log_in(self.client, admin=True)
778 Issue.objects.create().archive(self.user)
779 response = self.client.get(reverse('api:issue-restore', kwargs={"pk": 1}))
780 self.assertEqual(response.status_code, 200)
782 def test_restore_not_archived_issue(self):
783 Issue.objects.create()
784 self.user = create_user_and_log_in(self.client, admin=True)
785 response = self.client.get(reverse('api:issue-restore', kwargs={"pk": 1}))
786 self.assertEqual(response.status_code, 400)
787 self.assertEqual(response.json(), {"message": "The issue is not currently archived"})
789 def test_update_to_expected(self):
790 Issue.objects.create(expected=False)
791 self.user = create_user_and_log_in(self.client, admin=True)
792 response = self.client.patch(reverse('api:issue-detail', kwargs={"pk": 1}), data={"id": 1, "expected": True},
793 content_type="application/json")
794 self.assertEqual(response.status_code, 200)
795 self.assertEqual(response.json()["expected"], True)
796 self.assertEqual(Issue.objects.get(pk=1).expected, True)
798 def test_update_to_expected_wrong_value(self):
799 Issue.objects.create(expected=False)
800 self.user = create_user_and_log_in(self.client, admin=True)
801 response = self.client.patch(reverse('api:issue-detail', kwargs={"pk": 1}), data={"id": 1, "expected": "Str"},
802 content_type="application/json")
803 self.assertEqual(response.status_code, 400)
804 self.assertEqual(response.json()["expected"], ["Must be a valid boolean."])
806 def test_try_to_update_read_only_field(self):
807 Issue.objects.create(runconfigs_covered_count=100)
808 self.user = create_user_and_log_in(self.client, admin=True)
809 response = self.client.patch(reverse('api:issue-detail', kwargs={"pk": 1}),
810 data={"id": 1, "runconfigs_covered_count": 99}, content_type="application/json")
811 self.assertEqual(response.status_code, 200)
812 self.assertEqual(response.json()["runconfigs_covered_count"], 100)
814 def test_add_issue_filters_without_permission(self):
815 issue = baker.make(Issue)
816 f1 = baker.make(IssueFilter)
817 f2 = baker.make(IssueFilter)
818 self.user = create_user_and_log_in(self.client)
819 response = self.client.post(
820 reverse(
821 'api:issue-add-filters',
822 kwargs={"pk": issue.id}),
823 data={"filters": [f1.id, f2.id]},
824 content_type="application/json",
825 )
826 self.assertEqual(response.status_code, 403)
827 self.assertEqual(response.json(), {'detail': 'You do not have permission to perform this action.'})
829 def test_add_issue_filters(self):
830 self.user = create_user_and_log_in(self.client, permissions=["change_issue", "add_issue"])
831 issue = baker.make(Issue)
832 f_default = baker.make(IssueFilter)
833 issue.filters.add(f_default)
834 f1 = baker.make(IssueFilter)
835 f2 = baker.make(IssueFilter)
836 issue_filter_list = sorted(list(issue.filters.all()), key=lambda f: f.id)
837 self.assertEqual(issue_filter_list, [f_default])
838 response = self.client.post(
839 reverse(
840 'api:issue-add-filters',
841 kwargs={"pk": issue.id}),
842 data={"filters": [f1.id, f2.id]},
843 content_type="application/json",
844 )
845 issue_filter_list = sorted(list(issue.filters.all()), key=lambda f: f.id)
846 expected_list = sorted([f_default, f1, f2], key=lambda f: f.id)
847 self.assertEqual(issue_filter_list, expected_list)
848 self.assertEqual(response.status_code, 200)
850 def test_add_issue_filters_duplicates(self):
851 self.user = create_user_and_log_in(self.client, permissions=["change_issue", "add_issue"])
852 issue = baker.make(Issue)
853 f1 = baker.make(IssueFilter)
854 f2 = baker.make(IssueFilter)
855 issue.filters.add(f1)
856 issue.filters.add(f2)
857 issue_filter_list = sorted(list(issue.filters.all()), key=lambda f: f.id)
858 expected_list = sorted([f1, f2], key=lambda f: f.id)
859 self.assertEqual(issue_filter_list, expected_list)
860 response = self.client.post(
861 reverse(
862 'api:issue-add-filters',
863 kwargs={"pk": issue.id}),
864 data={"filters": [f1.id, f2.id]},
865 content_type="application/json",
866 )
867 issue_filter_list = sorted(list(issue.filters.all()), key=lambda f: f.id)
868 self.assertEqual(issue_filter_list, expected_list)
869 self.assertEqual(response.status_code, 200)
872class UnknownFailureViewSetTests(TestCase):
873 def test_retrieve_by_id(self):
874 unknown_failure = baker.make(UnknownFailure)
875 response = self.client.get(
876 reverse("api:unknownfailure-detail", kwargs={"pk": unknown_failure.id}), content_type="application/json"
877 )
878 self.assertEqual(response.status_code, 200)
879 data = response.json()
880 self.assertEqual(data["id"], unknown_failure.id)
881 self.assertEqual(data["test"], unknown_failure.result.test.name)
882 self.assertEqual(data["status"], unknown_failure.result.status.name)
883 self.assertRaises(KeyError, lambda: data["dmesg"])
884 self.assertRaises(KeyError, lambda: data["stdout"])
885 self.assertRaises(KeyError, lambda: data["stderr"])
886 self.assertEqual(data["testsuite"], unknown_failure.result.ts_run.testsuite.name)
887 self.assertEqual(data["runconfig"], {
888 "name": unknown_failure.result.ts_run.runconfig.name,
889 "tags": list(unknown_failure.result.ts_run.runconfig.tags.values_list('name', flat=True))
890 })
891 self.assertEqual(data["machine"], {
892 "name": unknown_failure.result.ts_run.machine.name,
893 "tags": list(unknown_failure.result.ts_run.machine.tags.values_list('name', flat=True))
894 })
896 def test_retrieve_by_id__extra_fields(self):
897 unknown_failure = baker.make(UnknownFailure)
898 response = self.client.get(
899 reverse("api:unknownfailure-detail", kwargs={"pk": unknown_failure.id}),
900 content_type="application/json",
901 data={"extra_fields": "stdout"},
902 )
903 self.assertEqual(response.status_code, 200)
904 data = response.json()
905 self.assertRaises(KeyError, lambda: data["dmesg"])
906 self.assertRaises(KeyError, lambda: data["stderr"])
907 self.assertEqual(data["stdout"], unknown_failure.result.stdout)
909 def test_list(self):
910 unknown_failures = baker.make(UnknownFailure, _quantity=3)
911 response = self.client.get(
912 reverse("api:unknownfailure-list"),
913 content_type="application/json",
914 )
915 data = response.json()
916 self.assertEqual(response.status_code, 200)
917 self.assertEqual(data["count"], 3)
918 for unknown_failure in unknown_failures:
919 self.assertIn(
920 {
921 "id": unknown_failure.id,
922 "test": unknown_failure.result.test.name,
923 "status": unknown_failure.result.status.name,
924 "testsuite": unknown_failure.result.ts_run.testsuite.name,
925 "runconfig": {
926 "name": unknown_failure.result.ts_run.runconfig.name,
927 "tags": list(unknown_failure.result.ts_run.runconfig.tags.values_list('name', flat=True))
928 },
929 "machine": {
930 "name": unknown_failure.result.ts_run.machine.name,
931 "tags": list(unknown_failure.result.ts_run.machine.tags.values_list('name', flat=True))
932 },
933 },
934 data["results"],
935 )
938class TestSetTests(TestCase):
939 def setUp(self):
940 self.arf = APIRequestFactory()
941 ts = TestSuite.objects.create(name="ts", description="", url="", public=True)
942 Test.objects.create(id=1, name="test1", testsuite=ts, public=True)
944 def test_vet_test_without_permission(self):
945 self.user = create_user_and_log_in(self.client, admin=False)
946 response = self.client.post(reverse('api:test-vet', kwargs={"pk": 1}))
947 self.assertEqual(response.status_code, 403)
949 @patch("django.utils.timezone.now")
950 def test_vet_test(self, now_mocked):
951 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc)
952 now_mocked.return_value = date
953 test = baker.make(Test, vetted_on=None)
954 self.user = create_user_and_log_in(self.client, admin=True)
955 response = self.client.post(reverse('api:test-vet', kwargs={"pk": test.pk}))
956 test.refresh_from_db()
957 self.assertEqual(response.status_code, 200)
958 self.assertEqual(test.vetted_on, date)
960 def test_vet_already_vetted(self):
961 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc)
962 test = baker.make(Test, vetted_on=date)
963 self.user = create_user_and_log_in(self.client, admin=True)
964 response = self.client.post(reverse('api:test-vet', kwargs={"pk": test.pk}))
965 test.refresh_from_db()
966 self.assertEqual(response.status_code, 200)
967 self.assertEqual(test.vetted_on, date)
969 def test_suppress_test_without_permission(self):
970 self.user = create_user_and_log_in(self.client, admin=False)
971 response = self.client.post(reverse('api:test-suppress', kwargs={"pk": 1}))
972 self.assertEqual(response.status_code, 403)
974 def test_suppress_test(self):
975 test = baker.make(Test, vetted_on=datetime.datetime(2021, 1, 1))
976 self.user = create_user_and_log_in(self.client, admin=True)
977 response = self.client.post(reverse('api:test-suppress', kwargs={"pk": test.pk}))
978 test.refresh_from_db()
979 self.assertEqual(response.status_code, 200)
980 self.assertIsNone(test.vetted_on)
983class TextStatusViewSetTests(TestCase):
984 def setUp(self):
985 self.arf = APIRequestFactory()
986 self.user = create_user_and_log_in(self.client, admin=True)
988 @patch("django.utils.timezone.now")
989 def test_vet(self, now_mocked):
990 date = datetime.datetime(2021, 1, 1, tzinfo=datetime.timezone.utc)
991 now_mocked.return_value = date
992 ts = baker.make(TextStatus, vetted_on=None)
993 response = self.client.get(reverse('api:textstatus-vet', kwargs={"pk": ts.pk}))
994 ts.refresh_from_db()
995 self.assertEqual(response.status_code, 200)
996 self.assertEqual(ts.vetted_on, date)
998 def test_suppress(self):
999 ts = baker.make(TextStatus, vetted_on="2021-01-01")
1000 response = self.client.get(reverse('api:textstatus-suppress', kwargs={"pk": ts.pk}))
1001 ts.refresh_from_db()
1002 self.assertEqual(response.status_code, 200)
1003 self.assertIsNone(ts.vetted_on)
1006class metrics_passrate_trend_viewTests(TestCase):
1007 def setUp(self):
1008 self.arf = APIRequestFactory()
1010 def test_basic(self):
1011 # TODO: Add a fixture that would return useful data here
1012 response = self.client.get(reverse('api-metrics-passrate-per-runconfig'),
1013 {'query': ""}, format='json')
1014 self.assertEqual(response.status_code, 200)
1017class metrics_passrate_viewTests(TestCase):
1018 def setUp(self):
1019 self.arf = APIRequestFactory()
1021 def test_basic(self):
1022 # TODO: Add a fixture that would return useful data here
1023 response = self.client.get(reverse('api-metrics-passrate-per-test'),
1024 {'query': ""}, format='json')
1025 self.assertEqual(response.status_code, 200)