from __future__ import annotations import unittest from fastapi import FastAPI from fastapi.testclient import TestClient from govoplan_core.auth import get_api_principal 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") app.dependency_overrides[get_api_principal] = lambda: object() 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()