Files
leocrm/templates/plugin-template/tests/test_plugin.py
T
Agent Zero fc96a2f86c Phase 3: Plugin-UI-System (WordPress-Style)
Backend:
- PluginManifest um 5 neue UI-Felder erweitert: menu_items, page_routes,
  detail_tabs, settings_pages, dashboard_widgets (FrontendMenuItem,
  FrontendPageRoute, FrontendDetailTab, FrontendSettingsPage,
  FrontendDashboardWidget)
- GET /api/v1/plugins/active-manifests Endpoint liefert UI-Manifeste
  aller aktiven Plugins
- Registry.get_active_manifests() + PluginService.get_active_manifests()
- 12 Built-in Plugins mit UI-Manifest-Daten gefuellt (menu_items,
  page_routes, detail_tabs, settings_pages)
- Plugin-Install-System: POST /upload (ZIP), POST /install-url (URL)
  mit Validierung (Manifest, dangerous imports, SQL migrations)

Frontend:
- pluginStore.ts (Zustand) mit PluginUiManifest Typen + Selektoren
- useActivePluginManifests() React Query Hook
- PluginRegistry.tsx — fetcht Manifeste beim App-Start
- PluginLoader.tsx — dynamisches React.lazy() mit ErrorBoundary
- PluginRouteRenderer.tsx — Catch-all fuer Plugin-Routes
- routes/index.tsx — Catch-all Routes fuer Plugin-Pages + Settings
- Sidebar.tsx — dynamische Plugin Menu-Items mit Grouping + Icons
- Settings.tsx — dynamische Plugin Settings-Pages
- ContactDetail.tsx — dynamische Plugin Detail-Tabs mit Permissions
- AppShell.tsx — PluginRegistry Provider eingebunden
- SettingsPlugins.tsx — Install-UI (ZIP Upload + URL Install)
- plugins.ts — useUploadPlugin() + useInstallPluginFromUrl() Hooks

Docs & Templates:
- docs/plugin-development-guide.md — komplette Entwickler-Doku
- templates/plugin-template/ — Boilerplate mit allen Manifest-Feldern

Tests:
- 34 Vitest-Tests (PluginRegistry, PluginLoader, PluginRouteRenderer,
  pluginStore) — alle bestanden
- TSC: keine neuen Errors (nur pre-existing Dms.tsx)
2026-07-23 19:01:18 +02:00

64 lines
1.8 KiB
Python

"""Tests for the example plugin."""
import pytest
from httpx import AsyncClient, ASGITransport
from app.main import create_app
@pytest.fixture
async def client():
"""Create a test client."""
app = create_app()
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
yield ac
@pytest.fixture
def auth_headers():
"""Return headers with valid authentication."""
return {
"Authorization": "Bearer test-token",
"X-Tenant-ID": "test-tenant",
}
@pytest.mark.asyncio
async def test_list_items_requires_auth(client: AsyncClient):
"""Test that listing items requires authentication."""
response = await client.get("/api/v1/example")
assert response.status_code in (401, 403)
@pytest.mark.asyncio
async def test_list_items_with_auth(client: AsyncClient, auth_headers: dict):
"""Test that listing items works with authentication."""
response = await client.get("/api/v1/example", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert "total" in data
@pytest.mark.asyncio
async def test_create_item_requires_write_permission(client: AsyncClient, auth_headers: dict):
"""Test that creating items requires write permission."""
response = await client.post(
"/api/v1/example",
headers=auth_headers,
json={"name": "Test Item"},
)
# May return 201 or 403 depending on test permissions
assert response.status_code in (201, 403)
@pytest.mark.asyncio
async def test_get_item_returns_404_for_missing(client: AsyncClient, auth_headers: dict):
"""Test that getting a non-existent item returns 404."""
response = await client.get(
"/api/v1/example/non-existent-id",
headers=auth_headers,
)
assert response.status_code == 404