from types import SimpleNamespace import unittest from unittest.mock import Mock, patch from fastapi import HTTPException from govoplan_core.core.identity import CAPABILITY_IDENTITY_DIRECTORY, IdentityDirectory from govoplan_idm.backend.api.v1 import function_changes, relationships, routes class DirectoryDependencyTests(unittest.TestCase): def test_identical_routes_preserve_missing_invalid_and_success_contracts(self) -> None: valid = Mock(spec=IdentityDirectory) for route in (relationships, routes): for registry, expected_status, expected_detail in ( (None, 503, "Identity directory is unavailable"), (Mock(has_capability=Mock(return_value=False)), 503, "Identity directory is unavailable"), (Mock(has_capability=Mock(return_value=True), require_capability=Mock(return_value=object())), 500, f"Invalid capability: {CAPABILITY_IDENTITY_DIRECTORY}"), ): with self.subTest(route=route.__name__, status=expected_status), patch.object(route, "get_registry", return_value=registry): with self.assertRaises(HTTPException) as caught: route._identity_directory() self.assertEqual(expected_status, caught.exception.status_code) self.assertEqual(expected_detail, caught.exception.detail) registry = Mock(has_capability=Mock(return_value=True), require_capability=Mock(return_value=valid)) with patch.object(route, "get_registry", return_value=registry): self.assertIs(valid, route._identity_directory()) registry.has_capability.assert_called_once_with(CAPABILITY_IDENTITY_DIRECTORY) registry.require_capability.assert_called_once_with(CAPABILITY_IDENTITY_DIRECTORY) def test_each_call_resolves_current_registry_without_caching_authority(self) -> None: for route in (relationships, routes): valid = Mock(spec=IdentityDirectory) registry = Mock(has_capability=Mock(return_value=True), require_capability=Mock(return_value=valid)) with patch.object(route, "get_registry", side_effect=[registry, None]) as get_registry: self.assertIs(valid, route._identity_directory()) with self.assertRaises(HTTPException) as caught: route._identity_directory() self.assertEqual(503, caught.exception.status_code) self.assertEqual(2, get_registry.call_count) def test_lookup_failure_is_not_silently_replaced_or_retried(self) -> None: failure = RuntimeError("registry changed during lookup") for route in (relationships, routes): registry = Mock(has_capability=Mock(return_value=True), require_capability=Mock(side_effect=failure)) with patch.object(route, "get_registry", return_value=registry), self.assertRaises(RuntimeError) as caught: route._identity_directory() self.assertIs(failure, caught.exception) self.assertEqual(1, registry.require_capability.call_count) def test_function_changes_keeps_its_distinct_unavailable_contract(self) -> None: registry = SimpleNamespace(capability=lambda _name: object()) with patch.object(function_changes, "get_registry", return_value=registry), self.assertRaises(HTTPException) as caught: function_changes._identity_directory() self.assertEqual(503, caught.exception.status_code) self.assertEqual("The Identity directory is unavailable.", caught.exception.detail) if __name__ == "__main__": unittest.main()