Skip to content

Commit 37fbffb

Browse files
committed
Add unit tests and changelog entry #310
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 505767b commit 37fbffb

7 files changed

Lines changed: 86 additions & 17 deletions

File tree

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Release notes
22
=============
33

4+
### Version 5.3.1-dev (unreleased)
5+
6+
- Add new `is_locked` "Locked inventory" field to the ProductStatus model.
7+
When a Product is locked through his status, its inventory cannot be modified.
8+
https://github.com/aboutcode-org/dejacode/issues/189
9+
410
### Version 5.3.0
511

612
- Rename ProductDependency is_resolved to is_pinned.

dejacode/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import git
1616

17-
VERSION = "5.3.0"
17+
VERSION = "5.3.1-dev"
1818

1919
PROJECT_DIR = Path(__file__).resolve().parent
2020
ROOT_DIR = PROJECT_DIR.parent

product_portfolio/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ def actions_on_status_change(self):
368368
def is_locked(self):
369369
if self.configuration_status_id:
370370
return self.configuration_status.is_locked
371+
return False
371372

372373
@cached_property
373374
def all_packages(self):

product_portfolio/tests/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from product_portfolio.models import ProductDependency
1919
from product_portfolio.models import ProductItemPurpose
2020
from product_portfolio.models import ProductPackage
21+
from product_portfolio.models import ProductStatus
2122

2223

2324
def make_product(dataspace, inventory=None, **data):
@@ -71,9 +72,24 @@ def make_product_component(product, component=None):
7172

7273

7374
def make_product_item_purpose(dataspace, **data):
75+
if "label" not in data:
76+
data["label"] = make_string(10)
77+
if "text" not in data:
78+
data["text"] = make_string(10)
79+
7480
return ProductItemPurpose.objects.create(
75-
label=make_string(10),
76-
text=make_string(10),
81+
dataspace=dataspace,
82+
**data,
83+
)
84+
85+
86+
def make_product_status(dataspace, **data):
87+
if "label" not in data:
88+
data["label"] = make_string(10)
89+
if "text" not in data:
90+
data["text"] = make_string(10)
91+
92+
return ProductStatus.objects.create(
7793
dataspace=dataspace,
7894
**data,
7995
)

product_portfolio/tests/test_models.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@
3737
from product_portfolio.models import ProductPackage
3838
from product_portfolio.models import ProductRelationStatus
3939
from product_portfolio.models import ProductSecuredManager
40-
from product_portfolio.models import ProductStatus
4140
from product_portfolio.models import ScanCodeProject
4241
from product_portfolio.tests import make_product
4342
from product_portfolio.tests import make_product_dependency
4443
from product_portfolio.tests import make_product_item_purpose
4544
from product_portfolio.tests import make_product_package
45+
from product_portfolio.tests import make_product_status
4646
from vulnerabilities.tests import make_vulnerability
4747
from workflow.models import RequestTemplate
4848

@@ -69,19 +69,15 @@ def setUp(self):
6969
)
7070

7171
def test_product_model_default_status_on_product_addition(self):
72-
status1 = ProductStatus.objects.create(
73-
label="S1", text="Status1", default_on_addition=True, dataspace=self.dataspace
74-
)
75-
status2 = ProductStatus.objects.create(label="S2", text="Status2", dataspace=self.dataspace)
72+
status1 = make_product_status(default_on_addition=True, dataspace=self.dataspace)
73+
status2 = make_product_status(dataspace=self.dataspace)
7674

7775
# No status given at creation time, the default is set
78-
p1 = Product.objects.create(name="P1", dataspace=self.dataspace)
76+
p1 = make_product(dataspace=self.dataspace, name="P1")
7977
self.assertEqual(status1, p1.configuration_status)
8078

8179
# A status is given at creation time, no default is set
82-
p2 = Product.objects.create(
83-
name="P2", configuration_status=status2, dataspace=self.dataspace
84-
)
80+
p2 = make_product(dataspace=self.dataspace, name="P2", configuration_status=status2)
8581
self.assertEqual(status2, p2.configuration_status)
8682

8783
def test_product_model_get_attribution_url(self):
@@ -105,6 +101,19 @@ def test_product_model_secured_manager(self):
105101
self.assertEqual(1, Product.objects.get_queryset(self.super_user).count())
106102
self.assertIn(self.product1, Product.objects.get_queryset(self.super_user))
107103

