From c82483e39bdcde4a91bff23b802e16b3ecb30aaa Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:01:09 -0300 Subject: [PATCH 1/2] test: mock NVD API calls, add nvd_integration marker - conftest.py patches requests.get + time.sleep in cve_fetcher module - Skips mock for tests marked @pytest.mark.nvd_integration - Moves test_cve_fetcher.py module-level code into fixtures - pytest.ini excludes nvd_integration by default 66 tests now run in ~1s vs timing out at >120s --- backend/analyzer/test/conftest.py | 65 +++++++++++++++++++++++ backend/analyzer/test/test_cve_fetcher.py | 31 +++++++---- backend/pytest.ini | 6 ++- 3 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 backend/analyzer/test/conftest.py diff --git a/backend/analyzer/test/conftest.py b/backend/analyzer/test/conftest.py new file mode 100644 index 0000000..76f0f24 --- /dev/null +++ b/backend/analyzer/test/conftest.py @@ -0,0 +1,65 @@ +from unittest.mock import patch + +import pytest + +MOCK_NVD_RESPONSE = { + "vulnerabilities": [ + { + "cve": { + "id": "CVE-2021-44228", + "published": "2025-01-01T00:00:00Z", + "lastModified": "2025-01-01T00:00:00Z", + "descriptions": [{"value": "A test vulnerability description."}], + "metrics": { + "cvssMetricV31": [ + { + "cvssData": { + "baseScore": 7.5, + "baseSeverity": "HIGH", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "confidentialityImpact": "HIGH", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "scope": "UNCHANGED", + } + } + ] + }, + "weaknesses": [{"description": [{"value": "CWE-94"}]}], + "references": [{"url": "https://example.com/advisory", "tags": ["Vendor Advisory"]}], + } + } + ] +} + +MOCK_EPSS_RESPONSE = {"data": [{"epss": 0.5}]} + + +def _mock_requests_get(url, **kwargs): + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + url_str = str(url) + if "nvd.nist.gov" in url_str: + return MockResponse(MOCK_NVD_RESPONSE, 200) + if "api.first.org" in url_str: + return MockResponse(MOCK_EPSS_RESPONSE, 200) + raise ConnectionError(f"Unexpected request: {url_str}") + + +@pytest.fixture(autouse=True) +def no_nvd_network(request): + if request.node.get_closest_marker("nvd_integration"): + yield + return + with patch("analyzer.services.cve_fetcher.requests.get", side_effect=_mock_requests_get), \ + patch("analyzer.services.cve_fetcher.time.sleep"): + yield diff --git a/backend/analyzer/test/test_cve_fetcher.py b/backend/analyzer/test/test_cve_fetcher.py index b1df86b..d4072f5 100644 --- a/backend/analyzer/test/test_cve_fetcher.py +++ b/backend/analyzer/test/test_cve_fetcher.py @@ -1,29 +1,42 @@ from datetime import datetime +import pytest + from analyzer.services.cve_fetcher import CVEFetcher from utilities.constants import BaseSeverity, AttackVector, AttackComplexity, UserInteraction, IntegrityImpact, \ AvailabilityImpact, ConfidentialityImpact, Scope, PrivilegesRequired cve_id = "CVE-2021-44228" -cve_fetcher = CVEFetcher(cve_id=cve_id) -cve_data = cve_fetcher.generate() -def test_description(): +@pytest.fixture +def cve_data(): + fetcher = CVEFetcher(cve_id=cve_id) + return fetcher.generate() + + +@pytest.mark.nvd_integration +def test_real_nvd_api_contract(): + fetcher = CVEFetcher(cve_id=cve_id) + data = fetcher.generate() + assert fetcher.successful + assert len(data["description"]) > 0 + assert 0 < data["cve_attributes"]["baseScore"] <= 10 + + +def test_description(cve_data): assert len(cve_data["description"]) > 0 -def test_dates(): +def test_dates(cve_data): published = cve_data["published"] assert isinstance(published, datetime) - updated = cve_data["updated"] assert isinstance(updated, datetime) -def test_cve_attributes_cvss_v3(): +def test_cve_attributes_cvss_v3(cve_data): attributes = cve_data["cve_attributes"] - assert 0 < attributes["baseScore"] <= 10 assert attributes["baseSeverity"] in BaseSeverity.names assert attributes["attackVector"] in AttackVector.names @@ -36,9 +49,9 @@ def test_cve_attributes_cvss_v3(): assert attributes["scope"] in Scope.names -def test_epss_score(): +def test_epss_score(cve_data): assert 0 <= float(cve_data["epss"]) <= 1.0 -def test_vendor_reference(): +def test_vendor_reference(cve_data): assert len(cve_data["vendor_reference"]) >= 0 diff --git a/backend/pytest.ini b/backend/pytest.ini index ac3d988..a8cc62a 100644 --- a/backend/pytest.ini +++ b/backend/pytest.ini @@ -1,3 +1,5 @@ [pytest] -addopts = --nomigrations --reuse-db -DJANGO_SETTINGS_MODULE = securecheckplus.settings \ No newline at end of file +addopts = --nomigrations --reuse-db -m "not nvd_integration" +DJANGO_SETTINGS_MODULE = securecheckplus.settings +markers = + nvd_integration: tests that call the real NVD API (requires internet + API key) \ No newline at end of file From c5c6680113a7daed125245e88f89cb19e43bbbb3 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:45:28 -0300 Subject: [PATCH 2/2] fix: guard Docker Login and add hybrid fallback for DB/SALT --- .github/workflows/cicd-actions.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd-actions.yml b/.github/workflows/cicd-actions.yml index dffbe4d..36c0355 100644 --- a/.github/workflows/cicd-actions.yml +++ b/.github/workflows/cicd-actions.yml @@ -26,10 +26,13 @@ jobs: uses: actions/checkout@v4 - name: Create .env file + env: + POSTGRES_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }} + SALT: ${{ secrets.TEST_SALT }} run: | echo "NVD_API_KEY=${{ secrets.TEST_NVD_API_KEY }}" >> .env echo 'DJANGO_SECRET_KEY="${{ secrets.TEST_DJANGO_SECRET_KEY }}"' >> .env - echo 'SALT="${{ secrets.TEST_SALT }}"' >> .env + echo "SALT=${SALT:-local-dev-salt}" >> .env echo "ADMIN_USERNAME=admin@acme.de" >> .env echo "ADMIN_PASSWORD=secure!" >> .env echo "USER_USERNAME=user@acme.de" >> .env @@ -41,7 +44,7 @@ jobs: echo "POSTGRES_USER=securecheckplus" >> .env echo "POSTGRES_DB=securecheckplus" >> .env echo "POSTGRES_PORT=5432" >> .env - echo 'POSTGRES_PASSWORD="${{ secrets.TEST_DB_PASSWORD }}"' >> .env + echo "POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-scp_test_pass}" >> .env echo "EMAIL_HOST=localhost" >> .env echo "EMAIL_PORT=25" >> .env echo 'LDAP_ORGANISATION="ACME"' >> .env @@ -77,6 +80,7 @@ jobs: path: backend - name: Docker Login + if: env.DOCKER_USER != '' && env.DOCKER_KEY != '' run: echo "$DOCKER_KEY" | docker login -u "$DOCKER_USER" --password-stdin - name: Build Docker Compose @@ -182,6 +186,7 @@ jobs: path: backend/assets - name: Docker Login + if: env.DOCKER_USER != '' && env.DOCKER_KEY != '' run: echo "$DOCKER_KEY" | docker login -u "$DOCKER_USER" --password-stdin - name: Extract metadata (tags, labels) for Docker @@ -221,6 +226,7 @@ jobs: path: backend - name: Docker Login + if: env.DOCKER_USER != '' && env.DOCKER_KEY != '' run: echo "$DOCKER_KEY" | docker login -u "$DOCKER_USER" --password-stdin - name: Extract metadata (tags, labels) for Docker