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

241 lines
8.8 KiB
Python

"""M1 数据基座单元测试:模块规格表、校验器、Transition 扩展、rewriter 安全读取。"""
from __future__ import annotations
from app.enrichment_modules import ENRICHMENT_MODULES, ENRICHMENT_QUEUES, module_by_name
from app.fsm import Transition, component
from app.models import JobType, Stage
from app.services import RuleBasedResumeRewriter
from app.validators import (
competition_entry_errors,
normalize_tags,
valid_email,
valid_url,
)
def test_queues_match_prd_priorities():
assert ENRICHMENT_QUEUES[JobType.CAMPUS] == (
"internship",
"project",
"competition",
"skills",
"certificates",
)
assert ENRICHMENT_QUEUES[JobType.SOCIAL] == (
"more_work",
"project",
"education",
"skills",
"certificates",
)
assert ENRICHMENT_QUEUES[JobType.INTERNSHIP] == (
"campus_experience",
"project",
"competition",
"skills",
"certificates",
)
assert "education_highlight" not in ENRICHMENT_MODULES
assert "anchor_description" not in ENRICHMENT_MODULES
# picker 与社招职责补充模块已移除
for removed in ("internship_or_project", "next_experience", "work_description", "skills_certs"):
assert removed not in ENRICHMENT_MODULES
def test_every_queue_entry_resolves_to_a_spec():
for queue in ENRICHMENT_QUEUES.values():
for name in queue:
spec = ENRICHMENT_MODULES[name]
assert spec.name == name
assert spec.skippable is True
def test_module_spec_shapes():
competition = module_by_name("competition")
assert competition.kind == "record_form"
assert competition.record_type == "competition"
assert competition.multi is True
assert set(competition.core_fields) == {"name", "award", "date"}
skills = module_by_name("skills")
assert skills.kind == "tags"
assert skills.multi is False
certificates = module_by_name("certificates")
assert certificates.kind == "tags"
assert certificates.multi is False
internship = module_by_name("internship")
assert internship.kind == "record_fields"
assert internship.record_type == "internship_experience"
assert internship.multi is True
def test_skill_suggestions_match_keywords_and_profile_facts():
from app.enrichment_modules import skill_suggestions
assert "Vue" in skill_suggestions("前端工程师")
assert "MySQL" in skill_suggestions("Java后端开发")
fallback = skill_suggestions("")
assert fallback == skill_suggestions(None)
assert "沟通协调" in fallback
assert skill_suggestions("某种冷门职位") == fallback
profile = {
"records": {
"project_experience": [
{
"description": "使用 Python、FastAPI 和 Redis 开发接口服务。",
"rewrite_confirmed": True,
}
]
},
"tags": {"skills": ["Python"]},
}
suggestions = skill_suggestions("后端工程师", profile)
assert "FastAPI" in suggestions
assert "Redis" in suggestions
assert "Python" not in suggestions
def test_new_component_slugs_registered():
assert component("TagsInput")["data"]["component"] == "tags_input"
assert component("CompetitionFields")["data"]["component"] == "competition_fields"
assert component("AddAnother")["data"]["component"] == "add_another"
assert component("ProgressCard")["data"]["component"] == "progress_card"
def test_transition_resume_content_defaults_none():
transition = Transition(stage=Stage.CONTENT_READY, profile={}, turn={})
assert transition.resume_content is None
def test_valid_email():
assert valid_email("user@example.com")
assert not valid_email("not-an-email")
assert not valid_email("")
assert not valid_email(None)
def test_valid_url():
assert valid_url("https://portfolio.example.com")
assert valid_url("http://example.com/a")
assert not valid_url("ftp://example.com")
assert not valid_url("")
assert not valid_url(None)
def test_normalize_tags_strips_dedups_and_limits():
raw = [" Python ", "python", "FastAPI", "", " ", "Python"] + [f"tag{i}" for i in range(30)]
result = normalize_tags(raw)
assert result[:3] == ["Python", "FastAPI", "tag0"]
assert len(result) == 20
def test_normalize_tags_drops_overlong_items():
assert normalize_tags(["x" * 33, "ok"]) == ["ok"]
def test_competition_entry_errors():
assert competition_entry_errors({"name": "蓝桥杯", "award": "二等奖", "date": "2024-04"}) == []
assert competition_entry_errors({"name": "", "award": "二等奖", "date": "2024-04"}) == ["name"]
assert competition_entry_errors({"name": "蓝桥杯", "award": "", "date": "2024-04"}) == ["award"]
assert competition_entry_errors({"name": "蓝桥杯", "award": "二等奖", "date": "2024-13"}) == ["date"]
assert competition_entry_errors({"name": "蓝桥杯", "award": "二等奖", "date": "bad"}) == ["date"]
def _profile_without_enrichment_keys() -> dict:
return {
"phone": "13800138000",
"phone_source": "account",
"name": "测试用户",
"job_type": "campus",
"anchor_type": "education",
"anchor": {"school": "示例大学"},
"experiences": [],
}
def test_rewriter_output_unchanged_without_enrichment_keys():
rewritten = RuleBasedResumeRewriter().rewrite(_profile_without_enrichment_keys())
kinds = [section["kind"] for section in rewritten["sections"]]
assert kinds == ["education"]
assert "contacts" not in rewritten["basics"]
def test_rewriter_emits_sections_for_confirmed_records_only():
profile = _profile_without_enrichment_keys()
profile["records"] = {
"competition": [
{"name": "蓝桥杯", "award": "二等奖", "date": "2024-04", "rewrite_confirmed": True},
{"name": "未确认竞赛", "award": "参与奖", "date": "2024-05", "rewrite_confirmed": False},
],
"internship_experience": [
{"organization": "星河科技", "role": "实习生", "rewrite_confirmed": True},
],
}
profile["tags"] = {"skills": ["Python"], "certificates": ["CET-6"]}
profile["email"] = "me@example.com"
profile["city"] = "上海"
profile["portfolio_url"] = "https://me.com"
rewritten = RuleBasedResumeRewriter().rewrite(profile)
sections = {section["kind"]: section for section in rewritten["sections"]}
assert sections["competition"]["items"] == [
{"name": "蓝桥杯", "award": "二等奖", "date": "2024-04", "rewrite_confirmed": True}
]
assert sections["internship_experience"]["heading"] == "实习经历"
assert rewritten["skill_groups"] == [
{"category": "编程语言与框架", "skills": ["Python"]}
]
assert sections["certificates"]["items"] == [{"value": "CET-6"}]
assert rewritten["basics"]["email"] == "me@example.com"
assert rewritten["basics"]["city"] == "上海"
assert rewritten["basics"]["portfolio_url"] == "https://me.com"
def test_profile_facts_for_llm_includes_enrichment_safely():
from app.llm_services import profile_facts_for_llm
profile = {
"experiences": [],
"records": {
"internship_experience": [
{"organization": "星河科技", "role": "实习生", "rewrite_confirmed": True},
{"organization": "未确认公司", "role": "待定", "rewrite_confirmed": False},
],
"competition": [
{"name": "蓝桥杯", "award": "二等奖", "date": "2024-04", "rewrite_confirmed": True}
],
},
"tags": {"skills": ["Python"], "certificates": ["CET-6"]},
"city": "上海",
"portfolio_url": "https://me.com",
}
dto = profile_facts_for_llm(profile)
record_types = [item["record_type"] for item in dto["records"]]
assert record_types == ["internship_experience", "competition"]
assert "未确认公司" not in str(dto)
assert dto["tags"] == {"skills": ["Python"], "certificates": ["CET-6"]}
assert dto["contacts"] == {"city": "上海", "portfolio_url": "https://me.com"}
assert "me@example.com" not in str(dto)
assert "some-wechat-id" not in str(dto)
def test_skill_groups_are_classified_for_confirmed_user_skills():
from app.skill_classifier import classify_skills
assert classify_skills(["Python", "FastAPI", "Vue", "PostgreSQL", "Docker", "Figma", "SQL analysis", "Unusual Tool"]) == [
{"category": "编程语言与框架", "skills": ["Python", "FastAPI"]},
{"category": "前端", "skills": ["Vue"]},
{"category": "后端与数据存储", "skills": ["PostgreSQL"]},
{"category": "云、DevOps 与工具", "skills": ["Docker"]},
{"category": "产品、设计与分析", "skills": ["Figma", "SQL analysis"]},
{"category": "其他技能", "skills": ["Unusual Tool"]},
]