feat: compose stable product surfaces
Module Package Release / publish-packages (push) Successful in 13s

This commit is contained in:
2026-08-24 13:21:24 +02:00
parent f98cf9ced8
commit 6e518fa6a2
25 changed files with 1012 additions and 17 deletions
+88
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import unittest
from dataclasses import replace
from fastapi import FastAPI
from fastapi.testclient import TestClient
@@ -10,7 +11,9 @@ from govoplan_core.core.modules import (
FrontendModule,
FrontendRoute,
ModuleManifest,
ProductAvailabilityExplanation,
ProductAreaContribution,
ProductSurfaceContribution,
QuickAccessTool,
)
from govoplan_core.core.registry import PlatformRegistry, RegistryError
@@ -49,6 +52,26 @@ def presentation_manifest() -> ModuleManifest:
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",
@@ -121,6 +144,16 @@ class PresentationContractTests(unittest.TestCase):
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(
@@ -132,6 +165,61 @@ class PresentationContractTests(unittest.TestCase):
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()