HR-ATS-Portal/backend/tests/test_application_history.py

165 lines
6.2 KiB
Python

"""Reapplicant history — badge vs kept attempts.
No database: the helpers decide whether a prior row counts as Reapplied
and how a system-dropped attempt is labelled.
"""
from job.candidate.serializers import (
is_assigned_application,
is_kept_application,
rejection_reason,
serialize_application_history,
serialize_application_history_item,
)
from job.candidate.views import _is_current_application, _is_earlier_application
def test_unassigned_inbox_is_not_a_reapplication():
row = {
"source": "inbox",
"job_post_id": None,
"job_title": None,
"status": "CLOSED",
"attachment": True,
"match_status": "matched",
}
assert is_assigned_application(row) is False
assert is_kept_application(row) is True
assert rejection_reason(row) is None
def test_assigned_inbox_is_a_reapplication():
row = {"source": "inbox", "job_post_id": "job-1", "job_title": "Engineer"}
assert is_assigned_application(row) is True
assert rejection_reason(row) is None
def test_form_role_name_counts_without_job_post():
row = {"source": "form", "job_post_id": None, "job_title": "Data Analyst"}
assert is_assigned_application(row) is True
def test_unreadable_cv_is_wrong_format():
row = {
"source": "inbox",
"job_post_id": None,
"attachment": True,
"match_status": "no_text",
"status": "CLOSED",
}
assert rejection_reason(row) == "wrong_format"
item = serialize_application_history_item(row)
assert item["status"] == "WRONG_FORMAT"
assert item["rejection_reason"] == "wrong_format"
assert is_kept_application(item) is True
def test_unreadable_cv_plus_later_mail_is_a_reapplication():
"""Attached CVs count even when the first PDF had no extractable text."""
later = {
"source": "inbox",
"message_id": "new",
"job_post_id": None,
"status": "CLOSED",
"attachment": True,
"match_status": "matched",
}
earlier = {
"source": "inbox",
"message_id": "old",
"job_post_id": None,
"status": "CLOSED",
"attachment": True,
"match_status": "no_text",
}
history = serialize_application_history("a@x.com", applications=[later, earlier])
assert history["is_reapplicant"] is True
assert [item["rejection_reason"] for item in history["applications"]] == [None, "wrong_format"]
def test_body_only_mail_is_wrong_format():
row = {"source": "inbox", "job_post_id": None, "attachment": False, "status": "CLOSED"}
assert rejection_reason(row) == "wrong_format"
def test_filtered_classifier_row_is_wrong_format():
row = {"source": "filtered", "job_post_id": None, "status": "WRONG_FORMAT"}
assert rejection_reason(row) == "wrong_format"
history = serialize_application_history("a@x.com", applications=[row])
assert history["is_reapplicant"] is False
assert history["applications"] == []
def test_history_reapplicant_counts_two_unassigned_mails():
unassigned = {"source": "inbox", "job_post_id": None, "status": "CLOSED", "attachment": True, "match_status": "matched"}
only_mail = serialize_application_history("a@x.com", applications=[unassigned])
assert only_mail["is_reapplicant"] is False
both = serialize_application_history("a@x.com", applications=[unassigned, dict(unassigned)])
assert both["is_reapplicant"] is True
assert len(both["applications"]) == 2
def test_wrong_format_plus_one_mail_is_not_a_reapplication():
mail = {"source": "inbox", "job_post_id": None, "status": "CLOSED", "attachment": True, "match_status": "matched"}
dropped = {"source": "filtered", "job_post_id": None, "status": "WRONG_FORMAT"}
history = serialize_application_history("a@x.com", applications=[mail, dropped])
assert history["is_reapplicant"] is False
assert len(history["applications"]) == 1
assert history["applications"][0]["source"] == "inbox"
def test_clicked_inbox_row_is_not_a_previous_application():
"""List payloads use `source` for the To address, not 'inbox'."""
pk = "11111111-1111-1111-1111-111111111111"
item = {"source": "inbox", "message_id": pk, "inbox_id": 9, "job_post_id": None}
payload = {
"id": pk,
"email": "a@x.com",
"source": "careers-rozee@example.com",
"position": "Backend Engineer",
}
assert _is_current_application(item, payload) is True
assert _is_current_application(item, {"id": "other", "source": "careers-rozee@example.com"}) is False
def test_form_row_matches_without_a_sheet_field():
fid = "22222222-2222-2222-2222-222222222222"
item = {"source": "form", "form_data_id": fid, "job_title": "Analyst"}
assert _is_current_application(item, {"id": fid, "candidate_email": "a@x.com"}) is True
def test_filtered_row_matches_graph_id_on_detail_payload():
item = {"source": "filtered", "message_id": "AAMkGraph", "upstream_id": "AAMkGraph"}
payload = {"id": "11111111-1111-1111-1111-111111111111", "message_id": "AAMkGraph"}
assert _is_current_application(item, payload) is True
def test_later_mail_is_not_a_previous_application():
"""12:52 is later than 12:51 — it is not a previous attempt of the 12:51 row."""
later = {"source": "inbox", "message_id": "b", "applied_at": "2026-09-07T07:52:00+00:00"}
payload = {"id": "a", "received": "2026-09-07T07:51:00+00:00"}
assert _is_current_application(later, payload) is False
assert _is_earlier_application(later, payload) is False
def test_earlier_mail_is_a_previous_application():
earlier = {"source": "inbox", "message_id": "a", "applied_at": "2026-09-07T07:51:00+00:00"}
payload = {"id": "b", "received": "2026-09-07T07:52:00+00:00"}
assert _is_earlier_application(earlier, payload) is True
def test_undated_row_is_not_guessed_earlier():
item = {"source": "inbox", "message_id": "b", "applied_at": None}
payload = {"id": "a", "received": "2026-09-07T07:51:00+00:00"}
assert _is_earlier_application(item, payload) is False
def test_history_item_keeps_user_id():
item = serialize_application_history_item({
"source": "manual",
"user_id": "user-1",
"manual_upload_candidate_id": "m-1",
"job_post_id": "job-1",
"job_title": "Analyst",
})
assert item["user_id"] == "user-1"