"""Defects the post-delivery review found, and the guards that keep them fixed. Each test here names a real failure the feature shipped with — every one of them silent, which is why they survived the per-PR test suites. """ from __future__ import annotations import uuid from datetime import UTC, datetime, timedelta import pytest from app.domains.approval_registry import confirmation as conf from app.domains.approval_registry import constants as const from app.domains.approval_registry import notify from app.domains.approval_registry import requests as reqs from app.domains.approval_registry import service as svc from app.identity.person_models import IdentityPerson BASE = "/api/v1/approval-flows" SELF = "/api/v1/me/approval-flows" async def _person(db_session, email, *, name="P", status="active"): person = IdentityPerson( id=uuid.uuid4(), work_email=email, display_name=name, employment_status=status, source_system="successfactors", hris_person_id=f"sf-{uuid.uuid4().hex[:10]}", ) db_session.add(person) await db_session.flush() await db_session.commit() return person @pytest.mark.asyncio async def test_the_flow_is_ready_notification_actually_sends( db_session, employee_user, ): """The payoff message of the whole feature. ``ApprovalFlow.owner`` is ``lazy="raise"``. A flow that was just CREATED (rather than adopted from the discovery sweep) is persistent but has never loaded that relationship, so reading it to name the owner raises — and the route wraps the push in a try/except, so the requester simply never hears that their flow is ready. The common path was the broken one. """ person, _ = employee_user row = await reqs.submit_request( db_session, requester=person, proposed_name="Request to join the GitHub organization", purpose="新同事需要访问公司的 GitHub 组织。", ) await reqs.decide_request(db_session, row, approve=True, actor=None) await db_session.commit() row = await reqs.get_request(db_session, row.id) flow = await reqs.register_built_flow( db_session, row, approval_code=str(uuid.uuid4()).upper(), actor=None, ) await db_session.commit() # ``flow`` here is the freshly created object, exactly what the route # hands to the notifier — that is the shape that used to raise. sent = await notify.notify_flow_request_registered(db_session, row, flow) assert sent == 1 @pytest.mark.asyncio async def test_nobody_is_chased_about_a_card_that_never_arrived( db_session, ): """Escalation copies the owner's MANAGER. Doing that over a card the platform failed to deliver blames somebody for silence they were never given the chance to break. """ owner = await _person(db_session, f"n-{uuid.uuid4().hex[:6]}@t.com") flow = await svc.create_flow( db_session, approval_code=str(uuid.uuid4()).upper(), name="流程", owner_person_id=owner.id, ) await db_session.commit() round_row, _ = await conf.open_round(db_session, label="2026-REVIEW-A") await db_session.commit() item = (await conf.round_items(db_session, round_row.id))[0] # Old enough to chase, but the card never went out. item.created_at = datetime.now(UTC).replace(tzinfo=None) - timedelta(days=30) item.notified_at = None await db_session.commit() assert await conf.overdue_items(db_session, round_row) == [] # Once the ask actually reached them, the clock is real. item.notified_at = datetime.now(UTC).replace(tzinfo=None) - timedelta(days=30) await db_session.commit() assert len(await conf.overdue_items(db_session, round_row)) == 1 assert flow.owner_person_id == owner.id @pytest.mark.asyncio async def test_the_card_landing_page_can_style_itself(client): """The platform's default ``Content-Security-Policy: default-src 'none'`` makes the browser drop the page's inline