feat: prepare postgres sbom cutover
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a028f0-a42f-7582-89a8-ebaad7343834
This commit is contained in:
parent
cf7e3acb78
commit
ba535e1f8f
26 changed files with 1573 additions and 411 deletions
|
|
@ -33,7 +33,11 @@ def register(
|
|||
|
||||
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"}
|
||||
assert client.get("/state/health").json() == {
|
||||
"status": "ok",
|
||||
"store": "connected",
|
||||
"dialect": "sqlite",
|
||||
}
|
||||
register(client, "demo")
|
||||
|
||||
response = client.post(
|
||||
|
|
|
|||
103
tests/test_import_state_hub.py
Normal file
103
tests/test_import_state_hub.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from sbom_nexus import importer
|
||||
|
||||
|
||||
def test_import_reconciles_history_licences_and_is_idempotent(monkeypatch) -> None:
|
||||
target_snapshots: list[dict[str, Any]] = []
|
||||
groups = [
|
||||
{
|
||||
"license_spdx": "MIT",
|
||||
"count": 1,
|
||||
"repos": ["demo"],
|
||||
"is_copyleft": False,
|
||||
},
|
||||
{
|
||||
"license_spdx": None,
|
||||
"count": 2,
|
||||
"repos": ["zeta", "demo"],
|
||||
"is_copyleft": False,
|
||||
},
|
||||
]
|
||||
|
||||
def fake_request(
|
||||
base_url: str,
|
||||
path: str,
|
||||
*,
|
||||
method: str = "GET",
|
||||
body: dict[str, Any] | None = None,
|
||||
) -> Any:
|
||||
if base_url == "source":
|
||||
if path == "/repos/":
|
||||
return [
|
||||
{
|
||||
"id": "repo-id",
|
||||
"slug": "demo",
|
||||
"status": "active",
|
||||
"local_path": "/repos/demo",
|
||||
}
|
||||
]
|
||||
if path == "/sbom/snapshots/":
|
||||
return [
|
||||
{
|
||||
"id": "snapshot-id",
|
||||
"repo_id": "repo-id",
|
||||
"snapshot_at": "2026-01-02T03:04:05Z",
|
||||
"source": "manual",
|
||||
"entry_count": 1,
|
||||
}
|
||||
]
|
||||
if path == "/sbom/snapshots/snapshot-id":
|
||||
return {
|
||||
"entries": [
|
||||
{
|
||||
"package_name": "example",
|
||||
"package_version": "1.0",
|
||||
"ecosystem": "other",
|
||||
"license_spdx": "MIT",
|
||||
"is_direct": True,
|
||||
"is_dev": False,
|
||||
}
|
||||
]
|
||||
}
|
||||
if path == "/sbom/report/licences/":
|
||||
return {"groups": list(reversed(groups)), "copyleft_direct_count": 0}
|
||||
if base_url == "target":
|
||||
if method == "PUT":
|
||||
return {"slug": "demo"}
|
||||
if method == "POST" and path == "/sbom/import/":
|
||||
assert body is not None
|
||||
already_present = any(
|
||||
snapshot["legacy_id"] == body["legacy_id"]
|
||||
for snapshot in target_snapshots
|
||||
)
|
||||
if not already_present:
|
||||
target_snapshots.append(
|
||||
{
|
||||
"legacy_id": body["legacy_id"],
|
||||
"repo_slug": body["repo_slug"],
|
||||
"snapshot_at": body["snapshot_at"],
|
||||
"entry_count": len(body["entries"]),
|
||||
}
|
||||
)
|
||||
return {"imported": not already_present}
|
||||
if path == "/sbom/snapshots/":
|
||||
return target_snapshots
|
||||
if path == "/sbom/report/licences/":
|
||||
return {"groups": groups, "copyleft_direct_count": 0}
|
||||
raise AssertionError((base_url, method, path))
|
||||
|
||||
monkeypatch.setattr(importer, "request_json", fake_request)
|
||||
|
||||
first = importer.import_history("source", "target", dry_run=False)
|
||||
second = importer.import_history("source", "target", dry_run=False)
|
||||
|
||||
assert first["ok"] is True
|
||||
assert first["results"] == {"imported": 1}
|
||||
assert first["snapshot_reconciliation"]["matched_snapshot_count"] == 1
|
||||
assert first["licence_reconciliation"]["groups_match"] is True
|
||||
assert second["ok"] is True
|
||||
assert second["results"] == {"already_present": 1}
|
||||
assert len(target_snapshots) == 1
|
||||
38
tests/test_migrations.py
Normal file
38
tests/test_migrations.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
from sqlalchemy import create_engine, inspect
|
||||
|
||||
|
||||
def migration_config(database_path: Path) -> Config:
|
||||
config = Config("alembic.ini")
|
||||
config.set_main_option("sqlalchemy.url", f"sqlite:///{database_path}")
|
||||
return config
|
||||
|
||||
|
||||
def test_initial_migration_upgrades_and_downgrades(tmp_path: Path) -> None:
|
||||
database_path = tmp_path / "migrated.db"
|
||||
config = migration_config(database_path)
|
||||
|
||||
command.upgrade(config, "head")
|
||||
|
||||
engine = create_engine(f"sqlite:///{database_path}")
|
||||
assert set(inspect(engine).get_table_names()) == {
|
||||
"alembic_version",
|
||||
"entries",
|
||||
"repositories",
|
||||
"snapshots",
|
||||
}
|
||||
assert {index["name"] for index in inspect(engine).get_indexes("entries")} == {
|
||||
"ix_entries_license",
|
||||
"ix_entries_repo",
|
||||
"ix_entries_snapshot",
|
||||
}
|
||||
command.check(config)
|
||||
|
||||
command.downgrade(config, "base")
|
||||
|
||||
assert set(inspect(engine).get_table_names()) == {"alembic_version"}
|
||||
52
tests/test_postgres_integration.py
Normal file
52
tests/test_postgres_integration.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from sbom_nexus.api import create_app
|
||||
|
||||
POSTGRES_URL = os.getenv("SBOM_NEXUS_TEST_POSTGRES_URL")
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not POSTGRES_URL,
|
||||
reason="SBOM_NEXUS_TEST_POSTGRES_URL is not configured",
|
||||
)
|
||||
|
||||
|
||||
def test_migrated_postgres_runtime_contract() -> None:
|
||||
assert POSTGRES_URL is not None
|
||||
client = TestClient(create_app(POSTGRES_URL))
|
||||
|
||||
assert client.get("/state/health").json() == {
|
||||
"status": "ok",
|
||||
"store": "connected",
|
||||
"dialect": "postgresql",
|
||||
}
|
||||
response = client.put(
|
||||
"/repositories/postgres-contract",
|
||||
json={"active": True},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
ingest = client.post(
|
||||
"/sbom/ingest/",
|
||||
json={
|
||||
"repo_slug": "postgres-contract",
|
||||
"entries": [
|
||||
{
|
||||
"package_name": "psycopg",
|
||||
"package_version": "3.2",
|
||||
"ecosystem": "python",
|
||||
"license_spdx": "LGPL-3.0-only",
|
||||
"is_direct": True,
|
||||
"is_dev": False,
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
assert ingest.status_code == 200
|
||||
assert ingest.json()["ingested"] == 1
|
||||
assert client.get("/sbom/postgres-contract").json()["entry_count"] == 1
|
||||
assert client.get("/sbom/report/licences/").json()["copyleft_direct_count"] == 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue