"""Test for subscriptions model.""" from unittest.mock import call, patch import pytest from neo4j.time import DateTime from owsresponse import response from notifications.models import subscriptions from tests.unit.conftest import get_session_mock def test_fetch_ids(make_graph_node): """Test fetch node.""" query_result = [{'n': make_graph_node('wxyz', data={'name': 'qwerty'})}] expected_result = ['wxyz'] with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = query_result result = subscriptions.fetch_ids('GlobalSoundRecording', 'isrc', ['wxyz']) assert result == expected_result assert session_mock.run.call_args_list == [ call( 'MATCH (n:GlobalSoundRecording) WHERE n.isrc IN $attr_values RETURN n', attr_values=['wxyz'], ) ] def test_fetch_ids_not_found(make_graph_node): """Test fetch node when no node is found.""" query_result = None expected_result = [] with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = query_result result = subscriptions.fetch_ids('GlobalSoundRecording', 'isrc', ['wxyz']) assert result == expected_result assert session_mock.run.call_args_list == [ call( 'MATCH (n:GlobalSoundRecording) WHERE n.isrc IN $attr_values RETURN n', attr_values=['wxyz'], ) ] @pytest.mark.parametrize( ('app_ids', 'notification_type', 'expected_cypher'), [ ( None, None, ( 'MATCH (s:Subscription) WHERE true RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, s.feedType as feedType, collect(s.followedEntity) as followedEntityTypes', # noqa: E501 ), ), ( ['workstation', 'insights'], None, ( 'MATCH (s:Subscription) WHERE true AND s.appId IN $appIds RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, s.feedType as feedType, collect(s.followedEntity) as followedEntityTypes', # noqa: E501 ), ), ( None, 'emailNotification', ( 'MATCH (s:Subscription) WHERE true AND s.notificationType = $notificationType RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, s.feedType as feedType, collect(s.followedEntity) as followedEntityTypes', # noqa: E501 ), ), ( ['workstation'], 'emailNotification', ( 'MATCH (s:Subscription) WHERE true AND s.appId IN $appIds AND s.notificationType = $notificationType RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, s.feedType as feedType, collect(s.followedEntity) as followedEntityTypes', # noqa: E501 ), ), ], ) def test_get_all_subscriptions(app_ids, notification_type, expected_cypher): """Test get_all_subscriptions.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] subscriptions.get_all_subscriptions(app_ids, notification_type) assert session_mock.run.call_args[0] == expected_cypher assert session_mock.run.call_args[1] == { 'appIds': app_ids or None, 'notificationType': notification_type, } def test_create_subscription(make_graph_node): """Test create_subscription.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value result = subscriptions.create_subscription( 'InsightsProfile', 555, 'GlobalParticipant', 19, 'HAS_FOLLOWED', False ) assert result.status == 201 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'MATCH (p:Profile),(pa:GlobalParticipant) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND pa.id = $entity_id CREATE (p)-[r:HAS_FOLLOWED {dateCreated: localdatetime(), automatic: $automatic}]->(pa) RETURN r.dateCreated', # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_id': 19, 'relationship': 'HAS_FOLLOWED', 'automatic': False, } @pytest.mark.parametrize( ('query_result_index', 'expected_message'), [ (0, 'Profile not found with id 555 and type InsightsProfile'), (1, 'GlobalParticipant not found with id 19'), (2, 'GlobalParticipant not found with id 19'), (3, 'Unknown error'), ], ) @patch('notifications.models.subscriptions.g') def test_create_subscription_error( flask_global, app_context, query_result_index, expected_message, make_graph_node ): """Test all error cases of subscription insert failure.""" profile_node = make_graph_node('1234') entity_node = make_graph_node('5678') query_results = [ {'pa': profile_node, 'p': None}, {'p': profile_node, 'pa': None}, {'p': None, 'pa': None}, {'p': profile_node, 'pa': entity_node}, ] with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value.peek.return_value = None session_mock.run.return_value.single.return_value = query_results[query_result_index] with pytest.raises(Exception) as err: subscriptions.create_subscription( 'InsightsProfile', 555, 'GlobalParticipant', 19, 'HAS_FOLLOWED', False ) assert err.message == expected_message assert flask_global.log.info.called def test_soft_undeleted_subscription(make_graph_node): """Test soft_undelete_subscription.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value result = subscriptions.soft_undelete_subscription( 'InsightsProfile', 555, 'GlobalParticipant', 19, 'HAS_FOLLOWED', False ) assert result.status == 204 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:DELETED_HAS_FOLLOWED]->(pa:GlobalParticipant) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND pa.id = $entity_id SET r.dateCreated = localdatetime(), r.automatic = CASE WHEN $automatic IS NOT NULL THEN $automatic ELSE r.automatic END WITH r CALL apoc.refactor.setType(r, 'HAS_FOLLOWED') YIELD input, output RETURN input, output", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_id': 19, 'automatic': False, } def test_soft_delete_subscription(make_graph_node): """Test soft_delete_subscription.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value result = subscriptions.soft_delete_subscription( 'InsightsProfile', 555, 'GlobalParticipant', 19, 'HAS_FOLLOWED' ) assert result.status == 204 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:HAS_FOLLOWED]->(pa:GlobalParticipant) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND pa.id = $entity_id SET r.dateDeleted = localdatetime(), r.automatic = CASE WHEN $automatic IS NOT NULL THEN $automatic ELSE r.automatic END WITH r CALL apoc.refactor.setType(r, 'DELETED_HAS_FOLLOWED') YIELD input, output RETURN input, output", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_id': 19, 'automatic': None, } def test_process_result_subscriptions_list(make_graph_node): """Test get_subscriptions default params and result processing.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [ { 'pa': make_graph_node( '12345', data={ 'createdAt': DateTime(2019, 6, 15, 15, 00, 5), 'name': 'The Ones and Zeros', 'chartmetricId': 9876, 'spotifyId': 'spotify:artist:xyz', }, ), 'state': 'HAS_FOLLOWED', 'last_modified': DateTime(2019, 6, 15, 15, 00, 10), 'rel_created': DateTime(2019, 6, 16, 15, 00, 5), }, { 'pa': make_graph_node( '6789', data={ 'createdAt': DateTime(2020, 6, 15, 15, 00, 5), 'name': 'The Seg Faults', 'chartmetricId': 98762, 'spotifyId': 'spotify:artist:xyz', }, ), 'state': 'DELETED_HAS_FOLLOWED', 'last_modified': DateTime(2020, 6, 15, 15, 00, 10), 'rel_created': DateTime(2020, 6, 16, 15, 00, 5), }, ] result = subscriptions.get_subscriptions( 'InsightsProfile', 555, 'GlobalParticipant', 'HAS_FOLLOWED', { 'ids': [], 'offset': 0, 'limit': None, 'order_dir': 'desc', 'order_by': 'last_modified', 'state': 'undeleted', 'sub_type': None, }, ) assert result.status == 200 assert result.message == [ { 'name': 'The Ones and Zeros', 'spotify_id': 'spotify:artist:xyz', 'id': '12345', 'chartmetric_id': 9876, 'created_at': '2019-06-16T15:00:05.000000000', 'last_modified': '2019-06-15T15:00:10.000000000', 'deleted': False, }, { 'name': 'The Seg Faults', 'spotify_id': 'spotify:artist:xyz', 'id': '6789', 'chartmetric_id': 98762, 'created_at': '2020-06-16T15:00:05.000000000', 'last_modified': '2020-06-15T15:00:10.000000000', 'deleted': True, }, ] session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:HAS_FOLLOWED]->(pa:GlobalParticipant) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND ( SIZE($ids) = 0 OR pa.id IN $ids ) AND ( $sub_type IS NULL OR $sub_type IN pa.types ) RETURN pa, r.dateCreated AS rel_created, TYPE(r) AS state, CASE TYPE(r) STARTS WITH 'DELETED_' WHEN true THEN r.dateDeleted ELSE r.dateCreated END AS last_modified ORDER BY last_modified DESC SKIP $skip", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_type': 'GlobalParticipant', 'relationship': 'HAS_FOLLOWED', 'ids': [], 'sub_type': None, 'skip': 0, } def test_get_subscriptions_subaccount(make_graph_node): """Test get_subscriptions default params and result processing.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [ { 'pa': make_graph_node( '12345', data={ 'createdAt': DateTime(2019, 6, 15, 15, 00, 5), 'name': 'CrashMusic', 'isDeleted': False, }, ), 'state': 'HAS_FOLLOWED', 'last_modified': DateTime(2019, 6, 15, 15, 00, 10), 'rel_created': DateTime(2019, 6, 16, 15, 00, 5), }, ] result = subscriptions.get_subscriptions( 'InsightsProfile', 555, 'Subaccount', 'HAS_FOLLOWED', { 'ids': [], 'offset': 0, 'limit': None, 'order_dir': 'desc', 'order_by': 'last_modified', 'state': 'undeleted', 'sub_type': None, }, ) assert result.status == 200 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:HAS_FOLLOWED]->(pa:Subaccount) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND ( SIZE($ids) = 0 OR pa.id IN $ids ) AND ( $sub_type IS NULL OR $sub_type IN pa.types ) AND pa.isDeleted = false RETURN pa, r.dateCreated AS rel_created, TYPE(r) AS state, CASE TYPE(r) STARTS WITH 'DELETED_' WHEN true THEN r.dateDeleted ELSE r.dateCreated END AS last_modified ORDER BY last_modified DESC SKIP $skip", # noqa: E501 ) def test_get_subscriptions_product(make_graph_node): """Test get_subscriptions for product type.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [ { 'pa': make_graph_node( '12345', labels=['Product', 'Orchard'], data={ 'createdAt': DateTime(2019, 6, 15, 15, 00, 5), 'name': 'Caramelo', 'upc': '697691883793', 'id': 2869335, }, ), 'state': 'HAS_FOLLOWED', 'last_modified': DateTime(2019, 6, 15, 15, 00, 10), 'rel_created': DateTime(2019, 6, 16, 15, 00, 5), }, ] result = subscriptions.get_subscriptions( 'InsightsProfile', 555, 'Product:Orchard', 'HAS_FOLLOWED', { 'ids': [], 'offset': 0, 'limit': None, 'order_dir': 'desc', 'order_by': 'last_modified', 'state': 'undeleted', 'sub_type': None, }, ) assert result.status == 200 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:HAS_FOLLOWED]->(pa:Product:Orchard) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND ( SIZE($ids) = 0 OR pa.id IN $ids ) AND ( $sub_type IS NULL OR $sub_type IN pa.types ) AND ( (p.fullCatalogAccess IS NOT null AND p.fullCatalogAccess = true) OR EXISTS { (p)-[:HAS_ACCESS_TO|HAS_ADMIN_ACCESS_TO]->() <-[:BELONGS_TO|CREATED_FOR_PARTICIPANT]-(:Project)-[:INCLUDES]->(pa) } ) RETURN pa, r.dateCreated AS rel_created, TYPE(r) AS state, CASE TYPE(r) STARTS WITH 'DELETED_' WHEN true THEN r.dateDeleted ELSE r.dateCreated END AS last_modified ORDER BY last_modified DESC SKIP $skip", # noqa: E501 ) def test_get_subscriptions_channels(make_graph_node): """Test get_subscriptions for channels.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [ { 'pa': make_graph_node( '12345', data={ 'createdAt': DateTime(2019, 6, 15, 15, 00, 5), 'name': 'Channel A', 'channelId': 'ABC1Ufeagjk8', }, ), 'state': 'HAS_FOLLOWED', 'last_modified': DateTime(2019, 6, 15, 15, 00, 10), 'rel_created': DateTime(2019, 6, 16, 15, 00, 5), }, { 'pa': make_graph_node( '6789', data={ 'createdAt': DateTime(2020, 6, 15, 15, 00, 5), 'name': 'Channel B', 'channelId': 'UZC1Ufeagjk8', }, ), 'state': 'DELETED_HAS_FOLLOWED', 'last_modified': DateTime(2020, 6, 15, 15, 00, 10), 'rel_created': DateTime(2020, 6, 16, 15, 00, 5), }, ] result = subscriptions.get_subscriptions( 'InsightsProfile', 555, 'Channel', 'HAS_FOLLOWED', { 'ids': [], 'offset': 0, 'limit': None, 'order_dir': 'desc', 'order_by': 'last_modified', 'state': 'undeleted', 'sub_type': None, }, ) assert result.status == 200 assert result.message == [ { 'name': 'Channel A', 'channel_id': 'ABC1Ufeagjk8', 'id': '12345', 'created_at': '2019-06-16T15:00:05.000000000', 'last_modified': '2019-06-15T15:00:10.000000000', 'deleted': False, }, { 'name': 'Channel B', 'channel_id': 'UZC1Ufeagjk8', 'id': '6789', 'created_at': '2020-06-16T15:00:05.000000000', 'last_modified': '2020-06-15T15:00:10.000000000', 'deleted': True, }, ] def test_no_results_subscriptions_list(): """Test result when no matches found.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] result = subscriptions.get_subscriptions( 'InsightsProfile', 555, 'GlobalParticipant', 'HAS_FOLLOWED', { 'ids': [], 'offset': 0, 'limit': None, 'order_dir': 'desc', 'order_by': 'last_modified', 'state': 'undeleted', 'sub_type': None, }, ) assert result.message == [] def test_filter_ids_input_subscrptions_list(): """Test filter ids input to query.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] subscriptions.get_subscriptions( 'InsightsProfile', 555, 'GlobalParticipant', 'HAS_FOLLOWED', { 'ids': [1, 2, 3, 4], 'offset': 0, 'limit': None, 'order_dir': 'desc', 'order_by': 'last_modified', 'state': 'undeleted', 'sub_type': None, }, ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_type': 'GlobalParticipant', 'relationship': 'HAS_FOLLOWED', 'ids': [1, 2, 3, 4], 'sub_type': None, 'skip': 0, } def test_filter_sub_type_subscriptions_list(): """Test filter sub_type input to query.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] subscriptions.get_subscriptions( 'InsightsProfile', 555, 'GlobalParticipant', 'HAS_FOLLOWED', { 'ids': [], 'offset': 0, 'limit': None, 'order_dir': 'desc', 'order_by': 'last_modified', 'state': 'undeleted', 'sub_type': 'video', }, ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_type': 'GlobalParticipant', 'relationship': 'HAS_FOLLOWED', 'ids': [], 'sub_type': 'video', 'skip': 0, } @pytest.mark.parametrize( ('order_by', 'order_clause'), [('last_modified', 'last_modified'), ('created_at', 'r.dateCreated')], ) def test_order_and_limit_subscriptions_list(order_by, order_clause): """Test query modification on offset input.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] subscriptions.get_subscriptions( 'InsightsProfile', 555, 'GlobalParticipant', 'HAS_FOLLOWED', { 'ids': [1, 2, 3, 4], 'offset': 5, 'limit': 10, 'order_dir': 'asc', 'order_by': order_by, 'state': 'undeleted', 'sub_type': None, }, ) assert session_mock.run.call_args[0] == ( f"MATCH (p:Profile)-[r:HAS_FOLLOWED]->(pa:GlobalParticipant) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND ( SIZE($ids) = 0 OR pa.id IN $ids ) AND ( $sub_type IS NULL OR $sub_type IN pa.types ) RETURN pa, r.dateCreated AS rel_created, TYPE(r) AS state, CASE TYPE(r) STARTS WITH 'DELETED_' WHEN true THEN r.dateDeleted ELSE r.dateCreated END AS last_modified ORDER BY {order_clause} ASC SKIP $skip LIMIT 10", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_type': 'GlobalParticipant', 'relationship': 'HAS_FOLLOWED', 'ids': [1, 2, 3, 4], 'sub_type': None, 'skip': 5, } def test_deleted_state_input_subscriptions_list(): """Test query modification on deleted state.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] subscriptions.get_subscriptions( 'InsightsProfile', 555, 'GlobalParticipant', 'HAS_FOLLOWED', { 'ids': [], 'offset': 0, 'limit': None, 'order_dir': 'asc', 'order_by': 'last_modified', 'state': 'deleted', 'sub_type': None, }, ) assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:DELETED_HAS_FOLLOWED]->(pa:GlobalParticipant) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND ( SIZE($ids) = 0 OR pa.id IN $ids ) AND ( $sub_type IS NULL OR $sub_type IN pa.types ) RETURN pa, r.dateCreated AS rel_created, TYPE(r) AS state, CASE TYPE(r) STARTS WITH 'DELETED_' WHEN true THEN r.dateDeleted ELSE r.dateCreated END AS last_modified ORDER BY last_modified ASC SKIP $skip", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_type': 'GlobalParticipant', 'relationship': 'HAS_FOLLOWED', 'ids': [], 'sub_type': None, 'skip': 0, } def test_all_state_input_subscriptions_list(): """Test query modification on all state.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] subscriptions.get_subscriptions( 'InsightsProfile', 555, 'GlobalParticipant', 'HAS_FOLLOWED', { 'ids': [], 'offset': 0, 'limit': None, 'order_dir': 'asc', 'order_by': 'last_modified', 'state': 'all', 'sub_type': None, }, ) assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:DELETED_HAS_FOLLOWED|HAS_FOLLOWED]->(pa:GlobalParticipant) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND ( SIZE($ids) = 0 OR pa.id IN $ids ) AND ( $sub_type IS NULL OR $sub_type IN pa.types ) RETURN pa, r.dateCreated AS rel_created, TYPE(r) AS state, CASE TYPE(r) STARTS WITH 'DELETED_' WHEN true THEN r.dateDeleted ELSE r.dateCreated END AS last_modified ORDER BY last_modified ASC SKIP $skip", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_type': 'GlobalParticipant', 'relationship': 'HAS_FOLLOWED', 'ids': [], 'sub_type': None, 'skip': 0, } @patch('notifications.models.subscriptions.has_subscription') def test_has_deleted_subscription(has_subscription): """Test has_deleted_subscription wrapper function.""" subscriptions.has_deleted_subscription( 'InsightsProfile', 555, 'GlobalParticipant', 'xyz', 'HAS_FOLLOWED' ) assert has_subscription.call_args_list == [ call('InsightsProfile', 555, 'GlobalParticipant', 'xyz', 'DELETED_HAS_FOLLOWED') ] @pytest.mark.parametrize(('exists', 'expected_status'), [(True, 200), (False, 404)]) def test_has_subscription(make_graph_node, exists, expected_status): """Test has_subscription.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value.peek.return_value = exists result = subscriptions.has_subscription( 'InsightsProfile', 555, 'GlobalParticipant', 19, 'HAS_FOLLOWED' ) assert result.status == expected_status assert result.message == {'exists': exists} session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'MATCH (p:Profile)-[r:HAS_FOLLOWED]->(pa:GlobalParticipant) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND pa.id = $entity_id RETURN r, pa', # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'entity_id': 19, 'relationship': 'HAS_FOLLOWED', } def test_soft_delete_notification_subscription_no_entity(make_graph_node): """Test soft_delete_notification_subscription on all entity types.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value result = subscriptions.soft_delete_notification_subscription( 'InsightsProfile', 555, 'push_notifications', 'social_spike', None ) assert result.status == 204 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:HAS_SUBSCRIPTION]->(s:Subscription) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND s.notificationType = $notification_type AND s.feedType = $feed_type AND ( s.followedEntity = $followed_entity OR $followed_entity_empty ) SET r.dateDeleted = localdatetime() WITH r CALL apoc.refactor.setType(r, 'DELETED_HAS_SUBSCRIPTION') YIELD input, output RETURN input, output", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'notification_type': 'pushNotifications', 'feed_type': 'socialSpike', 'followed_entity': None, 'followed_entity_empty': True, } def test_soft_delete_notification_subscription(make_graph_node): """Test soft_delete_notification_subscription.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value result = subscriptions.soft_delete_notification_subscription( 'InsightsProfile', 555, 'push_notifications', 'social_spike', 'GlobalParticipant' ) assert result.status == 204 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:HAS_SUBSCRIPTION]->(s:Subscription) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND s.notificationType = $notification_type AND s.feedType = $feed_type AND ( s.followedEntity = $followed_entity OR $followed_entity_empty ) SET r.dateDeleted = localdatetime() WITH r CALL apoc.refactor.setType(r, 'DELETED_HAS_SUBSCRIPTION') YIELD input, output RETURN input, output", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'notification_type': 'pushNotifications', 'feed_type': 'socialSpike', 'followed_entity': 'GlobalParticipant', 'followed_entity_empty': False, } def test_soft_undelete_notification_subscription(make_graph_node): """Test soft_delete_notification_subscription.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value result = subscriptions.soft_undelete_notification_subscription( 'InsightsProfile', 555, 'push_notifications', 'social_spike', 'GlobalParticipant' ) assert result.status == 204 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (p:Profile)-[r:DELETED_HAS_SUBSCRIPTION]->(s:Subscription) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND s.notificationType = $notification_type AND s.feedType = $feed_type AND ( s.followedEntity = $followed_entity OR $followed_entity_empty ) SET r.dateCreated = localdatetime() WITH r CALL apoc.refactor.setType(r, 'HAS_SUBSCRIPTION') YIELD input, output RETURN input, output", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'notification_type': 'pushNotifications', 'feed_type': 'socialSpike', 'followed_entity': 'GlobalParticipant', 'followed_entity_empty': False, } @pytest.mark.parametrize( ('query_result_index', 'expected_status', 'expected_message'), [(0, 200, [{'profile_type': 'InsightsProfile', 'profile_id': '555'}]), (1, 200, [])], ) def test_get_subscribed_profiles( query_result_index, expected_status, expected_message, make_graph_node ): """Test get all profile to entity connections.""" profile_node = make_graph_node( '12345', data={'profileType': 'InsightsProfile', 'profileId': '555', 'profileName': 'Jane Doe'}, ) query_results = [[{'profile': profile_node}], []] with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = query_results[query_result_index] result = subscriptions.get_subscribed_profiles( ['InsightsProfile', 'ArtistProfile'], 'GlobalParticipant', 'abcd', 'HAS_FOLLOWED', 'profile', ) assert result.status == expected_status assert result.message == expected_message session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (g:GlobalParticipant) WHERE g.id = $entity_id OPTIONAL MATCH (g)<-[:HAS_FOLLOWED]-(p:Profile)<-[:HAS_PROFILE]-(i:Identity) WHERE p.profileType IN $profile_types AND i.active = 'Y' OPTIONAL MATCH (g)<-[:OWNS]-(v:Vendor)<-[:HAS_FOLLOWED]-(p2:Profile)<-[:HAS_PROFILE]-(i2:Identity) WHERE p2.profileType IN $profile_types AND i2.active = 'Y' WITH collect({profile: p, identity: i}) + collect({profile: p2, identity: i2}) AS pairs UNWIND pairs AS profile_identity WITH profile_identity WHERE profile_identity.identity IS NOT NULL AND profile_identity.profile IS NOT NULL WITH profile_identity.identity.id AS identity_id, profile_identity.profile AS profile WITH identity_id, profile, EXISTS { (profile)-[:HAS_ACCESS_TO]->(:Subaccount) } AS has_sub ORDER BY has_sub ASC WITH identity_id, collect(profile)[0] AS selected RETURN DISTINCT selected AS profile", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'entity_id': 'abcd', 'entity_type': 'GlobalParticipant', 'profile_types': ['InsightsProfile', 'ArtistProfile'], 'subscription_name': None, } def test_get_subscribed_profiles_profile_cypher(make_graph_node): """Test get all profile to entity connections.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [ { 'profile': make_graph_node( '12345', data={ 'profileType': 'LabelProfile', 'profileId': '555', 'profileName': 'Jane Doe', }, ) } ] result = subscriptions.get_subscribed_profiles( ['LabelProfile'], 'Vendor', 7123, 'HAS_AUTO_FOLLOWED', 'identity' ) assert result.status == 200 assert result.message == [{'profile_type': 'LabelProfile', 'profile_id': '555'}] session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( "MATCH (s:Subscription), (g:Vendor) WHERE s.followedEntity = $entity_type AND s.name = $subscription_name AND g.id = $entity_id WITH s, g OPTIONAL MATCH (s)<-[:HAS_AUTO_FOLLOWED]-(i:Identity) -[:HAS_PROFILE]->(p:Profile)-[:HAS_ACCESS_TO]->(g) WHERE p.profileType IN $profile_types AND i.active = 'Y' OPTIONAL MATCH (s)<-[:HAS_AUTO_FOLLOWED]-(i2:Identity) -[:HAS_PROFILE]->(p2:Profile)-[:HAS_ACCESS_TO]->(v:Vendor)-[:OWNS]->(g) WHERE p2.profileType IN $profile_types AND i2.active = 'Y' WITH collect(p) + collect(p2) as listProfiles UNWIND listProfiles as profile RETURN DISTINCT profile", # noqa: E501 ) assert session_mock.run.call_args[1] == { 'entity_id': 7123, 'entity_type': 'Vendor', 'profile_types': ['LabelProfile'], 'subscription_name': None, } def test_get_subscribed_profiles_deduplicate_by_identity_query(make_graph_node): """Test that profile-level query deduplicates by identity.""" # Vendor-level profile (no subaccount) — should be selected vendor_profile = make_graph_node( '10052049', data={ 'profileType': 'LabelProfile', 'profileId': '10052049', 'profileName': 'Customer Multiaccounts', }, ) with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [{'profile': vendor_profile}] result = subscriptions.get_subscribed_profiles( ['LabelProfile'], 'Subaccount', '98458', 'HAS_FOLLOWED_RELEASE_APPROVAL', 'profile', ) assert result.status == 200 assert result.message == [{'profile_type': 'LabelProfile', 'profile_id': '10052049'}] query_used = session_mock.run.call_args[0][0] assert 'identity_id' in query_used assert 'has_sub' in query_used assert 'collect(profile)[0]' in query_used def test_get_subscribed_profiles_single_profile_type(make_graph_node): """Test profile level query with a single profile type to ensure no regressions.""" profile_node = make_graph_node( '999', data={'profileType': 'ArtistProfile', 'profileId': '999', 'profileName': 'Artist One'}, ) with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [{'profile': profile_node}] result = subscriptions.get_subscribed_profiles( ['ArtistProfile'], 'GlobalParticipant', 'xyz', 'HAS_FOLLOWED', 'profile', ) assert result.status == 200 assert result.message == [{'profile_type': 'ArtistProfile', 'profile_id': '999'}] assert session_mock.run.call_args[1]['profile_types'] == ['ArtistProfile'] def test_get_subscribed_profiles_empty_result_profile_level(): """Test profile level query returns empty list when no profiles match at all.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] result = subscriptions.get_subscribed_profiles( ['LabelProfile'], 'Subaccount', '98458', 'HAS_FOLLOWED_RELEASE_APPROVAL', 'profile', ) assert result.status == 200 assert result.message == [] def test_get_all_notifications_for_profile(make_graph_node): """Test get_all_notifications_for_profile.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [ { 's': make_graph_node( '12345', data={ 'notificationType': 'email', 'feedType': 'socialSpike', 'followedEntity': 'GlobalParticipant', }, ) } ] result = subscriptions.get_all_notifications_for_profile('LabelProfile', 555) assert result == [ { 'notification_type': 'email', 'feed_type': 'social_spike', 'followed_entity': 'GlobalParticipant', } ] session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'MATCH (p:Profile)-[r:HAS_SUBSCRIPTION]->(s:Subscription) WHERE p.profileId = $profile_id AND p.profileType = $profile_type RETURN s', # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'LabelProfile', 'profile_id': 555, } def test_create_notification_subscription_no_entity(make_graph_node): """Test create_notification_subscription on all entity types at once.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value result = subscriptions.create_notification_subscription( 'InsightsProfile', 555, 'push_notifications', 'social_spike', None ) assert result.status == 201 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'MATCH (p:Profile),(s:Subscription) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND s.notificationType = $notification_type AND s.feedType = $feed_type AND ( s.followedEntity = $followed_entity OR $followed_entity_empty ) CREATE (p)-[r:HAS_SUBSCRIPTION]->(s) SET r.dateCreated = localdatetime() RETURN r.dateCreated', # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'notification_type': 'pushNotifications', 'feed_type': 'socialSpike', 'followed_entity': None, 'followed_entity_empty': True, } def test_create_notification_subscription(make_graph_node): """Test create_notification_subscription.""" with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value result = subscriptions.create_notification_subscription( 'InsightsProfile', 555, 'push_notifications', 'social_spike', 'GlobalParticipant' ) assert result.status == 201 session_mock.run.assert_called_once() assert session_mock.run.call_args[0] == ( 'MATCH (p:Profile),(s:Subscription) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND s.notificationType = $notification_type AND s.feedType = $feed_type AND ( s.followedEntity = $followed_entity OR $followed_entity_empty ) CREATE (p)-[r:HAS_SUBSCRIPTION]->(s) SET r.dateCreated = localdatetime() RETURN r.dateCreated', # noqa: E501 ) assert session_mock.run.call_args[1] == { 'profile_type': 'InsightsProfile', 'profile_id': 555, 'notification_type': 'pushNotifications', 'feed_type': 'socialSpike', 'followed_entity': 'GlobalParticipant', 'followed_entity_empty': False, } @pytest.mark.parametrize( ('followed_entity', 'query_result_index', 'expected_message'), [ ('GlobalParticipant', 0, 'Profile not found with id 555 and type InsightsProfile'), ( 'GlobalParticipant', 1, 'Subscription not found with type pushNotifications and feed socialSpike and followed entity GlobalParticipant', # noqa: E501 ), ( 'GlobalParticipant', 2, 'Subscription not found with type pushNotifications and feed socialSpike and followed entity GlobalParticipant', # noqa: E501 ), ( None, 1, 'Subscription not found with type pushNotifications and feed socialSpike', ), ( None, 2, 'Subscription not found with type pushNotifications and feed socialSpike', ), ('GlobalParticipant', 3, 'Unknown error'), ], ) @patch('notifications.models.subscriptions.g') def test_create_notification_subscription_error( flask_global, app_context, followed_entity, query_result_index, expected_message, make_graph_node, ): """Test all error cases of subscription insert failure.""" subscription_node = make_graph_node('push_notifications', 'social_spike') subscription_node_2 = make_graph_node('pushNotifications', 'socialSpike') profile_node = make_graph_node('1234') query_results = [ {'s': subscription_node, 'p': None}, {'p': profile_node, 's': None}, {'p': None, 's': None}, {'p': profile_node, 's': subscription_node_2}, ] with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value.peek.return_value = None session_mock.run.return_value.single.return_value = query_results[query_result_index] with pytest.raises(Exception) as err: subscriptions.create_notification_subscription( 'InsightsProfile', 555, 'push_notifications', 'social_spike', followed_entity ) assert err.message == expected_message assert flask_global.log.info.called @pytest.mark.parametrize( ('followed_entity', 'query_result_index', 'expected_response'), [ ('GlobalParticipant', 0, response.Response(status=404, message={'exists': False})), ('GlobalParticipant', 1, response.Response(status=200, message={'exists': True})), (None, 1, response.Response(status=200, message={'exists': True})), ], ) def test_has_notification_subscription( followed_entity, query_result_index, expected_response, make_graph_node ): """Test checking for notification subscription.""" profile_node = make_graph_node('1234') query_results = [None, {'p': profile_node}] with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value.peek.return_value = query_results[query_result_index] result = subscriptions.has_notification_subscription( 'InsightsProfile', 555, 'push_notifications', 'social_spike', followed_entity ) assert isinstance(result, type(expected_response)) assert result.message == expected_response.message assert result.status == expected_response.status assert session_mock.run.call_args_list == [ call( 'MATCH (p:Profile)-[r:HAS_SUBSCRIPTION]->(s:Subscription) WHERE p.profileId = $profile_id AND p.profileType = $profile_type AND s.notificationType = $notification_type AND s.feedType = $feed_type AND ( s.followedEntity = $followed_entity OR $followed_entity_empty ) RETURN r, s, p', # noqa: E501 feed_type='socialSpike', followed_entity=followed_entity, followed_entity_empty=(followed_entity is None), notification_type='pushNotifications', profile_id=555, profile_type='InsightsProfile', relationship='HAS_SUBSCRIPTION', ) ] @pytest.mark.parametrize( ('app_ids', 'profile_types', 'expected_cypher'), [ ( None, None, ( 'Match (i:Identity {id: $identityId})-[:HAS_PROFILE]->(p:Profile) -[:HAS_SUBSCRIPTION]->(s:Subscription) WHERE true OPTIONAL MATCH (p)-[rel_name]->(r) WHERE apoc.rel.type(rel_name) = "HAS_FOLLOWED_" + toUpper(s.name) WITH i, s, apoc.coll.toSet(collect( {labels: labels(r), uuid: coalesce(r.uuid, r.id), name: r.name} )) as resources OPTIONAL MATCH (i)-[auto:HAS_AUTO_FOLLOWED]->(s) WITH *, size(collect(auto)) as sizeOfAuto RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, sizeOfAuto > 0 as followAllResources, CASE WHEN sizeOfAuto > 0 or resources IS NULL THEN null ELSE [r in resources WHERE r.uuid is not null] END as followResources', # noqa: E501 ), ), ( 'workstation', None, ( 'Match (i:Identity {id: $identityId})-[:HAS_PROFILE]->(p:Profile) -[:HAS_SUBSCRIPTION]->(s:Subscription) WHERE true AND s.appId IN $appIds OPTIONAL MATCH (p)-[rel_name]->(r) WHERE apoc.rel.type(rel_name) = "HAS_FOLLOWED_" + toUpper(s.name) WITH i, s, apoc.coll.toSet(collect( {labels: labels(r), uuid: coalesce(r.uuid, r.id), name: r.name} )) as resources OPTIONAL MATCH (i)-[auto:HAS_AUTO_FOLLOWED]->(s) WITH *, size(collect(auto)) as sizeOfAuto RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, sizeOfAuto > 0 as followAllResources, CASE WHEN sizeOfAuto > 0 or resources IS NULL THEN null ELSE [r in resources WHERE r.uuid is not null] END as followResources', # noqa: E501 ), ), ( 'workstation,insights', None, ( 'Match (i:Identity {id: $identityId})-[:HAS_PROFILE]->(p:Profile) -[:HAS_SUBSCRIPTION]->(s:Subscription) WHERE true AND s.appId IN $appIds OPTIONAL MATCH (p)-[rel_name]->(r) WHERE apoc.rel.type(rel_name) = "HAS_FOLLOWED_" + toUpper(s.name) WITH i, s, apoc.coll.toSet(collect( {labels: labels(r), uuid: coalesce(r.uuid, r.id), name: r.name} )) as resources OPTIONAL MATCH (i)-[auto:HAS_AUTO_FOLLOWED]->(s) WITH *, size(collect(auto)) as sizeOfAuto RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, sizeOfAuto > 0 as followAllResources, CASE WHEN sizeOfAuto > 0 or resources IS NULL THEN null ELSE [r in resources WHERE r.uuid is not null] END as followResources', # noqa: E501 ), ), ( None, ['LabelProfile'], ( 'Match (i:Identity {id: $identityId})-[:HAS_PROFILE]->(p:Profile) -[:HAS_SUBSCRIPTION]->(s:Subscription) WHERE true AND p.profileType IN $profileTypes OPTIONAL MATCH (p)-[rel_name]->(r) WHERE apoc.rel.type(rel_name) = "HAS_FOLLOWED_" + toUpper(s.name) WITH i, s, apoc.coll.toSet(collect( {labels: labels(r), uuid: coalesce(r.uuid, r.id), name: r.name} )) as resources OPTIONAL MATCH (i)-[auto:HAS_AUTO_FOLLOWED]->(s) WITH *, size(collect(auto)) as sizeOfAuto RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, sizeOfAuto > 0 as followAllResources, CASE WHEN sizeOfAuto > 0 or resources IS NULL THEN null ELSE [r in resources WHERE r.uuid is not null] END as followResources', # noqa: E501 ), ), ( 'workstation', ['LabelProfile'], ( 'Match (i:Identity {id: $identityId})-[:HAS_PROFILE]->(p:Profile) -[:HAS_SUBSCRIPTION]->(s:Subscription) WHERE true AND s.appId IN $appIds AND p.profileType IN $profileTypes OPTIONAL MATCH (p)-[rel_name]->(r) WHERE apoc.rel.type(rel_name) = "HAS_FOLLOWED_" + toUpper(s.name) WITH i, s, apoc.coll.toSet(collect( {labels: labels(r), uuid: coalesce(r.uuid, r.id), name: r.name} )) as resources OPTIONAL MATCH (i)-[auto:HAS_AUTO_FOLLOWED]->(s) WITH *, size(collect(auto)) as sizeOfAuto RETURN s.name as name, s.notificationType as notificationType, s.appId as appId, sizeOfAuto > 0 as followAllResources, CASE WHEN sizeOfAuto > 0 or resources IS NULL THEN null ELSE [r in resources WHERE r.uuid is not null] END as followResources', # noqa: E501 ), ), ], ) def test_get_all_subscriptions_for_identity(app_ids, profile_types, expected_cypher): """Test get_all_subscriptions_for_identity.""" identity_id = 'uuid' with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value session_mock.run.return_value = [] subscriptions.get_all_subscriptions_for_identity(identity_id, profile_types, app_ids) assert session_mock.run.call_args[0] == expected_cypher assert session_mock.run.call_args[1] == { 'appIds': app_ids, 'identityId': identity_id, 'profileTypes': profile_types, } def test_get_subscription_by_param(): """Test get_subscription_by_param.""" subscription_name = 'approval' with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value subscriptions.get_subscription_by_param(subscription_name) assert session_mock.run.call_args_list[0][0] == ( 'MATCH (s:Subscription) ' 'WHERE true AND s.name = $subscriptionName ' 'RETURN ' 's.name as name, ' 's.notificationType as notificationType, ' 's.appId as appId, ' 's.feedType as feedType, ' 'collect(s.followedEntity) as followedEntityTypes', ) def test_get_subscription_by_type_and_entity(): """Test get_subscription_by_type_and_entity.""" feed_type = 'productApproval' followed_entity = 'Vendor' with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value subscriptions.get_subscription_by_param( feed_type=feed_type, followed_entity=followed_entity ) assert session_mock.run.call_args_list[0][0] == ( 'MATCH (s:Subscription) ' 'WHERE true AND s.feedType = $feedType AND s.followedEntity = $followedEntity ' 'RETURN ' 's.name as name, ' 's.notificationType as notificationType, ' 's.appId as appId, ' 's.feedType as feedType, ' 'collect(s.followedEntity) as followedEntityTypes', ) def test_edit_subscription_for_identity_auto_follow(): """Test edit_subscription_for_identity.""" identity_id = 'uuid' subscription_name = 'approval' profile_type = 'LabelProfile' follow_all_resources = True follow_resources = [] with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value subscriptions.edit_subscription_for_identity( identity_id, subscription_name, profile_type, follow_all_resources, follow_resources ) assert session_mock.run.call_count == 1 assert 'MERGE (p)-[newrel:HAS_SUBSCRIPTION]->(s)' in session_mock.run.call_args_list[0][0][0] assert 'MERGE (i)-[auto:HAS_AUTO_FOLLOWED]->(s)' in session_mock.run.call_args_list[0][0][0] assert ( 'MERGE (p)-[newrel:HAS_FOLLOWED_APPROVAL]->(r)' not in session_mock.run.call_args_list[0][0][0] ) def test_edit_subscription_for_identity_selected_follow(): """Test edit_subscription_for_identity.""" identity_id = 'uuid' subscription_name = 'approval' profile_type = 'LabelProfile' follow_all_resources = False follow_resources = ['vendor-uuid'] session_mock = get_session_mock(['success result']) with patch('notifications.models.subscriptions.get_session', return_value=session_mock): subscriptions.edit_subscription_for_identity( identity_id, subscription_name, profile_type, follow_all_resources, follow_resources ) assert session_mock.run.call_count == 1 assert 'MERGE (p)-[sub:HAS_SUBSCRIPTION]->(s)' in session_mock.run.call_args_list[0][0][0] assert 'MERGE (i)-[auto:HAS_AUTO_FOLLOWED]->(s)' not in session_mock.run.call_args_list[0][0][0] assert ( 'MERGE (p)-[newrel:HAS_FOLLOWED_APPROVAL]->(r)' in session_mock.run.call_args_list[0][0][0] ) assert ( 'EXISTS((p)-[:HAS_ACCESS_TO]->(:Vendor)-[:OWNS]->(r:Subaccount))' in session_mock.run.call_args_list[0][0][0] ) def test_delete_subscription_for_identity(): """Test delete_subscription_for_identity.""" identity_id = 'uuid' subscription_name = 'approval' profile_type = 'LabelProfile' with patch('notifications.models.subscriptions.get_session') as mock_fn: session_mock = mock_fn.return_value subscriptions.delete_subscription_for_identity(identity_id, subscription_name, profile_type) # There are 3 queries assert session_mock.run.call_count == 3 assert ( "CALL apoc.refactor.setType(auto, 'DELETED_HAS_AUTO_FOLLOWED')" in session_mock.run.call_args_list[0][0][0] ) assert ( "CALL apoc.refactor.setType(sub, 'DELETED_HAS_SUBSCRIPTION')" in session_mock.run.call_args_list[1][0][0] ) assert ( "CALL apoc.refactor.setType(foll, 'DELETED_HAS_FOLLOWED_APPROVAL')" in session_mock.run.call_args_list[2][0][0] ) def test_delete_selected_subscription_for_identity(): """Test delete_selected_subscription_for_identity.""" identity_id = 'uuid' subscription_name = 'approval' profile_type = 'LabelProfile' follow_resources = ['vendor-uuid'] session_mock = get_session_mock(['successfully deleted']) with patch('notifications.models.subscriptions.get_session', return_value=session_mock): subscriptions.delete_selected_subscription_for_identity( identity_id, subscription_name, profile_type, follow_resources ) assert session_mock.run.call_count == 1 assert ( "CALL apoc.refactor.setType(sub, 'DELETED_HAS_SUBSCRIPTION')" in session_mock.run.call_args[0][0] ) assert ( "CALL apoc.refactor.setType(rel, 'DELETED_HAS_FOLLOWED_APPROVAL')" in session_mock.run.call_args[0][0] )