226 lines
8.3 KiB
Python
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()
|