generated from kgod/ai-review-template
323 lines
12 KiB
Python
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 |