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

323 lines
12 KiB
Python

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