import os import shutil import sqlite3 import tempfile import pytest import backend.db as db_mod @pytest.fixture def drift_db_dir(): # Persistent temp dir; on Windows the global WAL connection can outlive the # test frame, so we avoid TemporaryDirectory's immediate rmtree race and clean # up best-effort after the handle is released. tmp = tempfile.mkdtemp(prefix="slab_drift_") yield tmp db_mod._reset_for_tests() shutil.rmtree(tmp, ignore_errors=True) def _setup_temp_db_with_m2_schema(tmp_dir: str) -> str: db_path = os.path.join(tmp_dir, "test.db") # M2-era settings shape: NO `value` column (this is the drift signature). # Use a raw connection (DELETE journal) exactly as an old M2 db would be on disk. conn = sqlite3.connect(db_path) conn.execute("CREATE TABLE settings (key TEXT PRIMARY KEY, value_enc BLOB, updated_at TEXT)") conn.execute("INSERT INTO settings (key, value_enc, updated_at) VALUES ('woo_wp_username', X'00', '2026-01-01')") conn.commit() conn.close() # Force any journal sidecars to merge before the WAL-mode factory opens the file. for suffix in ("-journal", "-wal", "-shm"): try: os.remove(db_path + suffix) except OSError: pass return db_path def test_drift_detection_and_rebuild(monkeypatch, drift_db_dir): db_path = _setup_temp_db_with_m2_schema(drift_db_dir) monkeypatch.setenv("SLAB_DB_PATH", db_path) # init_db must detect pre-M3 drift and rebuild to the M3 settings shape conn = db_mod.init_db() cur = conn.execute("SELECT version FROM schema_version").fetchone() assert cur is not None assert cur[0] == 1 info = conn.execute("PRAGMA table_info(settings)").fetchall() cols = {row[1] for row in info} assert "value" in cols assert "value_enc" in cols # M2 rows are disposable (M3 supersedes M2 on this dev volume) rows = conn.execute("SELECT key FROM settings").fetchall() assert rows == []