"""Regression: asset approval templates + API key approval submission must read their approval_code from ``integration_approval_flows``. This catches the production bug where ApprovalService.template_config() was still reading ``approval_xxx_code`` from the Feishu config dict — after migration 027 moved those to a separate table, the old read returned nothing and the UI's asset-request picker showed 0 templates. """ from __future__ import annotations import uuid import pytest from app.integrations.models import Integration, IntegrationApprovalFlow async def _seed_feishu_integration(db_session, flows: dict[str, str]): """Insert a Feishu chat_approval integration + the given flow map directly into the test DB so the consumer under test queries real rows (no mocking of the service helper).""" integ = Integration( id=uuid.uuid4(), kind="chat_approval", provider="feishu", name="Feishu Test", slug="feishu-test-primary", enabled=True, is_primary=True, config_public={"app_id": "cli_test"}, ) db_session.add(integ) await db_session.flush() for flow_key, external_code in flows.items(): db_session.add( IntegrationApprovalFlow( id=uuid.uuid4(), integration_id=integ.id, flow_key=flow_key, label=flow_key, external_code=external_code, active=True, ) ) await db_session.commit() return integ @pytest.mark.asyncio async def test_asset_template_config_reads_from_approval_flows_table(db_session): """Core fix: ApprovalService.template_config() must surface rows from integration_approval_flows, not from get_feishu_config's flat dict.""" await _seed_feishu_integration(db_session, { "laptop_replacement": "CODE-LAPTOP", "computer_borrow": "CODE-BORROW", "asset_return": "CODE-RETURN", "offboarding_buyback": "CODE-BUYBACK", # software_seat intentionally missing "api_key_request": "CODE-API-KEY", # should be filtered out }) from app.approval.service import ApprovalService svc = ApprovalService(db_session) config = await svc.template_config() # Asset-facing flows surface with the right approval_code. assert set(config.keys()) == { "laptop_replacement", "computer_borrow", "asset_return", "offboarding_buyback", } assert config["laptop_replacement"]["approval_code"] == "CODE-LAPTOP" assert config["asset_return"]["approval_code"] == "CODE-RETURN" # api_key_request is NOT in the asset picker (handled by apikey domain). assert "api_key_request" not in config # software_seat absent because the row wasn't seeded. assert "software_seat" not in config @pytest.mark.asyncio async def test_asset_template_config_empty_when_nothing_configured(db_session): """When no chat_approval integration exists at all, the asset picker returns {} rather than raising — matches the 'progressively roll out' behaviour the original code documented.""" from app.approval.service import ApprovalService svc = ApprovalService(db_session) config = await svc.template_config() assert config == {} @pytest.mark.asyncio async def test_apikey_submission_reads_approval_code_from_flows_table(db_session): """ApikeyService.submit_api_key_request must resolve the ``api_key_request`` flow_key from the approval-flows table. This was the SECOND broken consumer after the migration.""" from app.integrations.service import get_approval_flows_map # Without seeding: flows_map is empty → service should 503 with a # clear message pointing at the new admin path. flows = await get_approval_flows_map(session=db_session) assert flows == {} # After seeding: flow is resolvable. await _seed_feishu_integration(db_session, { "api_key_request": "CODE-API-KEY-REQ", }) flows = await get_approval_flows_map(session=db_session) assert flows.get("api_key_request") == "CODE-API-KEY-REQ" @pytest.mark.asyncio async def test_approval_flows_map_filters_inactive(db_session): """Inactive flow rows must not surface in the map so admins can disable a flow without editing the code.""" from app.integrations.service import get_approval_flows_map integ = Integration( id=uuid.uuid4(), kind="chat_approval", provider="feishu", name="Feishu", slug="feishu-inactive-test", enabled=True, is_primary=True, config_public={}, ) db_session.add(integ) await db_session.flush() db_session.add( IntegrationApprovalFlow( id=uuid.uuid4(), integration_id=integ.id, flow_key="laptop_replacement", label="x", external_code="CODE-ACTIVE", active=True, ) ) db_session.add( IntegrationApprovalFlow( id=uuid.uuid4(), integration_id=integ.id, flow_key="computer_borrow", label="y", external_code="CODE-DISABLED", active=False, ) ) await db_session.commit() flows = await get_approval_flows_map(session=db_session) assert flows == {"laptop_replacement": "CODE-ACTIVE"} @pytest.mark.asyncio async def test_approval_flows_map_respects_primary_integration(db_session): """If there are two chat_approval integrations, only the primary's flows are returned by the default helper call.""" from app.integrations.service import get_approval_flows_map primary = Integration( id=uuid.uuid4(), kind="chat_approval", provider="feishu", name="Primary", slug="feishu-primary-test", enabled=True, is_primary=True, config_public={}, ) secondary = Integration( id=uuid.uuid4(), kind="chat_approval", provider="feishu", name="Secondary", slug="feishu-secondary-test", enabled=True, is_primary=False, config_public={}, ) db_session.add_all([primary, secondary]) await db_session.flush() db_session.add( IntegrationApprovalFlow( id=uuid.uuid4(), integration_id=primary.id, flow_key="laptop_replacement", label="p", external_code="PRIMARY-CODE", active=True, ) ) db_session.add( IntegrationApprovalFlow( id=uuid.uuid4(), integration_id=secondary.id, flow_key="laptop_replacement", label="s", external_code="SECONDARY-CODE", active=True, ) ) await db_session.commit() flows = await get_approval_flows_map(session=db_session) assert flows == {"laptop_replacement": "PRIMARY-CODE"} # Explicit integration_id pulls that instance's flows. flows = await get_approval_flows_map( session=db_session, integration_id=secondary.id, ) assert flows == {"laptop_replacement": "SECONDARY-CODE"}