157 lines
6.3 KiB
Python
157 lines
6.3 KiB
Python
"""Tests for interview prep endpoints: research GET/generate/task, contacts GET."""
|
|
import json
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
import sys
|
|
sys.path.insert(0, "/Library/Development/CircuitForge/peregrine/.worktrees/feature-vue-spa")
|
|
from dev_api import app
|
|
return TestClient(app)
|
|
|
|
|
|
# ── /api/jobs/{id}/research ─────────────────────────────────────────────────
|
|
|
|
def test_get_research_found(client):
|
|
"""Returns research row (minus raw_output) when present."""
|
|
row = {
|
|
"job_id": 1,
|
|
"company_brief": "Acme Corp makes anvils.",
|
|
"ceo_brief": "Wile E Coyote",
|
|
"talking_points": "- Ask about roadrunner containment",
|
|
"tech_brief": "Python, Rust",
|
|
"funding_brief": "Series B",
|
|
"red_flags": None,
|
|
"accessibility_brief": None,
|
|
"generated_at": "2026-03-20T12:00:00",
|
|
"raw_output": "should be stripped",
|
|
}
|
|
with patch("scripts.db.get_research", return_value=row):
|
|
resp = client.get("/api/jobs/1/research")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["company_brief"] == "Acme Corp makes anvils."
|
|
assert "raw_output" not in data
|
|
|
|
|
|
def test_get_research_not_found(client):
|
|
"""Returns 404 when no research row exists for job."""
|
|
with patch("scripts.db.get_research", return_value=None):
|
|
resp = client.get("/api/jobs/99/research")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ── /api/jobs/{id}/research/generate ────────────────────────────────────────
|
|
|
|
def test_generate_research_new_task(client):
|
|
"""POST generate returns task_id and is_new=True for fresh submission."""
|
|
with patch("scripts.task_runner.submit_task", return_value=(42, True)):
|
|
resp = client.post("/api/jobs/1/research/generate")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["task_id"] == 42
|
|
assert data["is_new"] is True
|
|
|
|
|
|
def test_generate_research_duplicate_task(client):
|
|
"""POST generate returns is_new=False when task already queued."""
|
|
with patch("scripts.task_runner.submit_task", return_value=(17, False)):
|
|
resp = client.post("/api/jobs/1/research/generate")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["is_new"] is False
|
|
|
|
|
|
def test_generate_research_error(client):
|
|
"""POST generate returns 500 when submit_task raises."""
|
|
with patch("scripts.task_runner.submit_task", side_effect=Exception("LLM unavailable")):
|
|
resp = client.post("/api/jobs/1/research/generate")
|
|
assert resp.status_code == 500
|
|
|
|
|
|
# ── /api/jobs/{id}/research/task ────────────────────────────────────────────
|
|
|
|
def test_research_task_none(client):
|
|
"""Returns status=none when no background task exists for job."""
|
|
mock_db = MagicMock()
|
|
mock_db.execute.return_value.fetchone.return_value = None
|
|
with patch("dev_api._get_db", return_value=mock_db):
|
|
resp = client.get("/api/jobs/1/research/task")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "none"
|
|
assert data["stage"] is None
|
|
assert data["message"] is None
|
|
|
|
|
|
def test_research_task_running(client):
|
|
"""Returns current status/stage/message for an active task."""
|
|
mock_row = {"status": "running", "stage": "Scraping company site", "error": None}
|
|
mock_db = MagicMock()
|
|
mock_db.execute.return_value.fetchone.return_value = mock_row
|
|
with patch("dev_api._get_db", return_value=mock_db):
|
|
resp = client.get("/api/jobs/1/research/task")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "running"
|
|
assert data["stage"] == "Scraping company site"
|
|
assert data["message"] is None
|
|
|
|
|
|
def test_research_task_failed(client):
|
|
"""Returns message (mapped from error column) for failed task."""
|
|
mock_row = {"status": "failed", "stage": None, "error": "LLM timeout"}
|
|
mock_db = MagicMock()
|
|
mock_db.execute.return_value.fetchone.return_value = mock_row
|
|
with patch("dev_api._get_db", return_value=mock_db):
|
|
resp = client.get("/api/jobs/1/research/task")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "failed"
|
|
assert data["message"] == "LLM timeout"
|
|
|
|
|
|
# ── /api/jobs/{id}/contacts ──────────────────────────────────────────────────
|
|
|
|
def test_get_contacts_empty(client):
|
|
"""Returns empty list when job has no contacts."""
|
|
mock_db = MagicMock()
|
|
mock_db.execute.return_value.fetchall.return_value = []
|
|
with patch("dev_api._get_db", return_value=mock_db):
|
|
resp = client.get("/api/jobs/1/contacts")
|
|
assert resp.status_code == 200
|
|
assert resp.json() == []
|
|
|
|
|
|
def test_get_contacts_list(client):
|
|
"""Returns list of contact dicts for job."""
|
|
mock_rows = [
|
|
{"id": 1, "direction": "inbound", "subject": "Interview next week",
|
|
"from_addr": "hr@acme.com", "body": "Hi! We'd like to...", "received_at": "2026-03-19T10:00:00"},
|
|
{"id": 2, "direction": "outbound", "subject": "Re: Interview next week",
|
|
"from_addr": None, "body": "Thank you!", "received_at": "2026-03-19T11:00:00"},
|
|
]
|
|
mock_db = MagicMock()
|
|
mock_db.execute.return_value.fetchall.return_value = mock_rows
|
|
with patch("dev_api._get_db", return_value=mock_db):
|
|
resp = client.get("/api/jobs/1/contacts")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 2
|
|
assert data[0]["direction"] == "inbound"
|
|
assert data[1]["direction"] == "outbound"
|
|
|
|
|
|
def test_get_contacts_ordered_by_received_at(client):
|
|
"""Most recent contacts appear first (ORDER BY received_at DESC)."""
|
|
mock_db = MagicMock()
|
|
mock_db.execute.return_value.fetchall.return_value = []
|
|
with patch("dev_api._get_db", return_value=mock_db):
|
|
resp = client.get("/api/jobs/99/contacts")
|
|
# Verify the SQL contains ORDER BY received_at DESC
|
|
call_args = mock_db.execute.call_args
|
|
sql = call_args[0][0]
|
|
assert "ORDER BY received_at DESC" in sql
|