"""Tests for label participant logic.""" from http.client import OK from unittest.mock import MagicMock, call, patch import participant.constants.label_participant_queries as q import pytest from flask import g from owsresponse import response from participant.api import app from participant.constants import error from participant.logic import label_participant as label_participant_logic from participant.models import artist_name_updates from participant.models import base as base_models from participant.utils.convert import escape_term, format_response mock_label_participant_response = { 'name': 'L.G. Wise', 'vendorId': 8759, 'subaccountId': 0, 'spotifyId': '5xaT2601i5MC4h6yZa7EOD', 'id': 786281, 'appleMusicId': 111000530, } mock_label_participant_relationship_node_response = [ ( { 'name': 'L.G. Wise', 'vendorId': 8759, 'subaccountId': 0, 'spotifyId': '5xaT2601i5MC4h6yZa7EOD', 'id': 786281, 'appleMusicId': 111000530, }, {'id': 123, 'participated_as': 'performer'}, ) ] mock_label_participant_relationship_response = { 'role': 'performer', 'labelParticipant': { 'id': 786281, 'name': 'L.G. Wise', 'vendorId': 8759, 'subaccountId': 0, 'spotifyId': '5xaT2601i5MC4h6yZa7EOD', 'appleMusicId': 111000530, }, } mock_relationship_response = { 'type': 'PARTICIPATED_IN', 'properties': [{'name': 'participated_as', 'value': 'performer'}], } @pytest.mark.parametrize( ( 'test_description', 'label_participant_id', 'flask_g', 'get_node_result', 'expected_get_node_calls', 'expected_result_status', 'expected_result_message', ), [ ( 'Test participant get by id, no context, success.', 123, {}, mock_label_participant_response, [call(q.get_label_participant_by_id_only, id=123)], OK, format_response(mock_label_participant_response), ), ( 'Test participant get by id, vendor context, no subaccount, success.', 123, {'vendor_id': 54565, 'subaccount_id': 0}, mock_label_participant_response, [ call( q.get_label_participant_by_id.format(subaccount_clause=''), id=123, vendor_id=54565, subaccount_id=0, ) ], OK, format_response(mock_label_participant_response), ), ( ( 'Test participant get by id, vendor context, ' 'None subaccount, success.' ), 123, {'vendor_id': 54565, 'subaccount_id': 0}, mock_label_participant_response, [ call( q.get_label_participant_by_id.format(subaccount_clause=''), id=123, vendor_id=54565, subaccount_id=0, ) ], OK, format_response(mock_label_participant_response), ), ( 'Test participant get by id, vendor context, 0 subaccount, success.', 123, {'vendor_id': 54565, 'subaccount_id': 0}, mock_label_participant_response, [ call( q.get_label_participant_by_id.format(subaccount_clause=''), id=123, vendor_id=54565, subaccount_id=0, ) ], OK, format_response(mock_label_participant_response), ), ( 'Test participant get by id, subaccount context, success.', 123, {'vendor_id': 54565, 'subaccount_id': 4564654}, mock_label_participant_response, [ call( q.get_label_participant_by_id.format( subaccount_clause='AND node.subaccountId = $subaccount_id' ), id=123, vendor_id=54565, subaccount_id=4564654, ) ], OK, format_response(mock_label_participant_response), ), ( 'Test participant get by id, artist info context, success.', 123, {'artist_info_resources': [54565]}, mock_label_participant_response, [ call( q.get_label_participant_by_id_and_resources, id=123, artist_info_resources=[54565], ) ], OK, format_response(mock_label_participant_response), ), ], ) def test_get_label_participant_by_id( test_description, label_participant_id, flask_g, get_node_result, expected_get_node_calls, expected_result_status, expected_result_message, mocker, test_client, ): """Test participant get by id.""" mocker.patch.object( base_models, 'get_node', return_value=mock_label_participant_response, autospec=True, ) with app.test_request_context(): for key, val in flask_g.items(): setattr(g, key, val) result = label_participant_logic.get_label_participant_by_id( label_participant_id ) assert (base_models.get_node.mock_calls) == expected_get_node_calls assert result.status == expected_result_status assert result.message == expected_result_message @pytest.fixture def get_nodes_mock(mocker): """Mock get_nodes function.""" return mocker.patch.object( base_models, 'get_nodes', return_value=[mock_label_participant_response], autospec=True, ) @pytest.mark.parametrize( ('flask_g', 'subaccount_clause', 'role_clause', 'search_params'), [ ( {'vendor_id': 1, 'subaccount_id': 0}, '', '', {'name': ' test-search ', 'vendor_id': 1, 'subaccount_id': 0, 'role': ''}, ), ( {'vendor_id': 1, 'subaccount_id': 0}, 'AND node.subaccountId = $subaccount_id', '', {'name': ' test-search ', 'vendor_id': 1, 'subaccount_id': 2, 'role': ''}, ), ( {'vendor_id': 1, 'subaccount_id': 2}, 'AND node.subaccountId = $subaccount_id', '', {'name': ' test-search ', 'vendor_id': 1, 'subaccount_id': 2, 'role': ''}, ), ( {'vendor_id': 1, 'subaccount_id': 2}, 'AND node.subaccountId = $subaccount_id', 'WITH node, score MATCH (node)-[r:PARTICIPATED_IN]->(:Product) WHERE r.participated_as = "performer" OR r.participated_as = "featuring"', # noqa { 'name': ' test-search ', 'vendor_id': 1, 'subaccount_id': 2, 'role': 'true', }, ), ], ) def test_search_label_participant( get_nodes_mock, flask_g, subaccount_clause, role_clause, search_params ): """Test search_label_participant function.""" with app.test_request_context(): for key, val in flask_g.items(): setattr(g, key, val) result = label_participant_logic.search_label_participant( search_params, search_params['role'] ) query = escape_term(search_params['name']) query = query.strip() base_models.get_nodes.assert_called_once_with( q.search_label_participant.format( subaccount_clause=subaccount_clause, role_clause=role_clause ), wildcard_query=f'{query}*', fuzzy_query=f'{query}~', exact_query=f'"{query}"', default_query=f'{query}', vendor_id=search_params['vendor_id'], subaccount_id=search_params['subaccount_id'], ) assert result.message == format_response([mock_label_participant_response]) @pytest.fixture def get_nodes_and_relationship(mocker): """Mock get_nodes_and_relationship function.""" return mocker.patch.object( base_models, 'get_nodes_and_relationship', return_value=mock_label_participant_relationship_node_response, autospec=True, ) def test_get_label_participants_by_related_product_id(get_nodes_and_relationship): """Test get_label_participants_by_related_product_id function.""" product_id = 1 vendor_id = 123 subaccount_id = 456 result = label_participant_logic.get_label_participants_by_related_product_id( product_id, vendor_id, subaccount_id ) base_models.get_nodes_and_relationship.assert_called_once_with( q.get_label_participants_by_related_product_id, product_id=product_id, vendor_id=vendor_id, subaccount_id=subaccount_id, ) assert result.message == format_response( [mock_label_participant_relationship_response] ) @pytest.mark.parametrize( ('params', 'expected_result'), [ ( { 'from_node_label': 'wrong from label', 'to_node_label': 'Product', 'relationship_name': 'PARTICIPATED_IN', 'relationship_property': 'participated_as', }, response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ), ), ( { 'from_node_label': 'LabelParticipant', 'to_node_label': 'wrong to label', 'relationship_name': 'PARTICIPATED_IN', 'relationship_property': 'participated_as', }, response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ), ), ( { 'from_node_label': 'LabelParticipant', 'to_node_label': 'Product', 'relationship_name': 'wrong relationship name', 'relationship_property': 'participated_as', }, response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ), ), ( { 'from_node_label': 'LabelParticipant', 'to_node_label': 'Product', 'relationship_name': 'PARTICIPATED_IN', 'relationship_property': 'wrong relationship property', }, response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ), ), ( { 'from_node_label': 'LabelParticipant', 'to_node_label': 'Product', 'relationship_name': 'PARTICIPATED_IN', 'relationship_property': 'participated_as', 'relationship_value': 'wrong', }, response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ), ), ( { 'from_node_label': 'LabelParticipant', 'to_node_label': 'Product', 'relationship_name': 'PARTICIPATED_IN', 'relationship_property': 'participated_as', 'relationship_value': 'performer', }, response.Response(), ), ( { 'from_node_label': 'LabelParticipant', 'to_node_label': 'Product', 'relationship_name': 'PARTICIPATED_IN', 'relationship_property': 'participated_as', 'relationship_value': 'orchestra', }, response.Response(), ), ], ) def test_validate_relationship_data(params, expected_result): """Test validate_relationship_data function.""" result = label_participant_logic.validate_relationship_data( params['from_node_label'], params['to_node_label'], params['relationship_name'], params['relationship_property'], ) if result.errors: assert result.errors['message'] == expected_result.errors['message'] assert result.errors['code'] == expected_result.errors['code'] else: assert not result.errors @pytest.fixture def create_relationship_mock(mocker): """Mock create_relationship function.""" return mocker.patch.object( base_models, 'create_relationship', return_value=mock_relationship_response, autospec=True, ) @pytest.mark.parametrize( ('flask_g', 'params', 'expected_result'), [ ( {'vendor_id': 1, 'subaccount_id': 2}, { 'from_node': {'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', }, }, response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ), ), ( {'vendor_id': 1, 'subaccount_id': 2}, { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', }, }, response.Response(message=format_response(mock_relationship_response)), ), ], ) def test_create_relationship( create_relationship_mock, flask_g, params, expected_result ): """Test create_relationship function.""" with app.test_request_context(): for key, val in flask_g.items(): setattr(g, key, val) result = label_participant_logic.create_relationship( params['from_node'], params['to_node'], params['relationship'], ) if result.errors: assert result.errors['message'] == expected_result.errors['message'] assert result.errors['code'] == expected_result.errors['code'] else: expected_query = """ MATCH (fromNode:LabelParticipant:Orchard {id: $from_node_id}), (toNode:Product:Orchard {id: $to_node_id}) WHERE fromNode.vendorId = $vendor_id AND fromNode.subaccountId = $subaccount_id MERGE(fromNode) -[r:PARTICIPATED_IN {participated_as: $relationship_value}]->(toNode) RETURN r """ query_arg = base_models.create_relationship.call_args[0][0] query_arg = query_arg.replace('\n', '').replace(' ', '') expected_query = expected_query.replace('\n', '').replace(' ', '') assert expected_query == query_arg assert result.message == expected_result.message @pytest.fixture def update_relationship_mock(mocker): """Mock update_relationship function.""" return mocker.patch.object( base_models, 'update_relationship', return_value=mock_relationship_response, autospec=True, ) @pytest.mark.parametrize( ('flask_g', 'params', 'expected_result'), [ ( {'vendor_id': 1, 'subaccount_id': 2}, { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', }, }, response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ), ), ( {'vendor_id': 1, 'subaccount_id': 2}, { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', 'new_value': 'featuring', }, }, response.Response(message=format_response(mock_relationship_response)), ), ], ) def test_update_relationship( update_relationship_mock, flask_g, params, expected_result ): """Test update_relationship function.""" with app.test_request_context(): for key, val in flask_g.items(): setattr(g, key, val) result = label_participant_logic.update_relationship( params['from_node'], params['to_node'], params['relationship'], ) if result.errors: assert result.errors['message'] == expected_result.errors['message'] assert result.errors['code'] == expected_result.errors['code'] else: expected_query = """ MATCH (fromNode:LabelParticipant:Orchard {id: $from_node_id}) -[r:PARTICIPATED_IN {participated_as: $relationship_value}]-> (toNode:Product:Orchard {id: $to_node_id}) WHERE fromNode.vendorId = $vendor_id AND fromNode.subaccountId = $subaccount_id SET r.participated_as = $relationship_new_value RETURN r """ query_arg = base_models.update_relationship.call_args[0][0] query_arg = query_arg.replace('\n', '').replace(' ', '') expected_query = expected_query.replace('\n', '').replace(' ', '') assert expected_query == query_arg assert result.message == expected_result.message @pytest.fixture def delete_relationship_mock(mocker): """Mock delete_relationship function.""" return mocker.patch.object( base_models, 'delete_relationship', return_value=None, autospec=True, ) @pytest.mark.parametrize( ('flask_g', 'params', 'expected_result'), [ ( {'vendor_id': 1, 'subaccount_id': 2}, { 'from_node': {'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', }, }, response.create_error_response( code=error.ERROR_CODE_INVALID_USAGE, message=error.ERROR_MESSAGE_INVALID_USAGE, ), ), ( {'vendor_id': 1, 'subaccount_id': 2}, { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', }, }, response.Response(), ), ], ) def test_delete_relationship( delete_relationship_mock, flask_g, params, expected_result ): """Test delete_relationship function.""" with app.test_request_context(): for key, val in flask_g.items(): setattr(g, key, val) result = label_participant_logic.delete_relationship( params['from_node'], params['to_node'], params['relationship'], ) if result.errors: assert result.errors['message'] == expected_result.errors['message'] assert result.errors['code'] == expected_result.errors['code'] else: expected_query = """ MATCH (fromNode:LabelParticipant:Orchard {id: $from_node_id}) -[r:PARTICIPATED_IN {participated_as: $relationship_value}]-> (toNode:Product:Orchard {id: $to_node_id}) WHERE fromNode.vendorId = $vendor_id AND fromNode.subaccountId = $subaccount_id DELETE r """ query_arg = base_models.delete_relationship.call_args[0][0] query_arg = query_arg.replace('\n', '').replace(' ', '') expected_query = expected_query.replace('\n', '').replace(' ', '') assert expected_query == query_arg assert result.message == expected_result.message def test_update_artist_name(mocker): """Test update_artist_name function.""" artist_id = 123 data = {'artist_name': 'test'} artist_name_update_response = response.Response() mocker.patch.object( artist_name_updates, 'update_artist_name', return_value=artist_name_update_response, autospec=True, ) result = label_participant_logic.update_artist_name(artist_id, data) assert result.status == 200 def test_update_artist_name_for_merge(mocker): """Test update_artist_name function for merge.""" artist_id = 123 data = {'artist_name': 'test', 'request_type': 'merge', 'current_artist_id': 234} artist_name_update_response = response.Response() mocker.patch.object( artist_name_updates, 'merge_artist', return_value=artist_name_update_response, autospec=True, ) result = label_participant_logic.update_artist_name(artist_id, data) assert result.status == 200 @pytest.mark.parametrize( 'uuids, expected_items, description', [ ([], [], 'empty list returns empty list'), ( ['uuid1', 'missing-uuid', 'uuid2'], [ {'uuid': 'uuid1', 'label_participant_id': 123}, None, {'uuid': 'uuid2', 'label_participant_id': 789}, ], 'dataloader format means None will be returned if lookup returned nothing', ), ( ['uuid2', 'uuid1'], [ {'uuid': 'uuid2', 'label_participant_id': 789}, {'uuid': 'uuid1', 'label_participant_id': 123}, ], 'dataloader format means ordered list', ), ], ) @patch('participant.logic.label_participant.base_models') def test_lookup_participants_by_uuid( mock_base_models: MagicMock, uuids: list, expected_items: any, description: str, ) -> None: """Test lookup_participant_by_uuids response formatting.""" nodes_return_value = [ {'uuid': 'uuid1', 'label_participant_id': 123}, {'uuid': 'uuid2', 'label_participant_id': 789}, ] mock_base_models.get_records = MagicMock(return_value=nodes_return_value) result = label_participant_logic.lookup_participants_hierarchy_by_uuids(uuids) assert result.message['label_participants'] == expected_items, description if len(expected_items): mock_base_models.get_records.assert_called_once_with( q.lookup_label_participants_hierarchy_by_uuid, uuids=uuids, ) def test_update_lp_and_artist_name(mocker): """Test update_lp_and_artist_name function.""" label_participant_uuid = 'abc-def' data = {'updated_artist_name': 'test'} artist_name_update_response = response.Response() mocker.patch.object( artist_name_updates, 'update_lp_and_artist_name', return_value=artist_name_update_response, autospec=True, ) result = label_participant_logic.update_lp_and_artist_name( label_participant_uuid, data ) assert result.status == 200 def test_get_products_and_tracks_for_lp(mocker): """Test update_lp_and_artist_name function.""" label_participant_uuid = 'abc-def' artist_name_get_response = response.Response( {'status': 200, 'message': {'release': 2, 'track': 2}} ) mocker.patch.object( artist_name_updates, 'get_products_and_tracks_for_lp', return_value=artist_name_get_response, autospec=True, ) result = label_participant_logic.get_products_and_tracks_for_lp( label_participant_uuid ) assert result.status == 200 def test_merge_lp_and_artist_name(mocker): """Test merge_lp_and_artist_name function.""" uuid_1 = 'abc-def' uuid_2 = 'xyz-def' data = {'uuid_1': uuid_1, 'uuid_2': uuid_2} artist_name_merge_response = response.Response( {'status': 200, 'message': 'Artist is merged successfully.'} ) mocker.patch.object( artist_name_updates, 'merge_lp_and_artist_names', return_value=artist_name_merge_response, autospec=True, ) result = label_participant_logic.merge_lp_and_artist_name(data) assert result.status == 200