Add FastAPI catalog backend (games/snapshots/diff) + tests
Modular-monolith backend over SQLAlchemy (SQLite by default, Postgres-ready
via DATABASE_URL). The full snapshot.json is stored verbatim; diffing reads it
back through the ams.diff engine, so the DB never mirrors the snapshot schema.
- ams.api.db/models/schemas/service : Game 1-N Snapshot, sha256-deduped upsert
- routes: POST/GET /games, POST/GET /snapshots (import, deduped), GET /diff
(?old&new[&owner]) running compute_diff on stored snapshots, /health
- ams.api.importer : bulk CLI loader (python -m ams.api.importer --game ...)
- run: uvicorn ams.api.app:create_app --factory
11 tests pass (6 diff + 5 API via TestClient over the golden pair). Smoke-tested
live on uvicorn: import -> /snapshots -> /diff returns the BlooMoo deltas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
70
tests/test_api.py
Normal file
70
tests/test_api.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""API tests against a temp SQLite DB, exercising import -> list -> diff over the golden pair."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("fastapi")
|
||||
from fastapi.testclient import TestClient # noqa: E402
|
||||
|
||||
from ams.api.app import create_app # noqa: E402
|
||||
|
||||
SNAP_DIR = Path(__file__).resolve().parents[1] / "snapshots"
|
||||
PIKLIB = SNAP_DIR / "PIKLIB8.dll.snapshot.json"
|
||||
BLOOMOO = SNAP_DIR / "bloomoodll.dll.snapshot.json"
|
||||
|
||||
pytestmark = pytest.mark.skipif(not PIKLIB.exists(), reason="golden snapshots not present")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(tmp_path) -> TestClient:
|
||||
app = create_app(database_url="sqlite:///{0}/api.db".format(tmp_path))
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def _load(path: Path) -> dict:
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
return json.load(fh)
|
||||
|
||||
|
||||
def test_health(client: TestClient):
|
||||
assert client.get("/health").json()["status"] == "ok"
|
||||
|
||||
|
||||
def test_import_list_and_dedup(client: TestClient):
|
||||
r = client.post("/snapshots", params={"game": "Reksio i UFO"}, json=_load(PIKLIB))
|
||||
assert r.status_code == 201
|
||||
first_id = r.json()["id"]
|
||||
assert r.json()["engine"] == "Piklib" and r.json()["n_types"] == 40
|
||||
|
||||
# re-importing the same binary (same sha256) updates in place, not a duplicate row
|
||||
r2 = client.post("/snapshots", json=_load(PIKLIB))
|
||||
assert r2.json()["id"] == first_id
|
||||
assert len(client.get("/snapshots").json()) == 1
|
||||
|
||||
# the game was auto-created and linked
|
||||
games = client.get("/games").json()
|
||||
assert [g["name"] for g in games] == ["Reksio i UFO"]
|
||||
|
||||
|
||||
def test_invalid_snapshot_rejected(client: TestClient):
|
||||
assert client.post("/snapshots", json={"not": "a snapshot"}).status_code == 422
|
||||
|
||||
|
||||
def test_diff_endpoint(client: TestClient):
|
||||
a = client.post("/snapshots", params={"game": "Reksio i UFO"}, json=_load(PIKLIB)).json()["id"]
|
||||
b = client.post("/snapshots", params={"game": "Reksio i Kapitan Nemo"}, json=_load(BLOOMOO)).json()["id"]
|
||||
|
||||
diff = client.get("/diff", params={"old": a, "new": b}).json()
|
||||
added = {t["script_name"] for t in diff["types"]["added"]}
|
||||
assert {"GRBUFFER", "INTERNET"} <= added
|
||||
|
||||
animo = client.get("/diff", params={"old": a, "new": b, "owner": "CMC_Animo"}).json()
|
||||
assert "GETFPS" in {m["name"] for m in animo["methods"]["added"]}
|
||||
|
||||
|
||||
def test_diff_missing_snapshot_404(client: TestClient):
|
||||
assert client.get("/diff", params={"old": 999, "new": 998}).status_code == 404
|
||||
Reference in New Issue
Block a user