Add governed View surface runtime
This commit is contained in:
260
tests/test_view_surfaces.py
Normal file
260
tests/test_view_surfaces.py
Normal 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()
|
||||
Reference in New Issue
Block a user