"""Tests for the discovery CSV/reprocess endpoints. Run under the X-API-Key admin identity so they don't depend on the Google OAuth flow. The DiscoveryExtractor is replaced with a fake so no network call or API key is needed. QUESTIONS_DIR / QUESTIONS_MASTER_CSV are pointed at tmp_path so the suite never writes into the repo's data dir. """ import os from app.services import answer_store API_KEY = {"X-API-Key": "test-api-key"} class FakeExtractor: """Stand-in for DiscoveryExtractor: returns a fixed well-formed profile.""" def __init__(self, api_key=None, model=None): pass async def extract(self, responses): return { "triad": "gut", "probable_type": 8, "wing": 9, "instinctual_variant": "sp", "instinctual_stack": "sp/so/sx", "love_summary": "love", "strength_summary": "strength", "mission_summary": "mission", "vocation_summary": "vocation", "overlap_narrative": "narrative", "short_term_goals": "short", "long_term_goals": "long", "confidence": { "triad": "high", "type": "high", "variant": "medium", "ikigai": "high", }, "extraction_notes": "ok", } def _patch(monkeypatch, tmp_path): monkeypatch.setenv("QUESTIONS_DIR", str(tmp_path / "questions")) monkeypatch.setenv("QUESTIONS_MASTER_CSV", str(tmp_path / "master.csv")) monkeypatch.setattr( "app.routers.discovery.DiscoveryExtractor", FakeExtractor ) async def _start_and_respond(app_client): start = await app_client.post("/discovery/start", headers=API_KEY) assert start.status_code == 200 conv_id = start.json()["conversation_id"] r = await app_client.put( f"/discovery/{conv_id}/respond", headers=API_KEY, json={ "prompt_alive": "I felt alive building things", "prompt_friction": "unfairness bothers me", "prompt_goals_short": "ship the app", }, ) assert r.status_code == 200 return conv_id async def test_respond_writes_csv(app_client, monkeypatch, tmp_path): _patch(monkeypatch, tmp_path) conv_id = await _start_and_respond(app_client) assert os.path.exists(answer_store.conversation_csv_path(conv_id)) answers = answer_store.read_conversation_answers(conv_id) assert answers["alive"] == "I felt alive building things" assert os.path.exists(str(tmp_path / "master.csv")) async def test_get_answers(app_client, monkeypatch, tmp_path): _patch(monkeypatch, tmp_path) conv_id = await _start_and_respond(app_client) r = await app_client.get(f"/discovery/{conv_id}/answers", headers=API_KEY) assert r.status_code == 200 body = r.json() assert body["conversation_id"] == conv_id assert body["completed_at"] is None by_key = {a["prompt_key"]: a["answer"] for a in body["answers"]} assert by_key["friction"] == "unfairness bothers me" assert len(body["answers"]) == 7 async def test_download_answers_csv(app_client, monkeypatch, tmp_path): _patch(monkeypatch, tmp_path) conv_id = await _start_and_respond(app_client) r = await app_client.get( f"/discovery/{conv_id}/answers.csv", headers=API_KEY ) assert r.status_code == 200 assert r.headers["content-type"].startswith("text/csv") assert "attachment" in r.headers["content-disposition"] assert "ship the app" in r.text async def test_reprocess_creates_profile(app_client, monkeypatch, tmp_path): _patch(monkeypatch, tmp_path) conv_id = await _start_and_respond(app_client) r = await app_client.post( f"/discovery/{conv_id}/reprocess", headers=API_KEY ) assert r.status_code == 200 body = r.json() assert body["conversation_id"] == conv_id assert body["triad"] == "gut" assert body["probable_type"] == 8 # A profile now exists for the user. prof = await app_client.get("/discovery/profile/me", headers=API_KEY) assert prof.status_code == 200 async def test_reprocess_locked_returns_409(app_client, monkeypatch, tmp_path): _patch(monkeypatch, tmp_path) conv_id = await _start_and_respond(app_client) # Complete to create a profile, then lock it via confirm. complete = await app_client.post( f"/discovery/{conv_id}/complete", headers=API_KEY ) assert complete.status_code == 200 confirm = await app_client.put( "/discovery/profile/me/confirm", headers=API_KEY ) assert confirm.status_code == 200 r = await app_client.post( f"/discovery/{conv_id}/reprocess", headers=API_KEY ) assert r.status_code == 409 async def test_complete_writes_completed_csv(app_client, monkeypatch, tmp_path): _patch(monkeypatch, tmp_path) conv_id = await _start_and_respond(app_client) complete = await app_client.post( f"/discovery/{conv_id}/complete", headers=API_KEY ) assert complete.status_code == 200 # The answers endpoint now reports a completion timestamp. answers = await app_client.get( f"/discovery/{conv_id}/answers", headers=API_KEY ) assert answers.json()["completed_at"] is not None