"""Test for model profiles.""" from datetime import datetime import textwrap from unittest.mock import MagicMock, patch from freezegun import freeze_time from neo4j.exceptions import ConstraintError import pytest from tests.unit.conftest import get_session_mock from users import constants from users.models import profiles from users.utils import api_utils GET_PROFILES_QUERY = """MATCH (u:Identity)-[:HAS_PROFILE]->(p:Profile) WHERE u.id = $orchard_identity OR u.auth0UserId = $orchard_identity RETURN p as profile""" GET_PROFILES_FOR_APPS_QUERY = """MATCH (u:Identity)-[:HAS_PROFILE]-> (p1:Profile{profileType:'SettingsProfile'}) WHERE u.id = $orchard_identity OR u.auth0UserId = $orchard_identity RETURN p1 as profile UNION MATCH (u:Identity)-[:HAS_PROFILE]->(p2:Profile)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(r) WHERE (u.id = $orchard_identity OR u.auth0UserId = $orchard_identity) RETURN p2 as profile""" GET_PROFILES_FOR_APPS_WITH_TENANT_QUERY = """MATCH (u:Identity)-[:HAS_PROFILE]-> (p1:Profile{profileType:'SettingsProfile'}) WHERE u.id = $orchard_identity OR u.auth0UserId = $orchard_identity RETURN p1 as profile UNION MATCH (u:Identity)-[:HAS_PROFILE]->(p2:Profile)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->(r) WHERE (u.id = $orchard_identity OR u.auth0UserId = $orchard_identity) AND (r:Vendor AND r.uuid = "TEST_RESOURCE_UUID") RETURN p2 as profile""" # noqa: E501 def test_get_profiles_by_type(make_graph_node, mocker): """Test get_profiles_by_type.""" identity_id = '5ec1f2b182c9710aabe8cfff' profile_type = 'InsightsProfile' profile_data = { 'profileId': 444, 'profileName': 'Test Profile', 'profileType': profile_type, 'roles': ['analytics', 'catalog'], 'brand': 'orchard', } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.get_profiles_by_type(identity_id, profile_type) session_mock.run.assert_called_once() session_mock.run.assert_called_with( 'MATCH (u:Identity)-[:HAS_PROFILE]->(p:Profile)\n WHERE u.id = $orchard_identity\n AND p.profileType = $profile_type\n RETURN p as profile', # noqa: E501 orchard_identity=identity_id, profile_type=profile_type, ) assert result assert result.message == [ { 'profile_id': 444, 'profile_name': 'Test Profile', 'profile_type': profile_type, 'roles': ['analytics', 'catalog'], 'id': 10, 'brand': 'orchard', } ] def test_get_profile_by_uuid(make_graph_node, mocker): """Test get_profile_by_uuid.""" uuid = '68206f1a-3ce5-44d5-a1fc-9922bbe4ffff' profile_type = 'InsightsProfile' profile_data = { 'profileId': 444, 'profileName': 'Test Profile', 'profileType': profile_type, 'roles': ['analytics', 'catalog'], 'brand': 'orchard', 'uuid': uuid, } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.get_by_uuid(uuid) session_mock.run.assert_called_once() session_mock.run.assert_called_with( 'MATCH (p:Profile {\n uuid: $uuid\n })\n RETURN p as profile', uuid=uuid ) assert result assert result.message == { 'profile_id': 444, 'profile_name': 'Test Profile', 'profile_type': profile_type, 'roles': ['analytics', 'catalog'], 'id': 10, 'uuid': uuid, 'brand': 'orchard', } def test_create_profile_to_resource_relationship(make_graph_node, mocker): """Test create_profile_to_resource_relationship.""" identity_id = '12345abcd' profile_id = 444 profile_type = 'InsightsProfile' resource_type = 'Vendor' resource_id = 7123 db_response = [{'p': {'profile': 'data'}, 'rel': {'rel': 'data'}}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.create_profile_to_resource_relationship( identity_id, profile_type, profile_id, resource_type, resource_id, False ) assert session_mock.run.call_count == 2 calls = session_mock.run.call_args_list assert calls[1][0] == ( 'MATCH (p:Profile)\n MATCH(r:Vendor)\n WHERE\n p.profileType = $profile_type AND\n p.profileId = $profile_id AND r.id = $resource_id\n MERGE (p)-[rel:HAS_ACCESS_TO]->(r)\n SET rel.roles = $roles, rel.createdAt = datetime()\n RETURN p, rel', # noqa: E501 ) assert calls[1][1] == { 'profile_type': profile_type, 'profile_id': profile_id, 'resource_type': resource_type, 'resource_id': resource_id, 'roles': [], } assert result assert result.message == {'rel': 'data'} def test_get_profiles(make_graph_node, mocker): """Test get_profiles with valid profiles.""" identity = 'auth0id' db_response = [ { 'profile': make_graph_node( node_id=10, data={'profileType': 'ArtistProfile', 'profileId': 10, 'brand': 'orchard'}, ) }, { 'profile': make_graph_node( node_id=11, data={'profileType': 'LabelProfile', 'profileId': 11, 'brand': 'orchard'}, ) }, ] expected = [ {'id': 10, 'profile_id': 10, 'profile_type': 'ArtistProfile', 'brand': 'orchard'}, {'id': 11, 'profile_id': 11, 'profile_type': 'LabelProfile', 'brand': 'orchard'}, ] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.get_profiles(identity) assert result assert result.message == expected # assert query is executed with correct params. session_mock.run.assert_called_once() query, params = session_mock.run.call_args assert 'MATCH (u:Identity)-[:HAS_PROFILE]->(p:Profile)' in query[0] assert 'WHERE u.id = $orchard_identity OR u.auth0UserId = $orchard_identity' in query[0] assert 'as roles' not in query[0] assert params == {'orchard_identity': identity} def test_get_profiles_no_profiles(make_graph_node, mocker): """Test get_profiles when identity has no profiles.""" identity = 'auth0id' db_response = [] expected = [] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.get_profiles(identity) assert result assert result.message == expected # assert query is executed with correct params. session_mock.run.assert_called_once() query, params = session_mock.run.call_args assert query[0] == GET_PROFILES_QUERY assert 'as roles' not in query[0] assert params == {'orchard_identity': identity} @pytest.mark.parametrize( ('resource_type', 'resource_uuid', 'expected_query'), ( (None, None, GET_PROFILES_FOR_APPS_QUERY), (constants.RESOURCE_VENDOR, None, GET_PROFILES_FOR_APPS_QUERY), (None, 'TEST_RESOURCE_UUID', GET_PROFILES_FOR_APPS_QUERY), (constants.RESOURCE_VENDOR, 'TEST_RESOURCE_UUID', GET_PROFILES_FOR_APPS_WITH_TENANT_QUERY), ), ) def test_get_profiles_for_applications( resource_type, resource_uuid, expected_query, make_graph_node, mocker ): """Test get_profiles with valid profiles.""" identity = 'auth0id' db_response = [ { 'profile': make_graph_node( node_id=10, data={'profileType': 'ArtistProfile', 'profileId': 10, 'brand': 'orchard'}, ) }, { 'profile': make_graph_node( node_id=11, data={'profileType': 'LabelProfile', 'profileId': 11, 'brand': 'orchard'}, ) }, ] expected = [ {'id': 10, 'profile_id': 10, 'profile_type': 'ArtistProfile', 'brand': 'orchard'}, {'id': 11, 'profile_id': 11, 'profile_type': 'LabelProfile', 'brand': 'orchard'}, ] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.get_profiles_for_applications( identity, resource_type=resource_type, resource_uuid=resource_uuid ) assert result assert result.message == expected # assert query is executed with correct params. session_mock.run.assert_called_once() query, params = session_mock.run.call_args assert query[0] == expected_query assert 'as roles' not in query[0] assert params == {'orchard_identity': identity} def test_get_profiles_for_applications_no_profiles(make_graph_node, mocker): """Test get_profiles when identity has no profiles.""" identity = 'auth0id' db_response = [] expected = [] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.get_profiles_for_applications(identity) assert result assert result.message == expected # assert query is executed with correct params. session_mock.run.assert_called_once() query, params = session_mock.run.call_args assert query[0] == GET_PROFILES_FOR_APPS_QUERY assert 'as roles' not in query[0] assert params == {'orchard_identity': identity} def test_get_by_profile_id_and_type(make_graph_node, mocker): """Test get_by_profile_id_and_type.""" profile_id = 444 profile_type = 'ArtistProfile' profile_data = { 'profile_id': 444, 'profile_name': 'Test Profile', 'profile_type': 'ArtistProfile', 'roles': ['analytics', 'catalog'], 'brand': 'orchard', } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.get_by_profile_id_and_type(profile_id, profile_type) session_mock.run.assert_called_once() session_mock.run.assert_called_with( 'MATCH (p:Profile {\n profileId: $profile_id,\n profileType: $profile_type\n })\n RETURN p as profile', # noqa: E501 profile_id=444, profile_type='ArtistProfile', ) # @todo test actual 200 and 404 responses by mocking neo4j def test_create_profile(make_graph_node, mocker): """Test test_create_profile.""" profile_data = { 'profile_id': 444, 'profile_name': 'Test Profile', 'profile_type': 'ArtistProfile', 'roles': ['analytics', 'catalog'], 'brand': 'orchard', } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.create_profile(profile_data) session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'CREATE (p:Profile)\n SET p = $profile_data, p.uuid = randomUUID()\n SET p.profileId = 444\n RETURN p as node', # noqa: E501 ) assert session_mock.run.call_args[1] == {'profile_data': api_utils.to_camel(profile_data)} @freeze_time('2021-10-25 05:34:28') def test_create_profile_no_profile_id(make_graph_node, mocker): """Test test_create_profile_no_profile_id.""" profile_data = { 'profile_name': 'Test Profile', 'profile_type': 'ArtistProfile', 'roles': ['analytics', 'catalog'], 'brand': 'orchard', } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.create_profile(profile_data) session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( """ MERGE (i:IncrementId {nodeName: 'Profile'}) ON CREATE SET i.id = 1 WITH i CALL apoc.atomic.add(i, 'id', 1, 3) YIELD newValue as profileId WITH profileId CREATE (p:Profile { profileId: toInteger(profileId), profileType: $profile_type }) SET p += $profile_data, p.uuid = randomUUID() RETURN p as node """, ) assert session_mock.run.call_args[1] == { 'profile_type': 'ArtistProfile', 'profile_data': { 'id': 10, 'fullCatalogAccess': False, 'createdAt': datetime.utcnow(), 'profileName': 'Test Profile', 'roles': ['analytics', 'catalog'], 'brand': 'orchard', }, } def test_create_profile_with_extra_attributes(make_graph_node, mocker): """Test test_create_profile_with_extra_attributes.""" profile_data = { 'profile_id': 444, 'profile_name': 'Test Profile', 'profile_type': 'ArtistProfile', 'first_name': 'foo', 'last_name': 'bar', 'roles': ['analytics', 'catalog'], 'brand': 'orchard', } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.create_profile(profile_data) session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'CREATE (p:Profile)\n SET p = $profile_data, p.uuid = randomUUID()\n SET p.profileId = 444\n RETURN p as node', # noqa: E501 ) assert session_mock.run.call_args[1] == {'profile_data': api_utils.to_camel(profile_data)} def test_create_profile_with_uuid(make_graph_node, mocker): """Test create_profile.""" profile_data = { 'profile_id': 12345, 'uuid': '83b04518-108e-11ec-82a8-0242ac130003', 'profile_name': 'Test Profile', 'profile_type': 'ArtistProfile', 'first_name': 'foo', 'last_name': 'bar', 'roles': ['analytics', 'catalog'], } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.create_profile(profile_data) session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'CREATE (p:Profile)\n SET p = $profile_data\n SET p.profileId = 12345\n RETURN p as node', # noqa: E501 ) assert session_mock.run.call_args[1] == {'profile_data': api_utils.to_camel(profile_data)} @freeze_time('2021-10-25 05:34:28') def test_create_profile_no_profile_id_with_extra_attributes(make_graph_node, mocker): """Test test_create_profile_no_profile_id_with_extra_attributes.""" profile_data = { 'profile_name': 'Test Profile', 'profile_type': 'ArtistProfile', 'first_name': 'foo', 'last_name': 'bar', 'roles': ['analytics', 'catalog'], 'brand': 'orchard', } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.create_profile(profile_data) session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( """ MERGE (i:IncrementId {nodeName: 'Profile'}) ON CREATE SET i.id = 1 WITH i CALL apoc.atomic.add(i, 'id', 1, 3) YIELD newValue as profileId WITH profileId CREATE (p:Profile { profileId: toInteger(profileId), profileType: $profile_type }) SET p += $profile_data, p.uuid = randomUUID() RETURN p as node """, ) assert session_mock.run.call_args[1] == { 'profile_type': 'ArtistProfile', 'profile_data': { 'id': 10, 'fullCatalogAccess': False, 'createdAt': datetime.utcnow(), 'profileName': 'Test Profile', 'firstName': 'foo', 'lastName': 'bar', 'roles': ['analytics', 'catalog'], 'brand': 'orchard', }, } @freeze_time('2021-10-25 05:34:28') def test_create_profile_no_profile_id_profile_exists(make_graph_node, mocker): """Test test_create_profile_no_profile_id_profile_exists. Test when the next auto-increment id / profile_type combo already matches an existing profile. """ profile_data = { 'profile_name': 'Test Profile', 'profile_type': 'ArtistProfile', 'roles': ['analytics', 'catalog'], 'brand': 'orchard', } session_mock = MagicMock() # ConstraintError is raised at .peek() time (lazy driver), not at .run() time result_mock = MagicMock() result_mock.peek.side_effect = ConstraintError('profile exists') session_mock.run.return_value = result_mock with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.create_profile(profile_data) session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( """ MERGE (i:IncrementId {nodeName: 'Profile'}) ON CREATE SET i.id = 1 WITH i CALL apoc.atomic.add(i, 'id', 1, 3) YIELD newValue as profileId WITH profileId CREATE (p:Profile { profileId: toInteger(profileId), profileType: $profile_type }) SET p += $profile_data, p.uuid = randomUUID() RETURN p as node """, ) assert session_mock.run.call_args[1] == { 'profile_type': 'ArtistProfile', 'profile_data': { 'fullCatalogAccess': False, 'profileName': 'Test Profile', 'createdAt': datetime.utcnow(), 'roles': ['analytics', 'catalog'], 'brand': 'orchard', }, } assert result.status == 400 assert result.errors == { 'code': constants.ERROR_CODE_VALIDATION_ERROR, 'message': constants.ERROR_MESSAGE_PROFILE_EXISTS, } @freeze_time('2024-08-29 15:11:15') @patch('users.models.profiles.get_session') def test_create_profile_orchadmin_with_profile_id(session_method_mock, make_graph_node): """Test mostly that creating an OrchAdmin profile sets its fullCatalogAccess to true.""" profile_data = { 'profile_id': 11, 'profile_name': 'Test Profile', 'profile_type': 'OrchAdminProfile', 'roles': ['admin'], 'brand': 'theorchard', } db_response = [{'profile': make_graph_node(node_id=11, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) session_method_mock.return_value = session_mock profiles.create_profile(profile_data) session_mock.run.assert_called_once_with( 'CREATE (p:Profile)\n SET p = $profile_data, p.uuid = randomUUID()\n SET p.profileId = 11\n RETURN p as node', # noqa: E501 profile_data={ 'profileId': 11, 'profileName': 'Test Profile', 'profileType': 'OrchAdminProfile', 'roles': ['admin'], 'brand': 'theorchard', 'createdAt': datetime.utcnow(), 'fullCatalogAccess': True, # Attribute added as a side effect of calling make_graph_node above 'id': 11, }, ) def test_delete_profile(mocker): """Test test_delete_profile.""" session_mock = get_session_mock() with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.delete_profile(888, 'ArtistProfile') session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'MATCH (p:Profile {\n profileId: $profile_id,\n profileType: $profile_type })\n DETACH DELETE p', # noqa: E501 ) assert session_mock.run.call_args[1] == {'profile_id': 888, 'profile_type': 'ArtistProfile'} def test_delete_profile_by_uuid(mocker): """Test delete_profile_by_uuid.""" session_mock = get_session_mock() with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.delete_profile_by_uuid('some-uuid') session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'MATCH (p:Profile { uuid: $profile_uuid }) DETACH DELETE p', ) assert session_mock.run.call_args[1] == {'profile_uuid': 'some-uuid'} def test_update_profile(make_graph_node, mocker): """Test update_profile.""" profile_id = 8888 profile_type = 'ArtistProfile' profile_data = { 'profile_name': 'Test Profile updated', 'roles': ['analytics', 'catalog', 'hot dogs'], 'brand': 'orchard', } db_response = [{'profile': make_graph_node(node_id=10, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.update_profile('abc-123', profile_id, profile_type, profile_data) session_mock.run.assert_called_with( textwrap.dedent( """ MATCH (p:Profile) WHERE p.profileId = $profile_id AND p.profileType = $profile_type SET p += $profile_data RETURN p as node """ ), **{ 'profile_id': 8888, 'profile_type': 'ArtistProfile', 'profile_data': { 'id': 10, 'profileName': 'Test Profile updated', 'roles': ['analytics', 'catalog', 'hot dogs'], 'brand': 'orchard', }, }, ) def test_update_profile_by_uuid(make_graph_node, mocker): """Test update_profile_by_uuid.""" profile_uuid = 'some-uuid' profile_data = { 'profile_name': 'Test Profile updated', 'roles': ['analytics', 'catalog'], 'uuid': profile_uuid, 'brand': 'orchard', } input_data = {'roles': ['analytics']} db_response = [{'profile': make_graph_node(node_id=11, labels=('Profile'), data=profile_data)}] session_mock = get_session_mock(db_response) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.update_profile_by_uuid(profile_uuid, input_data) session_mock.run.assert_called_with( textwrap.dedent( """ MATCH (p:Profile) WHERE p.uuid = $profile_uuid SET p += $profile_data RETURN p as node """ ), **{'profile_uuid': 'some-uuid', 'profile_data': {'roles': ['analytics']}}, ) def test_link_identity_to_profile(make_graph_node, mocker): """Test test_link_identity_to_profile.""" schema = { 'orchard_identity_id': 'auth0_id', 'profile_id': 555, 'profile_type': 'InsightsProfile', } session_mock = get_session_mock([]) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.link_identity_to_profile('auth0_id', 555, 'InsightsProfile') assert session_mock.run.call_count == 2 calls = session_mock.run.call_args_list assert 'MATCH (i:Identity),(p:Profile)' in calls[1][0][0] assert 'WHERE i.id = $orchard_identity_id' in calls[1][0][0] assert 'MERGE (i)-[r:HAS_PROFILE]->(p)' in calls[1][0][0] assert calls[1][1] == schema def test_soft_delete_profile_to_identity_relationship(make_graph_node, mocker): """Test test_soft_delete_profile_to_identity_relationship.""" session_mock = get_session_mock() with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.soft_delete_profile_to_identity_relationship('auth0_id', 555, 'ArtistProfile') session_mock.run.assert_called_once() args = session_mock.run.call_args assert 'MATCH (i:Identity)-[rel:HAS_PROFILE]->' in args[0][0] assert 'i.id = $orchard_identity_id AND' in args[0][0] assert 'SET rel.dateDeleted = localdatetime()' in args[0][0] assert "apoc.refactor.setType(rel, 'DELETED_HAS_PROFILE')" in args[0][0] assert 'YIELD input, output' in args[0][0] assert args[1] == { 'orchard_identity_id': 'auth0_id', 'profile_id': 555, 'profile_type': 'ArtistProfile', 'relationship_name': 'HAS_PROFILE', } def test_link_identity_to_profile_by_uuid(make_graph_node, mocker): """Test link_identity_to_profile_by_uuid.""" profile_uuid = 'some-uuid' identity_uuid = 'identity-uuid' session_mock = get_session_mock([]) with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.link_identity_to_profile_by_uuid(identity_uuid, profile_uuid) assert session_mock.run.call_count == 2 calls = session_mock.run.call_args_list assert 'MATCH (i:Identity),(p:Profile)' in calls[1][0][0] assert 'WHERE i.id = $orchard_identity_id' in calls[1][0][0] assert 'MERGE (i)-[r:HAS_PROFILE]->(p)' in calls[1][0][0] assert calls[1][1] == {'orchard_identity_id': identity_uuid, 'profile_uuid': profile_uuid} def test_soft_delete_profile_to_identity_relationship_by_uuid(make_graph_node, mocker): """Test soft_delete_profile_to_identity_relationship_by_uuid.""" session_mock = get_session_mock() profile_uuid = 'some-uuid' identity_uuid = 'identity-uuid' with mocker.patch('users.models.profiles.get_session', return_value=session_mock): profiles.soft_delete_profile_to_identity_relationship_by_uuid(identity_uuid, profile_uuid) session_mock.run.assert_called_once() args = session_mock.run.call_args assert 'MATCH (i:Identity)-[rel:HAS_PROFILE]->' in args[0][0] assert 'i.id = $orchard_identity_id AND' in args[0][0] assert 'SET rel.dateDeleted = localdatetime()' in args[0][0] assert "apoc.refactor.setType(rel, 'DELETED_HAS_PROFILE')" in args[0][0] assert 'YIELD input, output' in args[0][0] assert args[1] == {'orchard_identity_id': identity_uuid, 'profile_uuid': profile_uuid} def test_create_settings_profile_constraint_error(mocker): """Test that ConstraintError on duplicate profileId returns 400, not 500. Neo4j's driver is lazy: session.run() queues the query, .peek() executes it. The ConstraintError is raised at .peek() time. """ session_mock = MagicMock() result_mock = MagicMock() result_mock.peek.side_effect = ConstraintError('profile exists') session_mock.run.return_value = result_mock with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.create_settings_profile('identity-123', 'Test Profile', 'orchard') assert result.status == 400 assert result.errors == { 'code': constants.ERROR_CODE_VALIDATION_ERROR, 'message': constants.ERROR_MESSAGE_PROFILE_EXISTS, } @freeze_time('2021-10-25 05:34:28') def test_create_profile_with_profile_id_constraint_error(mocker): """Test that ConstraintError on explicit profile_id returns 400, not 500. The create_profile branch with an explicit profile_id had no try/except at all before the fix. """ profile_data = { 'profile_id': 675821, 'profile_name': 'Test Profile', 'profile_type': 'LabelProfile', 'roles': ['catalog'], 'brand': 'orchard', } session_mock = MagicMock() result_mock = MagicMock() result_mock.peek.side_effect = ConstraintError('profile exists') session_mock.run.return_value = result_mock with mocker.patch('users.models.profiles.get_session', return_value=session_mock): result = profiles.create_profile(profile_data) assert result.status == 400 assert result.errors == { 'code': constants.ERROR_CODE_VALIDATION_ERROR, 'message': constants.ERROR_MESSAGE_PROFILE_EXISTS, }