Skip to content

Commit 865df9e

Browse files
committed
add missing unit test for full coverage
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 112037b commit 865df9e

6 files changed

Lines changed: 280 additions & 1 deletion

File tree

product_portfolio/admin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
from product_portfolio.models import ProductRelationStatus
7373
from product_portfolio.models import ProductStatus
7474
from reporting.filters import ReportingQueryListFilter
75+
from vulnerabilities.triage.models import ProductTriageRuleset
7576

7677

7778
@admin.register(ProductStatus, site=dejacode_site)
@@ -240,7 +241,7 @@ def save_model(self, request, obj, form, change):
240241
if "_saveasnew" in request.POST:
241242
old_product_id = request.resolver_match.kwargs.get("object_id")
242243
old_product = self.get_object(request, old_product_id)
243-
for model_class in [ProductComponent, ProductPackage]:
244+
for model_class in [ProductComponent, ProductPackage, ProductTriageRuleset]:
244245
for relationship in model_class.objects.filter(product=old_product):
245246
relationship.id = None
246247
relationship.uuid = uuid.uuid4()

product_portfolio/tests/test_admin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
from product_portfolio.models import ProductComponent
2626
from product_portfolio.models import ProductPackage
2727
from product_portfolio.tests import make_product_dependency
28+
from vulnerabilities.triage.models import ProductTriageRuleset
29+
from vulnerabilities.triage.models import TriageRuleset
2830

2931

3032
class ProductPortfolioAdminsTestCase(TestCase):
@@ -430,6 +432,12 @@ def test_product_admin_changeform_save_as_includes_relationships(self):
430432
ProductPackage.objects.create(
431433
product=self.product1, package=self.package1, dataspace=self.dataspace
432434
)
435+
ruleset = TriageRuleset.objects.create(
436+
name="Upgrade Ruleset", precedence=100, dataspace=self.dataspace
437+
)
438+
ProductTriageRuleset.objects.create(
439+
product=self.product1, ruleset=ruleset, dataspace=self.dataspace
440+
)
433441

