"""Tests for internal handlers.""" from unittest import mock import pytest from flask import testing from permissions.constants import application, constants, parent_companies from permissions.handlers import internal_handlers # noqa: F401 from permissions.types import AdminIdentity, Tenant, TenantRolesInput, TenantType VALID_CREATE_BODY = { 'first_name': '🐶', 'last_name': '🦴', 'email': 'nugget@theorchard.com', 'brand': constants.THEORCHARD_BRAND, 'tenant': { 'tenant_type': constants.PARENT_COMPANY_TENANT_TYPE, 'tenant_uuid': parent_companies.ORCHARD_PARENT_COMPANY_UUID, }, 'roles_to_attach': [application.SETTINGS_BASE_ROLE, application.INSIGHTS_BASE_ROLE], } VALID_UPDATE_BODY = { 'roles_to_attach': [], 'roles_to_detach': [application.INSIGHTS_BASE_ROLE], 'tenant': { 'tenant_type': constants.PARENT_COMPANY_TENANT_TYPE, 'tenant_uuid': parent_companies.ORCHARD_PARENT_COMPANY_UUID, }, } @pytest.mark.parametrize( ['identity_id', 'pdp_check_result', 'expected_status'], [ pytest.param(None, None, 401, id='No valid JWT'), pytest.param('74165b07-c597-46ba-b64a-21ff3632e843', False, 403, id='PDP check failed'), pytest.param('74165b07-c597-46ba-b64a-21ff3632e843', True, 201, id='Valid request'), ], ) @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.user_notify') @mock.patch('permissions.handlers.internal_handlers.user_invite') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.logic.identity.get_identity_by_email') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_create_internal_identity_auth( mock_g: mock.MagicMock, pdp_check_mock: mock.MagicMock, get_by_email_mock: mock.MagicMock, _identity_model_mock: mock.MagicMock, user_invite_mock: mock.MagicMock, _user_notify_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, identity_id: str | None, pdp_check_result: bool | None, expected_status: int, fixture_client: testing.FlaskClient, ) -> None: """Test create_internal_identity endpoint authentication/authorization.""" mock_g.request_context.jwt_identity_id = identity_id pdp_check_mock.return_value = pdp_check_result get_by_email_mock.return_value = None tenant_logic_mock.check_compatibility_with_tenant_configuration.return_value = True user_invite_mock.create_employee.return_value = mock.Mock(id='new-identity-uuid') response = fixture_client.post('/internal/v2/identities', json=VALID_CREATE_BODY) assert response.status_code == expected_status if pdp_check_result is not None: pdp_check_mock.assert_called_once() else: pdp_check_mock.assert_not_called() @mock.patch('permissions.logic.identity.get_identity_by_email') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_create_internal_identity_conflict( mock_g: mock.MagicMock, pdp_check_mock: mock.MagicMock, get_by_email_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test create_internal_identity endpoint when identity with email already exists.""" mock_g.request_context.jwt_identity_id = '74165b07-c597-46ba-b64a-21ff3632e843' pdp_check_mock.return_value = True get_by_email_mock.return_value = {'id': 'existing-uuid', 'email': VALID_CREATE_BODY['email']} response = fixture_client.post('/internal/v2/identities', json=VALID_CREATE_BODY) assert response.status_code == 409 get_by_email_mock.assert_called_once_with(VALID_CREATE_BODY['email']) @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.logic.identity.get_identity_by_email') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_create_internal_identity_incompatible_roles( mock_g: mock.MagicMock, pdp_check_mock: mock.MagicMock, get_by_email_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test create_internal_identity endpoint when roles are incompatible with tenant config.""" mock_g.request_context.jwt_identity_id = '74165b07-c597-46ba-b64a-21ff3632e843' pdp_check_mock.return_value = True get_by_email_mock.return_value = None admin = mock.Mock() identity_model_mock.get_identity_by_id_new.return_value = admin identity_model_mock.get_identity_settings_profile.return_value = mock.Mock() tenant_logic_mock.check_compatibility_with_tenant_configuration.return_value = False request_body = { **VALID_CREATE_BODY, 'tenant': { 'tenant_type': constants.ACCOUNT_TENANT_TYPE, 'tenant_uuid': '94e951af-5ce4-4a2c-9f78-14d7b6a21331', }, 'roles_to_attach': [application.FANSIFTER_BASE_ROLE], } response = fixture_client.post('/internal/v2/identities', json=request_body) assert response.status_code == 422 assert response.get_json()['message'] == 'Role(s) cannot be attached' tenant_logic_mock.check_compatibility_with_tenant_configuration.assert_called_once() call_args = tenant_logic_mock.check_compatibility_with_tenant_configuration.call_args assert isinstance(call_args[0][0], Tenant) assert call_args[0][0].tenant_uuid == request_body['tenant']['tenant_uuid'] assert call_args[0][0].tenant_type == constants.ACCOUNT_TENANT_TYPE assert call_args[0][1] == request_body['roles_to_attach'] @mock.patch('permissions.handlers.internal_handlers.user_notify') @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.Tenant') @mock.patch('permissions.handlers.internal_handlers.TenantRolesInput') @mock.patch('permissions.handlers.internal_handlers.AdminIdentity') @mock.patch('permissions.handlers.internal_handlers.IdentityInput') @mock.patch('permissions.logic.user_invite.create_employee') @mock.patch('permissions.logic.identity.get_identity_by_email') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_create_internal_identity_success( mock_g: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, get_by_email_mock: mock.MagicMock, create_employee_mock: mock.MagicMock, identity_input_mock: mock.MagicMock, admin_identity_mock: mock.MagicMock, tenant_roles_input_mock: mock.MagicMock, tenant_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, _user_notify_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test successful create_internal_identity endpoint.""" mock_g.request_context.jwt_identity_id = '74165b07-c597-46ba-b64a-21ff3632e843' pdp_check_mock.return_value = True get_by_email_mock.return_value = None tenant_logic_mock.check_compatibility_with_tenant_configuration.return_value = True create_employee_mock.return_value = mock.Mock(id='new-identity-uuid') admin = mock.Mock() identity_model_mock.get_identity_by_id_new.return_value = admin # Create mock instances that will be returned by the class constructors identity_input_instance = mock.Mock() admin_identity_instance = mock.Mock() tenant_roles_input_instance = mock.Mock() tenant_instance = mock.Mock() identity_input_mock.return_value = identity_input_instance admin_identity_mock.return_value = admin_identity_instance tenant_roles_input_mock.return_value = tenant_roles_input_instance tenant_mock.return_value = tenant_instance response = fixture_client.post('/internal/v2/identities', json=VALID_CREATE_BODY) assert response.status_code == 201 assert response.get_json() == {'id': 'new-identity-uuid'} identity_input_mock.assert_called_once_with( first_name=VALID_CREATE_BODY['first_name'], last_name=VALID_CREATE_BODY['last_name'], email=VALID_CREATE_BODY['email'], ) tenant_mock.assert_called_once_with( tenant_type=VALID_CREATE_BODY['tenant']['tenant_type'], tenant_uuid=VALID_CREATE_BODY['tenant']['tenant_uuid'], ) tenant_roles_input_mock.assert_called_once_with( tenant=tenant_instance, roles_to_attach=VALID_CREATE_BODY['roles_to_attach'], roles_to_detach=[], ) admin_identity_mock.assert_called_once_with( id=admin.id, first_name=admin.first_name, last_name=admin.last_name, name=admin.name, email=admin.email, auth0_user_id=admin.auth0_user_id, active=admin.active, user_types=admin.user_types, default_brand=admin.default_brand, settings_profile=identity_model_mock.get_identity_settings_profile.return_value, ) create_employee_mock.assert_called_once_with( assignee_identity=identity_input_instance, admin_identity=admin_identity_instance, tenant_roles_input=tenant_roles_input_instance, brand=VALID_CREATE_BODY['brand'], ) @pytest.mark.parametrize( ['admin_id', 'pdp_check_result', 'expected_status'], [ pytest.param(None, None, 401, id='No valid JWT'), pytest.param('74165b07-c597-46ba-b64a-21ff3632e843', False, 403, id='PDP check failed'), pytest.param('74165b07-c597-46ba-b64a-21ff3632e843', True, 200, id='Valid request'), ], ) @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_update_internal_identity_auth( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, _identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, admin_id: str | None, pdp_check_result: bool | None, expected_status: int, fixture_client: testing.FlaskClient, ) -> None: """Test update_internal_identity endpoint authentication/authorization.""" g_mock.request_context.jwt_identity_id = admin_id pdp_check_mock.return_value = pdp_check_result tenant_logic_mock.seat_is_update_removing_last_role.return_value = False response = fixture_client.patch( '/internal/v2/identities/94e951af-5ce4-4a2c-9f78-14d7b6a21331', json=VALID_UPDATE_BODY, ) assert response.status_code == expected_status if pdp_check_result is not None: pdp_check_mock.assert_called_once() else: pdp_check_mock.assert_not_called() @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_update_internal_identity_not_found( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test update_internal_identity endpoint when identity not found.""" g_mock.request_context.jwt_identity_id = '74165b07-c597-46ba-b64a-21ff3632e843' pdp_check_mock.return_value = True identity_model_mock.get_identity_by_id_new.return_value = None response = fixture_client.patch( '/internal/v2/identities/94e951af-5ce4-4a2c-9f78-14d7b6a21331', json=VALID_UPDATE_BODY, ) assert response.status_code == 404 assert response.get_json()['message'] == 'User does not exist' identity_model_mock.get_identity_by_id_new.assert_called_once_with( '94e951af-5ce4-4a2c-9f78-14d7b6a21331' ) @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_update_internal_identity_removing_last_role_value_error( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, _identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test update_internal_identity when seat_is_update_removing_last_role raises ValueError.""" g_mock.request_context.jwt_identity_id = '74165b07-c597-46ba-b64a-21ff3632e843' pdp_check_mock.return_value = True tenant_logic_mock.seat_is_update_removing_last_role.side_effect = ValueError('Tenant not found') response = fixture_client.patch( '/internal/v2/identities/94e951af-5ce4-4a2c-9f78-14d7b6a21331', json=VALID_UPDATE_BODY, ) assert response.status_code == 400 assert response.get_json()['message'] == 'Tenant not found' tenant_logic_mock.seat_is_update_removing_last_role.assert_called_once_with( identity_id='94e951af-5ce4-4a2c-9f78-14d7b6a21331', tenant_uuid=VALID_UPDATE_BODY['tenant']['tenant_uuid'], roles_to_detach=VALID_UPDATE_BODY['roles_to_detach'], ) @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_update_internal_identity_removing_last_role_returns_true( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test update_internal_identity when trying to remove all roles from a tenant.""" g_mock.request_context.jwt_identity_id = '74165b07-c597-46ba-b64a-21ff3632e843' pdp_check_mock.return_value = True identity_model_mock.get_identity_by_id_new.return_value = mock.Mock() tenant_logic_mock.seat_is_update_removing_last_role.return_value = True response = fixture_client.patch( '/internal/v2/identities/94e951af-5ce4-4a2c-9f78-14d7b6a21331', json=VALID_UPDATE_BODY, ) assert response.status_code == 400 assert ( response.get_json()['message'] == 'Cannot remove all roles for a tenant via this endpoint. Please use revoke access endpoint instead.' ) tenant_logic_mock.seat_is_update_removing_last_role.assert_called_once_with( identity_id='94e951af-5ce4-4a2c-9f78-14d7b6a21331', tenant_uuid=VALID_UPDATE_BODY['tenant']['tenant_uuid'], roles_to_detach=VALID_UPDATE_BODY['roles_to_detach'], ) @mock.patch('permissions.handlers.internal_handlers.user_update') @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_update_internal_identity_success( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, user_update_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test successful update_internal_identity endpoint.""" admin_id = '74165b07-c597-46ba-b64a-21ff3632e843' identity_id = '94e951af-5ce4-4a2c-9f78-14d7b6a21331' g_mock.request_context.jwt_identity_id = admin_id pdp_check_mock.return_value = True existing_identity = mock.Mock() existing_identity.email = 'test@theorchard.com' identity_model_mock.get_identity_by_id_new.return_value = existing_identity identity_with_auth0 = mock.Mock() identity_model_mock.get_identity_with_auth0_for_existing_identity.return_value = ( identity_with_auth0 ) tenant_logic_mock.seat_is_update_removing_last_role.return_value = False response = fixture_client.patch( f'/internal/v2/identities/{identity_id}', json=VALID_UPDATE_BODY, ) assert response.status_code == 200 assert response.get_json() == {'id': identity_id} tenant_logic_mock.seat_is_update_removing_last_role.assert_called_once_with( identity_id=identity_id, tenant_uuid=VALID_UPDATE_BODY['tenant']['tenant_uuid'], roles_to_detach=VALID_UPDATE_BODY['roles_to_detach'], ) identity_model_mock.get_identity_with_auth0_for_existing_identity.assert_called_once() auth0_call_args = identity_model_mock.get_identity_with_auth0_for_existing_identity.call_args assert isinstance(auth0_call_args.kwargs['admin'], AdminIdentity) assert auth0_call_args.kwargs['existing_identity'] == existing_identity assert auth0_call_args.kwargs['email'] == existing_identity.email assert auth0_call_args.kwargs['brand'] == constants.AUTH0_ORCHARD_ORG_NAME user_update_mock.update_employee.assert_called_once() update_call_args = user_update_mock.update_employee.call_args assert isinstance(update_call_args.kwargs['admin'], AdminIdentity) assert update_call_args.kwargs['identity_with_auth0'] == identity_with_auth0 assert isinstance(update_call_args.kwargs['tenant_roles_input'], TenantRolesInput) assert update_call_args.kwargs['brand'] is None @pytest.mark.parametrize( ('role',), [(application.FANSIFTER_BASE_ROLE,), (application.SONGWHIP_READ_ROLE,)] ) @mock.patch('permissions.handlers.internal_handlers.user_update') @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_update_internal_identity_with_account_tenant_type_success( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, user_update_mock: mock.MagicMock, fixture_client: testing.FlaskClient, role: str, ) -> None: """Test successful update_internal_identity endpoint.""" admin_id = '74165b07-c597-46ba-b64a-21ff3632e843' identity_id = '94e951af-5ce4-4a2c-9f78-14d7b6a21331' account_id = '9ddec0b8-ad40-4880-973a-c6ffe25ac702' g_mock.request_context.jwt_identity_id = admin_id pdp_check_mock.return_value = True existing_identity = mock.Mock() existing_identity.email = 'test@theorchard.com' identity_model_mock.get_identity_by_id_new.return_value = existing_identity identity_with_auth0 = mock.Mock() identity_model_mock.get_identity_with_auth0_for_existing_identity.return_value = ( identity_with_auth0 ) tenant_logic_mock.seat_is_update_removing_last_role.return_value = False tenant_logic_mock.get_parent_company_brand_for_tenant.return_value = constants.AWAL_BRAND body = { 'roles_to_attach': [], 'roles_to_detach': [role], 'tenant': { 'tenant_type': constants.ACCOUNT_TENANT_TYPE, 'tenant_uuid': account_id, }, } response = fixture_client.patch( f'/internal/v2/identities/{identity_id}', json=body, ) assert response.status_code == 200, response.text assert response.get_json() == {'id': identity_id} tenant_logic_mock.seat_is_update_removing_last_role.assert_called_once_with( identity_id=identity_id, tenant_uuid=body['tenant']['tenant_uuid'], roles_to_detach=body['roles_to_detach'], ) identity_model_mock.get_identity_with_auth0_for_existing_identity.assert_called_once() auth0_call_args = identity_model_mock.get_identity_with_auth0_for_existing_identity.call_args assert isinstance(auth0_call_args.kwargs['admin'], AdminIdentity) assert auth0_call_args.kwargs['existing_identity'] == existing_identity assert auth0_call_args.kwargs['email'] == existing_identity.email assert auth0_call_args.kwargs['brand'] == constants.SONY_BRAND user_update_mock.update_employee.assert_called_once() update_call_args = user_update_mock.update_employee.call_args assert isinstance(update_call_args.kwargs['admin'], AdminIdentity) assert update_call_args.kwargs['identity_with_auth0'] == identity_with_auth0 assert isinstance(update_call_args.kwargs['tenant_roles_input'], TenantRolesInput) assert update_call_args.kwargs['brand'] == constants.AWAL_BRAND @pytest.mark.parametrize( ('tenant', 'role', 'is_compatible', 'expected_status_code'), [ # Parent company tenant type only allows insights and settings (constants.PARENT_COMPANY_TENANT_TYPE, application.INSIGHTS_BASE_ROLE, True, 200), (constants.PARENT_COMPANY_TENANT_TYPE, application.INSIGHTS_BASE_ROLE, False, 200), (constants.PARENT_COMPANY_TENANT_TYPE, application.SETTINGS_BASE_ROLE, True, 200), (constants.PARENT_COMPANY_TENANT_TYPE, application.SETTINGS_BASE_ROLE, False, 200), (constants.PARENT_COMPANY_TENANT_TYPE, application.FANSIFTER_BASE_ROLE, True, 400), (constants.PARENT_COMPANY_TENANT_TYPE, application.FANSIFTER_BASE_ROLE, False, 400), # Account tenant type only allows Fansifter and Songwhip plus requires compatibility check (constants.ACCOUNT_TENANT_TYPE, application.FANSIFTER_BASE_ROLE, True, 200), (constants.ACCOUNT_TENANT_TYPE, application.FANSIFTER_BASE_ROLE, False, 422), (constants.ACCOUNT_TENANT_TYPE, application.SONGWHIP_READ_ROLE, True, 200), (constants.ACCOUNT_TENANT_TYPE, application.SONGWHIP_READ_ROLE, False, 422), (constants.ACCOUNT_TENANT_TYPE, application.INSIGHTS_BASE_ROLE, True, 400), (constants.ACCOUNT_TENANT_TYPE, application.INSIGHTS_BASE_ROLE, False, 400), # Other tenant types not allowed (constants.SUBACCOUNT_TENANT_TYPE, application.SONGWHIP_READ_ROLE, True, 400), ], ) @mock.patch('permissions.handlers.internal_handlers.user_update') @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context', 'log']) def test_update_internal_identity_with_role_and_compatibility_combinations( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, user_update_mock: mock.MagicMock, fixture_client: testing.FlaskClient, tenant: str, role: str, is_compatible: bool, expected_status_code: int, ) -> None: """Test successful update_internal_identity endpoint.""" admin_id = '74165b07-c597-46ba-b64a-21ff3632e843' identity_id = '94e951af-5ce4-4a2c-9f78-14d7b6a21331' if tenant == constants.PARENT_COMPANY_TENANT_TYPE: tenant_id = parent_companies.ORCHARD_PARENT_COMPANY_UUID else: tenant_id = '9ddec0b8-ad40-4880-973a-c6ffe25ac702' g_mock.request_context.jwt_identity_id = admin_id pdp_check_mock.return_value = True existing_identity = mock.Mock() existing_identity.email = 'test@theorchard.com' identity_model_mock.get_identity_by_id_new.return_value = existing_identity tenant_logic_mock.check_compatibility_with_tenant_configuration.return_value = is_compatible body = { 'roles_to_attach': [role], 'roles_to_detach': [], 'tenant': {'tenant_type': tenant, 'tenant_uuid': tenant_id}, } response = fixture_client.patch(f'/internal/v2/identities/{identity_id}', json=body) assert response.status_code == expected_status_code, response.text @pytest.mark.parametrize( ['admin_id', 'pdp_check_result', 'identity_exists', 'expected_status'], [ pytest.param(None, None, False, 401, id='No valid JWT'), pytest.param( '74165b07-c597-46ba-b64a-21ff3632e843', False, False, 403, id='PDP check failed' ), pytest.param( '74165b07-c597-46ba-b64a-21ff3632e843', True, False, 404, id='Identity not found' ), ], ) @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_delete_internal_identity_errors( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, admin_id: str | None, pdp_check_result: bool | None, identity_exists: bool, expected_status: int, fixture_client: testing.FlaskClient, ) -> None: """Test delete_internal_identity endpoint error cases (401, 403, 404).""" g_mock.request_context.jwt_identity_id = admin_id pdp_check_mock.return_value = pdp_check_result identity_model_mock.get_identity_by_id_new.return_value = ( mock.Mock() if identity_exists else None ) response = fixture_client.delete('/internal/v2/identities/94e951af-5ce4-4a2c-9f78-14d7b6a21331') assert response.status_code == expected_status if pdp_check_result is not None: pdp_check_mock.assert_called_once() else: pdp_check_mock.assert_not_called() @mock.patch('permissions.handlers.internal_handlers.user_revoke') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.handlers.internal_handlers.g', spec=['request_context']) @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_delete_internal_identity_success( g_mock: mock.MagicMock, g_mock2: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, user_revoke_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test successful delete_internal_identity endpoint.""" admin_id = '74165b07-c597-46ba-b64a-21ff3632e843' identity_id = '94e951af-5ce4-4a2c-9f78-14d7b6a21331' admin_profile_id = 12345 g_mock.request_context.jwt_identity_id = admin_id g_mock2.request_context.jwt_identity_id = admin_id pdp_check_mock.return_value = True admin_identity = mock.Mock(id=admin_id) existing_identity = mock.Mock(id=identity_id) settings_profile = mock.Mock(profile_id=admin_profile_id) # Set up return values for different get identity calls identity_model_mock.get_identity_by_id_new.side_effect = lambda identity_id: ( admin_identity if identity_id == admin_id else existing_identity ) identity_model_mock.get_identity_settings_profile.return_value = settings_profile response = fixture_client.delete(f'/internal/v2/identities/{identity_id}') assert response.status_code == 204 user_revoke_mock.revoke_all_access_for_employee_identity.assert_called_once_with( admin_context={'identity_id': admin_id, 'profile_id': admin_profile_id}, identity=existing_identity, ) @pytest.mark.parametrize( ['admin_id', 'pdp_check_result', 'tenant_type', 'identity_exists', 'tenant_exists', 'expected'], [ pytest.param(None, None, 'account', True, True, 401, id='No valid JWT'), pytest.param( '74165b07-c597-46ba-b64a-21ff3632e843', False, 'account', True, True, 403, id='PDP check failed', ), pytest.param( '74165b07-c597-46ba-b64a-21ff3632e843', True, 'parent_company', True, True, 400, id='Non-account tenant type', ), pytest.param( '74165b07-c597-46ba-b64a-21ff3632e843', True, 'account', False, True, 404, id='Identity not found', ), pytest.param( '74165b07-c597-46ba-b64a-21ff3632e843', True, 'account', True, False, 404, id='Tenant not found', ), ], ) @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.utils.api_utils.g', spec=['request_context', 'log']) def test_delete_internal_identity_tenant_access_errors( g_mock: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, admin_id: str | None, pdp_check_result: bool | None, tenant_type: str, identity_exists: bool, tenant_exists: bool, expected: int, fixture_client: testing.FlaskClient, ) -> None: """Test delete_internal_identity_tenant_access error cases (401, 403, 400, 404).""" g_mock.request_context.jwt_identity_id = admin_id pdp_check_mock.return_value = pdp_check_result identity_model_mock.get_identity_by_id_new.return_value = ( mock.Mock() if identity_exists else None ) tenant_logic_mock.does_tenant_exist.return_value = tenant_exists identity_id = '94e951af-5ce4-4a2c-9f78-14d7b6a21331' tenant_uuid = 'dffedd4d-b88d-444d-a9eb-6ce89aa4d2f6' response = fixture_client.delete( f'/internal/v2/identities/{identity_id}/tenants/{tenant_type}/{tenant_uuid}' ) assert response.status_code == expected @mock.patch('permissions.handlers.internal_handlers.user_revoke') @mock.patch('permissions.handlers.internal_handlers.tenant_logic') @mock.patch('permissions.handlers.internal_handlers.identity_model') @mock.patch('permissions.utils.authorization.pdp_authorize_manage_employee') @mock.patch('permissions.handlers.internal_handlers.g', spec=['request_context']) @mock.patch('permissions.utils.api_utils.g', spec=['request_context']) def test_delete_internal_identity_tenant_access_success( g_mock: mock.MagicMock, g_mock2: mock.MagicMock, pdp_check_mock: mock.MagicMock, identity_model_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, user_revoke_mock: mock.MagicMock, fixture_client: testing.FlaskClient, ) -> None: """Test successful revoke of account tenant access.""" admin_id = '74165b07-c597-46ba-b64a-21ff3632e843' identity_id = '94e951af-5ce4-4a2c-9f78-14d7b6a21331' tenant_uuid = 'dffedd4d-b88d-444d-a9eb-6ce89aa4d2f6' admin_profile_id = 12345 g_mock.request_context.jwt_identity_id = admin_id g_mock2.request_context.jwt_identity_id = admin_id pdp_check_mock.return_value = True admin_identity = mock.Mock(id=admin_id) existing_identity = mock.Mock(id=identity_id) settings_profile = mock.Mock(profile_id=admin_profile_id) identity_model_mock.get_identity_by_id_new.side_effect = lambda identity_id: ( admin_identity if identity_id == admin_id else existing_identity ) identity_model_mock.get_identity_settings_profile.return_value = settings_profile tenant_logic_mock.does_tenant_exist.return_value = True response = fixture_client.delete( f'/internal/v2/identities/{identity_id}/tenants/account/{tenant_uuid}' ) assert response.status_code == 204 user_revoke_mock.revoke_access_to_tenant_for_identity.assert_called_once_with( admin_identity_id=admin_id, admin_profile_id=admin_profile_id, identity_id=identity_id, tenant=Tenant(tenant_type=TenantType.ACCOUNT, tenant_uuid=tenant_uuid), deactivate_if_last_tenant=False, )