"""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"