"""Migrationen passen zu den Modellen, auf SQLite und PostgreSQL (→ KONZEPT.md 18).""" import hashlib from alembic.autogenerate import compare_metadata from alembic.script import ScriptDirectory from alembic.runtime.migration import MigrationContext from sqlalchemy import create_engine from hugin_app.migration import FOLDER, VERSION_TABLE, migrate from hugin_app.store.models import Base def test_migrations_match_models(database_url): migrate(database_url) engine = create_engine(database_url) try: with engine.connect() as connection: context = MigrationContext.configure(connection, opts={"version_table": VERSION_TABLE}) differences = [u for u in compare_metadata(context, Base.metadata) if not (isinstance(u, tuple) and u[0] == "remove_table" and u[1].name == VERSION_TABLE)] finally: engine.dispose() assert differences == [], "Modelle geändert ohne Migration: uv run alembic revision --autogenerate …" # Ausgelieferte Migrationen bleiben, wie sie sind: Datenbanken kennen ihre Revision. Eine # geänderte oder ersetzte Migration lässt bestehende Datenbanken nicht mehr migrieren # („Can't locate revision“). Änderungen am Schema nur als neue Migration; neue Dateien hier # mit Prüfsumme eintragen, sobald sie ausgeliefert sind. RELEASED = { "0001_initial_tables.py": "ff511bb66729de1998602be87a1714b37d20dff58265542c9bbce11b5fbacbde", } def test_released_migrations_unchanged(): for name, digest in RELEASED.items(): path = FOLDER / "versions" / name assert path.exists(), f"{name} fehlt: ausgelieferte Migrationen nie löschen oder umbenennen" assert hashlib.sha256(path.read_bytes()).hexdigest() == digest, ( f"{name} wurde geändert: ausgelieferte Migrationen nie ändern, sondern eine neue anlegen") def test_migrations_form_one_chain(): scripts = ScriptDirectory(str(FOLDER)) assert len(scripts.get_heads()) == 1, f"mehrere Enden: {scripts.get_heads()}; mit alembic merge zusammenführen"