feat(simulator): FastAPI app with /api/select and /api/catalog/meta

Per docs/superpowers/plans/2026-06-04-experience-simulator.md Task 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-05 03:42:13 -07:00
parent aadfccf32d
commit cdf36c9b57
2 changed files with 186 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
import pytest
from fastapi.testclient import TestClient
from hef.catalog import Record
from simulator.app import create_app
def make_record(**overrides):
base = dict(
id="r",
title="t",
source_url="u",
source_archive="internet_archive",
license="public_domain",
mode="video",
left=0,
right=0,
dark=0,
light=0,
duration_s=600,
file_path="",
)
base.update(overrides)
return Record(**base)
@pytest.fixture
def client():
records = [
make_record(id="v-near", mode="video", left=0, right=0, dark=0, light=0),
make_record(id="v-far", mode="video", left=4, right=4, dark=4, light=4),
make_record(id="a-one", mode="audio", left=1, right=1, dark=1, light=1),
make_record(id="prop", mode="video", left=0, right=0, dark=0, light=1,
review_status="proposed"),
make_record(id="appr", mode="video", left=0, right=0, dark=0, light=1,
review_status="approved"),
]
return TestClient(create_app(records=records))
def _body(**overrides):
base = dict(left=0, right=0, dark=0, light=0, mode="video")
base.update(overrides)
return base
def test_select_returns_pick_and_ranked_pool(client):
resp = client.post("/api/select", json=_body(mode="video", pool_size=4))
assert resp.status_code == 200
data = resp.json()
assert data["pick"]["id"] == "v-near"
ids = [c["record"]["id"] for c in data["pool"]]
assert ids[0] == "v-near"
assert all("distance" in c and "rank" in c for c in data["pool"])
assert [c["rank"] for c in data["pool"]] == list(range(1, len(data["pool"]) + 1))
def test_none_mode_is_the_void(client):
resp = client.post("/api/select", json=_body(mode="none"))
assert resp.status_code == 200
data = resp.json()
assert data["pick"] is None
assert data["pool"] == []
def test_dial_out_of_range_is_rejected(client):
resp = client.post("/api/select", json=_body(left=7))
assert resp.status_code == 422
def test_bad_mode_is_rejected(client):
resp = client.post("/api/select", json=_body(mode="banana"))
assert resp.status_code == 422
def test_approved_only_narrows_pool(client):
resp = client.post("/api/select", json=_body(left=0, right=0, dark=0, light=1,
mode="video", approved_only=True))
data = resp.json()
assert all(c["record"]["review_status"] == "approved" for c in data["pool"])
def test_catalog_meta_reports_counts(client):
resp = client.get("/api/catalog/meta")
assert resp.status_code == 200
data = resp.json()
assert data["total"] == 5
assert data["by_mode"]["video"] == 4
assert data["by_mode"]["audio"] == 1
assert set(data["by_status"]) == {"proposed", "approved"}