"""Test for profile.""" import textwrap import uuid from typing import Any from unittest.mock import ANY, MagicMock, Mock, call, patch import pytest from owsresponse import response from permissions.connectors import mysql, neo4j from permissions.constants import constants from permissions.models import auth0, identity as identity_model, label, owsusers, profile from permissions.types import Tenant, TenantType from tests.unit.conftest import get_session_mock def test_has_admin_access_to_resources(): """Test has_admin_access_to_resources.""" admin_context = { 'identity_id': 'admin-uuid', 'profile_type': 'InsightsProfile', 'profile_id': 12, } uuids = ['ccea8e71-e2c9-4804-93d7-ae471a974597'] resources = [{'uuid': 'ccea8e71-e2c9-4804-93d7-ae471a974597', 'name': 'Adam and Ants'}] session_mock = get_session_mock(resources) with patch.object(neo4j, 'db_session', return_value=session_mock): actual = profile.has_admin_access_to_resources(admin_context, uuids) assert actual assert session_mock.__enter__().read_transaction.called def test_get_identity_id_for_admin(): """Test get_identity_id_for_admin.""" identity_id = 'uuid' identity_obj = [{'id': 'uuid', 'email': 'test@theorcahrd.com'}] admin_context = { 'identity_id': 'admin-uuid', 'profile_type': 'InsightsProfile', 'profile_id': 12, } session_mock = get_session_mock(identity_obj) with patch.object(neo4j, 'db_session', return_value=session_mock): actual = profile.get_identity_id_for_admin(admin_context, identity_id) assert actual assert actual.message == identity_obj args = session_mock.__enter__().read_transaction.call_args assert args[0][1] == admin_context assert args[0][2] == identity_id def test_check_vendor_star_access(): """Test check_vendor_star_access.""" identity_id = 'uuid' profile_type = 'InsightsProfile' profile_id = '1234' identity_obj = [{'id': 'uuid', 'email': 'test@theorcahrd.com'}] session_mock = get_session_mock(identity_obj) with patch.object(neo4j, 'db_session', return_value=session_mock): actual = profile.check_vendor_star_access(identity_id, profile_type, profile_id) assert actual assert actual.message == identity_obj args = session_mock.__enter__().read_transaction.call_args assert args[0][1] == identity_id assert args[0][2] == profile_type assert args[0][3] == profile_id def test_check_vendor_star_access_settings_profile(): """Test check_vendor_star_access.""" identity_id = 'uuid' profile_type = 'SettingsProfile' profile_id = 2323 identity_obj = [{'id': 'uuid', 'email': 'test@theorcahrd.com'}] session_mock = get_session_mock(identity_obj) with patch.object(neo4j, 'db_session', return_value=session_mock): actual = profile.check_vendor_star_access(identity_id, profile_type, profile_id) assert actual assert actual.message == identity_obj args = session_mock.__enter__().read_transaction.call_args assert args[0][1] == identity_id assert args[0][2] == profile_id @patch('permissions.models.profile.neo4j') @patch('permissions.models.profile._check_vendor_star_access_logic') def test_check_vendor_star_access_existing_session(logic_mock, neo4j_mock): """Test check_vendor_star_access case where a session is passed in.""" session_mock = get_session_mock() profile.check_vendor_star_access( identity_id='abc', profile_type='SettingsProfile', profile_id=123, session=session_mock, ) neo4j_mock.db_session.assert_not_called() logic_mock.assert_called_once_with('abc', 'SettingsProfile', 123, session_mock) def test_get_identities_by_emails(): """Test get_identities_by_emails.""" emails = ['one@theorchard.io', 'two@foo.com'] identity_obj = [{'id': 'uuid', 'email': 'test@theorcahrd.com'}] session_mock = get_session_mock(identity_obj) with patch.object(neo4j, 'db_session', return_value=session_mock): actual = profile.get_identities_by_emails(emails) assert actual args = session_mock.__enter__().read_transaction.call_args assert args[0][1] == emails assert isinstance(args[0][0], type(profile._get_identities_by_emails)) def test_filter_by_backend_structure(): """Test _filter_by_backend_structure.""" profile_types = [ {'profile_type': 'ArtistProfile', 'roles': ['analytics']}, {'profile_type': 'LabelProfile', 'roles': ['administrator']}, {'profile_type': 'InsightsProfile', 'roles': ['analytics']}, ] actual = profile._filter_by_backend_structure(profile_types) assert actual assert actual[0] == [profile_types[0], profile_types[2]] assert actual[1] == ['ArtistProfile', 'InsightsProfile'] assert actual[2] == profile_types[1] def test_restructure_label_resources(): """Test _restructure_label_resources.""" resources = [ {'resource_type': 'LabelParticipant', 'ids': [497611, 532010]}, {'resource_type': 'Vendor', 'ids': [7123, 25824]}, {'resource_type': 'SubAccount', 'ids': [155]}, ] db_result = [ {'vendor_id': 25464, 'subaccount_id': 155}, ] run_mock = MagicMock() run_mock.data.return_value = db_result session_mock = MagicMock() session_mock.run.return_value = run_mock actual = profile._restructure_label_resources(session_mock, resources) assert actual == [(7123, None), (25824, None), (25464, 155)] def test_map_to_vend_contact_roles(): """Test _map_to_vend_contact_roles.""" roles = ['analytics', 'marketing', 'advertising'] actual = profile._map_to_vend_contact_roles(roles) assert actual == {2, 3, 7} def test_get_identity_by_email(): """Test get_identity_by_email.""" identity = {'id': 'uuid', 'email': 'foo@theorchard.com', 'name': 'foo'} session_mock = get_session_mock([identity]) with patch.object(neo4j, 'db_session', return_value=session_mock): actual = profile.get_identity_by_email('foo@theorchard.com') assert actual == [identity] def test_get_label_for_label_participant(): """Test get_label_for_label_participant.""" uuids = ['2379e764-5dc1-4f5a-9187-1b3f50a805ce'] data = [ { 'uuid': '2379e764-5dc1-4f5a-9187-1b3f50a805ce', 'name': 'Billy', 'label_name': 'Tronicsole', 'label_id': 32762, } ] session_mock = get_session_mock(data) with patch.object(neo4j, 'db_session', return_value=session_mock): actual = profile.get_label_for_label_participant(uuids) assert actual.status == 200 assert actual.message == data @pytest.mark.parametrize( ('profile_type', 'profile_id', 'neo4j_query'), [ ( # InsightsProfile with profile id 'InsightsProfile', 123456, """MATCH(i:Identity {id: $identity_id})-[rr:HAS_PROFILE]->(p:Profile {profileType:$profile_type, profileId:$profile_id}) -[rel:HAS_ACCESS_TO]-> (r:Vendor {uuid: $resource_uuid}) SET rel.updatedOn = localdatetime(), rel.updatedBy = $auditUser WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ACCESS_TO') YIELD input,output RETURN p.profileType as profileType, p.profileId as profileId, p.roles as roles, rel """, ), ( # InsightsProfile without profile id 'InsightsProfile', None, """MATCH(i:Identity {id: $identity_id})-[rr:HAS_PROFILE]->(p:Profile {profileType:$profile_type}) -[rel:HAS_ACCESS_TO]-> (r:Vendor {uuid: $resource_uuid}) SET rel.updatedOn = localdatetime(), rel.updatedBy = $auditUser WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ACCESS_TO') YIELD input,output RETURN p.profileType as profileType, p.profileId as profileId, p.roles as roles, rel """, ), ( # LabelProfile with profile id 'LabelProfile', 123456, """MATCH(i:Identity {id: $identity_id})-[rr:HAS_PROFILE]->(p:Profile {profileType:$profile_type, profileId:$profile_id}) -[rel:HAS_ACCESS_TO]-> (r:Vendor {uuid: $resource_uuid}) SET rel.updatedOn = localdatetime(), rel.updatedBy = $auditUser WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ACCESS_TO') YIELD input,output RETURN p.profileType as profileType, p.profileId as profileId, p.roles as roles, rel """, ), ( # SettingsProfile with profile id 'SettingsProfile', 123456, """MATCH(i:Identity {id: $identity_id})-[rr:HAS_PROFILE]->(p:Profile {profileType:$profile_type, profileId:$profile_id}) -[rel:HAS_ADMIN_ACCESS_TO]-> (r:Vendor {uuid: $resource_uuid}) SET rel.updatedOn = localdatetime(), rel.updatedBy = $auditUser WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ADMIN_ACCESS_TO') YIELD input,output RETURN p.profileType as profileType, p.profileId as profileId, p.roles as roles, rel """, ), ( # SettingsProfile without profile id 'SettingsProfile', None, """MATCH(i:Identity {id: $identity_id})-[rr:HAS_PROFILE]->(p:Profile {profileType:$profile_type}) -[rel:HAS_ADMIN_ACCESS_TO]-> (r:Vendor {uuid: $resource_uuid}) SET rel.updatedOn = localdatetime(), rel.updatedBy = $auditUser WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ADMIN_ACCESS_TO') YIELD input,output RETURN p.profileType as profileType, p.profileId as profileId, p.roles as roles, rel """, ), ], ) def test_delete_access_for_profile(profile_type, profile_id, neo4j_query): """Test soft deleting resource access using delete_access_for_profile.""" session_mock = MagicMock() result_mock = MagicMock() result_mock.single.return_value = {'profileId': 123, 'profileType': profile_type} session_mock.run.return_value = result_mock identity_id = 'abc-123-def' audit_user = 'test_user' resource_type = 'Vendor' resource_uuid = '123-xyz-456' with patch.object(neo4j, '_get_neo4j_session', return_value=session_mock): actual = profile.delete_access_for_profile( session_mock, audit_user, identity_id, profile_type, profile_id, resource_type, resource_uuid, ) assert actual assert actual['profile_id'] assert actual['profile_type'] assert actual['roles'] == [] session_mock.run.assert_called_once_with( neo4j_query, identity_id=identity_id, profile_type=profile_type, profile_id=profile_id, resource_type=resource_type, resource_uuid=resource_uuid, auditUser=audit_user, ) session_mock.run.return_value.single.assert_called_once() result_mock.single.assert_called_once_with() @patch('permissions.models.profile.g') def test_add_resources_to_identity(_, app_context): """Test add_resources_to_identity.""" identity = { 'id': 'uuid', 'email': 'foo@theorchard.com', 'name': 'foo', 'auth0_user_id': 'aaa', 'user_types': ['artist'], } resources = [ { 'resource_type': 'Vendor', 'uuid': '7123', 'roles': ['analytics', 'catalog', 'payee_management'], 'explicit_profile_types': ['InsightsProfile', 'DocumentsProfile'], 'implicit_profile_types': ['LabelProfile'], }, { 'resource_type': 'Subaccount', 'uuid': '7123', 'roles': ['accounting', 'catalog'], 'explicit_profile_types': ['CollaboratorsProfile'], 'implicit_profile_types': ['LabelProfile'], }, { 'resource_type': 'LabelParticipant', 'uuid': '100', 'roles': ['analytics'], 'explicit_profile_types': ['InsightsProfile'], 'implicit_profile_types': [], }, ] profile_roles = {'InsightsProfile': ['analytics', 'catalog', 'analytics']} admin_context = { 'identity_id': 'admin-uuid', 'profile_type': 'InsightsProfile', 'profile_id': 12, } can_admin_catalog_content = True with patch.object(neo4j, '_get_neo4j_session', return_value=MagicMock()), patch.object( mysql, 'db_session', return_value=MagicMock() ), patch.object(profile, '_delete_all_user_resources'), patch.object( profile, '_delete_resource_to_settings_profile' ), patch.object(profile, 'get_identity_by_email', return_value=identity), patch.object( owsusers, 'get_auth0_user_id_by_email', return_value=None ), patch.object( label, 'get_vend_contact_with_auth0_user_id', return_value=([], 'Y') ), patch.object( label, 'create_full_vend_contact_user', return_value={'id': 1234} ), patch.object(profile, 'create_profiles_if_not_exist'), patch.object( profile, '_add_profile_specific_roles', return_value=[{'profile': 7755}] ), patch.object(profile, '_add_single_resource_to_label_profiles'), patch.object( profile, '_create_profile_with_id_if_not_exist' ), patch.object(profile, '_add_single_resource_to_profiles'), patch.object( profile, '_update_identity_user_type' ): actual = profile.add_resources_to_identity( identity, resources, profile_roles, admin_context, can_admin_catalog_content, False ) assert actual profile._delete_all_user_resources.assert_called_once # for super admin profile._delete_resource_to_settings_profile.assert_called_once assert label.create_full_vend_contact_user.call_count == 2 # for Vendor & Subaccount assert profile._create_profile_with_id_if_not_exist.call_count == 2 # twice for LabeParticipant and Vendor with Analytic role assert profile.create_profiles_if_not_exist.call_count == 3 assert profile._add_single_resource_to_profiles.call_count == 3 assert profile._update_identity_user_type.call_count == 1 add_resource_calls = profile._add_single_resource_to_profiles.call_args_list create_profile_calls = profile.create_profiles_if_not_exist.call_args_list for index, res in enumerate(resources): assert create_profile_calls[index][0] == ( ANY, identity['id'], identity['name'], [{'profile_type': t, 'roles': []} for t in res['explicit_profile_types']], admin_context['identity_id'], ) assert add_resource_calls[index][0] == ( ANY, res['explicit_profile_types'], identity['id'], res['resource_type'], res['uuid'], admin_context['identity_id'], ) label_calls = profile._create_profile_with_id_if_not_exist.call_args_list # no payee role on LabelProfile node. assert label_calls[0][0][1] == identity['id'] assert label_calls[0][0][2] == 1234 assert label_calls[0][0][3] == constants.LABELPROFILE assert set(label_calls[0][0][4]) == {'analytics', 'catalog'} assert label_calls[0][0][5] == admin_context['identity_id'] @patch('permissions.models.profile.g') def test_add_resources_to_identity_no_overwrite(_, app_context): """Test add_resources_to_identity.""" identity = {'id': 'uuid', 'email': 'new@theorchard.com', 'name': 'foo', 'auth0_user_id': 'aaa'} resources = [ { 'resource_type': 'Vendor', 'uuid': '7123', 'roles': ['analytics', 'catalog'], 'explicit_profile_types': ['InsightsProfile'], 'implicit_profile_types': ['LabelProfile'], } ] profile_roles = {'InsightsProfile': ['analytics', 'catalog', 'analytics']} admin_context = { 'identity_id': 'admin-uuid', 'profile_type': 'InsightsProfile', 'profile_id': 12, } can_admin_catalog_content = True with patch.object(neo4j, '_get_neo4j_session', return_value=MagicMock()), patch.object( mysql, 'db_session', return_value=MagicMock() ), patch.object(profile, '_delete_all_user_resources'), patch.object( profile, '_delete_resource_to_settings_profile' ), patch.object(profile, 'get_identity_by_email', return_value=identity), patch.object( owsusers, 'get_auth0_user_id_by_email', return_value=None ), patch.object( label, 'get_vend_contact_with_auth0_user_id', return_value=([], 'Y') ), patch.object(label, 'deactivate_other_vend_contacts', return_value=None), patch.object( label, 'create_full_vend_contact_user', return_value={'id': 1234} ), patch.object(profile, 'create_profiles_if_not_exist'), patch.object( profile, '_add_profile_specific_roles', return_value=[{'profile': 7755}] ), patch.object(profile, '_add_single_resource_to_label_profiles'), patch.object( profile, '_add_single_resource_to_profiles' ): actual = profile.add_resources_to_identity( identity, resources, profile_roles, admin_context, can_admin_catalog_content, False, False, ) assert actual assert profile._delete_all_user_resources.call_count == 0 assert profile._delete_resource_to_settings_profile.call_count == 0 assert label.create_full_vend_contact_user.call_count == 1 assert profile.create_profiles_if_not_exist.call_count == 1 assert profile._add_single_resource_to_profiles.call_count == 1 assert label.deactivate_other_vend_contacts.call_count == 0 # overwrite is false @patch('permissions.models.profile.g') def test_add_resources_to_identity_no_overwrite_upsert(_, app_context): """Test add_resources_to_identity.""" identity = {'id': 'uuid', 'email': 'new@theorchard.com', 'name': 'foo', 'auth0_user_id': 'aaa'} resources = [ { 'resource_type': 'Vendor', 'uuid': '7123', 'roles': ['analytics', 'catalog'], 'explicit_profile_types': ['InsightsProfile'], 'implicit_profile_types': ['LabelProfile'], } ] profile_roles = {'InsightsProfile': ['analytics', 'catalog', 'analytics']} admin_context = { 'identity_id': 'admin-uuid', 'profile_type': 'InsightsProfile', 'profile_id': 12, } can_admin_catalog_content = True vend_contact = ( {'id': 1234, 'active': 'Y', 'all_roles': {4}, 'vendor_id': 7123, 'subaccount_id': 12345}, None, ) with patch.object(neo4j, '_get_neo4j_session', return_value=MagicMock()), patch.object( mysql, 'db_session', return_value=MagicMock() ), patch.object(profile, '_delete_all_user_resources'), patch.object( profile, '_delete_resource_to_settings_profile' ), patch.object(profile, 'get_identity_by_email', return_value=identity), patch.object( owsusers, 'get_auth0_user_id_by_email', return_value=None ), patch.object( label, 'get_vend_contact_with_auth0_user_id', return_value=vend_contact ), patch.object(label, 'deactivate_other_vend_contacts', return_value=None), patch.object( profile, 'create_profiles_if_not_exist' ), patch.object( profile, '_add_profile_specific_roles', return_value=[{'profile': 7755}] ), patch.object(profile, '_add_single_resource_to_label_profiles'), patch.object( profile, '_add_single_resource_to_profiles' ): actual = profile.add_resources_to_identity( identity, resources, profile_roles, admin_context, can_admin_catalog_content, False, False, ) assert actual assert profile._delete_all_user_resources.call_count == 0 assert profile._delete_resource_to_settings_profile.call_count == 0 assert profile.create_profiles_if_not_exist.call_count == 1 assert profile._add_single_resource_to_profiles.call_count == 1 assert label.deactivate_other_vend_contacts.call_count == 0 # overwrite is false assert label.get_vend_contact_with_auth0_user_id.call_count == 1 @patch('permissions.models.profile.g') def test_add_resources_to_identity_new_identity(_, app_context): """Test add_resources_to_identity creates a new identity if not found.""" identity = { 'id': 'uuid', 'email': 'foo@theorchard.com', 'name': 'foo', 'auth0_user_id': 'aaa', 'user_types': ['label', 'artist'], } auth0_create_mock = response.Response(message=identity) resources = [ { 'resource_type': 'Vendor', 'uuid': '7123', 'roles': ['analytics', 'catalog'], 'explicit_profile_types': ['InsightsProfile'], 'implicit_profile_types': ['LabelProfile'], }, { 'resource_type': 'Subaccount', 'uuid': '7123', 'roles': ['accounting', 'catalog'], 'explicit_profile_types': [], 'implicit_profile_types': ['LabelProfile'], }, { 'resource_type': 'LabelParticipant', 'uuid': '100', 'roles': ['analytics'], 'explicit_profile_types': ['InsightsProfile'], 'implicit_profile_types': [], }, ] profile_roles = {'InsightsProfile': ['analytics', 'catalog', 'analytics']} admin_context = { 'identity_id': 'admin-uuid', 'profile_type': 'InsightsProfile', 'profile_id': 12, } can_admin_catalog_content = True with patch.object(neo4j, '_get_neo4j_session', return_value=MagicMock()), patch.object( mysql, 'db_session', return_value=MagicMock() ), patch.object(profile, '_delete_all_user_resources'), patch.object( profile, '_delete_resource_to_settings_profile' ), patch.object(profile, 'get_identity_by_email', return_value=None), patch.object( auth0, 'create_user', return_value=auth0_create_mock ), patch.object( label, 'get_vend_contact_with_auth0_user_id', return_value=([], 'Y') ), patch.object(label, 'deactivate_other_vend_contacts', return_value=None), patch.object( label, 'create_full_vend_contact_user', return_value={'id': 1234} ), patch.object(profile, 'create_profiles_if_not_exist'), patch.object( profile, '_add_profile_specific_roles', return_value=[{'profile': 7755}] ), patch.object(profile, '_add_single_resource_to_label_profiles'), patch.object( profile, '_add_single_resource_to_profiles' ), patch.object(identity_model, 'run_neo4j_identity_create'), patch.object( owsusers, 'get_auth0_user_id_by_email', return_value=None ), patch.object(profile, '_create_settings_profile_if_not_exist'): actual = profile.add_resources_to_identity( identity, resources, profile_roles, admin_context, can_admin_catalog_content, False ) assert actual assert actual.message['identity_created'] # assert defaults are properly set on identity creation assert len(actual.message['identities_affected']) == 1 identity_model.run_neo4j_identity_create.assert_called_once() args = identity_model.run_neo4j_identity_create.call_args assert args[0][3] == identity['email'] assert args[0][4] is None assert args[0][6] == constants.LOCALES['English'] assert args[0][7] == constants.NUMBER_FORMAT[0] assert args[0][10] == ['label', 'artist'] assert args[1]['is_employee'] is True # is_employee for @theorchard.com email assert profile._create_settings_profile_if_not_exist.call_count == 1 assert label.deactivate_other_vend_contacts.call_count == 1 @patch('permissions.models.profile.g') def test_add_resources_to_identity_no_auth0_user(_, app_context): """Test add_resources_to_identity with create_auth0_user=False.""" identity = { 'id': 'uuid', 'email': 'foo@theorchard.com', 'name': 'foo', 'auth0_user_id': 'aaa', 'user_types': ['label', 'artist'], } auth0_create_mock = response.Response(message=identity) resources = [ { 'resource_type': 'Vendor', 'uuid': '7123', 'roles': ['analytics', 'catalog'], 'explicit_profile_types': ['InsightsProfile'], 'implicit_profile_types': ['LabelProfile'], } ] profile_roles = {'InsightsProfile': ['analytics', 'catalog', 'analytics']} admin_context = { 'identity_id': 'admin-uuid', 'profile_type': 'InsightsProfile', 'profile_id': 12, } can_admin_catalog_content = True with patch.object(neo4j, '_get_neo4j_session', return_value=MagicMock()), patch.object( mysql, 'db_session', return_value=MagicMock() ), patch.object(profile, '_delete_all_user_resources'), patch.object( profile, '_delete_resource_to_settings_profile' ), patch.object(profile, 'get_identity_by_email', return_value=None), patch.object( owsusers, 'get_auth0_user_id_by_email', return_value=None ), patch.object(auth0, 'create_user', return_value=auth0_create_mock), patch.object( label, 'get_vend_contact_with_auth0_user_id', return_value=([], 'Y') ), patch.object(label, 'deactivate_other_vend_contacts', return_value=None), patch.object( label, 'create_full_vend_contact_user', return_value={'id': 1234} ), patch.object(profile, 'create_profiles_if_not_exist'), patch.object( profile, '_add_profile_specific_roles', return_value=[{'profile': 7755}] ), patch.object(profile, '_add_single_resource_to_label_profiles'), patch.object( profile, '_add_single_resource_to_profiles' ), patch.object(identity_model, 'run_neo4j_identity_create'), patch.object( profile, '_create_settings_profile_if_not_exist' ): actual = profile.add_resources_to_identity( identity, resources, profile_roles, admin_context, can_admin_catalog_content, False, True, constants.AWAL_BRAND, create_auth0_user=False, ) assert actual assert actual.message['identity_created'] assert len(actual.message['identities_affected']) == 1 assert identity_model.run_neo4j_identity_create.call_count == 1 assert profile._create_settings_profile_if_not_exist.call_count == 1 assert auth0.create_user.call_count == 0 assert label.create_full_vend_contact_user.call_count == 1 assert label.deactivate_other_vend_contacts.call_count == 1 @patch('permissions.models.profile.g') def test_add_resources_to_identity_multiple_profile_types(_, app_context): """Test add_resources_to_identity with data that caused an error.""" identity = { 'id': 'uuid', 'email': 'foo@theorchard.com', 'name': 'foo', 'auth0_user_id': 'aaa', 'user_types': ['label', 'artist'], } resources = [ { 'explicit_profile_types': ['InsightsProfile'], 'implicit_profile_types': ['LabelProfile'], 'resource_type': 'Subaccount', 'roles': ['analytics'], 'uuid': 'fffa', }, { 'explicit_profile_types': ['InsightsProfile', 'MoneyhubProfile'], 'implicit_profile_types': [], 'resource_type': 'LabelParticipant', 'roles': ['administrator'], 'uuid': 'aaaf', }, ] profile_roles = { 'InsightsProfile': ['analytics', 'administrator'], 'MoneyhubProfile': ['administrator'], } admin_context = {'identity_id': 'afaf', 'profile_id': 1234, 'profile_type': 'SettingsProfile'} can_admin_catalog_content = False with patch.object(neo4j, '_get_neo4j_session', return_value=MagicMock()), patch.object( mysql, 'db_session', return_value=MagicMock() ), patch.object(profile, '_delete_all_user_resources'), patch.object( profile, '_delete_resource_to_settings_profile' ), patch.object(profile, 'get_identity_by_email', return_value=identity), patch.object( owsusers, 'get_auth0_user_id_by_email', return_value=None ), patch.object( label, 'get_vend_contact_with_auth0_user_id', return_value=([], 'Y') ), patch.object( label, 'create_full_vend_contact_user', return_value={'id': 1234} ), patch.object(profile, 'create_profiles_if_not_exist'), patch.object( profile, '_add_profile_specific_roles', return_value=[{'profile': 7755}] ), patch.object(profile, '_add_single_resource_to_label_profiles'), patch.object( profile, '_add_single_resource_to_profiles' ), patch.object(profile, '_update_identity_user_type'): actual = profile.add_resources_to_identity( identity, resources, profile_roles, admin_context, can_admin_catalog_content, False ) assert actual profile._delete_all_user_resources.assert_called_once profile._delete_resource_to_settings_profile.assert_called_once assert label.create_full_vend_contact_user.call_count == 1 assert profile.create_profiles_if_not_exist.call_count == 2 assert profile._add_single_resource_to_profiles.call_count == 2 assert profile._update_identity_user_type.call_count == 1 @patch('permissions.models.profile.g') def test_add_vendor_star_resource_to_identity_multiple_profile_types(_, app_context): """Test _add_full_catalog_access function.""" identity = { 'id': 'uuid', 'email': 'foo@theorchard.com', 'name': 'foo', 'auth0_user_id': 'aaa', 'user_types': ['label', 'artist'], } resources = [ { 'explicit_profile_types': [ 'InsightsProfile', 'MoneyhubProfile', constants.SONGWHIPPROFILE, ], 'implicit_profile_types': [], 'resource_type': 'Vendor', 'roles': ['analytics'], 'uuid': constants.VENDOR_STAR_UUID, } ] profile_roles = { 'InsightsProfile': ['analytics', 'administrator'], 'MoneyhubProfile': ['administrator'], } admin_context = {'identity_id': 'afaf', 'profile_id': 1234, 'profile_type': 'SettingsProfile'} can_admin_catalog_content = False transaction_mock = MagicMock() transaction_mock.run.return_value = MagicMock() enter_mock = MagicMock() enter_mock.begin_transaction.return_value = transaction_mock session_mock = MagicMock() session_mock.__enter__.return_value = enter_mock with patch.object(neo4j, '_get_neo4j_session', return_value=session_mock), patch.object( mysql, 'db_session', return_value=MagicMock() ), patch.object(profile, '_delete_all_user_resources'), patch.object( profile, '_delete_resource_to_settings_profile' ), patch.object(profile, 'get_identity_by_email', return_value=identity), patch.object( owsusers, 'get_auth0_user_id_by_email', return_value=None ), patch.object( label, 'get_vend_contact_with_auth0_user_id', return_value=([], 'Y') ), patch.object( label, 'create_full_vend_contact_user', return_value={'id': 1234} ), patch.object(profile, 'create_profiles_if_not_exist'), patch.object( profile, '_add_profile_specific_roles', return_value=[{'profile': 7755}] ), patch.object(profile, '_add_single_resource_to_label_profiles'), patch.object( profile, '_add_single_resource_to_profiles' ), patch.object( profile, '_add_full_catalog_access', return_value=[{'profile': 5555555}] ), patch.object(profile, '_update_identity_user_type'): actual = profile.add_resources_to_identity( identity, resources, profile_roles, admin_context, can_admin_catalog_content, False ) assert actual profile._delete_resource_to_settings_profile.assert_called_once profile._add_full_catalog_access.assert_has_calls( [ call( enter_mock.begin_transaction(), identity['id'], constants.SETTINGSPROFILE, ), call( enter_mock.begin_transaction(), identity['id'], constants.INSIGHTSPROFILE, ), call( enter_mock.begin_transaction(), identity['id'], constants.SONGWHIPPROFILE, ), ] ) @pytest.mark.parametrize( 'overwrite_existing_access', [ pytest.param(False, id="Don't overwrite existing access (default for this method)"), pytest.param(True, id='Overwrite existing access (set for calls from graphql-user)'), ], ) @patch('permissions.models.profile.g') def test_add_resources_to_identity_gsuite_user(_, overwrite_existing_access, app_context): """Test add_resources_to_identity when gsuite user does not have auth0_user_id.""" identity = {'id': 'uuid', 'email': 'foo@theorchard.com', 'name': 'foo', 'user_types': []} resources = [ { 'resource_type': 'Vendor', 'uuid': '7123', 'roles': ['analytics', 'catalog'], 'explicit_profile_types': ['InsightsProfile'], 'implicit_profile_types': ['LabelProfile'], } ] profile_roles = {'InsightsProfile': ['analytics']} admin_context = { 'identity_id': 'admin-uuid', 'profile_type': 'SettingsProfile', 'profile_id': 12, } can_admin_catalog_content = True can_access_vendor_star = False with patch.object(neo4j, '_get_neo4j_session', return_value=MagicMock()), patch.object( mysql, 'db_session', return_value=MagicMock() ), patch.object(profile, '_delete_all_user_resources'), patch.object( profile, '_delete_resource_to_settings_profile' ), patch.object(profile, 'get_identity_by_email', return_value=identity), patch.object( owsusers, 'get_auth0_user_id_by_email', return_value=None ), patch.object(auth0, 'create_user'), patch.object( label, 'get_vend_contact_with_auth0_user_id', return_value=([], 'Y') ), patch.object(label, 'deactivate_other_vend_contacts', return_value=None), patch.object( label, 'create_full_vend_contact_user', return_value={'id': 1234} ), patch.object(profile, 'create_profiles_if_not_exist'), patch.object( profile, '_add_profile_specific_roles', return_value=[{'profile': 7755}] ), patch.object(profile, '_add_single_resource_to_label_profiles'), patch.object( profile, '_add_single_resource_to_profiles' ), patch.object(profile, '_remove_full_catalog_access'): actual = profile.add_resources_to_identity( identity, resources, profile_roles, admin_context, can_access_vendor_star, can_admin_catalog_content, overwrite_existing_access, False, ) assert actual assert label.create_full_vend_contact_user.call_count == 1 assert profile.create_profiles_if_not_exist.call_count == 1 assert profile._add_single_resource_to_profiles.call_count == 1 assert profile._delete_all_user_resources.call_count == 0 assert profile._remove_full_catalog_access.call_count == 3 if overwrite_existing_access: assert profile._delete_resource_to_settings_profile.call_count == 1 assert auth0.create_user.call_count == 0 else: assert profile._delete_resource_to_settings_profile.call_count == 0 assert label.deactivate_other_vend_contacts.call_count == 0 assert auth0.create_user.call_count == 1 @pytest.mark.parametrize( ('profile_types', 'identity_id', 'audit_user', 'neo4j_query'), [ ( [ {'profile_type': 'ArtistProfile', 'roles': ['analytics']}, {'profile_type': 'LabelProfile', 'roles': ['administrator']}, {'profile_type': 'InsightsProfile', 'roles': ['analytics']}, ], 'user-identity-id', 'admin-user-identity-id', """MATCH(i:Identity {id: $identity_id})-[:HAS_PROFILE]->(p:Profile) -[rel:HAS_ACCESS_TO]->(r) WHERE p.profileType in $profileTypes AND r.id <> '*' SET rel.updatedOn = datetime(), rel.updatedBy = $auditUser WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ACCESS_TO') YIELD input,output RETURN p""", ) ], ) def test_delete_all_user_resources(profile_types, identity_id, audit_user, neo4j_query): """Test _delete_all_user_resources.""" test_tx = MagicMock() with patch.object(mysql, 'db_session', return_value=MagicMock()): profile._delete_all_user_resources( tx=test_tx, profile_types=profile_types, identity_id=identity_id, audit_user=audit_user ) assert test_tx.run.called assert test_tx.run.call_args[0] == (neo4j_query,) assert test_tx.run.call_args[1]['identity_id'] == identity_id assert test_tx.run.call_args[1]['profileTypes'] == profile_types assert test_tx.run.call_args[1]['auditUser'] == audit_user @pytest.mark.parametrize( ('profile_types', 'identity_id', 'audit_user', 'neo4j_query'), [ ( [ {'profile_type': 'SettingsProfile', 'roles': ['analytics']}, {'profile_type': 'SongwhipProfile', 'roles': ['administrator']}, {'profile_type': 'InsightsProfile', 'roles': ['analytics']}, ], 'user-identity-id', 'admin-user-identity-id', """MATCH(i:Identity {id: $identity_id})-[:HAS_PROFILE]->(p:Profile) -[rel:HAS_ACCESS_TO]->(r) WHERE p.profileType in $profileTypes AND r.id = '*' SET rel.updatedOn = datetime(), rel.updatedBy = $auditUser WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ACCESS_TO') YIELD input,output RETURN p""", ) ], ) def test_delete_settings_vendor_star_resources(profile_types, identity_id, audit_user, neo4j_query): """Test _delete_all_user_resources.""" test_tx = MagicMock() with patch.object(mysql, 'db_session', return_value=MagicMock()): profile._delete_settings_vendor_star_resources( tx=test_tx, profile_types=profile_types, identity_id=identity_id, audit_user=audit_user ) assert test_tx.run.called assert test_tx.run.call_args[0] == (neo4j_query,) assert test_tx.run.call_args[1]['identity_id'] == identity_id assert test_tx.run.call_args[1]['profileTypes'] == profile_types assert test_tx.run.call_args[1]['auditUser'] == audit_user @pytest.mark.parametrize( ('profile_types', 'identity_id', 'admin_context', 'neo4j_query'), [ ( [ {'profile_type': 'ArtistProfile', 'roles': ['analytics']}, {'profile_type': 'LabelProfile', 'roles': ['administrator']}, {'profile_type': 'InsightsProfile', 'roles': ['analytics']}, ], 'user-identity-id', { 'identity_id': 'admin-user-identity-id', 'profile_type': 'InsightsProfile', 'profile_id': 123, }, """MATCH(i:Identity {id: $identityId})-[:HAS_PROFILE]->(p:Profile)-[rel:HAS_ACCESS_TO]->(r) WHERE p.profileType in $profileTypes AND r.id <> '*' WITH r, rel MATCH (admin:Identity {id: $adminIdentityId})-[:HAS_PROFILE]->(ap:Profile)-[:HAS_ACCESS_TO]->(x)-[*0..1]->(r) WHERE 'administrator' IN ap.roles AND ap.profileType = $adminProfileType AND ap.profileId = $adminProfileId SET rel.updatedBy = $adminIdentityId, rel.updatedOn = datetime() WITH rel, ap CALL apoc.refactor.setType(rel, 'DELETED_HAS_ACCESS_TO') YIELD input,output RETURN ap""", # noqa ) ], ) def test_delete_admin_specific_resources(profile_types, identity_id, admin_context, neo4j_query): """Test _delete_admin_specific_resources.""" test_tx = MagicMock() with patch.object(mysql, 'db_session', return_value=MagicMock()): profile._delete_admin_specific_resources( tx=test_tx, profile_types=profile_types, identity_id=identity_id, admin_context=admin_context, ) assert test_tx.run.called assert test_tx.run.call_args[0] == (neo4j_query,) assert test_tx.run.call_args[1]['identityId'] == identity_id assert test_tx.run.call_args[1]['profileTypes'] == profile_types assert test_tx.run.call_args[1]['adminIdentityId'] == admin_context['identity_id'] assert test_tx.run.call_args[1]['adminProfileId'] == admin_context['profile_id'] assert test_tx.run.call_args[1]['adminProfileType'] == admin_context['profile_type'] @pytest.mark.parametrize( ('profile_types', 'identity_id', 'admin_context', 'neo4j_query'), [ ( [ {'profile_type': 'ArtistProfile', 'roles': ['analytics']}, {'profile_type': 'LabelProfile', 'roles': ['administrator']}, {'profile_type': 'InsightsProfile', 'roles': ['analytics']}, ], 'user-identity-id', { 'identity_id': 'admin-user-identity-id', 'profile_type': 'InsightsProfile', 'profile_id': 123, }, """MATCH (i:Identity {id: $identityId})-[:HAS_PROFILE]->(p:Profile)-[rel:HAS_ACCESS_TO]->(r) WHERE p.profileType in $profileTypes AND r.id <> '*' WITH r, rel MATCH (admin:Identity {id: $adminIdentityId})-[:HAS_PROFILE]->(ap:Profile)-[:HAS_ADMIN_ACCESS_TO]->(x)-[*0..1]->(r) WHERE ap.profileType = 'SettingsProfile' AND ap.profileId = $adminProfileId SET rel.updatedBy = $adminIdentityId, rel.updatedOn = datetime() WITH rel, ap CALL apoc.refactor.setType(rel, 'DELETED_HAS_ACCESS_TO') YIELD input, output RETURN ap""", # noqa ) ], ) def test_delete_gda_admin_specific_resources( profile_types, identity_id, admin_context, neo4j_query ): """Test _delete_gda_admin_specific_resources.""" test_tx = MagicMock() with patch.object(mysql, 'db_session', return_value=MagicMock()): profile._delete_gda_admin_specific_resources( tx=test_tx, profile_types=profile_types, identity_id=identity_id, admin_context=admin_context, ) assert test_tx.run.called assert test_tx.run.call_args[0] == (neo4j_query,) assert test_tx.run.call_args[1]['identityId'] == identity_id assert test_tx.run.call_args[1]['profileTypes'] == profile_types assert test_tx.run.call_args[1]['adminIdentityId'] == admin_context['identity_id'] assert test_tx.run.call_args[1]['adminProfileId'] == admin_context['profile_id'] @pytest.mark.parametrize( ('identity_id', 'audit_user', 'is_super_admin', 'neo4j_query'), [ ( # Test Case 1: NOT Super Admin User, check for vend star access 'user-identity-id', 'auditing-user1', False, """MATCH (admin:Identity {id: $audit_user})-[:HAS_PROFILE] ->(ap:Profile {profileType: 'SettingsProfile'})-[:HAS_ADMIN_ACCESS_TO]->(x)-[*0..1]->(r) WITH r MATCH (i:Identity)-[:HAS_PROFILE]->(p:Profile)-[rel:HAS_ADMIN_ACCESS_TO]->(r) WHERE i.id = $identity_id AND p.profileType = 'SettingsProfile' SET rel.updatedOn = datetime(), rel.updatedBy = $audit_user WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ADMIN_ACCESS_TO') YIELD input, output RETURN p""", ), ( # Test Case 2: Super Admin User 'admin-user-identity-id', 'auditing-user2', True, """ MATCH (i:Identity)-[:HAS_PROFILE]->(p:Profile)-[rel:HAS_ADMIN_ACCESS_TO]->(r) WHERE i.id = $identity_id AND p.profileType = 'SettingsProfile' SET rel.updatedOn = datetime(), rel.updatedBy = $audit_user WITH rel, p CALL apoc.refactor.setType(rel, 'DELETED_HAS_ADMIN_ACCESS_TO') YIELD input, output RETURN p""", ), ], ) def test_delete_resource_to_settings_profile(identity_id, audit_user, is_super_admin, neo4j_query): """Test _delete_resource_to_settings_profile.""" test_tx = MagicMock() with patch.object(mysql, 'db_session', return_value=MagicMock()): profile._delete_resource_to_settings_profile( tx=test_tx, identity_id=identity_id, audit_user=audit_user, super_admin=is_super_admin ) assert test_tx.run.called assert test_tx.run.call_args[0] == (neo4j_query,) assert test_tx.run.call_args[1]['identity_id'] == identity_id assert test_tx.run.call_args[1]['audit_user'] == audit_user @patch('permissions.models.profile._get_identities_given_profile_uuid') @patch('permissions.models.profile._get_identities_given_profile') def test_get_identities_by_profile_prefers_profile_uuid(mock_get_by_profile, mock_get_by_uuid): """Test get_identities_by_profile_uses_profile uses profile uuid.""" expected_identity_uuids = ['the-expected-identity', 'some-impersonator-identity'] mock_get_by_uuid.return_value = expected_identity_uuids mock_get_by_profile.return_value = [] session_mock = get_session_mock([]) with patch.object(neo4j, '_get_neo4j_session', return_value=session_mock): actual = profile.get_identities_by_profile( 'IgnoreProfile', 1234, profile_uuid='the-profile-uuid', ) assert actual == expected_identity_uuids assert mock_get_by_profile.call_count == 0 mock_get_by_uuid.assert_called_once_with(session_mock.__enter__(), 'the-profile-uuid') @patch('permissions.models.profile._get_identities_given_profile_uuid') @patch('permissions.models.profile._get_identities_given_profile') def test_get_identities_by_profile_uses_profile(mock_get_by_profile, mock_get_by_uuid): """Test get_identities_by_profile_uses_profile uses profile type and id.""" expected_identity_uuids = ['the-expected-identity', 'some-impersonator-identity'] mock_get_by_uuid.return_value = [] mock_get_by_profile.return_value = ['the-expected-identity', 'some-impersonator-identity'] session_mock = get_session_mock([]) with patch.object(neo4j, '_get_neo4j_session', return_value=session_mock): actual = profile.get_identities_by_profile( 'MyProfile', 1234, ) assert actual == expected_identity_uuids assert mock_get_by_uuid.call_count == 0 mock_get_by_profile.assert_called_once_with(session_mock.__enter__(), 'MyProfile', 1234) def test__get_identities_given_profile_uuid(): """Test _get_identities_given_profile_uuid.""" profile_uuid = 'my-profile-uuid' session_mock = get_session_mock() session_mock.__enter__().run.return_value.data.return_value = [ {'identity_uuid': 'uuid1'}, {'identity_uuid': 'uuid2'}, ] actual = profile._get_identities_given_profile_uuid(session_mock.__enter__(), profile_uuid) assert actual == ['uuid1', 'uuid2'] assert session_mock.__enter__().run.call_count == 1 session_mock.__enter__().run.assert_called_once_with( ( 'MATCH (p:Profile {uuid: $profileUuid})<-[:HAS_PROFILE]-(i:Identity) ' 'RETURN i.id AS identity_uuid' ), profileUuid=profile_uuid, ) def test__get_identities_given_profile(): """Test _get_identities_given_profile.""" profile_type = 'DogProfile' profile_id = 123 session_mock = get_session_mock() session_mock.__enter__().run.return_value.data.return_value = [ {'identity_uuid': 'uuid1'}, {'identity_uuid': 'uuid2'}, ] actual = profile._get_identities_given_profile( session_mock.__enter__(), profile_type, profile_id, ) assert actual == ['uuid1', 'uuid2'] assert session_mock.__enter__().run.call_count == 1 session_mock.__enter__().run.assert_called_once_with( ( 'MATCH (p:Profile {profileType: $profileType, profileId: $profileId})' '<-[:HAS_PROFILE]-(i:Identity) ' 'RETURN i.id AS identity_uuid' ), profileType=profile_type, profileId=profile_id, ) @pytest.mark.parametrize( ('profile_type', 'expected_query'), [ pytest.param( 'InsightsProfile', textwrap.dedent(""" MATCH (i:Identity {id: $identity_id}) -[:HAS_PROFILE]->(p:Profile {profileType: $profile_type}) // Not really an optional match, but neo4j complains about cartesian products otherwise OPTIONAL MATCH (t:Vendor {uuid: $tenant_uuid}) OPTIONAL MATCH (p)-[existing_relationship:HAS_ACCESS_TO]->(t) OPTIONAL MATCH (p)-[deleted_rel:DELETED_HAS_ACCESS_TO]->(t) MERGE (p)-[r:HAS_ACCESS_TO]->(t) ON CREATE SET r.createdAt = datetime(), r.createdBy = $audit_user_id DELETE deleted_rel RETURN p.id, existing_relationship """), id='InsightsProfile', ), pytest.param( 'SettingsProfile', textwrap.dedent(""" MATCH (i:Identity {id: $identity_id}) -[:HAS_PROFILE]->(p:Profile {profileType: $profile_type}) // Not really an optional match, but neo4j complains about cartesian products otherwise OPTIONAL MATCH (t:Vendor {uuid: $tenant_uuid}) OPTIONAL MATCH (p)-[existing_relationship:HAS_ADMIN_ACCESS_TO]->(t) OPTIONAL MATCH (p)-[deleted_rel:DELETED_HAS_ADMIN_ACCESS_TO]->(t) MERGE (p)-[r:HAS_ADMIN_ACCESS_TO]->(t) ON CREATE SET r.createdAt = datetime(), r.createdBy = $audit_user_id DELETE deleted_rel RETURN p.id, existing_relationship """), id='SettingsProfile', ), ], ) @patch('permissions.models.profile.get_resources_key') @patch('permissions.models.profile.redis') def test_add_tenant_connection_to_profile_and_clear_cache_settings_and_non( redis_mock: MagicMock, get_key_mock: MagicMock, profile_type: str, expected_query: str ): """Test add_tenant_connection_to_profile_and_clear_cache for settings vs. other profiles.""" tx_mock = MagicMock() params = { 'tx': tx_mock, 'identity_id': 'some-uuid', 'profile_type': profile_type, 'tenant': Tenant(tenant_uuid='another-uuid', tenant_type=TenantType.ACCOUNT), 'audit_user_id': 'yet-another-uuid', } profile_id = 123123 tx_mock.run.return_value.single.return_value = {'p.id': profile_id} redis_key = 'resources_for_{profile_type}_{profile_id}_*' get_key_mock.return_value = redis_key profile.add_tenant_connection_to_profile_and_clear_cache(**params) tx_mock.run.assert_called_with( expected_query, identity_id=params['identity_id'], profile_type=profile_type, tenant_uuid=params['tenant'].tenant_uuid, audit_user_id=params['audit_user_id'], ) redis_mock.delete_all_matching_pattern.assert_called_with(redis_key) @patch('permissions.models.profile.g') @patch('permissions.models.profile.redis') def test_add_tenant_connection_to_profile_and_clear_cache_existing( redis_mock: MagicMock, g_mock: MagicMock, app_context: Any ): """Test add_tenant_connection_to_profile_and_clear_cache behavior for existing profile.""" tx_mock = MagicMock() params = { 'tx': tx_mock, 'identity_id': 'some-uuid', 'profile_type': 'InsightsProfile', 'tenant': Tenant(tenant_uuid='another-uuid', tenant_type=TenantType.ACCOUNT), 'audit_user_id': 'yet-another-uuid', } tx_mock.run.return_value.single.return_value = {'existing_relationship': 'truthy value'} profile.add_tenant_connection_to_profile_and_clear_cache(**params) g_mock.log.warn.assert_called_with( 'Existing relationship found between profile and tenant during user invite', resources={ 'tenant_neo_type': 'Vendor', 'tenant_uuid': 'another-uuid', 'profile_type': params['profile_type'], 'identity_id': params['identity_id'], }, ) redis_mock.delete_all_matching_pattern.assert_not_called() def test_get_label_profile_by_identity_and_tenant(): """Test get_label_profile_by_identity_and_tenant.""" tx_mock = MagicMock() params = { 'tx': tx_mock, 'identity_id': 'some-uuid', 'tenant': Tenant(tenant_uuid='another-uuid', tenant_type=TenantType.ACCOUNT), } profile_attrs = { 'profileId': 11111, 'profileType': 'LabelProfile', 'roles': ['catalog', 'advertising'], 'uuid': 'profile-uuid', } tx_mock.run.return_value.single.return_value = { 'p': profile_attrs, 'r': MagicMock(type='HAS_ACCESS_TO'), } result = profile.get_label_profile_by_identity_and_tenant(**params) assert result == { 'profile_id': profile_attrs['profileId'], 'profile_type': profile_attrs['profileType'], 'roles': profile_attrs['roles'], 'uuid': profile_attrs['uuid'], 'tenant_relationship': 'HAS_ACCESS_TO', } tx_mock.run.assert_called_with( textwrap.dedent(""" MATCH (i:Identity {id: $identity_id}) -[:HAS_PROFILE]->(p:Profile {profileType: 'LabelProfile'}) -[r:HAS_ACCESS_TO|DELETED_HAS_ACCESS_TO] ->(t:Vendor {uuid: $tenant_uuid}) RETURN p, r """), identity_id=params['identity_id'], tenant_uuid=params['tenant'].tenant_uuid, ) def test_create_or_update_label_profile_with_tenant_relationship(): """Test create_or_update_label_profile_with_tenant_relationship.""" tx_mock = MagicMock() params = { 'tx': tx_mock, 'identity_id': 'some-uuid', 'profile_id': 11111, 'roles': ['catalog', 'advertising'], 'tenant': Tenant(tenant_uuid='another-uuid', tenant_type=TenantType.ACCOUNT), 'audit_user_id': 'yet-another-uuid', } profile.create_or_update_label_profile_with_tenant_relationship(**params) tx_mock.run.assert_called_with( textwrap.dedent(""" MATCH (t:Vendor {uuid: $tenant_uuid}) MATCH (i:Identity {id: $identityId}) OPTIONAL MATCH (p:Profile {profileId: $profileId, profileType: $profileType}) -[deleted_rel:DELETED_HAS_ACCESS_TO]->(t) MERGE (pNew:Profile {profileId: $profileId, profileType: $profileType}) ON CREATE SET pNew.profileName = coalesce($profileName, (i.name + $profileType)), pNew.roles = $roles, pNew.lastModifiedBy = $auditUser, pNew.lastModifiedAt = datetime(), pNew.uuid = randomUUID(), pNew.createdAt = datetime(), pNew.createdBy = $auditUser ON MATCH SET pNew.roles = $roles, pNew.lastModifiedBy = $auditUser, pNew.lastModifiedAt = datetime() MERGE (i)-[rel:HAS_PROFILE]->(pNew) SET rel.lastModifiedBy = $auditUser, rel.lastModifiedAt = datetime(), rel.createdAt = datetime(), rel.createdBy = $auditUser MERGE (pNew)-[r:HAS_ACCESS_TO]->(t) SET r.lastModifiedBy = $auditUser, r.lastModifiedAt = datetime(), r.createdAt = datetime(), r.createdBy = $auditUser DELETE deleted_rel """), tenant_uuid=params['tenant'].tenant_uuid, identityId=params['identity_id'], profileId=params['profile_id'], profileName=params['identity_id'], profileType='LabelProfile', roles=params['roles'], auditUser=params['audit_user_id'], ) def test_create_profiles_with_increments() -> None: """Test create_profiles_if_not_exist.""" tx_mock = MagicMock() profile_data = [ {'profile_type': 'SettingsProfile', 'roles': []}, {'profile_type': 'InsightsProfile', 'roles': ['analytics']}, ] tx_mock.run.return_value = Mock( data=lambda: [ {'pNew': {'profileType': 'SettingsProfile', 'profileId': 1}}, {'pNew': {'profileType': 'InsightsProfile', 'profileId': 2}}, ] ) profile.create_profiles_if_not_exist( tx=tx_mock, identity_id='test-id', profile_name='Test Profile', profile_types_and_roles=profile_data, audit_user='test-user', ) assert tx_mock.run.call_count == 1 call_args = tx_mock.run.call_args assert call_args.kwargs['profileTypesAndRoles'] == profile_data query = call_args.args[0] assert query == textwrap.dedent(""" MATCH (i:Identity {id: $identityId}) MATCH (inc:IncrementId {nodeName: 'Profile'}) WITH i, inc UNWIND $profileTypesAndRoles AS profileTypesAndRoles WITH i, inc, profileTypesAndRoles.profile_type AS profileType, profileTypesAndRoles.roles AS roles MATCH (i) WHERE NOT (i)-[:HAS_PROFILE]->(:Profile {profileType: profileType}) // Get new increment atomically CALL apoc.atomic.add(inc, 'id', 1, 3) YIELD newValue AS newIncrement // Then create profile with new profileId WITH i, profileType, roles, newIncrement CREATE (pNew:Profile { profileType: profileType, profileId: newIncrement, profileName: coalesce($profileName, (i.name + profileType)), roles: roles, uuid: randomUUID(), updatedBy: $auditUser, updatedOn: datetime(), createdAt: datetime(), createdBy: $auditUser, lastModifiedAt: datetime(), lastModifiedBy: $auditUser }) // connect the profile to the identity MERGE (i)-[rel:HAS_PROFILE]->(pNew) SET rel.updatedBy = $auditUser, rel.updatedOn = datetime(), rel.createdAt = datetime(), rel.createdBy = $auditUser, rel.lastModifiedAt = datetime(), rel.lastModifiedBy = $auditUser RETURN pNew """) @pytest.mark.parametrize( 'vendor_star, expected_result, test_id', [ pytest.param(True, True, 'has_vendor_star_access', id='has_vendor_star_access'), pytest.param(False, False, 'no_vendor_star_access', id='no_vendor_star_access'), pytest.param(None, False, 'profile_not_found', id='profile_not_found'), ], ) @patch('permissions.models.profile.neo4j.db_session') def test_check_vendor_star_access_v2( mock_db_session: MagicMock, vendor_star: bool | None, expected_result: bool, test_id: str ) -> None: """Test check_vendor_star_access_v2.""" # set up test data identity_id = uuid.uuid4() profile_id = 12345 profile_type = 'AbacusProfile' # set up mock session mock_session = MagicMock() mock_db_session.return_value.__enter__.return_value = mock_session # configure the mock behavior based on the test case if test_id == 'profile_not_found': # mock the profile not found case mock_session.run.return_value.single.return_value = None else: # mock the record with vendor star access value mock_record = MagicMock() mock_record.__getitem__.return_value = vendor_star mock_session.run.return_value.single.return_value = mock_record result = profile.check_vendor_star_access_v2( identity_id=identity_id, profile_id=profile_id, profile_type=profile_type ) # assert result matches expected assert result is expected_result # test session query was called mock_session.run.assert_called_once() # test the query was called with correct cypher expected_query = textwrap.dedent(""" MATCH (i:Identity {id: $identity_id})-[:HAS_PROFILE]-> (p:Profile {profileType: $profile_type, profileId: $profile_id, fullCatalogAccess: true}) OPTIONAL MATCH (p)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(v:Vendor {id: '*'}) RETURN (v IS NOT NULL) AS vendorStarAccess""").strip() actual_query = mock_session.run.call_args[0][0] assert actual_query.strip() == expected_query # test the query parameters actual_params = mock_session.run.call_args[1] assert actual_params['identity_id'] == str(identity_id) assert actual_params['profile_id'] == profile_id assert actual_params['profile_type'] == profile_type def test_update_existing_profiles_with_roles() -> None: """Test update_existing_profiles_with_roles.""" tx = MagicMock() identity = MagicMock() identity.id = 'test-identity-id' audit_user_id = 'audit-user-id' profile_role_dicts = [ {'profile_type': 'InsightsProfile', 'roles': ['analytics']}, ] profile.update_existing_profiles_with_roles( tx=tx, identity_id=identity.id, profile_types_and_roles=profile_role_dicts, audit_user=audit_user_id, ) tx.run.assert_called_once_with( textwrap.dedent(""" MATCH (i:Identity {id: $identityId}) UNWIND $profileTypesAndRoles AS profileTypesAndRoles WITH i, profileTypesAndRoles.profile_type AS profileType, profileTypesAndRoles.roles AS roles MATCH (i)-[:HAS_PROFILE]->(p:Profile {profileType: profileType}) WHERE p.roles <> roles SET p.roles = roles, p.updatedBy = $auditUser, p.updatedOn = datetime() RETURN p """), identityId=identity.id, profileTypesAndRoles=profile_role_dicts, auditUser=audit_user_id, )