intermittent commit
This commit is contained in:
132
tests/test_assignment_workflow.py
Normal file
132
tests/test_assignment_workflow.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from types import SimpleNamespace
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from govoplan_idm.backend.api.v1.routes import _ensure_assignment_shape, _ensure_assignment_source_rules
|
||||
|
||||
|
||||
def assignment(**overrides: object) -> SimpleNamespace:
|
||||
values = {
|
||||
"id": "assignment-1",
|
||||
"identity_id": "identity-1",
|
||||
"account_id": "account-1",
|
||||
"function_id": "function-1",
|
||||
"source": "direct",
|
||||
"delegated_from_assignment_id": None,
|
||||
"acting_for_account_id": None,
|
||||
"valid_from": None,
|
||||
"valid_until": None,
|
||||
}
|
||||
values.update(overrides)
|
||||
return SimpleNamespace(**values)
|
||||
|
||||
|
||||
def function(**overrides: object) -> SimpleNamespace:
|
||||
values = {"delegable": True, "act_in_place_allowed": True}
|
||||
values.update(overrides)
|
||||
return SimpleNamespace(**values)
|
||||
|
||||
|
||||
class AssignmentWorkflowTests(unittest.TestCase):
|
||||
def assert_invalid(self, expected_detail: str, callback: object) -> None:
|
||||
with self.assertRaises(HTTPException) as captured:
|
||||
callback()
|
||||
self.assertEqual(captured.exception.detail, expected_detail)
|
||||
|
||||
def test_direct_assignment_accepts_plain_shape(self) -> None:
|
||||
item = assignment()
|
||||
|
||||
_ensure_assignment_shape(item) # type: ignore[arg-type]
|
||||
_ensure_assignment_source_rules( # type: ignore[arg-type]
|
||||
item,
|
||||
function=function(),
|
||||
base=None,
|
||||
account_linked_to_identity=lambda _identity_id, _account_id: False,
|
||||
)
|
||||
|
||||
def test_shape_rejects_backwards_validity_window(self) -> None:
|
||||
now = datetime.now(timezone.utc)
|
||||
item = assignment(valid_from=now, valid_until=now - timedelta(days=1))
|
||||
|
||||
self.assert_invalid("Valid until must be after valid from.", lambda: _ensure_assignment_shape(item)) # type: ignore[arg-type]
|
||||
|
||||
def test_shape_rejects_self_delegation(self) -> None:
|
||||
item = assignment(id="assignment-1", delegated_from_assignment_id="assignment-1")
|
||||
|
||||
self.assert_invalid("A function assignment cannot delegate from itself.", lambda: _ensure_assignment_shape(item)) # type: ignore[arg-type]
|
||||
|
||||
def test_delegated_assignment_accepts_different_target(self) -> None:
|
||||
base = assignment(id="source-1", identity_id="identity-1", account_id="account-1")
|
||||
item = assignment(
|
||||
id="assignment-2",
|
||||
identity_id="identity-2",
|
||||
account_id="account-2",
|
||||
source="delegated",
|
||||
delegated_from_assignment_id="source-1",
|
||||
)
|
||||
|
||||
_ensure_assignment_source_rules( # type: ignore[arg-type]
|
||||
item,
|
||||
function=function(delegable=True),
|
||||
base=base,
|
||||
account_linked_to_identity=lambda _identity_id, _account_id: False,
|
||||
)
|
||||
|
||||
def test_delegated_assignment_rejects_same_target(self) -> None:
|
||||
base = assignment(id="source-1", identity_id="identity-1", account_id="account-1")
|
||||
item = assignment(source="delegated", delegated_from_assignment_id="source-1")
|
||||
|
||||
self.assert_invalid(
|
||||
"A delegated assignment must target another identity or account.",
|
||||
lambda: _ensure_assignment_source_rules( # type: ignore[arg-type]
|
||||
item,
|
||||
function=function(delegable=True),
|
||||
base=base,
|
||||
account_linked_to_identity=lambda _identity_id, _account_id: False,
|
||||
),
|
||||
)
|
||||
|
||||
def test_acting_for_assignment_accepts_identity_linked_account(self) -> None:
|
||||
base = assignment(id="source-1", identity_id="identity-1", account_id=None)
|
||||
item = assignment(
|
||||
id="assignment-2",
|
||||
source="acting_for",
|
||||
delegated_from_assignment_id="source-1",
|
||||
account_id="acting-account",
|
||||
acting_for_account_id="represented-account",
|
||||
)
|
||||
|
||||
_ensure_assignment_source_rules( # type: ignore[arg-type]
|
||||
item,
|
||||
function=function(act_in_place_allowed=True),
|
||||
base=base,
|
||||
account_linked_to_identity=lambda identity_id, account_id: identity_id == "identity-1" and account_id == "represented-account",
|
||||
)
|
||||
|
||||
def test_acting_for_assignment_rejects_unlinked_represented_account(self) -> None:
|
||||
base = assignment(id="source-1", identity_id="identity-1", account_id=None)
|
||||
item = assignment(
|
||||
id="assignment-2",
|
||||
source="acting_for",
|
||||
delegated_from_assignment_id="source-1",
|
||||
account_id="acting-account",
|
||||
acting_for_account_id="represented-account",
|
||||
)
|
||||
|
||||
self.assert_invalid(
|
||||
"Acting-for account must belong to the source assignment identity.",
|
||||
lambda: _ensure_assignment_source_rules( # type: ignore[arg-type]
|
||||
item,
|
||||
function=function(act_in_place_allowed=True),
|
||||
base=base,
|
||||
account_linked_to_identity=lambda _identity_id, _account_id: False,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user