fix(audit): bind jsonb through asyncpg
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m45s
CD Pipeline / build-and-deploy (push) Successful in 17m47s
CD Pipeline / post-deploy-checks (push) Successful in 2m49s
AI 技術雷達監控 / ai-technology-watch (push) Successful in 35s
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m45s
CD Pipeline / build-and-deploy (push) Successful in 17m47s
CD Pipeline / post-deploy-checks (push) Successful in 2m49s
AI 技術雷達監控 / ai-technology-watch (push) Successful in 35s
This commit is contained in:
@@ -199,7 +199,7 @@ async def _write_audit_impl(
|
||||
INSERT INTO audit_logs
|
||||
(project_id, action, resource_type, resource_id, details)
|
||||
VALUES
|
||||
(:project_id, :action, :resource_type, :resource_id, :details::jsonb)
|
||||
(:project_id, :action, :resource_type, :resource_id, CAST(:details AS JSONB))
|
||||
"""),
|
||||
{
|
||||
"project_id": project_id,
|
||||
|
||||
@@ -430,7 +430,7 @@ async def _write_audit(
|
||||
INSERT INTO audit_logs
|
||||
(project_id, action, resource_type, resource_id, details)
|
||||
VALUES
|
||||
(:project_id, :action, :resource_type, :resource_id, :details::jsonb)
|
||||
(:project_id, :action, :resource_type, :resource_id, CAST(:details AS JSONB))
|
||||
"""),
|
||||
{
|
||||
"project_id": project_id,
|
||||
|
||||
89
apps/api/tests/test_postgres_jsonb_bind_contract.py
Normal file
89
apps/api/tests/test_postgres_jsonb_bind_contract.py
Normal file
@@ -0,0 +1,89 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user