"""Tests for ``_cancel_parallel_feishu_instance_for_seat`` — the helper that converges the Feishu approver's inbox with a local admin-override approval of a seat allocation. Mirrors the testable shape of ``test_scope_request_admin_cancel_feishu.py`` for the scope-request counterpart. Six branches covered: 1. Happy path — pending ApprovalRequest with an external_id → Feishu ``cancel_instance`` called with the right args. 2. No matching ApprovalRequest (admin-only seat with Feishu approval disabled) → no Feishu call. Implicit coverage of the ``status == "pending"`` filter: a terminal-state row from a re-request cycle simply doesn't match the SELECT and lands in the same "no row" branch as test #2. 3. ApprovalRequest exists but ``external_id`` is empty (submit-race rollback) → no Feishu call (defence against the empty-string call the API would reject). 4. ApprovalRequest exists but ``approval_code`` missing from ``source_payload`` (legacy / malformed row) → no Feishu call (the Feishu cancel API requires the template code). 5. Feishu cancel API raises → warning log carries the instance code so ops can clean the orphan manually; helper does NOT raise. 6. DB SELECT itself raises (rare transient error) → warning log notes the lookup failed; no Feishu call is attempted because no instance was identified. These tests stub the SQLAlchemy session + ApprovalService rather than spinning up a real DB; the production helper is small enough that the SELECT shape and the cancel-args contract are what need testing. Heavier end-to-end coverage rides on the existing seat-admin tests. """ from __future__ import annotations from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock from uuid import uuid4 import pytest from app.domains.asset.router import ( _cancel_parallel_feishu_instance_for_seat, ) def _make_db_returning(approval_row) -> MagicMock: """Build a fake AsyncSession whose ``execute(...).scalar_one_or_none()`` returns the supplied row. The exact SELECT shape is opaque to the test — only the return-value contract matters.""" db = MagicMock() result = MagicMock() result.scalar_one_or_none = MagicMock(return_value=approval_row) db.execute = AsyncMock(return_value=result) return db def _patch_feishu_client(monkeypatch, *, cancel_side_effect=None) -> MagicMock: """Replace ``ApprovalService._get_client`` so the helper doesn't try to instantiate a real Feishu HTTP client.""" client = MagicMock() client.resolve_user_id_by_email = AsyncMock(return_value="feishu_admin_uid") if cancel_side_effect is None: client.cancel_instance = AsyncMock() else: client.cancel_instance = AsyncMock(side_effect=cancel_side_effect) monkeypatch.setattr( "app.approval.service.ApprovalService._get_client", AsyncMock(return_value=client), ) return client @pytest.mark.asyncio async def test_pending_approval_request_triggers_feishu_cancel(monkeypatch): """When there's a pending ApprovalRequest carrying the parallel Feishu instance code, the admin-override path cancels it so the approver's inbox doesn't sit on a stale card.""" alloc_id = uuid4() approval_row = SimpleNamespace( external_id="INSTANCE_ABC", source_payload={ "approval_code": "TEMPLATE_SOFTWARE_SEAT", "seat_allocation_id": str(alloc_id), }, ) db = _make_db_returning(approval_row) client = _patch_feishu_client(monkeypatch) await _cancel_parallel_feishu_instance_for_seat( db, seat_allocation_id=alloc_id, actor_work_email="admin@example.com", ) client.cancel_instance.assert_awaited_once_with( instance_code="INSTANCE_ABC", user_id="feishu_admin_uid", approval_code="TEMPLATE_SOFTWARE_SEAT", ) @pytest.mark.asyncio async def test_no_matching_approval_request_skips_feishu_call(monkeypatch): """Seats requested with Feishu approval disabled have no parallel ApprovalRequest row — the helper must short-circuit cleanly without a Feishu API call.""" db = _make_db_returning(None) client = _patch_feishu_client(monkeypatch) await _cancel_parallel_feishu_instance_for_seat( db, seat_allocation_id=uuid4(), actor_work_email="admin@example.com", ) client.cancel_instance.assert_not_awaited() @pytest.mark.asyncio async def test_approval_row_without_external_id_skips_cancel(monkeypatch): """A submit-race rollback could leave an ApprovalRequest row with a cleared external_id. Without an instance code there's nothing to cancel and we must NOT call the Feishu API with an empty string.""" approval_row = SimpleNamespace( external_id="", source_payload={"approval_code": "TEMPLATE_SOFTWARE_SEAT"}, ) db = _make_db_returning(approval_row) client = _patch_feishu_client(monkeypatch) await _cancel_parallel_feishu_instance_for_seat( db, seat_allocation_id=uuid4(), actor_work_email="admin@example.com", ) client.cancel_instance.assert_not_awaited() @pytest.mark.asyncio async def test_missing_approval_code_skips_cancel(monkeypatch): """Legacy rows / malformed source_payload without ``approval_code`` → can't tell Feishu which template the instance belongs to, so refuse to call (the API requires it). No raise.""" approval_row = SimpleNamespace( external_id="INSTANCE_ABC", source_payload={}, # approval_code missing ) db = _make_db_returning(approval_row) client = _patch_feishu_client(monkeypatch) await _cancel_parallel_feishu_instance_for_seat( db, seat_allocation_id=uuid4(), actor_work_email="admin@example.com", ) client.cancel_instance.assert_not_awaited() @pytest.mark.asyncio async def test_feishu_cancel_failure_logged_with_instance_code( monkeypatch, caplog, ): """A Feishu API hiccup must NOT bubble — the local seat decision is durable already (committed before this call). The warning log must carry a stable event code and pseudonymized instance field so ops can correlate the orphan without copying the raw Feishu identifier.""" alloc_id = uuid4() approval_row = SimpleNamespace( external_id="INSTANCE_FAIL", source_payload={ "approval_code": "TEMPLATE_SOFTWARE_SEAT", "seat_allocation_id": str(alloc_id), }, ) db = _make_db_returning(approval_row) client = _patch_feishu_client( monkeypatch, cancel_side_effect=RuntimeError("feishu 500"), ) # Must not raise await _cancel_parallel_feishu_instance_for_seat( db, seat_allocation_id=alloc_id, actor_work_email="admin@example.com", ) client.cancel_instance.assert_awaited_once() assert any( "APPROVAL_CANCEL_FAILED" in record.message for record in caplog.records if record.levelname == "WARNING" ) assert all("INSTANCE_FAIL" not in record.message for record in caplog.records) assert all("feishu 500" not in record.message for record in caplog.records) @pytest.mark.asyncio async def test_approval_request_lookup_failure_is_swallowed(monkeypatch, caplog): """If the DB SELECT itself raises (rare — transient DB error between the outer commit and this helper), the helper logs and returns. The local seat decision is already durable; no Feishu side effect is possible since we never identified an instance.""" db = MagicMock() db.execute = AsyncMock(side_effect=RuntimeError("db transient")) client = _patch_feishu_client(monkeypatch) await _cancel_parallel_feishu_instance_for_seat( db, seat_allocation_id=uuid4(), actor_work_email="admin@example.com", ) client.cancel_instance.assert_not_awaited() assert any( "APPROVAL_LOOKUP_FAILED" in record.message for record in caplog.records if record.levelname == "WARNING" ) assert all("db transient" not in record.message for record in caplog.records)