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()