Files
resume-agent/backend/tests/test_resume_expansion.py

232 lines
9.4 KiB
Python

from __future__ import annotations
from app.resume_expansion import (
FallbackEntryExpander,
OpenAIEntryExpander,
_EXPANSION_REPAIR_PROMPT,
_system_prompt,
build_expander,
)
from app.resume_expansion_prompts import _repair_prompt
from app.settings import Settings
def test_light_expansion_prompt_prioritizes_fact_completeness() -> None:
prompt = _system_prompt("project_experience")
assert "Completeness first" in prompt
assert "do not drop meaningful facts for brevity" in prompt
assert "covered_fact_ids" not in prompt
def test_light_expansion_prompt_keeps_hard_boundaries_and_star() -> None:
prompt = _system_prompt("work_experience")
assert "hard_required_facts" in prompt
assert "quantity with its original object" in prompt
assert "responsibility level" in prompt
assert "STAR" in prompt
assert prompt.index("STAR") < prompt.index("- ")
def test_education_prompt_polishes_without_star_or_bullets() -> None:
prompt = _system_prompt("education")
assert "Do not use a STAR" in prompt
assert "education entries" in prompt
assert "bullet points" not in prompt
class _SequentialCompletion:
def __init__(self, outputs: list[dict[str, object] | Exception]) -> None:
self.outputs = outputs
self.calls: list[dict[str, object]] = []
self.call_options: list[dict[str, object]] = []
self.schema_names: list[str] = []
self.system_prompts: list[str] = []
def complete(self, *, schema, schema_name, system_prompt, payload, **kwargs):
self.calls.append(payload)
self.call_options.append(kwargs)
self.schema_names.append(schema_name)
self.system_prompts.append(system_prompt)
value = self.outputs[min(len(self.calls) - 1, len(self.outputs) - 1)]
if isinstance(value, Exception):
raise value
return schema.model_validate({"optimized_description": value["optimized_description"]})
def _output(text: str) -> dict[str, object]:
return {"optimized_description": text}
def test_missing_coverage_declaration_does_not_add_a_repair_round() -> None:
completion = _SequentialCompletion([_output("\u5b8c\u6210\u5df2\u786e\u8ba4\u7684\u5de5\u4f5c\u3002")])
expander = OpenAIEntryExpander(completion)
entry = {"description": "\u8fdb\u884c\u9700\u6c42\u5206\u6790\u3002\n\u5b8c\u6210\u63a5\u53e3\u8bbe\u8ba1\u3002\n\u6267\u884c\u4e0a\u7ebf\u652f\u6301\u3002"}
proposal = expander.expand(entry, context={"entry_type": "project_experience"})
assert len(completion.calls) == 1
assert proposal["changes"] == []
assert "coverage_targets" not in completion.calls[0]
assert "covered_fact_ids" not in proposal
def test_hard_fact_omission_repairs_with_atomic_anchor() -> None:
entry = {"description": "\u4f7f\u7528 FastAPI \u5f00\u53d1\u670d\u52a1\uff0c\u652f\u6301 300 \u540d\u7528\u6237\u3002"}
completion = _SequentialCompletion([
_output("\u652f\u6301 300 \u540d\u7528\u6237\u3002"),
_output("\u4f7f\u7528 FastAPI \u5f00\u53d1\u670d\u52a1\uff0c\u652f\u6301 300 \u540d\u7528\u6237\u3002"),
])
expander = OpenAIEntryExpander(completion)
proposal = expander.expand(entry, context={"entry_type": "project_experience"})
assert len(completion.calls) == 2
assert completion.calls[1]["rejected_reason"] == "hard_fact_omitted"
assert completion.calls[1]["omitted_facts"] == ["fastapi"]
assert proposal["uncovered_facts"] == []
def test_failed_repair_keeps_the_first_pass_candidate() -> None:
entry = {"description": "\u4f7f\u7528 FastAPI \u5f00\u53d1\u670d\u52a1\uff0c\u652f\u6301 300 \u540d\u7528\u6237\u3002"}
completion = _SequentialCompletion([
_output("\u652f\u6301 300 \u540d\u7528\u6237\u3002"),
RuntimeError("network failure"),
])
expander = OpenAIEntryExpander(completion)
proposal = expander.expand(entry, context={"entry_type": "project_experience"})
assert len(completion.calls) == 2
assert proposal["optimized_description"].endswith("300 \u540d\u7528\u6237\u3002")
assert "repair_failed" in proposal["validation_warnings"]
def test_non_education_bullets_are_normalized_locally() -> None:
completion = _SequentialCompletion([_output("- \u8d1f\u8d23\u9700\u6c42\u5206\u6790\u3002\n2. \u5b8c\u6210\u90e8\u7f72\u4e0a\u7ebf\u3002")])
expander = OpenAIEntryExpander(completion)
proposal = expander.expand(
{"description": "\u8d1f\u8d23\u9700\u6c42\u5206\u6790\u3002\u5b8c\u6210\u90e8\u7f72\u4e0a\u7ebf\u3002"},
context={"entry_type": "project_experience"},
)
assert proposal["optimized_description"].splitlines() == [
"\u2022 \u8d1f\u8d23\u9700\u6c42\u5206\u6790\u3002",
"\u2022 \u5b8c\u6210\u90e8\u7f72\u4e0a\u7ebf\u3002",
]
def test_education_never_gets_local_bullets() -> None:
completion = _SequentialCompletion([_output("\u5b8c\u6210\u6570\u636e\u5e93\u8bfe\u7a0b\u9879\u76ee\uff0cGPA 3.8/4.0\u3002")])
expander = OpenAIEntryExpander(completion)
proposal = expander.expand(
{"description": "\u5b8c\u6210\u6570\u636e\u5e93\u8bfe\u7a0b\u9879\u76ee\uff0cGPA 3.8/4.0\u3002"},
context={"entry_type": "education"},
)
assert proposal["optimized_description"] == "\u5b8c\u6210\u6570\u636e\u5e93\u8bfe\u7a0b\u9879\u76ee\uff0cGPA 3.8/4.0\u3002"
def test_repair_prompt_keeps_star_and_dash_bullets() -> None:
prompt = _repair_prompt("project_experience")
assert _EXPANSION_REPAIR_PROMPT in prompt
assert "STAR" in prompt
assert prompt.index("STAR") < prompt.index("- ")
assert "bullet points" in prompt
def test_entry_expansion_uses_one_attempt_and_a_remaining_repair_budget() -> None:
entry = {"description": "Built a FastAPI service for 300 users."}
completion = _SequentialCompletion([
_output("Supported 300 users."),
_output("Built a FastAPI service for 300 users."),
])
expander = OpenAIEntryExpander(completion, timeout_seconds=30.0)
expander.expand(entry, context={"entry_type": "project_experience"})
assert completion.call_options[0]["max_attempts"] == 1
assert completion.call_options[0]["timeout_seconds"] <= 30.0
assert completion.call_options[1]["max_attempts"] == 1
assert 0 < completion.call_options[1]["timeout_seconds"] <= completion.call_options[0]["timeout_seconds"]
def test_repair_is_skipped_when_the_first_pass_exhausts_the_budget() -> None:
entry = {"description": "Built a FastAPI service for 300 users."}
completion = _SequentialCompletion([_output("Supported 300 users.")])
expander = OpenAIEntryExpander(completion, timeout_seconds=5.0)
proposal = expander.expand(entry, context={"entry_type": "project_experience"})
assert len(completion.calls) == 1
assert proposal["optimized_description"].endswith("Supported 300 users.")
assert proposal["optimized_description"].splitlines()[0].lstrip("\u2022 ").startswith("Supported")
assert "repair_skipped_budget" in proposal["validation_warnings"]
def test_repair_that_does_not_reduce_hard_omissions_keeps_first_pass() -> None:
entry = {"description": "Built a FastAPI service for 300 users."}
completion = _SequentialCompletion([
_output("Supported 300 users."),
_output("Supported 300 users."),
])
expander = OpenAIEntryExpander(completion)
proposal = expander.expand(entry, context={"entry_type": "project_experience"})
assert proposal["optimized_description"].endswith("Supported 300 users.")
assert "repair_rejected_quality_regression" in proposal["validation_warnings"]
def test_repair_that_loses_a_retained_hard_fact_keeps_first_pass() -> None:
entry = {"description": "Built a FastAPI service for 300 users."}
completion = _SequentialCompletion([
_output("Built a FastAPI service."),
_output("Supported 300 users."),
])
expander = OpenAIEntryExpander(completion)
proposal = expander.expand(entry, context={"entry_type": "project_experience"})
assert proposal["optimized_description"].endswith("Built a FastAPI service.")
assert "repair_rejected_quality_regression" in proposal["validation_warnings"]
def test_generic_api_term_does_not_trigger_repair() -> None:
completion = _SequentialCompletion([_output("Developed the service endpoint.")])
expander = OpenAIEntryExpander(completion)
proposal = expander.expand(
{"description": "Built an API endpoint."},
context={"entry_type": "project_experience"},
)
assert len(completion.calls) == 1
assert proposal["uncovered_facts"] == []
def test_build_expander_honors_rule_fallback_setting() -> None:
settings = Settings(
llm_provider="openai",
openai_api_key="test-key-not-a-secret",
fallback_to_rules=False,
)
expander = build_expander(settings, _SequentialCompletion([]))
assert isinstance(expander, OpenAIEntryExpander)
def test_repair_cannot_flatten_a_structured_first_draft() -> None:
entry = {"description": "Built a FastAPI and Redis service for 300 users."}
completion = _SequentialCompletion([
_output("Built a FastAPI service for 300 users.\nDesigned service modules.\nReleased documentation."),
_output("Built a FastAPI and Redis service for 300 users."),
])
expander = OpenAIEntryExpander(completion)
proposal = expander.expand(entry, context={"entry_type": "project_experience"})
assert len(proposal["optimized_description"].splitlines()) == 3
assert "repair_rejected_quality_regression" in proposal["validation_warnings"]