generated from kgod/ai-review-template
自内部仓库剥离深度优化与 RAG 知识库后的交付版本: - Builder 对话式简历生成(FSM + 意图路由 LLM 兜底增强) - 条目级轻度优化:事实覆盖门禁 + STAR/bullet 修复链,功能/简介/成果与技术栈同级保护 - 简历导入:DOCX/PDF 解析、结构归一、手机号脱敏 - PostgreSQL 运行时 + Alembic 迁移链 Co-Authored-By: Claude <noreply@anthropic.com>
167 lines
7.7 KiB
Python
167 lines
7.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.resume_document_mutations import set_generated_profile_summary
|
|
from builder_flow_helpers import (
|
|
active_component,
|
|
confirm_card,
|
|
create_builder_session,
|
|
event,
|
|
finish_education,
|
|
select_section,
|
|
send_message,
|
|
start_education,
|
|
submit_identity,
|
|
)
|
|
|
|
|
|
def test_create_resume_suggests_experience_type_before_showing_identity_card(client: TestClient) -> None:
|
|
_, body = create_builder_session(client, job_type="campus")
|
|
card = active_component(body)
|
|
assert card["data"]["component"] == "choice_chips"
|
|
assert card["data"]["module"] == "builder_next_section"
|
|
assert card["data"]["value"] == "education"
|
|
assert all(block["data"].get("component") != "record_fields" for block in body["turn"]["blocks"] if block["type"] == "component")
|
|
|
|
|
|
def test_next_step_offers_skill_recommendation_and_finish_actions(client: TestClient) -> None:
|
|
_, body = create_builder_session(client)
|
|
card = active_component(body)
|
|
values = {option["value"] for option in card["data"]["options"]}
|
|
assert "builder_recommend_skills" in values
|
|
assert "builder_finish" in values
|
|
|
|
|
|
def test_builder_skill_recommendations_require_confirmation_before_write(client: TestClient) -> None:
|
|
session_id, body = create_builder_session(client)
|
|
recommended = event(client, session_id, body, "select", {"value": "builder_recommend_skills"})
|
|
assert recommended.status_code == 200, recommended.text
|
|
recommendation_card = active_component(recommended.json())
|
|
assert recommendation_card["data"]["module"] == "builder_skill_select"
|
|
assert recommendation_card["data"]["multiple"] is True
|
|
options = recommendation_card["data"]["options"]
|
|
assert options
|
|
assert recommended.json()["resume"]["content"].get("skill_groups") == []
|
|
|
|
chosen = options[0]["value"]
|
|
confirmed = event(
|
|
client,
|
|
session_id,
|
|
recommended.json(),
|
|
"select",
|
|
{"values": [chosen], "value": chosen},
|
|
)
|
|
assert confirmed.status_code == 200, confirmed.text
|
|
skills = [
|
|
skill
|
|
for group in confirmed.json()["resume"]["content"]["skill_groups"]
|
|
for skill in group["skills"]
|
|
]
|
|
assert chosen in skills
|
|
assert any(block["type"] == "resume_patch" for block in confirmed.json()["turn"]["blocks"])
|
|
|
|
|
|
def test_builder_finish_generates_summary_from_current_resume(client: TestClient) -> None:
|
|
session_id, body = create_builder_session(client)
|
|
finished = event(client, session_id, body, "select", {"value": "builder_finish"})
|
|
assert finished.status_code == 200, finished.text
|
|
payload = finished.json()
|
|
summary = payload["resume"]["content"]["profile_summary"]
|
|
assert summary["source"] == "ai_generated"
|
|
assert summary["stale"] is False
|
|
assert summary["content"]
|
|
assert "resume_patch" in {block["type"] for block in payload["turn"]["blocks"]}
|
|
assert payload.get("builder_stream_phases") == ["saving"]
|
|
|
|
|
|
def test_generated_summary_replaces_only_stale_builder_summary() -> None:
|
|
stale = {
|
|
"profile_summary": {"content": "旧的个人总结内容足够长,可以被新的总结替换。", "stale": True},
|
|
}
|
|
refreshed = set_generated_profile_summary(stale, "根据最新简历信息生成的个人总结内容足够长。", replace_stale=True)
|
|
assert refreshed["profile_summary"]["content"] == "根据最新简历信息生成的个人总结内容足够长。"
|
|
assert refreshed["profile_summary"]["stale"] is False
|
|
|
|
current = {
|
|
"profile_summary": {"content": "用户手工维护的总结内容足够长,不应被自动覆盖。", "stale": False},
|
|
}
|
|
preserved = set_generated_profile_summary(current, "新的自动总结内容足够长。", replace_stale=True)
|
|
assert preserved["profile_summary"]["content"] == current["profile_summary"]["content"]
|
|
|
|
|
|
def test_social_builder_suggests_work_but_allows_another_experience_type(client: TestClient) -> None:
|
|
session_id, body = create_builder_session(client, job_type="social")
|
|
assert active_component(body)["data"]["value"] == "work_experience"
|
|
response = select_section(client, session_id, body, "project_experience")
|
|
assert active_component(response)["data"]["record_type"] == "project_experience"
|
|
|
|
|
|
def test_education_missing_facts_asks_follow_up_before_confirmation(client: TestClient) -> None:
|
|
session_id, body = create_builder_session(client)
|
|
start_education(client, session_id, body)
|
|
|
|
follow_up = send_message(client, session_id, "学习了数据结构和数据库课程。")
|
|
assert "GPA/均分" in follow_up["turn"]["content"]
|
|
assert "课程项目" in follow_up["turn"]["content"]
|
|
assert not any(block["data"].get("component") == "experience_confirm_card" for block in follow_up["turn"]["blocks"] if block["type"] == "component")
|
|
|
|
proposal = send_message(client, session_id, "GPA 3.7/4.0,专业前 20%,完成数据库课程项目。")
|
|
card = active_component(proposal)
|
|
assert card["data"]["component"] == "experience_confirm_card"
|
|
assert "学习了数据结构" in card["data"]["value"]["description"]
|
|
assert "GPA 3.7/4.0" in card["data"]["value"]["description"]
|
|
|
|
|
|
def test_no_information_reply_skips_asked_gaps_and_then_shows_confirmation(client: TestClient) -> None:
|
|
session_id, body = create_builder_session(client)
|
|
start_education(client, session_id, body)
|
|
first_follow_up = send_message(client, session_id, "学习了软件工程相关课程。")
|
|
assert "没有也可以直接说没有" in first_follow_up["turn"]["content"]
|
|
|
|
proposal = send_message(client, session_id, "没有")
|
|
card = active_component(proposal)
|
|
assert card["data"]["component"] == "experience_confirm_card"
|
|
assert card["data"]["value"]["description"] == "学习了软件工程相关课程。"
|
|
|
|
|
|
def test_confirmed_campus_education_recommends_project_experience(client: TestClient) -> None:
|
|
session_id, body = create_builder_session(client)
|
|
start_education(client, session_id, body)
|
|
proposal = finish_education(client, session_id, body, "GPA 3.7/4.0,获得两次校级奖学金,完成数据库课程项目。")
|
|
confirmed = confirm_card(client, session_id, proposal)
|
|
|
|
entry = next(block for block in confirmed["turn"]["blocks"] if block["type"] == "resume_patch")["data"]["value"]["sections"][0]["items"][0]
|
|
assert entry["description"].startswith("GPA 3.7/4.0")
|
|
next_card = active_component(confirmed)
|
|
assert next_card["data"]["component"] == "choice_chips"
|
|
assert next_card["data"]["value"] == "project_experience"
|
|
|
|
|
|
def test_project_gaps_are_limited_and_only_candidate_after_follow_up(client: TestClient) -> None:
|
|
session_id, body = create_builder_session(client)
|
|
identity_card = select_section(client, session_id, body, "project_experience")
|
|
detail_prompt = submit_identity(
|
|
client,
|
|
session_id,
|
|
identity_card,
|
|
{
|
|
"project_name": "Resume Agent",
|
|
"project_role": "Developer",
|
|
"start_date": "2024-01",
|
|
"end_date_or_present": "2024-06",
|
|
},
|
|
)
|
|
assert "没有也可以直接说没有" in detail_prompt["turn"]["content"]
|
|
|
|
first_follow_up = send_message(client, session_id, "负责后端接口开发,使用 Python 和 FastAPI。")
|
|
questions = [line for line in first_follow_up["turn"]["content"].splitlines() if line]
|
|
assert len(questions) == 2
|
|
assert "交付物" in first_follow_up["turn"]["content"]
|
|
assert "量化信息" in first_follow_up["turn"]["content"]
|
|
|
|
proposal = send_message(client, session_id, "交付 REST API 并上线,覆盖 3 个业务流程。")
|
|
assert active_component(proposal)["data"]["component"] == "experience_confirm_card"
|