feat: add reversible SBOM Nexus read facade
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a028f0-a42f-7582-89a8-ebaad7343834
This commit is contained in:
parent
9e7e91300f
commit
5fc4c56215
7 changed files with 368 additions and 3 deletions
167
tests/test_sbom_nexus_compat.py
Normal file
167
tests/test_sbom_nexus_compat.py
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
"""Compatibility coverage for the reversible SBOM Nexus read facade."""
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from api.config import settings
|
||||
from api.services.sbom_nexus import SBOMNexusError
|
||||
|
||||
|
||||
async def _create_repo(client) -> dict:
|
||||
domain = await client.post(
|
||||
"/domains/", json={"slug": "sbom-test", "name": "SBOM Test"}
|
||||
)
|
||||
assert domain.status_code == 201
|
||||
repo = await client.post(
|
||||
"/repos/",
|
||||
json={
|
||||
"slug": "testrepo",
|
||||
"name": "Test Repo",
|
||||
"domain_slug": "sbom-test",
|
||||
"local_path": "/tmp/testrepo",
|
||||
},
|
||||
)
|
||||
assert repo.status_code == 201
|
||||
return repo.json()
|
||||
|
||||
|
||||
def _entry(snapshot_id: str, nexus_repo_id: str) -> dict:
|
||||
return {
|
||||
"id": str(uuid.uuid4()),
|
||||
"repo_id": nexus_repo_id,
|
||||
"repo_slug": "testrepo",
|
||||
"snapshot_id": snapshot_id,
|
||||
"package_name": "fastapi",
|
||||
"package_version": "0.115.0",
|
||||
"ecosystem": "python",
|
||||
"license_spdx": "MIT",
|
||||
"is_direct": True,
|
||||
"is_dev": False,
|
||||
"snapshot_at": "2026-08-22T12:00:00+00:00",
|
||||
"created_at": "2026-08-22T12:00:01+00:00",
|
||||
"source_path": "uv.lock",
|
||||
}
|
||||
|
||||
|
||||
async def test_nexus_read_mode_preserves_state_hub_response_contract(client, monkeypatch):
|
||||
repo = await _create_repo(client)
|
||||
snapshot_id = str(uuid.uuid4())
|
||||
nexus_repo_id = str(uuid.uuid4())
|
||||
entry = _entry(snapshot_id, nexus_repo_id)
|
||||
snapshot = {
|
||||
"id": snapshot_id,
|
||||
"repo_id": nexus_repo_id,
|
||||
"repo_slug": "testrepo",
|
||||
"snapshot_at": "2026-08-22T12:00:00+00:00",
|
||||
"source": "state-hub-import",
|
||||
"entry_count": 1,
|
||||
"created_at": "2026-08-22T12:00:01+00:00",
|
||||
"status": "imported",
|
||||
}
|
||||
|
||||
async def fake_get(path: str, *, params=None):
|
||||
if path == "/sbom/snapshots/":
|
||||
assert params == {"repo_slug": "testrepo"}
|
||||
return [snapshot]
|
||||
if path == f"/sbom/snapshots/{snapshot_id}":
|
||||
return {**snapshot, "entries": [entry]}
|
||||
if path == "/sbom/":
|
||||
assert params == {
|
||||
"repo_slug": "testrepo",
|
||||
"ecosystem": "python",
|
||||
"is_direct": True,
|
||||
}
|
||||
return [entry]
|
||||
if path == "/sbom/report/licences/":
|
||||
return {
|
||||
"groups": [
|
||||
{
|
||||
"license_spdx": "MIT",
|
||||
"count": 1,
|
||||
"repos": ["testrepo"],
|
||||
"is_copyleft": False,
|
||||
}
|
||||
],
|
||||
"copyleft_direct_count": 0,
|
||||
"signal_qualification": "extra Nexus field",
|
||||
}
|
||||
if path == "/sbom/testrepo":
|
||||
return {
|
||||
"repo_slug": "testrepo",
|
||||
"last_sbom_at": "2026-08-22T12:00:00+00:00",
|
||||
"last_attempt_at": "2026-08-22T12:00:00+00:00",
|
||||
"entry_count": 1,
|
||||
"entries": [entry],
|
||||
}
|
||||
raise AssertionError(f"unexpected Nexus path {path}")
|
||||
|
||||
monkeypatch.setattr(settings, "sbom_nexus_read_mode", "nexus")
|
||||
monkeypatch.setattr("api.routers.sbom.get_json", fake_get)
|
||||
|
||||
snapshots = await client.get("/sbom/snapshots/?repo_slug=testrepo")
|
||||
detail = await client.get(f"/sbom/snapshots/{snapshot_id}")
|
||||
entries = await client.get(
|
||||
"/sbom/?repo_slug=testrepo&ecosystem=python&is_direct=true"
|
||||
)
|
||||
licences = await client.get("/sbom/report/licences/")
|
||||
repo_view = await client.get("/sbom/testrepo")
|
||||
|
||||
assert snapshots.status_code == 200
|
||||
assert detail.status_code == 200
|
||||
assert entries.status_code == 200
|
||||
assert licences.status_code == 200
|
||||
assert repo_view.status_code == 200
|
||||
assert snapshots.json()[0]["id"] == snapshot_id
|
||||
assert snapshots.json()[0]["repo_id"] == repo["id"]
|
||||
assert detail.json()["entries"][0]["repo_id"] == repo["id"]
|
||||
assert entries.json()[0]["repo_id"] == repo["id"]
|
||||
assert "source_path" not in entries.json()[0]
|
||||
assert set(licences.json()) == {"groups", "copyleft_direct_count"}
|
||||
assert repo_view.json()["entries"][0]["repo_id"] == repo["id"]
|
||||
assert set(repo_view.json()) == {
|
||||
"repo_slug",
|
||||
"last_sbom_at",
|
||||
"entry_count",
|
||||
"entries",
|
||||
}
|
||||
|
||||
|
||||
async def test_nexus_read_mode_does_not_move_ingest_write_authority(client, monkeypatch):
|
||||
await _create_repo(client)
|
||||
|
||||
async def unexpected_get(*args, **kwargs):
|
||||
raise AssertionError("POST /sbom/ingest/ must not call SBOM Nexus")
|
||||
|
||||
monkeypatch.setattr(settings, "sbom_nexus_read_mode", "nexus")
|
||||
monkeypatch.setattr("api.routers.sbom.get_json", unexpected_get)
|
||||
response = await client.post(
|
||||
"/sbom/ingest/",
|
||||
json={
|
||||
"repo_slug": "testrepo",
|
||||
"entries": [
|
||||
{
|
||||
"package_name": "fastapi",
|
||||
"package_version": "0.115.0",
|
||||
"ecosystem": "python",
|
||||
"license_spdx": "MIT",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["ingested"] == 1
|
||||
|
||||
|
||||
async def test_nexus_failure_is_visible_and_does_not_fall_back(client, monkeypatch):
|
||||
await _create_repo(client)
|
||||
|
||||
async def unavailable(*args, **kwargs):
|
||||
raise SBOMNexusError(502, "SBOM Nexus is unavailable: ConnectError")
|
||||
|
||||
monkeypatch.setattr(settings, "sbom_nexus_read_mode", "nexus")
|
||||
monkeypatch.setattr("api.routers.sbom.get_json", unavailable)
|
||||
response = await client.get("/sbom/snapshots/")
|
||||
|
||||
assert response.status_code == 502
|
||||
assert response.json()["detail"] == "SBOM Nexus is unavailable: ConnectError"
|
||||
Loading…
Add table
Add a link
Reference in a new issue