"""Миграция схемы checks: идемпотентность и сохранение старых NULL.""" import sqlite3 def _cols(db_path): con = sqlite3.connect(db_path) cols = [r[1] for r in con.execute("PRAGMA table_info(checks)").fetchall()] con.close() return cols def _make_old_db(db_path, with_error_col=False): """БД со старой схемой checks (до учёта usage) и живыми данными.""" con = sqlite3.connect(db_path) con.execute("""CREATE TABLE checks ( id INTEGER PRIMARY KEY AUTOINCREMENT, model_id TEXT NOT NULL, checked_at TEXT NOT NULL, ok INTEGER NOT NULL, latency_ms INTEGER)""") if with_error_col: con.execute("ALTER TABLE checks ADD COLUMN error TEXT") con.execute( "INSERT INTO checks (model_id, checked_at, ok, latency_ms, error) VALUES (?,?,?,?,?)" if with_error_col else "INSERT INTO checks (model_id, checked_at, ok, latency_ms) VALUES (?,?,?,?)", ("zai/old-model", "2026-01-01T00:00:00+00:00", 1, 123, None) if with_error_col else ("zai/old-model", "2026-01-01T00:00:00+00:00", 1, 123), ) con.commit() con.close() def test_migration_old_schema_preserves_null(fresh_main): _make_old_db(fresh_main.DB_PATH, with_error_col=True) fresh_main.init_db() cols = _cols(fresh_main.DB_PATH) for c in ("input_tokens", "output_tokens", "total_tokens", "usage_json"): assert c in cols, f"колонка {c} не добавлена" con = sqlite3.connect(fresh_main.DB_PATH) con.row_factory = sqlite3.Row row = con.execute("SELECT * FROM checks WHERE model_id='zai/old-model'").fetchone() con.close() assert row["ok"] == 1 and row["latency_ms"] == 123 # старые данные: неизвестное потребление = NULL, не нули assert row["input_tokens"] is None assert row["output_tokens"] is None assert row["total_tokens"] is None assert row["usage_json"] is None def test_migration_from_ancient_schema_without_error(fresh_main): _make_old_db(fresh_main.DB_PATH, with_error_col=False) fresh_main.init_db() # не должен падать: добавит и error, и usage-колонки cols = _cols(fresh_main.DB_PATH) assert "error" in cols assert "usage_json" in cols def test_migration_idempotent(fresh_main): _make_old_db(fresh_main.DB_PATH, with_error_col=True) fresh_main.init_db() fresh_main.init_db() # повторный запуск — no-op без ошибок con = sqlite3.connect(fresh_main.DB_PATH) n = con.execute("SELECT COUNT(*) FROM checks").fetchone()[0] con.close() assert n == 1 # данные не задвоились и не потерялись def test_fresh_db_has_usage_columns(fresh_main): fresh_main.init_db() cols = _cols(fresh_main.DB_PATH) for c in ("input_tokens", "output_tokens", "total_tokens", "usage_json"): assert c in cols