88
99from unittest .mock import patch
1010
11+ from django .contrib .contenttypes .models import ContentType
1112from django .test import TestCase
1213
1314from component_catalog .tests import make_package
1415from dje .models import Dataspace
16+ from dje .tests import create_user
17+ from product_portfolio .models import Product
1518from product_portfolio .tests import make_product
1619from product_portfolio .tests import make_product_package
1720from vulnerabilities .models import VulnerabilityAnalysis
2629from vulnerabilities .triage .tests import make_analysis_preset
2730from vulnerabilities .triage .tests import make_product_triage_ruleset
2831from vulnerabilities .triage .tests import make_triage_ruleset
32+ from workflow .models import Request
33+ from workflow .models import RequestTemplate
2934
3035
3136class ReevaluateProductRulesetsTestCase (TestCase ):
@@ -120,6 +125,76 @@ def test_disabling_a_ruleset_deletes_its_triage_records_instead_of_evaluating(
120125 self .assertFalse (TriageRecord .objects .exists ())
121126 mock_evaluate .assert_not_called ()
122127
128+ @patch ("vulnerabilities.triage.signals.evaluate_ruleset" )
129+ def test_disabling_a_ruleset_keeps_records_that_have_an_open_request (self , mock_evaluate ):
130+ package = make_package (self .dataspace )
131+ make_product_package (self .product , package = package )
132+ vulnerability = make_vulnerability (self .dataspace , affecting = package )
133+ ruleset = make_triage_ruleset (self .dataspace , action = TriageAction .NOTIFY )
134+ make_product_triage_ruleset (self .product , ruleset = ruleset )
135+ requester = create_user ("requester" , self .dataspace )
136+ request_template = RequestTemplate .objects .create (
137+ name = "Template" ,
138+ description = "Header" ,
139+ dataspace = self .dataspace ,
140+ content_type = ContentType .objects .get_for_model (Product ),
141+ created_by = requester ,
142+ )
143+ request = request_template .create_request (
144+ requester = requester ,
145+ title = "Vulnerability request" ,
146+ product_context = self .product ,
147+ object_id = self .product .pk ,
148+ )
149+ record = TriageRecord .objects .create (
150+ vulnerability = vulnerability ,
151+ product = self .product ,
152+ ruleset = ruleset ,
153+ action = ruleset .action ,
154+ request = request ,
155+ dataspace = self .dataspace ,
156+ )
157+ mock_evaluate .reset_mock ()
158+
159+ ruleset .enabled = False
160+ ruleset .save ()
161+
162+ record .refresh_from_db ()
163+ self .assertEqual (request , record .request )
164+
165+ def test_disabling_then_reenabling_a_ruleset_reuses_the_existing_request (self ):
166+ # Regression: disabling then re-enabling a ruleset used to reopen a new Request
167+ # instead of reconnecting to the one already tracking this vulnerability.
168+ package = make_package (self .dataspace )
169+ make_product_package (self .product , package = package )
170+ make_vulnerability (self .dataspace , affecting = package , risk_score = 9.0 )
171+ requester = create_user ("requester" , self .dataspace )
172+ request_template = RequestTemplate .objects .create (
173+ name = "Template" ,
174+ description = "Header" ,
175+ dataspace = self .dataspace ,
176+ content_type = ContentType .objects .get_for_model (Product ),
177+ created_by = requester ,
178+ )
179+ ruleset = make_triage_ruleset (
180+ self .dataspace ,
181+ action = TriageAction .NOTIFY ,
182+ request_template = request_template ,
183+ rules_config = {"risk_score" : {"is_active" : True , "min_risk_score" : 8.0 }},
184+ )
185+ make_product_triage_ruleset (self .product , ruleset = ruleset )
186+ evaluate_ruleset (ruleset , self .product )
187+ original_request = TriageRecord .objects .get ().request
188+ self .assertIsNotNone (original_request )
189+
190+ ruleset .enabled = False
191+ ruleset .save ()
192+ ruleset .enabled = True
193+ ruleset .save ()
194+
195+ self .assertEqual (1 , Request .objects .count ())
196+ self .assertEqual (original_request , TriageRecord .objects .get ().request )
197+
123198
124199class DeleteTriageRecordsOnUnassignSignalTestCase (TestCase ):
125200 def setUp (self ):
@@ -143,6 +218,53 @@ def test_unassigning_the_ruleset_deletes_its_triage_records_for_the_product(self
143218 self .assignment .delete ()
144219 self .assertFalse (TriageRecord .objects .exists ())
145220
221+ def test_unassigning_keeps_a_record_that_has_an_open_request (self ):
222+ requester = create_user ("requester" , self .dataspace )
223+ request_template = RequestTemplate .objects .create (
224+ name = "Template" ,
225+ description = "Header" ,
226+ dataspace = self .dataspace ,
227+ content_type = ContentType .objects .get_for_model (Product ),
228+ created_by = requester ,
229+ )
230+ request = request_template .create_request (
231+ requester = requester ,
232+ title = "Vulnerability request" ,
233+ product_context = self .product ,
234+ object_id = self .product .pk ,
235+ )
236+ record = TriageRecord .objects .get ()
237+ record .request = request
238+ record .save ()
239+
240+ self .assignment .delete ()
241+
242+ record .refresh_from_db ()
243+ self .assertEqual (request , record .request )
244+
245+ def test_reassigning_the_ruleset_reuses_the_existing_request (self ):
246+ # Regression: unassigning then reassigning a ruleset used to reopen a new Request
247+ # instead of reconnecting to the one already tracking this vulnerability.
248+ requester = create_user ("requester" , self .dataspace )
249+ request_template = RequestTemplate .objects .create (
250+ name = "Template" ,
251+ description = "Header" ,
252+ dataspace = self .dataspace ,
253+ content_type = ContentType .objects .get_for_model (Product ),
254+ created_by = requester ,
255+ )
256+ self .ruleset .request_template = request_template
257+ self .ruleset .save ()
258+ original_request = TriageRecord .objects .get ().request
259+ self .assertIsNotNone (original_request )
260+
261+ self .assignment .delete ()
262+ make_product_triage_ruleset (self .product , ruleset = self .ruleset )
263+ evaluate_ruleset (self .ruleset , self .product )
264+
265+ self .assertEqual (1 , Request .objects .count ())
266+ self .assertEqual (original_request , TriageRecord .objects .get ().request )
267+
146268 def test_does_not_delete_records_belonging_to_another_product (self ):
147269 other_product = make_product (self .dataspace )
148270 make_product_package (other_product , package = self .package )
0 commit comments