mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
fix(persistence): derive SCHEMA_VERSION from the migrations directory
db.py hardcoded SCHEMA_VERSION = 26 while migrations/ had already reached v29 (v27 dispatcher floor-drop counter, v28 mesh_observations, v29 IPAWS). The migration runner globs the directory and applies every vN.sql it finds regardless of the constant, so a fresh DB actually landed at 29 while the constant claimed 26 -- a three-version drift that three tests were correctly catching. Derive it from the highest vN.sql present instead of bumping the literal, so it cannot drift again the next time someone adds a migration. Falls back to 0 if the directory is missing so import never fails; the migrations dir sits alongside db.py and ships with the package (Dockerfile COPYs meshai/). Adds a regression guard asserting the constant matches the highest migration file, and updates three tests that hardcoded 26 as a literal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bbe97398bc
commit
bc94cf237a
4 changed files with 48 additions and 8 deletions
|
|
@ -30,9 +30,11 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
DEFAULT_DB_PATH = "/data/meshai.sqlite"
|
||||
MESHAI_DB_PATH_ENV = "MESHAI_DB_PATH"
|
||||
SCHEMA_VERSION = 26
|
||||
SCHEMA_META_TABLE = "schema_meta"
|
||||
MIGRATIONS_DIR = Path(__file__).parent / "migrations"
|
||||
# SCHEMA_VERSION is derived below (after _read_migration_files is defined)
|
||||
# from the highest vN.sql present in MIGRATIONS_DIR, so it can never drift
|
||||
# from what a fresh DB actually lands at -- see _derive_schema_version().
|
||||
|
||||
# Per-thread connection pool. Each thread that calls get_db() gets its
|
||||
# own sqlite3.Connection cached on threading.local. Tests can clear
|
||||
|
|
@ -163,6 +165,22 @@ def _read_migration_files() -> list[tuple[int, str, str]]:
|
|||
return out
|
||||
|
||||
|
||||
def _derive_schema_version() -> int:
|
||||
"""Highest migration version present in MIGRATIONS_DIR, i.e. the
|
||||
version a fresh DB lands at once _apply_migrations() runs (it applies
|
||||
every vN.sql it finds, independent of any hardcoded constant). Falls
|
||||
back to 0 if the directory is missing/empty so import never fails;
|
||||
that would only happen in a broken packaging layout, since
|
||||
MIGRATIONS_DIR sits alongside this file and is copied/installed with
|
||||
the rest of the meshai package (see Dockerfile: `COPY meshai/ ./meshai/`
|
||||
then `pip install -e .`)."""
|
||||
versions = [n for n, _, _ in _read_migration_files()]
|
||||
return max(versions) if versions else 0
|
||||
|
||||
|
||||
SCHEMA_VERSION = _derive_schema_version()
|
||||
|
||||
|
||||
def _current_version(conn: sqlite3.Connection) -> int:
|
||||
"""Return the highest applied migration version, or 0 if none."""
|
||||
# Does the schema_meta table exist?
|
||||
|
|
|
|||
|
|
@ -14,8 +14,11 @@ from meshai.persistence.observer_locations import (
|
|||
|
||||
# -- schema / migration -------------------------------------------------------
|
||||
|
||||
def test_schema_version_is_25():
|
||||
assert SCHEMA_VERSION == 26
|
||||
def test_schema_version_is_current():
|
||||
# SCHEMA_VERSION is derived from the highest vN.sql in migrations/, so
|
||||
# this just guards against the derivation returning something bogus
|
||||
# (e.g. 0, which would mean the migrations dir wasn't found).
|
||||
assert SCHEMA_VERSION >= 23
|
||||
|
||||
|
||||
def test_observer_locations_table_exists():
|
||||
|
|
@ -30,7 +33,7 @@ def test_schema_meta_at_current():
|
|||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT value FROM schema_meta WHERE key='version'").fetchone()
|
||||
assert int(row["value"]) == 26
|
||||
assert int(row["value"]) == SCHEMA_VERSION
|
||||
|
||||
|
||||
# -- accessors ----------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -90,6 +90,23 @@ def test_schema_version_recorded(tmp_db):
|
|||
assert int(row["value"]) == SCHEMA_VERSION
|
||||
|
||||
|
||||
def test_schema_version_matches_highest_migration_file():
|
||||
"""SCHEMA_VERSION is derived from migrations/ at import time (see
|
||||
db._derive_schema_version); this test independently re-derives the
|
||||
expected value straight off the filenames so a future regression
|
||||
(e.g. someone re-hardcoding the constant) is caught even if the
|
||||
derivation logic itself is what breaks."""
|
||||
import re
|
||||
|
||||
versions = []
|
||||
for p in persistence_db.MIGRATIONS_DIR.iterdir():
|
||||
m = re.match(r"^v(\d+)", p.stem)
|
||||
if p.suffix.lower() == ".sql" and m:
|
||||
versions.append(int(m.group(1)))
|
||||
assert versions, "no vN.sql migration files found"
|
||||
assert SCHEMA_VERSION == max(versions)
|
||||
|
||||
|
||||
def test_migration_idempotent_rerun(tmp_db):
|
||||
init_db()
|
||||
# Force a "second startup" by closing the connection and clearing the
|
||||
|
|
|
|||
|
|
@ -99,9 +99,11 @@ def _ingest_envelope(norad_id=25544, observer="Boise", max_el=72.5,
|
|||
# ── schema / migration ───────────────────────────────────────────────
|
||||
|
||||
def test_schema_version_is_current():
|
||||
# Bumped to 26 by the generic-source migration (v26 generic_events); v24
|
||||
# avalanche_events, v25 ducting_events; was 23 at native-satpass (v23).
|
||||
assert SCHEMA_VERSION == 26
|
||||
# SCHEMA_VERSION is derived from the highest vN.sql in migrations/, so
|
||||
# this just guards against the derivation returning something bogus
|
||||
# (e.g. 0, which would mean the migrations dir wasn't found). The v22
|
||||
# migration (this file's focus) must always be <= the current version.
|
||||
assert SCHEMA_VERSION >= 22
|
||||
|
||||
|
||||
def test_v22_migration_applies_and_adds_due_at_column(tmp_path, monkeypatch):
|
||||
|
|
@ -114,7 +116,7 @@ def test_v22_migration_applies_and_adds_due_at_column(tmp_path, monkeypatch):
|
|||
close_thread_connection()
|
||||
conn = init_db()
|
||||
row = conn.execute("SELECT value FROM schema_meta WHERE key='version'").fetchone()
|
||||
assert int(row["value"]) == 26
|
||||
assert int(row["value"]) == SCHEMA_VERSION
|
||||
cols = {r["name"] for r in conn.execute("PRAGMA table_info(satpass_pending)")}
|
||||
assert "due_at" in cols
|
||||
close_thread_connection()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue