fix(arch): externes Audit — 13 Backend-Fixes (Workspace-Modules, Tenant-Manifeste, Lifecycle, Contracts, Permissions)
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
Verifikation: Alle 17 Audit-Findings gegen den Code geprueft — alle bestaetigt. Backend-Lifecycle-Fixes umgesetzt; 4 Frontend-Plugin-Architektur-Punkte als Phase Q in die Roadmap eingeplant. - P1 list_workspaces: Module + User-Counts gebuendelt laden (Editor-Overwrite-Bug) - P1 active-manifests: Tenant-Deaktivierung (tenant_plugin_activation) filtern - P1 uninstall: volle Service-Deactivation VOR registry.uninstall() - P1 ContractRegistry: DB-Aktivstatus-Guard (Restart-Edge-Case) + Re-Activate - P1/P2 Field-Definitions: voller Lifecycle (register/unregister) im Service - P1/P2 Contact-Felddefinitionen (39) ins ContactsPlugin-Manifest verschoben - P1 12 fehlende Permission-Keys registriert (AST-Scan: 0 fehlend) - P2 contact_folder -> ContactsPlugin; ENTITY_PLUGIN_OWNERS wird befuellt - P2 Entity-Permission-Fallback fail-closed statt contacts:read - P2 forgejo_error_reporter is_core=False; DMS is_core=True (ADR-020) - P2 Worker: Contacts-Trash-Cleanup ins Plugin (get_job_modules-Discovery) - P1/P2 DSGVO-Export delegiert an DSAR-Collector (kein Core->Contacts) - P2 False-green Tests korrigiert (or True, veraltete Route-Count-Assertion) Verifikation: tests/test_audit_architecture_fixes.py 17/17; Regressionen gruen (contacts_lifecycle, entity_registry, workspace_scopes, rbac, lifecycle_service); Combo-Order-Test 35/35; Cross-Plugin-Checker 497/0; compileall sauber; ruff auf 7-Error-Baseline. Doku: PROGRESS.md Audit-Section, PLATFORM_ROADMAP.md Phase Q (Q1-Q4), plugin-development-guide.md Lifecycle, permissions.md Katalog.
This commit is contained in:
@@ -46,7 +46,13 @@ class TestContactsPluginLifecycle:
|
||||
# Before activation: nothing registered
|
||||
reg = get_restore_registry()
|
||||
assert not reg.is_registered("contact")
|
||||
assert "contact" not in ENTITY_MODELS or True # May be in core models
|
||||
# Audit P2 (false-green): this used to be
|
||||
# ``assert "contact" not in ENTITY_MODELS or True`` — always true.
|
||||
# Entity models are registered by the conftest bootstrap for ALL
|
||||
# discovered plugins (mirroring main.py), so "contact" IS present
|
||||
# before on_activate; what must NOT be registered yet is the
|
||||
# restore config (checked above) and the plugin gate:
|
||||
assert not get_permission_registry().is_plugin_active("contacts")
|
||||
|
||||
# Activate
|
||||
import asyncio
|
||||
@@ -136,11 +142,26 @@ class TestContactsPluginLifecycle:
|
||||
assert models["company"] is Contact
|
||||
|
||||
def test_app_still_starts_without_contacts_special_case(self):
|
||||
"""App creates successfully with Contacts as a plugin, not a Core special case."""
|
||||
"""App creates successfully with Contacts as a plugin, not a Core special case.
|
||||
|
||||
Audit P2 (false-green / stale assertion): the old ``> 100 routes``
|
||||
assertion reflected the PRE-plugin architecture where business
|
||||
routes were hardcoded in main.py. Since the plugin refactor,
|
||||
create_app() mounts only CORE routes (~85); plugin routes are
|
||||
mounted by the registry lifecycle. The real "no special case"
|
||||
proof is that no contacts route is statically included.
|
||||
"""
|
||||
from app.main import create_app
|
||||
app = create_app()
|
||||
assert app is not None
|
||||
assert len(app.routes) > 100 # Should have many routes
|
||||
# Core routes must exist
|
||||
assert len(app.routes) > 50
|
||||
# Contacts is a plugin: its routes must NOT be hardcoded in the
|
||||
# core app (they are mounted via registry.activate at startup).
|
||||
static_paths = {getattr(r, "path", "") for r in app.routes}
|
||||
assert not any(p.startswith("/api/v1/contacts") for p in static_paths), (
|
||||
"contacts routes must not be statically mounted — Contacts is a plugin"
|
||||
)
|
||||
|
||||
def test_hook_isolation_deactivate_one_plugin_keeps_other(self):
|
||||
"""Two plugins register on same event; deactivating one keeps the other's handler."""
|
||||
@@ -180,12 +201,14 @@ class TestContactsPluginLifecycle:
|
||||
# Cleanup
|
||||
reg._reset_for_testing()
|
||||
|
||||
def test_full_lifecycle_activate_deactivate_via_service(self):
|
||||
"""Full lifecycle: activate via PluginService → verify registered → deactivate → verify deregistered.
|
||||
def test_full_lifecycle_activate_deactivate_simulated(self):
|
||||
"""Full lifecycle: simulate the service activation sequence → verify
|
||||
registered → simulate deactivation → verify deregistered.
|
||||
|
||||
This is the E2E lifecycle test (Phase 5.2): tests that activation/deactivation
|
||||
through the service layer properly registers/deregisters permissions,
|
||||
entity models, restore config, and history hooks.
|
||||
Audit P2 (false-green): renamed — this test drives the plugin hooks
|
||||
and registry calls MANUALLY (mirroring what PluginService does),
|
||||
it does NOT go through the real PluginService. The true E2E test
|
||||
through PluginService lives in test_plugin_lifecycle_service.py.
|
||||
"""
|
||||
from app.services.plugin_service import PluginService
|
||||
from app.core.permission_registry import get_permission_registry, init_permission_registry
|
||||
@@ -252,8 +275,11 @@ class TestContactsPluginLifecycle:
|
||||
# Verify everything is deregistered
|
||||
assert not perm_reg.is_plugin_active(plugin_name), "Plugin should be inactive"
|
||||
assert not get_restore_registry().is_registered("contact"), "Restore config should be removed"
|
||||
assert "contact" not in ENTITY_MODELS or "contact" in {"contact", "contacts", "company"}, \
|
||||
"Entity model may still be in core ENTITY_MODELS"
|
||||
# Audit P2 (false-green): the old assertion
|
||||
# ``"contact" not in ENTITY_MODELS or "contact" in {...}`` was
|
||||
# always true. After unregister_entity_model() the type must be
|
||||
# GONE from the registry (contacts is plugin-owned, not core).
|
||||
assert "contact" not in ENTITY_MODELS, "contact must be deregistered after plugin deactivation"
|
||||
all_hooks = hook_reg._actions if hasattr(hook_reg, '_actions') else {}
|
||||
assert not any("contact.after_create" in k for k in all_hooks), "History hooks should be removed"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user