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) )