Files
MoviePilot/tests/test_agent_invocation_migration.py

81 lines
3.7 KiB
Python

"""Agent 写工具持久回执的 SQLite 迁移与模型一致性测试。"""
import importlib
import sqlalchemy as sa
from alembic.migration import MigrationContext
from alembic.operations import Operations
from sqlalchemy.dialects import postgresql
from sqlalchemy.schema import CreateTable
from app.db.models.agentinvocation import AgentInvocation
MIGRATION = "database.versions.b2d4f6a8c1e3_3_0_33"
def _bind_migration(monkeypatch, connection):
"""将迁移指令绑定到测试独占数据库。"""
migration = importlib.import_module(MIGRATION)
monkeypatch.setattr(migration, "op", Operations(MigrationContext.configure(connection)))
return migration
def test_invocation_migration_upgrade_and_downgrade_are_replay_safe(monkeypatch):
"""已有 SQLite 能重复升级和降级,原有业务表保持不变。"""
engine = sa.create_engine("sqlite://")
with engine.begin() as connection:
connection.execute(sa.text("CREATE TABLE existing_data (id INTEGER PRIMARY KEY)"))
migration = _bind_migration(monkeypatch, connection)
migration.upgrade()
migration.upgrade()
inspector = sa.inspect(connection)
assert "agentinvocation" in inspector.get_table_names()
assert {column["name"] for column in inspector.get_columns("agentinvocation")} == {
column.name for column in AgentInvocation.__table__.columns
}
indexes = {index["name"]: index for index in inspector.get_indexes("agentinvocation")}
identity = indexes["ix_agentinvocation_identity"]
assert identity["unique"]
assert identity["column_names"] == ["principal_id", "session_id", "invocation_id"]
assert inspector.get_check_constraints("agentinvocation")[0]["name"] == "ck_agentinvocation_status"
connection.execute(sa.insert(AgentInvocation).values(
principal_id="owner", session_id="session", invocation_id="call",
tool_name="moviepilot_api", arguments_digest="a" * 64, claim_token="b" * 32,
status="pending", summary="已确认提交", created_at="2026-09-09T00:00:00+00:00",
updated_at="2026-09-09T00:00:00+00:00",
))
assert connection.execute(sa.select(AgentInvocation.status)).scalar_one() == "pending"
migration.downgrade()
migration.downgrade()
assert sa.inspect(connection).get_table_names() == ["existing_data"]
engine.dispose()
def test_invocation_migration_accepts_current_model_tables(monkeypatch):
"""全新数据库 create_all 后运行 Alembic 不应重复创建表或索引。"""
engine = sa.create_engine("sqlite://")
with engine.begin() as connection:
AgentInvocation.__table__.create(connection)
migration = _bind_migration(monkeypatch, connection)
migration.upgrade()
migration.upgrade()
actual = {
(index["name"], tuple(index["column_names"]), bool(index["unique"]))
for index in sa.inspect(connection).get_indexes("agentinvocation")
}
expected = {
(index.name, tuple(column.name for column in index.columns), index.unique)
for index in AgentInvocation.__table__.indexes
}
assert actual == expected
engine.dispose()
def test_invocation_migration_uses_postgresql_identity():
"""PostgreSQL 独立迁移保留宿主主键 Identity 约定。"""
migration = importlib.import_module(MIGRATION)
table = sa.Table("agentinvocation", sa.MetaData(), migration._id_column("postgresql"))
assert table.c.id.identity.start == 1
assert table.c.id.identity.cycle is True
assert "GENERATED BY DEFAULT AS IDENTITY" in str(CreateTable(table).compile(dialect=postgresql.dialect()))