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

1from django.contrib import admin 

2from django.test import TestCase 

3from django.db.utils import IntegrityError 

4from django.core.exceptions import ValidationError 

5 

6from .models import * 

7from .admin import ( 

8 SecurityRequirementAdmin, 

9 RiskAdmin, RiskRatingAdmin, 

10) 

11 

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 ] 

19 

20 for t in td: 

21 self.assertEqual(truncate(t["str"], t["len"]), t["expected"]) 

22 

23class AssetTest(TestCase): 

24 test_name = "Operating System" 

25 

26 def test_simple(self): 

27 exp = Asset.objects.create(name=self.test_name) 

28 

29 act = Asset.objects.get(name=self.test_name) 

30 self.assertEqual(act, exp) 

31 

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 

39 

40 self.assertTrue(unique_check_ok) 

41 

42 def test_str(self): 

43 exp = Asset.objects.create(name=self.test_name) 

44 

45 self.assertEqual(str(exp), self.test_name) 

46 

47class LifeCycleTest(TestCase): 

48 test_name = "in use" 

49 

50 def test_simple(self): 

51 exp = LifeCycle.objects.create(name=self.test_name) 

52 

53 act = LifeCycle.objects.get(name=self.test_name) 

54 self.assertEqual(act, exp) 

55 

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 

63 

64 self.assertTrue(unique_check_ok) 

65 

66 def test_str(self): 

67 exp = LifeCycle.objects.create(name=self.test_name) 

68 

69 self.assertEqual(str(exp), self.test_name) 

70 

71class OriginTest(TestCase): 

72 test_name = "USB" 

73 

74 def test_simple(self): 

75 exp = Origin.objects.create(name=self.test_name) 

76 

77 act = Origin.objects.get(name=self.test_name) 

78 self.assertEqual(act, exp) 

79 

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 

87 

88 self.assertTrue(unique_check_ok) 

89 

90 def test_str(self): 

91 exp = Origin.objects.create(name=self.test_name) 

92 

93 self.assertEqual(str(exp), self.test_name) 

94 

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") 

98 

99 act = SecurityRequirement.objects.get(norm_short="CRA", description_short="Part 1.1") 

100 self.assertEqual(act, exp) 

101 

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 

109 

110 self.assertTrue(unique_check_ok) 

111 

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") 

114 

115 self.assertEqual(str(exp), f"CRA: Part 1.1") 

116 

117class StrideTest(TestCase): 

118 def test_simple(self): 

119 exp = Stride.objects.create(name="S") 

120 

121 act = Stride.objects.get(name="S") 

122 self.assertEqual(act, exp) 

123 

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 

130 

131 self.assertFalse(allowed_to_add) 

132 

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 

142 

143 self.assertTrue(unique_check_ok) 

144 

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") 

150 

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() 

163 

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") 

175 

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") 

180 

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"]) 

185 

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 ] 

197 

198 for tc in data: 

199 t = SeverityName.to_human_str(tc["value"]) 

200 self.assertEqual(t, tc["str"]) 

201 

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 ] 

211 

212 for tc in data: 

213 t = LikelihoodName.to_human_str(tc["value"]) 

214 self.assertEqual(t, tc["str"]) 

215 

216class RiskRatingTest(TestCase): 

217 def setUp(self): 

218 rr = RiskRating.objects.create(likelihood_of_occurrence=2, severity_of_impact=4) 

219 

220 def test_risk(self): 

221 act = RiskRating.objects.get(id=1) 

222 self.assertEqual(act.risk, 8) 

223 

224 def test_risk_level(self): 

225 td = [ 

226 {"in": 20, "expected": Risk5x5.HIGH}, 

227 

228 {"in": 16, "expected": Risk5x5.HIGH}, 

229 {"in": 15, "expected": Risk5x5.HIGH}, 

230 {"in": 14, "expected": Risk5x5.MID}, 

231 

232 {"in": 8, "expected": Risk5x5.MID}, 

233 {"in": 7, "expected": Risk5x5.MID}, 

234 {"in": 6, "expected": Risk5x5.LOW}, 

235 

236 {"in": 1, "expected": Risk5x5.LOW}, 

237 ] 

238 

239 for tr in td: 

240 self.assertEqual(RiskRating.risk_level(tr["in"]), tr["expected"]) 

241 

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"]) 

253 

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"]) 

267 

268 def test_risk_color_class(self): 

269 td = [ 

270 {"in": 20, "expected": "danger"}, 

271 

272 {"in": 16, "expected": "danger"}, 

273 {"in": 15, "expected": "danger"}, 

274 {"in": 14, "expected": "warning"}, 

275 

276 {"in": 8, "expected": "warning"}, 

277 {"in": 7, "expected": "warning"}, 

278 {"in": 6, "expected": "success"}, 

279 

280 {"in": 1, "expected": "success"}, 

281 ] 

282 

283 rr = RiskRating() 

284 for tr in td: 

285 self.assertEqual(rr.risk_color_class(tr["in"]), tr["expected"]) 

286 

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") 

292 

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) 

297 

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") 

301 

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") 

308 

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 ] 

316 

317 for tr in td: 

318 stat = Status.objects.get(id=tr["id"]) 

319 self.assertEqual(str(stat), tr["expected"]) 

320 

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 ] 

328 

329 for tr in td: 

