"""Unit tests for the user_update logic module.""" from unittest import mock import neo4j.exceptions import pytest import sqlalchemy from permissions import types from permissions.exceptions.incomplete_result_error import IncompleteResultError from permissions.logic import user_update @pytest.mark.parametrize( ['tenant_roles_input', 'has_roles_to_attach', 'has_roles_to_detach'], [ pytest.param( types.TenantRolesInput( roles_to_attach=['COLLABORATORS_BASE_ROLE', 'INSIGHTS_BASE_ROLE'], roles_to_detach=[], tenant=types.Tenant( tenant_type=types.TenantType.ACCOUNT, tenant_uuid='fe215625-1e69-4b89-b30a-d9e097970a59', ), ), True, False, id='Roles to attach but not detach', ), pytest.param( types.TenantRolesInput( roles_to_attach=[], roles_to_detach=['COLLABORATORS_BASE_ROLE', 'INSIGHTS_BASE_ROLE'], tenant=types.Tenant( tenant_type=types.TenantType.ACCOUNT, tenant_uuid='fe215625-1e69-4b89-b30a-d9e097970a59', ), ), False, True, id='Roles to detach but not attach', ), pytest.param( types.TenantRolesInput( roles_to_attach=['COLLABORATORS_BASE_ROLE', 'INSIGHTS_BASE_ROLE'], roles_to_detach=['SETTINGS_BASE_ROLE'], tenant=types.Tenant( tenant_type=types.TenantType.ACCOUNT, tenant_uuid='fe215625-1e69-4b89-b30a-d9e097970a59', ), ), True, True, id='Roles to attach and detach', ), ], ) @mock.patch('permissions.logic.user_update.detach_v2_roles') @mock.patch('permissions.logic.user_invite.update_identity_with_v2_roles') @mock.patch('permissions.connectors.neo4j.db_session') def test_update_user( db_session_mock: mock.MagicMock, add_roles_mock: mock.MagicMock, detach_roles_mock: mock.MagicMock, tenant_roles_input: types.TenantRolesInput, has_roles_to_attach: bool, has_roles_to_detach: bool, ): """Test update_user behavior when there are/aren't roles to attach/detach.""" admin = mock.MagicMock() identity = mock.MagicMock(active='Y') brand = 'theorchard' transaction_mock = mock.MagicMock() enter_mock = mock.MagicMock() enter_mock.begin_transaction.return_value = transaction_mock session_mock = mock.MagicMock() session_mock.__enter__.return_value = enter_mock db_session_mock.return_value = session_mock vend_contact = mock.Mock() add_roles_mock.return_value = ('identity-id', vend_contact) vend_contact_mock = mock.MagicMock() add_roles_mock.return_value = ('identity-id', vend_contact_mock) user_update.update_user( admin=admin, identity_with_auth0=identity, tenant_roles_input=tenant_roles_input, brand=brand, ) if has_roles_to_attach and has_roles_to_detach: enter_mock.begin_transaction.assert_has_calls([mock.call(), mock.call()]) else: enter_mock.begin_transaction.assert_called_once() if has_roles_to_attach: add_roles_mock.assert_called_once_with( tx=transaction_mock, admin_identity=admin, assignee_identity=identity, tenant_roles_input=tenant_roles_input, master_contact=False, ) if has_roles_to_detach: detach_roles_mock.assert_called_once_with( tx=transaction_mock, roles=tenant_roles_input.roles_to_detach, identity=identity, admin_identity=admin, tenant=tenant_roles_input.tenant, ) if not has_roles_to_attach: add_roles_mock.assert_not_called() if not has_roles_to_detach: detach_roles_mock.assert_not_called() @mock.patch('permissions.logic.user_invite.update_identity_with_v2_roles') @mock.patch('permissions.connectors.neo4j.db_session') def test_update_user_attach_error(db_session_mock, add_roles_mock): """Test update_user rolls back when attaching roles gets a neo4j error.""" add_roles_error = neo4j.exceptions.ClientError('no attach >:(') add_roles_mock.side_effect = add_roles_error transaction_mock = mock.MagicMock() db_session_mock.return_value.__enter__.return_value.begin_transaction.return_value = ( transaction_mock ) with pytest.raises(neo4j.exceptions.ClientError) as e: user_update.update_user( admin=mock.Mock(), identity_with_auth0=mock.Mock(active='Y'), tenant_roles_input=mock.Mock(), brand=mock.Mock(), ) transaction_mock.rollback.assert_called() assert e.value == add_roles_error @pytest.mark.parametrize('active', ['Y', 'N']) @mock.patch('permissions.logic.user_update.default_brand') @mock.patch('permissions.logic.user_update.identity_logic') @mock.patch('permissions.logic.user_invite.update_identity_with_v2_roles') @mock.patch('permissions.connectors.neo4j.db_session') def test_update_user_reactivates_and_updates_default_brand( db_session_mock: mock.MagicMock, _add_roles_mock: mock.MagicMock, identity_logic_mock: mock.MagicMock, default_brand_mock: mock.MagicMock, active: str, ) -> None: """Test update_user reactivates user and updates default brand.""" admin_id = 'admin-id-123' admin = mock.MagicMock(id=admin_id, settings_profile=mock.MagicMock(profile_id=456)) identity = mock.MagicMock(id='identity-id-123', active=active) _add_roles_mock.return_value = ('identity-id-123', None) identity_logic_mock.reactivate_user_if_needed.return_value = active != 'Y' # Add transaction mock setup transaction_mock = mock.MagicMock() enter_mock = mock.MagicMock() enter_mock.begin_transaction.return_value = transaction_mock session_mock = mock.MagicMock() session_mock.__enter__.return_value = enter_mock db_session_mock.return_value = session_mock user_update.update_user( admin=admin, identity_with_auth0=identity, tenant_roles_input=mock.MagicMock(), brand='theorchard', ) if active != 'Y': identity_logic_mock.reactivate_user_if_needed.assert_called_once_with( admin=admin, identity_with_auth0=identity, ) default_brand_mock.update_default_brand_if_needed.assert_called_once_with( identity=identity, admin_identity_id=admin_id, company_brand='theorchard', ) else: identity_logic_mock.reactivate_user_if_needed.assert_called_once_with( admin=admin, identity_with_auth0=identity, ) default_brand_mock.update_default_brand_if_needed.assert_not_called() @mock.patch('permissions.logic.user_update.default_brand') @mock.patch('permissions.logic.user_update.identity_logic') @mock.patch('permissions.logic.user_invite.update_identity_with_v2_roles') @mock.patch('permissions.connectors.neo4j.db_session') def test_update_user_raises_if_reactivate_fails( _db_session_mock: mock.MagicMock, add_roles_mock: mock.MagicMock, identity_logic_mock: mock.MagicMock, default_brand_mock: mock.MagicMock, ) -> None: """Test update_user raises if reactivating user fails.""" identity = mock.MagicMock(id='identity-id-123', active='N') # Simulate reactivation failure by raising RuntimeError identity_logic_mock.reactivate_user_if_needed.side_effect = RuntimeError( 'Failed to reactivate user during update' ) with pytest.raises(RuntimeError) as e: user_update.update_user( admin=mock.MagicMock(), identity_with_auth0=identity, tenant_roles_input=mock.MagicMock(), brand='theorchard', ) assert str(e.value) == 'Failed to reactivate user during update' add_roles_mock.assert_not_called() default_brand_mock.update_default_brand_if_needed.assert_not_called() @mock.patch('permissions.logic.user_update.g', spec=['log']) @mock.patch('permissions.logic.user_update.default_brand') @mock.patch('permissions.logic.user_update.identity_logic') @mock.patch('permissions.logic.user_invite.update_identity_with_v2_roles') @mock.patch('permissions.connectors.neo4j.db_session') def test_update_user_does_not_raise_if_update_default_brand_fails( _db_session_mock: mock.MagicMock, _add_roles_mock: mock.MagicMock, identity_logic_mock: mock.MagicMock, default_brand_mock: mock.MagicMock, g_mock: mock.MagicMock, ): """Test update_user does not raise if updating default brand fails.""" identity = mock.MagicMock(id='identity-id-123', active='N') _add_roles_mock.return_value = ('identity-id-123', None) identity_logic_mock.reactivate_user_if_needed.return_value = True default_brand_mock.update_default_brand_if_needed.side_effect = Exception('🫩') user_update.update_user( admin=mock.MagicMock(), identity_with_auth0=identity, tenant_roles_input=mock.MagicMock(), brand='theorchard', ) g_mock.log.error.assert_called_once_with( 'Error updating default brand after reactivation--user is still reactivated', resources={ 'identity_id': 'identity-id-123', 'error': '🫩', }, ) @mock.patch('permissions.logic.user_update.detach_v2_roles') @mock.patch('permissions.logic.user_invite.update_identity_with_v2_roles') @mock.patch('permissions.connectors.neo4j.db_session') def test_update_user_detach_error(db_session_mock, _, detach_roles_mock): """Test update_user behavior when detaching roles gets a neo4j error.""" detach_roles_error = neo4j.exceptions.ClientError('no attach >:(') detach_roles_mock.side_effect = detach_roles_error transaction_mock = mock.MagicMock() db_session_mock.return_value.__enter__.return_value.begin_transaction.return_value = ( transaction_mock ) with pytest.raises(neo4j.exceptions.ClientError) as e: user_update.update_user( admin=mock.Mock(), identity_with_auth0=mock.Mock(), tenant_roles_input=mock.Mock(roles_to_attach=[]), brand=mock.Mock(), ) transaction_mock.rollback.assert_called() assert e.value == detach_roles_error @mock.patch('permissions.logic.user_update.detach_label_profile_roles_for_vend_contact') @mock.patch('permissions.logic.user_update.profile_model') @mock.patch('permissions.logic.profile.v2_roles_to_profiles_and_roles_dict') @mock.patch('permissions.logic.user_invite.update_identity_with_v2_roles') @mock.patch('permissions.connectors.neo4j.db_session') def test_update_user_detach_mysql_error( neo4j_session_mock, _, roles_to_dict_mock, profile_model_mock, detach_label_profile_roles_for_vend_contact_mock, ): """Test update_user behavior when detaching roles gets a mysql error.""" roles_to_dict_mock.return_value = {'LabelProfile': ['analytics']} profile_id = 294812 profile_model_mock.get_label_profile_by_identity_and_tenant.return_value = { 'profile_id': profile_id, 'roles': ['analytics'], } tenant_roles_input = types.TenantRolesInput( roles_to_attach=[], roles_to_detach=['COLLABORATORS_BASE_ROLE', 'INSIGHTS_BASE_ROLE'], tenant=types.Tenant( tenant_type=types.TenantType.ACCOUNT, tenant_uuid='fe215625-1e69-4b89-b30a-d9e097970a59' ), ) transaction_mock = mock.MagicMock() neo4j_session_mock.return_value.__enter__.return_value.begin_transaction.return_value = ( transaction_mock ) detach_label_profile_roles_for_vend_contact_mock.side_effect = IncompleteResultError('test') with pytest.raises(IncompleteResultError): user_update.update_user( admin=mock.Mock(), identity_with_auth0=mock.Mock(), tenant_roles_input=tenant_roles_input, brand=mock.Mock(), ) profile_model_mock.create_or_update_label_profile_with_tenant_relationship.assert_called() transaction_mock.rollback.assert_called() @mock.patch('permissions.models.profile.delete_access_for_profile') @mock.patch('permissions.logic.profile.v2_roles_to_profiles_and_roles_dict') def test_detach_v2_roles_non_label(roles_to_dict_mock, delete_access_mock): """Test detach_v2_roles behavior for detaching non label profile roles.""" transaction_mock = mock.Mock() roles = ['COLLABORATORS_BASE_ROLE', 'INSIGHTS_BASE_ROLE'] identity_id = 'a-uuid' identity = mock.Mock(id=identity_id) tenant = types.Tenant(tenant_type=types.TenantType.ACCOUNT, tenant_uuid='another-uuid') admin_id = 'one-more-uuid' admin = mock.Mock(id=admin_id) roles_to_dict_mock.return_value = { 'CollaboratorsProfile': ['unused_here'], 'InsightsProfile': ['also_unused'], } user_update.detach_v2_roles( tx=transaction_mock, roles=roles, identity=identity, admin_identity=admin, tenant=tenant, ) roles_to_dict_mock.assert_called_with(roles) assert delete_access_mock.call_count == 2 delete_access_mock.assert_has_calls( [ mock.call( tx=transaction_mock, audit_user_id=admin_id, identity_id=identity.id, profile_id=None, profile_type='CollaboratorsProfile', resource_type='Vendor', resource_uuid=tenant.tenant_uuid, ), mock.call( tx=transaction_mock, audit_user_id=admin_id, identity_id=identity.id, profile_id=None, profile_type='InsightsProfile', resource_type='Vendor', resource_uuid=tenant.tenant_uuid, ), ], any_order=True, ) @mock.patch('permissions.logic.user_update.g') @mock.patch('permissions.models.profile.delete_access_for_profile') @mock.patch('permissions.logic.profile.v2_roles_to_profiles_and_roles_dict') def test_detach_v2_roles_non_label_no_access( roles_to_dict_mock, delete_access_mock, g_mock, app_context ): """Test detach_v2_roles behavior when the role wasn't there in the first place.""" identity_id = 'a-uuid' identity_mock = mock.Mock(id=identity_id) tenant = types.Tenant(tenant_type=types.TenantType.ACCOUNT, tenant_uuid='another-uuid') roles_to_dict_mock.return_value = {'CollaboratorsProfile': ['unused_here']} delete_access_mock.return_value = None user_update.detach_v2_roles( tx=mock.Mock(), roles=['WORKSTATION_ANALYTICS_BASE_ROLE'], identity=identity_mock, admin_identity=mock.Mock(), tenant=tenant, ) g_mock.log.warn.assert_called_with( 'Tried to delete access to tenant, but access did not exist', resources={ 'identity_id': identity_id, 'tenant_type': tenant.tenant_type, 'tenant_uuid': tenant.tenant_uuid, }, ) @mock.patch('permissions.logic.user_update.detach_label_profile_roles') @mock.patch('permissions.models.profile.delete_access_for_profile') @mock.patch('permissions.logic.profile.v2_roles_to_profiles_and_roles_dict') def test_detach_v2_roles_label(roles_to_dict_mock, _, detach_label_profile_roles_mock): """Test detach_v2_roles behavior when label profile roles are given.""" identity_id = 'a-uuid' identity_mock = mock.Mock(id=identity_id) tenant = types.Tenant(tenant_type=types.TenantType.ACCOUNT, tenant_uuid='another-uuid') roles_to_dict_mock.return_value = {'LabelProfile': ['analytics']} transaction_mock = mock.Mock() admin = mock.Mock() user_update.detach_v2_roles( tx=transaction_mock, roles=['WORKSTATION_ANALYTICS_BASE_ROLE'], identity=identity_mock, admin_identity=admin, tenant=tenant, ) detach_label_profile_roles_mock.assert_called_with( tx=transaction_mock, label_profile_roles=['analytics'], identity=identity_mock, admin_identity=admin, tenant=tenant, ) @mock.patch('permissions.models.profile.delete_access_for_profile') @mock.patch('permissions.logic.profile.v2_roles_to_profiles_and_roles_dict') def test_detach_v2_roles_filters_unrecognized_roles(roles_to_dict_mock, delete_access_mock): """Test detach_v2_roles behavior when unrecognized roles are given.""" transaction_mock = mock.Mock() roles = ['COLLABORATORS_BASE_ROLE', 'INSIGHTS_BASE_ROLE', 'INVALID_ROLE'] identity_id = 'a-uuid' identity = mock.Mock(id=identity_id) tenant = types.Tenant(tenant_type=types.TenantType.ACCOUNT, tenant_uuid='another-uuid') admin_id = 'one-more-uuid' admin = mock.Mock(id=admin_id) roles_to_dict_mock.return_value = { 'CollaboratorsProfile': ['unused_here'], 'InsightsProfile': ['also_unused'], } user_update.detach_v2_roles( tx=transaction_mock, roles=roles, identity=identity, admin_identity=admin, tenant=tenant, ) roles_to_dict_mock.assert_called_with(['COLLABORATORS_BASE_ROLE', 'INSIGHTS_BASE_ROLE']) assert delete_access_mock.call_count == 2 @mock.patch('permissions.logic.user_update.g') @mock.patch('permissions.models.profile.get_label_profile_by_identity_and_tenant') def test_detach_label_profile_roles_no_profile(get_profile_mock, g_mock, app_context): """Test behavior when label profile roles are given but profile doesn't exist.""" identity_id = 'a-uuid' tenant = types.Tenant(tenant_type=types.TenantType.ACCOUNT, tenant_uuid='another-uuid') get_profile_mock.return_value = None user_update.detach_label_profile_roles( tx=mock.Mock(), label_profile_roles=mock.Mock(), identity=mock.Mock(id=identity_id), admin_identity=mock.Mock(), tenant=tenant, ) g_mock.log.warn.expect_called_with( 'Tried to remove label profile roles, but profile did not exist', resources={ 'identity_id': identity_id, 'tenant_type': tenant.tenant_type, 'tenant_uuid': tenant.tenant_uuid, }, ) @mock.patch('permissions.logic.vend_contact.set_new_auth0_vend_contact_id') @mock.patch('permissions.logic.user_update.g') @mock.patch('permissions.logic.user_update.profile_model') @mock.patch('permissions.logic.user_update.detach_label_profile_roles_for_vend_contact') def test_detach_label_profile_roles_not_present(_, profile_model_mock, g_mock, __, app_context): """Test behavior when label profile roles are given, but they're not on the profile.""" identity_id = 'a-uuid' tenant = types.Tenant(tenant_type=types.TenantType.ACCOUNT, tenant_uuid='another-uuid') profile_id = 294812 profile_model_mock.get_label_profile_by_identity_and_tenant.return_value = { 'profile_id': profile_id, 'roles': ['analytics'], } roles_input = ['analytics', 'catalog'] user_update.detach_label_profile_roles( tx=mock.Mock(), label_profile_roles=roles_input, identity=mock.Mock(id=identity_id), admin_identity=mock.Mock(), tenant=tenant, ) g_mock.log.warn.assert_called_with( 'Tried to remove label profile roles that user does not have', resources={ 'identity_id': identity_id, 'profile_id': profile_id, 'roles_to_detach_that_arent_there': set(['catalog']), }, ) @mock.patch('permissions.logic.user_update.profile_model') @mock.patch('permissions.logic.user_update.detach_label_profile_roles_for_vend_contact') def test_detach_label_profile_role_access_remains( detach_label_profile_roles_for_vend_contact_mock, profile_model_mock, ): """Test behavior when label profile roles are detached but some remain.""" transaction_mock = mock.Mock() identity_id = 'a-uuid' identity_mock = mock.Mock(id=identity_id) tenant = types.Tenant(tenant_type=types.TenantType.ACCOUNT, tenant_uuid='another-uuid') label_profile_roles = ['catalog'] admin_id = 'one-more-uuid' admin = mock.Mock(id=admin_id) profile_id = 294812 profile_model_mock.get_label_profile_by_identity_and_tenant.return_value = { 'profile_id': profile_id, 'roles': ['analytics', 'catalog'], } user_update.detach_label_profile_roles( tx=transaction_mock, label_profile_roles=label_profile_roles, identity=identity_mock, admin_identity=admin, tenant=tenant, ) profile_model_mock.create_or_update_label_profile_with_tenant_relationship.assert_called_with( tx=transaction_mock, identity_id=identity_id, profile_id=profile_id, roles=['analytics'], tenant=tenant, audit_user_id=admin_id, ) detach_label_profile_roles_for_vend_contact_mock.assert_called_with( identity=identity_mock, vend_contact_id=profile_id, roles_to_detach_set=set(label_profile_roles), ) @mock.patch('permissions.logic.vend_contact.set_new_auth0_vend_contact_id') @mock.patch('permissions.logic.user_update.mysql') @mock.patch('permissions.logic.user_update.profile_model') @mock.patch('permissions.logic.user_update.detach_label_profile_roles_for_vend_contact') def test_detach_label_profile_role_remove_access( detach_label_profile_roles_for_vend_contact_mock, profile_model_mock, mysql_mock, set_new_primary_vc_mock, ): """Test behavior when all label profile roles are detached.""" transaction_mock = mock.Mock() identity_id = 'a-uuid' identity_mock = mock.Mock(id=identity_id) tenant = types.Tenant(tenant_type=types.TenantType.ACCOUNT, tenant_uuid='another-uuid') label_profile_roles = ['catalog', 'analytics'] admin_id = 'one-more-uuid' admin_profile_id = 11111 admin = mock.Mock(id=admin_id, settings_profile=mock.Mock(profile_id=admin_profile_id)) profile_id = 294812 profile_model_mock.get_label_profile_by_identity_and_tenant.return_value = { 'profile_id': profile_id, 'roles': ['analytics', 'catalog'], } session_mock = mock.Mock() mysql_mock.db_session.return_value.__enter__.return_value = session_mock user_update.detach_label_profile_roles( tx=transaction_mock, label_profile_roles=label_profile_roles, identity=identity_mock, admin_identity=admin, tenant=tenant, ) profile_model_mock.delete_access_for_profile.assert_called_with( tx=transaction_mock, audit_user_id=admin_id, identity_id=identity_id, profile_id=profile_id, profile_type='LabelProfile', resource_type='Vendor', resource_uuid=tenant.tenant_uuid, ) detach_label_profile_roles_for_vend_contact_mock.assert_called_with( identity=identity_mock, vend_contact_id=profile_id, roles_to_detach_set=set(label_profile_roles), ) set_new_primary_vc_mock.assert_called_with( session=session_mock, identity=identity_mock, admin_id=admin_id, admin_profile_id=admin_profile_id, ) @mock.patch('permissions.logic.user_update.vend_contact_logic') @mock.patch('permissions.logic.user_update.vendor_role_model') @mock.patch('permissions.logic.user_update.vend_contact_role_model') @mock.patch('permissions.logic.user_update.vend_contact_model') @mock.patch('permissions.logic.user_update.mysql') @mock.patch('permissions.logic.user_update.g') def test_detach_label_profile_roles_for_vend_contact_remains( _, mysql_mock, vend_contact_model_mock, vend_contact_role_model_mock, vendor_role_model_mock, vend_contact_logic_mock, app_context, ): """Test behavior when label profile roles for vend_contact are detached but some remain.""" identity_id = 'a-uuid' identity_mock = mock.Mock(id=identity_id) label_profile_roles = set('catalog') existing_roles_ids = [1, 2, 3] roles_ids_to_detach = [1] vend_contact_role_model_mock.VendContactRole.get_role_ids_by_vend_contact.return_value = ( existing_roles_ids ) vendor_role_model_mock.vendor_role_ids_from_label_profile_roles.return_value = ( roles_ids_to_detach ) mysql_session_mock = mock.Mock() mysql_mock.db_session.return_value.__enter__.return_value = mysql_session_mock user_update.detach_label_profile_roles_for_vend_contact( identity=identity_mock, vend_contact_id=1, roles_to_detach_set=label_profile_roles, ) # deleted only part of existing roles ( vend_contact_role_model_mock.VendContactRole.delete_roles_by_ids_and_vend_contact_id.assert_called_with( tx=mysql_session_mock, role_ids=roles_ids_to_detach, vend_contact_id=1 ) ) # vend_contact isn't deactivated because has some roles vend_contact_model_mock.VendContact.deactivate_by_id.assert_not_called() # A new primary vend_contact_id isn't set on auth0 metadata vend_contact_logic_mock.set_new_auth0_vend_contact_id.assert_not_called() @mock.patch('permissions.logic.user_update.vendor_role_model') @mock.patch('permissions.logic.user_update.vend_contact_role_model') @mock.patch('permissions.logic.user_update.vend_contact_model') @mock.patch('permissions.logic.user_update.mysql') @mock.patch('permissions.logic.user_update.g') def test_detach_label_profile_roles_for_vend_contact_all_roles( _, mysql_mock, vend_contact_model_mock, vend_contact_role_model_mock, vendor_role_model_mock, app_context, ): """Test behavior when all label profile roles are detached.""" identity_id = 'a-uuid' vend_contact_id = 123 identity_mock = mock.Mock(id=identity_id) label_profile_roles = set('catalog') existing_roles_ids = [1, 2, 3] roles_ids_to_detach = [1, 2, 3] vend_contact_role_model_mock.VendContactRole.get_role_ids_by_vend_contact.return_value = ( existing_roles_ids ) vendor_role_model_mock.vendor_role_ids_from_label_profile_roles.return_value = ( roles_ids_to_detach ) mysql_session_mock = mock.Mock() mysql_mock.db_session.return_value.__enter__.return_value = mysql_session_mock user_update.detach_label_profile_roles_for_vend_contact( identity=identity_mock, vend_contact_id=vend_contact_id, roles_to_detach_set=label_profile_roles, ) # deleted all existing roles ( vend_contact_role_model_mock.VendContactRole.delete_roles_by_ids_and_vend_contact_id.assert_called_with( tx=mysql_session_mock, role_ids=roles_ids_to_detach, vend_contact_id=123, ) ) # vend_contact is deactivated because hasn't roles vend_contact_model_mock.VendContact.deactivate_by_id.assert_called_with( mysql_session_mock, vend_contact_id=vend_contact_id, ) @mock.patch('permissions.logic.user_update.vendor_role_model') @mock.patch('permissions.logic.user_update.vend_contact_role_model') @mock.patch('permissions.logic.user_update.mysql') @mock.patch('permissions.logic.user_update.g') def test_detach_label_profile_roles_for_vend_contact_delete_roles_error( g_mock, mysql_mock, vend_contact_role_model_mock, vendor_role_model_mock, app_context ): """Test error behavior for delete vend contact roles.""" err = sqlalchemy.exc.SQLAlchemyError() identity_id = 'a-uuid' vend_contact_id = 123 identity_mock = mock.Mock(id=identity_id) label_profile_roles = set('catalog') existing_roles_ids = [1] roles_ids_to_detach = [1, 2, 3] vend_contact_role_model_mock.VendContactRole.get_role_ids_by_vend_contact.return_value = ( existing_roles_ids ) vendor_role_model_mock.vendor_role_ids_from_label_profile_roles.return_value = ( roles_ids_to_detach ) mysql_session_mock = mock.Mock() mysql_mock.db_session.return_value.__enter__.return_value = mysql_session_mock ( vend_contact_role_model_mock.VendContactRole.delete_roles_by_ids_and_vend_contact_id.side_effect ) = err with pytest.raises(IncompleteResultError): user_update.detach_label_profile_roles_for_vend_contact( identity=identity_mock, vend_contact_id=vend_contact_id, roles_to_detach_set=label_profile_roles, ) g_mock.log.error.assert_called_with( 'Error detaching roles for vend_contact.', resources={ 'error': err, 'identity_id': identity_id, 'vend_contact_id': vend_contact_id, 'detaching_roles': roles_ids_to_detach, 'existing_roles': existing_roles_ids, }, ) @mock.patch('permissions.logic.user_update.vendor_role_model') @mock.patch('permissions.logic.user_update.vend_contact_role_model') @mock.patch('permissions.logic.user_update.vend_contact_model') @mock.patch('permissions.logic.user_update.mysql') @mock.patch('permissions.logic.user_update.g') def test_detach_label_profile_roles_for_vend_contact_deactivate_error( g_mock, mysql_mock, vend_contact_model_mock, vend_contact_role_model_mock, vendor_role_model_mock, app_context, ): """Test error behavior for deactivate vend contact.""" err = sqlalchemy.exc.SQLAlchemyError() identity_id = 'a-uuid' vend_contact_id = 123 identity_mock = mock.Mock(id=identity_id) label_profile_roles = set('catalog') existing_roles_ids = [1, 2, 3] roles_ids_to_detach = [1, 2, 3] vend_contact_role_model_mock.VendContactRole.get_role_ids_by_vend_contact.return_value = ( existing_roles_ids ) vendor_role_model_mock.vendor_role_ids_from_label_profile_roles.return_value = ( roles_ids_to_detach ) mysql_session_mock = mock.Mock() mysql_mock.db_session.return_value.__enter__.return_value = mysql_session_mock vend_contact_model_mock.VendContact.deactivate_by_id.side_effect = err with pytest.raises(IncompleteResultError): user_update.detach_label_profile_roles_for_vend_contact( identity=identity_mock, vend_contact_id=vend_contact_id, roles_to_detach_set=label_profile_roles, ) ( vend_contact_role_model_mock.VendContactRole.delete_roles_by_ids_and_vend_contact_id.assert_called_with( tx=mysql_session_mock, role_ids=roles_ids_to_detach, vend_contact_id=123, ) ) g_mock.log.error.assert_called_with( 'Error detaching roles for vend_contact.', resources={ 'error': err, 'identity_id': identity_id, 'vend_contact_id': vend_contact_id, 'detaching_roles': roles_ids_to_detach, 'existing_roles': existing_roles_ids, }, ) @mock.patch('permissions.logic.user_update.tenant_logic') @mock.patch('permissions.models.profile.delete_access_for_profile') @mock.patch('permissions.logic.profile.v2_roles_to_profiles_and_roles_dict') def test_detach_v2_roles_employee( roles_to_dict_mock: mock.MagicMock, delete_access_mock: mock.MagicMock, tenant_logic_mock: mock.MagicMock, ) -> None: """Test detach_v2_roles for employees deletes access for each resolved tenant.""" transaction_mock = mock.Mock() roles = ['INSIGHTS_BASE_ROLE'] identity_id = 'identity-uuid' identity = mock.Mock(id=identity_id) tenant = types.Tenant( tenant_type=types.TenantType.PARENT_COMPANY, tenant_uuid='parent-company-uuid', ) admin_id = 'admin-uuid' admin = mock.Mock(id=admin_id) resolved_tenant_1 = types.Tenant( tenant_type=types.TenantType.PARENT_COMPANY, tenant_uuid='parent-company-uuid', ) resolved_tenant_2 = types.Tenant( tenant_type=types.TenantType.ACCOUNT, tenant_uuid='vendor-star-uuid', ) tenant_logic_mock.resolve_employee_tenants.return_value = [ resolved_tenant_1, resolved_tenant_2, ] roles_to_dict_mock.return_value = {'InsightsProfile': ['unused_here']} user_update.detach_v2_roles( tx=transaction_mock, roles=roles, identity=identity, admin_identity=admin, tenant=tenant, is_employee=True, ) tenant_logic_mock.resolve_employee_tenants.assert_called_once_with(tenant) delete_access_mock.assert_has_calls( [ mock.call( tx=transaction_mock, audit_user_id=admin_id, identity_id=identity.id, profile_id=None, profile_type='InsightsProfile', resource_type='ParentCompany', resource_uuid=resolved_tenant_1.tenant_uuid, ), mock.call( tx=transaction_mock, audit_user_id=admin_id, identity_id=identity.id, profile_id=None, profile_type='InsightsProfile', resource_type='Vendor', resource_uuid=resolved_tenant_2.tenant_uuid, ), ], any_order=True, )