Files
resume-agent/backend/tests/test_resume_import_structure.py
T

223 lines
8.3 KiB
Python

"""Structured fallback and quality-gate coverage for resume imports."""
from __future__ import annotations
from typing import Any
from app.import_parser import ImportParseOutput, OpenAIResumeImportParser
from app.resume_import_service import RuleBasedResumeImportParser
class FakeCompletion:
def __init__(self, result: ImportParseOutput) -> None:
self.result = result
self.calls: list[dict[str, Any]] = []
def complete(self, **kwargs: Any) -> ImportParseOutput:
self.calls.append(kwargs)
return self.result
def _sample_resume() -> str:
long_detail = "A" * 650
return "\n".join(
[
"Li Ming",
"li.ming@example.com | 13800138000 | Guangzhou",
"Education",
"Example University | Computer Science | Bachelor | 2022-09 - 2026-06",
"GPA 3.8/4.0; ranked in the top 10%.",
"Project Experience",
"Resume Copilot | Backend Developer | 2025-01 - 2025-06",
f"Built the resume parsing API and optimization workflow. {long_detail}",
"\u5b9e\u4e60\u7ecf\u5386",
"Example Tech | AI Engineering Intern | 2025-07 - 2025-09",
"Implemented evaluation scripts and integrated retrieval.",
"Skills",
"Python, FastAPI, PostgreSQL, Docker",
]
)
def test_rule_parser_structures_sections_and_does_not_truncate_text() -> None:
draft = RuleBasedResumeImportParser().parse(
source_name="resume.docx", text=_sample_resume()
)
document = draft.document
assert document["basics"]["name"] == "Li Ming"
assert document["basics"]["email"] == "li.ming@example.com"
assert document["basics"]["phone"] == "13800138000"
assert [section["kind"] for section in document["sections"]] == [
"education",
"project_experience",
"internship_experience",
]
project = document["sections"][1]["items"][0]
assert project["project_name"] == "Resume Copilot"
assert len(project["description"]) > 650
assert document["skill_groups"]
assert any("Python" in group["skills"] for group in document["skill_groups"])
def test_llm_skill_only_result_is_completed_with_local_sections() -> None:
completion = FakeCompletion(
ImportParseOutput.model_validate(
{
"basics": {},
"target": {},
"sections": [],
"skill_groups": [
{"category": "\u7f16\u7a0b\u8bed\u8a00\u4e0e\u6846\u67b6", "skills": ["Python"]}
],
}
)
)
parser = OpenAIResumeImportParser(
completion=completion,
fallback=RuleBasedResumeImportParser(),
)
draft = parser.parse(source_name="resume.docx", text=_sample_resume())
assert completion.calls
assert {section["kind"] for section in draft.document["sections"]} >= {
"education",
"project_experience",
"internship_experience",
}
assert draft.document["basics"]["phone"] == "13800138000"
def test_llm_unstructured_blob_is_replaced_by_detected_sections() -> None:
completion = FakeCompletion(
ImportParseOutput.model_validate(
{
"basics": {},
"target": {},
"sections": [
{
"kind": "additional_experience",
"heading": "导入内容",
"items": [{"fields": {"title": "resume.docx", "description": "raw text"}}],
}
],
"skill_groups": [],
}
)
)
parser = OpenAIResumeImportParser(
completion=completion,
fallback=RuleBasedResumeImportParser(),
)
draft = parser.parse(source_name="resume.docx", text=_sample_resume())
assert [section["kind"] for section in draft.document["sections"]] == [
"education",
"project_experience",
"internship_experience",
]
def test_llm_backfill_preserves_each_project_and_original_summary() -> None:
resume_text = "\n".join(
[
"Li Ming",
"li.ming@example.com | 13800138000",
"Project Experience",
"Project Alpha | Backend Developer | 2025-01 - 2025-03",
"Built the first service.",
"Project Beta | Platform Engineer | 2025-04 - 2025-06",
"Built the second service.",
"Personal Summary",
"Original summary paragraph one.",
"Original summary paragraph two.",
]
)
completion = FakeCompletion(
ImportParseOutput.model_validate(
{
"basics": {"name": "Li Ming"},
"target": {},
"profile_summary": "rewritten summary",
"sections": [
{
"kind": "project_experience",
"heading": "Project Experience",
"items": [{"fields": {"project_name": "Project Alpha"}}],
}
],
"skill_groups": [],
}
)
)
parser = OpenAIResumeImportParser(completion=completion, fallback=RuleBasedResumeImportParser())
draft = parser.parse(source_name="resume.docx", text=resume_text)
projects = next(section for section in draft.document["sections"] if section["kind"] == "project_experience")
assert [item["project_name"] for item in projects["items"]] == ["Project Alpha", "Project Beta"]
assert draft.document["profile_summary"] == {
"content": "Original summary paragraph one.\nOriginal summary paragraph two.",
"source": "user_edited",
"generated_at": None,
"stale": False,
}
def test_llm_discards_unidentified_entries_and_merges_duplicate_education() -> None:
resume_text = "\n".join(
[
"Li Ming",
"li.ming@example.com | 13800138000",
"Education",
"Example University | Computer Science | Bachelor | 2022-09 - 2026-06",
"GPA 3.8/4.0; ranked in the top 10%.",
"Project Experience",
"Project Alpha | Backend Developer | 2025-01 - 2025-03",
"Built the first service.",
"Project Beta | Platform Engineer | 2025-04 - 2025-06",
"Built the second service.",
]
)
completion = FakeCompletion(
ImportParseOutput.model_validate(
{
"basics": {"phone": "[redacted]", "email": "redacted@example.com"},
"target": {},
"sections": [
{
"kind": "education",
"heading": "Education",
"items": [
{"fields": {"school": "Example University"}},
{"fields": {"description": "orphaned education detail"}},
],
},
{
"kind": "project_experience",
"heading": "Project Experience",
"items": [
{"fields": {"description": "orphaned project detail"}},
{"fields": {"project_name": "Project Alpha"}},
],
},
],
"skill_groups": [],
}
)
)
parser = OpenAIResumeImportParser(completion=completion, fallback=RuleBasedResumeImportParser())
draft = parser.parse(source_name="resume.docx", text=resume_text)
education = next(section for section in draft.document["sections"] if section["kind"] == "education")
projects = next(section for section in draft.document["sections"] if section["kind"] == "project_experience")
assert len(education["items"]) == 1
assert education["items"][0]["school"] == "Example University"
assert education["items"][0]["major"] == "Computer Science"
assert "description" not in education["items"][0] or education["items"][0]["description"] != "orphaned education detail"
assert [item["project_name"] for item in projects["items"]] == ["Project Alpha", "Project Beta"]
assert draft.document["basics"]["phone"] == "13800138000"
assert draft.document["basics"]["email"] == "li.ming@example.com"
assert draft.document["import_metadata"]["parse_status"] == "needs_review"