refactor: реструктуризация — core/, db/, routes/, web/
guarddog_nexus/ ├── core/ scanner, harvester, nexus, llm ├── db/ engine, models, queries ├── routes/ webhooks, api_*, web └── web/ templates + static - 11 файлов перемещено (git mv — сохранена история) - Все импорты обновлены (~15 файлов) - main.py, tests — исправлены пути - 50/50 тестов, ruff clean
This commit is contained in:
@@ -9,17 +9,17 @@ import tempfile
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from guarddog_nexus.config import config
|
from ..config import config
|
||||||
from guarddog_nexus.constants import (
|
from ..constants import (
|
||||||
DEFAULT_ECOSYSTEM,
|
DEFAULT_ECOSYSTEM,
|
||||||
ERROR_MESSAGE_MAX_LENGTH,
|
ERROR_MESSAGE_MAX_LENGTH,
|
||||||
PACKAGE_EXTENSIONS,
|
PACKAGE_EXTENSIONS,
|
||||||
SCAN_ERROR_DOWNLOAD_FAILED,
|
SCAN_ERROR_DOWNLOAD_FAILED,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.logging_setup import log
|
from ..db.models import Finding, Scan, ScanStatus
|
||||||
from guarddog_nexus.models import Finding, Scan, ScanStatus
|
from ..logging_setup import log
|
||||||
from guarddog_nexus.nexus_client import compute_sha256, download_asset, extract_package_info
|
from .nexus import compute_sha256, download_asset, extract_package_info
|
||||||
from guarddog_nexus.scanner import scan_package
|
from .scanner import scan_package
|
||||||
|
|
||||||
# Per-URL locks to avoid parallel scans of the same asset
|
# Per-URL locks to avoid parallel scans of the same asset
|
||||||
_url_locks: dict[str, asyncio.Lock] = {}
|
_url_locks: dict[str, asyncio.Lock] = {}
|
||||||
@@ -193,7 +193,7 @@ async def harvest(
|
|||||||
|
|
||||||
async def _run_llm_analysis(findings: list[Finding], session: AsyncSession) -> list[dict]:
|
async def _run_llm_analysis(findings: list[Finding], session: AsyncSession) -> list[dict]:
|
||||||
"""Run LLM analysis on findings and persist reports to the database."""
|
"""Run LLM analysis on findings and persist reports to the database."""
|
||||||
from guarddog_nexus.llm import analyze_finding
|
from .llm import analyze_finding
|
||||||
|
|
||||||
reports = []
|
reports = []
|
||||||
for finding in findings:
|
for finding in findings:
|
||||||
@@ -7,9 +7,9 @@ import json
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from guarddog_nexus.config import config
|
from ..config import config
|
||||||
from guarddog_nexus.constants import LLM_ANALYSIS_SYSTEM_PROMPT
|
from ..constants import LLM_ANALYSIS_SYSTEM_PROMPT
|
||||||
from guarddog_nexus.logging_setup import log
|
from ..logging_setup import log
|
||||||
|
|
||||||
|
|
||||||
def _build_user_message(finding: dict) -> str:
|
def _build_user_message(finding: dict) -> str:
|
||||||
@@ -5,13 +5,13 @@ import os
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from guarddog_nexus.config import config
|
from ..config import config
|
||||||
from guarddog_nexus.constants import (
|
from ..constants import (
|
||||||
NPM_PATH_PREFIX,
|
NPM_PATH_PREFIX,
|
||||||
PYPI_PATH_PREFIX,
|
PYPI_PATH_PREFIX,
|
||||||
SHA256_CHUNK_SIZE,
|
SHA256_CHUNK_SIZE,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.logging_setup import log
|
from ..logging_setup import log
|
||||||
|
|
||||||
|
|
||||||
def extract_pypi_info(asset_path: str) -> tuple[str, str] | None:
|
def extract_pypi_info(asset_path: str) -> tuple[str, str] | None:
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from guarddog_nexus.config import config
|
from ..config import config
|
||||||
from guarddog_nexus.constants import (
|
from ..constants import (
|
||||||
DEFAULT_ECOSYSTEM,
|
DEFAULT_ECOSYSTEM,
|
||||||
DEFAULT_FINDING_SEVERITY,
|
DEFAULT_FINDING_SEVERITY,
|
||||||
GUARDDOG_OUTPUT_FORMAT,
|
GUARDDOG_OUTPUT_FORMAT,
|
||||||
@@ -14,7 +14,7 @@ from guarddog_nexus.constants import (
|
|||||||
SCAN_ERROR_JSON_PARSE,
|
SCAN_ERROR_JSON_PARSE,
|
||||||
SCAN_ERROR_TIMEOUT,
|
SCAN_ERROR_TIMEOUT,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.logging_setup import log
|
from ..logging_setup import log
|
||||||
|
|
||||||
|
|
||||||
async def scan_package(filepath: str, ecosystem: str = DEFAULT_ECOSYSTEM) -> dict:
|
async def scan_package(filepath: str, ecosystem: str = DEFAULT_ECOSYSTEM) -> dict:
|
||||||
@@ -20,7 +20,7 @@ class Base(DeclarativeBase):
|
|||||||
|
|
||||||
async def _migrate():
|
async def _migrate():
|
||||||
"""Add any missing columns from model definitions to existing SQLite tables."""
|
"""Add any missing columns from model definitions to existing SQLite tables."""
|
||||||
import guarddog_nexus.models # noqa: F401
|
import guarddog_nexus.db.models # noqa: F401
|
||||||
|
|
||||||
async with _engine.connect() as conn:
|
async with _engine.connect() as conn:
|
||||||
for table in Base.metadata.sorted_tables:
|
for table in Base.metadata.sorted_tables:
|
||||||
@@ -63,7 +63,7 @@ async def _migrate():
|
|||||||
|
|
||||||
|
|
||||||
async def init_db():
|
async def init_db():
|
||||||
import guarddog_nexus.models # noqa: F401
|
import guarddog_nexus.db.models # noqa: F401
|
||||||
|
|
||||||
async with _engine.begin() as conn:
|
async with _engine.begin() as conn:
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
@@ -6,7 +6,7 @@ from enum import Enum
|
|||||||
from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, Integer, String, Text, func
|
from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, Integer, String, Text, func
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from guarddog_nexus.database import Base
|
from guarddog_nexus.db.engine import Base
|
||||||
|
|
||||||
|
|
||||||
class ScanStatus(str, Enum):
|
class ScanStatus(str, Enum):
|
||||||
@@ -20,7 +20,7 @@ from guarddog_nexus.constants import (
|
|||||||
SCAN_SORT_FIELDS,
|
SCAN_SORT_FIELDS,
|
||||||
TOP_RULES_LIMIT,
|
TOP_RULES_LIMIT,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.models import Finding, Scan
|
from guarddog_nexus.db.models import Finding, Scan
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Scan list query builder
|
# Scan list query builder
|
||||||
@@ -5,8 +5,8 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
from logging.handlers import SysLogHandler
|
from logging.handlers import SysLogHandler
|
||||||
|
|
||||||
from guarddog_nexus.config import config
|
from .config import config
|
||||||
from guarddog_nexus.constants import APP_PACKAGE
|
from .constants import APP_PACKAGE
|
||||||
|
|
||||||
|
|
||||||
class JsonFormatter(logging.Formatter):
|
class JsonFormatter(logging.Formatter):
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import uvicorn
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from guarddog_nexus.api import findings, packages, scans
|
|
||||||
from guarddog_nexus.config import config
|
from guarddog_nexus.config import config
|
||||||
from guarddog_nexus.constants import APP_DESCRIPTION, APP_NAME, APP_PACKAGE, STATIC_MOUNT_PATH
|
from guarddog_nexus.constants import APP_DESCRIPTION, APP_NAME, APP_PACKAGE, STATIC_MOUNT_PATH
|
||||||
from guarddog_nexus.database import init_db
|
from guarddog_nexus.db.engine import init_db
|
||||||
from guarddog_nexus.logging_setup import log
|
from guarddog_nexus.logging_setup import log
|
||||||
from guarddog_nexus.web.routes import router as web_router
|
from guarddog_nexus.routes import api_findings, api_packages, api_scans
|
||||||
from guarddog_nexus.webhooks import router as webhook_router
|
from guarddog_nexus.routes.web import router as web_router
|
||||||
|
from guarddog_nexus.routes.webhooks import router as webhook_router
|
||||||
|
|
||||||
STATIC_DIR = os.path.join(os.path.dirname(__file__), "web", "static")
|
STATIC_DIR = os.path.join(os.path.dirname(__file__), "web", "static")
|
||||||
|
|
||||||
@@ -34,9 +34,9 @@ app = FastAPI(
|
|||||||
)
|
)
|
||||||
|
|
||||||
app.include_router(webhook_router)
|
app.include_router(webhook_router)
|
||||||
app.include_router(scans.router)
|
app.include_router(api_scans.router)
|
||||||
app.include_router(packages.router)
|
app.include_router(api_packages.router)
|
||||||
app.include_router(findings.router)
|
app.include_router(api_findings.router)
|
||||||
app.include_router(web_router)
|
app.include_router(web_router)
|
||||||
|
|
||||||
if os.path.isdir(STATIC_DIR):
|
if os.path.isdir(STATIC_DIR):
|
||||||
|
|||||||
0
guarddog_nexus/routes/__init__.py
Normal file
0
guarddog_nexus/routes/__init__.py
Normal file
@@ -4,16 +4,16 @@ from fastapi import APIRouter, Depends, Query
|
|||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from guarddog_nexus.config import config
|
from ..config import config
|
||||||
from guarddog_nexus.constants import (
|
from ..constants import (
|
||||||
DEFAULT_OFFSET,
|
DEFAULT_OFFSET,
|
||||||
DEFAULT_PAGE_SIZE,
|
DEFAULT_PAGE_SIZE,
|
||||||
JSON_PATH_RULE,
|
JSON_PATH_RULE,
|
||||||
JSON_PATH_SEVERITY,
|
JSON_PATH_SEVERITY,
|
||||||
MAX_PAGE_SIZE,
|
MAX_PAGE_SIZE,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.database import get_session
|
from ..db.engine import get_session
|
||||||
from guarddog_nexus.models import Finding
|
from ..db.models import Finding
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1/findings", tags=["findings"])
|
router = APIRouter(prefix="/api/v1/findings", tags=["findings"])
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ async def analyze_finding_endpoint(
|
|||||||
if not finding:
|
if not finding:
|
||||||
return {"detail": "Not found"}
|
return {"detail": "Not found"}
|
||||||
|
|
||||||
from guarddog_nexus.llm import analyze_finding
|
from ..core.llm import analyze_finding
|
||||||
|
|
||||||
report = await analyze_finding(finding.data)
|
report = await analyze_finding(finding.data)
|
||||||
if report is None:
|
if report is None:
|
||||||
@@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, Query, Response
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from guarddog_nexus.constants import (
|
from ..constants import (
|
||||||
CSV_MEDIA_TYPE,
|
CSV_MEDIA_TYPE,
|
||||||
DEFAULT_OFFSET,
|
DEFAULT_OFFSET,
|
||||||
DEFAULT_PAGE_SIZE,
|
DEFAULT_PAGE_SIZE,
|
||||||
@@ -16,9 +16,9 @@ from guarddog_nexus.constants import (
|
|||||||
DEFAULT_SORT_DIR,
|
DEFAULT_SORT_DIR,
|
||||||
MAX_PAGE_SIZE,
|
MAX_PAGE_SIZE,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.database import get_session
|
from ..db.engine import get_session
|
||||||
from guarddog_nexus.models import Finding, Scan
|
from ..db.models import Finding, Scan
|
||||||
from guarddog_nexus.queries import build_package_list_query
|
from ..db.queries import build_package_list_query
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1/packages", tags=["packages"])
|
router = APIRouter(prefix="/api/v1/packages", tags=["packages"])
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ from sqlalchemy import select
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from guarddog_nexus.constants import (
|
from ..constants import (
|
||||||
CSV_MEDIA_TYPE,
|
CSV_MEDIA_TYPE,
|
||||||
DEFAULT_OFFSET,
|
DEFAULT_OFFSET,
|
||||||
DEFAULT_PAGE_SIZE,
|
DEFAULT_PAGE_SIZE,
|
||||||
@@ -16,9 +16,9 @@ from guarddog_nexus.constants import (
|
|||||||
DEFAULT_SORT_DIR,
|
DEFAULT_SORT_DIR,
|
||||||
MAX_PAGE_SIZE,
|
MAX_PAGE_SIZE,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.database import get_session
|
from ..db.engine import get_session
|
||||||
from guarddog_nexus.models import Scan
|
from ..db.models import Scan
|
||||||
from guarddog_nexus.queries import build_scan_list_query, get_dashboard_stats
|
from ..db.queries import build_scan_list_query, get_dashboard_stats
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1/scans", tags=["scans"])
|
router = APIRouter(prefix="/api/v1/scans", tags=["scans"])
|
||||||
|
|
||||||
@@ -8,16 +8,16 @@ from jinja2 import Environment, PackageLoader, select_autoescape
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from guarddog_nexus.constants import (
|
from ..constants import (
|
||||||
APP_PACKAGE,
|
APP_PACKAGE,
|
||||||
DEFAULT_SORT_BY_PACKAGES,
|
DEFAULT_SORT_BY_PACKAGES,
|
||||||
DEFAULT_SORT_BY_SCANS,
|
DEFAULT_SORT_BY_SCANS,
|
||||||
DEFAULT_SORT_DIR,
|
DEFAULT_SORT_DIR,
|
||||||
WEB_PER_PAGE,
|
WEB_PER_PAGE,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.database import get_session
|
from ..db.engine import get_session
|
||||||
from guarddog_nexus.models import Finding, Scan
|
from ..db.models import Finding, Scan
|
||||||
from guarddog_nexus.queries import (
|
from ..db.queries import (
|
||||||
build_package_list_query,
|
build_package_list_query,
|
||||||
build_scan_list_query,
|
build_scan_list_query,
|
||||||
get_dashboard_stats,
|
get_dashboard_stats,
|
||||||
@@ -206,8 +206,8 @@ async def analyze_finding_htmx(
|
|||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""HTMX fragment: trigger LLM analysis and return styled result HTML."""
|
"""HTMX fragment: trigger LLM analysis and return styled result HTML."""
|
||||||
from guarddog_nexus.config import config
|
from ..config import config
|
||||||
from guarddog_nexus.llm import analyze_finding
|
from ..core.llm import analyze_finding
|
||||||
|
|
||||||
if not config.llm_enabled:
|
if not config.llm_enabled:
|
||||||
return HTMLResponse(
|
return HTMLResponse(
|
||||||
@@ -7,8 +7,8 @@ import re
|
|||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, Header, HTTPException, Request, status
|
from fastapi import APIRouter, BackgroundTasks, Header, HTTPException, Request, status
|
||||||
|
|
||||||
from guarddog_nexus.config import config
|
from ..config import config
|
||||||
from guarddog_nexus.constants import (
|
from ..constants import (
|
||||||
DEFAULT_ECOSYSTEM,
|
DEFAULT_ECOSYSTEM,
|
||||||
METADATA_PATTERNS,
|
METADATA_PATTERNS,
|
||||||
PACKAGE_EXTENSIONS,
|
PACKAGE_EXTENSIONS,
|
||||||
@@ -19,9 +19,9 @@ from guarddog_nexus.constants import (
|
|||||||
WEBHOOK_STATUS_ACCEPTED,
|
WEBHOOK_STATUS_ACCEPTED,
|
||||||
WEBHOOK_STATUS_IGNORED,
|
WEBHOOK_STATUS_IGNORED,
|
||||||
)
|
)
|
||||||
from guarddog_nexus.database import get_session
|
from ..core.harvester import harvest
|
||||||
from guarddog_nexus.harvester import harvest
|
from ..db.engine import get_session
|
||||||
from guarddog_nexus.logging_setup import log
|
from ..logging_setup import log
|
||||||
|
|
||||||
router = APIRouter(prefix="/webhooks", tags=["webhooks"])
|
router = APIRouter(prefix="/webhooks", tags=["webhooks"])
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ async def nexus_webhook(
|
|||||||
|
|
||||||
|
|
||||||
async def _scan_component(repository: str, name: str, version: str, ecosystem: str):
|
async def _scan_component(repository: str, name: str, version: str, ecosystem: str):
|
||||||
from guarddog_nexus.nexus_client import nexus_get
|
from ..core.nexus import nexus_get
|
||||||
|
|
||||||
api_path = (
|
api_path = (
|
||||||
f"/service/rest/v1/search"
|
f"/service/rest/v1/search"
|
||||||
@@ -18,9 +18,9 @@ os.environ["LOG_SYSLOG_HOST"] = ""
|
|||||||
os.environ["TEMP_DIR"] = "/tmp/guarddog-nexus-test"
|
os.environ["TEMP_DIR"] = "/tmp/guarddog-nexus-test"
|
||||||
|
|
||||||
from guarddog_nexus.constants import DEFAULT_ECOSYSTEM, SEVERITY_WARNING # noqa: E402
|
from guarddog_nexus.constants import DEFAULT_ECOSYSTEM, SEVERITY_WARNING # noqa: E402
|
||||||
from guarddog_nexus.database import Base, get_session # noqa: E402
|
from guarddog_nexus.db.engine import Base, get_session # noqa: E402
|
||||||
|
from guarddog_nexus.db.models import Finding, Scan, ScanStatus # noqa: E402
|
||||||
from guarddog_nexus.main import app # noqa: E402
|
from guarddog_nexus.main import app # noqa: E402
|
||||||
from guarddog_nexus.models import Finding, Scan, ScanStatus # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ from unittest.mock import patch
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from guarddog_nexus.harvester import harvest
|
from guarddog_nexus.core.harvester import harvest
|
||||||
from guarddog_nexus.models import Finding
|
from guarddog_nexus.db.models import Finding
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_harvest_new_package(db_session, guarddog_normalized_flagged):
|
async def test_harvest_new_package(db_session, guarddog_normalized_flagged):
|
||||||
with (
|
with (
|
||||||
patch("guarddog_nexus.harvester.download_asset") as mock_dl,
|
patch("guarddog_nexus.core.harvester.download_asset") as mock_dl,
|
||||||
patch("guarddog_nexus.harvester.compute_sha256") as mock_sha,
|
patch("guarddog_nexus.core.harvester.compute_sha256") as mock_sha,
|
||||||
patch("guarddog_nexus.harvester.scan_package") as mock_scan,
|
patch("guarddog_nexus.core.harvester.scan_package") as mock_scan,
|
||||||
):
|
):
|
||||||
mock_dl.return_value = "/tmp/test-package.tar.gz"
|
mock_dl.return_value = "/tmp/test-package.tar.gz"
|
||||||
mock_sha.return_value = "abc123"
|
mock_sha.return_value = "abc123"
|
||||||
@@ -50,9 +50,9 @@ async def test_harvest_new_package(db_session, guarddog_normalized_flagged):
|
|||||||
async def test_harvest_same_sha256_skips(db_session, guarddog_normalized_flagged):
|
async def test_harvest_same_sha256_skips(db_session, guarddog_normalized_flagged):
|
||||||
"""Same SHA256 as existing scan → skip, don't re-scan."""
|
"""Same SHA256 as existing scan → skip, don't re-scan."""
|
||||||
with (
|
with (
|
||||||
patch("guarddog_nexus.harvester.download_asset") as mock_dl,
|
patch("guarddog_nexus.core.harvester.download_asset") as mock_dl,
|
||||||
patch("guarddog_nexus.harvester.compute_sha256") as mock_sha,
|
patch("guarddog_nexus.core.harvester.compute_sha256") as mock_sha,
|
||||||
patch("guarddog_nexus.harvester.scan_package") as mock_scan,
|
patch("guarddog_nexus.core.harvester.scan_package") as mock_scan,
|
||||||
):
|
):
|
||||||
mock_dl.return_value = "/tmp/test.tar.gz"
|
mock_dl.return_value = "/tmp/test.tar.gz"
|
||||||
mock_sha.return_value = "deadbeef"
|
mock_sha.return_value = "deadbeef"
|
||||||
@@ -86,9 +86,9 @@ async def test_harvest_same_sha256_skips(db_session, guarddog_normalized_flagged
|
|||||||
async def test_harvest_different_sha256_scans_again(db_session, guarddog_normalized_flagged):
|
async def test_harvest_different_sha256_scans_again(db_session, guarddog_normalized_flagged):
|
||||||
"""Same name/version, different SHA256 → new scan."""
|
"""Same name/version, different SHA256 → new scan."""
|
||||||
with (
|
with (
|
||||||
patch("guarddog_nexus.harvester.download_asset") as mock_dl,
|
patch("guarddog_nexus.core.harvester.download_asset") as mock_dl,
|
||||||
patch("guarddog_nexus.harvester.compute_sha256") as mock_sha,
|
patch("guarddog_nexus.core.harvester.compute_sha256") as mock_sha,
|
||||||
patch("guarddog_nexus.harvester.scan_package") as mock_scan,
|
patch("guarddog_nexus.core.harvester.scan_package") as mock_scan,
|
||||||
):
|
):
|
||||||
mock_dl.return_value = "/tmp/test.tar.gz"
|
mock_dl.return_value = "/tmp/test.tar.gz"
|
||||||
mock_scan.return_value = guarddog_normalized_flagged
|
mock_scan.return_value = guarddog_normalized_flagged
|
||||||
@@ -123,9 +123,9 @@ async def test_harvest_different_sha256_scans_again(db_session, guarddog_normali
|
|||||||
async def test_harvest_skips_active_scan_same_url(db_session, guarddog_normalized_flagged):
|
async def test_harvest_skips_active_scan_same_url(db_session, guarddog_normalized_flagged):
|
||||||
"""Concurrent webhooks for same URL: first proceeding, second skips as PENDING."""
|
"""Concurrent webhooks for same URL: first proceeding, second skips as PENDING."""
|
||||||
with (
|
with (
|
||||||
patch("guarddog_nexus.harvester.download_asset") as mock_dl,
|
patch("guarddog_nexus.core.harvester.download_asset") as mock_dl,
|
||||||
patch("guarddog_nexus.harvester.compute_sha256") as mock_sha,
|
patch("guarddog_nexus.core.harvester.compute_sha256") as mock_sha,
|
||||||
patch("guarddog_nexus.harvester.scan_package") as mock_scan,
|
patch("guarddog_nexus.core.harvester.scan_package") as mock_scan,
|
||||||
):
|
):
|
||||||
mock_dl.return_value = "/tmp/test.tar.gz"
|
mock_dl.return_value = "/tmp/test.tar.gz"
|
||||||
mock_sha.return_value = "aaa"
|
mock_sha.return_value = "aaa"
|
||||||
@@ -147,9 +147,9 @@ async def test_harvest_skips_active_scan_same_url(db_session, guarddog_normalize
|
|||||||
async def test_harvest_same_url_sha256_dedup(db_session, guarddog_normalized_flagged):
|
async def test_harvest_same_url_sha256_dedup(db_session, guarddog_normalized_flagged):
|
||||||
"""Same URL twice: second run hits SHA256 dedup (first already completed)."""
|
"""Same URL twice: second run hits SHA256 dedup (first already completed)."""
|
||||||
with (
|
with (
|
||||||
patch("guarddog_nexus.harvester.download_asset") as mock_dl,
|
patch("guarddog_nexus.core.harvester.download_asset") as mock_dl,
|
||||||
patch("guarddog_nexus.harvester.compute_sha256") as mock_sha,
|
patch("guarddog_nexus.core.harvester.compute_sha256") as mock_sha,
|
||||||
patch("guarddog_nexus.harvester.scan_package") as mock_scan,
|
patch("guarddog_nexus.core.harvester.scan_package") as mock_scan,
|
||||||
):
|
):
|
||||||
mock_dl.return_value = "/tmp/test.tar.gz"
|
mock_dl.return_value = "/tmp/test.tar.gz"
|
||||||
mock_sha.return_value = "ccc"
|
mock_sha.return_value = "ccc"
|
||||||
@@ -182,9 +182,9 @@ async def test_harvest_same_url_sha256_dedup(db_session, guarddog_normalized_fla
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_harvest_clean_package(db_session, guarddog_normalized_clean):
|
async def test_harvest_clean_package(db_session, guarddog_normalized_clean):
|
||||||
with (
|
with (
|
||||||
patch("guarddog_nexus.harvester.download_asset") as mock_dl,
|
patch("guarddog_nexus.core.harvester.download_asset") as mock_dl,
|
||||||
patch("guarddog_nexus.harvester.compute_sha256") as mock_sha,
|
patch("guarddog_nexus.core.harvester.compute_sha256") as mock_sha,
|
||||||
patch("guarddog_nexus.harvester.scan_package") as mock_scan,
|
patch("guarddog_nexus.core.harvester.scan_package") as mock_scan,
|
||||||
):
|
):
|
||||||
mock_dl.return_value = "/tmp/test.tar.gz"
|
mock_dl.return_value = "/tmp/test.tar.gz"
|
||||||
mock_sha.return_value = "abc"
|
mock_sha.return_value = "abc"
|
||||||
@@ -205,7 +205,7 @@ async def test_harvest_clean_package(db_session, guarddog_normalized_clean):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_harvest_download_failure(db_session):
|
async def test_harvest_download_failure(db_session):
|
||||||
with patch("guarddog_nexus.harvester.download_asset") as mock_dl:
|
with patch("guarddog_nexus.core.harvester.download_asset") as mock_dl:
|
||||||
mock_dl.return_value = None
|
mock_dl.return_value = None
|
||||||
|
|
||||||
scan = await harvest(
|
scan = await harvest(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Tests for GuardDog scanner integration."""
|
"""Tests for GuardDog scanner integration."""
|
||||||
|
|
||||||
from guarddog_nexus.scanner import _normalize_output
|
from guarddog_nexus.core.scanner import _normalize_output
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_clean_output(guarddog_output_clean):
|
def test_normalize_clean_output(guarddog_output_clean):
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async def test_webhook_ignores_created_action(client, sample_nexus_webhook):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_webhook_accepts_asset_updated(client, sample_nexus_webhook):
|
async def test_webhook_accepts_asset_updated(client, sample_nexus_webhook):
|
||||||
sample_nexus_webhook["action"] = "UPDATED"
|
sample_nexus_webhook["action"] = "UPDATED"
|
||||||
with patch("guarddog_nexus.webhooks._scan_in_background") as _mock:
|
with patch("guarddog_nexus.routes.webhooks._scan_in_background") as _mock:
|
||||||
resp = await client.post("/webhooks/nexus", json=sample_nexus_webhook)
|
resp = await client.post("/webhooks/nexus", json=sample_nexus_webhook)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert resp.json()["status"] == "accepted"
|
assert resp.json()["status"] == "accepted"
|
||||||
@@ -68,7 +68,7 @@ async def test_webhook_no_asset_or_component(client):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_webhook_accepts_component(client, sample_nexus_component_webhook):
|
async def test_webhook_accepts_component(client, sample_nexus_component_webhook):
|
||||||
with patch("guarddog_nexus.webhooks._scan_component") as _mock:
|
with patch("guarddog_nexus.routes.webhooks._scan_component") as _mock:
|
||||||
resp = await client.post("/webhooks/nexus", json=sample_nexus_component_webhook)
|
resp = await client.post("/webhooks/nexus", json=sample_nexus_component_webhook)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
|
|||||||
Reference in New Issue
Block a user