"""Test for a vendor model.""" import textwrap from unittest.mock import MagicMock, patch import pytest from neo4j.exceptions import ClientError, DatabaseError, TransientError from account.models import neo4j_vendor from tests.unit.conftest import get_transactional_session_mock @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_create_or_update_vendor(mock_session): """Test create_vendor.""" mock_session.return_value = get_transactional_session_mock([{'some': 'result'}]) data = { 'vendor_id': 1234, 'name': 'foo', 'owner': 'bar', 'contact_email': 'abc@xyz.com', 'status': 'signed', 'source': 'event.gdaApproval', 'vendor_uuid': 'd6455851-3f86-48d7-b102-8d006eb92655', } response = neo4j_vendor.create_or_update_vendor(data) assert response assert response.message == 'Vendor successfully created or updated.' @pytest.mark.parametrize( 'details', [ # currently supported fields for update { 'name': 'foo', }, # poc: update_vendor can handle more fields # if we need to expand logic layer. { 'name': 'foo', 'isDistributor': 'N', 'labelIdentifier': 'Backline', 'status': 'signed', }, ], ) @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_update_vendor(mock_session, details): """Test v2 update_vendor.""" mock_session.return_value = get_transactional_session_mock([{'some': 'result'}]) response = neo4j_vendor.update_vendor(1234, details) assert response assert response.message == 'Vendor successfully updated.' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_update_vendor_not_found(mock_session): """Test v2 update_vendor not found.""" details = {'name': 'Vendor'} mock_session.return_value = get_transactional_session_mock([None]) with pytest.raises(Exception): response = neo4j_vendor.update_vendor(1234, details) assert response.message == 'Failed to update Vendor 1234 in Neo4j' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_update_vendor_exceptions(mock_session): """Test v2 update_vendor with common Neo4j Exceptions.""" details = {'name': 'Vendor'} mock_session.return_value = get_transactional_session_mock( [ClientError, DatabaseError, TransientError] ) with pytest.raises(Exception): response = neo4j_vendor.update_vendor(1234, details) assert response.message == 'Failed to update Vendor 1234 in Neo4j' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_add_company_brand(mock_session): """Test add_company_brand.""" mock_session.return_value = get_transactional_session_mock([{'some': 'result'}]) vendor_id = 1234 company_brand = 'awal' response = neo4j_vendor.add_company_brand(vendor_id, company_brand) assert response assert response.message == 'Company Brand successfully added to Vendor.' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_add_service_tier(mock_session): """Test add_service_tier.""" mock_session.return_value = get_transactional_session_mock([{'some': 'result'}]) vendor_id = 1234 service_tier = 'diy-tier-1' response = neo4j_vendor.add_service_tier(vendor_id, service_tier) assert response assert response.message == 'Service Tier successfully added to Vendor.' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_create_vendor_no_data(mock_session): """Test create_vendor.""" mock_session.return_value = get_transactional_session_mock([None]) data = { 'vendor_id': 1234, 'name': 'foo', 'owner': 'bar', 'contact_email': 'abc@xyz.com', 'status': 'signed', 'source': 'event.gdaApproval', } with pytest.raises(Exception): neo4j_vendor.create_vendor(data) @pytest.mark.parametrize( ( 'test_description', 'neo4j_response', 'expected_error', 'expected_response', ), [ ( 'success', MagicMock(data=MagicMock(return_value={'st': {'uuid': '123', 'name': 'service'}})), None, {'uuid': '123', 'name': 'service'}, ), ( 'no service tier in neo4j response', MagicMock(data=MagicMock(return_value={'st': None})), {'code': 'service_tier_not_found', 'message': 'Service Tier Not Found'}, None, ), ( 'empty neo4j response', None, {'code': 'not_found_error', 'message': 'Not found'}, None, ), ], ) def test_get_vendor_service_tier( mocker, test_description, neo4j_response, expected_error, expected_response, ): mocker.patch.object( neo4j_vendor, 'get_session', return_value=get_transactional_session_mock([neo4j_response]), ) vendor_id = 1234 result = neo4j_vendor.get_vendor_service_tier(vendor_id) assert result.errors == expected_error assert result.message == expected_response @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_get_company_brand(mock_session): mock_session.return_value = get_transactional_session_mock([{'name': 'awal'}]) vendor_id = 1234 response = neo4j_vendor.get_company_brand(vendor_id) assert response assert response.message == 'awal' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_get_company_brand_no_result(mock_session): mock_session.return_value = get_transactional_session_mock([None]) vendor_id = 1234 with pytest.raises(Exception): neo4j_vendor.get_company_brand(vendor_id) @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_update_company_brand(mock_session): mock_session.return_value = get_transactional_session_mock( [{'cb': 'awal'}, {'v': 'mockVendor'}] ) vendor_id = 1234 response = neo4j_vendor.update_company_brand(vendor_id, 'awal') assert response assert response.message == 'Company Brand successfully updated for vendor.' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_update_company_brand_no_result(mock_session): mock_session.return_value = get_transactional_session_mock([{}]) vendor_id = 1234 with pytest.raises(Exception): neo4j_vendor.update_company_brand(vendor_id) @pytest.mark.parametrize( 'neo4j_session_res', [ # complete results { 'country': 'USA', 'source': 'gda.KafkaTopic', 'company_brand': 'awal', 'service_tier_uuid': '412-uu1d-alph4n-um3ric', 'service_tier_name': 'tier-test', }, # optional matches {'country': 'USA', 'source': 'gda.KafkaTopic'}, {'country': 'USA', 'source': 'gda.KafkaTopic', 'company_brand': 'awal'}, { 'country': 'USA', 'source': 'gda.KafkaTopic', 'service_tier_uuid': '412-uu1d-alph4n-um3ric', 'service_tier_name': 'tier-test', }, ], ) @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_get_neo4j_vendor_info(mock_session, neo4j_session_res): mock_session.return_value = get_transactional_session_mock([{'data': neo4j_session_res}]) response = neo4j_vendor.get_neo4j_vendor_info(1234) assert response assert response.message == neo4j_session_res @pytest.mark.parametrize( ('profile_id', 'profile_type', 'vendor_id', 'expected_value'), [ ( # Test Case 1: Requesting Profile DOES NOT HAVE access to Vendor or Vendor(*) '2', 'ArtistProfile', '33811', {'p': False}, ), ( # Test Case 2: Requesting Profile HAS direct access to Vendor, but not Vendor (*) '127877', 'InsightsProfile', '33811', {'p': True}, ), ( # Test Case 3: Requesting Profile DOES NOT HAVE access to Vendor, but has access to Vendor(*) # noqa: E501 '318660', 'InsightsProfile', '33811', {'p': True}, ), ( # Test Case 4: Requesting Profile HAS access to BOTH Vendor and Vendor(*) '318660', 'InsightsProfile', '6971', {'p': True}, ), ], ) @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_profile_has_access_to_vendor( mock_session, profile_id, profile_type, vendor_id, expected_value ): mock_session.return_value = get_transactional_session_mock([expected_value]) response = neo4j_vendor.profile_has_access_to_vendor(profile_id, profile_type, vendor_id) if expected_value['p']: assert response else: assert not response @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_get_neo4j_vendor_info_not_found(mock_session): mock_session.return_value = get_transactional_session_mock([None]) response = neo4j_vendor.get_neo4j_vendor_info(1234) assert not response assert response.status == 404 assert response.errors['message'] == 'Not found' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_update_service_tier(mock_session): mock_session.return_value = get_transactional_session_mock( [{'st': 'diy-tier-1'}, {'v': 'mockVendor'}] ) vendor_id = 1234 response = neo4j_vendor.update_service_tier(vendor_id, 'tier-2') assert response assert response.message == 'Service tier successfully updated for vendor.' @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_update_service_tier_no_result(mock_session): mock_session.return_value = get_transactional_session_mock([{}]) vendor_id = 1234 with pytest.raises(Exception): neo4j_vendor.update_service_tier(vendor_id) @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_get_service_tiers(mock_session): expected_result = [ {'uuid': '5f2bd4fc-df94-4f35-97d3-ef23f8573279', 'displayName': 'Basic'}, { 'uuid': '1dd92c83-25a3-4034-9a3d-f7c3f434f4ce', 'displayName': 'Premium Services', }, ] mock_session.return_value = get_transactional_session_mock([expected_result]) response = neo4j_vendor.get_service_tiers() assert response assert response.message == expected_result @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_accessible_vendors_success_without_identity(mock_session): profile_id = 'profile123' profile_type = 'InsightsProfile' vendor_uuids = ['uuid1', 'uuid2'] expected_uuids = ['uuid1', 'uuid2'] mock_vendors = [{'uuids': ['uuid1', 'uuid2']}] mock_session.return_value = get_transactional_session_mock(mock_vendors) uuids = neo4j_vendor.accessible_vendors( profile_id=profile_id, profile_type=profile_type, vendor_uuids=vendor_uuids, identity_id=None, ) mock_session.return_value.run.assert_called_once_with( textwrap.dedent( """ MATCH (p:Profile {profileType: $profileType, profileId: toInteger($profileId)})<-[:HAS_PROFILE]-(i:Identity) // Collect all profiles for the identity, including p WITH i, p, [p] + [ (i)-[:HAS_PROFILE]->(otherProfile:Profile) WHERE otherProfile <> p | otherProfile ] AS allProfiles // Check if any profile has full catalog access WITH i, allProfiles, coalesce( any(profile IN allProfiles WHERE profile.fullCatalogAccess = true AND exists((profile)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(:Vendor {id: '*'})) ), false ) AS hasFullAccess CALL apoc.when( hasFullAccess, 'RETURN $vendorUuids AS uuids', ' // Check direct access for all profiles UNWIND $allProfiles AS profile OPTIONAL MATCH (profile)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(v1:Vendor) WHERE v1.uuid IN $vendorUuids RETURN apoc.coll.toSet(COLLECT(DISTINCT v1.uuid)) AS uuids ', {allProfiles: allProfiles, vendorUuids: $vendorUuids} ) YIELD value RETURN value.uuids AS uuids """ # noqa: E501 ), vendorUuids=vendor_uuids, profileId=profile_id, profileType=profile_type, ) assert uuids == expected_uuids @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_accessible_vendors_success_with_identity(mock_session): identity_id = 'abc-123' profile_id = 'profile123' profile_type = 'InsightsProfile' vendor_uuids = ['uuid1', 'uuid2'] expected_uuids = ['uuid1', 'uuid2'] mock_vendors = [{'uuids': ['uuid1', 'uuid2']}] mock_session.return_value = get_transactional_session_mock(mock_vendors) uuids = neo4j_vendor.accessible_vendors( profile_id=profile_id, profile_type=profile_type, vendor_uuids=vendor_uuids, identity_id=identity_id, ) mock_session.return_value.run.assert_called_once_with( textwrap.dedent( """ MATCH (p:Profile {profileType: $profileType, profileId: toInteger($profileId)})<-[:HAS_PROFILE]-(i:Identity {id: $identityId}) // Collect all profiles for the identity, including p WITH i, p, [p] + [ (i)-[:HAS_PROFILE]->(otherProfile:Profile) WHERE otherProfile <> p | otherProfile ] AS allProfiles // Check if any profile has full catalog access WITH i, allProfiles, coalesce( any(profile IN allProfiles WHERE profile.fullCatalogAccess = true AND exists((profile)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(:Vendor {id: '*'})) ), false ) AS hasFullAccess CALL apoc.when( hasFullAccess, 'RETURN $vendorUuids AS uuids', ' // Check direct access for all profiles UNWIND $allProfiles AS profile OPTIONAL MATCH (profile)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(v1:Vendor) WHERE v1.uuid IN $vendorUuids RETURN apoc.coll.toSet(COLLECT(DISTINCT v1.uuid)) AS uuids ', {allProfiles: allProfiles, vendorUuids: $vendorUuids} ) YIELD value RETURN value.uuids AS uuids """ # noqa: E501 ), vendorUuids=vendor_uuids, profileId=profile_id, profileType=profile_type, identityId=identity_id, ) assert uuids == expected_uuids @patch('account.models.neo4j_vendor.get_session', autospec=None) def test_accessible_vendors_empty(mock_session): profile_id = 'profile123' profile_type = 'InsightsProfile' vendor_uuids = ['uuid3', 'uuid4'] mock_session.return_value = get_transactional_session_mock([None]) uuids = neo4j_vendor.accessible_vendors( profile_id=profile_id, profile_type=profile_type, vendor_uuids=vendor_uuids, identity_id=None, ) mock_session.return_value.run.assert_called_once_with( textwrap.dedent( """ MATCH (p:Profile {profileType: $profileType, profileId: toInteger($profileId)})<-[:HAS_PROFILE]-(i:Identity) // Collect all profiles for the identity, including p WITH i, p, [p] + [ (i)-[:HAS_PROFILE]->(otherProfile:Profile) WHERE otherProfile <> p | otherProfile ] AS allProfiles // Check if any profile has full catalog access WITH i, allProfiles, coalesce( any(profile IN allProfiles WHERE profile.fullCatalogAccess = true AND exists((profile)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(:Vendor {id: '*'})) ), false ) AS hasFullAccess CALL apoc.when( hasFullAccess, 'RETURN $vendorUuids AS uuids', ' // Check direct access for all profiles UNWIND $allProfiles AS profile OPTIONAL MATCH (profile)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(v1:Vendor) WHERE v1.uuid IN $vendorUuids RETURN apoc.coll.toSet(COLLECT(DISTINCT v1.uuid)) AS uuids ', {allProfiles: allProfiles, vendorUuids: $vendorUuids} ) YIELD value RETURN value.uuids AS uuids """ # noqa: E501 ), vendorUuids=vendor_uuids, profileId=profile_id, profileType=profile_type, ) assert uuids == []