2026-06-23 07:26:41 +02:00
|
|
|
"""0.10.0 observability: tasks.revision_count + the audit_log query index.
|
|
|
|
|
|
|
|
|
|
Migration 045 adds ``tasks.revision_count`` (the O(1) rework counter —
|
|
|
|
|
forward-only, existing rows default to 0) and the composite index
|
|
|
|
|
``audit_log(target_id, event_type, timestamp)`` that powers the cycle-time and
|
|
|
|
|
rework reconstruction queries. The real upgrade/downgrade chain is verified
|
|
|
|
|
separately against a throwaway Postgres (see project migration-verification
|
|
|
|
|
discipline); these assertions guard the resulting schema shape.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-06-29 05:38:21 +02:00
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
2026-06-23 07:26:41 +02:00
|
|
|
import pytest
|
|
|
|
|
from sqlalchemy import text
|
|
|
|
|
|
2026-06-29 05:38:21 +02:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
2026-06-23 07:26:41 +02:00
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2026-06-29 05:38:21 +02:00
|
|
|
async def test_revision_count_defaults_to_zero(db_session: AsyncSession) -> None:
|
2026-06-23 07:26:41 +02:00
|
|
|
result = await db_session.execute(
|
|
|
|
|
text(
|
|
|
|
|
"SELECT column_default, is_nullable "
|
|
|
|
|
"FROM information_schema.columns "
|
|
|
|
|
"WHERE table_name = 'tasks' AND column_name = 'revision_count'"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
row = result.first()
|
|
|
|
|
assert row is not None, "tasks.revision_count column must exist"
|
|
|
|
|
assert row[1] == "NO", "revision_count must be NOT NULL"
|
|
|
|
|
assert "0" in (row[0] or ""), "revision_count must default to 0"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2026-06-29 05:38:21 +02:00
|
|
|
async def test_audit_log_query_index_exists(db_session: AsyncSession) -> None:
|
2026-06-23 07:26:41 +02:00
|
|
|
result = await db_session.execute(
|
|
|
|
|
text(
|
|
|
|
|
"SELECT indexname FROM pg_indexes "
|
|
|
|
|
"WHERE tablename = 'audit_log' "
|
|
|
|
|
"AND indexname = 'ix_audit_log_target_event_ts'"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
assert result.first() is not None, (
|
|
|
|
|
"composite index ix_audit_log_target_event_ts must exist on audit_log"
|
|
|
|
|
)
|