274 lines
9.7 KiB
Python
274 lines
9.7 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from govoplan_core.core.modules import (
|
|
CapabilityDocumentation,
|
|
DocumentationConfigurationDecision,
|
|
DocumentationConfigurationProviderRegistration,
|
|
DocumentationCondition,
|
|
DocumentationSourceDefinition,
|
|
DocumentationTopic,
|
|
ModuleManifest,
|
|
localized_documentation_metadata,
|
|
user_workflow_scope_condition_issues,
|
|
with_documentation_structured_translations,
|
|
)
|
|
from govoplan_core.core.registry import PlatformRegistry, RegistryError
|
|
|
|
|
|
def workflow_topic(
|
|
*,
|
|
conditions: tuple[DocumentationCondition, ...],
|
|
documentation_types: tuple[str, ...] = ("user",),
|
|
kind: str = "workflow",
|
|
) -> DocumentationTopic:
|
|
return DocumentationTopic(
|
|
id="example.workflow.task",
|
|
title="Complete a task",
|
|
summary="Complete the example task.",
|
|
documentation_types=documentation_types, # type: ignore[arg-type]
|
|
conditions=conditions,
|
|
metadata={"kind": kind},
|
|
)
|
|
|
|
|
|
class DocumentationTopicContractTests(unittest.TestCase):
|
|
def test_registry_rejects_empty_documentation_version_range(self) -> None:
|
|
topic = DocumentationTopic(
|
|
id="example.versioned",
|
|
title="Versioned",
|
|
summary="Invalid range",
|
|
version_min="2.0.0",
|
|
version_max_exclusive="2.0.0",
|
|
)
|
|
|
|
with self.assertRaisesRegex(RegistryError, "empty version range"):
|
|
registry_for(topic).validate()
|
|
|
|
def test_user_workflow_requires_scope_conditions(self) -> None:
|
|
topic = workflow_topic(conditions=())
|
|
|
|
self.assertEqual(
|
|
user_workflow_scope_condition_issues(topic),
|
|
("user workflow topics must declare at least one scope-conditioned alternative",),
|
|
)
|
|
with self.assertRaisesRegex(RegistryError, "scope-conditioned alternative"):
|
|
registry_for(topic).validate()
|
|
|
|
def test_every_condition_alternative_must_be_scope_conditioned(self) -> None:
|
|
topic = workflow_topic(
|
|
conditions=(
|
|
DocumentationCondition(required_scopes=("example:item:read",)),
|
|
DocumentationCondition(required_modules=("example",)),
|
|
)
|
|
)
|
|
|
|
with self.assertRaisesRegex(RegistryError, r"unscoped alternative\(s\): 2"):
|
|
registry_for(topic).validate()
|
|
|
|
def test_scoped_user_workflow_and_non_user_topics_are_accepted(self) -> None:
|
|
scoped = workflow_topic(
|
|
conditions=(
|
|
DocumentationCondition(required_scopes=("example:item:read",)),
|
|
DocumentationCondition(any_scopes=("example:item:write", "example:item:admin")),
|
|
)
|
|
)
|
|
admin_workflow = workflow_topic(
|
|
conditions=(),
|
|
documentation_types=("admin",),
|
|
)
|
|
user_reference = workflow_topic(conditions=(), kind="reference")
|
|
|
|
self.assertEqual(user_workflow_scope_condition_issues(scoped), ())
|
|
self.assertEqual(user_workflow_scope_condition_issues(admin_workflow), ())
|
|
self.assertEqual(user_workflow_scope_condition_issues(user_reference), ())
|
|
registry_for(scoped, admin_workflow, user_reference).validate()
|
|
|
|
def test_versioned_structured_translation_preserves_metadata_shape(self) -> None:
|
|
topic = DocumentationTopic(
|
|
id="example.workflow.localized",
|
|
title="Run task",
|
|
summary="Run the task.",
|
|
metadata={
|
|
"kind": "workflow",
|
|
"steps": ["Review", "Execute"],
|
|
"verification": "Confirm the result.",
|
|
},
|
|
structured_translation_version="1",
|
|
structured_translations={
|
|
"de": {
|
|
"steps": ["Prüfen", "Ausführen"],
|
|
"verification": "Das Ergebnis bestätigen.",
|
|
}
|
|
},
|
|
)
|
|
|
|
registry_for(topic).validate()
|
|
self.assertEqual(
|
|
["Prüfen", "Ausführen"],
|
|
localized_documentation_metadata(topic, "de")["steps"],
|
|
)
|
|
self.assertEqual(
|
|
"workflow", localized_documentation_metadata(topic, "de")["kind"]
|
|
)
|
|
|
|
def test_structured_translation_requires_version_and_complete_shape(self) -> None:
|
|
missing_version = DocumentationTopic(
|
|
id="example.localized.missing-version",
|
|
title="Localized",
|
|
summary="Invalid contract.",
|
|
metadata={"limitations": ["One", "Two"]},
|
|
structured_translations={"de": {"limitations": ["Eins", "Zwei"]}},
|
|
)
|
|
with self.assertRaisesRegex(
|
|
RegistryError, "require structured_translation_version"
|
|
):
|
|
registry_for(missing_version).validate()
|
|
|
|
incomplete_shape = DocumentationTopic(
|
|
id="example.localized.incomplete",
|
|
title="Localized",
|
|
summary="Invalid shape.",
|
|
metadata={"limitations": ["One", "Two"]},
|
|
structured_translation_version="1",
|
|
structured_translations={"de": {"limitations": ["Eins"]}},
|
|
)
|
|
with self.assertRaisesRegex(RegistryError, "preserve list length"):
|
|
registry_for(incomplete_shape).validate()
|
|
|
|
def test_manifest_helper_merges_and_validates_owner_translations(self) -> None:
|
|
topic = DocumentationTopic(
|
|
id="example.workflow.localized",
|
|
title="Run task",
|
|
summary="Run the task.",
|
|
metadata={"steps": ["Review", "Execute"]},
|
|
)
|
|
manifest = ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation=(topic,),
|
|
)
|
|
|
|
localized = with_documentation_structured_translations(
|
|
manifest,
|
|
locale="de",
|
|
translations={
|
|
topic.id: {"steps": ["Prüfen", "Ausführen"]},
|
|
},
|
|
)
|
|
|
|
self.assertEqual(
|
|
["Prüfen", "Ausführen"],
|
|
localized.documentation[0].structured_translations["de"]["steps"],
|
|
)
|
|
with self.assertRaisesRegex(ValueError, "unknown topic ids"):
|
|
with_documentation_structured_translations(
|
|
manifest,
|
|
locale="de",
|
|
translations={"missing.topic": {"steps": ["Prüfen", "Ausführen"]}},
|
|
)
|
|
with self.assertRaisesRegex(ValueError, "preserve list length"):
|
|
with_documentation_structured_translations(
|
|
manifest,
|
|
locale="de",
|
|
translations={topic.id: {"steps": ["Prüfen"]}},
|
|
)
|
|
|
|
def test_documentation_configuration_and_source_extensions_are_validated(self) -> None:
|
|
resolver = lambda _context, keys: { # noqa: E731
|
|
key: DocumentationConfigurationDecision(key=key, state="enabled")
|
|
for key in keys
|
|
}
|
|
registry = PlatformRegistry()
|
|
registry.register(ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation_configuration_providers=(
|
|
DocumentationConfigurationProviderRegistration(
|
|
keys=("example.feature",),
|
|
resolve=resolver,
|
|
),
|
|
),
|
|
documentation_sources=(
|
|
DocumentationSourceDefinition(
|
|
id="example.handbook",
|
|
kind="repository",
|
|
label="Example handbook",
|
|
),
|
|
),
|
|
))
|
|
|
|
registry.validate()
|
|
|
|
duplicate = PlatformRegistry()
|
|
duplicate.register(ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation_configuration_providers=(
|
|
DocumentationConfigurationProviderRegistration(
|
|
keys=("example.feature",),
|
|
resolve=resolver,
|
|
),
|
|
DocumentationConfigurationProviderRegistration(
|
|
keys=("example.feature",),
|
|
resolve=resolver,
|
|
),
|
|
),
|
|
))
|
|
with self.assertRaisesRegex(RegistryError, "duplicate documentation configuration key"):
|
|
duplicate.validate()
|
|
|
|
def test_capability_documentation_is_typed_and_must_match_a_provider(self) -> None:
|
|
registry = PlatformRegistry()
|
|
registry.register(ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
capability_factories={"example.lookup": lambda _context: object()},
|
|
capability_documentation={
|
|
"example.lookup": CapabilityDocumentation(
|
|
label="Example lookup",
|
|
summary="Resolves example records without exposing provider internals.",
|
|
contract_version="2",
|
|
audience=("module_admin",),
|
|
),
|
|
},
|
|
))
|
|
registry.validate()
|
|
|
|
missing_provider = PlatformRegistry()
|
|
missing_provider.register(ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
capability_documentation={
|
|
"example.lookup": CapabilityDocumentation(
|
|
label="Example lookup",
|
|
summary="Resolves example records.",
|
|
),
|
|
},
|
|
))
|
|
with self.assertRaisesRegex(RegistryError, "does not provide it"):
|
|
missing_provider.validate()
|
|
|
|
|
|
def registry_for(*topics: DocumentationTopic) -> PlatformRegistry:
|
|
registry = PlatformRegistry()
|
|
registry.register(
|
|
ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation=topics,
|
|
)
|
|
)
|
|
return registry
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|