test_cv_parser_live.py
1 """Live sanity-check tests for the CV parser service. 2 3 Makes real API calls using the provider configured in config.toml. 4 Run before a batch to verify API key, model, and PDF attachment support. 5 6 Usage: 7 uv run pytest tests/test_cv_parser_live.py --run-live -v 8 """ 9 10 __all__: list[str] = [] 11 12 import re 13 import sys 14 from pathlib import Path 15 16 import pytest 17 18 from integrations.llm import LLMProvider 19 from models.coaching import CandidateProfile 20 from services.cv_parser import parse_cv 21 22 _FIXTURE_PATH = Path(__file__).resolve().parent / "fixtures" / "sample_cv.pdf" 23 24 25 @pytest.fixture(scope="module") 26 def live_llm() -> LLMProvider: 27 if "--run-live" not in sys.argv: 28 pytest.skip("parse_cv: pass --run-live to run real LLM calls") 29 from config import settings 30 from integrations.llm import create_llm_provider 31 32 provider, model, _temperature, _seed, max_tokens = settings.get_llm_config("parse_cv") 33 return create_llm_provider(provider, model, settings, max_tokens=max_tokens) 34 35 36 @pytest.fixture(scope="module") 37 def live_profile(live_llm: LLMProvider) -> CandidateProfile: 38 from config import settings 39 40 _provider, _model, temperature, seed, _max_tokens = settings.get_llm_config("parse_cv") 41 pdf_data = _FIXTURE_PATH.read_bytes() 42 return parse_cv(pdf_data, live_llm, temperature=temperature, seed=seed) 43 44 45 class TestParseCvLive: 46 """Live tests for CV parsing — requires --run-live and API keys.""" 47 48 def test_parse_cv_returns_valid_profile(self, live_profile: CandidateProfile) -> None: 49 # Real PDF extraction must produce a valid CandidateProfile with content. 50 assert isinstance(live_profile, CandidateProfile) 51 assert live_profile.identity.title != "" 52 assert len(live_profile.experiences) > 0 53 54 def test_parse_cv_extracts_sequential_ids(self, live_profile: CandidateProfile) -> None: 55 # Experience and education IDs must follow exp_01, edu_01 pattern. 56 exp_pattern = re.compile(r"^exp_\d{2}$") 57 for exp in live_profile.experiences: 58 assert exp_pattern.match(exp.id), f"Bad experience ID: {exp.id!r}" 59 60 edu_pattern = re.compile(r"^edu_\d{2}$") 61 for edu in live_profile.education: 62 assert edu_pattern.match(edu.id), f"Bad education ID: {edu.id!r}"