|
13 | 13 | from django.utils import timezone |
14 | 14 |
|
15 | 15 | from dojo.finding.helper import prepare_duplicates_for_delete |
16 | | -from dojo.models import Engagement, Finding, Product, Product_Type, Test, Test_Type, User, UserContactInfo |
| 16 | +from dojo.models import ( |
| 17 | + Dojo_User, |
| 18 | + Engagement, |
| 19 | + Finding, |
| 20 | + Finding_Group, |
| 21 | + Product, |
| 22 | + Product_Type, |
| 23 | + Risk_Acceptance, |
| 24 | + Test, |
| 25 | + Test_Type, |
| 26 | + User, |
| 27 | + UserContactInfo, |
| 28 | +) |
17 | 29 |
|
18 | 30 | from .dojo_test_case import DojoTestCase |
19 | 31 |
|
@@ -409,3 +421,52 @@ def test_delete_product_with_tags(self): |
409 | 421 | self.assertEqual(product_shared_tag.count, 0) |
410 | 422 | finding_shared_tag.refresh_from_db() |
411 | 423 | self.assertEqual(finding_shared_tag.count, 0) |
| 424 | + |
| 425 | + def test_delete_product_with_reverse_m2m_relations(self): |
| 426 | + """ |
| 427 | + Deleting a product with findings that have reverse M2M relations succeeds. |
| 428 | +
|
| 429 | + Reverse M2M through tables (M2M fields on other models pointing to Finding) |
| 430 | + must be cleared before findings are deleted. This tests: |
| 431 | + - Finding_Group.findings (dojo_finding_group_findings) |
| 432 | + - Risk_Acceptance.accepted_findings (dojo_risk_acceptance_accepted_findings) |
| 433 | + """ |
| 434 | + from dojo.utils import async_delete # noqa: PLC0415 |
| 435 | + |
| 436 | + finding_a = self._create_finding(self.test1, "Grouped Finding A") |
| 437 | + finding_b = self._create_finding(self.test1, "Grouped Finding B") |
| 438 | + finding_c = self._create_finding(self.test1, "Risk Accepted Finding") |
| 439 | + |
| 440 | + # Finding_Group with findings |
| 441 | + creator = Dojo_User.objects.first() or Dojo_User.objects.create(username="testcreator") |
| 442 | + group = Finding_Group.objects.create( |
| 443 | + name="Test Group", |
| 444 | + test=self.test1, |
| 445 | + creator=creator, |
| 446 | + ) |
| 447 | + group.findings.add(finding_a, finding_b) |
| 448 | + |
| 449 | + # Risk_Acceptance with accepted findings |
| 450 | + ra = Risk_Acceptance.objects.create( |
| 451 | + name="Test RA", |
| 452 | + owner=self.testuser, |
| 453 | + ) |
| 454 | + ra.accepted_findings.add(finding_c) |
| 455 | + # Link to engagement so we can verify it survives |
| 456 | + self.engagement1.risk_acceptance.add(ra) |
| 457 | + |
| 458 | + product_id = self.product.id |
| 459 | + group_id = group.id |
| 460 | + ra_id = ra.id |
| 461 | + |
| 462 | + with impersonate(self.testuser): |
| 463 | + async_del = async_delete() |
| 464 | + async_del.delete(self.product) |
| 465 | + |
| 466 | + self.assertFalse(Product.objects.filter(id=product_id).exists()) |
| 467 | + self.assertFalse(Finding_Group.objects.filter(id=group_id).exists()) |
| 468 | + self.assertFalse(Finding.objects.filter(id__in=[finding_a.id, finding_b.id, finding_c.id]).exists()) |
| 469 | + # Risk_Acceptance itself survives (no FK to product/engagement), |
| 470 | + # but its accepted_findings M2M entries should be gone |
| 471 | + self.assertTrue(Risk_Acceptance.objects.filter(id=ra_id).exists()) |
| 472 | + self.assertEqual(Risk_Acceptance.objects.get(id=ra_id).accepted_findings.count(), 0) |
0 commit comments