Skip to content

Commit 07a5af2

Browse files
committed
Add tests for purl creation and use AdvisoryV2
Signed-off-by: Sampurna Pyne <sampurnapyne1710@gmail.com>
1 parent bf3f321 commit 07a5af2

File tree

5 files changed

+58
-40
lines changed

5 files changed

+58
-40
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ toml==0.10.2
118118
tomli==2.0.1
119119
traitlets==5.1.1
120120
typing_extensions==4.1.1
121-
univers==30.12.0
121+
git+https://github.com/aboutcode-org/univers.git@5e28f2cd0fbee81a2b1268e88916a52a208bb672#egg=univers
122122
urllib3==1.26.19
123123
wcwidth==0.2.5
124124
websocket-client==0.59.0

vulnerabilities/importers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@
7878
from vulnerabilities.pipelines.v2_importers import redhat_importer as redhat_importer_v2
7979
from vulnerabilities.pipelines.v2_importers import retiredotnet_importer as retiredotnet_importer_v2
8080
from vulnerabilities.pipelines.v2_importers import ruby_importer as ruby_importer_v2
81-
from vulnerabilities.pipelines.v2_importers import tuxcare_importer as tuxcare_importer_v2
8281
from vulnerabilities.pipelines.v2_importers import suse_score_importer as suse_score_importer_v2
82+
from vulnerabilities.pipelines.v2_importers import tuxcare_importer as tuxcare_importer_v2
8383
from vulnerabilities.pipelines.v2_importers import ubuntu_osv_importer as ubuntu_osv_importer_v2
8484
from vulnerabilities.pipelines.v2_importers import vulnrichment_importer as vulnrichment_importer_v2
8585
from vulnerabilities.pipelines.v2_importers import xen_importer as xen_importer_v2

vulnerabilities/pipelines/v2_importers/tuxcare_importer.py

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414
from packageurl import PackageURL
1515
from pytz import UTC
1616
from univers.version_range import RANGE_CLASS_BY_SCHEMES
17-
from univers.version_range import AlpineLinuxVersionRange
1817

19-
from vulnerabilities.importer import AdvisoryData
18+
from vulnerabilities.importer import AdvisoryDataV2
2019
from vulnerabilities.importer import AffectedPackageV2
2120
from vulnerabilities.importer import VulnerabilitySeverity
2221
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2
@@ -28,13 +27,6 @@
2827
AFFECTED_STATUSES = ["Ignored", "Needs Triage", "In Testing", "In Progress", "In Rollout"]
2928
FIXED_STATUSES = ["Released", "Already Fixed"]
3029

31-
VERSION_RANGE_BY_PURL_TYPE = {
32-
"rpm": RANGE_CLASS_BY_SCHEMES["rpm"],
33-
"deb": RANGE_CLASS_BY_SCHEMES["deb"],
34-
"apk": AlpineLinuxVersionRange,
35-
"generic": RANGE_CLASS_BY_SCHEMES["generic"],
36-
}
37-
3830

3931
class TuxCareImporterPipeline(VulnerableCodeBaseImporterPipelineV2):
4032
pipeline_id = "tuxcare_importer_v2"
@@ -45,6 +37,7 @@ class TuxCareImporterPipeline(VulnerableCodeBaseImporterPipelineV2):
4537
def steps(cls):
4638
return (
4739
cls.fetch,
40+
cls.group_records_by_cve,
4841
cls.collect_and_store_advisories,
4942
)
5043

@@ -53,13 +46,12 @@ def fetch(self) -> None:
5346
self.log(f"Fetching `{url}`")
5447
response = fetch_response(url)
5548
self.response = response.json() if response else []
56-
self._grouped = self._group_records_by_cve()
5749

58-
def _group_records_by_cve(self) -> dict:
50+
def group_records_by_cve(self):
5951
"""
6052
A single CVE can appear in multiple records across different operating systems, distributions, or package versions. This method groups all records with the same CVE together and skips entries that are invalid or marked as not affected. The result is a dictionary keyed by CVE ID, with each value containing the related records.
6153
"""
62-
grouped = {}
54+
self.cve_to_records = {}
6355
skipped_invalid = 0
6456
skipped_non_affected = 0
6557

