bored-cv-api / tests /test_llm.py
Aramente's picture
feat: switch entire backend from Gemini to Mistral (LLM + STT)
8b18ee8
import json
import pytest
from unittest.mock import patch, MagicMock
from app.services.llm import LLMService
from app.models import Profile, Offer, Experience, Education, OfferRequirement, GapAnalysis, ChatMessage
@pytest.fixture
def sample_profile():
return Profile(
name="Marie Dupont",
title="Product Manager",
location="Paris",
summary="8 years in SaaS product management",
experiences=[
Experience(
title="Senior PM", company="TechCorp", dates="2022 - Present",
description="Led B2B platform product strategy",
bullets=["Led B2B platform product strategy", "Grew ARR by 45%"],
)
],
education=[Education(degree="MSc Management", school="HEC Paris", year="2018")],
skills=["Product Strategy", "SQL", "Figma"],
)
@pytest.fixture
def sample_offer():
return Offer(
title="Senior Product Manager", company="BigTech",
description="Looking for a Senior PM to lead AI products",
requirements=[
OfferRequirement(text="5+ years PM experience", category="required"),
OfferRequirement(text="AI/ML product experience", category="required"),
],
nice_to_have=[OfferRequirement(text="MBA", category="nice_to_have")],
)
@pytest.fixture
def llm_service():
return LLMService(api_key="test-key")
def _mock_mistral_response(text: str):
mock_response = MagicMock()
mock_response.choices = [MagicMock()]
mock_response.choices[0].message.content = text
return mock_response
@patch("app.services.llm.Mistral")
def test_analyze_returns_gap_analysis(mock_mistral_cls, llm_service, sample_profile, sample_offer):
mock_client = MagicMock()
mock_mistral_cls.return_value = mock_client
mock_client.chat.complete.return_value = _mock_mistral_response(
json.dumps({
"matched_skills": ["Product Strategy"],
"gaps": ["AI/ML experience"],
"questions": ["Can you describe any experience with AI/ML products?"],
})
)
# Reset cached client so mock takes effect
llm_service._client = mock_client
result = llm_service.analyze(sample_profile, sample_offer)
assert len(result.matched_skills) > 0
assert len(result.questions) > 0
mock_client.chat.complete.assert_called_once()
@patch("app.services.llm.Mistral")
def test_generate_question_returns_string(mock_mistral_cls, llm_service, sample_profile, sample_offer):
mock_client = MagicMock()
mock_mistral_cls.return_value = mock_client
gap = GapAnalysis(matched_skills=["Product Strategy"], gaps=["AI/ML experience"], questions=["Tell me about AI experience"])
messages = [ChatMessage(role="assistant", content="Tell me about AI experience")]
mock_client.chat.complete.return_value = _mock_mistral_response(
json.dumps({"message": "Can you elaborate on your data analysis work?", "is_complete": False})
)
llm_service._client = mock_client
result = llm_service.generate_next_question(sample_profile, sample_offer, gap, messages)
assert result.message != ""
@patch("app.services.llm.Mistral")
def test_generate_cv_returns_cv_data(mock_mistral_cls, llm_service, sample_profile, sample_offer):
mock_client = MagicMock()
mock_mistral_cls.return_value = mock_client
gap = GapAnalysis(matched_skills=["Product Strategy"], gaps=[], questions=[])
messages = [
ChatMessage(role="assistant", content="Tell me about leadership"),
ChatMessage(role="user", content="I led a team of 12 engineers"),
]
mock_client.chat.complete.return_value = _mock_mistral_response(
json.dumps({
"name": "Marie Dupont", "title": "Senior Product Manager",
"email": "", "location": "Paris",
"summary": "Results-driven PM with 8 years in SaaS, specialized in AI product strategy.",
"experiences": [{"title": "Senior PM", "company": "TechCorp", "dates": "2022 - Present",
"bullets": ["Led a team of 12 engineers to ship AI-powered B2B platform"]}],
"education": [{"degree": "MSc Management", "school": "HEC Paris", "year": "2018"}],
"skills": ["AI Product Strategy", "Team Leadership", "SQL", "Figma"],
"language": "en",
})
)
llm_service._client = mock_client
result = llm_service.generate_cv(sample_profile, sample_offer, gap, messages)
assert result.name == "Marie Dupont"
assert len(result.experiences) > 0
assert len(result.experiences[0].bullets) > 0