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
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-06 08:12 +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 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")
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)
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".'])
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.']})
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)
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()
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"])
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)
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")
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."])
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)
510class BugViewSetTests(TestCase):
511 def setUp(self):
512 self.arf = APIRequestFactory()
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)
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)))
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)
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)
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)
553class ShortenerViewSetTests(TestCase):
554 def setUp(self):
555 self.arf = APIRequestFactory()
557 @classmethod
558 def setUpTestData(cls):
559 cls.short1 = Shortener.get_or_create("Some sort of long query!")
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={})
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")
570 def test_create_single(self):
571 full = 'Some sort of long query, but different!'
573 response = self.client.post(reverse('api:shortener-list'), data={'full': full}, content_type="application/json")
574 self.assertEqual(response.status_code, 200)
576 data = response.json()
577 self.assertNotEqual(data['id'], self.short1.id)
578 self.assertEqual(data['full'], full)
580 def test_create_multiple(self):
581 fulls = ["query1", "query2"]
583 response = self.client.post(reverse('api:shortener-list'), data={'full': fulls},
584 content_type="application/json")
585 self.assertEqual(response.status_code, 200)
587 data = response.json()
588 self.assertEqual(data[0]['full'], fulls[0])
589 self.assertEqual(data[1]['full'], fulls[1])
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)
596 data = response.json()
597 self.assertEqual(data['id'], self.short1.id)
600class MachineViewSetTests(APITestCase):
601 def setUp(self):
602 self.arf = APIRequestFactory()
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 )
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': []}])
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)
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 })
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 })
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.']})
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)
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)
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)
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)
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)
709class IssueViewSetTests(TestCase):
710 def setUp(self):
711 self.arf = APIRequestFactory()
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"})
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)
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"})
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"})
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)
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"})
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)
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."])
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)
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 })
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)
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 )
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)
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)
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)
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)
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)
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)
886class TextStatusViewSetTests(TestCase):
887 def setUp(self):
888 self.arf = APIRequestFactory()
889 self.user = create_user_and_log_in(self.client, admin=True)
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)
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)
909class metrics_passrate_trend_viewTests(TestCase):
910 def setUp(self):
911 self.arf = APIRequestFactory()
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)
920class metrics_passrate_viewTests(TestCase):
921 def setUp(self):
922 self.arf = APIRequestFactory()
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)