import os import tempfile import time from uuid import uuid4 test_id = uuid4().hex os.environ["DATABASE_URL"] = f"sqlite:///./test_{test_id}.db" os.environ["LOCAL_ONLY_MODE"] = "true" os.environ["STORAGE_DIR"] = os.path.join(tempfile.gettempdir(), f"speech_machine_test_{test_id}") from fastapi.testclient import TestClient from app.db import Base, engine from app.main import app client = TestClient(app) Base.metadata.create_all(bind=engine) _headers: dict[str, str] | None = None def get_headers() -> dict[str, str]: global _headers if _headers is not None: return _headers status = client.get("/setup/status").json() if status["setup_required"]: res = client.post("/setup", json={"email": "presenter@example.com", "password": "secret123"}) assert res.status_code == 201 else: client.post("/auth/signup", json={"email": "presenter@example.com", "password": "secret123"}) login = client.post("/auth/login", json={"email": "presenter@example.com", "password": "secret123"}) assert login.status_code == 200 _headers = {"Authorization": f"Bearer {login.json()['access_token']}"} return _headers def test_curriculum_profile_and_30_day_plan_auto_completion(): headers = get_headers() profile_res = client.get("/curriculum/profile", headers=headers) assert profile_res.status_code == 200 assert profile_res.json()["target_wpm_min"] == 125 update_res = client.put( "/curriculum/profile", json={ "display_name": "Ada Presenter", "primary_role": "Staff Systems Engineer", "focus_track": "keynote_clarity", "target_wpm_min": 130, "target_wpm_max": 155, "daily_minutes_goal": 20, "weaknesses": ["filler_words", "eye_contact", "rushing_pace"], }, headers=headers, ) assert update_res.status_code == 200 assert update_res.json()["display_name"] == "Ada Presenter" assert update_res.json()["focus_track"] == "keynote_clarity" plan_res = client.get("/curriculum/plan", headers=headers) assert plan_res.status_code == 200 plan = plan_res.json() assert plan["summary"]["total_days"] == 30 assert plan["summary"]["recommended_day"] == 1 assert len(plan["days"]) == 30 # Save a Day 1 session and verify Day 1 auto-completes and Day 2 becomes recommended sess_res = client.post( "/sessions", json={ "title": "Day 1: Baseline 60-Second Tech Intro", "mode": "Pitch", "curriculum_day": 1, "target_keywords": ["building", "technology", "problem", "matters"], "duration_ms": 60000, "eye_contact_pct": 0.55, "transcript_segments": [ { "start_ms": 0, "end_ms": 30000, "text": "Hi, um I am building a technology product because solving this problem really matters.", }, { "start_ms": 30000, "end_ms": 60000, "text": "First we measure speaking pace, um like second we track camera eye contact, therefore every rep improves.", }, ], "metrics": [ {"t": 1, "wpm": 138, "rms": 55, "eye_contact": 0.55, "smile": 0.4, "yaw": 0.2, "pitch": 0.1, "roll": 0.1, "filler_count": 3, "pause_ms": 900} ], }, headers=headers, ) assert sess_res.status_code == 200 day1_session_id = sess_res.json()["id"] assert sess_res.json()["curriculum_day"] == 1 assert sess_res.json()["transcription_status"] == "complete" updated_plan = client.get("/curriculum/plan", headers=headers).json() assert updated_plan["summary"]["completed_days"] >= 1 assert updated_plan["summary"]["recommended_day"] == 2 assert updated_plan["days"][0]["completed"] is True assert updated_plan["days"][0]["completed_session_id"] == day1_session_id note_res = client.post( "/curriculum/days/1/complete", json={"reflection_notes": "Felt much calmer speaking right to the lens."}, headers=headers, ) assert note_res.status_code == 200 assert "calmer" in note_res.json()["reflection_notes"] def test_targeted_exercises_and_tech_news_storyboard(monkeypatch): headers = get_headers() exercises_res = client.get("/curriculum/exercises", headers=headers) assert exercises_res.status_code == 200 ex = exercises_res.json() assert len(ex["daily_warmups"]) >= 4 assert len(ex["camera_challenges"]) >= 5 assert len(ex["targeted_exercises"]) >= 2 # Avoid network dependency in unit test for news feed from app.services import tech_news tech_news._FEED_CACHE["timestamp"] = time.time() tech_news._FEED_CACHE["items"] = [] feed_res = client.get("/news/feed", headers=headers) assert feed_res.status_code == 200 feed = feed_res.json() assert len(feed["stories"]) >= 6 assert len(feed["frameworks"]) == 4 for fw in ("briefing_60s", "explainer_2m", "contrarian_take", "war_story"): sb_res = client.post( "/news/storyboard", json={ "headline": "SQLite WAL Mode Scales Single-Node Web Apps", "summary": "Modern NVMe disks let single-node SQLite handle tens of thousands of reads per second.", "category": "Systems & Cloud", "framework": fw, "personal_angle": "Start simple before adopting distributed microservices.", }, headers=headers, ) assert sb_res.status_code == 200 sb = sb_res.json() assert len(sb["sections"]) == 4 assert "SQLite" in sb["teleprompter_script"] assert len(sb["target_keywords"]) >= 3 def test_auto_builtin_coaching_transcript_patch_weekly_review_and_compare(): headers = get_headers() s1 = client.post( "/sessions", json={ "title": "Take 1 — Rushed & Filler-Heavy", "mode": "Storytelling", "duration_ms": 60000, "eye_contact_pct": 0.45, "transcript_segments": [ { "start_ms": 0, "end_ms": 60000, "text": "Um like basically you know we built an API um like actually very fast um yeah.", } ], "metrics": [ {"t": 1, "wpm": 175, "rms": 45, "eye_contact": 0.45, "smile": 0.2, "yaw": 0.5, "pitch": 0.4, "roll": 0.2, "filler_count": 6, "pause_ms": 0} ], }, headers=headers, ).json() # Auto coaching generates immediately using built-in presenter coach coach_res = client.get(f"/coaching/session/{s1['id']}?auto=true", headers=headers) assert coach_res.status_code == 200 coach = coach_res.json() assert coach["status"] == "completed" assert len(coach["strengths"]) >= 2 assert len(coach["improvements"]) >= 2 assert len(coach["drills"]) >= 1 # Patch transcript with cleaner take and verify score + metrics recompute s2 = client.post( "/sessions", json={ "title": "Take 2 — Crisp Keynote Structure", "mode": "Pitch", "duration_ms": 60000, "eye_contact_pct": 0.82, "transcript_segments": [], "metrics": [ {"t": 1, "wpm": 142, "rms": 62, "eye_contact": 0.82, "smile": 0.55, "yaw": 0.1, "pitch": 0.1, "roll": 0.1, "filler_count": 0, "pause_ms": 850} ], }, headers=headers, ).json() patch_res = client.patch( f"/sessions/{s2['id']}/transcript", json={ "transcript_segments": [ { "start_ms": 0, "end_ms": 30000, "text": "First, our engineering team simplified database queries because tail latency hurt mobile users.", }, { "start_ms": 30000, "end_ms": 60000, "text": "Second, response times dropped by seventy percent. Therefore, every page now loads under one second.", }, ] }, headers=headers, ) assert patch_res.status_code == 200 assert patch_res.json()["transcription_status"] == "complete" assert patch_res.json()["score"]["total"] > s1["score"]["total"] # Compare Take 1 vs Take 2 cmp_res = client.get(f"/dashboard/compare?session_a={s1['id']}&session_b={s2['id']}", headers=headers) assert cmp_res.status_code == 200 cmp_data = cmp_res.json() assert cmp_data["comparison"]["overall_score"]["delta"] > 0 assert len(cmp_data["insights"]) >= 1 assert cmp_data["session_b"]["title"] == "Take 2 — Crisp Keynote Structure" # Weekly review + reflection note weekly_res = client.get("/dashboard/weekly-review", headers=headers) assert weekly_res.status_code == 200 weekly = weekly_res.json() assert weekly["current_week"]["session_count"] >= 2 assert len(weekly["wins"]) >= 1 note_save = client.put( "/dashboard/weekly-review/note", json={ "week_start_date": weekly["week_start_date"], "wins_note": "Cut fillers dramatically on Take 2!", "focus_next_week": "Practice 2-minute Tech News explainers from bullet cues.", }, headers=headers, ) assert note_save.status_code == 200 assert "Cut fillers" in note_save.json()["wins_note"]