Implements docs/api-contract-v2.md. Migration 002 adds follow-up fields. EchoTransport replaced by SMTP->Clipboard selection behind unchanged approval gate. Scheduler (APScheduler) daily 07:00 fetch+score, env-gated, default off. Test image installs workspace packages; build context moved to repo root. Recovered and committed by integration lead after W2 worker hit iteration limit.
162 lines
No EOL
5.1 KiB
Python
162 lines
No EOL
5.1 KiB
Python
"""Tests for v1 CV import endpoints (mock LLM mode)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture()
|
|
def client():
|
|
from app.main import app
|
|
return TestClient(app)
|
|
|
|
|
|
class TestCvImport:
|
|
def test_import_txt_file(self, client):
|
|
"""POST /cv/import with a .txt file returns drafts."""
|
|
content = b"John Doe\nSoftware Engineer at TechCorp\n5 years experience with Python"
|
|
resp = client.post(
|
|
"/api/cv/import",
|
|
json={
|
|
"filename": "my_cv.txt",
|
|
"content_base64": base64.b64encode(content).decode(),
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "drafts" in data
|
|
assert len(data["drafts"]) > 0
|
|
|
|
def test_import_empty_file_422(self, client):
|
|
"""Empty file -> 422."""
|
|
content = b""
|
|
resp = client.post(
|
|
"/api/cv/import",
|
|
json={
|
|
"filename": "empty.txt",
|
|
"content_base64": base64.b64encode(content).decode(),
|
|
},
|
|
)
|
|
assert resp.status_code == 422
|
|
assert "empty" in resp.text.lower()
|
|
|
|
def test_import_whitespace_only_file_422(self, client):
|
|
"""File with only whitespace -> 422."""
|
|
content = b" \n\n\t "
|
|
resp = client.post(
|
|
"/api/cv/import",
|
|
json={
|
|
"filename": "blank.txt",
|
|
"content_base64": base64.b64encode(content).decode(),
|
|
},
|
|
)
|
|
assert resp.status_code == 422
|
|
assert "empty" in resp.text.lower()
|
|
|
|
def test_import_unsupported_format_422(self, client):
|
|
"""Unsupported file format -> 422."""
|
|
content = b"some data"
|
|
resp = client.post(
|
|
"/api/cv/import",
|
|
json={
|
|
"filename": "file.xyz",
|
|
"content_base64": base64.b64encode(content).decode(),
|
|
},
|
|
)
|
|
assert resp.status_code == 422
|
|
assert "unsupported" in resp.text.lower()
|
|
|
|
def test_import_invalid_base64_422(self, client):
|
|
"""Invalid base64 -> 422."""
|
|
resp = client.post(
|
|
"/api/cv/import",
|
|
json={
|
|
"filename": "file.txt",
|
|
"content_base64": "!!!not-base64!!!",
|
|
},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
def test_import_md_file(self, client):
|
|
"""POST /cv/import with a .md file returns drafts."""
|
|
content = b"# Jane Doe\n\n## Experience\nSenior Developer at Acme"
|
|
resp = client.post(
|
|
"/api/cv/import",
|
|
json={
|
|
"filename": "cv.md",
|
|
"content_base64": base64.b64encode(content).decode(),
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert "drafts" in resp.json()
|
|
|
|
def test_import_creates_telemetry(self, client):
|
|
"""CV import should create a task_run entry."""
|
|
content = b"Some CV text with experience"
|
|
client.post(
|
|
"/api/cv/import",
|
|
json={
|
|
"filename": "cv.txt",
|
|
"content_base64": base64.b64encode(content).decode(),
|
|
},
|
|
)
|
|
resp = client.get("/api/telemetry/tasks")
|
|
tasks = resp.json()
|
|
assert any(t["task"] == "cv_extract" for t in tasks)
|
|
|
|
|
|
class TestCvImportConfirm:
|
|
def test_confirm_creates_sections(self, client):
|
|
"""POST /cv/import/confirm creates cv_section rows."""
|
|
client.get("/api/profile")
|
|
resp = client.post(
|
|
"/api/cv/import/confirm",
|
|
json={
|
|
"drafts": [
|
|
{
|
|
"kind": "experience",
|
|
"title": "Dev",
|
|
"org": "Corp",
|
|
"bullets": ["did stuff"],
|
|
"tags": ["python"],
|
|
},
|
|
{
|
|
"kind": "education",
|
|
"title": "MSc",
|
|
"org": "Uni",
|
|
},
|
|
]
|
|
},
|
|
)
|
|
assert resp.status_code == 201
|
|
data = resp.json()
|
|
assert data["created"] == 2
|
|
assert len(data["sections"]) == 2
|
|
|
|
def test_confirm_empty_drafts(self, client):
|
|
"""Empty drafts list creates zero sections."""
|
|
client.get("/api/profile")
|
|
resp = client.post(
|
|
"/api/cv/import/confirm",
|
|
json={"drafts": []},
|
|
)
|
|
assert resp.status_code == 201
|
|
assert resp.json()["created"] == 0
|
|
|
|
def test_confirm_sections_appear_in_list(self, client):
|
|
"""Confirmed sections appear in GET /profile/sections."""
|
|
client.get("/api/profile")
|
|
client.post(
|
|
"/api/cv/import/confirm",
|
|
json={
|
|
"drafts": [
|
|
{"kind": "skills", "title": "Python Dev", "bullets": ["FastAPI"]},
|
|
]
|
|
},
|
|
)
|
|
resp = client.get("/api/profile/sections")
|
|
assert resp.status_code == 200
|
|
assert any(s["title"] == "Python Dev" for s in resp.json()) |