Coverage for product_risk_suite/risk_assessment/tests.py: 99%
575 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 12:45 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 12:45 +0000
1from django.contrib import admin
2from django.test import TestCase
3from django.db.utils import IntegrityError
4from django.core.exceptions import ValidationError
6from .models import *
7from .admin import (
8 SecurityRequirementAdmin,
9 RiskAdmin, RiskRatingAdmin,
10)
12class FreeMethodTests(TestCase):
13 def test_truncate(self):
14 td = [
15 {"str": "Hello World", "len": 20, "expected": "Hello World"},
16 {"str": "Hello World", "len": 5, "expected": "He..."},
17 {"str": None, "len": 20, "expected": ""},
18 ]
20 for t in td:
21 self.assertEqual(truncate(t["str"], t["len"]), t["expected"])
23class AssetTest(TestCase):
24 test_name = "Operating System"
26 def test_simple(self):
27 exp = Asset.objects.create(name=self.test_name)
29 act = Asset.objects.get(name=self.test_name)
30 self.assertEqual(act, exp)
32 def test_unique(self):
33 exp = Asset.objects.create(name=self.test_name)
34 unique_check_ok = False
35 try:
36 item2 = Asset.objects.create(name=self.test_name)
37 except IntegrityError:
38 unique_check_ok = True
40 self.assertTrue(unique_check_ok)
42 def test_str(self):
43 exp = Asset.objects.create(name=self.test_name)
45 self.assertEqual(str(exp), self.test_name)
47class LifeCycleTest(TestCase):
48 test_name = "in use"
50 def test_simple(self):
51 exp = LifeCycle.objects.create(name=self.test_name)
53 act = LifeCycle.objects.get(name=self.test_name)
54 self.assertEqual(act, exp)
56 def test_unique(self):
57 exp = LifeCycle.objects.create(name=self.test_name)
58 unique_check_ok = False
59 try:
60 item2 = LifeCycle.objects.create(name=self.test_name)
61 except IntegrityError:
62 unique_check_ok = True
64 self.assertTrue(unique_check_ok)
66 def test_str(self):
67 exp = LifeCycle.objects.create(name=self.test_name)
69 self.assertEqual(str(exp), self.test_name)
71class OriginTest(TestCase):
72 test_name = "USB"
74 def test_simple(self):
75 exp = Origin.objects.create(name=self.test_name)
77 act = Origin.objects.get(name=self.test_name)
78 self.assertEqual(act, exp)
80 def test_unique(self):
81 exp = Origin.objects.create(name=self.test_name)
82 unique_check_ok = False
83 try:
84 item2 = Origin.objects.create(name=self.test_name)
85 except IntegrityError:
86 unique_check_ok = True
88 self.assertTrue(unique_check_ok)
90 def test_str(self):
91 exp = Origin.objects.create(name=self.test_name)
93 self.assertEqual(str(exp), self.test_name)
95class SecurityRequirementTest(TestCase):
96 def test_simple(self):
97 exp = SecurityRequirement.objects.create(norm_short="CRA", norm_long="CRA long", description_short="Part 1.1", description_long="details 1.1")
99 act = SecurityRequirement.objects.get(norm_short="CRA", description_short="Part 1.1")
100 self.assertEqual(act, exp)
102 def test_unique(self):
103 exp = SecurityRequirement.objects.create(norm_short="CRA", norm_long="CRA long", description_short="Part 1.1", description_long="details 1.1")
104 unique_check_ok = False
105 try:
106 item2 = SecurityRequirement.objects.create(norm_short="CRA", norm_long="CRA long", description_short="Part 1.1", description_long="details 1.1")
107 except IntegrityError:
108 unique_check_ok = True
110 self.assertTrue(unique_check_ok)
112 def test_str(self):
113 exp = SecurityRequirement.objects.create(norm_short="CRA", norm_long="CRA long", description_short="Part 1.1", description_long="details 1.1")
115 self.assertEqual(str(exp), f"CRA: Part 1.1")
117class StrideTest(TestCase):
118 def test_simple(self):
119 exp = Stride.objects.create(name="S")
121 act = Stride.objects.get(name="S")
122 self.assertEqual(act, exp)
124 def test_no_create_unallowed(self):
125 allowed_to_add = True
126 try:
127 unallowed = Stride.objects.create(name="F")
128 except ValidationError:
129 allowed_to_add = False
131 self.assertFalse(allowed_to_add)
133 def test_unique(self):
134 exp = Stride.objects.create(name="S")
135 unique_check_ok = False
136 try:
137 item2 = Stride.objects.create(name="S")
138 except ValidationError:
139 unique_check_ok = True
140 except IntegrityError:
141 unique_check_ok = True
143 self.assertTrue(unique_check_ok)
145 def test_str(self):
146 exp = Stride.objects.create(name="S")
147 self.assertEqual(str(exp), f"S")
148 text = Stride.STRIDE_CHOICES["S"]
149 self.assertEqual(text, "Spoofing")
151class RiskTest(TestCase):
152 def setUp(self):
153 ass = Asset.objects.create(name="OS")
154 lc = LifeCycle.objects.create(name="in use")
155 orig = Origin.objects.create(name="USB")
156 spo = Stride.objects.create(name="S")
157 risk = Risk(id=1,
158 custom_id="Test-risk-01",
159 asset=ass, origin=orig, life_cycle=lc,
160 title="Risk title", description="Risk description")
161 risk.stride.set([spo])
162 risk.save()
164 def test_simple(self):
165 act = Risk.objects.get(id=1)
166 self.assertEqual(act.custom_id, "Test-risk-01")
167 self.assertEqual(act.asset.name, "OS")
168 self.assertEqual(act.origin.name, "USB")
169 self.assertEqual(act.life_cycle.name, "in use")
170 self.assertEqual(act.title, "Risk title")
171 self.assertEqual(act.description, "Risk description")
172 self.assertEqual(len(act.stride.all()), 1)
173 for i in act.stride.all():
174 self.assertEqual(i.name, "S")
176 def test_stride_str(self):
177 act = Risk.objects.get(id=1)
178 stride_str = act.stride_str
179 self.assertEqual(stride_str, "S")
181 def test_list_strid(self):
182 act = Risk.objects.get(id=1)
183 list_stride = act.list_stride
184 self.assertEqual(list_stride, ["Spoofing"])
186class SeverityNameTest(TestCase):
187 def test_human_str(self):
188 data = [
189 { "value": SeverityName.Negligible, "str": "Negligible"},
190 { "value": SeverityName.Minor, "str": "Minor"},
191 { "value": SeverityName.Moderate, "str": "Moderate"},
192 { "value": SeverityName.Major, "str": "Major"},
193 { "value": SeverityName.Critical, "str": "Critical"},
194 { "value": -1, "str": "-1"},
195 { "value": 6, "str": "6"},
196 ]
198 for tc in data:
199 t = SeverityName.to_human_str(tc["value"])
200 self.assertEqual(t, tc["str"])
202class LikelihoodNameTest(TestCase):
203 def test_human_str(self):
204 data = [
205 { "value": LikelihoodName.VeryLow, "str": "Very low"},
206 { "value": LikelihoodName.Low, "str": "Low"},
207 { "value": LikelihoodName.Medium, "str": "Medium"},
208 { "value": LikelihoodName.High, "str": "High"},
209 { "value": LikelihoodName.VeryHigh, "str": "Very high"},
210 ]
212 for tc in data:
213 t = LikelihoodName.to_human_str(tc["value"])
214 self.assertEqual(t, tc["str"])
216class RiskRatingTest(TestCase):
217 def setUp(self):
218 rr = RiskRating.objects.create(likelihood_of_occurrence=2, severity_of_impact=4)
220 def test_risk(self):
221 act = RiskRating.objects.get(id=1)
222 self.assertEqual(act.risk, 8)
224 def test_risk_level(self):
225 td = [
226 {"in": 20, "expected": Risk5x5.HIGH},
228 {"in": 16, "expected": Risk5x5.HIGH},
229 {"in": 15, "expected": Risk5x5.HIGH},
230 {"in": 14, "expected": Risk5x5.MID},
232 {"in": 8, "expected": Risk5x5.MID},
233 {"in": 7, "expected": Risk5x5.MID},
234 {"in": 6, "expected": Risk5x5.LOW},
236 {"in": 1, "expected": Risk5x5.LOW},
237 ]
239 for tr in td:
240 self.assertEqual(RiskRating.risk_level(tr["in"]), tr["expected"])
242 def test_risk_level_to_color(self):
243 # Risk5x5
244 td = [
245 {"in": 3, "expected": "danger"},
246 {"in": Risk5x5.HIGH, "expected": "danger"},
247 {"in": Risk5x5.MID, "expected": "warning"},
248 {"in": Risk5x5.LOW, "expected": "success"},
249 {"in": -1, "expected": "danger"},
250 ]
251 for tr in td:
252 self.assertEqual(RiskRating.risk_level_to_color(tr["in"]), tr["expected"])
254 def test_color_class(self):
255 td = [
256 {"in": 0, "expected": "danger"},
257 {"in": 1, "expected": "success"},
258 {"in": 2, "expected": "success"},
259 {"in": 3, "expected": "warning"},
260 {"in": 4, "expected": "warning"},
261 {"in": 5, "expected": "danger"},
262 {"in": 6, "expected": "danger"},
263 ]
264 rr = RiskRating()
265 for tr in td:
266 self.assertEqual(rr.color_class(tr["in"]), tr["expected"])
268 def test_risk_color_class(self):
269 td = [
270 {"in": 20, "expected": "danger"},
272 {"in": 16, "expected": "danger"},
273 {"in": 15, "expected": "danger"},
274 {"in": 14, "expected": "warning"},
276 {"in": 8, "expected": "warning"},
277 {"in": 7, "expected": "warning"},
278 {"in": 6, "expected": "success"},
280 {"in": 1, "expected": "success"},
281 ]
283 rr = RiskRating()
284 for tr in td:
285 self.assertEqual(rr.risk_color_class(tr["in"]), tr["expected"])
287 def test_str(self):
288 act = RiskRating.objects.get(id=1)
289 self.assertEqual(act.likelihood_of_occurrence, 2)
290 self.assertEqual(act.severity_of_impact, 4)
291 self.assertEqual(str(act), "Low (2) * Major (4) = 8")
293 def test_color_props(self):
294 act = RiskRating.objects.get(id=1)
295 self.assertEqual(act.likelihood_of_occurrence, 2)
296 self.assertEqual(act.severity_of_impact, 4)
298 self.assertEqual(act.color_likelihood_of_occurrence, "success")
299 self.assertEqual(act.color_severity_of_impact, "warning")
300 self.assertEqual(act.color_risk, "warning")
302class StatusTest(TestCase):
303 def setUp(self):
304 Status.objects.create(id=0, status="Open")
305 Status.objects.create(id=1, status="On going")
306 Status.objects.create(id=2, status="Finished")
307 Status.objects.create(id=3, status="Rejected")
309 def test_str(self):
310 td = [
311 {"id": 0, "expected": "Open"},
312 {"id": 1, "expected": "On going"},
313 {"id": 2, "expected": "Finished"},
314 {"id": 3, "expected": "Rejected"},
315 ]
317 for tr in td:
318 stat = Status.objects.get(id=tr["id"])
319 self.assertEqual(str(stat), tr["expected"])
321 def test_is_todo(self):
322 td = [
323 {"id": 0, "expected": True},
324 {"id": 1, "expected": True},
325 {"id": 2, "expected": False},
326 {"id": 3, "expected": False},
327 ]
329 for tr in td:
330 stat = Status.objects.get(id=tr["id"])
331 self.assertEqual(stat.is_todo, tr["expected"])
333 def test_web_idx_name(self):
334 td = [
335 {"in": "Open", "expected": "open"},
336 {"in": "On going", "expected": "on_going"},
337 {"in": "Finished", "expected": "finished"},
338 {"in": "Rejected", "expected": "rejected"},
339 {"in": "foobar", "expected": "unknown"},
340 {"in": "", "expected": "unknown"},
341 ]
343 for tr in td:
344 self.assertEqual(Status.web_idx_name(tr["in"]), tr["expected"])
346 def test_invalid(self):
347 td = [
348 "", "Foobar"
349 ]
350 for tr in td:
351 failed_create = False
352 try:
353 Status.objects.create(status=tr)
354 except ValidationError:
355 failed_create = True
356 self.assertTrue(failed_create)
358class EvidenceTest(TestCase):
359 def setUp(self):
360 self.u = User.objects.create(username="testuser")
361 self.s0 = Status.objects.create(id=0, status="Open")
362 self.s1 = Status.objects.create(id=1, status="On going")
363 self.s2 = Status.objects.create(id=2, status="Finished")
364 self.s3 = Status.objects.create(id=3, status="Rejected")
365 Evidence.objects.create(id=0, due_date="2026-04-23", responsible=self.u, status=self.s0)
366 Evidence.objects.create(id=1, due_date="2026-04-23", responsible=self.u, status=self.s1, evidence="[REQ-001] the evidence req", evidence_link="https://example.com/requirement-001")
367 Evidence.objects.create(id=2, due_date="2026-04-23", responsible=self.u, status=self.s2, evidence="[REQ-001] the evidence req", evidence_link="https://example.com/requirement-001")
368 Evidence.objects.create(id=3, due_date="2026-04-23", responsible=self.u, status=self.s3, evidence="[REQ-001] the evidence req", evidence_link="https://example.com/requirement-001")
369 Evidence.objects.create(id=4, due_date="2026-04-23", responsible=self.u, status=self.s3)
371 def test_str(self):
372 td = [
373 {"evidence": Evidence.objects.get(id=0), "expected": "Open responsible: testuser due: 2026-04-23"},
374 {"evidence": Evidence.objects.get(id=1), "expected": "On going responsible: testuser due: 2026-04-23 evidence: [REQ-001] the evidence req"},
375 {"evidence": Evidence.objects.get(id=2), "expected": "Finished evidence: [REQ-001] the evidence req"},
376 {"evidence": Evidence.objects.get(id=3), "expected": "Rejected evidence: [REQ-001] the evidence req"},
377 {"evidence": Evidence.objects.get(id=4), "expected": "Rejected evidence: None"},
378 ]
379 for tr in td:
380 self.assertEqual(str(tr["evidence"]), tr["expected"])
382 def test_color_due_date(self):
383 yesterday = Evidence(due_date=(datetime.today() - timedelta(days=1)).date(), responsible=self.u, status=self.s0)
384 finished = Evidence(due_date=(datetime.today() - timedelta(days=1)).date(), responsible=self.u, status=self.s3)
385 in_a_week = Evidence(due_date=(datetime.today() + timedelta(weeks=1)).date(), responsible=self.u, status=self.s0)
386 in_5_weeks = Evidence(due_date=(datetime.today() + timedelta(weeks=5)).date(), responsible=self.u, status=self.s0)
388 self.assertEqual(yesterday.color_due_date, "danger")
389 self.assertEqual(finished.color_due_date, "success")
390 self.assertEqual(in_a_week.color_due_date, "danger")
391 self.assertEqual(in_5_weeks.color_due_date, "warning")
393class RiskMitigationTest(TestCase):
394 def setUp(self):
395 self.secReq1 = SecurityRequirement.objects.create(norm_short="CRA", norm_long="CRA long", description_short="Part 1.1", description_long="details 1.1", slug="cra-1.1")
396 self.secReq2 = SecurityRequirement.objects.create(norm_short="CRA", norm_long="CRA long", description_short="Part 1.2", description_long="details 1.2", slug="cra-1.2")
397 rm = RiskMitigation(id=0, mitigation="We fixed it")
398 rm.save()
399 rm.security_requirements.set([self.secReq1])
400 self.rm_no_rat = rm.save()
402 rm = RiskMitigation(id=1, mitigation="Extremely long mitigation that bores us and clutters the ui", rational="We fixed it in a very good manor")
403 rm.save()
404 rm.security_requirements.set([self.secReq1, self.secReq2])
405 self.rm_rat = rm.save()
407 rm = RiskMitigation(id=2, mitigation="Extremely long mitigation that bores us and clutters the ui", title="short catchy")
408 self.rm_tit = rm.save()
410 def test_str(self):
411 self.assertEqual(str(RiskMitigation.objects.get(id=0)), "We fixed it")
412 self.assertEqual(str(RiskMitigation.objects.get(id=1)), "Extremely long mitigation that bores us and clutters the ui")
413 self.assertEqual(str(RiskMitigation.objects.get(id=2)), "short catchy")
415 def test_list_sec_req(self):
416 rm0 = RiskMitigation.objects.get(id=0)
417 rm1 = RiskMitigation.objects.get(id=1)
419 secReqs = rm0.list_security_requirements
420 self.assertEqual(len(secReqs), 1)
421 self.assertEqual(secReqs, [
422 {"str": "CRA: Part 1.1", "slug": "cra-1.1"}
423 ])
425 secReqs = rm1.list_security_requirements
426 self.assertEqual(len(secReqs), 2)
427 self.assertEqual(secReqs, [
428 {"str": "CRA: Part 1.1", "slug": "cra-1.1"},
429 {"str": "CRA: Part 1.2", "slug": "cra-1.2"},
430 ])
432class SeverityExampleTest(TestCase):
433 def test_simple(self):
434 SeverityExample.objects.create(severity_of_impact=1, examples="Hello severity world")
436 act = SeverityExample.objects.get(severity_of_impact=1)
437 self.assertEqual(act.severity_of_impact, 1)
438 self.assertEqual(act.examples, "Hello severity world")
439 self.assertEqual(str(act), "Negligible - Examples")
441class LikelihoodExampleTest(TestCase):
442 def test_simple(self):
443 LikelihoodExample.objects.create(likelihood_of_occurrence=1, examples="Hello like world")
445 act = LikelihoodExample.objects.get(likelihood_of_occurrence=1)
446 self.assertEqual(act.likelihood_of_occurrence, 1)
447 self.assertEqual(act.examples, "Hello like world")
449class SuggestedMitigationValidationTest(TestCase):
450 def test_simple(self):
451 td = [
452 {"id": 1, "mit": "Verification of signature before installation", "vali": "<ol><li>Modify update file and verify detection</li><li>Ensure only signed files are installed</li></ol>", "expected_str": "Verification of signature bef... - <ol><li>Modify update file an..."},
453 {"id": 2, "mit": None, "vali": "<ol><li>Modify update file and verify detection</li><li>Ensure only signed files are installed</li></ol>", "expected_str": " - <ol><li>Modify update file an..."},
454 {"id": 3, "mit": "Verification of signature before installation", "vali": None, "expected_str": "Verification of signature bef... - "},
455 ]
457 for t in td:
458 SuggestedMitigationValidation.objects.create(id=t["id"], suggested_mitigation=t["mit"], suggested_validation=t["vali"])
460 act = SuggestedMitigationValidation.objects.get(id=t["id"])
461 self.assertEqual(act.id, t["id"])
462 self.assertEqual(act.suggested_mitigation, t["mit"])
463 self.assertEqual(act.suggested_validation, t["vali"])
464 self.assertEqual(str(act), t["expected_str"])
467# --- Admin tests ---
469class RiskAssessmentAdminChangelistTest(TestCase):
470 """Smoke tests: every registered admin changelist returns HTTP 200."""
472 def setUp(self):
473 superuser = User.objects.create_superuser(username="admin", password="admin123")
474 self.client.force_login(superuser)
476 def _get(self, model_name):
477 return self.client.get(f'/admin/risk_assessment/{model_name}/')
479 def test_asset_changelist(self):
480 self.assertEqual(self._get('asset').status_code, 200)
482 def test_origin_changelist(self):
483 self.assertEqual(self._get('origin').status_code, 200)
485 def test_lifecycle_changelist(self):
486 self.assertEqual(self._get('lifecycle').status_code, 200)
488 def test_securityrequirement_changelist(self):
489 self.assertEqual(self._get('securityrequirement').status_code, 200)
491 def test_risk_changelist(self):
492 self.assertEqual(self._get('risk').status_code, 200)
494 def test_riskmitigation_changelist(self):
495 self.assertEqual(self._get('riskmitigation').status_code, 200)
497 def test_riskrating_changelist(self):
498 self.assertEqual(self._get('riskrating').status_code, 200)
500 def test_evidence_changelist(self):
501 self.assertEqual(self._get('evidence').status_code, 200)
503 def test_severityexample_changelist(self):
504 self.assertEqual(self._get('severityexample').status_code, 200)
506 def test_likelihoodexample_changelist(self):
507 self.assertEqual(self._get('likelihoodexample').status_code, 200)
509 def test_suggestedmitigationvalidation_changelist(self):
510 self.assertEqual(self._get('suggestedmitigationvalidation').status_code, 200)
513class SecurityRequirementAdminTest(TestCase):
514 def setUp(self):
515 self.sr = SecurityRequirement.objects.create(
516 norm_short="CRA", norm_long="Cyber Resilience Act",
517 description_short="Part 1.1", description_long="Details about Part 1.1",
518 slug="cra-1.1",
519 )
520 self.admin = SecurityRequirementAdmin(SecurityRequirement, admin.site)
522 def test_norm_display(self):
523 self.assertEqual(self.admin.norm(self.sr), "CRA")
525 def test_short_display(self):
526 self.assertEqual(self.admin.short(self.sr), "Part 1.1")
528 def test_description_display(self):
529 self.assertEqual(self.admin.description(self.sr), "Details about Part 1.1")
532class RiskAdminTest(TestCase):
533 def setUp(self):
534 asset = Asset.objects.create(name="OS")
535 origin = Origin.objects.create(name="USB")
536 lc = LifeCycle.objects.create(name="in use")
537 self.stride_s = Stride.objects.create(name="S")
538 self.stride_t = Stride.objects.create(name="T")
539 self.admin = RiskAdmin(Risk, admin.site)
541 risk = Risk(custom_id="R-001", asset=asset, origin=origin, life_cycle=lc, title="Test risk", description="d")
542 risk.save()
543 risk.stride.set([self.stride_s])
544 risk.save()
545 self.risk_single = risk
547 risk2 = Risk(custom_id="R-002", asset=asset, origin=origin, life_cycle=lc, title="Multi stride risk", description="d")
548 risk2.save()
549 risk2.stride.set([self.stride_s, self.stride_t])
550 risk2.save()
551 self.risk_multi = risk2
553 def test_stride_str_display_single(self):
554 self.assertEqual(self.admin.stride_str(self.risk_single), "S")
556 def test_stride_str_display_multiple(self):
557 result = self.admin.stride_str(self.risk_multi)
558 self.assertIn("S", result)
559 self.assertIn("T", result)
562class RiskRatingAdminTest(TestCase):
563 def setUp(self):
564 self.rr = RiskRating.objects.create(likelihood_of_occurrence=2, severity_of_impact=4)
565 self.admin = RiskRatingAdmin(RiskRating, admin.site)
567 def test_risk_str_display(self):
568 self.assertEqual(self.admin.risk_str(self.rr), str(self.rr))
569 self.assertEqual(self.admin.risk_str(self.rr), "Low (2) * Major (4) = 8")
572# ---------------------------------------------------------------------------
573# View tests
574# ---------------------------------------------------------------------------
576class SecurityRequirementsViewTest(TestCase):
577 def setUp(self):
578 self.user = User.objects.create_user(username="viewer", password="pw")
579 self.sr1 = SecurityRequirement.objects.create(
580 norm_short="CRA", norm_long="CRA long",
581 description_short="Part 1.1", description_long="Details 1.1", slug="cra-1-1",
582 )
583 self.sr1_sum = {"description_short": "Part 1.1", "description_long": "Details 1.1"}
584 self.sr2 = SecurityRequirement.objects.create(
585 norm_short="CRA", norm_long="CRA long",
586 description_short="Part 1.2", description_long="Details 1.2", slug="cra-1-2",
587 )
588 self.sr2_sum = {"description_short": "Part 1.2", "description_long": "Details 1.2"}
590 def test_unauthenticated_redirects_to_login(self):
591 response = self.client.get('/security-requirements/')
592 self.assertRedirects(response, '/login?next=/security-requirements/', fetch_redirect_response=False)
594 def test_authenticated_returns_200(self):
595 self.client.force_login(self.user)
596 self.assertEqual(self.client.get('/security-requirements/').status_code, 200)
598 def test_context_contains_all_requirements(self):
599 self.client.force_login(self.user)
600 response = self.client.get('/security-requirements/')
601 req_list = dict(response.context['security_req_map'])
602 self.assertIn(self.sr1.norm_long, req_list)
603 self.assertEqual(len(req_list[self.sr1.norm_long]), 2)
604 self.assertIn(self.sr1_sum, req_list[self.sr1.norm_long])
605 self.assertIn(self.sr2_sum, req_list[self.sr1.norm_long])
607 def test_context_empty_when_no_requirements_exist(self):
608 SecurityRequirement.objects.all().delete()
609 self.client.force_login(self.user)
610 response = self.client.get('/security-requirements/')
611 self.assertEqual(dict(response.context['security_req_map']), {})
614class SecurityRequirementViewTest(TestCase):
615 def setUp(self):
616 self.user = User.objects.create_user(username="viewer", password="pw")
617 self.sr = SecurityRequirement.objects.create(
618 norm_short="CRA", norm_long="CRA long",
619 description_short="Part 1.1", description_long="Details 1.1", slug="cra-1-1",
620 )
622 def test_unauthenticated_redirects_to_login(self):
623 response = self.client.get('/security-requirements/cra-1-1/')
624 self.assertRedirects(response, '/login?next=/security-requirements/cra-1-1/', fetch_redirect_response=False)
626 def test_authenticated_returns_200(self):
627 self.client.force_login(self.user)
628 self.assertEqual(self.client.get('/security-requirements/cra-1-1/').status_code, 200)
630 def test_nonexistent_slug_returns_404(self):
631 self.client.force_login(self.user)
632 self.assertEqual(self.client.get('/security-requirements/does-not-exist/').status_code, 404)
634 def test_context_contains_correct_requirement(self):
635 self.client.force_login(self.user)
636 response = self.client.get('/security-requirements/cra-1-1/')
637 self.assertEqual(response.context['req'], self.sr)
640class RiskInfoViewTest(TestCase):
641 def setUp(self):
642 self.user = User.objects.create_user(username="viewer", password="pw")
643 SeverityExample.objects.create(severity_of_impact=1, examples="Minor damage only")
644 SeverityExample.objects.create(severity_of_impact=5, examples="Catastrophic damage")
645 LikelihoodExample.objects.create(likelihood_of_occurrence=1, examples="Very rare")
646 LikelihoodExample.objects.create(likelihood_of_occurrence=5, examples="Almost certain")
648 def test_unauthenticated_redirects_to_login(self):
649 response = self.client.get('/help/risk-info/')
650 self.assertRedirects(response, '/login?next=/help/risk-info/', fetch_redirect_response=False)
652 def test_authenticated_returns_200(self):
653 self.client.force_login(self.user)
654 self.assertEqual(self.client.get('/help/risk-info/').status_code, 200)
656 def test_context_severities_has_five_entries(self):
657 self.client.force_login(self.user)
658 response = self.client.get('/help/risk-info/')
659 self.assertEqual(len(response.context['severities']), 5)
661 def test_context_severities_have_str_and_value_keys(self):
662 self.client.force_login(self.user)
663 response = self.client.get('/help/risk-info/')
664 for entry in response.context['severities']:
665 self.assertIn('str', entry)
666 self.assertIn('value', entry)
668 def test_context_loos_has_five_entries(self):
669 self.client.force_login(self.user)
670 response = self.client.get('/help/risk-info/')
671 self.assertEqual(len(response.context['loos']), 5)
673 def test_context_risk_is_complete_5x5_matrix(self):
674 self.client.force_login(self.user)
675 response = self.client.get('/help/risk-info/')
676 risk = response.context['risk']
677 self.assertEqual(len(risk), 5)
678 for row in risk.values():
679 self.assertEqual(len(row), 5)
681 def test_context_risk_corner_values(self):
682 self.client.force_login(self.user)
683 response = self.client.get('/help/risk-info/')
684 risk = response.context['risk']
685 self.assertEqual(risk['1']['1'], RiskRating.calc_risk(1, 1))
686 self.assertEqual(risk['5']['5'], RiskRating.calc_risk(5, 5))
688 def test_context_risk_color_is_complete_5x5_matrix(self):
689 self.client.force_login(self.user)
690 response = self.client.get('/help/risk-info/')
691 risk_color = response.context['risk_color']
692 self.assertEqual(len(risk_color), 5)
693 for row in risk_color.values():
694 self.assertEqual(len(row), 5)
696 def test_context_example_severities_populated(self):
697 self.client.force_login(self.user)
698 response = self.client.get('/help/risk-info/')
699 self.assertEqual(response.context['example_severities']['1'], "Minor damage only")
700 self.assertEqual(response.context['example_severities']['5'], "Catastrophic damage")
702 def test_context_example_loos_populated(self):
703 self.client.force_login(self.user)
704 response = self.client.get('/help/risk-info/')
705 self.assertEqual(response.context['example_loos']['1'], "Very rare")
706 self.assertEqual(response.context['example_loos']['5'], "Almost certain")
708 def test_context_example_severities_empty_without_data(self):
709 SeverityExample.objects.all().delete()
710 self.client.force_login(self.user)
711 response = self.client.get('/help/risk-info/')
712 self.assertEqual(response.context['example_severities'], {})
714 def test_context_example_loos_empty_without_data(self):
715 LikelihoodExample.objects.all().delete()
716 self.client.force_login(self.user)
717 response = self.client.get('/help/risk-info/')
718 self.assertEqual(response.context['example_loos'], {})
721class SuggestionViewTest(TestCase):
722 def setUp(self):
723 self.user = User.objects.create_user(username="viewer", password="pw")
724 self.sug = SuggestedMitigationValidation.objects.create(
725 id=1,
726 suggested_mitigation="Use signed firmware",
727 suggested_validation="<ol><li>Check signature</li></ol>",
728 )
730 def test_unauthenticated_redirects_to_login(self):
731 response = self.client.get('/suggestion/1')
732 self.assertRedirects(response, '/login?next=/suggestion/1', fetch_redirect_response=False)
734 def test_authenticated_returns_200(self):
735 self.client.force_login(self.user)
736 self.assertEqual(self.client.get('/suggestion/1').status_code, 200)
738 def test_nonexistent_id_returns_404(self):
739 self.client.force_login(self.user)
740 self.assertEqual(self.client.get('/suggestion/9999').status_code, 404)
742 def test_context_contains_correct_suggestion(self):
743 self.client.force_login(self.user)
744 response = self.client.get('/suggestion/1')
745 self.assertEqual(response.context['sug'], self.sug)
748# ---------------------------------------------------------------------------
749# Risk versioning — model
750# ---------------------------------------------------------------------------
752class RiskVersioningModelTest(TestCase):
753 def setUp(self):
754 asset = Asset.objects.create(name="VER-Asset")
755 origin = Origin.objects.create(name="VER-Origin")
756 lc = LifeCycle.objects.create(name="VER-LC")
757 self.stride = Stride.objects.create(name="S")
758 self.kwargs = dict(asset=asset, origin=origin, life_cycle=lc,
759 title="Title", description="Desc")
761 def test_default_version_is_1(self):
762 risk = Risk.objects.create(custom_id="R-VER-001", **self.kwargs)
763 self.assertEqual(risk.version, 1)
765 def test_default_is_current_is_true(self):
766 risk = Risk.objects.create(custom_id="R-VER-001", **self.kwargs)
767 self.assertTrue(risk.is_current)
769 def test_has_newer_version_false_when_current(self):
770 risk = Risk.objects.create(custom_id="R-VER-001", **self.kwargs)
771 self.assertFalse(risk.has_newer_version)
773 def test_has_newer_version_true_when_not_current(self):
774 risk = Risk.objects.create(custom_id="R-VER-001", is_current=False, **self.kwargs)
775 self.assertTrue(risk.has_newer_version)
777 def test_str_includes_version_number(self):
778 risk = Risk.objects.create(custom_id="R-VER-001", version=3, **self.kwargs)
779 self.assertIn("v3", str(risk))
781 def test_str_includes_custom_id(self):
782 risk = Risk.objects.create(custom_id="R-VER-001", **self.kwargs)
783 self.assertIn("R-VER-001", str(risk))
785 def test_same_custom_id_different_versions_allowed(self):
786 Risk.objects.create(custom_id="R-VER-001", version=1, **self.kwargs)
787 risk_v2 = Risk.objects.create(custom_id="R-VER-001", version=2, **self.kwargs)
788 self.assertEqual(risk_v2.version, 2)
790 def test_duplicate_custom_id_and_version_raises_integrity_error(self):
791 Risk.objects.create(custom_id="R-VER-001", version=1, **self.kwargs)
792 constraint_violated = False
793 try:
794 Risk.objects.create(custom_id="R-VER-001", version=1, **self.kwargs)
795 except IntegrityError:
796 constraint_violated = True
797 self.assertTrue(constraint_violated)
800# ---------------------------------------------------------------------------
801# Risk versioning — admin form: save as new version
802# ---------------------------------------------------------------------------
804class RiskSaveAsNewVersionFormTest(TestCase):
805 def setUp(self):
806 self.superuser = User.objects.create_superuser(username="admin", password="pw")
807 self.client.force_login(self.superuser)
809 self.asset = Asset.objects.create(name="FrmAsset")
810 self.origin = Origin.objects.create(name="FrmOrigin")
811 self.lc = LifeCycle.objects.create(name="FrmLC")
812 self.stride = Stride.objects.create(name="S")
814 self.risk = Risk(custom_id="R-FRM-001", asset=self.asset, origin=self.origin,
815 life_cycle=self.lc, title="Original", description="Original desc")
816 self.risk.save()
817 self.risk.stride.set([self.stride])
818 self.risk.save()
820 def _post(self, extra=None):
821 data = {
822 'custom_id': 'R-FRM-001',
823 'asset': self.asset.pk,
824 'origin': self.origin.pk,
825 'life_cycle': self.lc.pk,
826 'stride': [self.stride.pk],
827 'title': 'Updated title',
828 'description': 'Updated desc',
829 '_save': 'Save',
830 }
831 if extra:
832 data.update(extra)
833 return self.client.post(
834 f'/admin/risk_assessment/risk/{self.risk.pk}/change/',
835 data,
836 )
838 # --- without checkbox: in-place update ---
840 def test_no_checkbox_updates_title_in_place(self):
841 self._post()
842 self.risk.refresh_from_db()
843 self.assertEqual(self.risk.title, 'Updated title')
845 def test_no_checkbox_does_not_create_new_version(self):
846 self._post()
847 self.assertFalse(Risk.objects.filter(custom_id='R-FRM-001', version=2).exists())
849 def test_no_checkbox_keeps_risk_current(self):
850 self._post()
851 self.risk.refresh_from_db()
852 self.assertTrue(self.risk.is_current)
854 # --- with checkbox: new version ---
856 def test_checkbox_creates_version_2(self):
857 self._post({'create_new_version': True})
858 self.assertTrue(Risk.objects.filter(custom_id='R-FRM-001', version=2).exists())
860 def test_checkbox_new_version_has_updated_data(self):
861 self._post({'create_new_version': True})
862 new = Risk.objects.get(custom_id='R-FRM-001', version=2)
863 self.assertEqual(new.title, 'Updated title')
864 self.assertEqual(new.description, 'Updated desc')
866 def test_checkbox_new_version_is_current(self):
867 self._post({'create_new_version': True})
868 new = Risk.objects.get(custom_id='R-FRM-001', version=2)
869 self.assertTrue(new.is_current)
871 def test_checkbox_old_version_becomes_not_current(self):
872 self._post({'create_new_version': True})
873 self.risk.refresh_from_db()
874 self.assertFalse(self.risk.is_current)
876 def test_checkbox_old_version_data_is_unchanged(self):
877 self._post({'create_new_version': True})
878 self.risk.refresh_from_db()
879 self.assertEqual(self.risk.title, 'Original')
880 self.assertEqual(self.risk.description, 'Original desc')
882 def test_checkbox_copies_stride_to_new_version(self):
883 self._post({'create_new_version': True})
884 new = Risk.objects.get(custom_id='R-FRM-001', version=2)
885 self.assertIn(self.stride, new.stride.all())
887 def test_checkbox_redirects_to_new_version_change_form(self):
888 response = self._post({'create_new_version': True})
889 new = Risk.objects.get(custom_id='R-FRM-001', version=2)
890 self.assertRedirects(
891 response,
892 f'/admin/risk_assessment/risk/{new.pk}/change/',
893 fetch_redirect_response=False,
894 )