"""Opt-in PostgreSQL checks for the task alert event state machine.""" from __future__ import annotations import os from datetime import datetime, timezone from types import SimpleNamespace from uuid import uuid4 import pytest from psycopg import AsyncConnection from psycopg.rows import dict_row from app.core.config import settings from app.domain.schemas.task_alert import TaskAlertEvent from app.infra.db.postgresql.task_alerts import TaskAlertRepository from app.services.task_alerts import TaskAlertService pytestmark = [ pytest.mark.skipif( os.getenv("TASK_ALERT_LIVE_TEST") != "1", reason="set TASK_ALERT_LIVE_TEST=1 to use provisioned workbench alert tables", ), pytest.mark.anyio, ] @pytest.fixture def anyio_backend(): return "asyncio" @pytest.mark.anyio async def test_alert_event_round_trip_and_idempotency(): project_id = uuid4() now = datetime(2026, 9, 18, 3, tzinfo=timezone.utc) conn = await AsyncConnection.connect( host=settings.DB_HOST, port=settings.DB_PORT, user=settings.DB_USER, password=settings.DB_PASSWORD, dbname="tjwater_v2", connect_timeout=3, options="-c statement_timeout=10000", row_factory=dict_row, ) try: service = TaskAlertService( TaskAlertRepository(conn, project_id), None, clock=lambda: now, internal_producer_id="AUTOTEST-scheduler", ) event = TaskAlertEvent( event_id=uuid4(), producer_id="AUTOTEST-scheduler", alert_id=f"AUTOTEST-{uuid4()}", event_type="raised", occurred_at=now, condition_id=None, source={ "task_type": "scheduled_task", "task_id": "AUTOTEST-schedule", "run_id": "AUTOTEST-occurrence", }, title="AUTOTEST alert", description="Synthetic integration record", severity="warning", ) first = await service.push(event) repeated = await service.push(event) assert first == repeated row = await conn.execute( """SELECT revision,status,document FROM workbench.alerts WHERE project_id=%s AND alert_id=%s""", (project_id, event.alert_id), ) stored = await row.fetchone() assert stored["revision"] == 1 and stored["status"] == "active" assert stored["document"]["producer_id"] == "AUTOTEST-scheduler" finally: # Assertion reads open an implicit transaction. End it before starting the # cleanup transaction, otherwise closing the connection rolls the nested # cleanup savepoint back together with that read transaction. await conn.rollback() async with conn.transaction(): await conn.execute( "DELETE FROM workbench.alert_events WHERE project_id=%s", (project_id,) ) await conn.execute( "DELETE FROM workbench.alerts WHERE project_id=%s", (project_id,) ) await conn.close()