Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a028f0-a42f-7582-89a8-ebaad7343834
160 lines
5.2 KiB
Python
160 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from sbom_nexus.api import create_app
|
|
|
|
|
|
def client_for(tmp_path: Path) -> TestClient:
|
|
return TestClient(create_app(tmp_path / "nexus.db"))
|
|
|
|
|
|
def register(
|
|
client: TestClient,
|
|
slug: str,
|
|
*,
|
|
checkout_path: str | None = None,
|
|
last_sbom_at: datetime | None = None,
|
|
active: bool = True,
|
|
) -> None:
|
|
response = client.put(
|
|
f"/repositories/{slug}",
|
|
json={
|
|
"checkout_path": checkout_path,
|
|
"last_sbom_at": last_sbom_at.isoformat() if last_sbom_at else None,
|
|
"active": active,
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
|
|
def test_health_and_legacy_ingest_query_and_licence_report(tmp_path: Path) -> None:
|
|
client = client_for(tmp_path)
|
|
assert client.get("/state/health").json() == {
|
|
"status": "ok",
|
|
"store": "connected",
|
|
"dialect": "sqlite",
|
|
}
|
|
register(client, "demo")
|
|
|
|
response = client.post(
|
|
"/sbom/ingest/",
|
|
json={
|
|
"repo_slug": "demo",
|
|
"entries": [
|
|
{
|
|
"package_name": "safe",
|
|
"package_version": "1.0",
|
|
"ecosystem": "python",
|
|
"license_spdx": "MIT",
|
|
"is_direct": True,
|
|
"is_dev": False,
|
|
},
|
|
{
|
|
"package_name": "copyleft",
|
|
"package_version": "2.0",
|
|
"ecosystem": "python",
|
|
"license_spdx": "GPL-3.0-only",
|
|
"is_direct": True,
|
|
"is_dev": False,
|
|
},
|
|
],
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["ingested"] == 2
|
|
assert len(client.get("/sbom/snapshots/?repo_slug=demo").json()) == 1
|
|
assert client.get("/sbom/demo").json()["entry_count"] == 2
|
|
assert len(client.get("/sbom/?license_spdx=MIT").json()) == 1
|
|
report = client.get("/sbom/report/licences/").json()
|
|
assert report["copyleft_direct_count"] == 1
|
|
assert report["groups"][0]["repos"] == ["demo"]
|
|
|
|
|
|
def test_catch_up_is_bounded_null_first_then_oldest(tmp_path: Path) -> None:
|
|
client = client_for(tmp_path)
|
|
now = datetime.now(UTC)
|
|
register(client, "never-b")
|
|
register(client, "never-a")
|
|
register(client, "oldest", last_sbom_at=now - timedelta(days=90))
|
|
register(client, "old", last_sbom_at=now - timedelta(days=60))
|
|
register(client, "fresh", last_sbom_at=now - timedelta(days=2))
|
|
register(client, "inactive", active=False)
|
|
|
|
result = client.get("/sbom/catch-up?limit=3").json()
|
|
|
|
assert [repo["repo_slug"] for repo in result["repos"]] == [
|
|
"never-a",
|
|
"never-b",
|
|
"oldest",
|
|
]
|
|
assert result["selected_count"] == 3
|
|
assert result["stale_count"] == 4
|
|
assert result["never_count"] == 2
|
|
assert result["total_count"] == 5
|
|
|
|
|
|
def test_recorded_skip_advances_queue_without_claiming_success(tmp_path: Path) -> None:
|
|
client = client_for(tmp_path)
|
|
register(client, "a-no-checkout")
|
|
register(client, "b-never")
|
|
|
|
before = client.get("/sbom/catch-up?limit=1").json()
|
|
assert before["repos"][0]["repo_slug"] == "a-no-checkout"
|
|
|
|
outcome = client.post("/sbom/a-no-checkout/ingest").json()
|
|
assert outcome["status"] == "skipped"
|
|
assert outcome["reason"] == "no-checkout"
|
|
|
|
after = client.get("/sbom/catch-up?limit=1").json()
|
|
assert after["repos"][0]["repo_slug"] == "b-never"
|
|
repo = client.get("/sbom/a-no-checkout").json()
|
|
assert repo["last_attempt_at"] is not None
|
|
assert repo["last_success_at"] is None
|
|
|
|
|
|
def test_checkout_scan_records_provenance_and_success(tmp_path: Path) -> None:
|
|
repo = tmp_path / "checkout"
|
|
repo.mkdir()
|
|
(repo / "requirements.txt").write_text("fastapi==0.136.1\n", encoding="utf-8")
|
|
client = client_for(tmp_path)
|
|
register(client, "checkout", checkout_path=str(repo))
|
|
|
|
outcome = client.post("/sbom/checkout/ingest")
|
|
|
|
assert outcome.status_code == 200
|
|
assert outcome.json()["status"] == "ingested"
|
|
detail = client.get(f"/sbom/snapshots/{outcome.json()['snapshot_id']}").json()
|
|
assert detail["sources"][0]["path"] == "requirements.txt"
|
|
assert detail["sources"][0]["sha256"]
|
|
assert detail["entries"][0]["package_name"] == "fastapi"
|
|
assert detail["entries"][0]["snapshot_at"] == detail["snapshot_at"]
|
|
|
|
|
|
def test_historical_import_is_idempotent(tmp_path: Path) -> None:
|
|
client = client_for(tmp_path)
|
|
payload = {
|
|
"repo_slug": "legacy",
|
|
"legacy_id": "017f8f04-84c2-4e58-8be1-33a4cb43c43f",
|
|
"snapshot_at": "2026-01-02T03:04:05Z",
|
|
"source": "state-hub:manual",
|
|
"entries": [
|
|
{
|
|
"package_name": "legacy-package",
|
|
"package_version": "1.0",
|
|
"ecosystem": "other",
|
|
}
|
|
],
|
|
}
|
|
|
|
first = client.post("/sbom/import/", json=payload)
|
|
second = client.post("/sbom/import/", json=payload)
|
|
|
|
assert first.status_code == 200
|
|
assert first.json()["imported"] is True
|
|
assert second.json()["imported"] is False
|
|
assert len(client.get("/sbom/snapshots/?repo_slug=legacy").json()) == 1
|