Newer
Older
navi-1 / tests / unit / core / test_synapse_ddl.py
"""Unit tests for the Synapse DDL — fresh database and migration in one path.

The bug this file exists for: an index over a column the migration adds used to
live in `_DDL`, which runs *first*. On a database that already had the table
(any real deployment) the batch died with `column "doc" does not exist` before
the ALTER ever ran, and the whole synapse schema stayed on the previous shape
while the app started anyway.
"""

import re

from navi.synapse._ddl import _DDL, _MIGRATE, ensure_tables
from tests.conftest_factory import FakeConnection, FakePool


def _statements(sql: str) -> str:
    return re.sub(r"--[^\n]*", "", sql)


def test_migration_creates_every_column_the_ddl_declares():
    """A column added to the CREATE TABLE must be added to the ALTER batch too,
    or existing databases never get it (IF NOT EXISTS makes one batch serve both)."""
    ddl, migrate = _statements(_DDL), _statements(_MIGRATE)
    for table, columns in (
        ("synapse_settings", ["dispatcher_instructions", "reaction_session_ttl_minutes"]),
        ("synapse_instruction_versions", ["doc"]),
    ):
        block = re.search(rf"CREATE TABLE IF NOT EXISTS {table} \((.*?)\n\);", ddl, re.DOTALL)
        assert block, f"{table} is no longer declared in _DDL — update this test"
        for column in columns:
            assert column in block.group(1), f"{table}.{column} missing from the CREATE TABLE"
            assert f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {column}" in migrate


def test_no_index_the_migration_needs_is_created_before_it():
    """_DDL runs before _MIGRATE: an index on a migrated column there fails the
    batch on an existing database (the new column does not exist yet)."""
    migrated_columns = set(re.findall(r"ADD COLUMN IF NOT EXISTS (\w+)", _MIGRATE))
    assert migrated_columns, "no columns found in the migration"
    for match in re.finditer(r"CREATE INDEX IF NOT EXISTS \w+\s+ON (\w+) \(([^)]*)\)", _DDL):
        columns = {part.strip() for part in match.group(2).split(",")}
        assert not (columns & migrated_columns), (
            f"_DDL creates an index on {match.group(1)} over migrated column(s) "
            f"{sorted(columns & migrated_columns)} — move it into _MIGRATE"
        )


async def test_ensure_tables_runs_ddl_then_migration():
    conn = FakeConnection()
    await ensure_tables(FakePool(conn))
    executed = [call[1] for call in conn.calls if call[0] == "execute"]
    assert executed == [_DDL, _MIGRATE]