"""Participant handler tests.""" import json import textwrap from unittest.mock import MagicMock, call, patch from uuid import UUID import boto3 import pytest from neo4j.time import DateTime as Neo4jDateTime from owsrequest import context from owsrequest.constants import headers as owsrequest_headers from owsrequest.utils import mock_request from owsresponse import response from participant import config from participant.api import app from participant.constants import service as service_constants from participant.handlers import base as base_handlers from participant.handlers import label_participant as label_participant_handlers from participant.logic import label_participant as label_participant_logic mock_label_participant_response = { 'name': 'L.G. Wise', 'vendorId': 8759, 'subaccountId': 0, 'spotifyId': '5xaT2601i5MC4h6yZa7EOD', 'id': 786281, 'appleMusicId': 111000530, 'createdAt': Neo4jDateTime.from_iso_format('2020-01-01T00:00:00.000000000+00:00'), 'modifiedAt': Neo4jDateTime.from_iso_format('2020-02-01T00:00:00.000000000+00:00'), } mock_label_participant_response_json = { 'name': 'L.G. Wise', 'vendorId': 8759, 'subaccountId': 0, 'spotifyId': '5xaT2601i5MC4h6yZa7EOD', 'id': 786281, 'appleMusicId': 111000530, 'createdAt': '2020-01-01T00:00:00.000000000+00:00', 'modifiedAt': '2020-02-01T00:00:00.000000000+00:00', } @pytest.mark.parametrize( ( 'test_description', 'label_participant_id', 'flask_g', 'get_label_participant_by_id_status', 'get_label_participant_by_id_message', 'expected_get_label_participant_by_id_calls', 'expected_response_status', 'expected_response_json', ), [ ( 'Test label participant get by id, no context, success.', 123, {}, 222, mock_label_participant_response, [call(123)], 222, mock_label_participant_response_json, ), ], ) def test_get_label_participant_by_id( test_description, label_participant_id, flask_g, get_label_participant_by_id_status, get_label_participant_by_id_message, expected_get_label_participant_by_id_calls, expected_response_status, expected_response_json, mocker, test_client, ): """Test participant get by id.""" mocker.patch.object( label_participant_logic, 'get_label_participant_by_id', return_value=response.Response( mock_label_participant_response, status=get_label_participant_by_id_status ), autospec=True, ) with app.test_request_context(): handler_response = label_participant_handlers.get_label_participant_by_id( label_participant_id ) result = json.loads(handler_response.data.decode()) assert ( label_participant_logic.get_label_participant_by_id.mock_calls ) == expected_get_label_participant_by_id_calls assert handler_response.status_code == expected_response_status assert result == expected_response_json mock_label_participants_by_name_response = { 'items': [ {'name': 'The Moors', 'vendorId': 24503, 'subaccountId': 0, 'id': 355008}, { 'name': 'The Latin Brothers', 'vendorId': 24503, 'subaccountId': 0, 'id': 514843, 'createdAt': Neo4jDateTime.from_iso_format( '2020-01-01T00:00:00.000000000+00:00' ), 'modifiedAt': Neo4jDateTime.from_iso_format( '2020-02-01T00:00:00.000000000+00:00' ), }, { 'name': 'Eddy and The Antillan Group', 'vendorId': 24503, 'subaccountId': 0, 'id': 20511754, 'createdAt': Neo4jDateTime.from_iso_format( '2020-01-01T00:00:00.000000000+00:00' ), 'modifiedAt': Neo4jDateTime.from_iso_format( '2020-02-01T00:00:00.000000000+00:00' ), }, ], 'pagination': {'total_records': 7}, } mock_label_participants_by_name_response_json = { 'items': [ {'name': 'The Moors', 'vendorId': 24503, 'subaccountId': 0, 'id': 355008}, { 'name': 'The Latin Brothers', 'vendorId': 24503, 'subaccountId': 0, 'id': 514843, 'createdAt': '2020-01-01T00:00:00.000000000+00:00', 'modifiedAt': '2020-02-01T00:00:00.000000000+00:00', }, { 'name': 'Eddy and The Antillan Group', 'vendorId': 24503, 'subaccountId': 0, 'id': 20511754, 'createdAt': '2020-01-01T00:00:00.000000000+00:00', 'modifiedAt': '2020-02-01T00:00:00.000000000+00:00', }, ], 'pagination': {'total_records': 7}, } def test_label_participants_search(mocker, test_client): """Test label participant search success.""" mock = mocker.patch( 'participant.logic.label_participant.search_label_participant', return_value=response.Response(mock_label_participants_by_name_response), ) name = 'the' vendor_id = 24503 subaccount_id = 777 role = 'true' handler_response = test_client.get( f'/label-participants/search?name={name}' + f'&vendor_id={vendor_id}&subaccount_id={subaccount_id}&role={role}' ) result = json.loads(handler_response.data.decode()) mock.assert_called_with( {'name': name, 'vendor_id': vendor_id, 'subaccount_id': subaccount_id}, role ) assert handler_response.status_code == 200 assert result == mock_label_participants_by_name_response_json def test_label_participants_search_bad_name_results_in_400(mocker, test_client): """Test label participant search with bad name.""" mock = mocker.patch('participant.logic.label_participant.search_label_participant') name = '' vendor_id = 24503 handler_response = test_client.get( f'/label-participants/search?name={name}&vendor_id={vendor_id}' ) result = json.loads(handler_response.data.decode()) mock.assert_not_called() assert handler_response.status_code == 400 assert result['code'] == 'invalid_usage' assert result['message'] == {'subaccount_id': ['Field may not be null.']} def test_label_participants_search_bad_vendor_id_results_in_400(mocker, test_client): """Test label participant search with bad data.""" mock = mocker.patch('participant.logic.label_participant.search_label_participant') name = 'the' vendor_id = None handler_response = test_client.get( f'/label-participants/search?name={name}&vendor_id={vendor_id}' ) result = json.loads(handler_response.data.decode()) mock.assert_not_called() assert handler_response.status_code == 400 assert result['code'] == 'invalid_usage' assert result['message'] == { 'subaccount_id': ['Field may not be null.'], 'vendor_id': ['Not a valid integer.'], } @pytest.mark.parametrize( 'params', [ { 'name': 'Josh and the Supremes', 'vendor_id': 3232, 'subaccount_id': 12345, 'spotify_id': None, 'apple_music_id': None, }, { 'name': 'Josh and the Supremes', 'vendor_id': 3232, 'subaccount_id': 12345, 'spotify_id': 'spotify-id', 'apple_music_id': None, }, { 'name': 'Josh and the Supremes', 'vendor_id': 3232, 'subaccount_id': 12345, 'spotify_id': None, 'apple_music_id': 'apple-music-id', }, { 'name': 'Josh and the Supremes', 'vendor_id': 3232, 'subaccount_id': 12345, 'spotify_id': 'spotify-id', 'apple_music_id': 'apple-music-id', }, ], ) def test_label_participant_create_success(mocker, test_client, params): """Test label participant create success.""" request_params = {key: str(value) for key, value in params.items() if value} create_label_participant_response_mock = {'mock': 'response'} status = 333 mock = mocker.patch( 'participant.logic.label_participant.create_label_participant', return_value=response.Response( create_label_participant_response_mock, status=status ), ) handler_response = test_client.post('/label-participants', json=request_params) result = json.loads(handler_response.data.decode()) mock.assert_called_with(**params) assert handler_response.status_code == status assert result == create_label_participant_response_mock def test_label_participant_create_400_on_bad_input(mocker, test_client): """Test label participant create with bad data.""" name = 'Josh and the Supremes' vendor_id = 'non integer' subaccount_id = 'non integer' mock_create_label_participant_request = { 'name': f'{name}', 'vendor_id': f'{vendor_id}', 'subaccount_id': f'{subaccount_id}', } mock_create_label_participant_response = { 'code': 'validation_error', 'message': { 'error': { 'subaccount_id': ['Not a valid integer.'], 'vendor_id': ['Not a valid integer.'], } }, } mock = mocker.patch('participant.logic.label_participant.create_label_participant') handler_response = test_client.post( '/label-participants', json=mock_create_label_participant_request ) result = json.loads(handler_response.data.decode()) # mock not called since input validator short-circuits on bad data mock.assert_not_called() assert handler_response.status_code == 400 assert result == mock_create_label_participant_response # flake8: noqa @pytest.mark.parametrize( ('assigned_to_email', 'expected_list_of_email', 'participant_id'), [ ( 'applesauce.bananas@theorchard.com', ['applesauce.bananas@theorchard.com'], 334455, ), ( 'foo@theorchard.com', ['foo@theorchard.com'], 'a87122db-3d92-41fb-a3c9-9d5b35f3f6f8', ), ], ) def test_contact( mocker, test_client, assigned_to_email, expected_list_of_email, participant_id ): """Test contact.""" mock_request.get( service_constants.OWS_ACCOUNT, service_constants.OWS_ACCOUNT_VENDOR_DOCUMENT_RESOURCE.format(vendor_id=123), {'assigned_to_email': assigned_to_email}, 200, ) mocker.patch.object( context, 'get_request_context_from_headers', autospec=True, return_value=MagicMock( context_type=None, identity_id=None, profile_id=678, profile_type=owsrequest_headers.PROFILE_TYPE_LABEL, ), ) mocker.patch.object( base_handlers, 'get_vendor_and_subaccount', autospec=True, return_value=(123, 678), ) mocker.patch.object(boto3, 'client', autospec=True) with app.test_request_context(): test_client.post( '/contact', json={ 'participant_id': participant_id, 'participant_name': 'Name', 'message': 'a message about something', 'vendor_id': 123, 'subaccount_id': 678, 'product_id': 123, 'product_name': 'product name', 'upc': 'UPC', 'current_artist_name': 'current', 'new_artist_name': 'new', }, query_string={'vendor_id': 123, 'subaccount_id': 678}, ) boto3.client.return_value.send_email.assert_called_once_with( Source='info@theorchard.com', Destination={ 'ToAddresses': expected_list_of_email, 'CcAddresses': config.BASE_EMAIL_CC, }, Message={ 'Subject': { 'Data': 'Missing or Incorrect Artist Profile', 'Charset': 'UTF-8', }, 'Body': { 'Html': { 'Charset': 'UTF-8', 'Data': textwrap.dedent( f""" Greetings Label Manager,
Your label listed below has notified us that there's missing or incorrect information regarding an artist in their roster. Request Artist Name Update

