"""ows_pdp unit tests.""" import uuid from typing import Any from unittest.mock import MagicMock, patch import httpx import pytest import tenacity from owsclient.test import OwsClientMock from backfill import config from backfill.cli.backfill import CorrelationIdGetter from backfill.connectors.ows_pdp.models.attach_detach_roles_request import ( AttachDetachRolesRequest, ) from backfill.connectors.ows_pdp.models.pagination_cursor import PaginationCursor from backfill.connectors.ows_pdp.models.role import Role from backfill.connectors.ows_pdp.models.roles_response import RolesResponse from backfill.connectors.ows_pdp.models.tenant_roles import TenantRoles from backfill.connectors.ows_pdp.models.tenant_type import TenantType from backfill.connectors.ows_pdp.ows_pdp import OwsPdpClient, get_ows_pdp_connector @pytest.fixture() def identity_uuid() -> uuid.UUID: """Return reusable identity_uuid.""" return uuid.uuid4() @pytest.fixture() def tenant_uuid() -> uuid.UUID: """Return reusable tenant_uuid.""" return uuid.uuid1() @pytest.fixture() def mock_attach_detach_request(tenant_uuid: uuid.UUID) -> dict[str, Any]: return { "tenant_type": "account", "tenant_uuid": str(tenant_uuid), "roles_to_attach": [{"role": "add_me"}], "roles_to_detach": [{"role": "take_me_away"}], } def test_attach_detach_roles_by_identity_tenant( identity_uuid: uuid.UUID, tenant_uuid: uuid.UUID, mock_attach_detach_request: dict[str, Any], ows_pdp_client: OwsPdpClient, ows_client_mock: OwsClientMock, ) -> None: """Test attach_detach_roles_by_identity_tenant.""" mock_response = { "cursor": {"cursor": None, "shorthand": None}, "errors": {}, "tenants": { str(tenant_uuid): { "tenant_type": "account", "tenant_uuid": str(tenant_uuid), "roles": [{"role": "add_me"}], } }, } ows_client_mock.put( "ows-pdp", path=f"/identity/{identity_uuid}/tenant/{tenant_uuid}/attach-and-detach/roles/", json=mock_attach_detach_request, ).mock(return_value=httpx.Response(200, json=mock_response)) adrr = AttachDetachRolesRequest.from_dict(mock_attach_detach_request) assert adrr response = ows_pdp_client.attach_detach_roles_by_identity_tenant( identity_uuid, adrr ) if response is not None: assert ( response.to_dict() == RolesResponse( cursor=PaginationCursor(shorthand=None, cursor=None), errors={}, tenants={ str(tenant_uuid): TenantRoles( tenant_type=TenantType.ACCOUNT, tenant_uuid=str(tenant_uuid), roles=[Role(role="add_me")], ) }, ).to_dict() ) else: pytest.fail("RolesResponse is None") @pytest.mark.parametrize( "status_code", [ (400), (401), (500), ], ) def test_attach_detach_roles_by_identity_tenant_immediate_failure( status_code: int, identity_uuid: uuid.UUID, tenant_uuid: uuid.UUID, mock_attach_detach_request: dict[str, Any], ows_pdp_client: OwsPdpClient, ows_client_mock: OwsClientMock, ) -> None: """Test attach_detach_roles_by_identity_tenant immediately fails.""" ows_client_mock.put( "ows-pdp", path=f"/identity/{identity_uuid}/tenant/{tenant_uuid}/attach-and-detach/roles/", json=mock_attach_detach_request, ).mock( return_value=httpx.Response( status_code, text="sometimes retry is not appropriate", ) ) adrr = AttachDetachRolesRequest.from_dict(mock_attach_detach_request) assert adrr with pytest.raises(httpx.RequestError): ows_pdp_client.attach_detach_roles_by_identity_tenant(identity_uuid, adrr) @patch( "backfill.connectors.ows_pdp.ows_pdp.wait_random_exponential.__call__", MagicMock() ) def test_attach_detach_roles_by_identity_tenant_retries_and_succeeds( identity_uuid: uuid.UUID, tenant_uuid: uuid.UUID, mock_attach_detach_request: dict[str, Any], ows_pdp_client: OwsPdpClient, ows_client_mock: OwsClientMock, ) -> None: """Test attach_detach_roles_by_identity_tenant retries and finally succeeds.""" ows_client_mock.put( "ows-pdp", path=f"/identity/{identity_uuid}/tenant/{tenant_uuid}/attach-and-detach/roles/", json=mock_attach_detach_request, ).mock( return_value=httpx.Response( 502, text="Retryable 50x", ) ).mock( return_value=httpx.Response( 503, text="Retryable 50x", ) ).mock( return_value=httpx.Response( 504, text="Retryable 50x", ) ).mock( return_value=httpx.Response( 200, json={ "cursor": {"cursor": None, "shorthand": None}, "errors": {}, "tenants": { str(tenant_uuid): { "tenant_type": "account", "tenant_uuid": str(tenant_uuid), "roles": [{"role": "add_me"}], } }, }, ) ) adrr = AttachDetachRolesRequest.from_dict(mock_attach_detach_request) assert adrr response = ows_pdp_client.attach_detach_roles_by_identity_tenant( identity_uuid, adrr ) if response is not None: assert ( response.to_dict() == RolesResponse( cursor=PaginationCursor(shorthand=None, cursor=None), errors={}, tenants={ str(tenant_uuid): TenantRoles( tenant_type=TenantType.ACCOUNT, tenant_uuid=str(tenant_uuid), roles=[Role(role="add_me")], ) }, ).to_dict() ) else: pytest.fail("RolesResponse is None") @patch( "backfill.connectors.ows_pdp.ows_pdp.wait_random_exponential.__call__", MagicMock() ) def test_attach_detach_roles_by_identity_tenant_retries_and_fails( identity_uuid: uuid.UUID, tenant_uuid: uuid.UUID, mock_attach_detach_request: dict[str, Any], ows_pdp_client: OwsPdpClient, ows_client_mock: OwsClientMock, ) -> None: """Test attach_detach_roles_by_identity_tenant retries and stops.""" ows_client_mock.put( "ows-pdp", path=f"/identity/{identity_uuid}/tenant/{tenant_uuid}/attach-and-detach/roles/", json=mock_attach_detach_request, ).mock( return_value=httpx.Response( 502, text="Retryable 50x", ) ).mock( return_value=httpx.Response( 503, text="Retryable 50x", ) ).mock( return_value=httpx.Response( 504, text="Retryable 50x", ) ).mock( return_value=httpx.Response( 504, text="This is the last retry attempt and stops here" ) ) adrr = AttachDetachRolesRequest.from_dict(mock_attach_detach_request) assert adrr with pytest.raises(tenacity.RetryError): ows_pdp_client.attach_detach_roles_by_identity_tenant(identity_uuid, adrr) @patch("backfill.connectors.ows_pdp.ows_pdp.session") @patch("backfill.connectors.ows_pdp.ows_pdp.SecretsManager") @patch("backfill.connectors.ows_pdp.ows_pdp.M2MTokenManager") @patch("backfill.connectors.ows_pdp.ows_pdp.OwsClient") def test_get_ows_pdp_connector( mock_ows_client: MagicMock, mock_m2m_token_manager: MagicMock, mock_secrets_manager: MagicMock, mock_aws_session: MagicMock, backfill_uuid_getter: CorrelationIdGetter, backfill_uuid: str, ) -> None: """Test the ows-pdp connector factory function.""" mock_session_instance = mock_aws_session.Session.return_value mock_secrets_manager_instance = mock_secrets_manager.return_value m2m_token_manager_instance = mock_m2m_token_manager.return_value mock_ows_client_instance = mock_ows_client.return_value ows_pdp_client = get_ows_pdp_connector( environment="test", correlation_id_getter=backfill_uuid_getter ) assert ows_pdp_client mock_secrets_manager.assert_called_once_with( session=mock_session_instance, ) mock_m2m_token_manager.assert_called_once_with( secrets_manager=mock_secrets_manager_instance, environment="test", service_name=config.SERVICE_NAME, ) mock_ows_client.assert_called_once_with( environment="test", service_name=config.SERVICE_NAME, m2m_token_manager=m2m_token_manager_instance, correlation_id_getter=backfill_uuid_getter, ) assert ows_pdp_client.ows_client == mock_ows_client_instance