434442
url = self.product1.get_admin_url()
435443
data = {
@@ -447,6 +455,7 @@ def test_product_admin_changeform_save_as_includes_relationships(self):
447455
new_product = Product.unsecured_objects.get(name=self.product1.name, version="new version")
448456
self.assertEqual(1, new_product.productcomponents.count())
449457
self.assertEqual(1, new_product.productpackages.count())
458+
self.assertEqual(1, new_product.product_triage_rulesets.count())
450459

451460
def test_codebaseresource_admin_changeform_product_prefill_on_save_addanother(self):
452461
self.client.login(username=self.user.username, password="secret")

product_portfolio/tests/test_filters.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
from product_portfolio.tests import make_product_component
2727
from product_portfolio.tests import make_product_package
2828
from vulnerabilities.tests import make_vulnerability
29+
from vulnerabilities.triage.engine import evaluate_ruleset
30+
from vulnerabilities.triage.models import ProductTriageRuleset
31+
from vulnerabilities.triage.models import TriageAction
32+
from vulnerabilities.triage.models import TriageRuleset
2933

3034

3135
class ProductPackageFilterSetTestCase(TestCase):
@@ -187,6 +191,47 @@ def test_filter_by_vulnerability_rule_returns_distinct_results(self):
187191
self.assertEqual(1, filterset.qs.count())
188192

189193

194+
class ProductPackageFilterByTriageActionTestCase(TestCase):
195+
def setUp(self):
196+
self.dataspace = Dataspace.objects.create(name="nexB")
197+
self.product = make_product(self.dataspace)
198+
self.pp_with_recommendation = make_product_package(
199+
self.product, make_package(self.dataspace)
200+
)
201+
self.pp_without_recommendation = make_product_package(
202+
self.product, make_package(self.dataspace)
203+
)
204+
make_vulnerability(
205+
self.dataspace, affecting=self.pp_with_recommendation.package, risk_score=9.0
206+
)
207+
ruleset = TriageRuleset.objects.create(
208+
name="Upgrade Ruleset",
209+
action=TriageAction.UPGRADE,
210+
precedence=100,
211+
dataspace=self.dataspace,
212+
rules_config={"risk_score": {"is_active": True, "min_risk_score": 8.0}},
213+
)
214+
ProductTriageRuleset.objects.create(
215+
product=self.product, ruleset=ruleset, dataspace=self.dataspace
216+
)
217+
evaluate_ruleset(ruleset, self.product)
218+
219+
def test_filter_by_triage_action_filters_matching_packages(self):
220+
filterset = ProductPackageFilterSet(
221+
dataspace=self.dataspace,
222+
data={"triage_action": "upgrade"},
223+
)
224+
self.assertIn(self.pp_with_recommendation, filterset.qs)
225+
self.assertNotIn(self.pp_without_recommendation, filterset.qs)
226+
227+
def test_filter_by_triage_action_excludes_non_matching_action(self):
228+
filterset = ProductPackageFilterSet(
229+
dataspace=self.dataspace,
230+
data={"triage_action": "notify"},
231+
)
232+
self.assertNotIn(self.pp_with_recommendation, filterset.qs)
233+
234+
190235
class ProductComponentFilterByRuleTestCase(TestCase):
191236
def setUp(self):
192237
self.dataspace = Dataspace.objects.create(name="nexB")

product_portfolio/tests/test_views.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
from vulnerabilities.models import VulnerabilityAnalysis
6565
from vulnerabilities.tests import make_vulnerability
6666
from vulnerabilities.tests import make_vulnerability_analysis
67+
from vulnerabilities.triage.models import AnalysisPreset
6768
from vulnerabilities.triage.models import ProductTriageRuleset
6869
from vulnerabilities.triage.models import TriageAction
6970
from vulnerabilities.triage.models import TriageRuleset
@@ -1994,6 +1995,12 @@ def test_product_portfolio_product_update_save_as_new(self):
19941995
CodebaseResource.objects.create(
19951996
path="/path1/", product=self.product1, dataspace=self.dataspace
19961997
)
1998+
ruleset = TriageRuleset.objects.create(
1999+
name="Upgrade Ruleset", precedence=100, dataspace=self.dataspace
2000+
)
2001+
ProductTriageRuleset.objects.create(
2002+
product=self.product1, ruleset=ruleset, dataspace=self.dataspace
2003+
)
19972004
initial_product_count = Product.objects.get_queryset(self.super_user).count()
19982005

19992006
data = {
@@ -2015,6 +2022,7 @@ def test_product_portfolio_product_update_save_as_new(self):
20152022
self.assertEqual(1, cloned_product.productcomponents.count())
20162023
self.assertEqual(1, cloned_product.productpackages.count())
20172024
self.assertEqual(1, cloned_product.codebaseresources.count())
2025+
self.assertEqual(1, cloned_product.product_triage_rulesets.count())
20182026

20192027
def test_product_portfolio_product_delete_view(self):
20202028
delete_url = self.product1.get_delete_url()
@@ -3423,6 +3431,54 @@ def test_product_portfolio_vulnerability_analysis_form_view(self):
34233431
self.assertEqual(vulnerability1, analysis.vulnerability)
34243432
self.assertEqual("resolved", analysis.state)
34253433

3434+
def test_product_portfolio_apply_analysis_preset_view(self):
3435+
self.client.login(username=self.super_user.username, password="secret")
3436+
3437+
package1 = make_package(self.dataspace)
3438+
vulnerability1 = make_vulnerability(self.dataspace, affecting=[package1])
3439+
product1 = make_product(self.dataspace, inventory=[package1])
3440+
product_package = ProductPackage.objects.get(product=product1, package=package1)
3441+
preset = AnalysisPreset.objects.create(
3442+
name="Preset1",
3443+
state="not_affected",
3444+
detail="Not deployed",
3445+
dataspace=self.dataspace,
3446+
)
3447+
3448+
url = reverse(
3449+
"product_portfolio:apply_analysis_preset",
3450+
args=[product_package.uuid, preset.pk, vulnerability1.advisory_uid],
3451+
)
3452+
response = self.client.post(url)
3453+
self.assertEqual(200, response.status_code)
3454+
self.assertEqual(b'{"success": "applied"}', response.content)
3455+
analysis = VulnerabilityAnalysis.objects.get()
3456+
self.assertEqual(product_package, analysis.product_package)
3457+
self.assertEqual(vulnerability1, analysis.vulnerability)
3458+
self.assertEqual("not_affected", analysis.state)
3459+
self.assertEqual(preset, analysis.applied_by_preset)
3460+
3461+
def test_product_portfolio_apply_analysis_preset_view_existing_analysis(self):
3462+
self.client.login(username=self.super_user.username, password="secret")
3463+
3464+
package1 = make_package(self.dataspace)
3465+
vulnerability1 = make_vulnerability(self.dataspace, affecting=[package1])
3466+
product1 = make_product(self.dataspace, inventory=[package1])
3467+
product_package = ProductPackage.objects.get(product=product1, package=package1)
3468+
make_vulnerability_analysis(product_package, vulnerability1, state="exploitable")
3469+
preset = AnalysisPreset.objects.create(
3470+
name="Preset1", state="not_affected", dataspace=self.dataspace
3471+
)
3472+
3473+
url = reverse(
3474+
"product_portfolio:apply_analysis_preset",
3475+
args=[product_package.uuid, preset.pk, vulnerability1.advisory_uid],
3476+
)
3477+
response = self.client.post(url)
3478+
self.assertEqual(400, response.status_code)
3479+
self.assertEqual(1, VulnerabilityAnalysis.objects.count())
3480+
self.assertEqual("exploitable", VulnerabilityAnalysis.objects.get().state)
3481+
34263482
def test_product_portfolio_tab_compliance_view_empty(self):
34273483
self.client.login(username="nexb_user", password="secret")
34283484
url = self.product1.get_url("tab_compliance")
@@ -4295,6 +4351,85 @@ def test_post_with_change_perm_evaluates_and_returns_hx_refresh(self, mock_evalu
42954351
mock_evaluate.assert_called_once_with(self.product1)
42964352

42974353

4354+
class ManageTriageRulesetsViewTestCase(TestCase):
4355+
def setUp(self):
4356+
self.dataspace = Dataspace.objects.create(name="nexB")
4357+
self.super_user = create_superuser("nexb_user", self.dataspace)
4358+
self.basic_user = create_user("basic_user", self.dataspace)
4359+
self.product1 = Product.objects.create(
4360+
name="Product1", version="1.0", dataspace=self.dataspace
4361+
)
4362+
self.ruleset = TriageRuleset.objects.create(
4363+
name="Upgrade Ruleset",
4364+
action=TriageAction.UPGRADE,
4365+
precedence=100,
4366+
dataspace=self.dataspace,
4367+
)
4368+
4369+
def test_get_without_login_redirects(self):
4370+
url = self.product1.get_manage_triage_rulesets_url()
4371+
response = self.client.get(url)
4372+
self.assertEqual(302, response.status_code)
4373+
4374+
def test_get_without_change_perm_returns_404(self):
4375+
self.client.login(username="basic_user", password="secret")
4376+
url = self.product1.get_manage_triage_rulesets_url()
4377+
response = self.client.get(url)
4378+
self.assertEqual(404, response.status_code)
4379+
4380+
def test_get_with_change_perm_renders_available_rulesets(self):
4381+
self.client.login(username="nexb_user", password="secret")
4382+
url = self.product1.get_manage_triage_rulesets_url()
4383+
response = self.client.get(url)
4384+
self.assertEqual(200, response.status_code)
4385+
self.assertContains(response, self.ruleset.name)
4386+
self.assertNotContains(response, "checked")
4387+
4388+
def test_get_marks_the_assigned_rulesets_as_checked(self):
4389+
ProductTriageRuleset.objects.create(
4390+
product=self.product1, ruleset=self.ruleset, dataspace=self.dataspace
4391+
)
4392+
self.client.login(username="nexb_user", password="secret")
4393+
url = self.product1.get_manage_triage_rulesets_url()
4394+
response = self.client.get(url)
4395+
self.assertContains(response, "checked")
4396+
4397+
def test_post_without_change_perm_returns_404(self):
4398+
self.client.login(username="basic_user", password="secret")
4399+
url = self.product1.get_manage_triage_rulesets_url()
4400+
response = self.client.post(url)
4401+
self.assertEqual(404, response.status_code)
4402+
4403+
@patch("product_portfolio.views.evaluate_ruleset")
4404+
def test_post_assigns_the_submitted_rulesets(self, mock_evaluate):
4405+
self.client.login(username="nexb_user", password="secret")
4406+
url = self.product1.get_manage_triage_rulesets_url()
4407+
response = self.client.post(url, {"ruleset_uuids": [str(self.ruleset.uuid)]})
4408+
self.assertEqual(b'{"success": true}', response.content)
4409+
self.assertTrue(
4410+
ProductTriageRuleset.objects.filter(
4411+
product=self.product1, ruleset=self.ruleset
4412+
).exists()
4413+
)
4414+
mock_evaluate.assert_called_once_with(ruleset=self.ruleset, product=self.product1)
4415+
4416+
@patch("product_portfolio.views.evaluate_ruleset")
4417+
def test_post_unassigns_the_deselected_rulesets(self, mock_evaluate):
4418+
ProductTriageRuleset.objects.create(
4419+
product=self.product1, ruleset=self.ruleset, dataspace=self.dataspace
4420+
)
4421+
self.client.login(username="nexb_user", password="secret")
4422+
url = self.product1.get_manage_triage_rulesets_url()
4423+
response = self.client.post(url, {"ruleset_uuids": []})
4424+
self.assertEqual(b'{"success": true}', response.content)
4425+
self.assertFalse(
4426+
ProductTriageRuleset.objects.filter(
4427+
product=self.product1, ruleset=self.ruleset
4428+
).exists()
4429+
)
4430+
mock_evaluate.assert_not_called()
4431+
4432+
42984433
class TabCompliancePolicyContextTestCase(TestCase):
42994434
def setUp(self):
43004435
self.dataspace = Dataspace.objects.create(name="nexB")

vulnerabilities/tests/test_forms.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from product_portfolio.tests import make_product_package
1717
from vulnerabilities.forms import VulnerabilityAnalysisForm
1818
from vulnerabilities.tests import make_vulnerability
19+
from vulnerabilities.triage.models import AnalysisPreset
1920

2021

2122
class VulnerabilitiesFormsTestCase(TestCase):
@@ -49,6 +50,31 @@ def test_vulnerability_forms_vulnerability_analysis_save(self):
4950
self.assertEqual(product_package1.package, analysis.package)
5051
self.assertEqual(data["detail"], analysis.detail)
5152

53+
def test_vulnerability_forms_vulnerability_analysis_save_clears_applied_by_preset(self):
54+
product_package1 = make_product_package(make_product(self.dataspace))
55+
vulnerability1 = make_vulnerability(
56+
dataspace=self.dataspace, affecting=[product_package1.package]
57+
)
58+
preset = AnalysisPreset.objects.create(
59+
name="Preset1", state="not_affected", dataspace=self.dataspace
60+
)
61+
analysis = product_package1.vulnerability_analyses.create(
62+
vulnerability=vulnerability1,
63+
dataspace=self.dataspace,
64+
state="not_affected",
65+
applied_by_preset=preset,
66+
)
67+
68+
data = {
69+
"product_package": product_package1,
70+
"vulnerability": vulnerability1,
71+
"detail": "Human edit",
72+
}
73+
form = VulnerabilityAnalysisForm(user=self.super_user, data=data, instance=analysis)
74+
self.assertTrue(form.is_valid())
75+
saved_analysis = form.save()
76+
self.assertIsNone(saved_analysis.applied_by_preset)
77+
5278
def test_vulnerability_forms_vulnerability_analysis_propagate_to_products(self):
5379
product_package1 = make_product_package(make_product(self.dataspace))
5480
vulnerability1 = make_vulnerability(
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# DejaCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: AGPL-3.0-only
5+
# See https://github.com/aboutcode-org/dejacode for support or download.
6+
# See https://aboutcode.org for more information about AboutCode FOSS projects.
7+
#
8+
9+
from django.test import TestCase
10+
11+
from dje.models import Dataspace
12+
from vulnerabilities.triage.forms import TriageRulesetForm
13+
from vulnerabilities.triage.models import TriageRuleset
14+
15+
16+
class TriageRulesetFormTestCase(TestCase):
17+
def setUp(self):
18+
self.dataspace = Dataspace.objects.create(name="nexB")
19+
20+
def test_adds_a_dynamic_field_pair_for_every_registered_rule(self):
21+
form = TriageRulesetForm(instance=TriageRuleset(dataspace=self.dataspace))
22+
self.assertIn("rule_risk_score_enabled", form.fields)
23+
self.assertIn("rule_risk_score_min_risk_score", form.fields)
24+
self.assertIn("rule_exploited_vulnerability_enabled", form.fields)
25+
self.assertNotIn("rule_exploited_vulnerability_min_risk_score", form.fields)
26+
27+
def test_save_builds_rules_config_from_the_submitted_rule_fields(self):
28+
data = {
29+
"name": "My Ruleset",
30+
"precedence": 100,
31+
"rule_risk_score_enabled": "on",
32+
"rule_risk_score_min_risk_score": "7.5",
33+
"rule_exploited_vulnerability_enabled": "on",
34+
}
35+
form = TriageRulesetForm(data=data, instance=TriageRuleset(dataspace=self.dataspace))
36+
self.assertTrue(form.is_valid(), form.errors)
37+
38+
ruleset = form.save()
39+
40+
self.assertEqual(
41+
{"is_active": True, "min_risk_score": 7.5}, ruleset.rules_config["risk_score"]
42+
)
43+
self.assertEqual({"is_active": True}, ruleset.rules_config["exploited_vulnerability"])
44+
45+
def test_save_drops_an_inactive_rule_that_has_no_parameters(self):
46+
data = {"name": "My Ruleset", "precedence": 100}
47+
form = TriageRulesetForm(data=data, instance=TriageRuleset(dataspace=self.dataspace))
48+
self.assertTrue(form.is_valid(), form.errors)
49+
50+
ruleset = form.save()
51+
52+
self.assertNotIn("exploited_vulnerability", ruleset.rules_config)
53+
54+
def test_save_keeps_an_inactive_rule_that_has_parameters_with_their_defaults(self):
55+
data = {"name": "My Ruleset", "precedence": 100}
56+
form = TriageRulesetForm(data=data, instance=TriageRuleset(dataspace=self.dataspace))
57+
self.assertTrue(form.is_valid(), form.errors)
58+
59+
ruleset = form.save()
60+
61+
self.assertEqual(
62+
{"is_active": False, "min_risk_score": 8.0}, ruleset.rules_config["risk_score"]
63+
)

0 commit comments

Comments
 (0)