from __future__ import annotations import unittest from types import SimpleNamespace from govoplan_core.core.access import ( CAPABILITY_ACCESS_DIRECTORY, AccountRef, GroupRef, UserRef, ) from govoplan_core.core.references import ( CAPABILITY_ACCESS_REFERENCE_OPTIONS, ReferenceOption, ReferenceSearchPage, access_scope_reference_page, access_scope_reference_options, validate_access_scope_reference, ) class _Directory: users = ( UserRef( id="membership-1", account_id="account-1", tenant_id="tenant-1", email="ada@example.test", display_name="Ada", ), UserRef( id="membership-2", account_id="account-2", tenant_id="tenant-1", email="inactive@example.test", display_name="Inactive", status="inactive", ), ) groups = ( GroupRef(id="group-1", tenant_id="tenant-1", name="Finance"), GroupRef( id="group-2", tenant_id="tenant-1", name="Former team", status="inactive", ), ) def get_account(self, account_id): if account_id not in {"account-1", "account-2"}: return None return AccountRef(id=account_id, email=f"{account_id}@example.test") def get_user(self, user_id): return next((item for item in self.users if item.id == user_id), None) def get_users(self, user_ids): return {item.id: item for item in self.users if item.id in set(user_ids)} def users_for_tenant(self, tenant_id): return tuple(item for item in self.users if item.tenant_id == tenant_id) def get_group(self, group_id): return next((item for item in self.groups if item.id == group_id), None) def get_groups(self, group_ids): return {item.id: item for item in self.groups if item.id in set(group_ids)} def groups_for_tenant(self, tenant_id): return tuple(item for item in self.groups if item.tenant_id == tenant_id) def groups_for_user(self, user_id, *, tenant_id): del user_id return self.groups_for_tenant(tenant_id) def display_label(self, subject): return subject.label class _Registry: directory = _Directory() def has_capability(self, name): return name == CAPABILITY_ACCESS_DIRECTORY def capability(self, name): return self.directory if self.has_capability(name) else None class _ReferenceProvider: def __init__(self) -> None: self.requests = [] def search_reference_options(self, session, principal, *, request): del session, principal self.requests.append(request) return ReferenceSearchPage( options=( ReferenceOption( value="account-1", label="Ada", kind="user", source_module="access", ), ), next_cursor="offset:1", has_more=True, ) class _ProviderRegistry: def __init__(self) -> None: self.provider = _ReferenceProvider() def has_capability(self, name): return name == CAPABILITY_ACCESS_REFERENCE_OPTIONS def capability(self, name): return self.provider if self.has_capability(name) else None class ReferenceOptionTests(unittest.TestCase): def setUp(self) -> None: self.principal = SimpleNamespace( tenant_id="tenant-1", account_id="account-1", display_name="Ada", email="ada@example.test", group_ids=frozenset({"group-1"}), ) def test_user_values_are_canonical_account_ids(self) -> None: options = access_scope_reference_options( _Registry(), self.principal, scope_type="user", administrative=True, ) self.assertEqual(["account-1", "account-2"], [item.value for item in options]) self.assertEqual("membership-1", options[0].provenance["membership_id"]) self.assertTrue(options[1].disabled) self.assertEqual("inactive", options[1].availability) def test_unavailable_selected_values_remain_readable(self) -> None: options = access_scope_reference_options( _Registry(), self.principal, scope_type="group", query="missing", selected_values=("removed-group",), administrative=True, ) self.assertEqual(1, len(options)) self.assertEqual("removed-group", options[0].value) self.assertEqual("unavailable", options[0].availability) self.assertTrue(options[0].disabled) def test_non_administrators_only_receive_permitted_scope_targets(self) -> None: users = access_scope_reference_options( _Registry(), self.principal, scope_type="user", administrative=False, ) groups = access_scope_reference_options( _Registry(), self.principal, scope_type="group", administrative=False, ) self.assertEqual(["account-1"], [item.value for item in users]) self.assertEqual(["group-1"], [item.value for item in groups]) def test_inactive_targets_are_rejected_but_existing_values_survive(self) -> None: with self.assertRaisesRegex(ValueError, "Inactive groups"): validate_access_scope_reference( _Registry(), tenant_id="tenant-1", scope_type="group", scope_id="group-2", ) self.assertEqual( "group-2", validate_access_scope_reference( _Registry(), tenant_id="tenant-1", scope_type="group", scope_id="group-2", preserve_existing="group-2", ), ) def test_missing_optional_access_provider_keeps_reduced_mode(self) -> None: options = access_scope_reference_options( None, self.principal, scope_type="user", ) self.assertEqual("account-1", options[0].value) self.assertEqual("core", options[0].source_module) def test_bounded_provider_receives_normalized_search_and_retains_stale_selection(self) -> None: registry = _ProviderRegistry() page = access_scope_reference_page( registry, self.principal, scope_type="user", query=" Ada ", selected_values=("removed-account",), limit=500, cursor="offset:20", administrative=True, session=object(), ) request = registry.provider.requests[0] self.assertEqual("ada", request.query) self.assertEqual(200, request.limit) self.assertEqual("offset:20", request.cursor) self.assertTrue(request.context["administrative"]) self.assertEqual( ["account-1", "removed-account"], [option.value for option in page.options], ) self.assertEqual("unavailable", page.options[1].availability) self.assertTrue(page.has_more) self.assertEqual("offset:1", page.next_cursor) if __name__ == "__main__": unittest.main()