Add governed View surface runtime

This commit is contained in:
2026-07-28 21:04:54 +02:00
parent 13bc3d3b4e
commit ce9ef8d88f
24 changed files with 1215 additions and 39 deletions

260
tests/test_view_surfaces.py Normal file
View File

@@ -0,0 +1,260 @@
from __future__ import annotations
import unittest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from govoplan_core.core.modules import (
FrontendModule,
FrontendRoute,
ModuleManifest,
NavItem,
)
from govoplan_core.core.registry import (
PlatformRegistry,
RegistryError,
manifest_view_surfaces,
)
from govoplan_core.core.views import ViewSurface
from govoplan_core.server.platform import create_platform_router
from govoplan_core.server.registry import available_module_manifests
def example_manifest(
*,
custom_surfaces: tuple[ViewSurface, ...] = (),
) -> ModuleManifest:
return ModuleManifest(
id="example",
name="Example",
version="test",
frontend=FrontendModule(
module_id="example",
nav_items=(
NavItem(
path="/example",
label="Example",
order=20,
),
),
routes=(
FrontendRoute(
path="/example",
component="ExamplePage",
order=20,
),
),
view_surfaces=custom_surfaces,
),
)
class ViewSurfaceContractTests(unittest.TestCase):
def test_manifest_derives_module_navigation_and_route_surfaces(self) -> None:
surfaces = manifest_view_surfaces(example_manifest())
self.assertEqual(
[
"example.module",
"example.nav.example",
"example.route.example",
],
[surface.id for surface in surfaces],
)
self.assertEqual(
"example.module",
next(
surface.parent_id
for surface in surfaces
if surface.id == "example.route.example"
),
)
def test_registry_includes_valid_custom_surface(self) -> None:
registry = PlatformRegistry()
registry.register(
example_manifest(
custom_surfaces=(
ViewSurface(
id="example.settings.connection",
module_id="example",
kind="section",
label="Connection settings",
),
)
)
)
registry.validate()
custom_surface = next(
surface
for surface in registry.view_surfaces()
if surface.id == "example.settings.connection"
)
self.assertEqual("example.module", custom_surface.parent_id)
def test_registry_rejects_surface_owned_by_another_module(self) -> None:
registry = PlatformRegistry()
registry.register(
example_manifest(
custom_surfaces=(
ViewSurface(
id="other.settings.connection",
module_id="other",
kind="section",
label="Connection settings",
),
)
)
)
with self.assertRaisesRegex(RegistryError, "belongs to"):
registry.validate()
def test_registry_rejects_unknown_surface_parent(self) -> None:
registry = PlatformRegistry()
registry.register(
example_manifest(
custom_surfaces=(
ViewSurface(
id="example.settings.connection",
module_id="example",
kind="section",
label="Connection settings",
parent_id="example.missing",
),
)
)
)
with self.assertRaisesRegex(RegistryError, "unknown parent"):
registry.validate()
def test_registry_rejects_cyclic_surface_hierarchy(self) -> None:
registry = PlatformRegistry()
registry.register(
example_manifest(
custom_surfaces=(
ViewSurface(
id="example.section.one",
module_id="example",
kind="section",
label="One",
parent_id="example.section.two",
),
ViewSurface(
id="example.section.two",
module_id="example",
kind="section",
label="Two",
parent_id="example.section.one",
),
)
)
)
with self.assertRaisesRegex(RegistryError, "contains a cycle"):
registry.validate()
def test_required_surface_must_be_visible_by_default(self) -> None:
registry = PlatformRegistry()
registry.register(
example_manifest(
custom_surfaces=(
ViewSurface(
id="example.recovery",
module_id="example",
kind="action",
label="Recovery",
required=True,
default_visible=False,
),
)
)
)
with self.assertRaisesRegex(RegistryError, "visible by default"):
registry.validate()
def test_registry_validates_automatically_derived_surface_ids(self) -> None:
registry = PlatformRegistry()
registry.register(
ModuleManifest(
id="example",
name="Example",
version="test",
frontend=FrontendModule(
module_id="example",
routes=(
FrontendRoute(
path=f"/{'segment-' * 30}",
component="LongRoutePage",
),
),
),
)
)
with self.assertRaisesRegex(RegistryError, "View surface id"):
registry.validate()
def test_platform_module_payload_exposes_normalized_surface_contract(self) -> None:
registry = PlatformRegistry()
registry.register(
example_manifest(
custom_surfaces=(
ViewSurface(
id="example.section.details",
module_id="example",
kind="section",
label="Details",
),
)
)
)
registry.validate()
app = FastAPI()
app.state.govoplan_registry = registry
app.include_router(create_platform_router(), prefix="/api/v1")
with TestClient(app) as client:
response = client.get("/api/v1/platform/modules")
self.assertEqual(200, response.status_code)
frontend = response.json()["modules"][0]["frontend"]
self.assertEqual("1", frontend["view_surface_contract_version"])
self.assertEqual(
"example.route.example",
frontend["routes"][0]["surface_id"],
)
surfaces = {
surface["id"]: surface
for surface in frontend["view_surfaces"]
}
self.assertEqual(
"example.module",
surfaces["example.section.details"]["parent_id"],
)
def test_access_admin_route_accepts_view_manager_permissions(self) -> None:
access = available_module_manifests()["access"]
admin_route = next(
route
for route in access.frontend.routes
if route.path == "/admin"
)
self.assertTrue(
{
"views:definition:read",
"views:assignment:read",
"views:system_definition:read",
"views:system_assignment:read",
}.issubset(admin_route.required_any)
)
if __name__ == "__main__":
unittest.main()