feat: API routes — speakers, rooms, search, RAG

Full implementations replacing stubs: speakers CRUD with utterance count,
rooms list + mute/unmute via pipeline, FTS5-based search, RAG /ask and
/summary endpoints. 15 route tests (8 new) all passing; 35 total.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 10:46:24 -05:00
parent 4def6a526c
commit 59014f6fe7
8 changed files with 221 additions and 4 deletions
+21
View File
@@ -0,0 +1,21 @@
def test_ask_returns_response(api_client, seeded_db, mocker):
mock_post = mocker.patch("app.api.routes.rag.httpx.post")
mock_post.return_value.json.return_value = {
"message": {"content": "You talked about groceries."}
}
mock_post.return_value.raise_for_status = lambda: None
room = seeded_db.execute("SELECT id FROM rooms WHERE name='Kitchen'").fetchone()
seeded_db.execute(
"INSERT INTO utterances (room_id, transcript, match_status, start_time, end_time) VALUES (?,?,?,?,?)",
(room["id"], "we need milk from the store", "unknown", "2026-01-01 10:00:00", "2026-01-01 10:00:03"),
)
seeded_db.commit()
resp = api_client.post("/api/ask", json={"question": "What did we need from the store?"})
assert resp.status_code == 200
data = resp.json()
assert "answer" in data
def test_get_summary_not_found(api_client):
resp = api_client.get("/api/summary/2026-01-01")
assert resp.status_code == 404
+14
View File
@@ -0,0 +1,14 @@
def test_list_rooms(api_client, seeded_db):
resp = api_client.get("/api/rooms")
assert resp.status_code == 200
data = resp.json()
assert any(r["name"] == "Kitchen" for r in data)
def test_mute_room_calls_pipeline(api_client, seeded_db, mocker):
mock_post = mocker.patch("app.api.routes.rooms.httpx.post")
mock_post.return_value.status_code = 200
room = seeded_db.execute("SELECT id FROM rooms WHERE name='Kitchen'").fetchone()
resp = api_client.post(f"/api/rooms/{room['id']}/mute")
assert resp.status_code == 200
mock_post.assert_called_once()
+18
View File
@@ -0,0 +1,18 @@
def test_search_returns_matches(api_client, seeded_db):
room = seeded_db.execute("SELECT id FROM rooms WHERE name='Kitchen'").fetchone()
seeded_db.execute(
"INSERT INTO utterances (room_id, transcript, match_status, start_time, end_time) VALUES (?,?,?,?,?)",
(room["id"], "the grocery list is ready", "unknown", "2026-01-01 10:00:00", "2026-01-01 10:00:03"),
)
seeded_db.commit()
resp = api_client.get("/api/search?q=grocery")
assert resp.status_code == 200
data = resp.json()
assert len(data) == 1
assert "grocery" in data[0]["transcript"]
def test_search_no_results(api_client):
resp = api_client.get("/api/search?q=xyznotfound")
assert resp.status_code == 200
assert resp.json() == []
+26
View File
@@ -0,0 +1,26 @@
def test_list_speakers_empty(api_client):
resp = api_client.get("/api/speakers")
assert resp.status_code == 200
assert resp.json() == []
def test_create_speaker(api_client):
resp = api_client.post("/api/speakers", json={"name": "Alice"})
assert resp.status_code == 200
data = resp.json()
assert data["name"] == "Alice"
assert "id" in data
def test_list_speakers_includes_utterance_count(api_client, seeded_db):
room = seeded_db.execute("SELECT id FROM rooms WHERE name='Kitchen'").fetchone()
speaker = seeded_db.execute("SELECT id FROM speakers WHERE name='Jeremy'").fetchone()
seeded_db.execute(
"INSERT INTO utterances (room_id, speaker_id, transcript, match_status, start_time, end_time) VALUES (?,?,?,?,?,?)",
(room["id"], speaker["id"], "hi", "known", "2026-01-01 10:00:00", "2026-01-01 10:00:01"),
)
seeded_db.commit()
resp = api_client.get("/api/speakers")
data = resp.json()
jeremy = next(s for s in data if s["name"] == "Jeremy")
assert jeremy["utterance_count"] == 1