330 stat = Status.objects.get(id=tr["id"]) 

331 self.assertEqual(stat.is_todo, tr["expected"]) 

332 

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 ] 

342 

343 for tr in td: 

344 self.assertEqual(Status.web_idx_name(tr["in"]), tr["expected"]) 

345 

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) 

357 

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) 

370 

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"]) 

381 

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) 

387 

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") 

392 

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() 

401 

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() 

406 

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() 

409 

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") 

414 

415 def test_list_sec_req(self): 

416 rm0 = RiskMitigation.objects.get(id=0) 

417 rm1 = RiskMitigation.objects.get(id=1) 

418 

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 ]) 

424 

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 ]) 

431 

432class SeverityExampleTest(TestCase): 

433 def test_simple(self): 

434 SeverityExample.objects.create(severity_of_impact=1, examples="Hello severity world") 

435 

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") 

440 

441class LikelihoodExampleTest(TestCase): 

442 def test_simple(self): 

443 LikelihoodExample.objects.create(likelihood_of_occurrence=1, examples="Hello like world") 

444 

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") 

448 

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 ] 

456 

457 for t in td: 

458 SuggestedMitigationValidation.objects.create(id=t["id"], suggested_mitigation=t["mit"], suggested_validation=t["vali"]) 

459 

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"]) 

465 

466 

467# --- Admin tests --- 

468 

469class RiskAssessmentAdminChangelistTest(TestCase): 

470 """Smoke tests: every registered admin changelist returns HTTP 200.""" 

471 

472 def setUp(self): 

473 superuser = User.objects.create_superuser(username="admin", password="admin123") 

474 self.client.force_login(superuser) 

475 

476 def _get(self, model_name): 

477 return self.client.get(f'/admin/risk_assessment/{model_name}/') 

478 

479 def test_asset_changelist(self): 

480 self.assertEqual(self._get('asset').status_code, 200) 

481 

482 def test_origin_changelist(self): 

483 self.assertEqual(self._get('origin').status_code, 200) 

484 

485 def test_lifecycle_changelist(self): 

486 self.assertEqual(self._get('lifecycle').status_code, 200) 

487 

488 def test_securityrequirement_changelist(self): 

489 self.assertEqual(self._get('securityrequirement').status_code, 200) 

490 

491 def test_risk_changelist(self): 

492 self.assertEqual(self._get('risk').status_code, 200) 

493 

494 def test_riskmitigation_changelist(self): 

495 self.assertEqual(self._get('riskmitigation').status_code, 200) 

496 

497 def test_riskrating_changelist(self): 

498 self.assertEqual(self._get('riskrating').status_code, 200) 

499 

500 def test_evidence_changelist(self): 

501 self.assertEqual(self._get('evidence').status_code, 200) 

502 

503 def test_severityexample_changelist(self): 

504 self.assertEqual(self._get('severityexample').status_code, 200) 

505 

506 def test_likelihoodexample_changelist(self): 

507 self.assertEqual(self._get('likelihoodexample').status_code, 200) 

508 

509 def test_suggestedmitigationvalidation_changelist(self): 

510 self.assertEqual(self._get('suggestedmitigationvalidation').status_code, 200) 

511 

512 

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) 

521 

522 def test_norm_display(self): 

523 self.assertEqual(self.admin.norm(self.sr), "CRA") 

524 

525 def test_short_display(self): 

526 self.assertEqual(self.admin.short(self.sr), "Part 1.1") 

527 

528 def test_description_display(self): 

529 self.assertEqual(self.admin.description(self.sr), "Details about Part 1.1") 

530 

531 

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) 

540 

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 

546 

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 

552 

553 def test_stride_str_display_single(self): 

554 self.assertEqual(self.admin.stride_str(self.risk_single), "S") 

555 

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) 

560 

561 

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) 

566 

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") 

570 

571 

572# --------------------------------------------------------------------------- 

573# View tests 

574# --------------------------------------------------------------------------- 

575 

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"} 

589 

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) 

593 

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) 

597 

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]) 

606 

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']), {}) 

612 

613 

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 ) 

621 

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) 

625 

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) 

629 

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) 

633 

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) 

638 

639 

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") 

647 

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) 

651 

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) 

655 

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) 

660 

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) 

667 

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) 

672 

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) 

680 

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)) 

687 

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) 

695 

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") 

701 

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") 

707 

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'], {}) 

713 

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'], {}) 

719 

720 

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 ) 

729 

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) 

733 

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) 

737 

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) 

741 

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) 

746 

747 

748# --------------------------------------------------------------------------- 

749# Risk versioning — model 

750# --------------------------------------------------------------------------- 

751 

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") 

760 

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) 

764 

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) 

768 

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) 

772 

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) 

776 

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)) 

780 

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)) 

784 

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) 

789 

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) 

798 

799 

800# --------------------------------------------------------------------------- 

801# Risk versioning — admin form: save as new version 

802# --------------------------------------------------------------------------- 

803 

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) 

808 

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") 

813 

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() 

819 

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 ) 

837 

838 # --- without checkbox: in-place update --- 

839 

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') 

844 

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()) 

848 

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) 

853 

854 # --- with checkbox: new version --- 

855 

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()) 

859 

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') 

865 

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) 

870 

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) 

875 

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') 

881 

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()) 

886 

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 )