@@ -90,20 +82,19 @@ def _group_records_by_cve(self) -> dict:
9082
skipped_invalid += 1
9183
continue
9284

93-
if cve_id not in grouped:
94-
grouped[cve_id] = []
95-
grouped[cve_id].append(record)
85+
if cve_id not in self.cve_to_records:
86+
self.cve_to_records[cve_id] = []
87+
self.cve_to_records[cve_id].append(record)
9688

9789
total_skipped = skipped_invalid + skipped_non_affected
9890
self.log(
99-
f"Grouped {len(self.response):,d} records into {len(grouped):,d} unique CVEs "
91+
f"Grouped {len(self.response):,d} records into {len(self.cve_to_records):,d} unique CVEs "
10092
f"(skipped {total_skipped:,d}: {skipped_invalid:,d} invalid, "
10193
f"{skipped_non_affected:,d} non-affected)"
10294
)
103-
return grouped
10495

10596
def advisories_count(self) -> int:
106-
return len(self._grouped)
97+
return len(self.cve_to_records)
10798

10899
def _create_purl(self, project_name: str, os_name: str) -> PackageURL:
109100
normalized_os = os_name.lower().replace(" ", "-")
@@ -136,10 +127,8 @@ def _create_purl(self, project_name: str, os_name: str) -> PackageURL:
136127
type=pkg_type, namespace=namespace, name=project_name, qualifiers=qualifiers
137128
)
138129

139-
def collect_advisories(self) -> Iterable[AdvisoryData]:
140-
grouped_by_cve = self._grouped
141-
142-
for cve_id, records in grouped_by_cve.items():
130+
def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
131+
for cve_id, records in self.cve_to_records.items():
143132
affected_packages = []
144133
severities = []
145134
date_published = None
@@ -161,7 +150,7 @@ def collect_advisories(self) -> Iterable[AdvisoryData]:
161150
)
162151
continue
163152

164-
version_range_class = VERSION_RANGE_BY_PURL_TYPE.get(purl.type)
153+
version_range_class = RANGE_CLASS_BY_SCHEMES.get(purl.type)
165154

166155
try:
167156
version_range = version_range_class.from_versions([version])
@@ -209,7 +198,7 @@ def collect_advisories(self) -> Iterable[AdvisoryData]:
209198
self.log(f"Skipping {cve_id} - no valid affected packages")
210199
continue
211200

