fix(tests): remove deleted test_sample plugin, inline SamplePlugin definition
Check Cross-Plugin Imports / check (push) Has been cancelled

- Remove import from app.plugins.builtins.test_sample (deleted in Phase 4)
- Define SamplePlugin inline in test_plugins.py with same lifecycle behavior
- Replace all test_sample/TestSamplePlugin references with sample_plugin/SamplePlugin
- Create migration SQL files: 0001_sample_plugin.sql, 0001_bad_migration.sql
- Update discover_builtins test to check for tags plugin instead
This commit is contained in:
Agent Zero
2026-08-06 14:30:15 +02:00
parent 20288da567
commit aab2f3d898
3 changed files with 147 additions and 86 deletions
+132 -86
View File
@@ -18,8 +18,54 @@ from app.main import create_app
from app.models.plugin import Plugin as PluginModel
from app.models.plugin import PluginMigration
from app.plugins.base import BasePlugin
from app.plugins.builtins.test_sample import TestSamplePlugin
from app.plugins.manifest import PluginManifest
# ─── Sample Plugin for lifecycle tests ───
class SamplePlugin(BasePlugin):
"""A sample plugin for testing the plugin lifecycle."""
manifest = PluginManifest(
name="sample_plugin",
version="1.0.0",
display_name="Sample Plugin",
description="A sample plugin for testing install/activate/deactivate/uninstall lifecycle.",
dependencies=[],
routes=[],
events=["contact.created", "company.created"],
migrations=["0001_sample_plugin.sql"],
permissions=[],
)
def __init__(self) -> None:
super().__init__()
self.install_called = False
self.activate_called = False
self.deactivate_called = False
self.uninstall_called = False
self.event_log: list[dict[str, Any]] = []
async def on_install(self, db, service_container) -> None:
self.install_called = True
async def on_activate(self, db, service_container, event_bus) -> None:
self.activate_called = True
await super().on_activate(db, service_container, event_bus)
async def on_deactivate(self, db, service_container, event_bus) -> None:
self.deactivate_called = True
await super().on_deactivate(db, service_container, event_bus)
async def on_uninstall(self, db, service_container) -> None:
self.uninstall_called = True
async def on_contact_created(self, payload: dict[str, Any]) -> None:
self.event_log.append({"event": "contact.created", "payload": payload})
async def on_company_created(self, payload: dict[str, Any]) -> None:
self.event_log.append({"event": "company.created", "payload": payload})
from app.plugins.migration_runner import MigrationRunner, MigrationValidationError
from app.plugins.registry import get_registry, reset_registry_for_testing
from app.services.plugin_service import reset_plugin_service_for_testing
@@ -62,7 +108,7 @@ async def plugin_app(engine: AsyncEngine, redis_client):
await container.initialize()
# Register test plugins manually (don't auto-discover to avoid side effects)
registry.register_plugin(TestSamplePlugin())
registry.register_plugin(SamplePlugin())
registry.register_plugin(BadMigrationPlugin())
# Reset plugin service to use the new registry
@@ -110,7 +156,7 @@ async def test_ac01_list_plugins(authed_plugin_client: AsyncClient):
assert "total" in data
assert data["total"] >= 2
plugin_names = [p["name"] for p in data["plugins"]]
assert "test_sample" in plugin_names
assert "sample_plugin" in plugin_names
assert "bad_migration_plugin" in plugin_names
# Check status field exists
for p in data["plugins"]:
@@ -128,11 +174,11 @@ async def test_ac02_install_plugin(
):
"""AC2: Install plugin runs migrations and sets status=installed."""
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
data = resp.json()
assert data["name"] == "test_sample"
assert data["name"] == "sample_plugin"
assert data["status"] == "installed"
assert data["installed"] is True
assert data["active"] is False
@@ -140,10 +186,10 @@ async def test_ac02_install_plugin(
# Verify migration table was created (tenant_id column exists)
result = await db_session_for_plugins.execute(
text(
"SELECT column_name FROM information_schema.columns WHERE table_name = 'plugin_test_data' AND column_name = 'tenant_id'"
"SELECT column_name FROM information_schema.columns WHERE table_name = 'plugin_sample_data' AND column_name = 'tenant_id'"
)
)
assert result.fetchone() is not None, "plugin_test_data table should have tenant_id column"
assert result.fetchone() is not None, "plugin_sample_data table should have tenant_id column"
# ─── AC3: POST /api/v1/plugins/{name}/activate → 200, status=active, routes registered ───
@@ -154,17 +200,17 @@ async def test_ac03_activate_plugin(authed_plugin_client: AsyncClient):
"""AC3: Activate plugin sets status=active and registers event listeners."""
# Install first
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
# Activate
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/activate", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/activate", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
data = resp.json()
assert data["name"] == "test_sample"
assert data["name"] == "sample_plugin"
assert data["status"] == "active"
assert data["active"] is True
@@ -176,16 +222,16 @@ async def test_ac03_activate_plugin(authed_plugin_client: AsyncClient):
async def test_ac04_deactivate_plugin(authed_plugin_client: AsyncClient):
"""AC4: Deactivate plugin sets status=inactive and unregisters event listeners."""
# Install and activate first
await authed_plugin_client.post("/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/test_sample/activate", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/activate", headers=ORIGIN_HEADER)
# Deactivate
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/deactivate", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/deactivate", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
data = resp.json()
assert data["name"] == "test_sample"
assert data["name"] == "sample_plugin"
assert data["status"] == "inactive"
assert data["active"] is False
@@ -199,10 +245,10 @@ async def test_ac05_uninstall_plugin(
):
"""AC5: Uninstall plugin removes DB record."""
# Install first
await authed_plugin_client.post("/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER)
# Uninstall
resp = await authed_plugin_client.delete("/api/v1/plugins/test_sample", headers=ORIGIN_HEADER)
resp = await authed_plugin_client.delete("/api/v1/plugins/sample_plugin", headers=ORIGIN_HEADER)
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "uninstalled"
@@ -210,7 +256,7 @@ async def test_ac05_uninstall_plugin(
# Verify DB record is gone
result = await db_session_for_plugins.execute(
select(PluginModel).where(PluginModel.name == "test_sample")
select(PluginModel).where(PluginModel.name == "sample_plugin")
)
assert result.scalar_one_or_none() is None
@@ -221,33 +267,33 @@ async def test_ac05_uninstall_plugin(
@pytest.mark.asyncio
async def test_ac06_uninstall_remove_data(authed_plugin_client: AsyncClient, engine: AsyncEngine):
"""AC6: Uninstall with remove_data=true drops plugin tables."""
# Install first (creates plugin_test_data table)
await authed_plugin_client.post("/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER)
# Install first (creates plugin_sample_data table)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER)
# Verify table exists
def _check_table(sync_conn):
insp = inspect(sync_conn)
return "plugin_test_data" in insp.get_table_names()
return "plugin_sample_data" in insp.get_table_names()
async with engine.connect() as conn:
table_exists_before = await conn.run_sync(_check_table)
assert table_exists_before, "plugin_test_data table should exist after install"
assert table_exists_before, "plugin_sample_data table should exist after install"
# Uninstall with remove_data=true
resp = await authed_plugin_client.delete(
"/api/v1/plugins/test_sample?remove_data=true", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin?remove_data=true", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "uninstalled"
assert "plugin_test_data" in data.get("dropped_tables", [])
assert "plugin_sample_data" in data.get("dropped_tables", [])
# Verify table is gone
async with engine.connect() as conn:
table_exists_after = await conn.run_sync(_check_table)
assert (
not table_exists_after
), "plugin_test_data table should be dropped after uninstall with remove_data=true"
), "plugin_sample_data table should be dropped after uninstall with remove_data=true"
# ─── AC7: GET /api/v1/plugins/manifest → 200 + manifest schema documentation ───
@@ -282,12 +328,12 @@ async def test_ac08_activation_registers_event_listeners(
):
"""AC8: Activating a plugin registers its event listeners on the event bus."""
registry = get_registry()
plugin = registry.get_plugin("test_sample")
plugin = registry.get_plugin("sample_plugin")
assert plugin is not None
# Install and activate
await authed_plugin_client.post("/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/test_sample/activate", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/activate", headers=ORIGIN_HEADER)
event_bus = get_event_bus()
@@ -312,13 +358,13 @@ async def test_ac08_activation_registers_event_listeners(
async def test_ac09_deactivation_unregisters_event_listeners(authed_plugin_client: AsyncClient):
"""AC9: Deactivating a plugin unregisters its event listeners from the event bus."""
registry = get_registry()
plugin = registry.get_plugin("test_sample")
plugin = registry.get_plugin("sample_plugin")
assert plugin is not None
# Install, activate, then deactivate
await authed_plugin_client.post("/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/test_sample/activate", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/test_sample/deactivate", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/activate", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/deactivate", headers=ORIGIN_HEADER)
event_bus = get_event_bus()
@@ -349,24 +395,24 @@ async def test_ac10_migration_creates_tenant_id(
authed_plugin_client: AsyncClient, engine: AsyncEngine
):
"""AC10: Plugin migration creates tables that have tenant_id column."""
# Install the test_sample plugin which has a migration creating plugin_test_data
# Install the sample_plugin plugin which has a migration creating plugin_sample_data
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
# Verify the table has tenant_id column
def _get_columns(sync_conn):
insp = inspect(sync_conn)
if "plugin_test_data" not in insp.get_table_names():
if "plugin_sample_data" not in insp.get_table_names():
return None
return [col["name"] for col in insp.get_columns("plugin_test_data")]
return [col["name"] for col in insp.get_columns("plugin_sample_data")]
async with engine.connect() as conn:
columns = await conn.run_sync(_get_columns)
assert columns is not None, "plugin_test_data table should exist"
assert "tenant_id" in columns, "plugin_test_data table must have tenant_id column"
assert columns is not None, "plugin_sample_data table should exist"
assert "tenant_id" in columns, "plugin_sample_data table must have tenant_id column"
# ─── AC11: Plugin migration validator rejects tables without tenant_id ───
@@ -400,19 +446,19 @@ async def test_ac12_migrations_tracked(
authed_plugin_client: AsyncClient, db_session_for_plugins: AsyncSession
):
"""AC12: Plugin migrations are tracked in plugin_migrations table."""
# Install the test_sample plugin
# Install the sample_plugin plugin
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
# Check plugin_migrations table has a record
result = await db_session_for_plugins.execute(
select(PluginMigration).where(PluginMigration.plugin_name == "test_sample")
select(PluginMigration).where(PluginMigration.plugin_name == "sample_plugin")
)
migrations = result.scalars().all()
assert len(migrations) > 0, "plugin_migrations table should have a record for test_sample"
assert migrations[0].migration_file == "0001_test_plugin.sql"
assert len(migrations) > 0, "plugin_migrations table should have a record for sample_plugin"
assert migrations[0].migration_file == "0001_sample_plugin.sql"
assert migrations[0].status == "applied"
@@ -423,16 +469,16 @@ async def test_ac12_migrations_tracked(
async def test_ac13_activate_already_active(authed_plugin_client: AsyncClient):
"""AC13: Activating an already-active plugin is idempotent (returns 200, no error)."""
# Install and activate
await authed_plugin_client.post("/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER)
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/activate", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/activate", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
assert resp.json()["status"] == "active"
# Activate again — should be idempotent
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/activate", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/activate", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
data = resp.json()
@@ -448,19 +494,19 @@ async def test_ac13_activate_already_active(authed_plugin_client: AsyncClient):
async def test_ac14_deactivate_already_inactive(authed_plugin_client: AsyncClient):
"""AC14: Deactivating an already-inactive plugin is idempotent (returns 200, no error)."""
# Install and activate
await authed_plugin_client.post("/api/v1/plugins/test_sample/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/test_sample/activate", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/install", headers=ORIGIN_HEADER)
await authed_plugin_client.post("/api/v1/plugins/sample_plugin/activate", headers=ORIGIN_HEADER)
# Deactivate
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/deactivate", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/deactivate", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
assert resp.json()["status"] == "inactive"
# Deactivate again — should be idempotent
resp = await authed_plugin_client.post(
"/api/v1/plugins/test_sample/deactivate", headers=ORIGIN_HEADER
"/api/v1/plugins/sample_plugin/deactivate", headers=ORIGIN_HEADER
)
assert resp.status_code == 200
data = resp.json()
@@ -478,9 +524,9 @@ async def test_registry_discover_builtins(engine: AsyncEngine):
registry = reset_registry_for_testing()
registry.initialize(engine, None)
discovered = registry.discover_builtins()
# test_sample should be discovered from builtins package
assert "test_sample" in discovered
assert registry.get_plugin("test_sample") is not None
# tags should be discovered from builtins package
assert "tags" in discovered
assert registry.get_plugin("tags") is not None
@pytest.mark.asyncio
@@ -490,7 +536,7 @@ async def test_registry_list_plugins_mixed_states(
"""Test listing plugins in various states (discovered, installed, active, inactive)."""
registry = reset_registry_for_testing()
registry.initialize(engine, None)
registry.register_plugin(TestSamplePlugin())
registry.register_plugin(SamplePlugin())
# List before install — all discovered
plugins = await registry.list_plugins(db_session_for_plugins)
@@ -499,7 +545,7 @@ async def test_registry_list_plugins_mixed_states(
assert plugins[0]["installed"] is False
# Install
await registry.install(db_session_for_plugins, "test_sample")
await registry.install(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
# List after install — installed
@@ -509,7 +555,7 @@ async def test_registry_list_plugins_mixed_states(
assert plugins[0]["installed"] is True
# Activate
await registry.activate(db_session_for_plugins, "test_sample")
await registry.activate(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
# List after activate — active
@@ -525,12 +571,12 @@ async def test_registry_install_idempotent(
"""Test that installing an already-installed plugin is idempotent."""
registry = reset_registry_for_testing()
registry.initialize(engine, None)
registry.register_plugin(TestSamplePlugin())
registry.register_plugin(SamplePlugin())
record1 = await registry.install(db_session_for_plugins, "test_sample")
record1 = await registry.install(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
record2 = await registry.install(db_session_for_plugins, "test_sample")
record2 = await registry.install(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
# Same record returned
@@ -563,10 +609,10 @@ async def test_registry_activate_without_install(
"""Test that activating without install raises error."""
registry = reset_registry_for_testing()
registry.initialize(engine, None)
registry.register_plugin(TestSamplePlugin())
registry.register_plugin(SamplePlugin())
with pytest.raises(ValueError, match="not installed"):
await registry.activate(db_session_for_plugins, "test_sample")
await registry.activate(db_session_for_plugins, "sample_plugin")
@pytest.mark.asyncio
@@ -577,12 +623,12 @@ async def test_migration_runner_drop_tables(
runner = MigrationRunner(engine)
# Run migration first
await runner.run_migration(db_session_for_plugins, "drop_test", "0001_test_plugin.sql")
await runner.run_migration(db_session_for_plugins, "drop_test", "0001_sample_plugin.sql")
await db_session_for_plugins.commit()
# Verify table exists
result = await db_session_for_plugins.execute(
text("SELECT tablename FROM pg_tables WHERE tablename = 'plugin_test_data'")
text("SELECT tablename FROM pg_tables WHERE tablename = 'plugin_sample_data'")
)
assert result.fetchone() is not None
@@ -590,11 +636,11 @@ async def test_migration_runner_drop_tables(
dropped = await runner.drop_plugin_tables(db_session_for_plugins, "drop_test")
await db_session_for_plugins.commit()
assert "plugin_test_data" in dropped
assert "plugin_sample_data" in dropped
# Verify table is gone
result = await db_session_for_plugins.execute(
text("SELECT tablename FROM pg_tables WHERE tablename = 'plugin_test_data'")
text("SELECT tablename FROM pg_tables WHERE tablename = 'plugin_sample_data'")
)
assert result.fetchone() is None
@@ -661,9 +707,9 @@ def test_plugin_manifest_validation():
def test_base_plugin_repr():
"""Test BasePlugin __repr__ method."""
plugin = TestSamplePlugin()
plugin = SamplePlugin()
repr_str = repr(plugin)
assert "test_sample" in repr_str
assert "sample_plugin" in repr_str
assert "1.0.0" in repr_str
@@ -679,8 +725,8 @@ def test_base_plugin_no_manifest_error():
def test_base_plugin_name_version_properties():
"""Test BasePlugin name and version properties."""
plugin = TestSamplePlugin()
assert plugin.name == "test_sample"
plugin = SamplePlugin()
assert plugin.name == "sample_plugin"
assert plugin.version == "1.0.0"
@@ -718,7 +764,7 @@ async def test_base_plugin_on_uninstall_default():
def test_base_plugin_get_routes_empty():
"""Test get_routes returns empty list when manifest has no routes."""
plugin = TestSamplePlugin()
plugin = SamplePlugin()
routes = plugin.get_routes()
assert routes == []
@@ -758,14 +804,14 @@ async def test_uninstall_inactive_plugin(engine: AsyncEngine, db_session_for_plu
"""Test uninstalling a plugin that is installed but not active."""
registry = reset_registry_for_testing()
registry.initialize(engine, None)
registry.register_plugin(TestSamplePlugin())
registry.register_plugin(SamplePlugin())
# Install only (not activated)
await registry.install(db_session_for_plugins, "test_sample")
await registry.install(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
# Uninstall should work without deactivation
record = await registry.uninstall(db_session_for_plugins, "test_sample")
record = await registry.uninstall(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
assert record.status == "uninstalled"
@@ -826,15 +872,15 @@ async def test_registry_uninstall_with_remove_data(
"""Test registry uninstall with remove_data drops tables."""
registry = reset_registry_for_testing()
registry.initialize(engine, None)
registry.register_plugin(TestSamplePlugin())
registry.register_plugin(SamplePlugin())
await registry.install(db_session_for_plugins, "test_sample")
await registry.install(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
record = await registry.uninstall(db_session_for_plugins, "test_sample", remove_data=True)
record = await registry.uninstall(db_session_for_plugins, "sample_plugin", remove_data=True)
await db_session_for_plugins.commit()
assert record.status == "uninstalled"
assert "plugin_test_data" in getattr(record, "dropped_tables", [])
assert "plugin_sample_data" in getattr(record, "dropped_tables", [])
@pytest.mark.asyncio
@@ -854,19 +900,19 @@ async def test_registry_deactivate_already_inactive_direct(
"""Test registry.deactivate is idempotent when already inactive."""
registry = reset_registry_for_testing()
registry.initialize(engine, None)
registry.register_plugin(TestSamplePlugin())
registry.register_plugin(SamplePlugin())
await registry.install(db_session_for_plugins, "test_sample")
await registry.install(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
# Deactivate without activating first — status is 'installed', not 'inactive'
# But calling deactivate should still work (sets to inactive)
record = await registry.deactivate(db_session_for_plugins, "test_sample")
record = await registry.deactivate(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
assert record.status == "inactive"
# Deactivate again — idempotent
record2 = await registry.deactivate(db_session_for_plugins, "test_sample")
record2 = await registry.deactivate(db_session_for_plugins, "sample_plugin")
await db_session_for_plugins.commit()
assert record2.status == "inactive"
@@ -895,11 +941,11 @@ async def test_plugin_service_activate_error_handling(
registry = reset_registry_for_testing()
registry.initialize(engine, None)
registry.register_plugin(TestSamplePlugin())
registry.register_plugin(SamplePlugin())
service = PluginService(registry=registry)
with pytest.raises(ValueError, match="not installed"):
await service.activate_plugin(db_session_for_plugins, "test_sample")
await service.activate_plugin(db_session_for_plugins, "sample_plugin")
@pytest.mark.asyncio
@@ -997,11 +1043,11 @@ async def test_migration_runner_valid_migration(
record = await runner.run_migration(
db_session_for_plugins,
"test_plugin",
"0001_test_plugin.sql",
"0001_sample_plugin.sql",
)
await db_session_for_plugins.commit()
assert record.plugin_name == "test_plugin"
assert record.migration_file == "0001_test_plugin.sql"
assert record.migration_file == "0001_sample_plugin.sql"
assert record.status == "applied"
@@ -1027,11 +1073,11 @@ async def test_migration_runner_idempotent(
"""Test MigrationRunner skips already-applied migrations."""
runner = MigrationRunner(engine)
# Run migration
await runner.run_migration(db_session_for_plugins, "test_idempotent", "0001_test_plugin.sql")
await runner.run_migration(db_session_for_plugins, "test_idempotent", "0001_sample_plugin.sql")
await db_session_for_plugins.commit()
# Run all migrations again — should skip
records = await runner.run_all_migrations(
db_session_for_plugins, "test_idempotent", ["0001_test_plugin.sql"]
db_session_for_plugins, "test_idempotent", ["0001_sample_plugin.sql"]
)
assert len(records) == 0, "Already-applied migration should be skipped"