@@ -932,4 +932,105 @@ def test_change_vulnerability_ids_on_reimport(self):
932932 # Verify only new Vulnerability_Id objects exist
933933 vuln_ids = list (Vulnerability_Id .objects .filter (finding = finding ).values_list ("vulnerability_id" , flat = True ))
934934 self .assertEqual (set (new_vulnerability_ids ), set (vuln_ids ))
935- finding .delete ()
935+
936+
937+ class ReimportDuplicateReactivationTest (DojoTestCase ):
938+
939+ """
940+ Regression test for https://github.com/DefectDojo/django-DefectDojo/issues/14910
941+
942+ Reimport reactivation of a mitigated finding must not produce an invalid
943+ active/verified duplicate finding state.
944+ """
945+
946+ def setUp (self ):
947+ self .user , _ = User .objects .get_or_create (username = "admin" , is_superuser = True )
948+ Development_Environment .objects .get_or_create (name = "Development" )
949+ self .product_type , _ = Product_Type .objects .get_or_create (name = "dup_reactivation_pt" )
950+ self .product , _ = Product .objects .get_or_create (
951+ name = "DupReactivationProduct" ,
952+ description = "test product" ,
953+ prod_type = self .product_type ,
954+ )
955+ self .engagement = Engagement .objects .create (
956+ name = "Dup Reactivation Engagement" ,
957+ product = self .product ,
958+ target_start = timezone .now (),
959+ target_end = timezone .now (),
960+ )
961+ self .test = self .create_test (engagement = self .engagement , scan_type = NPM_AUDIT_SCAN_TYPE , title = "dup reactivation test" )
962+
963+ def _make_finding (self , title , ** kwargs ):
964+ return Finding .objects .create (
965+ title = title ,
966+ test = self .test ,
967+ severity = "High" ,
968+ reporter = self .user ,
969+ ** kwargs ,
970+ )
971+
972+ def test_reactivation_keeps_duplicate_inactive_and_unverified (self ):
973+ # Original active finding
974+ original = self ._make_finding ("original finding" , active = True , verified = True )
975+ # Mitigated finding that is marked as a duplicate of the original
976+ existing_duplicate = self ._make_finding (
977+ "duplicate finding" ,
978+ active = False ,
979+ verified = False ,
980+ duplicate = True ,
981+ duplicate_finding = original ,
982+ is_mitigated = True ,
983+ mitigated = timezone .now (),
984+ mitigated_by = self .user ,
985+ )
986+ # The reimported (unsaved) finding that re-matches the duplicate, and is active/not mitigated
987+ unsaved_finding = self ._make_finding ("duplicate finding incoming" , active = True , verified = True )
988+
989+ reimporter = DefaultReImporter (
990+ test = self .test ,
991+ user = self .user ,
992+ scan_type = NPM_AUDIT_SCAN_TYPE ,
993+ active = True ,
994+ verified = True ,
995+ do_not_reactivate = False ,
996+ )
997+
998+ result_finding , _ = reimporter .process_matched_mitigated_finding (unsaved_finding , existing_duplicate )
999+
1000+ result_finding .refresh_from_db ()
1001+ # The mitigation is cleared (the finding reappeared in the scan)...
1002+ self .assertFalse (result_finding .is_mitigated )
1003+ self .assertIsNone (result_finding .mitigated )
1004+ # ...but a duplicate must never become active or verified (issue #14910)
1005+ self .assertTrue (result_finding .duplicate )
1006+ self .assertFalse (result_finding .active )
1007+ self .assertFalse (result_finding .verified )
1008+
1009+ def test_reactivation_of_non_duplicate_still_activates (self ):
1010+ # A regular mitigated finding (not a duplicate) must still reactivate as before
1011+ existing = self ._make_finding (
1012+ "regular finding" ,
1013+ active = False ,
1014+ verified = False ,
1015+ is_mitigated = True ,
1016+ mitigated = timezone .now (),
1017+ mitigated_by = self .user ,
1018+ )
1019+ unsaved_finding = self ._make_finding ("regular finding incoming" , active = True , verified = True )
1020+
1021+ reimporter = DefaultReImporter (
1022+ test = self .test ,
1023+ user = self .user ,
1024+ scan_type = NPM_AUDIT_SCAN_TYPE ,
1025+ active = True ,
1026+ verified = True ,
1027+ do_not_reactivate = False ,
1028+ )
1029+
1030+ result_finding , _ = reimporter .process_matched_mitigated_finding (unsaved_finding , existing )
1031+
1032+ result_finding .refresh_from_db ()
1033+ self .assertFalse (result_finding .is_mitigated )
1034+ self .assertIsNone (result_finding .mitigated )
1035+ self .assertTrue (result_finding .active )
1036+ self .assertTrue (result_finding .verified )
0 commit comments