from __future__ import annotations import pytest from app.resume_document import ( DocumentError, apply_delete_bullet, apply_delete_entry, apply_update_basics, apply_update_bullet, apply_update_entry, confirm_proposal, entry_fingerprint, find_entry, merge_ids, merge_profile_refresh, reject_proposal, set_pending_proposal, undo_entry, ) from app.services import RuleBasedEntryExpander def old_doc() -> dict: return { "schema_version": 1, "basics": {"name": "Test User", "masked_phone": "138****8000"}, "target": {"job_type": "campus"}, "sections": [ { "kind": "education", "heading": "Education", "items": [ { "school": "Example University", "major": "Computer Science", "degree": "Bachelor", "start_date": "2021-09", "end_date_or_present": "2025-06", } ], } ], } def test_merge_ids_assigns_three_level_ids() -> None: merged = merge_ids(None, old_doc()) section = merged["sections"][0] entry = section["items"][0] assert merged["schema_version"] == 3 assert merged["skill_groups"] == [] assert section["id"].startswith("sec_") assert entry["id"].startswith("entry_") assert entry["provenance"] == "user_provided" def test_merge_ids_preserves_matched_ids() -> None: first = merge_ids(None, old_doc()) changed = old_doc() changed["sections"][0]["items"][0]["major"] = "Software Engineering" second = merge_ids(first, changed) assert second["sections"][0]["id"] == first["sections"][0]["id"] assert second["sections"][0]["items"][0]["id"] == first["sections"][0]["items"][0]["id"] def test_merge_ids_new_item_gets_new_id() -> None: first = merge_ids(None, old_doc()) changed = old_doc() changed["sections"][0]["items"].append( {"school": "Another University", "major": "Mathematics", "start_date": "2017-09"} ) second = merge_ids(first, changed) ids = [item["id"] for item in second["sections"][0]["items"]] assert ids[0] == first["sections"][0]["items"][0]["id"] assert ids[1] != ids[0] assert len(set(ids)) == 2 def test_profile_refresh_preserves_confirmed_entry_content_and_adds_new_records() -> None: existing = merge_ids(None, old_doc()) entry_id = existing["sections"][0]["items"][0]["id"] existing = apply_update_entry( existing, entry_id, {"description": "Built the original course project."} ) existing = set_pending_proposal( existing, entry_id, "Led the course project delivery and completed the core implementation.", source="ai_expanded", ) existing = confirm_proposal(existing, entry_id) regenerated = old_doc() regenerated["sections"][0]["items"][0]["description"] = "Built the original course project." regenerated["sections"].append( { "kind": "internship_experience", "heading": "Internship", "items": [ { "company": "Example Labs", "position": "Backend Intern", "start_date": "2024-03", "end_date_or_present": "2024-09", "description": "Implemented API endpoints.", } ], } ) refreshed = merge_profile_refresh(existing, regenerated) education = refreshed["sections"][0]["items"][0] internship = refreshed["sections"][1]["items"][0] assert education["id"] == entry_id assert education["description"] == ( "Led the course project delivery and completed the core implementation." ) assert education["provenance"] == "ai_expanded" assert internship["company"] == "Example Labs" assert internship["id"].startswith("entry_") def test_merge_ids_normalizes_bullets_to_objects() -> None: doc = old_doc() doc["sections"][0]["items"][0]["resume_bullets"] = ["Built an order service", "Improved QPS by 30%"] bullets = merge_ids(None, doc)["sections"][0]["items"][0]["resume_bullets"] assert all(set(bullet) == {"id", "text"} for bullet in bullets) assert bullets[0]["text"] == "Built an order service" def test_update_basics_and_entry_fields() -> None: doc = merge_ids(None, old_doc()) entry_id = doc["sections"][0]["items"][0]["id"] doc = apply_update_basics(doc, {"name": "Updated User", "phone": "13900139000"}) assert doc["basics"]["name"] == "Updated User" assert doc["basics"]["masked_phone"] == "139****9000" assert "phone" not in doc["basics"] doc = apply_update_entry(doc, entry_id, {"major": "Software Engineering"}) _, entry = find_entry(doc, entry_id) assert entry["major"] == "Software Engineering" assert entry["provenance"] == "user_edited" def test_update_entry_rejects_unknown_field() -> None: doc = merge_ids(None, old_doc()) entry_id = doc["sections"][0]["items"][0]["id"] with pytest.raises(DocumentError) as exc: apply_update_entry(doc, entry_id, {"hack_field": "x"}) assert exc.value.code == "field_not_writable" def test_proposal_lifecycle_confirm_and_undo() -> None: doc = merge_ids(None, old_doc()) entry_id = doc["sections"][0]["items"][0]["id"] doc = apply_update_entry(doc, entry_id, {"description": "Original description"}) doc = set_pending_proposal(doc, entry_id, "Optimized experience description.", source="ai_expanded") _, entry = find_entry(doc, entry_id) assert entry["pending_proposal"]["based_on"] == entry_fingerprint(entry) doc = confirm_proposal(doc, entry_id) _, entry = find_entry(doc, entry_id) assert "pending_proposal" not in entry assert entry["description"] == "Optimized experience description." assert "resume_bullets" not in entry assert entry["provenance"] == "ai_expanded" assert entry["previous_version"]["description"] == "Original description" assert entry["previous_version"]["provenance"] == "user_edited" doc = undo_entry(doc, entry_id) _, entry = find_entry(doc, entry_id) assert "previous_version" not in entry assert entry["description"] == "Original description" assert entry["provenance"] == "user_edited" def test_confirm_rejects_stale_proposal() -> None: doc = merge_ids(None, old_doc()) entry_id = doc["sections"][0]["items"][0]["id"] doc = set_pending_proposal(doc, entry_id, "Optimized proposal", source="ai_expanded") doc = apply_update_entry(doc, entry_id, {"major": "Changed"}) with pytest.raises(DocumentError) as exc: confirm_proposal(doc, entry_id) assert exc.value.code == "proposal_stale" def test_confirm_allows_user_to_apply_proposal_with_omission_diagnostics() -> None: doc = merge_ids(None, old_doc()) entry_id = doc["sections"][0]["items"][0]["id"] doc = apply_update_entry(doc, entry_id, {"description": "Original imported description"}) doc = set_pending_proposal( doc, entry_id, "Compressed candidate", source="ai_expanded", omitted_fact_ids=["fact_1"], ) confirmed = confirm_proposal(doc, entry_id) _, entry = find_entry(confirmed, entry_id) assert entry["description"] == "Compressed candidate" assert entry["previous_version"]["description"] == "Original imported description" def test_reject_proposal_keeps_original_description() -> None: doc = merge_ids(None, old_doc()) entry_id = doc["sections"][0]["items"][0]["id"] doc = apply_update_entry(doc, entry_id, {"description": "Original text"}) doc = set_pending_proposal(doc, entry_id, "Optimized proposal", source="rule_polish") doc = reject_proposal(doc, entry_id) _, entry = find_entry(doc, entry_id) assert "pending_proposal" not in entry assert entry["description"] == "Original text" def test_legacy_bullet_edit_and_delete_remain_supported() -> None: source = old_doc() source["sections"][0]["items"][0]["resume_bullets"] = ["b1", "b2"] doc = merge_ids(None, source) entry_id = doc["sections"][0]["items"][0]["id"] _, entry = find_entry(doc, entry_id) bullet_id = entry["resume_bullets"][0]["id"] doc = apply_update_bullet(doc, entry_id, bullet_id, "Updated bullet") _, entry = find_entry(doc, entry_id) assert entry["resume_bullets"][0]["text"] == "Updated bullet" doc = apply_delete_bullet(doc, entry_id, bullet_id) _, entry = find_entry(doc, entry_id) assert len(entry["resume_bullets"]) == 1 doc = apply_delete_entry(doc, entry_id) assert find_entry(doc, entry_id) is None def test_rule_expander_uses_highlights() -> None: expander = RuleBasedEntryExpander() entry = {"title": "Backend internship", "highlights": ["built A", "built B"], "metrics": []} proposal = expander.expand(entry, context={}) assert proposal["source"] == "rule_polish" assert "built A" in proposal["optimized_description"] assert "built B" in proposal["optimized_description"] assert "bullets" not in proposal def test_rule_expander_falls_back_to_description() -> None: expander = RuleBasedEntryExpander() proposal = expander.expand({"description": "Handled A. Improved B."}, context={}) assert proposal["optimized_description"].startswith("Handled A. Improved B.") def test_rule_expander_empty_when_no_material() -> None: expander = RuleBasedEntryExpander() assert expander.expand({"title": "x"}, context={})["optimized_description"] == "" def test_rule_expander_visibly_rewrites_common_formal_sentence() -> None: expander = RuleBasedEntryExpander() proposal = expander.expand( {"description": "Used Python to complete algorithm practice and won a provincial second prize."}, context={"entry_type": "competition"}, ) assert "Python" in proposal["optimized_description"] assert "provincial second prize" in proposal["optimized_description"] def test_confirm_keeps_unconfirmed_suggestions_out_of_resume_description() -> None: doc = merge_ids(None, old_doc()) entry_id = doc["sections"][0]["items"][0]["id"] doc = apply_update_entry(doc, entry_id, {"description": "Implemented the service API."}) doc = set_pending_proposal( doc, entry_id, "Implemented and maintained the service API for the project.", source="ai_expanded", unconfirmed_suggestions=["Confirm whether Redis caching was used."], validation_warnings=["suggestion_requires_confirmation"], ) _, pending_entry = find_entry(doc, entry_id) pending = pending_entry["pending_proposal"] assert pending["unconfirmed_suggestions"] == ["Confirm whether Redis caching was used."] assert pending["validation_warnings"] == ["suggestion_requires_confirmation"] confirmed = confirm_proposal(doc, entry_id) _, confirmed_entry = find_entry(confirmed, entry_id) assert confirmed_entry["description"] == "Implemented and maintained the service API for the project." assert "Redis" not in confirmed_entry["description"] assert "pending_proposal" not in confirmed_entry def test_profile_refresh_retains_imported_sections_and_unmatched_entries() -> None: existing = merge_ids( None, { **old_doc(), "sections": [ *old_doc()["sections"], { "kind": "project_experience", "heading": "Projects", "items": [ {"project_name": "Imported Project", "description": "Imported detail."}, ], }, ], }, ) existing["sections"][0]["items"].append( { "id": "entry_manual", "provenance": "user_edited", "school": "Manual University", "major": "Mathematics", "description": "Manual addition.", } ) existing["profile_summary"] = { "content": "Imported personal summary.", "source": "user_edited", "generated_at": None, "stale": False, } refreshed = merge_profile_refresh(existing, old_doc()) sections = {section["kind"]: section for section in refreshed["sections"]} assert sections["project_experience"]["items"][0]["project_name"] == "Imported Project" assert any(item["school"] == "Manual University" for item in sections["education"]["items"]) assert refreshed["profile_summary"]["content"] == "Imported personal summary." assert refreshed["profile_summary"]["stale"] is True