Skip to content

Commit e3f3fbb

Browse files
authored
Prevent the creation of duplicated "resolved" dependencies during import #297 (#299)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 9f0a4bc commit e3f3fbb

File tree

6 files changed

+119
-11
lines changed

6 files changed

+119
-11
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,15 @@ Release notes
128128
- Add the ability to download Product "Imports" input file.
129129
https://github.com/aboutcode-org/dejacode/issues/156
130130

131-
- Fix a logic issue in the ImportPackageFromScanCodeIO.import_package that occurs when
131+
- Fix a logic issue in the ``ImportPackageFromScanCodeIO.import_package`` that occurs when
132132
multiple packages with the same PURL, but different download_url or filename,
133133
are present in the Dataspace.
134134
https://github.com/aboutcode-org/dejacode/issues/295
135135

136+
- Fix a logic issue in the ``ImportPackageFromScanCodeIO.import_dependencies`` to
137+
prevent the creation of duplicated "resolved" dependencies.
138+
https://github.com/aboutcode-org/dejacode/issues/297
139+
136140
### Version 5.2.1
137141

138142
- Fix the models documentation navigation.

dje/tasks.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,12 @@ def pull_project_data_from_scancodeio(scancodeproject_uuid):
218218
scancode_project.append_to_log(msg)
219219

220220
for object_type, values in existing.items():
221-
msg = (
222-
f"- {len(values)} {object_type}{pluralize(values)} already available in the Dataspace."
223-
)
221+
object_type_plural = f"{object_type}{pluralize(values)}"
222+
object_type_plural = object_type_plural.replace("dependencys", "dependencies")
223+
reason = "already available in the dataspace"
224+
if object_type == "dependency":
225+
reason = "already defined on the product"
226+
msg = f"- {len(values)} {object_type_plural} skipped: {reason}."
224227
scancode_project.append_to_log(msg)
225228

226229
for object_type, values in errors.items():

product_portfolio/importers.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -747,18 +747,30 @@ def import_package(self, package_data):
747747
def import_dependency(self, dependency_data):
748748
dependency_uid = dependency_data.get("dependency_uid")
749749

750-
dependency_qs = ProductDependency.objects.scope(self.user.dataspace)
751-
if dependency_qs.filter(product=self.product, dependency_uid=dependency_uid).exists():
750+
dependency_qs = self.product.dependencies.all()
751+
if dependency_qs.filter(dependency_uid=dependency_uid).exists():
752752
self.existing["dependency"].append(str(dependency_uid))
753753
return
754754

