feat(M5): Plugin-MiniApps — dms, mail, wiki, graph_rag, automation (#363)
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- 5 Manifest-Beiträge (MiniAppContribution): dms_folders (dms:read), mail_unread (mail:read), wiki_recent (wiki:read), graph_overview (graph:read), automation_status (automation:read) — je settings_schema max_items, order 60-100 - 5 Frontend-Widgets auf bestehenden API-Clients (keine neuen Backend-Endpoints): DmsFoldersWidget, MailUnreadWidget, WikiRecentWidget, GraphOverviewWidget, AutomationStatusWidget - MiniAppHost-Registry +5; tsc clean, build OK - Tests: M5 7/7 (TDD rot->gruen), Backend-Regression 53/53, Vitest 22/22
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""M5 — Plugin-MiniApps tests.
|
||||
|
||||
Each business plugin contributes its MiniApps via the manifest
|
||||
contribution (same pattern as contacts_stats from M4): dms, mail, wiki,
|
||||
graph_rag and automation ship one dashboard app each, wired to the
|
||||
plugin's own permission and frontend component.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _fresh_registry():
|
||||
from app.plugins.miniapp_registry import reset_miniapp_registry
|
||||
|
||||
reset_miniapp_registry()
|
||||
yield
|
||||
reset_miniapp_registry()
|
||||
|
||||
|
||||
# (plugin_name, app_id, permission, component, order)
|
||||
EXPECTED_APPS: list[tuple[str, str, str, str, int]] = [
|
||||
("dms", "dms_folders", "dms:read", "@/components/dashboard/DmsFoldersWidget", 60),
|
||||
("mail", "mail_unread", "mail:read", "@/components/dashboard/MailUnreadWidget", 70),
|
||||
("wiki", "wiki_recent", "wiki:read", "@/components/dashboard/WikiRecentWidget", 80),
|
||||
("graph_rag", "graph_overview", "graph:read", "@/components/dashboard/GraphOverviewWidget", 90),
|
||||
("automation", "automation_status", "automation:read", "@/components/dashboard/AutomationStatusWidget", 100),
|
||||
]
|
||||
|
||||
|
||||
class TestPluginMiniAppContributions:
|
||||
@pytest.mark.parametrize("plugin_name,app_id,permission,component,order", EXPECTED_APPS)
|
||||
def test_manifest_contains_miniapp(
|
||||
self, plugin_name: str, app_id: str, permission: str, component: str, order: int
|
||||
):
|
||||
"""The plugin manifest declares the MiniApp with the right fields."""
|
||||
import importlib
|
||||
|
||||
module = importlib.import_module(f"app.plugins.builtins.{plugin_name}.plugin")
|
||||
plugin_cls = getattr(module, f"{plugin_name.replace('_', '').title().replace('Graphrag', 'GraphRAG')}Plugin")
|
||||
contributions = plugin_cls().manifest.miniapps
|
||||
found = [m for m in contributions if m.app_id == app_id]
|
||||
assert len(found) == 1, f"{plugin_name}: miniapp {app_id} missing"
|
||||
app = found[0]
|
||||
assert app.permission == permission
|
||||
assert app.component == component
|
||||
assert app.order == order
|
||||
assert "dashboard" in app.hosts
|
||||
|
||||
def test_lifecycle_registers_all_plugin_miniapps(self):
|
||||
"""BasePlugin._register_manifest_miniapps carries component (M4 fix)
|
||||
for every contributing plugin — all five apps land in the registry."""
|
||||
from app.plugins.miniapp_registry import get_miniapp_registry
|
||||
|
||||
registry = get_miniapp_registry()
|
||||
for plugin_name, app_id, _permission, component, _order in EXPECTED_APPS:
|
||||
import importlib
|
||||
|
||||
module = importlib.import_module(f"app.plugins.builtins.{plugin_name}.plugin")
|
||||
plugin_cls = getattr(
|
||||
module, f"{plugin_name.replace('_', '').title().replace('Graphrag', 'GraphRAG')}Plugin"
|
||||
)
|
||||
plugin_cls()._register_manifest_miniapps()
|
||||
|
||||
app = registry.get_app(app_id)
|
||||
assert app is not None, f"{app_id} not registered"
|
||||
assert app.component == component, f"{app_id}: component lost"
|
||||
|
||||
def test_settings_schemas_present(self):
|
||||
"""List-style widgets expose max_items in their settings_schema so the
|
||||
generic settings form (M3) can render it."""
|
||||
import importlib
|
||||
|
||||
for plugin_name, app_id, *_rest in EXPECTED_APPS:
|
||||
module = importlib.import_module(f"app.plugins.builtins.{plugin_name}.plugin")
|
||||
plugin_cls = getattr(
|
||||
module, f"{plugin_name.replace('_', '').title().replace('Graphrag', 'GraphRAG')}Plugin"
|
||||
)
|
||||
contributions = plugin_cls().manifest.miniapps
|
||||
app = next(m for m in contributions if m.app_id == app_id)
|
||||
fields = app.settings_schema.get("fields", [])
|
||||
assert any(f["name"] == "max_items" for f in fields), (
|
||||
f"{app_id}: max_items missing in settings_schema"
|
||||
)
|
||||
Reference in New Issue
Block a user