chore: import upstream snapshot with attribution
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
This commit is contained in:
+70
@@ -0,0 +1,70 @@
|
||||
import sqlite3
|
||||
|
||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_2026_07_01_add_workflow_call_queue_metadata import (
|
||||
AddWorkflowCallQueueMetadataCallback,
|
||||
build_migration,
|
||||
)
|
||||
|
||||
|
||||
def _get_columns(cursor: sqlite3.Cursor, table_name: str) -> set[str]:
|
||||
cursor.execute(f"PRAGMA table_info({table_name});")
|
||||
return {row[1] for row in cursor.fetchall()}
|
||||
|
||||
|
||||
def _get_indexes(cursor: sqlite3.Cursor) -> set[str]:
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type = 'index';")
|
||||
return {row[0] for row in cursor.fetchall()}
|
||||
|
||||
|
||||
def test_adds_workflow_call_columns_and_indexes_to_session_queue() -> None:
|
||||
db = sqlite3.connect(":memory:")
|
||||
cursor = db.cursor()
|
||||
cursor.execute("CREATE TABLE session_queue (item_id INTEGER PRIMARY KEY);")
|
||||
|
||||
AddWorkflowCallQueueMetadataCallback()(cursor)
|
||||
|
||||
assert _get_columns(cursor, "session_queue") >= {
|
||||
"workflow_call_id",
|
||||
"parent_item_id",
|
||||
"parent_session_id",
|
||||
"root_item_id",
|
||||
"workflow_call_depth",
|
||||
}
|
||||
assert _get_indexes(cursor) >= {
|
||||
"idx_session_queue_workflow_call_id",
|
||||
"idx_session_queue_parent_item_id",
|
||||
"idx_session_queue_parent_session_id",
|
||||
"idx_session_queue_root_item_id",
|
||||
"idx_session_queue_workflow_call_depth",
|
||||
}
|
||||
|
||||
db.close()
|
||||
|
||||
|
||||
def test_migration_is_idempotent_and_tolerates_missing_session_queue() -> None:
|
||||
db = sqlite3.connect(":memory:")
|
||||
cursor = db.cursor()
|
||||
|
||||
AddWorkflowCallQueueMetadataCallback()(cursor)
|
||||
cursor.execute("CREATE TABLE session_queue (item_id INTEGER PRIMARY KEY, workflow_call_id TEXT);")
|
||||
AddWorkflowCallQueueMetadataCallback()(cursor)
|
||||
AddWorkflowCallQueueMetadataCallback()(cursor)
|
||||
|
||||
assert _get_columns(cursor, "session_queue") >= {
|
||||
"workflow_call_id",
|
||||
"parent_item_id",
|
||||
"parent_session_id",
|
||||
"root_item_id",
|
||||
"workflow_call_depth",
|
||||
}
|
||||
|
||||
db.close()
|
||||
|
||||
|
||||
def test_build_migration_declares_stable_id_and_dependency() -> None:
|
||||
migration = build_migration()
|
||||
|
||||
assert migration.id == "2026_07_01_add_workflow_call_queue_metadata"
|
||||
assert migration.depends_on == "migration_33"
|
||||
assert migration.from_version is None
|
||||
assert migration.to_version is None
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Tests for migration 31: Add image_subfolder column to images table."""
|
||||
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_31 import (
|
||||
Migration31Callback,
|
||||
build_migration_31,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db() -> sqlite3.Connection:
|
||||
"""In-memory SQLite database with a minimal images table mimicking pre-migration schema."""
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE images (
|
||||
image_name TEXT NOT NULL PRIMARY KEY,
|
||||
image_origin TEXT NOT NULL,
|
||||
image_category TEXT NOT NULL,
|
||||
width INTEGER NOT NULL DEFAULT 0,
|
||||
height INTEGER NOT NULL DEFAULT 0,
|
||||
session_id TEXT,
|
||||
node_id TEXT,
|
||||
metadata TEXT,
|
||||
is_intermediate BOOLEAN DEFAULT FALSE,
|
||||
created_at DATETIME NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%f', 'NOW')),
|
||||
updated_at DATETIME NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%f', 'NOW')),
|
||||
deleted_at DATETIME,
|
||||
starred BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
has_workflow BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
"""
|
||||
)
|
||||
return conn
|
||||
|
||||
|
||||
class TestMigration31:
|
||||
def test_adds_image_subfolder_column(self, db: sqlite3.Connection):
|
||||
"""Migration adds image_subfolder column to existing images table."""
|
||||
callback = Migration31Callback()
|
||||
cursor = db.cursor()
|
||||
callback(cursor)
|
||||
|
||||
cursor.execute("PRAGMA table_info(images);")
|
||||
columns = {row[1] for row in cursor.fetchall()}
|
||||
assert "image_subfolder" in columns
|
||||
|
||||
def test_existing_rows_get_empty_string_default(self, db: sqlite3.Connection):
|
||||
"""Pre-existing image rows should get image_subfolder = '' after migration."""
|
||||
db.execute(
|
||||
"INSERT INTO images (image_name, image_origin, image_category, width, height, has_workflow) "
|
||||
"VALUES ('old_image.png', 'internal', 'general', 512, 512, 0)"
|
||||
)
|
||||
db.commit()
|
||||
|
||||
callback = Migration31Callback()
|
||||
callback(db.cursor())
|
||||
db.commit()
|
||||
|
||||
row = db.execute("SELECT image_subfolder FROM images WHERE image_name = 'old_image.png'").fetchone()
|
||||
assert row is not None
|
||||
assert row[0] == ""
|
||||
|
||||
def test_idempotent_migration(self, db: sqlite3.Connection):
|
||||
"""Running migration twice should not fail (column already exists)."""
|
||||
callback = Migration31Callback()
|
||||
cursor = db.cursor()
|
||||
callback(cursor)
|
||||
# Running again should be safe
|
||||
callback(cursor)
|
||||
|
||||
cursor.execute("PRAGMA table_info(images);")
|
||||
columns = [row[1] for row in cursor.fetchall()]
|
||||
assert columns.count("image_subfolder") == 1
|
||||
|
||||
def test_no_images_table_is_noop(self):
|
||||
"""If images table doesn't exist, migration is a no-op."""
|
||||
conn = sqlite3.connect(":memory:")
|
||||
callback = Migration31Callback()
|
||||
cursor = conn.cursor()
|
||||
# Should not raise
|
||||
callback(cursor)
|
||||
|
||||
def test_build_migration_31_version_numbers(self):
|
||||
"""build_migration_31 returns correct version range."""
|
||||
migration = build_migration_31()
|
||||
assert migration.from_version == 30
|
||||
assert migration.to_version == 31
|
||||
@@ -0,0 +1,131 @@
|
||||
"""Tests for migration 32: Repair model_relationships foreign keys."""
|
||||
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_32 import (
|
||||
Migration32Callback,
|
||||
build_migration_32,
|
||||
)
|
||||
|
||||
|
||||
def _create_models_table(conn: sqlite3.Connection) -> None:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE models (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
config TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _create_broken_relationships_table(conn: sqlite3.Connection) -> None:
|
||||
"""Recreates the broken state left by migration 22: FKs reference the dropped models_old table."""
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE model_relationships (
|
||||
model_key_1 TEXT NOT NULL,
|
||||
model_key_2 TEXT NOT NULL,
|
||||
created_at TEXT DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),
|
||||
PRIMARY KEY (model_key_1, model_key_2),
|
||||
FOREIGN KEY (model_key_1) REFERENCES "models_old"(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (model_key_2) REFERENCES "models_old"(id) ON DELETE CASCADE
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.execute("CREATE INDEX keyx_model_relationships_model_key_2 ON model_relationships(model_key_2);")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db() -> sqlite3.Connection:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
_create_models_table(conn)
|
||||
_create_broken_relationships_table(conn)
|
||||
conn.execute("INSERT INTO models (id, config) VALUES ('a', '{}'), ('b', '{}'), ('c', '{}')")
|
||||
return conn
|
||||
|
||||
|
||||
class TestMigration32:
|
||||
def test_repoints_foreign_keys_to_models(self, db: sqlite3.Connection):
|
||||
"""After migration, the foreign keys reference models, not models_old."""
|
||||
Migration32Callback()(db.cursor())
|
||||
db.commit()
|
||||
|
||||
sql = db.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='model_relationships'").fetchone()[
|
||||
0
|
||||
]
|
||||
assert "models_old" not in sql
|
||||
assert "REFERENCES models(id)" in sql
|
||||
|
||||
def test_preserves_valid_links(self, db: sqlite3.Connection):
|
||||
"""Links between existing models are preserved."""
|
||||
db.execute("INSERT INTO model_relationships (model_key_1, model_key_2) VALUES ('a', 'b')")
|
||||
db.commit()
|
||||
|
||||
Migration32Callback()(db.cursor())
|
||||
db.commit()
|
||||
|
||||
rows = db.execute("SELECT model_key_1, model_key_2 FROM model_relationships ORDER BY model_key_1").fetchall()
|
||||
assert rows == [("a", "b")]
|
||||
|
||||
def test_drops_orphaned_links(self, db: sqlite3.Connection):
|
||||
"""Links referencing missing models are dropped so the restored FKs are satisfiable."""
|
||||
db.execute("INSERT INTO model_relationships (model_key_1, model_key_2) VALUES ('a', 'b')")
|
||||
db.execute("INSERT INTO model_relationships (model_key_1, model_key_2) VALUES ('a', 'gone')")
|
||||
db.commit()
|
||||
|
||||
Migration32Callback()(db.cursor())
|
||||
db.commit()
|
||||
|
||||
rows = db.execute("SELECT model_key_1, model_key_2 FROM model_relationships").fetchall()
|
||||
assert rows == [("a", "b")]
|
||||
|
||||
def test_cascade_works_after_repair(self, db: sqlite3.Connection):
|
||||
"""ON DELETE CASCADE against models works once the FKs are repaired."""
|
||||
db.execute("INSERT INTO model_relationships (model_key_1, model_key_2) VALUES ('a', 'b')")
|
||||
db.commit()
|
||||
|
||||
Migration32Callback()(db.cursor())
|
||||
db.commit()
|
||||
|
||||
db.execute("PRAGMA foreign_keys = ON;")
|
||||
db.execute("DELETE FROM models WHERE id = 'a'")
|
||||
db.commit()
|
||||
|
||||
rows = db.execute("SELECT * FROM model_relationships").fetchall()
|
||||
assert rows == []
|
||||
|
||||
def test_index_recreated(self, db: sqlite3.Connection):
|
||||
"""The lookup index on model_key_2 is recreated on the rebuilt table."""
|
||||
Migration32Callback()(db.cursor())
|
||||
db.commit()
|
||||
|
||||
idx = db.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='index' AND name='keyx_model_relationships_model_key_2'"
|
||||
).fetchone()
|
||||
assert idx is not None
|
||||
|
||||
def test_idempotent_when_already_correct(self, db: sqlite3.Connection):
|
||||
"""Running on an already-correct table is a no-op (no rebuild)."""
|
||||
Migration32Callback()(db.cursor())
|
||||
db.commit()
|
||||
# Second run should detect the correct FKs and do nothing.
|
||||
Migration32Callback()(db.cursor())
|
||||
db.commit()
|
||||
|
||||
sql = db.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='model_relationships'").fetchone()[
|
||||
0
|
||||
]
|
||||
assert "REFERENCES models(id)" in sql
|
||||
|
||||
def test_no_relationships_table_is_noop(self):
|
||||
"""If the table doesn't exist, migration is a no-op."""
|
||||
conn = sqlite3.connect(":memory:")
|
||||
Migration32Callback()(conn.cursor()) # should not raise
|
||||
|
||||
def test_build_migration_32_version_numbers(self):
|
||||
migration = build_migration_32()
|
||||
assert migration.from_version == 31
|
||||
assert migration.to_version == 32
|
||||
@@ -0,0 +1,252 @@
|
||||
import importlib
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from invokeai.app.services.shared.sqlite_migrator.migration_loader import (
|
||||
MigrationBuildContext,
|
||||
MigrationLoaderError,
|
||||
build_migrations,
|
||||
)
|
||||
|
||||
|
||||
def _write_package(tmp_path: Path, package_name: str, modules: dict[str, str]) -> str:
|
||||
package_path = tmp_path / package_name
|
||||
package_path.mkdir()
|
||||
(package_path / "__init__.py").write_text("", encoding="utf-8")
|
||||
for module_name, module_source in modules.items():
|
||||
(package_path / f"{module_name}.py").write_text(module_source, encoding="utf-8")
|
||||
importlib.invalidate_caches()
|
||||
return package_name
|
||||
|
||||
|
||||
def test_build_migrations_discovers_modules_in_numeric_order(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_order",
|
||||
{
|
||||
"migration_2": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_migration_2():
|
||||
return Migration(from_version=1, to_version=2, callback=lambda cursor: None)
|
||||
""",
|
||||
"migration_1": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_migration_1():
|
||||
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
|
||||
""",
|
||||
"not_a_migration": "VALUE = 1",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
migrations = build_migrations(
|
||||
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
assert [migration.id for migration in migrations] == ["migration_1", "migration_2"]
|
||||
|
||||
|
||||
def test_build_migrations_injects_requested_dependencies(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_injection",
|
||||
{
|
||||
"migration_1": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_migration_1(app_config, logger, image_files):
|
||||
assert app_config == "config"
|
||||
assert logger.name == "test"
|
||||
assert image_files == "images"
|
||||
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
|
||||
""",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
migrations = build_migrations(
|
||||
MigrationBuildContext(app_config="config", logger=Logger("test"), image_files="images"),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
assert [migration.id for migration in migrations] == ["migration_1"]
|
||||
|
||||
|
||||
def test_build_migrations_supports_dated_descriptive_modules(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_dated",
|
||||
{
|
||||
"migration_2026_06_30_add_example_table": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_migration():
|
||||
return Migration(
|
||||
id="2026_06_30_add_example_table",
|
||||
depends_on="migration_1",
|
||||
callback=lambda cursor: None,
|
||||
)
|
||||
""",
|
||||
"migration_1": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_migration_1():
|
||||
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
|
||||
""",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
migrations = build_migrations(
|
||||
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
assert [migration.id for migration in migrations] == ["migration_1", "2026_06_30_add_example_table"]
|
||||
|
||||
|
||||
def test_build_migrations_rejects_dated_module_id_mismatch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_dated_id_mismatch",
|
||||
{
|
||||
"migration_2026_06_30_add_example_table": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_migration():
|
||||
return Migration(
|
||||
id="2026_06_30_typo",
|
||||
depends_on="migration_1",
|
||||
callback=lambda cursor: None,
|
||||
)
|
||||
""",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
with pytest.raises(MigrationLoaderError, match="must return migration id"):
|
||||
build_migrations(
|
||||
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
|
||||
def test_build_migrations_rejects_numeric_module_id_mismatch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_numeric_id_mismatch",
|
||||
{
|
||||
"migration_1": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_migration_1():
|
||||
return Migration(id="wrong", from_version=0, to_version=1, callback=lambda cursor: None)
|
||||
""",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
with pytest.raises(MigrationLoaderError, match="must return migration id"):
|
||||
build_migrations(
|
||||
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
|
||||
def test_build_migrations_rejects_unknown_builder_dependency(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_unknown_dependency",
|
||||
{
|
||||
"migration_1": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_migration_1(unknown_service):
|
||||
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
|
||||
""",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
with pytest.raises(MigrationLoaderError, match="unknown dependency"):
|
||||
build_migrations(
|
||||
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
|
||||
def test_build_migrations_rejects_missing_expected_builder(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_missing_builder",
|
||||
{
|
||||
"migration_1": """
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
def build_something_else():
|
||||
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
|
||||
""",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
with pytest.raises(MigrationLoaderError, match="build_migration_1"):
|
||||
build_migrations(
|
||||
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
|
||||
def test_build_migrations_rejects_non_migration_return(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_bad_return",
|
||||
{
|
||||
"migration_1": """
|
||||
def build_migration_1():
|
||||
return object()
|
||||
""",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
with pytest.raises(MigrationLoaderError, match="must return Migration"):
|
||||
build_migrations(
|
||||
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
|
||||
def test_build_migrations_rejects_malformed_migration_module(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
package_name = _write_package(
|
||||
tmp_path,
|
||||
"test_migrations_malformed",
|
||||
{
|
||||
"migration_latest": "VALUE = 1",
|
||||
},
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
|
||||
with pytest.raises(MigrationLoaderError, match="Malformed migration module"):
|
||||
build_migrations(
|
||||
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
|
||||
package_name=package_name,
|
||||
)
|
||||
|
||||
|
||||
def test_build_migrations_discovers_production_migrations(tmp_path: Path) -> None:
|
||||
class FakeConfig:
|
||||
root_path = tmp_path
|
||||
models_path = tmp_path / "models"
|
||||
convert_cache_path = tmp_path / "models" / ".cache"
|
||||
legacy_conf_path = tmp_path / "models.yaml"
|
||||
legacy_conf_dir = tmp_path
|
||||
|
||||
migrations = build_migrations(
|
||||
MigrationBuildContext(app_config=FakeConfig(), logger=Logger("test"), image_files=object())
|
||||
)
|
||||
legacy_migrations = [migration for migration in migrations if migration.to_version is not None]
|
||||
latest_legacy_version = max(migration.to_version or 0 for migration in legacy_migrations)
|
||||
|
||||
assert [migration.id for migration in legacy_migrations] == [
|
||||
f"migration_{i}" for i in range(1, latest_legacy_version + 1)
|
||||
]
|
||||
assert [migration.depends_on for migration in legacy_migrations] == [None] + [
|
||||
f"migration_{i}" for i in range(1, latest_legacy_version)
|
||||
]
|
||||
Reference in New Issue
Block a user