fix(tests): remove deleted test_sample plugin, inline SamplePlugin definition
Check Cross-Plugin Imports / check (push) Has been cancelled
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:
@@ -0,0 +1,6 @@
|
||||
-- Bad migration: creates table WITHOUT tenant_id (for validator testing)
|
||||
CREATE TABLE IF NOT EXISTS plugin_bad_data (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Sample plugin migration: creates plugin_sample_data table with tenant_id
|
||||
CREATE TABLE IF NOT EXISTS plugin_sample_data (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_plugin_sample_data_tenant ON plugin_sample_data(tenant_id);
|
||||
+132
-86
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user