generated from kgod/ai-review-template
923 lines
32 KiB
Python
923 lines
32 KiB
Python
from __future__ import annotations
|
|
|
|
from copy import deepcopy
|
|
import json
|
|
from typing import Any
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from app.offerpai_resume import (
|
|
OfferPaiResumeClient,
|
|
OfferPaiResumeError,
|
|
OfferPaiResumeProvider,
|
|
build_offerpai_resume_payload,
|
|
map_v3_resume_to_offerpai,
|
|
merge_offerpai_resume_snapshot,
|
|
offerpai_payload_hash,
|
|
offerpai_update_marker,
|
|
)
|
|
|
|
|
|
TOKEN = "header.payload.signature-value"
|
|
BASE_URL = "https://test.offerpai.com.cn/api/"
|
|
|
|
|
|
def test_client_uses_api_base_path_cookie_and_normalizes_ids() -> None:
|
|
requests: list[httpx.Request] = []
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
requests.append(request)
|
|
assert request.headers.get("cookie") == f"Token={TOKEN}"
|
|
if request.url.path == "/api/resume/canCreate":
|
|
return httpx.Response(200, json=True)
|
|
if request.url.path == "/api/resume/list":
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"code": "0",
|
|
"data": [
|
|
{"id": 2081575100391407617, "resumeName": "First"},
|
|
{"resumeId": "resume-local", "resumeName": "Second"},
|
|
],
|
|
},
|
|
)
|
|
if request.url.path == "/api/resume":
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"code": "0",
|
|
"data": {"resumeId": 2081575100391407618},
|
|
},
|
|
)
|
|
if request.url.path == "/api/resume/work":
|
|
return httpx.Response(200, json={"id": "2081575100391407618"})
|
|
if request.url.path == "/api/resume/delete":
|
|
return httpx.Response(200, json={"code": "0", "data": True})
|
|
return httpx.Response(404)
|
|
|
|
main_payload = {
|
|
"resumeId": "2081575100391407617",
|
|
"resumeName": "Backend Resume",
|
|
}
|
|
with httpx.Client(
|
|
base_url=BASE_URL,
|
|
transport=httpx.MockTransport(handler),
|
|
) as http_client:
|
|
provider: OfferPaiResumeProvider = OfferPaiResumeClient(
|
|
BASE_URL, client=http_client
|
|
)
|
|
assert provider.can_create(TOKEN) is True
|
|
assert provider.list_resumes(TOKEN) == [
|
|
{"id": "2081575100391407617", "resumeName": "First"},
|
|
{
|
|
"resumeId": "resume-local",
|
|
"resumeName": "Second",
|
|
"id": "resume-local",
|
|
},
|
|
]
|
|
assert provider.save_main(TOKEN, main_payload) == "2081575100391407618"
|
|
assert (
|
|
provider.replace_section(
|
|
TOKEN,
|
|
"work",
|
|
resume_id="2081575100391407618",
|
|
items=[{"companyName": "OfferPai"}],
|
|
)
|
|
== "2081575100391407618"
|
|
)
|
|
assert provider.delete_resume(TOKEN, "2081575100391407618") is None
|
|
|
|
assert main_payload["resumeId"] == "2081575100391407617"
|
|
assert [request.url.path for request in requests] == [
|
|
"/api/resume/canCreate",
|
|
"/api/resume/list",
|
|
"/api/resume",
|
|
"/api/resume/work",
|
|
"/api/resume/delete",
|
|
]
|
|
assert json.loads(requests[2].content) == {
|
|
"resumeId": 2081575100391407617,
|
|
"resumeName": "Backend Resume",
|
|
}
|
|
assert json.loads(requests[3].content) == {
|
|
"resumeId": 2081575100391407618,
|
|
"items": [{"companyName": "OfferPai"}],
|
|
}
|
|
assert requests[4].url.params["resumeId"] == "2081575100391407618"
|
|
|
|
|
|
def test_client_accepts_raw_list_and_enveloped_boolean() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
if request.url.path.endswith("/canCreate"):
|
|
return httpx.Response(200, json={"code": 0, "data": False})
|
|
return httpx.Response(200, json=[{"id": 7}])
|
|
|
|
with httpx.Client(
|
|
base_url=BASE_URL,
|
|
transport=httpx.MockTransport(handler),
|
|
) as http_client:
|
|
client = OfferPaiResumeClient(BASE_URL, client=http_client)
|
|
assert client.can_create(TOKEN) is False
|
|
assert client.list_resumes(TOKEN) == [{"id": "7"}]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("status", "code", "public_status"),
|
|
[
|
|
(401, "external_auth_invalid", 401),
|
|
(403, "external_auth_invalid", 401),
|
|
(404, "offerpai_resume_not_found", 404),
|
|
(409, "offerpai_resume_conflict", 409),
|
|
(422, "offerpai_resume_rejected", 422),
|
|
(500, "offerpai_resume_unavailable", 502),
|
|
],
|
|
)
|
|
def test_http_errors_are_stable_and_do_not_leak_token(
|
|
status: int, code: str, public_status: int
|
|
) -> None:
|
|
def handler(_request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(status, text=f"upstream accidentally echoed {TOKEN}")
|
|
|
|
with httpx.Client(
|
|
base_url=BASE_URL,
|
|
transport=httpx.MockTransport(handler),
|
|
) as http_client:
|
|
with pytest.raises(OfferPaiResumeError) as captured:
|
|
OfferPaiResumeClient(BASE_URL, client=http_client).can_create(TOKEN)
|
|
|
|
error = captured.value
|
|
assert error.code == code
|
|
assert error.status_code == public_status
|
|
assert TOKEN not in str(error)
|
|
assert TOKEN not in error.public_message
|
|
assert TOKEN not in repr(error)
|
|
|
|
|
|
def test_business_error_and_invalid_json_are_token_free() -> None:
|
|
responses = iter(
|
|
[
|
|
httpx.Response(
|
|
200,
|
|
json={"code": "RESUME_REJECTED", "msg": TOKEN, "data": None},
|
|
),
|
|
httpx.Response(200, text=f"not-json-{TOKEN}"),
|
|
]
|
|
)
|
|
|
|
def handler(_request: httpx.Request) -> httpx.Response:
|
|
return next(responses)
|
|
|
|
with httpx.Client(
|
|
base_url=BASE_URL,
|
|
transport=httpx.MockTransport(handler),
|
|
) as http_client:
|
|
client = OfferPaiResumeClient(BASE_URL, client=http_client)
|
|
with pytest.raises(OfferPaiResumeError) as business_error:
|
|
client.can_create(TOKEN)
|
|
with pytest.raises(OfferPaiResumeError) as invalid_json_error:
|
|
client.can_create(TOKEN)
|
|
|
|
assert business_error.value.code == "offerpai_resume_rejected"
|
|
assert business_error.value.upstream_code == "RESUME_REJECTED"
|
|
assert invalid_json_error.value.code == "offerpai_resume_invalid_response"
|
|
assert TOKEN not in str(business_error.value)
|
|
assert TOKEN not in str(invalid_json_error.value)
|
|
|
|
|
|
def test_timeout_and_bad_local_token_have_stable_errors() -> None:
|
|
def timeout_handler(request: httpx.Request) -> httpx.Response:
|
|
raise httpx.ReadTimeout(f"timeout with {TOKEN}", request=request)
|
|
|
|
with httpx.Client(
|
|
base_url=BASE_URL,
|
|
transport=httpx.MockTransport(timeout_handler),
|
|
) as http_client:
|
|
with pytest.raises(OfferPaiResumeError) as timeout_error:
|
|
OfferPaiResumeClient(BASE_URL, client=http_client).list_resumes(TOKEN)
|
|
|
|
assert timeout_error.value.code == "offerpai_resume_timeout"
|
|
assert timeout_error.value.status_code == 504
|
|
assert TOKEN not in str(timeout_error.value)
|
|
|
|
with pytest.raises(OfferPaiResumeError) as token_error:
|
|
OfferPaiResumeClient(BASE_URL).can_create("short token")
|
|
assert token_error.value.code == "external_auth_invalid"
|
|
assert token_error.value.status_code == 401
|
|
assert "short token" not in str(token_error.value)
|
|
|
|
|
|
def test_build_payload_maps_v3_content_without_mutating_inputs() -> None:
|
|
content: dict[str, Any] = {
|
|
"basics": {
|
|
"name": "Ada Lovelace",
|
|
"email": "ada@example.com",
|
|
"phone": "134****2384",
|
|
"city": "Shenzhen",
|
|
"wechat_number": "ada-wechat",
|
|
"portfolio_url": "https://example.com/ada",
|
|
},
|
|
"target": {"position": "Backend Engineer"},
|
|
"profile_summary": {"content": "Builds reliable systems."},
|
|
"skills": ["python"],
|
|
"skill_groups": [
|
|
{"category": "Languages", "skills": ["Python", "SQL"]}
|
|
],
|
|
"certificates": ["CET-6"],
|
|
"sections": [
|
|
{
|
|
"kind": "education",
|
|
"items": [
|
|
{
|
|
"id": "edu-1",
|
|
"school": "Example University",
|
|
"major": "Computer Science",
|
|
"degree": "Bachelor",
|
|
"study_type": "Full-time",
|
|
"start_date": "2020-09",
|
|
"end_date_or_present": "2024-06",
|
|
"description": [
|
|
{"id": "paragraph-kept", "text": "Top 10%."}
|
|
],
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"kind": "work_experience",
|
|
"items": [
|
|
{
|
|
"company": "OfferPai",
|
|
"position": "Engineer",
|
|
"start_date": "2024-07",
|
|
"end_date_or_present": "present",
|
|
"resume_bullets": "Built APIs.\nReduced latency.",
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"kind": "internship_experience",
|
|
"items": [
|
|
{
|
|
"id": "intern-1",
|
|
"company": "Example Labs",
|
|
"role": "Intern",
|
|
"start_date": "2023-01",
|
|
"end_date_or_present": "\u81f3\u4eca",
|
|
"description": ["Shipped a service."],
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"kind": "project_experience",
|
|
"items": [
|
|
{
|
|
"id": "project-1",
|
|
"project_name": "Resume Agent",
|
|
"project_role": "Lead",
|
|
"organization": "Personal",
|
|
"start_date": "2025-01",
|
|
"end_date": "now",
|
|
"description": {
|
|
"id": "project-paragraph",
|
|
"text": "Designed the workflow.",
|
|
},
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"kind": "competition",
|
|
"items": [
|
|
{
|
|
"id": "competition-1",
|
|
"name": "Hackathon",
|
|
"award": "Gold",
|
|
"date": "2024-05",
|
|
"highlights": ["Won first place."],
|
|
}
|
|
],
|
|
},
|
|
{"kind": "skills", "items": [{"name": "Go"}, "SQL"]},
|
|
{"kind": "certificates", "items": [{"value": "AWS"}]},
|
|
{"kind": "campus_experience", "items": []},
|
|
{"kind": "additional_experience", "items": []},
|
|
],
|
|
}
|
|
profile: dict[str, Any] = {
|
|
"phone": "134****2384",
|
|
"account_phone": "13421012384",
|
|
"target_position": "Platform Engineer",
|
|
"external_account": {"user_id": "2081575100391407617"},
|
|
"tags": {
|
|
"skills": ["SQL", "Docker"],
|
|
"certificates": ["CET-6", "PMP"],
|
|
},
|
|
}
|
|
original_content = deepcopy(content)
|
|
original_profile = deepcopy(profile)
|
|
|
|
main, sections, unsupported = build_offerpai_resume_payload(
|
|
content,
|
|
profile,
|
|
resume_name="Candidate Resume",
|
|
resume_id="2081575100391407617",
|
|
)
|
|
second = map_v3_resume_to_offerpai(
|
|
content,
|
|
profile,
|
|
resume_name="Candidate Resume",
|
|
resume_id="2081575100391407617",
|
|
)
|
|
|
|
assert content == original_content
|
|
assert profile == original_profile
|
|
assert main == {
|
|
"resumeName": "Candidate Resume",
|
|
"targetPosition": "Platform Engineer",
|
|
"avatarUrl": "",
|
|
"name": "Ada Lovelace",
|
|
"email": "ada@example.com",
|
|
"mobileNumber": "13421012384",
|
|
"city": "Shenzhen",
|
|
"wechatNumber": "ada-wechat",
|
|
"portfolioUrl": "https://example.com/ada",
|
|
"skills": ["Python", "SQL", "Docker", "Go"],
|
|
"certificates": ["CET-6", "PMP", "AWS"],
|
|
"summary": "Builds reliable systems.",
|
|
"resumeId": "2081575100391407617",
|
|
}
|
|
assert tuple(sections) == (
|
|
"education",
|
|
"work",
|
|
"internship",
|
|
"project",
|
|
"competition",
|
|
)
|
|
assert all(
|
|
payload["resumeId"] == "2081575100391407617"
|
|
for payload in sections.values()
|
|
)
|
|
assert sections["education"]["items"] == [
|
|
{
|
|
"school": "Example University",
|
|
"major": "Computer Science",
|
|
"degree": "Bachelor",
|
|
"studyType": "Full-time",
|
|
"startDate": "2020.09",
|
|
"endDate": "2024.06",
|
|
"description": [{"id": "paragraph-kept", "text": "Top 10%."}],
|
|
}
|
|
]
|
|
work = sections["work"]["items"][0]
|
|
assert work["companyName"] == "OfferPai"
|
|
assert work["position"] == "Engineer"
|
|
assert work["startDate"] == "2024.07"
|
|
assert work["endDate"] == ""
|
|
assert [paragraph["text"] for paragraph in work["description"]] == [
|
|
"Built APIs.",
|
|
"Reduced latency.",
|
|
]
|
|
assert work["description"] == second.sections["work"]["items"][0]["description"]
|
|
assert all(
|
|
paragraph["id"].startswith("desc_") for paragraph in work["description"]
|
|
)
|
|
assert sections["internship"]["items"][0]["endDate"] == ""
|
|
assert sections["project"]["items"][0] == {
|
|
"companyName": "Personal",
|
|
"projectName": "Resume Agent",
|
|
"role": "Lead",
|
|
"startDate": "2025.01",
|
|
"endDate": "",
|
|
"description": [
|
|
{"id": "project-paragraph", "text": "Designed the workflow."}
|
|
],
|
|
}
|
|
assert sections["competition"]["items"][0]["awardDate"] == "2024.05"
|
|
assert unsupported == ("campus_experience", "additional_experience")
|
|
|
|
|
|
def test_payload_never_sends_a_masked_phone() -> None:
|
|
main, _sections, _unsupported = build_offerpai_resume_payload(
|
|
{"basics": {"phone": "13421012384"}},
|
|
{"phone": "134****2384"},
|
|
resume_name="Resume",
|
|
)
|
|
|
|
assert main["mobileNumber"] == ""
|
|
|
|
|
|
def test_payload_accepts_raw_phone_only_from_session_profile() -> None:
|
|
main, _sections, _unsupported = build_offerpai_resume_payload(
|
|
{"basics": {"phone": "134****2384"}},
|
|
{
|
|
"phone": "134****2384",
|
|
"external_account": {"mobileNumber": "13421012384"},
|
|
},
|
|
resume_name="Resume",
|
|
)
|
|
|
|
assert main["mobileNumber"] == "13421012384"
|
|
|
|
|
|
def test_client_gets_main_and_all_sections_with_cookie_query_and_normalized_ids() -> None:
|
|
requests: list[httpx.Request] = []
|
|
section_names = ("education", "work", "internship", "project", "competition")
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
requests.append(request)
|
|
assert request.method == "GET"
|
|
assert request.headers.get("cookie") == f"Token={TOKEN}"
|
|
assert request.url.params["resumeId"] == "2081575100391407617"
|
|
if request.url.path == "/api/resume":
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"code": "0",
|
|
"data": {
|
|
"id": 2081575100391407617,
|
|
"resumeId": 2081575100391407617,
|
|
"resumeName": "Remote resume",
|
|
},
|
|
},
|
|
)
|
|
section = request.url.path.rsplit("/", 1)[-1]
|
|
assert section in section_names
|
|
data = [
|
|
{
|
|
"id": 2081575100391407700 + section_names.index(section),
|
|
"resumeId": 2081575100391407617,
|
|
"description": [
|
|
{"id": 700 + section_names.index(section), "text": section}
|
|
],
|
|
}
|
|
]
|
|
# Exercise both documented envelopes and direct/raw response bodies.
|
|
if section_names.index(section) % 2:
|
|
return httpx.Response(200, json={"code": 0, "data": data})
|
|
return httpx.Response(200, json=data)
|
|
|
|
with httpx.Client(
|
|
base_url=BASE_URL,
|
|
transport=httpx.MockTransport(handler),
|
|
) as http_client:
|
|
client = OfferPaiResumeClient(BASE_URL, client=http_client)
|
|
main = client.get_main(TOKEN, "2081575100391407617")
|
|
section_results = {
|
|
section: client.list_section(
|
|
TOKEN,
|
|
section, # type: ignore[arg-type]
|
|
resume_id="2081575100391407617",
|
|
)
|
|
for section in section_names
|
|
}
|
|
|
|
assert main["id"] == "2081575100391407617"
|
|
assert main["resumeId"] == "2081575100391407617"
|
|
for index, section in enumerate(section_names):
|
|
item = section_results[section][0]
|
|
assert item["id"] == str(2081575100391407700 + index)
|
|
assert item["resumeId"] == "2081575100391407617"
|
|
assert item["description"] == [{"id": str(700 + index), "text": section}]
|
|
assert [request.url.path for request in requests] == [
|
|
"/api/resume",
|
|
"/api/resume/education",
|
|
"/api/resume/work",
|
|
"/api/resume/internship",
|
|
"/api/resume/project",
|
|
"/api/resume/competition",
|
|
]
|
|
|
|
|
|
def test_client_get_main_accepts_a_raw_record() -> None:
|
|
def handler(_request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(
|
|
200,
|
|
json={"id": 2081575100391407617, "resumeName": "Raw resume"},
|
|
)
|
|
|
|
with httpx.Client(
|
|
base_url=BASE_URL,
|
|
transport=httpx.MockTransport(handler),
|
|
) as http_client:
|
|
result = OfferPaiResumeClient(BASE_URL, client=http_client).get_main(
|
|
TOKEN, 2081575100391407617
|
|
)
|
|
|
|
assert result == {
|
|
"id": "2081575100391407617",
|
|
"resumeName": "Raw resume",
|
|
}
|
|
|
|
|
|
def test_update_marker_supports_instant_and_scalar_values() -> None:
|
|
assert offerpai_update_marker(
|
|
{"updateTime": {"seconds": 1785912615, "nanos": 42}}
|
|
) == "1785912615:42"
|
|
assert offerpai_update_marker({"updateTime": "2026-08-05T12:00:00Z"}) == (
|
|
"2026-08-05T12:00:00Z"
|
|
)
|
|
assert offerpai_update_marker({"updateTime": None}) is None
|
|
|
|
|
|
def test_external_description_edit_keeps_local_entry_id_by_paragraph_id() -> None:
|
|
existing_content = {
|
|
"schema_version": 3,
|
|
"basics": {},
|
|
"target": {},
|
|
"skill_groups": [],
|
|
"sections": [
|
|
{
|
|
"id": "section-work",
|
|
"kind": "work_experience",
|
|
"heading": "Work",
|
|
"items": [
|
|
{
|
|
"id": "local-alpha",
|
|
"company": "Alpha",
|
|
"position": "Engineer",
|
|
"start_date": "2024-01",
|
|
"end_date_or_present": "present",
|
|
"description": "Original text",
|
|
"offerpai_record_id": "old-alpha-row",
|
|
"offerpai_description_ids": ["stable-alpha-paragraph"],
|
|
"pending_proposal": {"content": "stale proposal"},
|
|
"previous_version": {"description": "older text"},
|
|
"gap_report": {"missing": ["metric"]},
|
|
},
|
|
{
|
|
"id": "local-beta",
|
|
"company": "Beta",
|
|
"position": "Engineer",
|
|
"start_date": "2023-01",
|
|
"end_date_or_present": "2023-12",
|
|
"description": "Beta text",
|
|
"offerpai_record_id": "new-alpha-row",
|
|
"offerpai_description_ids": ["stable-beta-paragraph"],
|
|
},
|
|
],
|
|
}
|
|
],
|
|
}
|
|
remote_sections = {
|
|
"education": [],
|
|
"work": [
|
|
{
|
|
# The replacement row ID collides with Beta's old row ID. The
|
|
# stable paragraph ID must still associate this record to Alpha.
|
|
"id": "new-alpha-row",
|
|
"companyName": "Alpha",
|
|
"position": "Engineer",
|
|
"startDate": "2024.01",
|
|
"endDate": "",
|
|
"description": [
|
|
{
|
|
"id": "stable-alpha-paragraph",
|
|
"text": "Externally edited text",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
"internship": [],
|
|
"project": [],
|
|
"competition": [],
|
|
}
|
|
|
|
merged, _profile, _unsupported = merge_offerpai_resume_snapshot(
|
|
existing_content,
|
|
{},
|
|
{"skills": [], "certificates": []},
|
|
remote_sections,
|
|
)
|
|
|
|
work_section = next(
|
|
section
|
|
for section in merged["sections"]
|
|
if section["kind"] == "work_experience"
|
|
)
|
|
assert len(work_section["items"]) == 1
|
|
item = work_section["items"][0]
|
|
assert item["id"] == "local-alpha"
|
|
assert item["offerpai_record_id"] == "new-alpha-row"
|
|
assert item["offerpai_description_ids"] == ["stable-alpha-paragraph"]
|
|
assert item["description"] == "Externally edited text"
|
|
assert item["provenance"] == "external_synced"
|
|
assert "pending_proposal" not in item
|
|
assert "previous_version" not in item
|
|
assert item["gap_report"] == {"missing": ["metric"]}
|
|
|
|
|
|
def test_local_remote_pull_roundtrip_preserves_dates_paragraphs_and_unsupported_sections() -> None:
|
|
content: dict[str, Any] = {
|
|
"schema_version": 3,
|
|
"resume_name": "Candidate Resume",
|
|
"basics": {
|
|
"name": "Ada Lovelace",
|
|
"email": "ada@example.com",
|
|
"city": "Shenzhen",
|
|
"avatar_url": "https://example.com/avatar.png",
|
|
"wechat_number": "ada-wechat",
|
|
"portfolio_url": "https://example.com/ada",
|
|
},
|
|
"target": {"position": "Platform Engineer"},
|
|
"profile_summary": {"content": "Builds reliable systems."},
|
|
# Deliberately cross category order; hash comparison is semantic.
|
|
"skill_groups": [
|
|
{"category": "Tools", "skills": ["Docker", "Python"]}
|
|
],
|
|
"sections": [
|
|
{
|
|
"id": "section-education",
|
|
"kind": "education",
|
|
"heading": "Education",
|
|
"items": [
|
|
{
|
|
"id": "local-education",
|
|
"school": "Example University",
|
|
"major": "Computer Science",
|
|
"degree": "Bachelor",
|
|
"study_type": "Full-time",
|
|
"start_date": "2020-09",
|
|
"end_date_or_present": "2024-06",
|
|
"description": [
|
|
{"id": "education-paragraph", "text": "Top 10%."}
|
|
],
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"id": "section-work",
|
|
"kind": "work_experience",
|
|
"heading": "Work",
|
|
"items": [
|
|
{
|
|
"id": "local-work",
|
|
"company": "OfferPai",
|
|
"position": "Engineer",
|
|
"start_date": "2024-07",
|
|
"end_date_or_present": "present",
|
|
"description": [
|
|
{"id": "work-paragraph", "text": "Built APIs."}
|
|
],
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"id": "section-internship",
|
|
"kind": "internship_experience",
|
|
"heading": "Internship",
|
|
"items": [
|
|
{
|
|
"id": "local-internship",
|
|
"company": "Example Labs",
|
|
"position": "Intern",
|
|
"start_date": "2023-01",
|
|
"end_date_or_present": "2023-06",
|
|
"description": [
|
|
{
|
|
"id": "internship-paragraph",
|
|
"text": "Shipped a service.",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"id": "section-project",
|
|
"kind": "project_experience",
|
|
"heading": "Project",
|
|
"items": [
|
|
{
|
|
"id": "local-project",
|
|
"company": "Personal",
|
|
"project_name": "Resume Agent",
|
|
"project_role": "Lead",
|
|
"start_date": "2025-01",
|
|
"end_date_or_present": "present",
|
|
"description": [
|
|
{
|
|
"id": "project-paragraph",
|
|
"text": "Designed the workflow.",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"id": "section-competition",
|
|
"kind": "competition",
|
|
"heading": "Competition",
|
|
"items": [
|
|
{
|
|
"id": "local-competition",
|
|
"name": "Hackathon",
|
|
"award": "Gold",
|
|
"date": "2024-05",
|
|
"description": [
|
|
{
|
|
"id": "competition-paragraph",
|
|
"text": "Won first place.",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"id": "section-certificates",
|
|
"kind": "certificates",
|
|
"heading": "Certificates",
|
|
"items": [{"id": "local-certificate", "value": "CET-6"}],
|
|
},
|
|
{
|
|
"id": "section-campus",
|
|
"kind": "campus_experience",
|
|
"heading": "Campus",
|
|
"items": [
|
|
{
|
|
"id": "local-campus",
|
|
"organization": "Student Union",
|
|
"role": "Member",
|
|
}
|
|
],
|
|
},
|
|
],
|
|
}
|
|
profile: dict[str, Any] = {
|
|
"phone": "13421012384",
|
|
"phone_source": "account",
|
|
"account_phone": "13421012384",
|
|
"tags": {"skills": [], "certificates": []},
|
|
}
|
|
outbound = map_v3_resume_to_offerpai(
|
|
content,
|
|
profile,
|
|
resume_name="Candidate Resume",
|
|
resume_id="2081575100391407617",
|
|
)
|
|
remote_main = deepcopy(outbound.main)
|
|
remote_main.pop("resumeId")
|
|
remote_main["id"] = 2081575100391407617
|
|
remote_sections: dict[str, list[dict[str, Any]]] = {}
|
|
for section_index, (kind, payload) in enumerate(outbound.sections.items()):
|
|
items = deepcopy(payload["items"])
|
|
for item_index, item in enumerate(items):
|
|
item["id"] = 2081575100391407700 + section_index * 10 + item_index
|
|
remote_sections[kind] = items
|
|
|
|
merged_content, merged_profile, unsupported = merge_offerpai_resume_snapshot(
|
|
content,
|
|
profile,
|
|
remote_main,
|
|
remote_sections, # type: ignore[arg-type]
|
|
)
|
|
roundtrip = map_v3_resume_to_offerpai(
|
|
merged_content,
|
|
merged_profile,
|
|
resume_name="Candidate Resume",
|
|
resume_id="2081575100391407617",
|
|
)
|
|
|
|
assert offerpai_payload_hash(remote_main, remote_sections) == (
|
|
offerpai_payload_hash(roundtrip.main, roundtrip.sections)
|
|
)
|
|
assert unsupported == ("campus_experience",)
|
|
campus = next(
|
|
section
|
|
for section in merged_content["sections"]
|
|
if section["kind"] == "campus_experience"
|
|
)
|
|
assert campus == content["sections"][-1]
|
|
|
|
by_kind = {section["kind"]: section for section in merged_content["sections"]}
|
|
assert by_kind["education"]["items"][0]["id"] == "local-education"
|
|
assert by_kind["education"]["items"][0]["start_date"] == "2020-09"
|
|
assert by_kind["education"]["items"][0]["end_date_or_present"] == "2024-06"
|
|
assert by_kind["work_experience"]["items"][0]["end_date_or_present"] == (
|
|
"present"
|
|
)
|
|
assert by_kind["project_experience"]["items"][0]["project_role"] == "Lead"
|
|
assert by_kind["competition"]["items"][0]["date"] == "2024-05"
|
|
|
|
for kind, payload in outbound.sections.items():
|
|
assert [item["description"] for item in roundtrip.sections[kind]["items"]] == [
|
|
item["description"] for item in payload["items"]
|
|
]
|
|
assert all(
|
|
isinstance(
|
|
by_kind[local_kind]["items"][0]["offerpai_record_id"], str
|
|
)
|
|
for local_kind in (
|
|
"education",
|
|
"work_experience",
|
|
"internship_experience",
|
|
"project_experience",
|
|
"competition",
|
|
)
|
|
)
|
|
|
|
|
|
def test_remote_main_clears_converge_without_reusing_account_identity_phone() -> None:
|
|
existing_content = {
|
|
"schema_version": 3,
|
|
"resume_name": "Old resume",
|
|
"basics": {
|
|
"name": "Old name",
|
|
"email": "old@example.com",
|
|
"city": "Old city",
|
|
"avatar_url": "old-avatar",
|
|
"wechat_number": "old-wechat",
|
|
"portfolio_url": "old-portfolio",
|
|
"masked_phone": "134****2384",
|
|
},
|
|
"target": {"position": "Old target"},
|
|
"profile_summary": {"content": "Old summary"},
|
|
"summary": "Old legacy summary",
|
|
"skills": ["Old root skill"],
|
|
"certificates": ["Old root certificate"],
|
|
"skill_groups": [{"category": "Old", "skills": ["Old grouped skill"]}],
|
|
"sections": [
|
|
{
|
|
"id": "old-certificates",
|
|
"kind": "certificates",
|
|
"heading": "Certificates",
|
|
"items": [{"id": "old-certificate", "value": "Old certificate"}],
|
|
}
|
|
],
|
|
}
|
|
profile = {
|
|
"name": "Old name",
|
|
"email": "old@example.com",
|
|
"city": "Old city",
|
|
"avatar_url": "old-avatar",
|
|
"wechat_number": "old-wechat",
|
|
"portfolio_url": "old-portfolio",
|
|
"phone": "13421012384",
|
|
"phone_source": "account",
|
|
"account_phone": "13421012384",
|
|
"external_account": {"mobile_number": "13421012384"},
|
|
"target_position": "Old target",
|
|
"resume_name": "Old resume",
|
|
"summary": "Old summary",
|
|
"tags": {
|
|
"skills": ["Old tagged skill"],
|
|
"certificates": ["Old tagged certificate"],
|
|
},
|
|
}
|
|
remote_main = {
|
|
"id": "2081575100391407617",
|
|
"resumeName": "",
|
|
"targetPosition": "",
|
|
"avatarUrl": "",
|
|
"name": "",
|
|
"email": "",
|
|
"mobileNumber": "",
|
|
"city": "",
|
|
"wechatNumber": "",
|
|
"portfolioUrl": "",
|
|
"skills": [],
|
|
"certificates": [],
|
|
"summary": "",
|
|
}
|
|
remote_sections = {
|
|
"education": [],
|
|
"work": [],
|
|
"internship": [],
|
|
"project": [],
|
|
"competition": [],
|
|
}
|
|
|
|
merged_content, merged_profile, _unsupported = merge_offerpai_resume_snapshot(
|
|
existing_content,
|
|
profile,
|
|
remote_main,
|
|
remote_sections,
|
|
)
|
|
outbound = map_v3_resume_to_offerpai(
|
|
merged_content,
|
|
merged_profile,
|
|
resume_id="2081575100391407617",
|
|
)
|
|
|
|
assert merged_profile["account_phone"] == "13421012384"
|
|
assert merged_profile["external_account"] == {
|
|
"mobile_number": "13421012384"
|
|
}
|
|
assert merged_profile["phone"] == ""
|
|
assert merged_profile["phone_source"] == "offerpai_resume"
|
|
assert outbound.main == {
|
|
"resumeName": "",
|
|
"targetPosition": "",
|
|
"avatarUrl": "",
|
|
"name": "",
|
|
"email": "",
|
|
"mobileNumber": "",
|
|
"city": "",
|
|
"wechatNumber": "",
|
|
"portfolioUrl": "",
|
|
"skills": [],
|
|
"certificates": [],
|
|
"summary": "",
|
|
"resumeId": "2081575100391407617",
|
|
}
|
|
assert offerpai_payload_hash(remote_main, remote_sections) == (
|
|
offerpai_payload_hash(outbound.main, outbound.sections)
|
|
)
|