From f252c256d8fcd2fdf2c8c50afcd33a8c63aec4bb Mon Sep 17 00:00:00 2001 From: Marker689 Date: Sun, 10 May 2026 07:58:03 +0300 Subject: [PATCH] =?UTF-8?q?test:=20=D1=84=D0=B0=D0=B7=D0=B0=204=20?= =?UTF-8?q?=E2=80=94=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20extractors,=20ecos?= =?UTF-8?q?ystem,=20i18n,=20metrics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test_nexus.py: extract_pypi/go/npm, dispatch, edge cases (16 тестов) - test_i18n.py: RU/EN переводы, fallback, форматирование, web UI (10 тестов) - test_metrics.py: Prometheus endpoint (4 теста) - test_webhooks.py: _detect_ecosystem (6 тестов), Go/npm webhook fixtures - conftest.py: sample_nexus_go/npm_webhook fixtures - Всего: 85 тестов (было 50) --- tests/conftest.py | 36 +++++++++++++++++ tests/test_i18n.py | 47 ++++++++++++++++++++++ tests/test_metrics.py | 35 +++++++++++++++++ tests/test_nexus.py | 88 ++++++++++++++++++++++++++++++++++++++++++ tests/test_webhooks.py | 49 +++++++++++++++++++++++ 5 files changed, 255 insertions(+) create mode 100644 tests/test_i18n.py create mode 100644 tests/test_metrics.py create mode 100644 tests/test_nexus.py diff --git a/tests/conftest.py b/tests/conftest.py index 9d810fb..59eec9b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -209,3 +209,39 @@ def guarddog_normalized_clean(): "findings": [], "errors": [], } + + +@pytest.fixture +def sample_nexus_go_webhook(): + return { + "timestamp": "2026-05-10T12:00:00.000+00:00", + "nodeId": "test-node", + "initiator": "admin", + "action": "UPDATED", + "repositoryName": "go-proxy", + "asset": { + "id": "go123", + "assetId": "Z28xMjM=", + "format": "go", + "name": "/packages/github.com/gorilla/mux/@v/v1.8.0.zip", + "downloadUrl": "http://nexus:8081/repository/go-proxy/github.com/gorilla/mux/@v/v1.8.0.zip", + }, + } + + +@pytest.fixture +def sample_nexus_npm_webhook(): + return { + "timestamp": "2026-05-10T12:00:00.000+00:00", + "nodeId": "test-node", + "initiator": "admin", + "action": "UPDATED", + "repositoryName": "npm-proxy", + "asset": { + "id": "npm123", + "assetId": "bnBtMTIz", + "format": "npm", + "name": "/packages/lodash/-/lodash-4.17.21.tgz", + "downloadUrl": "http://nexus:8081/repository/npm-proxy/lodash/-/lodash-4.17.21.tgz", + }, + } diff --git a/tests/test_i18n.py b/tests/test_i18n.py new file mode 100644 index 0000000..6699f34 --- /dev/null +++ b/tests/test_i18n.py @@ -0,0 +1,47 @@ +"""Tests for i18n module.""" + +import pytest + +from guarddog_nexus.i18n import LANGUAGES, t + + +class TestTranslate: + def test_english(self): + assert t("nav_dashboard", "en") == "Dashboard" + assert t("nav_scans", "en") == "Scans" + + def test_russian(self): + assert t("nav_dashboard", "ru") == "Панель" + assert t("nav_scans", "ru") == "Сканирования" + + def test_default_lang(self): + assert t("nav_dashboard") == "Dashboard" + + def test_unknown_key(self): + assert t("nonexistent_key", "en") == "nonexistent_key" + + def test_unknown_lang_falls_back(self): + assert t("nav_dashboard", "fr") == "Dashboard" + + def test_positional_formatting(self): + result = t("total_scans", "en", 42) + assert "42" in result + assert "total scans" in result.lower() + + def test_languages_dict(self): + assert "en" in LANGUAGES + assert "ru" in LANGUAGES + + +@pytest.mark.asyncio +async def test_web_lang_ru(client): + resp = await client.get("/scans?lang=ru") + assert resp.status_code == 200 + assert "Сканирования" in resp.text + + +@pytest.mark.asyncio +async def test_web_lang_en(client): + resp = await client.get("/scans?lang=en") + assert resp.status_code == 200 + assert "Scans" in resp.text diff --git a/tests/test_metrics.py b/tests/test_metrics.py new file mode 100644 index 0000000..cac108c --- /dev/null +++ b/tests/test_metrics.py @@ -0,0 +1,35 @@ +"""Tests for Prometheus metrics endpoint.""" + +import pytest + + +@pytest.mark.asyncio +async def test_metrics_returns_200(client): + resp = await client.get("/metrics") + assert resp.status_code == 200 + assert "text/plain" in resp.headers["content-type"] + + +@pytest.mark.asyncio +async def test_metrics_contains_keys(client): + resp = await client.get("/metrics") + text = resp.text + assert "guarddog_scans_total" in text + assert "guarddog_scans_flagged_total" in text + assert "guarddog_findings_total" in text + assert "guarddog_scans_by_status" in text + assert "guarddog_scans_by_ecosystem" in text + # last_scan may not be present if no scans exist + + +@pytest.mark.asyncio +async def test_metrics_help_section(client): + resp = await client.get("/metrics") + assert "# HELP" in resp.text + assert "# TYPE" in resp.text + + +@pytest.mark.asyncio +async def test_metrics_last_scan_with_data(client, sample_flagged_scan): + resp = await client.get("/metrics") + assert "guarddog_last_scan_timestamp_seconds" in resp.text diff --git a/tests/test_nexus.py b/tests/test_nexus.py new file mode 100644 index 0000000..2b4c048 --- /dev/null +++ b/tests/test_nexus.py @@ -0,0 +1,88 @@ +"""Tests for Nexus package info extractors.""" + + +from guarddog_nexus.core.nexus import ( + extract_go_info, + extract_npm_info, + extract_package_info, + extract_pypi_info, +) + + +class TestPyPIExtractor: + def test_simple(self): + assert extract_pypi_info("/packages/requests/2.31.0/requests-2.31.0.tar.gz") == ( + "requests", + "2.31.0", + ) + + def test_leading_slash(self): + assert extract_pypi_info("packages/numpy/1.24.0/numpy-1.24.0.tar.gz") == ( + "numpy", + "1.24.0", + ) + + def test_non_package(self): + assert extract_pypi_info("/some/path") is None + + def test_too_short(self): + assert extract_pypi_info("/packages/pkg") is None + + +class TestGoExtractor: + def test_simple(self): + assert extract_go_info("packages/github.com/gorilla/mux/@v/v1.8.0.zip") == ( + "github.com/gorilla/mux", + "v1.8.0", + ) + + def test_long_module(self): + assert extract_go_info( + "/packages/github.com/gin-gonic/gin/@v/v1.9.0.zip" + ) == ("github.com/gin-gonic/gin", "v1.9.0") + + def test_no_at_v(self): + assert extract_go_info("packages/some/pkg/v1.0.0.zip") is None + + def test_empty(self): + assert extract_go_info("") is None + + +class TestNpmExtractor: + def test_simple(self): + assert extract_npm_info("packages/lodash/-/lodash-4.17.21.tgz") == ( + "lodash", + "4.17.21", + ) + + def test_scoped_package(self): + # Note: scoped packages have a different path in Nexus + assert extract_npm_info("packages/@scope/name/-/name-1.0.0.tgz") is None + + def test_not_packages(self): + assert extract_npm_info("/other/lodash/-/lodash-4.17.21.tgz") is None + + def test_short_path(self): + assert extract_npm_info("packages/lodash") is None + + +class TestDispatchExtractor: + def test_pypi(self): + assert extract_package_info( + "/packages/requests/2.31.0/requests-2.31.0.tar.gz", "pypi" + ) == ("requests", "2.31.0") + + def test_go(self): + assert extract_package_info( + "github.com/gorilla/mux/@v/v1.8.0.zip", "go" + ) == ("github.com/gorilla/mux", "v1.8.0") + + def test_npm(self): + assert extract_package_info( + "packages/lodash/-/lodash-4.17.21.tgz", "npm" + ) == ("lodash", "4.17.21") + + def test_unknown_ecosystem(self): + assert extract_package_info( + "/packages/pkg/1.0/file.tar.gz", "unknown" + ) == ("pkg", "1.0") diff --git a/tests/test_webhooks.py b/tests/test_webhooks.py index ab3015f..993292e 100644 --- a/tests/test_webhooks.py +++ b/tests/test_webhooks.py @@ -82,3 +82,52 @@ async def test_webhook_component_no_version(client, sample_nexus_component_webho resp = await client.post("/webhooks/nexus", json=sample_nexus_component_webhook) assert resp.status_code == 200 assert resp.json()["status"] == "ignored" + + +# --- Ecosystem detection tests --- + +def test_detect_ecosystem_pypi(): + from guarddog_nexus.routes.webhooks import _detect_ecosystem + + assert _detect_ecosystem({"format": "pypi"}) == "pypi" + assert _detect_ecosystem({"format": "pip"}) == "pypi" + assert _detect_ecosystem({"format": "python"}) == "pypi" + + +def test_detect_ecosystem_go(): + from guarddog_nexus.routes.webhooks import _detect_ecosystem + + assert _detect_ecosystem({"format": "go"}) == "go" + assert _detect_ecosystem({"format": "golang"}) == "go" + + +def test_detect_ecosystem_npm(): + from guarddog_nexus.routes.webhooks import _detect_ecosystem + + assert _detect_ecosystem({"format": "npm"}) == "npm" + assert _detect_ecosystem({"format": "node"}) == "npm" + + +def test_detect_ecosystem_unknown(): + from guarddog_nexus.routes.webhooks import _detect_ecosystem + + assert _detect_ecosystem({"format": "maven"}) == "maven" + assert _detect_ecosystem({}) == "pypi" # default + + +# --- Go/npm webhook integration --- + +@pytest.mark.asyncio +async def test_webhook_go_asset(client, sample_nexus_go_webhook): + with patch("guarddog_nexus.routes.webhooks._scan_in_background") as _mock: + resp = await client.post("/webhooks/nexus", json=sample_nexus_go_webhook) + assert resp.status_code == 200 + assert resp.json()["status"] == "accepted" + + +@pytest.mark.asyncio +async def test_webhook_npm_asset(client, sample_nexus_npm_webhook): + with patch("guarddog_nexus.routes.webhooks._scan_in_background") as _mock: + resp = await client.post("/webhooks/nexus", json=sample_nexus_npm_webhook) + assert resp.status_code == 200 + assert resp.json()["status"] == "accepted"