central/tests/test_migrate.py
Matt Johnson ce66ff9361 v0.9.18: reconcile schema_migrations drift + add --check drift detection
Migrations 025-029 (wzdx + traffic-family adapters/streams) were applied
out-of-band via direct psql during their deploys and never recorded in
schema_migrations. Result: a fresh restore would replay them, and an audit of
the tracking table understated what was actually live. All five are pure
additive INSERT ... ON CONFLICT DO NOTHING (verified idempotent by inspection),
so they were back-filled directly into schema_migrations (5 rows, applied_at =
now() = the reconciliation event; the original out-of-band apply dates are
unrecoverable and are documented as such rather than guessed).

Adds `central-migrate --check` to catch this class of drift going forward:
  - find_drift(): pure function comparing schema_migrations rows vs *.sql files,
    returning (untracked, orphan). Unit-tested, no DB dependency.
  - check_drift(): CI-assertion form -- exit 1 if any file is untracked, else 0.
  - log_drift(): WARNs per drifted entry, called on every migrate run too.

No migration 031 for the v0.9.17 wzdx state default: that default lives in
adapter code (_read_states falls back to _DEFAULT_STATES), so the live row's
explicit 7-state array is behaviorally identical to a fresh-install null.
Materializing it into SQL would freeze a snapshot of _DEFAULT_STATES and create
a new drift vector. Deferred to a future "show effective defaults" UI PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 06:40:38 +00:00

50 lines
1.5 KiB
Python

"""Drift-detection tests for the migration runner (v0.9.18).
`find_drift` is a pure function so the divergence logic is verified without a
database -- the suite runs identically as `zvx` or `central`.
"""
from pathlib import Path
from central.migrate import find_drift
def _discovered(*versions: str) -> list[tuple[str, Path]]:
"""Build a discover_migrations-shaped list from version stems."""
return [(v, Path(f"sql/migrations/{v}.sql")) for v in versions]
def test_find_drift_untracked_file():
"""A migration file on disk with no schema_migrations row is untracked.
This is the v0.9.18 case: 025-029 existed on disk but were never recorded.
"""
applied = {"001_a", "002_b"}
discovered = _discovered("001_a", "002_b", "003_c")
untracked, orphan = find_drift(applied, discovered)
assert untracked == ["003_c"]
assert orphan == []
def test_find_drift_orphan_row():
"""A schema_migrations row with no matching .sql file is an orphan."""
applied = {"001_a", "002_b", "099_removed"}
discovered = _discovered("001_a", "002_b")
untracked, orphan = find_drift(applied, discovered)
assert untracked == []
assert orphan == ["099_removed"]
def test_find_drift_clean():
"""No drift when disk and schema_migrations match exactly."""
applied = {"001_a", "002_b", "003_c"}
discovered = _discovered("001_a", "002_b", "003_c")
untracked, orphan = find_drift(applied, discovered)
assert untracked == []
assert orphan == []