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"