from __future__ import annotations import json import os from contextlib import asynccontextmanager from typing import Any import pytest os.environ.setdefault( "DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test", ) from src.db import base as db_base # noqa: E402 from src.services import audit_sink, contract_service # noqa: E402 class _CaptureDb: def __init__(self) -> None: self.calls: list[tuple[Any, dict[str, Any]]] = [] async def execute( self, statement: Any, params: dict[str, Any], ) -> None: self.calls.append((statement, params)) def _assert_jsonb_bind_contract(db: _CaptureDb) -> None: assert len(db.calls) == 1 statement, params = db.calls[0] normalized_sql = " ".join(str(statement).split()) assert ":details::jsonb" not in normalized_sql assert "CAST(:details AS JSONB)" in normalized_sql assert "details" in statement._bindparams assert "detail" not in statement._bindparams assert json.loads(params["details"])["result"] == "verified" @pytest.mark.asyncio async def test_audit_sink_uses_asyncpg_safe_jsonb_bind( monkeypatch: pytest.MonkeyPatch, ) -> None: db = _CaptureDb() @asynccontextmanager async def fake_db_context(project_id: str): assert project_id == "awoooi" yield db monkeypatch.setattr(db_base, "get_db_context", fake_db_context) await audit_sink._write_audit_impl( project_id="awoooi", action="run.approval.approve", resource_type="run", resource_id="run-1", details={"result": "verified"}, run_id="run-1", trace_id="trace-1", ) _assert_jsonb_bind_contract(db) @pytest.mark.asyncio async def test_contract_service_uses_asyncpg_safe_jsonb_bind( monkeypatch: pytest.MonkeyPatch, ) -> None: db = _CaptureDb() @asynccontextmanager async def fake_db_context(project_id: str): assert project_id == "awoooi" yield db monkeypatch.setattr(db_base, "get_db_context", fake_db_context) await contract_service._write_audit( project_id="awoooi", action="contract.revision.activated", resource_type="contract", resource_id="contract-1", details={"result": "verified"}, ) _assert_jsonb_bind_contract(db)