"""PR1 — per-person write observability. The identity-sync ledger used to store the *pre-render* payload and a null response, which is why a 41041-class failure was undiagnosable (you couldn't see the real ``department_ids`` sent or Feishu's code). The Feishu runner now captures, per person, the ACTUAL wire body it sent and Feishu's response/error via :meth:`FeishuContactRunner._write`, exposed as ``_last_write_request`` / ``_last_write_response`` for the orchestrator to persist verbatim on the run-item. These tests pin that behaviour: * the FINAL body is captured (a slot-dropped retry overwrites the first), * success vs error responses are recorded distinctly (incl. feishu_code), * a person resolved without any vendor write leaves the capture cleared. """ from __future__ import annotations from typing import Any import pytest from app.core.exceptions import AppException from app.domains.identity_sync.runners.feishu_contact import FeishuContactRunner def _client(): from app.approval.feishu_client import FeishuApprovalClient return FeishuApprovalClient(app_id="cli_x", app_secret="s") class _Calls(list): def __init__(self, responses: list[Any]) -> None: super().__init__() self._responses = list(responses) async def __call__( self, method: str, path: str, *, json_body: dict[str, Any] | None = None, **_kw, ) -> dict[str, Any]: self.append({"method": method, "path": path, "json_body": json_body}) if not self._responses: raise RuntimeError(f"unexpected call {method} {path}") nxt = self._responses.pop(0) if isinstance(nxt, BaseException): raise nxt return nxt def _err(code: int, msg: str = "boom") -> AppException: return AppException( code="FEISHU_API_ERROR", message=f"Feishu API error {code}: {msg}", status_code=502, ) @pytest.mark.asyncio async def test_capture_records_wire_body_and_response_on_success(monkeypatch): runner = FeishuContactRunner( client=_client(), helix_dept_to_external={"D1": "od-d1"}, ) calls = _Calls([ {"user": {"department_ids": ["od-d1"]}}, # GET current depts {"user": {"user_id": "u1"}}, # PATCH ok ]) monkeypatch.setattr(runner._client, "_request", calls) out = await runner.upsert( person_email="a@pingcap.com", payload={"email": "a@pingcap.com", "name": "A", "department_ids": ["D1"]}, prior_external_id="u1", ) assert out.status == "success" # The captured request is the actual wire body (carrying department_ids), # not the pre-render payload. assert runner._last_write_request is not None assert runner._last_write_request["email"] == "a@pingcap.com" assert "department_ids" in runner._last_write_request assert runner._last_write_response == { "ok": True, "response": {"user": {"user_id": "u1"}}, } @pytest.mark.asyncio async def test_capture_reflects_final_body_after_slot_drop(monkeypatch): """A dept-rejected PATCH retries WITHOUT department_ids; the capture must reflect the FINAL (fallback) body actually accepted, so the ledger shows what really landed.""" runner = FeishuContactRunner( client=_client(), helix_dept_to_external={"D1": "od-d1"}, ) calls = _Calls([ {"user": {"department_ids": ["od-d1"]}}, # GET current depts _err(230009, "department not found"), # PATCH → dept error {}, # retry (no dept) ok ]) monkeypatch.setattr(runner._client, "_request", calls) await runner.upsert( person_email="b@pingcap.com", payload={"email": "b@pingcap.com", "name": "B", "department_ids": ["D1"]}, prior_external_id="u2", ) # Final accepted body dropped the dept slot — capture reflects that. assert runner._last_write_request is not None assert "department_ids" not in runner._last_write_request assert runner._last_write_response == {"ok": True, "response": {}} @pytest.mark.asyncio async def test_capture_records_error_code_on_terminal_failure(monkeypatch): runner = FeishuContactRunner( client=_client(), helix_dept_to_external={"D1": "od-d1"}, ) calls = _Calls([ {"user": {"department_ids": ["od-d1"]}}, # GET current depts _err(99999, "boom"), # PATCH → terminal error ]) monkeypatch.setattr(runner._client, "_request", calls) out = await runner.upsert( person_email="c@pingcap.com", payload={"email": "c@pingcap.com", "name": "C", "department_ids": ["D1"]}, prior_external_id="u3", ) assert out.status == "failed" assert runner._last_write_response is not None assert runner._last_write_response["ok"] is False assert runner._last_write_response["feishu_code"] == 99999 @pytest.mark.asyncio async def test_capture_cleared_when_no_vendor_write(monkeypatch): """A suppressed person (admin skip-list, missing on Feishu) performs NO write — the capture must be cleared, not inherited from a prior person in the sweep.""" runner = FeishuContactRunner( client=_client(), helix_dept_to_external={"D1": "od-d1"}, skip_provision_emails=frozenset({"skip@pingcap.com"}), ) # Pre-seed as if a previous person wrote. runner._last_write_request = {"stale": True} runner._last_write_response = {"ok": True} calls = _Calls([ {"user_list": []}, # email lookup miss → would create, but suppressed ]) monkeypatch.setattr(runner._client, "_request", calls) out = await runner.upsert( person_email="skip@pingcap.com", payload={"email": "skip@pingcap.com", "name": "Skip", "department_ids": ["D1"]}, prior_external_id=None, ) assert out.status == "skipped" assert runner._last_write_request is None assert runner._last_write_response is None