peregrine/tests/test_suggest_helpers.py
pyr0ball ce8d5a4ac0 feat: add suggest_resume_keywords for skills/domains/keywords gap analysis
Replaces NotImplementedError stub with full LLM-backed implementation.
Builds a prompt from the last 3 resume positions plus already-selected
skills/domains/keywords, calls LLMRouter, and returns de-duped suggestions
in all three categories.
2026-03-05 15:00:53 -08:00

148 lines
6.4 KiB
Python

"""Tests for scripts/suggest_helpers.py."""
import json
import pytest
from pathlib import Path
from unittest.mock import patch, MagicMock
RESUME_PATH = Path(__file__).parent.parent / "config" / "plain_text_resume.yaml"
# ── _parse_json ───────────────────────────────────────────────────────────────
def test_parse_json_extracts_valid_object():
from scripts.suggest_helpers import _parse_json
raw = 'Here is the result: {"a": [1, 2], "b": "hello"} done.'
assert _parse_json(raw) == {"a": [1, 2], "b": "hello"}
def test_parse_json_returns_empty_on_invalid():
from scripts.suggest_helpers import _parse_json
assert _parse_json("no json here") == {}
assert _parse_json('{"broken": ') == {}
# ── suggest_search_terms ──────────────────────────────────────────────────────
BLOCKLIST = {
"companies": ["Meta", "Amazon"],
"industries": ["gambling"],
"locations": [],
}
USER_PROFILE = {
"career_summary": "Customer success leader with 10 years in B2B SaaS.",
"mission_preferences": {
"animal_welfare": "I volunteer at my local shelter.",
"education": "",
},
"nda_companies": ["Acme Corp"],
}
def _mock_llm(response_dict: dict):
"""Return a patcher that makes LLMRouter().complete() return a JSON string."""
mock_router = MagicMock()
mock_router.complete.return_value = json.dumps(response_dict)
return patch("scripts.suggest_helpers.LLMRouter", return_value=mock_router)
def test_suggest_search_terms_returns_titles_and_excludes():
from scripts.suggest_helpers import suggest_search_terms
payload = {"suggested_titles": ["VP Customer Success"], "suggested_excludes": ["cold calling"]}
with _mock_llm(payload):
result = suggest_search_terms(["Customer Success Manager"], RESUME_PATH, BLOCKLIST, USER_PROFILE)
assert result["suggested_titles"] == ["VP Customer Success"]
assert result["suggested_excludes"] == ["cold calling"]
def test_suggest_search_terms_prompt_contains_blocklist_companies():
from scripts.suggest_helpers import suggest_search_terms
with _mock_llm({"suggested_titles": [], "suggested_excludes": []}) as mock_cls:
suggest_search_terms(["CSM"], RESUME_PATH, BLOCKLIST, USER_PROFILE)
prompt_sent = mock_cls.return_value.complete.call_args[0][0]
assert "Meta" in prompt_sent
assert "Amazon" in prompt_sent
def test_suggest_search_terms_prompt_contains_mission():
from scripts.suggest_helpers import suggest_search_terms
with _mock_llm({"suggested_titles": [], "suggested_excludes": []}) as mock_cls:
suggest_search_terms(["CSM"], RESUME_PATH, BLOCKLIST, USER_PROFILE)
prompt_sent = mock_cls.return_value.complete.call_args[0][0]
assert "animal_welfare" in prompt_sent or "animal welfare" in prompt_sent.lower()
def test_suggest_search_terms_prompt_contains_career_summary():
from scripts.suggest_helpers import suggest_search_terms
with _mock_llm({"suggested_titles": [], "suggested_excludes": []}) as mock_cls:
suggest_search_terms(["CSM"], RESUME_PATH, BLOCKLIST, USER_PROFILE)
prompt_sent = mock_cls.return_value.complete.call_args[0][0]
assert "Customer success leader" in prompt_sent
def test_suggest_search_terms_returns_empty_on_bad_json():
from scripts.suggest_helpers import suggest_search_terms
mock_router = MagicMock()
mock_router.complete.return_value = "sorry, I cannot help with that"
with patch("scripts.suggest_helpers.LLMRouter", return_value=mock_router):
result = suggest_search_terms(["CSM"], RESUME_PATH, BLOCKLIST, USER_PROFILE)
assert result == {"suggested_titles": [], "suggested_excludes": []}
def test_suggest_search_terms_raises_on_llm_exhausted():
from scripts.suggest_helpers import suggest_search_terms
mock_router = MagicMock()
mock_router.complete.side_effect = RuntimeError("All LLM backends exhausted")
with patch("scripts.suggest_helpers.LLMRouter", return_value=mock_router):
with pytest.raises(RuntimeError, match="All LLM backends exhausted"):
suggest_search_terms(["CSM"], RESUME_PATH, BLOCKLIST, USER_PROFILE)
# ── suggest_resume_keywords ───────────────────────────────────────────────────
CURRENT_KW = {
"skills": ["Customer Success", "SQL"],
"domains": ["B2B SaaS"],
"keywords": ["NPS"],
}
def test_suggest_resume_keywords_returns_all_three_categories():
from scripts.suggest_helpers import suggest_resume_keywords
payload = {
"skills": ["Project Management"],
"domains": ["EdTech"],
"keywords": ["churn prevention"],
}
with _mock_llm(payload):
result = suggest_resume_keywords(RESUME_PATH, CURRENT_KW)
assert "skills" in result
assert "domains" in result
assert "keywords" in result
def test_suggest_resume_keywords_excludes_already_selected():
from scripts.suggest_helpers import suggest_resume_keywords
with _mock_llm({"skills": [], "domains": [], "keywords": []}) as mock_cls:
suggest_resume_keywords(RESUME_PATH, CURRENT_KW)
prompt_sent = mock_cls.return_value.complete.call_args[0][0]
# Already-selected tags should appear in the prompt so LLM knows to skip them
assert "Customer Success" in prompt_sent
assert "NPS" in prompt_sent
def test_suggest_resume_keywords_returns_empty_on_bad_json():
from scripts.suggest_helpers import suggest_resume_keywords
mock_router = MagicMock()
mock_router.complete.return_value = "I cannot assist."
with patch("scripts.suggest_helpers.LLMRouter", return_value=mock_router):
result = suggest_resume_keywords(RESUME_PATH, CURRENT_KW)
assert result == {"skills": [], "domains": [], "keywords": []}
def test_suggest_resume_keywords_raises_on_llm_exhausted():
from scripts.suggest_helpers import suggest_resume_keywords
mock_router = MagicMock()
mock_router.complete.side_effect = RuntimeError("All LLM backends exhausted")
with patch("scripts.suggest_helpers.LLMRouter", return_value=mock_router):
with pytest.raises(RuntimeError, match="All LLM backends exhausted"):
suggest_resume_keywords(RESUME_PATH, CURRENT_KW)