755+
for_package = None
756+
resolved_to_package = None
755757
dependency_data["product"] = self.product
756758
if for_package_uid := dependency_data.get("for_package_uid"):
757-
dependency_data["for_package"] = self.package_uid_mapping.get(for_package_uid)
759+
for_package = self.package_uid_mapping.get(for_package_uid)
760+
dependency_data["for_package"] = for_package
758761
if resolved_to_package_uid := dependency_data.get("resolved_to_package_uid"):
759-
dependency_data["resolved_to_package"] = self.package_uid_mapping.get(
760-
resolved_to_package_uid
762+
resolved_to_package = self.package_uid_mapping.get(resolved_to_package_uid)
763+
dependency_data["resolved_to_package"] = resolved_to_package
764+
765+
# Check if the dependency entry already exists in the product.
766+
if for_package and resolved_to_package:
767+
resolved_dependency_qs = dependency_qs.filter(
768+
for_package=for_package,
769+
resolved_to_package=resolved_to_package,
761770
)
771+
if resolved_dependency_qs.exists():
772+
self.existing["dependency"].append(str(dependency_uid))
773+
return
762774

763775
if purl := dependency_data.get("purl"):
764776
dependency_data["declared_dependency"] = purl

product_portfolio/templates/product_portfolio/tabs/tab_inventory.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
{{ filter_productcomponent.form.is_modified }}
7676
</th>
7777
{% if product.dataspace.enable_vulnerablecodedb_access %}
78-
<th style="width: 75;">
78+
<th style="width: 70px;">
7979
<span class="help_text" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="{{ help_texts.risk_score }}">
8080
{% trans 'Risk' %}
8181
</span>

product_portfolio/tests/test_importers.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,3 +1120,92 @@ def test_product_portfolio_import_packages_from_scio_importer_multiple_package_o
11201120
)
11211121
self.assertEqual({}, existing)
11221122
self.assertEqual({}, errors)
1123+
1124+
@mock.patch("dejacode_toolkit.scancodeio.ScanCodeIO.fetch_project_dependencies")
1125+
@mock.patch("dejacode_toolkit.scancodeio.ScanCodeIO.fetch_project_packages")
1126+
def test_product_portfolio_import_packages_from_scio_importer_duplicate_dependency(
1127+
self, mock_fetch_packages, mock_fetch_dependencies
1128+
):
1129+
for_package_purl = "pkg:pypi/package@1.0"
1130+
for_package_uid = "6eb9d787-2f2a-40f3-8815-fbf8f6c373de"
1131+
resolved_to_package_purl = "pkg:pypi/dep@0.5"
1132+
resolved_to_package_uid = "895de5cb-2d40-4532-ae46-56104cd6a1bf"
1133+
mock_fetch_packages.return_value = [
1134+
{
1135+
"type": "pypi",
1136+
"name": "package",
1137+
"version": "1.0",
1138+
"purl": for_package_purl,
1139+
"package_uid": for_package_uid,
1140+
},
1141+
{
1142+
"type": "pypi",
1143+
"name": "dep",
1144+
"version": "0.5",
1145+
"purl": resolved_to_package_purl,
1146+
"package_uid": resolved_to_package_uid,
1147+
},
1148+
]
1149+
1150+
dependency_uid = "12a4113b-99d2-455a-a96d-468ca29861d6"
1151+
mock_fetch_dependencies.return_value = [
1152+
{
1153+
"dependency_uid": dependency_uid,
1154+
"for_package_uid": for_package_uid,
1155+
"resolved_to_package_uid": resolved_to_package_uid,
1156+
}
1157+
]
1158+
1159+
importer = ImportPackageFromScanCodeIO(
1160+
user=self.super_user,
1161+
project_uuid=uuid.uuid4(),
1162+
product=self.product1,
1163+
)
1164+
created, existing, errors = importer.save()
1165+
expected = {
1166+
"package": ["pkg:pypi/package@1.0", "pkg:pypi/dep@0.5"],
1167+
"dependency": ["12a4113b-99d2-455a-a96d-468ca29861d6"],
1168+
}
1169+
self.assertEqual(expected, created)
1170+
self.assertEqual({}, existing)
1171+
self.assertEqual({}, errors)
1172+
self.assertEqual(2, self.product1.packages.count())
1173+
self.assertEqual(1, self.product1.dependencies.count())
1174+
1175+
# Re-run the importer and make sure no duplicate entries are created
1176+
importer = ImportPackageFromScanCodeIO(
1177+
user=self.super_user,
1178+
project_uuid=uuid.uuid4(),
1179+
product=self.product1,
1180+
)
1181+
created, existing, errors = importer.save()
1182+
self.assertEqual({}, created)
1183+
self.assertEqual(expected, existing)
1184+
self.assertEqual({}, errors)
1185+
self.assertEqual(2, self.product1.packages.count())
1186+
self.assertEqual(1, self.product1.dependencies.count())
1187+
1188+
# Change the dependency_uid to make sure the match happen on the FKs as well.
1189+
dependency_uid = "a46c682f-51a7-44a4-a00f-ccfc826befc6"
1190+
mock_fetch_dependencies.return_value = [
1191+
{
1192+
"dependency_uid": dependency_uid,
1193+
"for_package_uid": for_package_uid,
1194+
"resolved_to_package_uid": resolved_to_package_uid,
1195+
}
1196+
]
1197+
importer = ImportPackageFromScanCodeIO(
1198+
user=self.super_user,
1199+
project_uuid=uuid.uuid4(),
1200+
product=self.product1,
1201+
)
1202+
created, existing, errors = importer.save()
1203+
self.assertEqual({}, created)
1204+
expected = {
1205+
"package": ["pkg:pypi/package@1.0", "pkg:pypi/dep@0.5"],
1206+
"dependency": ["a46c682f-51a7-44a4-a00f-ccfc826befc6"],
1207+
}
1208+
self.assertEqual(expected, existing)
1209+
self.assertEqual({}, errors)
1210+
self.assertEqual(2, self.product1.packages.count())
1211+
self.assertEqual(1, self.product1.dependencies.count())

product_portfolio/tests/test_views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3283,7 +3283,7 @@ def test_product_portfolio_pull_project_data_from_scancodeio_task(self, mock_imp
32833283
self.assertEqual(ScanCodeProject.Status.SUCCESS, scancode_project.status)
32843284
expected = [
32853285
"- Imported 1 package.",
3286-
"- 1 package already available in the Dataspace.",
3286+
"- 1 package skipped: already available in the dataspace.",
32873287
"- 1 package error occurred during import.",
32883288
]
32893289
self.assertEqual(expected, scancode_project.import_log)

0 commit comments

Comments
 (0)