Current Name:
current

New Artist Name:
new

Message:
a message about something

Artist Name:
Name

UPC:
UPC

Release/Product ID:
123

Product Name:
product name

Participant Id:
{str(participant_id)}

Vendor Id:
123

Subaccount Id:
678

Cheers,
Orchard bot. """ ), }, }, }, ) def test_create_relationship_success(mocker, test_client): """Test create relationship success.""" mock_create_relationship_response = { 'type': 'PARTICIPATED_IN', 'properties': [{'name': 'participated_as', 'value': 'performer'}], } mock_create_relationship_request = { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', }, 'vendor_id': 1, 'subaccount_id': 0, } status = 200 mock = mocker.patch( 'participant.logic.label_participant.create_relationship', return_value=response.Response( mock_create_relationship_response, status=status ), ) handler_response = test_client.post( f'/label-participants/relationship', json=mock_create_relationship_request, ) result = json.loads(handler_response.data.decode()) mock.assert_called_with( mock_create_relationship_request['from_node'], mock_create_relationship_request['to_node'], mock_create_relationship_request['relationship'], ) assert handler_response.status_code == status assert result == mock_create_relationship_response def test_create_relationship_bad_params(mocker, test_client): """Test create relationship bad params.""" mock_create_relationship_request = { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': {}, 'vendor_id': 1, } mock = mocker.patch( 'participant.logic.label_participant.create_relationship', return_value=response.Response(), ) handler_response = test_client.post( f'/label-participants/relationship', json=mock_create_relationship_request, ) result = json.loads(handler_response.data.decode()) mock.assert_not_called() assert result['code'] == 'invalid_usage' assert result['message'] == {'subaccount_id': ['Missing data for required field.']} def test_update_relationship_success(mocker, test_client): """Test update relationship success.""" mock_update_relationship_response = { 'type': 'PARTICIPATED_IN', 'properties': [{'name': 'participated_as', 'value': 'performer'}], } mock_update_relationship_request = { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', }, 'vendor_id': 1, 'subaccount_id': 0, } status = 200 mock = mocker.patch( 'participant.logic.label_participant.update_relationship', return_value=response.Response( mock_update_relationship_response, status=status ), ) handler_response = test_client.put( f'/label-participants/relationship', json=mock_update_relationship_request, ) result = json.loads(handler_response.data.decode()) mock.assert_called_with( mock_update_relationship_request['from_node'], mock_update_relationship_request['to_node'], mock_update_relationship_request['relationship'], ) assert handler_response.status_code == status assert result == mock_update_relationship_response def test_update_relationship_bad_params(mocker, test_client): """Test update relationship bad params.""" mock_update_relationship_request = { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': {'label': 'Product', 'id': '456'}, 'vendor_id': 1, } mock = mocker.patch( 'participant.logic.label_participant.update_relationship', return_value=response.Response(), ) handler_response = test_client.put( f'/label-participants/relationship', json=mock_update_relationship_request, ) result = json.loads(handler_response.data.decode()) mock.assert_not_called() assert result['code'] == 'invalid_usage' assert result['message'] == {'subaccount_id': ['Missing data for required field.']} def test_delete_relationship_success(mocker, test_client): """Test delete relationship success.""" mock_update_relationship_request = { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': { 'name': 'PARTICIPATED_IN', 'property': 'participated_as', 'value': 'performer', }, 'vendor_id': 1, 'subaccount_id': 0, } status = 200 mock = mocker.patch( 'participant.logic.label_participant.delete_relationship', return_value=response.Response(status=status), ) handler_response = test_client.delete( f'/label-participants/relationship', json=mock_update_relationship_request, ) result = handler_response mock.assert_called_with( mock_update_relationship_request['from_node'], mock_update_relationship_request['to_node'], mock_update_relationship_request['relationship'], ) assert handler_response.status_code == status assert result def test_delete_relationship_bad_params(mocker, test_client): """Test delete relationship bad params.""" mock_update_relationship_request = { 'from_node': {'label': 'LabelParticipant', 'id': '123'}, 'to_node': {'label': 'Product', 'id': '456'}, 'relationship': {'label': 'Product', 'id': '456'}, 'vendor_id': 1, } mock = mocker.patch( 'participant.logic.label_participant.delete_relationship', return_value=response.Response(), ) handler_response = test_client.delete( f'/label-participants/relationship', json=mock_update_relationship_request, ) result = json.loads(handler_response.data.decode()) mock.assert_not_called() assert result['code'] == 'invalid_usage' assert result['message'] == {'subaccount_id': ['Missing data for required field.']} def test_update_artist_name(mocker, test_client): """Test update artist name.""" mock_update_artist_name_request = {'artist_name': 'test'} mock = mocker.patch( 'participant.logic.label_participant.update_artist_name', return_value=response.Response(), ) handler_response = test_client.put( f'/artist-info/123/rename', json=mock_update_artist_name_request, ) assert handler_response.status_code == 200 def test_update_artist_name_with_incorrect_param(mocker, test_client): """Test update artist name.""" mock_update_artist_name_request = {'name': 'test'} mock = mocker.patch( 'participant.logic.label_participant.update_artist_name', return_value=response.Response(), ) handler_response = test_client.put( f'/artist-info/123/rename', json=mock_update_artist_name_request, ) result = json.loads(handler_response.data.decode()) mock.assert_not_called() assert result['code'] == 'invalid_usage' assert result['message'] == { 'artist_name': ['Missing data for required field.'], 'name': ['Unknown field.'], } @patch('participant.handlers.label_participant.label_participant') def test_lookup_label_participants_hierarchy_by_uuids( mock_label_participant, test_client, ): """Test lookup_label_participants_hierarchy_by_uuids.""" mock_label_participant.lookup_participants_hierarchy_by_uuids = MagicMock( return_value=response.Response({'label_participants': []}) ) handler_response = test_client.post( '/lookup/label-participants/uuids/tenant-hierarchy/', json={ 'uuids': [ '42d022c2-50e4-4f5c-af30-f10dca6b8a50', '54381846-a817-4ef4-9e58-7eae15bcf1dd', '715ab136-e896-4cb7-9c3f-becedcf7cdcc', ] }, ) assert handler_response.status_code == 200 assert handler_response.json == {'label_participants': []} mock_label_participant.lookup_participants_hierarchy_by_uuids.assert_called_once_with( [ UUID('42d022c2-50e4-4f5c-af30-f10dca6b8a50'), UUID('54381846-a817-4ef4-9e58-7eae15bcf1dd'), UUID('715ab136-e896-4cb7-9c3f-becedcf7cdcc'), ], ) @patch('participant.handlers.label_participant.label_participant') def test_lookup_label_participants_hierarchy_by_uuids_empty_uuids( mock_label_participant, test_client, ): """Test lookup_label_participants_hierarchy_by_uuids on empty uuids.""" mock_label_participant.lookup_participants_hierarchy_by_uuids = MagicMock() handler_response = test_client.post( '/lookup/label-participants/uuids/tenant-hierarchy/', json={'uuids': []}, ) assert handler_response.status_code == 400 assert handler_response.json == { 'code': 'invalid_usage', 'message': 'Invalid usage', } mock_label_participant.lookup_participants_hierarchy_by_uuids.assert_not_called() def test_update_lp_and_artist_name(mocker, test_client): """Test update artist name.""" mock_update_artist_name_request = {'updated_artist_name': 'test'} mock = mocker.patch( 'participant.logic.label_participant.update_lp_and_artist_name', return_value=response.Response(), ) handler_response = test_client.put( f'/label-participant/abc-def/rename', json=mock_update_artist_name_request, ) assert handler_response.status_code == 200 def test_update_lp_and_artist_name(mocker, test_client): """Test get number of releases and tracks for the artist name.""" mock = mocker.patch( 'participant.logic.label_participant.get_products_and_tracks_for_lp', return_value=response.Response(), ) handler_response = test_client.get(f'/label-participant/abc-def/product-tracks') assert handler_response.status_code == 200 def test_merge_label_participants_and_artists(mocker, test_client): """Test update artist name.""" mock_merge_artist_name_request = { 'uuid_1': 'abc', 'uuid_2': 'def', 'merged_artist_uuid': 'def', } mock = mocker.patch( 'participant.logic.label_participant.merge_lp_and_artist_name', return_value=response.Response(), ) handler_response = test_client.put( f'/label-participant/merge', json=mock_merge_artist_name_request, ) assert handler_response.status_code == 200