Implement governed function assignment workflows

This commit is contained in:
2026-07-31 19:40:27 +02:00
parent f025b0c25b
commit c14719d55a
16 changed files with 4079 additions and 27 deletions
+62 -1
View File
@@ -9,7 +9,11 @@ from govoplan_core.db.base import Base
from govoplan_core.db.session import configure_database, reset_database
from govoplan_identity.backend.db import models as identity_models # noqa: F401
from govoplan_idm.backend.assignment_lifecycle import SqlIdmAssignmentLifecycle
from govoplan_idm.backend.db.models import IdmOrganizationFunctionAssignment
from govoplan_idm.backend.db.models import (
IdmFunctionAssignmentChange,
IdmFunctionAssignmentChangeEvent,
IdmOrganizationFunctionAssignment,
)
from govoplan_organizations.backend.db import models as organization_models # noqa: F401
@@ -20,6 +24,8 @@ class AssignmentExpiryTests(unittest.TestCase):
self.database.engine,
tables=[
IdmOrganizationFunctionAssignment.__table__,
IdmFunctionAssignmentChange.__table__,
IdmFunctionAssignmentChangeEvent.__table__,
ChangeSequenceEntry.__table__,
],
)
@@ -143,6 +149,61 @@ class AssignmentExpiryTests(unittest.TestCase):
with self.subTest(limit=value), self.assertRaises(ValueError):
self.lifecycle.process_expired(session, limit=value)
def test_sweep_expires_open_governed_changes_once(self) -> None:
boundary = datetime(2026, 7, 31, 12, tzinfo=timezone.utc)
with self.database.session() as session:
session.add(
IdmFunctionAssignmentChange(
id="change-due",
tenant_id="tenant-1",
kind="request",
state="awaiting_holder",
profile="holder_grant",
function_id="function-1",
organization_unit_id="unit-1",
candidate_identity_id="identity-1",
initiator_account_id="account-1",
justification="Need the function",
evidence=[],
assignment_source="governance",
required_steps=["holder"],
completed_steps=[],
policy_decision={},
idempotency_key="request-1",
expires_at=boundary - timedelta(seconds=1),
metadata_={},
)
)
session.commit()
events: list[PlatformEvent] = []
bus = EventBus()
bus.subscribe("idm.function_change.expired.v1", events.append)
with self.database.SessionLocal() as session, event_bus_context(bus):
result = self.lifecycle.process_expired(
session,
tenant_id="tenant-1",
effective_at=boundary,
)
session.commit()
repeated = self.lifecycle.process_expired(
session,
tenant_id="tenant-1",
effective_at=boundary,
)
session.commit()
self.assertEqual(1, result["expired_changes"])
self.assertEqual(["change-due"], result["change_ids"])
self.assertEqual(0, repeated["expired_changes"])
self.assertEqual(1, len(events))
with self.database.session() as session:
change = session.get(IdmFunctionAssignmentChange, "change-due")
self.assertEqual("expired", change.state)
self.assertEqual(2, change.resource_revision)
history = session.query(IdmFunctionAssignmentChangeEvent).all()
self.assertEqual(["expired"], [item.action for item in history])
if __name__ == "__main__":
unittest.main()