Files
govoplan-core/tests/test_presentation_contract.py
zemion 6e518fa6a2
Module Package Release / publish-packages (push) Successful in 13s
feat: compose stable product surfaces
2026-08-24 13:21:24 +02:00

226 lines
8.3 KiB
Python

from __future__ import annotations
import unittest
from dataclasses import replace
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,
ProductAvailabilityExplanation,
ProductAreaContribution,
ProductSurfaceContribution,
QuickAccessTool,
)
from govoplan_core.core.registry import PlatformRegistry, RegistryError
from govoplan_core.core.views import ViewSurface
from govoplan_core.server.platform import create_platform_router
def presentation_manifest() -> ModuleManifest:
return ModuleManifest(
id="example",
name="Example",
version="test",
frontend=FrontendModule(
module_id="example",
routes=(
FrontendRoute(
path="/example",
component="ExamplePage",
surface_id="example.route.main",
),
),
view_surfaces=(
ViewSurface(
id="example.quick.summary",
module_id="example",
kind="quick_access",
label="Example summary",
),
),
product_areas=(
ProductAreaContribution(
id="work",
module_id="example",
label="Work",
icon="list-checks",
surface_ids=("example.route.main",),
),
),
product_surfaces=(
ProductSurfaceContribution(
id="work.examples",
module_id="example",
label="Examples",
description="Review and update governed examples.",
icon="list-checks",
entry_path="/work/examples",
route_path="/example",
surface_ids=("example.route.main",),
presentations=("task", "reader"),
unavailable=ProductAvailabilityExplanation(
reason="authorization",
title="Examples are unavailable",
description="Your current responsibility does not include examples.",
resolution="Ask the responsible administrator to review your assignment.",
responsible_role="Access administrator",
),
),
),
quick_access_tools=(
QuickAccessTool(
id="example.summary",
module_id="example",
category_id="work",
label="Example summary",
surface_id="example.quick.summary",
icon="list-checks",
full_page_path="/example",
required_any=("example:item:read",),
returned_reference_kinds=("example.item",),
help_context_id="example.quick.summary",
),
),
),
)
class PresentationContractTests(unittest.TestCase):
def test_registry_accepts_owned_presentation_contributions(self) -> None:
registry = PlatformRegistry()
registry.register(presentation_manifest())
snapshot = registry.validate()
self.assertEqual("example", snapshot.manifests[0].id)
def test_registry_rejects_unknown_tool_surface(self) -> None:
manifest = presentation_manifest()
frontend = manifest.frontend
assert frontend is not None
invalid = ModuleManifest(
id=manifest.id,
name=manifest.name,
version=manifest.version,
frontend=FrontendModule(
module_id="example",
routes=frontend.routes,
quick_access_tools=(
QuickAccessTool(
id="example.summary",
module_id="example",
category_id="work",
label="Example summary",
surface_id="example.missing",
icon="list-checks",
),
),
),
)
registry = PlatformRegistry()
registry.register(invalid)
with self.assertRaisesRegex(RegistryError, "unknown surface"):
registry.validate()
def test_platform_payload_exposes_presentation_catalogue(self) -> None:
registry = PlatformRegistry()
registry.register(presentation_manifest())
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["presentation_contract_version"])
self.assertEqual("work", frontend["product_areas"][0]["id"])
product_surface = frontend["product_surfaces"][0]
self.assertEqual("1", product_surface["contract_version"])
self.assertEqual("work.examples", product_surface["id"])
self.assertEqual("/work/examples", product_surface["entry_path"])
self.assertEqual("/example", product_surface["route_path"])
self.assertEqual(["task", "reader"], product_surface["presentations"])
self.assertEqual(
"authorization",
product_surface["unavailable"]["reason"],
)
self.assertEqual("example.summary", frontend["quick_access_tools"][0]["id"])
self.assertEqual("1", frontend["quick_access_tools"][0]["contract_version"])
self.assertEqual(
["example.item"],
frontend["quick_access_tools"][0]["returned_reference_kinds"],
)
self.assertEqual(
"example.quick.summary",
frontend["quick_access_tools"][0]["help_context_id"],
)
def test_registry_rejects_product_surface_without_owner_route(self) -> None:
manifest = presentation_manifest()
frontend = manifest.frontend
assert frontend is not None
surface = frontend.product_surfaces[0]
invalid = ModuleManifest(
id=manifest.id,
name=manifest.name,
version=manifest.version,
frontend=FrontendModule(
module_id=manifest.id,
routes=frontend.routes,
product_surfaces=(
ProductSurfaceContribution(
id=surface.id,
module_id=surface.module_id,
label=surface.label,
description=surface.description,
icon=surface.icon,
entry_path=surface.entry_path,
route_path="/missing",
surface_ids=surface.surface_ids,
unavailable=surface.unavailable,
),
),
),
)
registry = PlatformRegistry()
registry.register(invalid)
with self.assertRaisesRegex(RegistryError, "unknown owner route"):
registry.validate()
def test_registry_rejects_product_alias_that_shadows_a_route(self) -> None:
manifest = presentation_manifest()
frontend = manifest.frontend
assert frontend is not None
surface = frontend.product_surfaces[0]
invalid = replace(
manifest,
frontend=replace(
frontend,
routes=(
*frontend.routes,
FrontendRoute(path="/shortcut", component="ShortcutPage"),
),
product_surfaces=(replace(surface, aliases=("/shortcut",)),),
),
)
registry = PlatformRegistry()
registry.register(invalid)
with self.assertRaisesRegex(RegistryError, "collides with a concrete route"):
registry.validate()
if __name__ == "__main__":
unittest.main()