mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-28 19:06:36 +08:00
145 lines
4.8 KiB
Python
145 lines
4.8 KiB
Python
"""
|
||
插件实例日志等级覆盖列 Alembic 迁移测试。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import importlib
|
||
from datetime import datetime, timezone
|
||
|
||
import sqlalchemy as sa
|
||
from alembic.migration import MigrationContext
|
||
from alembic.operations import Operations
|
||
|
||
from app.db.models.plugininstance import PluginInstance
|
||
|
||
MIGRATION_MODULE = "database.versions.487f7e681955_3_0_36"
|
||
|
||
|
||
def _bind_migration(monkeypatch, connection):
|
||
"""
|
||
把迁移绑定到隔离数据库连接。
|
||
"""
|
||
migration = importlib.import_module(MIGRATION_MODULE)
|
||
context = MigrationContext.configure(connection)
|
||
monkeypatch.setattr(migration, "op", Operations(context))
|
||
return migration
|
||
|
||
|
||
def _create_legacy_table(connection: sa.engine.Connection) -> None:
|
||
"""
|
||
建出加列前的表结构(3.0.35 的落点),模拟迁移前的存量数据库。
|
||
"""
|
||
now = datetime.now(timezone.utc).isoformat()
|
||
table = sa.Table(
|
||
"plugininstance",
|
||
sa.MetaData(),
|
||
sa.Column("id", sa.Integer(), primary_key=True),
|
||
sa.Column("instance_id", sa.String(length=128), nullable=False),
|
||
sa.Column("source_plugin_id", sa.String(length=128), nullable=False),
|
||
sa.Column("plugin_name", sa.String(length=255)),
|
||
sa.Column("plugin_desc", sa.String(length=255)),
|
||
sa.Column("plugin_icon", sa.String(length=255)),
|
||
sa.Column("config_data", sa.JSON()),
|
||
sa.Column("created_at", sa.String(length=40), nullable=False),
|
||
sa.Column("updated_at", sa.String(length=40), nullable=False),
|
||
)
|
||
table.create(connection)
|
||
connection.execute(
|
||
table.insert().values(
|
||
instance_id="DemoPluginWork",
|
||
source_plugin_id="DemoPlugin",
|
||
created_at=now,
|
||
updated_at=now,
|
||
)
|
||
)
|
||
|
||
|
||
def test_plugin_instance_log_level_migration_adds_columns_and_keeps_existing_rows(
|
||
monkeypatch,
|
||
) -> None:
|
||
"""
|
||
新增列必须可空,且不得影响已有行;重复升级与完整回滚都要幂等。
|
||
"""
|
||
engine = sa.create_engine("sqlite://")
|
||
with engine.begin() as connection:
|
||
_create_legacy_table(connection)
|
||
migration = _bind_migration(monkeypatch, connection)
|
||
|
||
migration.upgrade()
|
||
migration.upgrade()
|
||
|
||
columns = {
|
||
column["name"]: column
|
||
for column in sa.inspect(connection).get_columns("plugininstance")
|
||
}
|
||
# 与该迁移自身的落点比对,而不是与持续演进的当前模型比对:后续迁移
|
||
# 还会继续给该表加列,本断言不应该随之跟着变红。
|
||
assert columns.keys() == {
|
||
"id",
|
||
"instance_id",
|
||
"source_plugin_id",
|
||
"plugin_name",
|
||
"plugin_desc",
|
||
"plugin_icon",
|
||
"config_data",
|
||
"log_level",
|
||
"log_expires_at",
|
||
"created_at",
|
||
"updated_at",
|
||
}
|
||
assert columns["log_level"]["nullable"] is True
|
||
assert columns["log_expires_at"]["nullable"] is True
|
||
|
||
table = sa.Table("plugininstance", sa.MetaData(), autoload_with=connection)
|
||
row = connection.execute(sa.select(table)).mappings().one()
|
||
assert row["instance_id"] == "DemoPluginWork"
|
||
assert row["log_level"] is None
|
||
assert row["log_expires_at"] is None
|
||
|
||
migration.downgrade()
|
||
remaining = {
|
||
column["name"] for column in sa.inspect(connection).get_columns("plugininstance")
|
||
}
|
||
assert "log_level" not in remaining
|
||
assert "log_expires_at" not in remaining
|
||
|
||
migration.upgrade()
|
||
restored = {
|
||
column["name"] for column in sa.inspect(connection).get_columns("plugininstance")
|
||
}
|
||
assert {"log_level", "log_expires_at"}.issubset(restored)
|
||
|
||
|
||
def test_plugin_instance_log_level_migration_accepts_fresh_current_schema(
|
||
monkeypatch,
|
||
) -> None:
|
||
"""
|
||
create_all 已建当前表时重复升级不得因列已存在而报错。
|
||
"""
|
||
engine = sa.create_engine("sqlite://")
|
||
with engine.begin() as connection:
|
||
PluginInstance.__table__.create(connection)
|
||
migration = _bind_migration(monkeypatch, connection)
|
||
|
||
migration.upgrade()
|
||
migration.upgrade()
|
||
|
||
assert {
|
||
column["name"]
|
||
for column in sa.inspect(connection).get_columns("plugininstance")
|
||
} == {column.name for column in PluginInstance.__table__.columns}
|
||
|
||
|
||
def test_plugin_instance_log_level_migration_skips_when_table_absent(monkeypatch) -> None:
|
||
"""
|
||
表尚未建出时升级要安静跳过,不得因反射不到表而炸掉整条迁移链。
|
||
"""
|
||
engine = sa.create_engine("sqlite://")
|
||
with engine.begin() as connection:
|
||
migration = _bind_migration(monkeypatch, connection)
|
||
|
||
migration.upgrade()
|
||
|
||
assert "plugininstance" not in set(sa.inspect(connection).get_table_names())
|