diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index be89a5973..a92238227 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: max-parallel: 4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4428993e0..7c6872e44 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ env: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 services: postgres: diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 600b046d4..1bf50d19e 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -21,7 +21,7 @@ on: jobs: build-pypi-distribs: name: Build and publish library to PyPI - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@master diff --git a/docs/source/conf.py b/docs/source/conf.py index 05cec2924..dc1e573e0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,6 +36,8 @@ "https://www.softwaretestinghelp.com/how-to-write-good-bug-report/", # Cloudflare protection "https://www.openssl.org/news/vulnerabilities.xml", # OpenSSL legacy advisory URL, not longer available "https://example.org/api/non-existent-packages", + "https://github.com/aboutcode-org/vulnerablecode/pull/495/commits", + "https://nvd.nist.gov/products/cpe", ] # Add any Sphinx extension module names here, as strings. They can be diff --git a/vulnerabilities/import_runner.py b/vulnerabilities/import_runner.py index 796a03ef3..5bcf5f461 100644 --- a/vulnerabilities/import_runner.py +++ b/vulnerabilities/import_runner.py @@ -104,24 +104,30 @@ def process_advisories( advisories = [] for data in advisory_datas: content_id = compute_content_id(advisory_data=data) + advisory = { + "summary": data.summary, + "affected_packages": [pkg.to_dict() for pkg in data.affected_packages], + "references": [ref.to_dict() for ref in data.references], + "date_published": data.date_published, + "weaknesses": data.weaknesses, + "created_by": importer_name, + "date_collected": datetime.datetime.now(tz=datetime.timezone.utc), + } try: aliases = get_or_create_aliases(aliases=data.aliases) obj, created = Advisory.objects.get_or_create( unique_content_id=content_id, url=data.url, - defaults={ - "summary": data.summary, - "affected_packages": [pkg.to_dict() for pkg in data.affected_packages], - "references": [ref.to_dict() for ref in data.references], - "date_published": data.date_published, - "weaknesses": data.weaknesses, - "created_by": importer_name, - "date_collected": datetime.datetime.now(tz=datetime.timezone.utc), - }, + defaults=advisory, ) obj.aliases.add(*aliases) if not obj.date_imported: advisories.append(obj) + except Advisory.MultipleObjectsReturned: + logger.error( + f"Multiple Advisories returned: unique_content_id: {content_id}, url: {data.url}, advisory: {advisory!r}" + ) + raise except Exception as e: logger.error( f"Error while processing {data!r} with aliases {data.aliases!r}: {e!r} \n {traceback_format_exc()}" diff --git a/vulnerabilities/migrations/0091_alter_advisory_unique_together_and_more.py b/vulnerabilities/migrations/0091_alter_advisory_unique_together_and_more.py new file mode 100644 index 000000000..5a1029567 --- /dev/null +++ b/vulnerabilities/migrations/0091_alter_advisory_unique_together_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.17 on 2025-04-04 16:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("vulnerabilities", "0090_migrate_advisory_aliases"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="advisory", + unique_together=set(), + ), + migrations.AlterField( + model_name="advisory", + name="unique_content_id", + field=models.CharField( + help_text="A 64 character unique identifier for the content of the advisory since we use sha256 as hex", + max_length=64, + unique=True, + ), + ), + migrations.AlterField( + model_name="advisory", + name="url", + field=models.URLField(help_text="Link to the advisory on the upstream website"), + ), + ] diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index dba205500..fc317b3ce 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -1321,6 +1321,7 @@ class Advisory(models.Model): max_length=64, blank=False, null=False, + unique=True, help_text="A 64 character unique identifier for the content of the advisory since we use sha256 as hex", ) aliases = models.ManyToManyField( @@ -1355,14 +1356,14 @@ class Advisory(models.Model): "vulnerabilities.pipeline.nginx_importer.NginxImporterPipeline", ) url = models.URLField( - blank=True, + blank=False, + null=False, help_text="Link to the advisory on the upstream website", ) objects = AdvisoryQuerySet.as_manager() class Meta: - unique_together = ["unique_content_id", "date_published", "url"] ordering = ["date_published", "unique_content_id"] def save(self, *args, **kwargs): diff --git a/vulnerabilities/pipes/advisory.py b/vulnerabilities/pipes/advisory.py index 3d98392c9..dd21bc88c 100644 --- a/vulnerabilities/pipes/advisory.py +++ b/vulnerabilities/pipes/advisory.py @@ -43,20 +43,27 @@ def insert_advisory(advisory: AdvisoryData, pipeline_id: str, logger: Callable = aliases = get_or_create_aliases(aliases=advisory.aliases) content_id = compute_content_id(advisory_data=advisory) try: + default_data = { + "summary": advisory.summary, + "affected_packages": [pkg.to_dict() for pkg in advisory.affected_packages], + "references": [ref.to_dict() for ref in advisory.references], + "date_published": advisory.date_published, + "weaknesses": advisory.weaknesses, + "created_by": pipeline_id, + "date_collected": datetime.now(timezone.utc), + } + advisory_obj, _ = Advisory.objects.get_or_create( unique_content_id=content_id, url=advisory.url, - defaults={ - "summary": advisory.summary, - "affected_packages": [pkg.to_dict() for pkg in advisory.affected_packages], - "references": [ref.to_dict() for ref in advisory.references], - "date_published": advisory.date_published, - "weaknesses": advisory.weaknesses, - "created_by": pipeline_id, - "date_collected": datetime.now(timezone.utc), - }, + defaults=default_data, ) advisory_obj.aliases.add(*aliases) + except Advisory.MultipleObjectsReturned: + logger.error( + f"Multiple Advisories returned: unique_content_id: {content_id}, url: {advisory.url}, advisory: {advisory!r}" + ) + raise except Exception as e: if logger: logger( @@ -137,19 +144,18 @@ def import_advisory( }, ) vulnerability.severities.add(vulnerability_severity) + if not created and logger: + logger( + f"Severity updated for reference {ref.url!r} to value: {severity.value!r} " + f"and scoring_elements: {severity.scoring_elements!r}", + level=logging.DEBUG, + ) except: if logger: logger( f"Failed to create VulnerabilitySeverity for: {severity} with error:\n{traceback_format_exc()}", level=logging.ERROR, ) - if not created: - if logger: - logger( - f"Severity updated for reference {ref.url!r} to value: {severity.value!r} " - f"and scoring_elements: {severity.scoring_elements!r}", - level=logging.DEBUG, - ) for affected_purl in affected_purls or []: vulnerable_package, _ = Package.objects.get_or_create_from_purl(purl=affected_purl) diff --git a/vulnerabilities/tests/conftest.py b/vulnerabilities/tests/conftest.py index de75014fb..69f956925 100644 --- a/vulnerabilities/tests/conftest.py +++ b/vulnerabilities/tests/conftest.py @@ -25,7 +25,6 @@ def no_rmtree(monkeypatch): # Step 2: Run test for importer only if it is activated (pytestmark = pytest.mark.skipif(...)) # Step 3: Migrate all the tests collect_ignore = [ - "test_models.py", "test_rust.py", "test_suse_backports.py", "test_suse.py", diff --git a/vulnerabilities/tests/pipelines/test_populate_vulnerability_summary_pipeline.py b/vulnerabilities/tests/pipelines/test_populate_vulnerability_summary_pipeline.py index d8f3ad944..08b135afc 100644 --- a/vulnerabilities/tests/pipelines/test_populate_vulnerability_summary_pipeline.py +++ b/vulnerabilities/tests/pipelines/test_populate_vulnerability_summary_pipeline.py @@ -43,6 +43,7 @@ def test_populate_missing_summaries_from_nvd(self): created_by="nvd_importer", date_collected=datetime.datetime(2024, 1, 1, tzinfo=pytz.UTC), unique_content_id="Test", + url="https://test.com", ) adv.aliases.add(alias) @@ -110,6 +111,7 @@ def test_non_nvd_advisory_ignored(self): created_by="other_importer", date_collected=datetime.datetime(2024, 1, 1, tzinfo=pytz.UTC), unique_content_id="Test", + url="https://test.com", ) adv.aliases.add(alias) @@ -138,6 +140,7 @@ def test_multiple_matching_advisories(self): created_by="nvd_importer", date_collected=datetime.datetime(2024, 1, 1, tzinfo=pytz.UTC), unique_content_id="Test", + url="https://test.com", ) adv1.aliases.add(alias) @@ -147,6 +150,7 @@ def test_multiple_matching_advisories(self): created_by="nvd_importer", date_collected=datetime.datetime(2024, 1, 2, tzinfo=pytz.UTC), unique_content_id="Test-1", + url="https://test.com", ) adv2.aliases.add(alias) diff --git a/vulnerabilities/tests/pipelines/test_remove_duplicate_advisories.py b/vulnerabilities/tests/pipelines/test_remove_duplicate_advisories.py index d6cd5b5d7..0d7e682be 100644 --- a/vulnerabilities/tests/pipelines/test_remove_duplicate_advisories.py +++ b/vulnerabilities/tests/pipelines/test_remove_duplicate_advisories.py @@ -32,6 +32,7 @@ def setUp(self): ) ], references=[Reference(url="https://example.com/vuln1")], + url="https://test.url/", ) def test_remove_duplicates_keeps_oldest(self): @@ -49,9 +50,10 @@ def test_remove_duplicates_keeps_oldest(self): ] advisories = [] - for date in dates: + for i, date in enumerate(dates): advisory = Advisory.objects.create( - unique_content_id=compute_content_id(advisory_data=self.advisory_data), + unique_content_id=f"incorrect-content-id{i}", + url=self.advisory_data.url, summary=self.advisory_data.summary, affected_packages=[pkg.to_dict() for pkg in self.advisory_data.affected_packages], references=[ref.to_dict() for ref in self.advisory_data.references], @@ -77,6 +79,7 @@ def test_different_content_preserved(self): # Create two advisories with different content advisory1 = Advisory.objects.create( unique_content_id="test-id1", + url="https://test.url/", summary="Summary 1", affected_packages=[], date_collected=datetime.datetime(2024, 1, 1, tzinfo=pytz.UTC), @@ -87,6 +90,7 @@ def test_different_content_preserved(self): advisory2 = Advisory.objects.create( unique_content_id="test-id2", + url="https://test.url/", summary="Summary 2", affected_packages=[], references=[], @@ -111,6 +115,7 @@ def test_recompute_content_ids(self): # Create advisory without content ID advisory = Advisory.objects.create( unique_content_id="incorrect-content-id", + url=self.advisory_data.url, summary=self.advisory_data.summary, affected_packages=[pkg.to_dict() for pkg in self.advisory_data.affected_packages], references=[ref.to_dict() for ref in self.advisory_data.references], @@ -125,4 +130,4 @@ def test_recompute_content_ids(self): # Check that content ID was updated advisory.refresh_from_db() expected_content_id = compute_content_id(advisory_data=self.advisory_data) - self.assertNotEqual(advisory.unique_content_id, expected_content_id) + self.assertEqual(advisory.unique_content_id, expected_content_id) diff --git a/vulnerabilities/tests/pipes/test_advisory.py b/vulnerabilities/tests/pipes/test_advisory.py index c59c96ef8..ee29a4b8d 100644 --- a/vulnerabilities/tests/pipes/test_advisory.py +++ b/vulnerabilities/tests/pipes/test_advisory.py @@ -7,7 +7,10 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -import pytest +from datetime import datetime + +from django.core.exceptions import ValidationError +from django.test import TestCase from django.utils import timezone from packageurl import PackageURL from univers.version_range import VersionRange @@ -18,65 +21,116 @@ from vulnerabilities.importer import Reference from vulnerabilities.pipes.advisory import get_or_create_aliases from vulnerabilities.pipes.advisory import import_advisory +from vulnerabilities.utils import compute_content_id -advisory_data1 = AdvisoryData( - summary="vulnerability description here", - affected_packages=[ - AffectedPackage( - package=PackageURL(type="pypi", name="dummy"), - affected_version_range=VersionRange.from_string("vers:pypi/>=1.0.0|<=2.0.0"), - ) - ], - references=[Reference(url="https://example.com/with/more/info/CVE-2020-13371337")], - date_published=timezone.now(), - url="https://test.com", -) +class TestPipeAdvisory(TestCase): + def setUp(self): + self.advisory_data1 = AdvisoryData( + summary="vulnerability description here", + affected_packages=[ + AffectedPackage( + package=PackageURL(type="pypi", name="dummy"), + affected_version_range=VersionRange.from_string("vers:pypi/>=1.0.0|<=2.0.0"), + ) + ], + references=[Reference(url="https://example.com/with/more/info/CVE-2020-13371337")], + date_published=timezone.now(), + url="https://test.com", + ) -def get_advisory1(created_by="test_pipeline"): - from vulnerabilities.pipes.advisory import insert_advisory + def get_advisory1(self, created_by="test_pipeline"): + from vulnerabilities.pipes.advisory import insert_advisory - return insert_advisory( - advisory=advisory_data1, - pipeline_id=created_by, - ) + return insert_advisory( + advisory=self.advisory_data1, + pipeline_id=created_by, + ) + def get_all_vulnerability_relationships_objects(self): + return { + "vulnerabilities": list(models.Vulnerability.objects.all()), + "aliases": list(models.Alias.objects.all()), + "references": list(models.VulnerabilityReference.objects.all()), + "advisories": list(models.Advisory.objects.all()), + "packages": list(models.Package.objects.all()), + "references": list(models.VulnerabilityReference.objects.all()), + "severity": list(models.VulnerabilitySeverity.objects.all()), + } -def get_all_vulnerability_relationships_objects(): - return { - "vulnerabilities": list(models.Vulnerability.objects.all()), - "aliases": list(models.Alias.objects.all()), - "references": list(models.VulnerabilityReference.objects.all()), - "advisories": list(models.Advisory.objects.all()), - "packages": list(models.Package.objects.all()), - "references": list(models.VulnerabilityReference.objects.all()), - "severity": list(models.VulnerabilitySeverity.objects.all()), - } + def test_vulnerability_pipes_importer_import_advisory(self): + advisory1 = self.get_advisory1(created_by="test_importer_pipeline") + import_advisory(advisory=advisory1, pipeline_id="test_importer_pipeline") + all_vulnerability_relation_objects = self.get_all_vulnerability_relationships_objects() + import_advisory(advisory=advisory1, pipeline_id="test_importer_pipeline") + assert ( + all_vulnerability_relation_objects == self.get_all_vulnerability_relationships_objects() + ) + def test_vulnerability_pipes_importer_import_advisory_different_pipelines(self): + advisory1 = self.get_advisory1(created_by="test_importer_pipeline") + import_advisory(advisory=advisory1, pipeline_id="test_importer1_pipeline") + all_vulnerability_relation_objects = self.get_all_vulnerability_relationships_objects() + import_advisory(advisory=advisory1, pipeline_id="test_importer2_pipeline") + assert ( + all_vulnerability_relation_objects == self.get_all_vulnerability_relationships_objects() + ) -@pytest.mark.django_db -def test_vulnerability_pipes_importer_import_advisory(): - advisory1 = get_advisory1(created_by="test_importer_pipeline") - import_advisory(advisory=advisory1, pipeline_id="test_importer_pipeline") - all_vulnerability_relation_objects = get_all_vulnerability_relationships_objects() - import_advisory(advisory=advisory1, pipeline_id="test_importer_pipeline") - assert all_vulnerability_relation_objects == get_all_vulnerability_relationships_objects() + def test_vulnerability_pipes_get_or_create_aliases(self): + aliases = ["CVE-TEST-123", "CVE-TEST-124"] + result_aliases_qs = get_or_create_aliases(aliases=aliases) + result_aliases = [i.alias for i in result_aliases_qs] + assert 2 == result_aliases_qs.count() + assert "CVE-TEST-123" in result_aliases + assert "CVE-TEST-124" in result_aliases + def test_advisory_insert_without_url(self): + with self.assertRaises(ValidationError): + date = datetime.now() + models.Advisory.objects.create( + unique_content_id=compute_content_id(advisory_data=self.advisory_data1), + summary=self.advisory_data1.summary, + affected_packages=[pkg.to_dict() for pkg in self.advisory_data1.affected_packages], + references=[ref.to_dict() for ref in self.advisory_data1.references], + date_imported=date, + date_collected=date, + created_by="test_pipeline", + ) -@pytest.mark.django_db -def test_vulnerability_pipes_importer_import_advisory_different_pipelines(): - advisory1 = get_advisory1(created_by="test_importer_pipeline") - import_advisory(advisory=advisory1, pipeline_id="test_importer1_pipeline") - all_vulnerability_relation_objects = get_all_vulnerability_relationships_objects() - import_advisory(advisory=advisory1, pipeline_id="test_importer2_pipeline") - assert all_vulnerability_relation_objects == get_all_vulnerability_relationships_objects() + def test_advisory_insert_without_content_id(self): + with self.assertRaises(ValidationError): + date = datetime.now() + models.Advisory.objects.create( + url=self.advisory_data1.url, + summary=self.advisory_data1.summary, + affected_packages=[pkg.to_dict() for pkg in self.advisory_data1.affected_packages], + references=[ref.to_dict() for ref in self.advisory_data1.references], + date_imported=date, + date_collected=date, + created_by="test_pipeline", + ) + def test_advisory_insert_no_duplicate_content_id(self): + date = datetime.now() + models.Advisory.objects.create( + unique_content_id=compute_content_id(advisory_data=self.advisory_data1), + url=self.advisory_data1.url, + summary=self.advisory_data1.summary, + affected_packages=[pkg.to_dict() for pkg in self.advisory_data1.affected_packages], + references=[ref.to_dict() for ref in self.advisory_data1.references], + date_imported=date, + date_collected=date, + created_by="test_pipeline", + ) -@pytest.mark.django_db -def test_vulnerability_pipes_get_or_create_aliases(): - aliases = ["CVE-TEST-123", "CVE-TEST-124"] - result_aliases_qs = get_or_create_aliases(aliases=aliases) - result_aliases = [i.alias for i in result_aliases_qs] - assert 2 == result_aliases_qs.count() - assert "CVE-TEST-123" in result_aliases - assert "CVE-TEST-124" in result_aliases + with self.assertRaises(ValidationError): + models.Advisory.objects.create( + unique_content_id=compute_content_id(advisory_data=self.advisory_data1), + url=self.advisory_data1.url, + summary=self.advisory_data1.summary, + affected_packages=[pkg.to_dict() for pkg in self.advisory_data1.affected_packages], + references=[ref.to_dict() for ref in self.advisory_data1.references], + date_imported=date, + date_collected=date, + created_by="test_pipeline", + ) diff --git a/vulnerabilities/tests/test_add_cvsssv31.py b/vulnerabilities/tests/test_add_cvsssv31.py index 6b1c1875a..7116ad456 100644 --- a/vulnerabilities/tests/test_add_cvsssv31.py +++ b/vulnerabilities/tests/test_add_cvsssv31.py @@ -25,6 +25,7 @@ def setUp(self): advisory = Advisory.objects.create( created_by="nvd_importer", unique_content_id="test-unique-content-id", + url="https://nvd.nist.gov/vuln/detail/CVE-2024-1234", references=[ { "severities": [ diff --git a/vulnerabilities/tests/test_import_runner.py b/vulnerabilities/tests/test_import_runner.py index 3b8080086..2a0757e14 100644 --- a/vulnerabilities/tests/test_import_runner.py +++ b/vulnerabilities/tests/test_import_runner.py @@ -179,6 +179,7 @@ def test_advisory_summary_clean_up(): DUMMY_ADVISORY = models.Advisory( unique_content_id="test-unique-content-id", + url="https://test.url/", summary="dummy", created_by="tests", date_collected=timezone.now(), diff --git a/vulnerabilities/tests/test_models.py b/vulnerabilities/tests/test_models.py index a5f8e251c..7b2dd06cc 100644 --- a/vulnerabilities/tests/test_models.py +++ b/vulnerabilities/tests/test_models.py @@ -8,17 +8,25 @@ # import urllib.parse +from datetime import datetime from unittest import TestCase import pytest +from django.core.exceptions import ValidationError +from django.test import TestCase as DjangoTestCase from packageurl import PackageURL from univers import versions from univers.version_range import RANGE_CLASS_BY_SCHEMES +from univers.version_range import VersionRange from vulnerabilities import models +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import AffectedPackage +from vulnerabilities.importer import Reference from vulnerabilities.models import Alias from vulnerabilities.models import Package from vulnerabilities.models import Vulnerability +from vulnerabilities.utils import compute_content_id class TestVulnerabilityModel(TestCase): @@ -28,11 +36,9 @@ def test_vulnerability_save_with_vulnerability_id(self): assert models.Vulnerability.objects.filter(vulnerability_id="CVE-2020-7965").count() == 1 @pytest.mark.django_db - def test_cwe_not_present_in_weaknesses_db(self): + def test_cwe_present_in_weaknesses_db(self): w1 = models.Weakness.objects.create(cwe_id=189) - assert w1.weakness is None - assert w1.name is "" - assert w1.description is "" + assert w1.name == "Numeric Errors" # FIXME: The fixture code is duplicated. setUpClass is not working with the pytest mark. @@ -51,7 +57,7 @@ def test_package_to_vulnerability(self): assert p1.fixing_vulnerabilities.count() == 0 assert p2.fixing_vulnerabilities.count() == 1 - assert p2.fixing_vulnerabilities[0] == v1 + assert p2.fixing_vulnerabilities.first() == v1 def test_vulnerability_package(self): p1 = models.Package.objects.create(type="deb", name="git", version="2.30.1") @@ -66,8 +72,8 @@ def test_vulnerability_package(self): assert v1.vulnerable_packages.count() == 1 assert v1.fixed_by_packages.count() == 1 - assert v1.vulnerable_packages[0] == p1 - assert v1.fixed_by_packages[0] == p2 + assert v1.vulnerable_packages.first() == p1 + assert v1.fixed_by_packages.first() == p2 @pytest.mark.django_db @@ -208,10 +214,14 @@ def test_fixed_package_details(self): assert len(searched_for_package.affected_by) == 2 assert self.vuln_VCID_g2fu_45jw_aaan in searched_for_package.affected_by - assert self.package_pypi_redis_4_3_6 in self.vuln_VCID_g2fu_45jw_aaan.fixed_by_packages + assert ( + self.package_pypi_redis_4_3_6 in self.vuln_VCID_g2fu_45jw_aaan.fixed_by_packages.all() + ) assert self.vuln_VCID_rqe1_dkmg_aaad in searched_for_package.affected_by - assert self.package_pypi_redis_5_0_0b1 in self.vuln_VCID_rqe1_dkmg_aaad.fixed_by_packages + assert ( + self.package_pypi_redis_5_0_0b1 in self.vuln_VCID_rqe1_dkmg_aaad.fixed_by_packages.all() + ) searched_for_package_details = searched_for_package.fixed_package_details @@ -221,19 +231,8 @@ def test_fixed_package_details(self): name="redis", version="4.1.1", ), - "next_non_vulnerable": PackageURL( - type="pypi", - name="redis", - version="5.0.0b1", - ), - "latest_non_vulnerable": PackageURL( - type="pypi", - namespace=None, - name="redis", - version="5.0.0b1", - qualifiers={}, - subpath=None, - ), + "next_non_vulnerable": self.package_pypi_redis_5_0_0b1, + "latest_non_vulnerable": self.package_pypi_redis_5_0_0b1, "vulnerabilities": [ { "vulnerability": self.vuln_VCID_g2fu_45jw_aaan, @@ -276,13 +275,9 @@ def test_fixed_package_details(self): assert searched_for_package_details == package_details - assert searched_for_package_details.get("latest_non_vulnerable") == PackageURL( - type="pypi", - namespace=None, - name="redis", - version="5.0.0b1", - qualifiers={}, - subpath=None, + assert ( + searched_for_package_details.get("latest_non_vulnerable") + == self.package_pypi_redis_5_0_0b1 ) searched_for_package_fixing = searched_for_package.fixing @@ -477,15 +472,15 @@ def test_affecting_vulnerabilities_vulnerabilityqueryset_method(self): assert all_vulnerabilities_count == 4 - def test_affecting_vulnerabilities_package_property_method(self): + def test_affected_by_package_property_method(self): """ - Return a queryset of Vulnerabilities using the Package affecting_vulnerabilities() property + Return a queryset of Vulnerabilities using the Package affected_by() property method. """ searched_for_package = self.package_pypi_redis_4_1_1 # Return a queryset of Vulnerabilities that affect a specific Package. - this_package_vulnerabilities = searched_for_package.affecting_vulnerabilities + this_package_vulnerabilities = searched_for_package.affected_by assert this_package_vulnerabilities[0] == self.vuln_VCID_g2fu_45jw_aaan assert this_package_vulnerabilities[1] == self.vuln_VCID_rqe1_dkmg_aaad @@ -505,7 +500,7 @@ def test_fixing_vulnerabilities_package_property_method(self): redis_4_3_6_fixing_vulnerabilities = searched_for_package_redis_4_3_6.fixing_vulnerabilities assert redis_4_3_6_fixing_vulnerabilities.count() == 1 - assert redis_4_3_6_fixing_vulnerabilities[0] == self.vuln_VCID_g2fu_45jw_aaan + assert redis_4_3_6_fixing_vulnerabilities.first() == self.vuln_VCID_g2fu_45jw_aaan searched_for_package_redis_5_0_0b1 = self.package_pypi_redis_5_0_0b1 redis_5_0_0b1_fixing_vulnerabilities = ( @@ -513,7 +508,7 @@ def test_fixing_vulnerabilities_package_property_method(self): ) assert redis_5_0_0b1_fixing_vulnerabilities.count() == 1 - assert redis_5_0_0b1_fixing_vulnerabilities[0] == self.vuln_VCID_rqe1_dkmg_aaad + assert redis_5_0_0b1_fixing_vulnerabilities.first() == self.vuln_VCID_rqe1_dkmg_aaad def test_get_affecting_vulnerabilities_package_method(self): """ @@ -598,3 +593,57 @@ def test_get_fixed_by_package_versions(self): assert all_package_versions[1] == self.package_pypi_redis_4_3_6 assert all_package_versions[2] == self.package_pypi_redis_5_0_0b1 assert all_package_versions.count() == 3 + + +class TestAdvisoryModel(DjangoTestCase): + def setUp(self): + self.advisory_data1 = AdvisoryData( + summary="vulnerability description here", + affected_packages=[ + AffectedPackage( + package=PackageURL(type="pypi", name="dummy"), + affected_version_range=VersionRange.from_string("vers:pypi/>=1.0.0|<=2.0.0"), + ) + ], + references=[Reference(url="https://example.com/with/more/info/CVE-2020-13371337")], + date_published=datetime.now(), + url="https://test.com", + ) + + def test_advisory_insert_without_content_id(self): + with self.assertRaises(ValidationError): + date = datetime.now() + models.Advisory.objects.create( + url=self.advisory_data1.url, + summary=self.advisory_data1.summary, + affected_packages=[pkg.to_dict() for pkg in self.advisory_data1.affected_packages], + references=[ref.to_dict() for ref in self.advisory_data1.references], + date_imported=date, + date_collected=date, + created_by="test_pipeline", + ) + + def test_advisory_insert_no_duplicate_content_id(self): + date = datetime.now() + models.Advisory.objects.create( + unique_content_id=compute_content_id(advisory_data=self.advisory_data1), + url=self.advisory_data1.url, + summary=self.advisory_data1.summary, + affected_packages=[pkg.to_dict() for pkg in self.advisory_data1.affected_packages], + references=[ref.to_dict() for ref in self.advisory_data1.references], + date_imported=date, + date_collected=date, + created_by="test_pipeline", + ) + + with self.assertRaises(ValidationError): + models.Advisory.objects.create( + unique_content_id=compute_content_id(advisory_data=self.advisory_data1), + url=self.advisory_data1.url, + summary=self.advisory_data1.summary, + affected_packages=[pkg.to_dict() for pkg in self.advisory_data1.affected_packages], + references=[ref.to_dict() for ref in self.advisory_data1.references], + date_imported=date, + date_collected=date, + created_by="test_pipeline", + ) diff --git a/vulnerabilities/tests/test_postgres_workaround.py b/vulnerabilities/tests/test_postgres_workaround.py index 9fe2c66a0..b3d2d78cd 100644 --- a/vulnerabilities/tests/test_postgres_workaround.py +++ b/vulnerabilities/tests/test_postgres_workaround.py @@ -427,6 +427,7 @@ def test_postgres_workaround_with_many_references_many_affected_packages_and_long_summary(): adv, _ = Advisory.objects.get_or_create( unique_content_id="test-unique-content-id", + url="https://test.url/", summary=data.summary, affected_packages=[pkg.to_dict() for pkg in data.affected_packages], references=[ref.to_dict() for ref in data.references], diff --git a/vulnerabilities/tests/test_vulnerability_status_improver.py b/vulnerabilities/tests/test_vulnerability_status_improver.py index 2a67730d4..6605d717c 100644 --- a/vulnerabilities/tests/test_vulnerability_status_improver.py +++ b/vulnerabilities/tests/test_vulnerability_status_improver.py @@ -35,13 +35,15 @@ def test_interesting_advisories(): adv1 = Advisory.objects.create( unique_content_id="test-unique-content-id", + url="https://test.url/", created_by=NVDImporterPipeline.pipeline_id, summary="1", date_collected=datetime.now(), ) adv1.aliases.add(*get_or_create_aliases(["CVE-1"])) adv2 = Advisory.objects.create( - unique_content_id="test-unique-content-id", + unique_content_id="test-unique-content-id2", + url="https://test.url/", created_by=NVDImporterPipeline.pipeline_id, summary="2", date_collected=datetime.now(), @@ -58,6 +60,7 @@ def test_improver_end_to_end(mock_response): mock_response.return_value = response adv = Advisory.objects.create( unique_content_id="test-unique-content-id", + url="https://test.url/", created_by=NVDImporterPipeline.pipeline_id, summary="1", date_collected=datetime.now(),