"""Alembic migration smoke test. We do not boot a real Postgres in CI; instead we point Alembic at an in-process SQLite database and verify: - ``alembic upgrade head`` succeeds offline (SQL generation) using the real migration files, exercising every column type and constraint declaration; - ``downgrade base`` rewinds without errors. This catches typos and broken migration ordering early without requiring the full backing-service compose stack to be online. """ from __future__ import annotations import sys from pathlib import Path import pytest from alembic import command from alembic.config import Config from alembic.script import ScriptDirectory ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) @pytest.fixture def alembic_cfg(tmp_path, monkeypatch): """Configure Alembic against an isolated SQLite file.""" db_file = tmp_path / "legacyhub.db" monkeypatch.setenv("POSTGRES_HOST", "127.0.0.1") monkeypatch.setenv("POSTGRES_PORT", "5432") cfg = Config(str(ROOT / "alembic.ini")) cfg.set_main_option("script_location", str(ROOT / "app" / "db" / "migrations")) cfg.set_main_option("sqlalchemy.url", f"sqlite:///{db_file}") return cfg def test_migration_offline_emits_sql(alembic_cfg, tmp_path): """Offline mode generates SQL for every table; verify ``documents`` appears and the TeamHUB AssetManifest ingest job table is part of the linear head. SQLite has no JSONB but Alembic's offline mode happily emits the raw DDL for inspection. """ out_file = tmp_path / "upgrade.sql" # ``--sql`` mode bypasses dialect-specific runtime, perfect for a fast check. with out_file.open("w", encoding="utf-8") as f: old_stdout = sys.stdout sys.stdout = f try: command.upgrade(alembic_cfg, "head", sql=True) finally: sys.stdout = old_stdout sql = out_file.read_text(encoding="utf-8") assert "CREATE TABLE documents" in sql assert "CREATE TABLE chunks" in sql assert "CREATE TABLE processing_events" in sql assert "CREATE TABLE asset_ingest_jobs" in sql assert "CREATE TABLE audit_log" in sql # Constraint sanity assert "uq_chunks_doc_idx" in sql assert "uq_pages_doc_page" in sql assert "uq_asset_ingest_identity" in sql def test_revision_history_is_linear(alembic_cfg): """The project keeps a single linear migration head.""" script = ScriptDirectory.from_config(alembic_cfg) heads = script.get_heads() assert len(heads) == 1, f"expected one head, got: {heads}" assert heads == ["0003_audit_log"] revisions = [rev.revision for rev in script.walk_revisions()] assert revisions == ["0003_audit_log", "0002_asset_ingest_jobs", "0001_initial"]