peregrine/tests/test_resumes_api.py
pyr0ball ae7549c2c9 feat: add resume library and per-job resume API endpoints
- POST/GET /api/resumes — create and list resumes
- POST /api/resumes/import — import from .txt/.pdf/.docx/.odt/.yaml
- GET/PATCH/DELETE /api/resumes/{id} — CRUD for individual resumes
- POST /api/resumes/{id}/set-default — set default resume
- GET/PATCH /api/jobs/{job_id}/resume — per-job resume association
- Extend approve_resume to optionally save to resume library (save_to_library + resume_name body fields)
- 9 passing tests in tests/test_resumes_api.py
2026-04-12 10:42:38 -07:00

129 lines
4.1 KiB
Python

"""Tests for /api/resumes/* endpoints."""
import json
import io
import sqlite3
import tempfile
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from scripts.db_migrate import migrate_db
@pytest.fixture
def client(tmp_path, monkeypatch):
db_path = tmp_path / "test.db"
migrate_db(db_path)
monkeypatch.setenv("STAGING_DB", str(db_path))
import dev_api
monkeypatch.setattr(dev_api, "_request_db",
type("CV", (), {"get": lambda self: str(db_path), "set": lambda *a: None})())
return TestClient(dev_api.app), db_path
def test_create_and_list(client):
c, db = client
resp = c.post("/api/resumes", json={"name": "Base", "text": "Resume text here"})
assert resp.status_code == 200
r = resp.json()
assert r["name"] == "Base"
assert r["word_count"] == 3
resp2 = c.get("/api/resumes")
assert len(resp2.json()["resumes"]) == 1
def test_get_single(client):
c, db = client
created = c.post("/api/resumes", json={"name": "Test", "text": "text"}).json()
fetched = c.get(f"/api/resumes/{created['id']}").json()
assert fetched["name"] == "Test"
def test_patch_resume(client):
c, db = client
created = c.post("/api/resumes", json={"name": "Old", "text": "old text"}).json()
updated = c.patch(f"/api/resumes/{created['id']}", json={"name": "New"}).json()
assert updated["name"] == "New"
assert updated["text"] == "old text"
def test_delete_resume(client):
c, db = client
a = c.post("/api/resumes", json={"name": "A", "text": "text a"}).json()
b = c.post("/api/resumes", json={"name": "B", "text": "text b"}).json()
resp = c.delete(f"/api/resumes/{a['id']}")
assert resp.status_code == 200
assert len(c.get("/api/resumes").json()["resumes"]) == 1
def test_delete_only_resume_rejected(client):
c, db = client
r = c.post("/api/resumes", json={"name": "Only", "text": "text"}).json()
resp = c.delete(f"/api/resumes/{r['id']}")
assert resp.status_code == 409
def test_set_default(client):
c, db = client
a = c.post("/api/resumes", json={"name": "A", "text": "text a"}).json()
b = c.post("/api/resumes", json={"name": "B", "text": "text b"}).json()
c.post(f"/api/resumes/{a['id']}/set-default")
c.post(f"/api/resumes/{b['id']}/set-default")
resumes = {r["id"]: r for r in c.get("/api/resumes").json()["resumes"]}
assert resumes[a["id"]]["is_default"] == 0
assert resumes[b["id"]]["is_default"] == 1
def test_import_txt(client):
c, db = client
f = io.BytesIO(b"Software engineer with ten years experience building distributed systems.")
resp = c.post("/api/resumes/import", files={"file": ("resume.txt", f, "text/plain")},
data={"name": "Imported"})
assert resp.status_code == 200
r = resp.json()
assert r["source"] == "import"
assert r["word_count"] > 0
def test_import_yaml(client):
c, db = client
yaml_content = b"""
career_summary: Experienced engineer.
experience:
- title: Staff Engineer
company: Acme
start_date: 01/2020
end_date: Present
bullets:
- Led platform redesign serving 2M users
skills:
- Python
- FastAPI
"""
f = io.BytesIO(yaml_content)
resp = c.post("/api/resumes/import", files={"file": ("resume.yaml", f, "application/x-yaml")})
assert resp.status_code == 200
r = resp.json()
assert r["source"] == "import"
assert r["struct_json"] is not None
def test_per_job_resume_endpoints(client):
c, db = client
conn = sqlite3.connect(db)
conn.execute("INSERT INTO jobs (id, title, company, source) VALUES (1, 'Eng', 'Co', 'test')")
conn.commit()
conn.close()
r = c.post("/api/resumes", json={"name": "Default", "text": "default text"}).json()
c.post(f"/api/resumes/{r['id']}/set-default")
result = c.get("/api/jobs/1/resume").json()
assert result["id"] == r["id"]
specific = c.post("/api/resumes", json={"name": "Specific", "text": "specific text"}).json()
c.patch("/api/jobs/1/resume", json={"resume_id": specific["id"]})
result2 = c.get("/api/jobs/1/resume").json()
assert result2["id"] == specific["id"]