Files
govoplan-core/tests/test_configuration_package_architecture.py
zemion 1c3ee9e8c7
Module Package Release / publish-packages (push) Successful in 13s
Release Core v0.1.35 configuration package safeguards
2026-08-22 18:05:33 +02:00

367 lines
13 KiB
Python

from __future__ import annotations
import unittest
from govoplan_core.core.configuration_packages import (
ConfigurationApplyResult,
ConfigurationExportResult,
ConfigurationExportSelection,
ConfigurationModuleRequirement,
ConfigurationPackageFragment,
ConfigurationPackageEvidence,
ConfigurationPackageManifest,
ConfigurationPackageParent,
ConfigurationPlanItem,
ConfigurationPreflightContext,
ConfigurationPreflightResult,
ConfigurationProviderExpectation,
ConfigurationRequiredData,
apply_configuration_package,
dry_run_configuration_package,
export_configuration_package,
validate_configuration_package_derivation,
)
def _evidence(*kinds: str) -> tuple[ConfigurationPackageEvidence, ...]:
return tuple(
ConfigurationPackageEvidence(
kind=kind, # type: ignore[arg-type]
reference=f"evidence/{kind}.json",
summary=f"Evidence for {kind}.",
checksum=f"sha256:{'a' * 64}",
)
for kind in kinds
)
class ConfigurationPackageArchitectureTests(unittest.TestCase):
def test_deployment_data_references_are_declared_resolved_and_never_exported(self) -> None:
class Provider:
module_id = "forms"
def __init__(self) -> None:
self.preflight_payloads: list[dict[str, object]] = []
def describe(self):
from govoplan_core.core.configuration_packages import ConfigurationProviderDescription
return ConfigurationProviderDescription(
module_id=self.module_id,
fragment_types=("definition",),
)
def preflight(self, fragment, context):
del context
self.preflight_payloads.append(dict(fragment.payload))
return ConfigurationPreflightResult(plan=(ConfigurationPlanItem(
action="create",
module_id=self.module_id,
fragment_type=fragment.fragment_type,
fragment_id=fragment.fragment_id,
),))
def apply(self, fragment, supplied_data, context):
del supplied_data, context
return ConfigurationApplyResult(
created_refs={fragment.fragment_id or "definition": "form:resident-parking"}
)
def export(self, selection, context):
del selection, context
return ConfigurationExportResult(
fragments=(ConfigurationPackageFragment(
module_id=self.module_id,
fragment_type="definition",
payload={"name": "Resident parking permit"},
),),
data_requirements=(ConfigurationRequiredData(
key="payment_credential_ref",
label="Payment credential reference",
secret=True,
),),
)
def health(self, import_result, context):
del import_result, context
return ()
provider = Provider()
package = ConfigurationPackageManifest(
package_id="product.resident-parking",
name="Resident parking permit",
version="1.0.0",
required_modules=(ConfigurationModuleRequirement("forms"),),
data_requirements=({
"key": "service_name",
"label": "Public service name",
},),
fragments=(ConfigurationPackageFragment(
module_id="forms",
fragment_type="definition",
fragment_id="resident-parking",
payload={
"definition": {
"title": {"$data": "service_name"},
}
},
),),
)
missing_context = ConfigurationPreflightContext(
installed_modules={"forms": "0.1.0"},
)
missing = dry_run_configuration_package(package, (provider,), missing_context)
self.assertEqual([], provider.preflight_payloads)
self.assertIn(
"fragment_data_reference_missing",
{item.code for item in missing.diagnostics},
)
ready_context = ConfigurationPreflightContext(
installed_modules={"forms": "0.1.0"},
supplied_data={"service_name": "Anwohnerparkausweis"},
operator_user_id="operator-1",
)
ready = dry_run_configuration_package(package, (provider,), ready_context)
applied = apply_configuration_package(package, (provider,), ready_context)
exported = export_configuration_package(
(provider,),
ConfigurationExportSelection(
tenant_id="tenant-1",
module_ids=("forms",),
),
ready_context,
)
self.assertFalse(any(item.severity == "blocker" for item in ready.diagnostics))
self.assertEqual(
"Anwohnerparkausweis",
provider.preflight_payloads[-1]["definition"]["title"], # type: ignore[index]
)
self.assertIsNotNone(applied.rollback)
assert applied.rollback is not None
self.assertEqual("database_restore_required", applied.rollback.status)
self.assertIsNotNone(exported.provenance)
assert exported.provenance is not None
self.assertEqual("operator-1", exported.provenance.exporter_id)
self.assertEqual(
("payment_credential_ref",),
exported.provenance.redacted_secret_keys,
)
def test_legacy_package_defaults_to_product_and_round_trips(self) -> None:
package = ConfigurationPackageManifest.from_mapping(
{"package_id": "example", "name": "Example", "version": "1.0.0"}
)
self.assertEqual(package.package_class, "product")
self.assertEqual(
ConfigurationPackageManifest.from_mapping(package.to_dict()),
package,
)
def test_reference_package_requires_operational_evidence(self) -> None:
with self.assertRaisesRegex(ValueError, "accessibility"):
ConfigurationPackageManifest(
package_id="reference.example",
name="Example reference",
version="1.0.0",
package_class="reference",
evidence=_evidence("target_test", "recovery"),
)
def test_reference_evidence_requires_content_checksums(self) -> None:
with self.assertRaisesRegex(ValueError, "without checksums"):
ConfigurationPackageManifest(
package_id="reference.unbound",
name="Unbound reference",
version="1.0.0",
package_class="reference",
evidence=tuple(
ConfigurationPackageEvidence(
kind=kind, # type: ignore[arg-type]
reference=f"evidence/{kind}.json",
summary=f"{kind} evidence.",
checksum=(
None
if kind == "target_test"
else f"sha256:{'b' * 64}"
),
)
for kind in (
"target_test",
"recovery",
"security",
"operations",
"accessibility",
"privacy",
"documentation",
)
),
)
def test_integration_package_preflight_checks_provider_contract_and_health(self) -> None:
package = ConfigurationPackageManifest(
package_id="integration.example",
name="Example integration",
version="1.0.0",
package_class="integration",
evidence=_evidence(
"target_test", "recovery", "operations", "documentation"
),
provider_expectations=(
ConfigurationProviderExpectation(
provider_id="connectors.example",
authority_mode="external_mirror",
minimum_maturity="read",
),
),
)
healthy = dry_run_configuration_package(
package,
(),
ConfigurationPreflightContext(
external_provider_declarations={
"connectors.example": {
"authority_modes": ["external_mirror"],
"maturity": "synchronize",
}
},
external_provider_states={
"connectors.example": {"health": "healthy"}
},
),
)
unhealthy = dry_run_configuration_package(
package,
(),
ConfigurationPreflightContext(
external_provider_declarations={
"connectors.example": {
"authority_modes": ["external_authoritative"],
"maturity": "discover",
}
},
external_provider_states={
"connectors.example": {"health": "error"}
},
),
)
self.assertFalse(
any(item.severity == "blocker" for item in healthy.diagnostics)
)
self.assertEqual(
{
"external_provider_authority_mode_unsupported",
"external_provider_maturity_insufficient",
"external_provider_unhealthy",
},
{item.code for item in unhealthy.diagnostics},
)
def test_provider_preflight_selects_the_requested_runtime_binding(self) -> None:
package = ConfigurationPackageManifest(
package_id="integration.calendar",
name="Calendar integration",
version="1.0.0",
package_class="integration",
evidence=_evidence(
"target_test", "recovery", "operations", "documentation"
),
provider_expectations=(
ConfigurationProviderExpectation(
provider_id="calendar.caldav_sync",
authority_mode="governed_sync",
minimum_maturity="synchronize",
binding_ref="calendar:sync-source:required",
freshness_expectation="current",
recovery_expectation="ready",
),
),
)
context = ConfigurationPreflightContext(
external_provider_declarations={
"calendar.caldav_sync": {
"authority_modes": ["governed_sync"],
"maturity": "synchronize",
"behavior": {},
}
},
external_provider_states={
"calendar.caldav_sync": {
"health": "warning",
"bindings": [
{
"binding_ref": "calendar:sync-source:other",
"authority_mode": "governed_sync",
"health": "error",
"freshness": "stale",
"recovery": "attention",
},
{
"binding_ref": "calendar:sync-source:required",
"authority_mode": "governed_sync",
"health": "healthy",
"freshness": "current",
"recovery": "ready",
},
],
}
},
)
result = dry_run_configuration_package(package, (), context)
self.assertFalse(
any(item.severity == "blocker" for item in result.diagnostics)
)
def test_derived_package_may_tighten_but_not_remove_parent_constraints(self) -> None:
parent = ConfigurationPackageManifest(
package_id="product.base",
name="Base",
version="1.0.0",
required_modules=(
ConfigurationModuleRequirement("connectors", "1.0.0"),
),
required_capabilities=("connectors.profiles",),
provider_expectations=(
ConfigurationProviderExpectation(
provider_id="connectors.example",
authority_mode="external_mirror",
minimum_maturity="read",
),
),
)
child = ConfigurationPackageManifest(
package_id="sector.example",
name="Sector example",
version="1.0.0",
package_class="sector",
parents=(
ConfigurationPackageParent("product.base", "1.0.0"),
),
evidence=_evidence("documentation"),
)
codes = {
item.code
for item in validate_configuration_package_derivation(child, parent)
}
self.assertEqual(
{
"package_parent_module_constraint_loosened",
"package_parent_capability_constraint_loosened",
"package_parent_provider_constraint_removed",
},
codes,
)
if __name__ == "__main__":
unittest.main()