chorus/backend/tests/test_items_api.py
pyr0ball a90c3fc172 feat(backend): add FastAPI routes for items
Wraps the Task 2 CRUD layer in a FastAPI app (schemas.py + main.py) with
POST/GET/GET-by-id/PATCH routes for items, matching the exact paths and
status codes the Discord bot's HTTP client will depend on.

Also fixes a latent bug in db.get_engine: sqlite:///:memory: without a
StaticPool gives each new session a fresh, empty database, which broke
as soon as more than one session shared an engine (the API's per-request
session pattern). Tasks 1-2 never hit this because their tests used a
single session per engine.
2026-07-13 13:43:09 -07:00

70 lines
2.3 KiB
Python

import pytest
from fastapi.testclient import TestClient
from app.db import get_engine, make_session_factory, init_db
from app.main import create_app
@pytest.fixture()
def client():
engine = get_engine("sqlite:///:memory:")
init_db(engine)
SessionLocal = make_session_factory(engine)
app = create_app(SessionLocal)
return TestClient(app)
def test_create_item_returns_201(client):
resp = client.post("/items", json={
"modality": "bh_email",
"raw_content": "Books to donate",
"captured_at": "2026-07-13T07:00:00Z",
"discord_message_id": "abc-1",
})
assert resp.status_code == 201
body = resp.json()
assert body["stage"] == "new"
assert body["modality"] == "bh_email"
def test_create_item_idempotent_returns_200(client):
payload = {
"modality": "bh_email", "raw_content": "x",
"captured_at": "2026-07-13T07:00:00Z", "discord_message_id": "dup-9",
}
first = client.post("/items", json=payload)
second = client.post("/items", json=payload)
assert first.status_code == 201
assert second.status_code == 200
assert first.json()["id"] == second.json()["id"]
def test_list_items_excludes_done_by_default(client):
resp = client.post("/items", json={
"modality": "voice", "raw_content": "call note",
"captured_at": "2026-07-13T07:00:00Z", "discord_message_id": "v-1",
})
item_id = resp.json()["id"]
client.patch(f"/items/{item_id}", json={"type": "other"})
client.patch(f"/items/{item_id}", json={"stage": "done"})
listed = client.get("/items").json()
assert item_id not in [i["id"] for i in listed]
listed_all = client.get("/items?include_done=true").json()
assert item_id in [i["id"] for i in listed_all]
def test_patch_invalid_stage_returns_422(client):
resp = client.post("/items", json={
"modality": "voice", "raw_content": "x",
"captured_at": "2026-07-13T07:00:00Z", "discord_message_id": "v-2",
})
item_id = resp.json()["id"]
client.patch(f"/items/{item_id}", json={"type": "other"})
bad = client.patch(f"/items/{item_id}", json={"stage": "pickup_scheduled"})
assert bad.status_code == 422
def test_get_missing_item_returns_404(client):
resp = client.get("/items/999999")
assert resp.status_code == 404