104+
def test_product_model_secured_manager_get_queryset_exclude_locked(self):
105+
qs = Product.objects.get_queryset(self.super_user)
106+
self.assertIn(self.product1, qs)
107+
qs = Product.objects.get_queryset(self.super_user, exclude_locked=True)
108+
self.assertIn(self.product1, qs)
109+
110+
locked_status = make_product_status(self.dataspace, is_locked=True)
111+
self.product1.update(configuration_status=locked_status)
112+
qs = Product.objects.get_queryset(self.super_user)
113+
self.assertIn(self.product1, qs)
114+
qs = Product.objects.get_queryset(self.super_user, exclude_locked=True)
115+
self.assertNotIn(self.product1, qs)
116+
108117
def test_product_model_is_active(self):
109118
qs = Product.objects.get_queryset(self.super_user)
110119
self.assertIn(self.product1, qs)
@@ -490,14 +499,12 @@ def test_product_model_actions_on_status_change(self):
490499
dataspace=self.super_user.dataspace,
491500
content_type=product_ct,
492501
)
493-
494-
status1 = ProductStatus.objects.create(
502+
status1 = make_product_status(
503+
self.dataspace,
495504
label="status1",
496505
text="status1",
497506
request_to_generate=request_template1,
498-
dataspace=self.dataspace,
499507
)
500-
501508
product.configuration_status = status1
502509
product.last_modified_by = self.super_user
503510
self.assertTrue(product.has_changed("configuration_status_id"))
@@ -509,6 +516,19 @@ def test_product_model_actions_on_status_change(self):
509516
product.refresh_from_db()
510517
self.assertEqual(1, product.request_count)
511518

519+
def test_product_model_is_locked_property(self):
520+
product = make_product(self.dataspace)
521+
self.assertFalse(product.is_locked)
522+
523+
status1 = make_product_status(self.dataspace, is_locked=False)
524+
product.update(configuration_status=status1)
525+
product = Product.unsecured_objects.get(pk=product.pk)
526+
self.assertFalse(product.is_locked)
527+
528+
status1.update(is_locked=True)
529+
product = Product.unsecured_objects.get(pk=product.pk)
530+
self.assertTrue(product.is_locked)
531+
512532
@mock.patch("component_catalog.models.Package.update_from_purldb")
513533
def test_product_model_improve_packages_from_purldb(self, mock_update_from_purldb):
514534
mock_update_from_purldb.return_value = 1

product_portfolio/tests/test_views.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
from product_portfolio.tests import make_product
5555
from product_portfolio.tests import make_product_dependency
5656
from product_portfolio.tests import make_product_package
57+
from product_portfolio.tests import make_product_status
5758
from product_portfolio.views import ManageComponentGridView
5859
from vulnerabilities.models import VulnerabilityAnalysis
5960
from vulnerabilities.tests import make_vulnerability
@@ -1113,6 +1114,31 @@ def test_product_portfolio_detail_view_display_purldb_features(self):
11131114
self.assertContains(response, expected2)
11141115
self.assertContains(response, expected3)
11151116

1117+
def test_product_portfolio_detail_view_status_is_locked(self):
1118+
self.client.login(username=self.super_user.username, password="secret")
1119+
url = self.product1.get_absolute_url()
1120+
expected1 = (
1121+
'<button type="button" class="btn btn-outline-dark dropdown-toggle" disabled>'
1122+
'<i class="fas fa-tasks"></i> Manage'
1123+
"</button>"
1124+
)
1125+
expected2 = (
1126+
'<button type="button" class="btn btn-outline-dark dropdown-toggle" disabled>'
1127+
'<i class="fas fa-toolbox"></i> Actions'
1128+
"</button>"
1129+
)
1130+
expected3 = (
1131+
"This product version is marked as read-only, preventing any modifications "
1132+
"to its inventory."
1133+
)
1134+
1135+
locked_status = make_product_status(self.dataspace, is_locked=True)
1136+
self.product1.update(configuration_status=locked_status)
1137+
response = self.client.get(url)
1138+
self.assertContains(response, expected1, html=True)
1139+
self.assertContains(response, expected2, html=True)
1140+
self.assertContains(response, expected3, html=True)
1141+
11161142
def test_product_portfolio_list_view_secured_queryset(self):
11171143
self.client.login(username=self.basic_user.username, password="secret")
11181144
url = resolve_url("product_portfolio:product_list")

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = dejacode
3-
version = 5.3.0
3+
version = 5.3.1-dev
44
license = AGPL-3.0-only
55
description = Automate open source license compliance and ensure supply chain integrity
66
long_description = file:README.rst

0 commit comments

Comments
 (0)