212-
yield AdvisoryData(
201+
yield AdvisoryDataV2(
213202
advisory_id=cve_id,
214203
affected_packages=affected_packages,
215204
severities=severities,

vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,39 @@ def test_collect_advisories(self, mock_fetch):
2929

3030
pipeline = TuxCareImporterPipeline()
3131
pipeline.fetch()
32+
pipeline.group_records_by_cve()
3233

3334
advisories = [data.to_dict() for data in list(pipeline.collect_advisories())]
3435

3536
expected_file = TEST_DATA / "expected.json"
3637
util_tests.check_results_against_json(advisories, expected_file)
3738

3839
assert len(advisories) == 14
40+
41+
def test_create_purl(self):
42+
pipeline = TuxCareImporterPipeline()
43+
44+
cases = [
45+
("squid", "CloudLinux 7 ELS", "rpm", "cloudlinux", "cloudlinux-7-els"),
46+
("squid", "Oracle Linux 7 ELS", "rpm", "oracle", "oracle-linux-7-els"),
47+
("kernel", "CentOS 8.5 ELS", "rpm", "centos", "centos-8.5-els"),
48+
("squid", "CentOS Stream 8 ELS", "rpm", "centos", "centos-stream-8-els"),
49+
("libpng", "CentOS 7 ELS", "rpm", "centos", "centos-7-els"),
50+
("java-11-openjdk", "RHEL 7 ELS", "rpm", "rhel", "rhel-7-els"),
51+
("mysql", "AlmaLinux 9.2 ESU", "rpm", "almalinux", "almalinux-9.2-esu"),
52+
("linux", "Ubuntu 16.04 ELS", "deb", "ubuntu", "ubuntu-16.04-els"),
53+
("samba", "Debian 10 ELS", "deb", "debian", "debian-10-els"),
54+
("dpkg", "Alpine Linux 3.18 ELS", "apk", "alpine", "alpine-linux-3.18-els"),
55+
("kernel", "Unknown OS", "generic", "tuxcare", "unknown-os"),
56+
("webkit2gtk3", "TuxCare 9.6 ESU", "generic", "tuxcare", "tuxcare-9.6-esu"),
57+
]
58+
59+
for name, os_name, expected_type, expected_ns, expected_distro in cases:
60+
purl = pipeline._create_purl(name, os_name)
61+
assert purl is not None, f"Expected purl for os_name={os_name!r}"
62+
assert purl.type == expected_type
63+
assert purl.namespace == expected_ns
64+
assert purl.qualifiers == {"distro": expected_distro}
65+
66+
#Invalid PURL
67+
assert pipeline._create_purl("foo", "Foo 123") is None

vulnerabilities/tests/test_data/tuxcare/expected.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"fixed_by_commit_patches": []
6262
}
6363
],
64-
"references_v2": [],
64+
"references": [],
6565
"patches": [],
6666
"severities": [
6767
{
@@ -94,7 +94,7 @@
9494
"fixed_by_commit_patches": []
9595
}
9696
],
97-
"references_v2": [],
97+
"references": [],
9898
"patches": [],
9999
"severities": [
100100
{
@@ -127,7 +127,7 @@
127127
"fixed_by_commit_patches": []
128128
}
129129
],
130-
"references_v2": [],
130+
"references": [],
131131
"patches": [],
132132
"severities": [
133133
{
@@ -160,7 +160,7 @@
160160
"fixed_by_commit_patches": []
161161
}
162162
],
163-
"references_v2": [],
163+
"references": [],
164164
"patches": [],
165165
"severities": [
166166
{
@@ -193,7 +193,7 @@
193193
"fixed_by_commit_patches": []
194194
}
195195
],
196-
"references_v2": [],
196+
"references": [],
197197
"patches": [],
198198
"severities": [
199199
{
@@ -226,7 +226,7 @@
226226
"fixed_by_commit_patches": []
227227
}
228228
],
229-
"references_v2": [],
229+
"references": [],
230230
"patches": [],
231231
"severities": [
232232
{
@@ -259,7 +259,7 @@
259259
"fixed_by_commit_patches": []
260260
}
261261
],
262-
"references_v2": [],
262+
"references": [],
263263
"patches": [],
264264
"severities": [
265265
{
@@ -292,7 +292,7 @@
292292
"fixed_by_commit_patches": []
293293
}
294294
],
295-
"references_v2": [],
295+
"references": [],
296296
"patches": [],
297297
"severities": [
298298
{
@@ -325,7 +325,7 @@
325325
"fixed_by_commit_patches": []
326326
}
327327
],
328-
"references_v2": [],
328+
"references": [],
329329
"patches": [],
330330
"severities": [
331331
{
@@ -358,7 +358,7 @@
358358
"fixed_by_commit_patches": []
359359
}
360360
],
361-
"references_v2": [],
361+
"references": [],
362362
"patches": [],
363363
"severities": [
364364
{
@@ -391,7 +391,7 @@
391391
"fixed_by_commit_patches": []
392392
}
393393
],
394-
"references_v2": [],
394+
"references": [],
395395
"patches": [],
396396
"severities": [
397397
{
@@ -424,7 +424,7 @@
424424
"fixed_by_commit_patches": []
425425
}
426426
],
427-
"references_v2": [],
427+
"references": [],
428428
"patches": [],
429429
"severities": [
430430
{
@@ -457,7 +457,7 @@
457457
"fixed_by_commit_patches": []
458458
}
459459
],
460-
"references_v2": [],
460+
"references": [],
461461
"patches": [],
462462
"severities": [],
463463
"date_published": "2025-12-22T16:58:13.189255+00:00",
@@ -484,7 +484,7 @@
484484
"fixed_by_commit_patches": []
485485
}
486486
],
487-
"references_v2": [],
487+
"references": [],
488488
"patches": [],
489489
"severities": [],
490490
"date_published": "2025-12-19T05:00:40.874276+00:00",

0 commit comments

Comments
 (0)