diff --git a/apps/api/tests/test_v1_features.py b/apps/api/tests/test_v1_features.py index 611c6c6..139a63e 100644 --- a/apps/api/tests/test_v1_features.py +++ b/apps/api/tests/test_v1_features.py @@ -279,14 +279,22 @@ class TestInterviewPrep: class TestSeedDemo: def test_seed_demo_creates_data(self, client): - """POST /concierge/seed-demo creates profile, postings, applications.""" + """POST /concierge/seed-demo creates profile, postings, applications, and extended data.""" resp = client.post("/api/concierge/seed-demo") assert resp.status_code == 200 data = resp.json() assert data["profile"] == "Demo Demosson" - assert data["postings"] == 6 - assert data["applications"] == 6 assert data["sections"] == 4 + # 6 standalone + 3 agency + 2 deadline + 1 redflag + 1 interviewing = 13 postings + assert data["postings"] == 13 + # 6 standalone + 3 agency + 2 deadline + 1 redflag + 1 interviewing = 13 applications + assert data["applications"] == 13 + assert data["clusters"] >= 1 + assert data["deadlines"] >= 2 + assert data["suggestions"] == 2 + assert data["notifications"] == 3 + assert data["task_runs"] == 6 + assert data["cv_artifacts"] >= 1 def test_seed_demo_idempotent(self, client): """Running seed-demo twice returns the same counts.""" @@ -298,6 +306,12 @@ class TestSeedDemo: assert resp2.json()["postings"] == resp1.json()["postings"] assert resp2.json()["applications"] == resp1.json()["applications"] assert resp2.json()["sections"] == resp1.json()["sections"] + assert resp2.json()["clusters"] == resp1.json()["clusters"] + assert resp2.json()["deadlines"] == resp1.json()["deadlines"] + assert resp2.json()["suggestions"] == resp1.json()["suggestions"] + assert resp2.json()["notifications"] == resp1.json()["notifications"] + assert resp2.json()["task_runs"] == resp1.json()["task_runs"] + assert resp2.json()["cv_artifacts"] == resp1.json()["cv_artifacts"] def test_seed_demo_has_nudge_candidate(self, client): """After seeding, /today should show a nudge for the backdated sent app.""" @@ -313,6 +327,104 @@ class TestSeedDemo: digest = resp.json()["digest"] assert len(digest) >= 1 + # --- WS1: Extended seed demo tests (8 new) --- + + def test_seed_demo_agency_cluster_present(self, client): + """Seed creates a 3-posting agency cluster with the same cluster_id.""" + client.post("/api/concierge/seed-demo") + postings = client.get("/api/postings").json() + agency_names = {"Aderanto AB", "Wise IT", "TechTalent Nord"} + agency_postings = [p for p in postings if p.get("company") in agency_names] + assert len(agency_postings) == 3 + cluster_ids = {p["cluster_id"] for p in agency_postings if p.get("cluster_id")} + assert len(cluster_ids) == 1, f"Expected 1 cluster_id, got {cluster_ids}" + + def test_seed_demo_deadlines_populated(self, client): + """Seed creates at least 2 postings with apply_by in the next 7 days.""" + client.post("/api/concierge/seed-demo") + resp = client.get("/api/today") + assert resp.status_code == 200 + deadlines = resp.json().get("deadlines", []) + assert len(deadlines) >= 2 + for d in deadlines: + assert d["apply_by"] is not None + + def test_seed_demo_red_flag_rationale(self, client): + """Seed creates an application with red_flags in its score_rationale.""" + client.post("/api/concierge/seed-demo") + apps = client.get("/api/applications").json() + red_flag_apps = [ + a for a in apps + if a.get("score_rationale") and isinstance(a["score_rationale"], dict) + and "red_flags" in a["score_rationale"] + ] + assert len(red_flag_apps) >= 1 + red_flags = red_flag_apps[0]["score_rationale"]["red_flags"] + assert isinstance(red_flags, list) + assert any("unpaid trial" in str(rf).lower() for rf in red_flags) + + def test_seed_demo_has_interviewing_application(self, client): + """Seed creates at least one application in interviewing state.""" + client.post("/api/concierge/seed-demo") + apps = client.get("/api/applications").json() + interviewing = [a for a in apps if a["state"] == "interviewing"] + assert len(interviewing) >= 1 + + def test_seed_demo_has_cover_letter_artifact(self, client): + """Seed creates a cover_letter artifact (origin user_drafted) with Swedish text on the approved app.""" + client.post("/api/concierge/seed-demo") + apps = client.get("/api/applications").json() + for a in apps: + artifacts = client.get(f"/api/applications/{a['id']}/artifacts").json() + for art in artifacts: + if art["kind"] == "cover_letter" and art["origin"] == "user_drafted": + return + assert False, "No user_drafted cover_letter artifact found" + + def test_seed_demo_suggestions_present(self, client): + """Seed creates 2 pending email_suggestion rows with expected classifications.""" + client.post("/api/concierge/seed-demo") + suggestions = client.get("/api/suggestions").json() + assert len(suggestions) == 2 + classifications = {s["classification"] for s in suggestions} + assert "interview_invite" in classifications + assert "question" in classifications + # Verify the interview_invite comes from recruiter@festina-demo.se + interview_suggestion = [s for s in suggestions if s["classification"] == "interview_invite"][0] + assert interview_suggestion["mailbox_from"] == "recruiter@festina-demo.se" + + def test_seed_demo_notification_log_present(self, client): + """Seed creates 3 notification_log rows: 2 delivered, 1 webhook failed.""" + client.post("/api/concierge/seed-demo") + resp = client.get("/api/notifications/log") + assert resp.status_code == 200 + logs = resp.json() + assert len(logs) == 3 + # At least one delivered (daily_digest or email_suggestion) + delivered = [l for l in logs if l["delivered"] is True] + assert len(delivered) >= 2 + # At least one webhook failed with error text + failed = [l for l in logs if l["delivered"] is False] + assert len(failed) >= 1 + assert failed[0]["error"] is not None + assert len(failed[0]["error"]) > 0 + + def test_seed_demo_task_run_telemetry_variance(self, client): + """Seed creates 6 task_run rows across multiple providers and models.""" + client.post("/api/concierge/seed-demo") + resp = client.get("/api/telemetry/tasks") + assert resp.status_code == 200 + tasks = resp.json() + assert len(tasks) == 6 + providers = {t["provider"] for t in tasks} + models = {t["model"] for t in tasks} + assert len(providers) >= 3, f"Expected >= 3 providers, got {providers}" + assert len(models) >= 4, f"Expected >= 4 models, got {models}" + # Verify cost variance for CostDisplay + costs = [t["cost_usd"] for t in tasks if t["cost_usd"] is not None] + assert len(costs) >= 2 + assert max(costs) > min(costs) + # --- SMTP Transport ---