from __future__ import annotations import time from pathlib import Path from typing import Any from fastapi.testclient import TestClient from resume_agent.agent import save_json from resume_agent.models import CareerProfile, TailoringPackage from resume_agent.render import render_ohmycv_resume from resume_agent.webapp import create_app def sample_profile() -> CareerProfile: return CareerProfile.model_validate( { "contact": { "full_name": "Ada Example", "email": "ada@example.com", "phone": None, "location": "London", "linkedin": None, "website": None, }, "professional_identity": "Backend engineer", "differentiators": ["Reliable distributed systems"], "target_roles": ["Senior Backend Engineer"], "facts": [ { "id": "F001", "category": "experience", "statement": "Reduced API latency by 30%.", "source_name": "resume", "source_excerpt": "Reduced API latency by 30%.", } ], "skills": ["Python"], "unanswered_questions": [], } ) def sample_package() -> TailoringPackage: contact = sample_profile().contact.model_dump() item = {"text": "Reduced API latency by 30%. [F013]", "evidence_ids": ["F001"]} return TailoringPackage.model_validate( { "job": { "company": "Example Co", "role_title": "Backend Engineer", "mission": None, "requirements": [], "responsibilities": [], "culture_signals": [], "ats_keywords": ["Python"], }, "resume": { "contact": contact, "headline": "Backend Engineer", "summary": [item], "sections": [{"title": "Experience", "items": [item]}], }, "match": { "strong_matches": ["API performance"], "partial_matches": [], "genuine_gaps": [], "keywords_used": ["Python"], }, "changes_made": ["Prioritized relevant impact."], "questions_for_candidate": [], "warnings": [], } ) class FakeLLM: def __init__(self, responses: list[Any]) -> None: self.responses = responses def parse(self, schema: type[Any], instructions: str, input_text: str) -> Any: response = self.responses.pop(0) assert isinstance(response, schema) return response def list_models(self) -> list[str]: return ["test-model"] def wait_for_task(client: TestClient, task_id: str) -> dict[str, Any]: for _ in range(100): response = client.get(f"/api/tasks/{task_id}") assert response.status_code == 200 task = response.json() if task["status"] != "running": return task time.sleep(0.01) raise AssertionError("Background task did not finish in time.") def test_root_and_status(tmp_path: Path, monkeypatch: Any) -> None: monkeypatch.delenv("RESUME_AGENT_API_KEY", raising=False) monkeypatch.delenv("OPENAI_API_KEY", raising=False) app = create_app( profile_path=tmp_path / "profile.json", output_dir=tmp_path / "output", llm_factory=lambda: FakeLLM([]), # type: ignore[arg-type] ) with TestClient(app) as client: assert client.get("/").status_code == 200 favicon = client.get("/favicon.ico") assert favicon.status_code == 200 assert favicon.headers["content-type"] == "image/x-icon" status = client.get("/api/status").json() assert status["configured"] is False assert status["profile_exists"] is False def test_build_profile_from_upload(tmp_path: Path) -> None: expected = sample_profile() app = create_app( profile_path=tmp_path / "profile.json", output_dir=tmp_path / "output", llm_factory=lambda: FakeLLM([expected]), # type: ignore[arg-type] ) with TestClient(app) as client: response = client.post( "/api/profile", files={"resume": ("resume.md", b"# Ada\nReduced API latency by 30%.")}, data={"about": ""}, ) assert response.status_code == 200 assert response.json()["facts"][0]["id"] == "F001" assert (tmp_path / "profile.json").is_file() def test_background_profile_upload_returns_pollable_task(tmp_path: Path) -> None: expected = sample_profile() app = create_app( profile_path=tmp_path / "profile.json", output_dir=tmp_path / "output", llm_factory=lambda: FakeLLM([expected]), # type: ignore[arg-type] ) with TestClient(app) as client: started = client.post( "/api/profile/start", files={"resume": ("resume.md", b"# Ada\nReduced API latency by 30%.")}, data={"about": ""}, ) task = wait_for_task(client, started.json()["task_id"]) assert started.status_code == 200 assert task["status"] == "succeeded" assert task["result"]["facts"][0]["id"] == "F001" def test_tailor_creates_downloads(tmp_path: Path) -> None: profile_path = tmp_path / "profile.json" output_dir = tmp_path / "output" save_json(sample_profile(), profile_path) expected = sample_package() app = create_app( profile_path=profile_path, output_dir=output_dir, llm_factory=lambda: FakeLLM([expected, expected]), # type: ignore[arg-type] ) with TestClient(app) as client: response = client.post( "/api/tailor", json={ "job_url": None, "job_text": "Backend engineer role requiring Python and API performance. " * 3, }, ) download = client.get("/api/download/resume.md") ohmycv_download = client.get("/api/download/resume-ohmycv.md") assert response.status_code == 200 assert response.json()["job"]["company"] == "Example Co" assert download.status_code == 200 assert "Ada Example" in download.text assert "[F013]" not in download.text assert ohmycv_download.status_code == 200 assert ohmycv_download.text.startswith('---\nname: "Ada Example"') assert "[F013]" not in ohmycv_download.text def test_background_tailoring_returns_pollable_task(tmp_path: Path) -> None: profile_path = tmp_path / "profile.json" output_dir = tmp_path / "output" save_json(sample_profile(), profile_path) expected = sample_package() app = create_app( profile_path=profile_path, output_dir=output_dir, llm_factory=lambda: FakeLLM([expected, expected]), # type: ignore[arg-type] ) with TestClient(app) as client: started = client.post( "/api/tailor/start", json={ "job_url": None, "job_text": "Backend engineer role requiring Python and API performance. " * 3, }, ) task = wait_for_task(client, started.json()["task_id"]) assert started.status_code == 200 assert task["status"] == "succeeded" assert task["result"]["job"]["company"] == "Example Co" assert (output_dir / "resume-ohmycv.md").is_file() def test_tailor_requires_exactly_one_job_source(tmp_path: Path) -> None: profile_path = tmp_path / "profile.json" save_json(sample_profile(), profile_path) app = create_app( profile_path=profile_path, output_dir=tmp_path / "output", llm_factory=lambda: FakeLLM([]), # type: ignore[arg-type] ) with TestClient(app) as client: response = client.post( "/api/tailor", json={"job_url": "https://example.com/job", "job_text": "Also pasted"}, ) assert response.status_code == 422 def test_tailor_rejects_out_of_range_strength(tmp_path: Path) -> None: profile_path = tmp_path / "profile.json" save_json(sample_profile(), profile_path) app = create_app( profile_path=profile_path, output_dir=tmp_path / "output", llm_factory=lambda: FakeLLM([]), # type: ignore[arg-type] ) with TestClient(app) as client: response = client.post( "/api/tailor", json={ "job_url": None, "job_text": "Backend engineer role requiring Python. " * 3, "tailoring_strength": 101, }, ) assert response.status_code == 422 def test_revision_chat_updates_tailored_outputs(tmp_path: Path) -> None: profile_path = tmp_path / "profile.json" output_dir = tmp_path / "output" current = sample_package() revised = sample_package() revised.changes_made = ["Made the summary more direct."] save_json(sample_profile(), profile_path) save_json(current, output_dir / "tailoring.json") app = create_app( profile_path=profile_path, output_dir=output_dir, llm_factory=lambda: FakeLLM([revised, revised]), # type: ignore[arg-type] ) with TestClient(app) as client: response = client.post( "/api/revise", json={"message": "Make the summary more direct."}, ) download = client.get("/api/download/resume-ohmycv.md") assert response.status_code == 200 assert response.json()["package"]["changes_made"] == [ "Made the summary more direct." ] assert "Updated and re-audited" in response.json()["reply"] assert download.status_code == 200 assert "[F013]" not in download.text def test_revision_chat_requires_a_tailored_resume(tmp_path: Path) -> None: app = create_app( profile_path=tmp_path / "profile.json", output_dir=tmp_path / "output", llm_factory=lambda: FakeLLM([]), # type: ignore[arg-type] ) with TestClient(app) as client: response = client.post("/api/revise", json={"message": "Make it shorter."}) assert response.status_code == 409 def test_markdown_tailor_preserves_ohmycv_front_matter(tmp_path: Path) -> None: expected_profile = sample_profile() expected_package = sample_package() app = create_app( profile_path=tmp_path / "profile.json", output_dir=tmp_path / "output", cv_dist=tmp_path / "missing-cv", llm_factory=lambda: FakeLLM([expected_profile, expected_package, expected_package]), # type: ignore[arg-type] ) source = """--- name: Ada Example header: - text: ada@example.com --- ## Experience - Reduced API latency by 30%. """ with TestClient(app) as client: response = client.post( "/api/markdown/tailor", json={ "markdown": source, "about": "", "job_url": None, "job_text": "Backend engineer role requiring Python and API performance. " * 3, }, ) assert response.status_code == 200 markdown = response.json()["markdown"] assert markdown.startswith("---\nname: Ada Example\nheader:") assert "## Professional Summary\n\nReduced API latency" in markdown assert "Reduced API latency by 30%." in markdown assert "[F013]" not in markdown assert (tmp_path / "output/resume-ohmycv.md").read_text() == markdown def test_ohmycv_render_creates_native_header_when_source_has_none() -> None: markdown = render_ohmycv_resume(sample_package(), "# Old resume") assert markdown.startswith('---\nname: "Ada Example"\nheader:') assert 'data-icon=\\"tabler:mail\\"' in markdown assert 'link: "mailto:ada@example.com"' in markdown assert "[F013]" not in markdown def test_status_reports_built_cv_editor(tmp_path: Path) -> None: cv_dist = tmp_path / "cv" cv_dist.mkdir() (cv_dist / "index.html").write_text("