feat: define bulk governance projection contract

This commit is contained in:
2026-08-20 19:46:53 +02:00
parent 604f20eed7
commit 8e687c4420
3 changed files with 212 additions and 0 deletions
+61
View File
@@ -5506,6 +5506,67 @@ class ApiSmokeTests(unittest.TestCase):
self.assertEqual(deleted_delta.status_code, 200, deleted_delta.text)
self.assertTrue(any(item["id"] == template_id and item["resource_type"] == "governance_template" for item in deleted_delta.json()["deleted"]))
def test_governance_template_bulk_synchronization_previews_and_applies(self) -> None:
headers, login = self._login()
tenant_id = login["tenant"]["id"]
approver_headers = self._create_system_approver(
headers,
tenant_id=tenant_id,
email="governance-sync-approver@example.local",
)
create_payload = {
"kind": "role",
"slug": "governance-sync",
"name": "Governance Sync",
"description": "Bulk reconciliation contract",
"permissions": ["admin:roles:read"],
"is_active": True,
"assignments": [{"tenant_id": tenant_id, "mode": "required"}],
}
change_request_id = self._approved_configuration_change(
headers,
approver_headers,
key="governance_templates",
value=create_payload,
target={"kind": "role", "slug": "governance-sync"},
)
created = self.client.post(
"/api/v1/admin/system/governance-templates",
headers=headers,
json={**create_payload, "change_request_id": change_request_id},
)
self.assertEqual(201, created.status_code, created.text)
template_id = created.json()["id"]
preview = self.client.post(
"/api/v1/admin/system/governance-templates/synchronize",
headers=headers,
json={"template_ids": [template_id], "dry_run": True},
)
self.assertEqual(200, preview.status_code, preview.text)
self.assertTrue(preview.json()["dry_run"])
self.assertEqual({"unchanged": 1}, preview.json()["counts"])
self.assertEqual("1", preview.json()["version"])
self.assertEqual("admin.bulk-synchronization", preview.json()["outcomes"][0]["provenance"]["source"])
applied = self.client.post(
"/api/v1/admin/system/governance-templates/synchronize",
headers=headers,
json={"template_ids": [template_id], "dry_run": False},
)
self.assertEqual(200, applied.status_code, applied.text)
self.assertFalse(applied.json()["dry_run"])
self.assertEqual({"unchanged": 1}, applied.json()["counts"])
audit = self.client.get(
"/api/v1/admin/audit",
headers=headers,
params={"all_tenants": True, "limit": 500},
)
actions = {item["action"] for item in audit.json()["items"]}
self.assertIn("governance_template.synchronization_previewed", actions)
self.assertIn("governance_template.synchronized", actions)
def test_module_installer_history_supports_cursor_windows(self) -> None:
from govoplan_core.core.module_installer import default_installer_runtime_dir
@@ -0,0 +1,61 @@
from __future__ import annotations
import unittest
from govoplan_core.core.access import (
AccessGovernanceProjectionV1,
GovernanceProjectionBatch,
GovernanceProjectionCommand,
GovernanceProjectionResult,
GovernanceTemplateMaterialization,
)
def _command(assignment_id: str) -> GovernanceProjectionCommand:
return GovernanceProjectionCommand(
assignment_id=assignment_id,
operation="upsert",
template=GovernanceTemplateMaterialization(
template_id="template-1",
kind="role",
tenant_id=f"tenant-{assignment_id}",
slug="reader",
name="Reader",
),
provenance={"source": "contract-test"},
)
class _Projection:
def reconcile(self, session: object, batch: GovernanceProjectionBatch) -> GovernanceProjectionResult:
del session
return GovernanceProjectionResult(
operation_id=batch.operation_id,
outcomes=(),
dry_run=batch.dry_run,
)
class GovernanceProjectionContractTests(unittest.TestCase):
def test_protocol_is_runtime_checkable(self) -> None:
self.assertIsInstance(_Projection(), AccessGovernanceProjectionV1)
def test_batch_rejects_duplicate_assignment_ids(self) -> None:
with self.assertRaisesRegex(ValueError, "unique"):
GovernanceProjectionBatch(
operation_id="duplicate",
commands=(_command("same"), _command("same")),
)
def test_batch_enforces_bounds(self) -> None:
with self.assertRaisesRegex(ValueError, "between 1 and 500"):
GovernanceProjectionBatch(operation_id="empty", commands=())
with self.assertRaisesRegex(ValueError, "between 1 and 500"):
GovernanceProjectionBatch(
operation_id="large",
commands=tuple(_command(str(index)) for index in range(501)),
)
if __name__ == "__main__":
unittest.main()