feat: add checksummed database migrations

This commit is contained in:
Sergey Antropoff
2026-07-12 23:53:26 +03:00
parent 355bb23d5e
commit 882ae97fcf
10 changed files with 374 additions and 1 deletions
+1
View File
@@ -0,0 +1 @@
"""PostgreSQL-backed integration tests."""
+36
View File
@@ -0,0 +1,36 @@
"""PostgreSQL migration acceptance checks."""
import os
import uuid
import asyncpg # type: ignore[import-untyped]
import pytest
from app.db.migrations import migrate
pytestmark = [
pytest.mark.integration,
pytest.mark.skipif(not os.getenv("TEST_DATABASE_URL"), reason="TEST_DATABASE_URL is required"),
]
async def test_migration_is_repeatable_and_constraints_hold() -> None:
connection = await asyncpg.connect(os.environ["TEST_DATABASE_URL"])
try:
await migrate(connection)
assert await migrate(connection) == 0
node_id = uuid.uuid4()
await connection.execute(
"INSERT INTO nodes(id, name, status) VALUES($1, $2, 'online') ON CONFLICT DO NOTHING",
node_id,
f"test-{node_id}",
)
with pytest.raises(asyncpg.CheckViolationError):
async with connection.transaction():
await connection.execute(
"INSERT INTO nodes(id, name, status) VALUES($1, $2, 'invalid')",
uuid.uuid4(),
f"invalid-{node_id}",
)
finally:
await connection.close()
+59
View File
@@ -0,0 +1,59 @@
"""Database primitive behavior independent of PostgreSQL."""
import asyncpg # type: ignore[import-untyped]
import pytest
from app.db.primitives import (
ConflictError,
DatabaseOperationError,
ReferenceError,
RetryPolicy,
TransientDatabaseError,
map_database_error,
require_affected,
retry_transient,
)
def test_error_mapping_is_stable_and_safe() -> None:
assert isinstance(map_database_error(asyncpg.UniqueViolationError("secret")), ConflictError)
assert isinstance(
map_database_error(asyncpg.ForeignKeyViolationError("secret")), ReferenceError
)
assert isinstance(
map_database_error(asyncpg.SerializationError("secret")), TransientDatabaseError
)
assert "secret" not in str(map_database_error(asyncpg.PostgresError("secret")))
def test_affected_row_checks() -> None:
require_affected("UPDATE 1")
with pytest.raises(DatabaseOperationError, match="expected 1"):
require_affected("UPDATE 0")
with pytest.raises(DatabaseOperationError, match="unrecognized"):
require_affected("BROKEN")
async def test_transient_retry_is_bounded() -> None:
calls = 0
async def operation() -> str:
nonlocal calls
calls += 1
if calls < 3:
raise TransientDatabaseError("retry")
return "ok"
assert await retry_transient(operation, RetryPolicy(attempts=3, base_delay_seconds=0)) == "ok"
assert calls == 3
async def test_transient_retry_propagates_final_failure() -> None:
async def operation() -> None:
raise TransientDatabaseError("retry")
with pytest.raises(TransientDatabaseError):
await retry_transient(operation, RetryPolicy(attempts=2, base_delay_seconds=0))
with pytest.raises(ValueError, match="positive"):
await retry_transient(operation, RetryPolicy(attempts=0))
+32
View File
@@ -0,0 +1,32 @@
"""Migration discovery and checksum tests."""
from pathlib import Path
from app.db.migrations import load_migrations
def test_load_migrations_is_ordered_and_checksummed(tmp_path: Path) -> None:
(tmp_path / "002_second.sql").write_text("SELECT 2;")
(tmp_path / "001_first.sql").write_text("SELECT 1;")
migrations = load_migrations(tmp_path)
assert [migration.version for migration in migrations] == [1, 2]
assert migrations[0].name == "001_first"
assert len(migrations[0].checksum) == 64
def test_repository_migration_defines_required_planes() -> None:
migration = load_migrations()[0]
for table in (
"contract_snapshots",
"nodes",
"resources",
"principals",
"acl_entries",
"tasks",
"scenarios",
"audit_events",
):
assert f"CREATE TABLE {table}" in migration.sql