"""Test handler.""" from unittest.mock import ANY, MagicMock, patch from constants import errors, queries from ddex_ingester_common.constants.ddex_providers import \ SME_ANALYTICS_PROVIDER, SOM_LIVRE_VENDOR_ID from ddex_ingester_common.constants.roles import ( DDEX_RESOURCE_CONTRIBUTOR_ROLE_TO_ORCHARD_PERFORMER_ROLE ) from ddex_ingester_common.constants.status import IN_CONTENT from ddex_ingester_common.lambda_exceptions import \ OwsProductUserIsForbiddenException, SetTrackMetadataException from ddex_ingester_common.release_correction.release_correction_constants import ( # noqa RELEASE_CORRECTION_TRACK_FIELDS) from ddex_ingester_common.release_correction.release_correction_diffs import ( ReleaseCorrectionDiffDetail) from ddex_ingester_common.schemas.s3_schema import S3Schema from ddex_ingester_common.schemas.state_machine_schema import ( StateMachineSchema ) import index from lambdacommon.graphql import graphql import pytest def return_input(i): """Return the input, used as side effect when mocking functions.""" return i @patch('index.logger.warning') @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') @patch('index.load_rc_json') @patch('index.update_track_metadata') @patch('index.update_label_participants') @patch('index.get_unique_roles') def test_handler( mock_get_unique_roles, mock_update_label_participants, mock_update_track_metadata, mock_load_rc_json, mock_load_ddex_json, mock_graphql_execute, mock_logger_warning, mock_event, s3_release_corrections): """Test the main handler.""" mock_load_ddex_json.return_value = mock_event.get('context') mock_load_rc_json.return_value = s3_release_corrections upc = '231232563123' mock_get_unique_roles.return_value = { 'MainArtist': 'Giveon', 'Invalid_Role': 'Artist' } index.handler(mock_event, None) mock_update_track_metadata.assert_called() args, _ = mock_update_track_metadata.call_args assert args[2] == upc assert args[3] == 45677 mock_update_label_participants.assert_called() mock_logger_warning.assert_called() @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') @patch('index.load_rc_json') @patch('index.get_unique_roles') def test_handler_known_graphql_errors( mock_get_unique_roles, mock_load_rc_json, mock_load_ddex_json, mock_graphql_execute, mock_event): """Test the main handler.""" mock_event['context']['product']['status'] = IN_CONTENT mock_load_ddex_json.return_value = mock_event.get('context') swear_word_found_graphql_response = [ { 'message': '400: BAD REQUEST', 'path': [ 'saveTracks', 0 ], 'extensions': { 'code': 'INTERNAL_SERVER_ERROR', 'serviceName': 'graphql-product', 'query': 'mutation{saveTracks(input:{update:{tracks:[33466504]body:{explicit:"C" isrc:"FIMUK1500001" lyrics:"Testing explicit lyrics - fuck - for ISRC: FIMUK1500001"}}}){tuid}}', # noqa 'variables': {}, 'response': { 'url': 'https://qa-ows-track.theorchard.io/track/33466504?get_artist_info_ids_from_existing_rows=1', # noqa 'status': 400, 'statusText': 'BAD REQUEST', 'body': { 'code': 'validation_error', 'message': { 'explicit': [ 'Swear word found' ] } } }, 'exception': { 'stacktrace': [ 'GraphQLError: 400: BAD REQUEST', ' at downstreamServiceError (/var/app/node_modules/@apollo/gateway/dist/executeQueryPlan.js:273:12)', # noqa ' at /var/app/node_modules/@apollo/gateway/dist/executeQueryPlan.js:181:65', # noqa ' at Array.map ()', ' at /var/app/node_modules/@apollo/gateway/dist/executeQueryPlan.js:181:52', # noqa ' at Generator.next ()', ' at fulfilled (/var/app/node_modules/@apollo/gateway/dist/executeQueryPlan.js:5:58)', # noqa ' at runMicrotasks ()', ' at processTicksAndRejections (internal/process/task_queues.js:97:5)' # noqa ] } } } ] expected_response = \ str(swear_word_found_graphql_response[0]['extensions']['response'])[1:-1] # noqa graphql_error = graphql.GraphQLError(swear_word_found_graphql_response) mock_graphql_execute.side_effect = graphql_error with pytest.raises(SetTrackMetadataException) as exception_info: index.handler(mock_event, None) raised_exception_string = str(exception_info.value) assert eval(raised_exception_string) assert expected_response in raised_exception_string assert eval(raised_exception_string)['ISRC'] == mock_event['track']['isrc'] @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') @patch('index.load_rc_json') @patch('index.get_unique_roles') def test_handler_ows_product_user_is_forbidden_exception( mock_get_unique_roles, mock_load_rc_json, mock_load_ddex_json, mock_graphql_execute, mock_event): """Test the main handler.""" mock_event['context']['product']['status'] = IN_CONTENT mock_load_ddex_json.return_value = mock_event.get('context') graphql_error = graphql.GraphQLError( [{'message': f'message {errors.OWS_PRODUCT_USER_IS_FORBIDDEN}'}]) mock_graphql_execute.side_effect = graphql_error with pytest.raises(OwsProductUserIsForbiddenException): index.handler(mock_event, None) def test_get_unique_roles(mock_event): """Test get_unique_roles.""" parsed_ddex = S3Schema().load(mock_event.get('context')) track = parsed_ddex.tracks[0] expected_result = { 'MainArtist': 'Giveon', 'FeaturedArtist': 'Artist', 'TestRole': 'BackgroundVocalist Name', 'PrimaryMusician': 'PrimaryMusician Name', 'Soloist': 'PrimaryMusician Name', 'AssociatedPerformer': 'PrimaryMusician Name', 'Choir': 'PrimaryMusician Name', 'BackgroundVocalist': 'BackgroundVocalist Name', 'StudioMusician': 'StudioMusician Name', 'DJ': 'StudioMusician Name', 'AAndRAdministrator': 'StudioMusician Name', 'ARAdministrator': 'StudioMusician Name', 'ARCoordinator': 'StudioMusician Name', 'Composer': 'StudioMusician Name', 'VocalEngineer': 'VocalEngineer Name', 'ComposerLyricist': 'VocalEngineer Name', 'Producer': 'Resource Contributor Producer', 'RecordingEngineer': 'Resource Contributor Producer' } result = index.get_unique_roles(track) assert result == expected_result @patch('index.graphql_gateway.execute') @patch('index.format_track_update_data') def test_update_track_metadata( mock_format_track_update_data, mock_graphql_execute, mock_event): """Test update_track_metadata.""" correlation_id = '3f58d406-ef96-4c8b-8c02-5f52504f77aa' upc = '231232563123' tuid = 45677 payload = { 'update': { 'tracks': [tuid], 'body': { 'upc': upc, 'isrc': 'USSM12001698', 'trackName': 'first', 'metaLanguageCode': 'ENG', 'explicit': 'C', 'pInfo': '2018 Sony Music Associated Records', 'artists': [ { 'artistName': 'Giveon', 'artistType': 'performer' }, { 'artistName': 'Artist', 'artistType': 'featuring' } ] } } } mock_format_track_update_data.return_value = payload context = StateMachineSchema().load(mock_event.get('context')) parsed_ddex = S3Schema().load(mock_event.get('context')) index.update_track_metadata( context, parsed_ddex.label_participants, upc, tuid, 'R&B', parsed_ddex.tracks[0], {}, correlation_id) mock_format_track_update_data.assert_called_with( ANY, ANY, upc, tuid, 'R&B', ANY, {} ) mock_graphql_execute.assert_called_with( queries.UPDATE_TRACK_METADATA, {'data': payload} ) @patch('index.get_track_participants') @patch('index.get_publishers') @patch('index.get_localizations') def test_format_track_update_data( mock_get_localizations, mock_get_publishers, mock_get_track_participants, mock_event): """Test format_track_update_data.""" track_participants = { 'participations': [ { 'labelParticipantUuid': '1A2B3C', 'role': 'PERFORMER', 'localizations': [{ 'languageId': 35, 'name': 'Russian Giveon' }] }, { 'labelParticipantUuid': '1A2B3C', 'role': 'FEATURED_ARTIST', 'localizations': [{ 'languageId': 35, 'name': 'Russian Artist' }] }, { 'labelParticipantUuid': '1A2B3C', 'role': 'WRITER' } ], 'performers': [ { 'name': 'PrimaryMusician', 'roleId': 27, 'type': 'primary' }, { 'name': 'PrimaryMusician', 'roleId': 129, 'type': 'primary' }, { 'name': 'BackgroundVocalist', 'roleId': 74, 'type': 'primary' } ] } mock_get_track_participants.return_value = track_participants publishers = [ {'name': 'publisher1'}, {'name': 'publisher2'}, {'name': 'publisher3'} ] mock_get_publishers.return_value = publishers localizations = [{ 'languageId': 35, 'trackName': 'Russian Title', 'version': 'Remaster' }] mock_get_localizations.return_value = localizations expected_result = { 'update': { 'tracks': [45677], 'body': { 'upc': '231232563123', 'isrc': 'USSM12001698', 'trackName': 'first', 'metaLanguageCode': 'ENG', 'explicit': 'C', 'pInfo': '2018 Sony Music Associated Records', 'lyrics': 'track lyrics', 'recordingCountryId': 1, 'originalRightsHolderCountryId': 1, 'ownershipRights': 'original_owner', 'usPublishingObligation': 'COMPOSITION', 'thirdPartyPublisher': 'Y', 'participations': track_participants['participations'], 'performers': track_participants['performers'], 'publishers': publishers, 'localizations': localizations, 'previewStartTime': 30000, 'offerType': 'STREAM_ONLY', } } } context = StateMachineSchema().load(mock_event.get('context')) # test Som Livre case for third_party_publisher context.product.vendor_id = SOM_LIVRE_VENDOR_ID parsed_ddex = S3Schema().load(mock_event.get('context')) result = index.format_track_update_data( context, parsed_ddex.label_participants, '231232563123', 45677, 'R&B', parsed_ddex.tracks[0], {}) assert result == expected_result def test_get_publishers(mock_event): """Test get_publishers.""" parsed_ddex = S3Schema().load(mock_event.get('context')) expected_result = [ {'name': 'publisher1'}, {'name': 'publisher2'}, {'name': 'publisher3'} ] result = index.get_publishers(parsed_ddex.tracks[0]) assert result == expected_result def test_get_localizations(mock_event): """Test get_localizations.""" parsed_ddex = S3Schema().load(mock_event.get('context')) expected_result = [{ 'languageId': 35, 'trackName': 'Russian Title', 'version': 'Remaster' }] result = index.get_localizations(parsed_ddex.tracks[0]) assert result == expected_result def test_get_localizations_null(mock_event): """Test get_localizations with a null value.""" parsed_ddex = S3Schema().load(mock_event.get('context')) expected_result = [] result = index.get_localizations(parsed_ddex.tracks[1]) assert result == expected_result def test_get_track_participants(mock_event): """Test get_track_participants.""" participations = [ { 'labelParticipantUuid': 'AAA', 'role': 'PERFORMER', 'localizations': [{'languageId': 35, 'name': 'Russian Giveon'}] }, { 'labelParticipantUuid': 'BBB', 'role': 'FEATURED_ARTIST', 'localizations': [{'languageId': 35, 'name': 'Russian Artist'}] }, { 'labelParticipantUuid': 'CCC', 'role': 'WRITER' }, { 'labelParticipantUuid': 'DDD', 'role': 'WRITER' }, { 'labelParticipantUuid': 'GGG', 'role': 'WRITER' }, { 'labelParticipantUuid': 'HHH', 'role': 'WRITER' }, { 'labelParticipantUuid': 'JJJ', 'role': 'PRODUCER' } ] performers = [ { 'name': 'PrimaryMusician Name', 'roleId': 27, 'type': 'primary' }, { 'name': 'PrimaryMusician Name', 'roleId': 129, 'type': 'primary' }, { 'name': 'BackgroundVocalist Name', 'roleId': 74, 'type': 'primary' }, { 'name': 'StudioMusician Name', 'roleId': 27, 'type': 'non-featured' }, { 'name': 'StudioMusician Name', 'roleId': 10, 'type': 'non-featured' } ] context = StateMachineSchema().load(mock_event.get('context')) parsed_ddex = S3Schema().load(mock_event.get('context')) genres = parsed_ddex.product.genres expected_result = { 'participations': participations, 'performers': performers } result = index.get_track_participants( context, parsed_ddex.label_participants, parsed_ddex.tracks[0], genres, {}) assert result == expected_result # TODO add get_track_participants test for classical genre # when genre is part of S3Schema def test_add_artist(mock_event): """Test add_artist.""" parsed_ddex = S3Schema().load(mock_event.get('context')) localized_names = parsed_ddex.tracks[0].display_artists[0].localized_names unmapped_roles = { 'MainArtist': 'Giveon', 'Invalid_Role': 'Artist' } mock_participant = MagicMock() mock_participant.label_participant_uuid = '1A2B3C' mock_participant.name = 'Giveon' result = [] index.add_artist( localized_names, mock_participant, 'MainArtist', result, 'R&B', unmapped_roles ) expected_result = { 'labelParticipantUuid': '1A2B3C', 'role': 'PERFORMER', 'localizations': [{ 'languageId': 35, 'name': 'Russian Giveon' }] } assert len(result) == 1 assert result[0] == expected_result assert unmapped_roles == {'Invalid_Role': 'Artist'} def test_add_artist_classical(mock_event): """Test add_artist with a role exclusive to classical music.""" parsed_ddex = S3Schema().load(mock_event.get('context')) parsed_ddex.product.genres[0].genre = 'CLASSICAL-00' genres = parsed_ddex.product.genres localized_names = parsed_ddex.tracks[0].display_artists[0].localized_names mock_participant = MagicMock() mock_participant.label_participant_uuid = '1A2B3C' mock_participant.name = 'Giveon' result = [] index.add_artist( localized_names, mock_participant, 'Orchestra', result, genres, {}) expected_result = { 'labelParticipantUuid': '1A2B3C', 'role': 'ORCHESTRA', 'localizations': [{ 'languageId': 35, 'name': 'Russian Giveon' }] } assert len(result) == 1 assert result[0] == expected_result def test_add_artist_not_classical(mock_event): """Test add_artist with a role exclusive to classical music.""" parsed_ddex = S3Schema().load(mock_event.get('context')) genres = parsed_ddex.product.genres localized_names = parsed_ddex.tracks[0].display_artists[0].localized_names mock_participant = MagicMock() mock_participant.label_participant_uuid = '1A2B3C' mock_participant.name = 'Giveon' result = [] index.add_artist( localized_names, mock_participant, 'Orchestra', result, genres, {}) assert result == [] def test_add_artist_repeat(mock_event): """Test add_artist with repeated artists.""" parsed_ddex = S3Schema().load(mock_event.get('context')) localized_names = parsed_ddex.tracks[0].display_artists[0].localized_names mock_participant = MagicMock() mock_participant.label_participant_uuid = '1A2B3C' mock_participant.name = 'Giveon' artists = [{ 'labelParticipantUuid': '1A2B3C', 'role': 'PERFORMER', 'localizations': [{ 'languageId': 35, 'name': 'Russian Giveon' }] }] index.add_artist( localized_names, mock_participant, 'MainArtist', artists, 'R&B', {}) assert len(artists) == 1 def test_add_writer(): """Test add_writer.""" unmapped_roles = { 'Composer': 'Giveon', 'Invalid_Role': 'Artist' } mock_participant = MagicMock() mock_participant.label_participant_uuid = '1A2B3C' mock_participant.name = 'Giveon' result = [] index.add_writer(mock_participant, 'Composer', result, unmapped_roles) expected_result = { 'labelParticipantUuid': '1A2B3C', 'role': 'WRITER', } assert len(result) == 1 assert result[0] == expected_result assert unmapped_roles == {'Invalid_Role': 'Artist'} def test_add_writer_repeat(): """Test add_writer with repeated writers.""" writers = [{ 'labelParticipantUuid': '1A2B3C', 'role': 'WRITER', }] mock_participant = MagicMock() mock_participant.label_participant_uuid = '1A2B3C' mock_participant.name = 'Giveon' index.add_writer(mock_participant, 'Composer', writers, {}) assert len(writers) == 1 def test_add_performer(): """Test add_performer.""" unmapped_roles = { 'OriginalArtist': 'Giveon', 'Invalid_Role': 'Artist' } result = [] index.add_performer( 'name', 'OriginalArtist', result, unmapped_roles, DDEX_RESOURCE_CONTRIBUTOR_ROLE_TO_ORCHARD_PERFORMER_ROLE ) expected_result = { 'name': 'name', 'roleId': 27, 'type': 'primary' } assert len(result) == 1 assert result[0] == expected_result assert unmapped_roles == {'Invalid_Role': 'Artist'} def test_add_performer_repeat(): """Test add_performer with repeated performers.""" performers = [{ 'name': 'name', 'roleId': 27, 'type': 'primary' }] index.add_performer( 'name', 'OriginalArtist', performers, {}, DDEX_RESOURCE_CONTRIBUTOR_ROLE_TO_ORCHARD_PERFORMER_ROLE ) assert len(performers) == 1 @patch('index.graphql_gateway.execute') @patch('index.get_label_participations') def test_update_label_participants( mock_get_label_participations, mock_graphql_execute, mock_event): """Test update_label_participants.""" label_participations = [ { 'labelParticipantId': 2, 'participationRoleName': 'Vocal Engineer', 'sequenceNumber': 1 }, { 'labelParticipantId': 2, 'participationRoleName': 'A & R Administrator', 'sequenceNumber': 2 }, { 'labelParticipantId': 2, 'participationRoleName': 'A & R Coordinator', 'sequenceNumber': 3 } ] mock_get_label_participations.return_value = label_participations expected_payload = { 'isrc': 'USSM12001698', 'vendorId': 123, 'subaccountId': 456, 'participations': label_participations } context = StateMachineSchema().load(mock_event.get('context')) parsed_ddex = S3Schema().load(mock_event.get('context')) index.update_label_participants(context, parsed_ddex.label_participants, parsed_ddex.tracks[0], {}) mock_graphql_execute.assert_called_with( queries.SET_LABEL_PARTICIPANTS, expected_payload ) @patch('index.logger.info') @patch('index.retrieve_label_participants') def test_get_label_participations( mock_retrieve_label_participants, mock_logger, mock_event): """Test get_track_participants.""" mock_participant = MagicMock() mock_participant.label_participant_id = '2' mock_retrieve_label_participants.return_value = mock_participant context = StateMachineSchema().load(mock_event.get('context')) parsed_ddex = S3Schema().load(mock_event.get('context')) unmapped_roles = { 'VocalEngineer': 'VocalEngineer Name', 'Invalid_Role': 'Artist' } expected_result = [ { 'labelParticipantId': '2', 'participationRoleName': 'Vocal Engineer', 'sequenceNumber': 1 }, { 'labelParticipantId': '2', 'participationRoleName': 'A & R Administrator', 'sequenceNumber': 2 }, { 'labelParticipantId': '2', 'participationRoleName': 'A & R Coordinator', 'sequenceNumber': 3 }, { 'labelParticipantId': '2', 'participationRoleName': 'Recording Engineer', 'sequenceNumber': 4 } ] result = index.get_label_participations( context, parsed_ddex.tracks[0], unmapped_roles) assert result == expected_result assert unmapped_roles == {'Invalid_Role': 'Artist'} def test_order_participants(mock_event): """Test order_participants.""" parsed_ddex = S3Schema().load(mock_event.get('context')) track = parsed_ddex.tracks[0] artists = track.display_artists track.display_artists = track.resource_contributors track.resource_contributors = artists result = index.order_participants(track) expected_list = [ 'VocalEngineer Name', 'StudioMusician Name', 'Resource Contributor Producer', 'Giveon', 'Artist', 'Composer Name', 'ComposerLyricist Name', 'PrimaryMusician Name', 'PrimaryMusician Name', 'BackgroundVocalist Name', ] i = 0 for name in expected_list: assert result[i].name == name i += 1 def test_is_track_role_true(mock_event): """Test is_track_role.""" parsed_ddex = S3Schema().load(mock_event.get('context')) parsed_ddex.product.genres[0].genre = 'CLASSICAL-00' genres = parsed_ddex.product.genres result = index.is_track_role('Composer', genres) assert result def test_role_is_track_role_false(mock_event): """Test is_track_role.""" parsed_ddex = S3Schema().load(mock_event.get('context')) genres = parsed_ddex.product.genres result = index.is_track_role('Composer', genres) assert not result def test_role_is_track_role_unknown(mock_event): """Test is_track_role.""" parsed_ddex = S3Schema().load(mock_event.get('context')) genres = parsed_ddex.product.genres result = index.is_track_role('unknown', genres) assert not result def test_get_preview_start_time(mock_event): """Test get_preview_start_time.""" parsed_ddex = S3Schema().load(mock_event.get('context')) track = parsed_ddex.tracks[0] result = index.get_preview_start_time(track) assert result == 30000 def test_get_preview_start_time_null(mock_event): """Test get_preview_start_time with a null value.""" parsed_ddex = S3Schema().load(mock_event.get('context')) track = parsed_ddex.tracks[0] track.preview_start_time = None result = index.get_preview_start_time(track) assert result is None def test_get_preview_start_time_zero(mock_event): """Test get_preview_start_time with a zero value.""" parsed_ddex = S3Schema().load(mock_event.get('context')) track = parsed_ddex.tracks[0] track.preview_start_time = 0 result = index.get_preview_start_time(track) assert result == 0 def test_get_preview_start_time_negative(mock_event): """Test get_preview_start_time with a negative value.""" parsed_ddex = S3Schema().load(mock_event.get('context')) track = parsed_ddex.tracks[0] track.preview_start_time = -1 result = index.get_preview_start_time(track) assert result is None @patch('index.includes_genre_updates', return_value=False) @patch('index.write_rc_json') @patch('index.load_rc_json') @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') def test_diff_for_release_corrections_no_diff( mock_load_ddex_json, mock_graphql_execute, mock_rc_json, mock_write_rc, mock_contains_genres, mock_event, graphql_response, s3_release_corrections): """Test that diff_for_release_corrections will write an RC detail.""" state_machine_context = StateMachineSchema().load( mock_event.get('context')) s3_data = S3Schema().load(mock_event.get('context')) s3_track = s3_data.tracks[0] graphql_response = graphql_response['data']['track'] mock_rc_json.return_value = s3_release_corrections mock_load_ddex_json.return_value = mock_event.get('context') mock_graphql_execute.return_value = graphql_response index.diff_for_release_corrections( s3_data.product.genres, 12345, state_machine_context, s3_track, graphql_response, s3_release_corrections, {}, s3_data.label_participants) mock_write_rc.assert_not_called() @patch('index.includes_genre_updates', return_value=False) @patch('index.remove_processed_details', side_effect=return_input) @patch('index.write_rc_json') @patch('index.load_rc_json') @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') def test_diff_for_release_corrections_creates_diff( mock_load_ddex_json, mock_graphql_execute, mock_rc_json, mock_write_rc, mock_remove_processed_details, mock_contains_genres, mock_event, graphql_response, s3_release_corrections): """Test that diff_for_release_corrections will write an RC detail.""" state_machine_context = StateMachineSchema().load( mock_event.get('context')) s3_data = S3Schema().load(mock_event.get('context')) s3_track = s3_data.tracks[0] graphql_response['data']['track']['pInfo'] = 'Old pLine' graphql_response['data']['track']['trackName'] = 'old name' mock_rc_json.return_value = s3_release_corrections mock_load_ddex_json.return_value = mock_event.get('context') mock_graphql_execute.return_value = graphql_response event_details = { 'bucket': state_machine_context.bucket, 'key': state_machine_context.key } expected_changes = {'changes': [ ReleaseCorrectionDiffDetail( field_name='trackName', isrc='USSM12001698', old='old name', new='first', db_table_name='track', db_field_name='track_name', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='pInfo', isrc='USSM12001698', old='Old pLine', new='2018 Sony Music Associated Records', db_table_name='track', db_field_name='p_line', email_customer=True, accept_update=True), ]} expected_payload = { 'productId': 234324, 'releaseCorrectionId': 123, 'corrections': [ { 'tableName': 'track', 'fieldName': 'track_name', 'keyValue': '"first"', 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'p_line', 'keyValue': '"2018 Sony Music Associated Records"', 'keyId': 12345 } ] } index.diff_for_release_corrections( s3_data.product.genres, 12345, state_machine_context, s3_track, graphql_response['data']['track'], s3_release_corrections, {}, s3_data.label_participants) mock_write_rc.assert_called_with(event_details, expected_changes) mock_graphql_execute.assert_called_with( queries.CREATE_PRODUCT_CORRECTION_DETAIL, expected_payload ) @patch('index.includes_genre_updates', return_value=True) @patch('index.remove_processed_details', side_effect=return_input) @patch('index.write_rc_json') @patch('index.load_rc_json') @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') def test_diff_for_release_corrections_creates_diff_including_genres( mock_load_ddex_json, mock_graphql_execute, mock_rc_json, mock_write_rc, mock_remove_processed_details, mock_contains_genres, mock_event, graphql_response, s3_release_corrections): """Test that diff_for_release_corrections will write an RC detail.""" state_machine_context = StateMachineSchema().load( mock_event.get('context')) mock_event['context']['tracks'][0]['display_artists'].append( { 'name': 'FeatArtist', 'sequence_number': 9, 'apple_id': '2A', 'spotify_uri': '2S', 'roles': ['FeaturedArtist'] } ) s3_data = S3Schema().load(mock_event.get('context')) s3_track = s3_data.tracks[0] graphql_response['data']['track']['pInfo'] = 'Old pLine' graphql_response['data']['track']['trackName'] = 'old name' graphql_response['data']['track']['primaryArtists'].append( {'artistName': 'FeatArtist', 'artistType': 'featuring'} ) mock_rc_json.return_value = s3_release_corrections mock_load_ddex_json.return_value = mock_event.get('context') mock_graphql_execute.return_value = graphql_response event_details = { 'bucket': state_machine_context.bucket, 'key': state_machine_context.key } expected_changes = {'changes': [ ReleaseCorrectionDiffDetail( field_name='featuring', isrc='USSM12001698', old=None, new=[{'type': 'featuring', 'name': 'Artist'}], db_table_name='track', db_field_name='featuring', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='featuring', isrc='USSM12001698', old=None, new=[{'type': 'featuring', 'name': 'FeatArtist'}], db_table_name='track', db_field_name='featuring', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='trackName', isrc='USSM12001698', old='old name', new='first', db_table_name='track', db_field_name='track_name', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='pInfo', isrc='USSM12001698', old='Old pLine', new='2018 Sony Music Associated Records', db_table_name='track', db_field_name='p_line', email_customer=True, accept_update=True) ]} expected_payload = { 'productId': 234324, 'releaseCorrectionId': 123, 'corrections': [ { 'tableName': 'track', 'fieldName': 'track_name', 'keyValue': '"first"', 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'p_line', 'keyValue': '"2018 Sony Music Associated Records"', 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'featuring', 'keyValue': '[{"type": "featuring", "name": "Artist"}, {"type": "featuring", "name": "FeatArtist"}]', # noqa 'keyId': 12345 } ] } index.diff_for_release_corrections( s3_data.product.genres, 12345, state_machine_context, s3_track, graphql_response['data']['track'], s3_release_corrections, {}, s3_data.label_participants) mock_write_rc.assert_called_with(event_details, expected_changes) mock_graphql_execute.assert_called_with( queries.CREATE_PRODUCT_CORRECTION_DETAIL, expected_payload ) def test_create_release_correction_detail(): """Test that create_release_correction_detail returns DiffDetail.""" field_name = 'trackName' isrc = 'USSM12001698' old_value = 'old val' new_value = 'new val' rc_track_field = RELEASE_CORRECTION_TRACK_FIELDS[field_name] expected_return = ReleaseCorrectionDiffDetail( field_name, isrc, old_value, new_value, rc_track_field.db_table_name, rc_track_field.db_field_name, rc_track_field.email_customer, rc_track_field.accept_update) response = index.create_release_correction_detail( field_name, isrc, old_value, new_value) assert response == expected_return @patch('index.includes_genre_updates', return_value=False) @patch('index.update_track_metadata') @patch('index.write_rc_json') @patch('index.load_rc_json') @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') def test_update_track_not_called_for_swbdummy( mock_load_ddex_json, mock_graphql_execute, mock_rc_json, mock_write_rc_json, mock_update_track_metadata, mock_contains_genres, mock_event, graphql_response, s3_release_corrections): """Check update_track not called for NFD=N for in_content product.""" mock_rc_json.return_value = s3_release_corrections mock_load_ddex_json.return_value = mock_event.get('context') mock_graphql_execute.return_value = graphql_response mock_event['context']['product']['status'] = IN_CONTENT mock_event['context']['product']['not_for_distribution'] = 'N' index.handler(mock_event, None) mock_update_track_metadata.assert_not_called() @patch('index.is_track_role', return_value=True) def test_compare_list_of_dicts_same_data( mock_is_track_role, mock_event, graphql_response): """Test compare_list_of_dicts compares correctly.""" s3_participations = [ { 'artist_name': 'Digital Farm Animals', 'role': 'primary_artist', }, { 'artist_name': 'Digital Farm Animals', 'role': 'producer', }, { 'artist_name': 'Cash Cash', 'role': 'primary_artist', }, { 'artist_name': 'Nelly', 'role': 'featuring', } ] diff = index.compare_list_of_dicts( s3_participations, s3_participations ) assert not diff @patch('index.is_track_role', return_value=True) def test_compare_list_of_dicts_diff_data( mock_is_track_role, mock_event, graphql_response): """Test compare_list_of_dicts compares correctly.""" mock_event = StateMachineSchema().load( mock_event.get('context')) s3_participations = [ { 'artist_name': 'Digital Farm Animals', 'role': 'primary_artist', }, { 'artist_name': 'Digital Farm Animals', 'role': 'remixer', }, { 'artist_name': 'Cash Cash', 'role': 'primary_artist', }, { 'artist_name': 'Nelly', 'role': 'featuring', } ] formatted_gql = [ { 'artist_name': 'Digital Farm Animals', 'role': 'primary_artist', }, { 'artist_name': 'Digital Farm Animals', 'role': 'producer', }, { 'artist_name': 'Cash Cash', 'role': 'primary_artist', }, { 'artist_name': 'Nelly', 'role': 'featuring', } ] expected = [ { 'artist_name': 'Digital Farm Animals', 'role': 'remixer' }, { 'role': 'producer', 'artist_name': 'Digital Farm Animals' } ] diff = index.compare_list_of_dicts( s3_participations, formatted_gql ) assert diff == expected @patch('index.includes_genre_updates', return_value=False) @patch('index.remove_processed_details', side_effect=return_input) @patch('index.compare_list_of_dicts') @patch('index.write_rc_json') @patch('index.load_rc_json') @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') def test_diff_for_release_corrections_for_artists( mock_load_ddex_json, mock_graphql_execute, mock_rc_json, mock_write_rc, mock_compare_dicts, mock_remove_processed_details, mock_contains_genres, mock_event, graphql_response, s3_release_corrections): """Test that diff_for_release_corrections will write artist RC detail.""" state_machine_context = StateMachineSchema().load( mock_event.get('context')) mock_event['context']['tracks'][0]['display_artists'].pop(1) s3_data = S3Schema().load(mock_event.get('context')) s3_track = s3_data.tracks[0] mock_rc_json.return_value = s3_release_corrections mock_compare_dicts.return_value = True mock_load_ddex_json.return_value = mock_event.get('context') mock_graphql_execute.return_value = graphql_response index.diff_for_release_corrections( s3_data.product.genres, 12345, state_machine_context, s3_track, graphql_response['data']['track'], s3_release_corrections, {}, s3_data.label_participants) event_details = { 'bucket': state_machine_context.bucket, 'key': state_machine_context.key } expected_changes = {'changes': [ ReleaseCorrectionDiffDetail( field_name='writer', isrc='USSM12001698', old=None, new=['Composer Name'], db_table_name='track', db_field_name='track_writer', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='writer', isrc='USSM12001698', old=None, new=['ComposerLyricist Name'], db_table_name='track', db_field_name='track_writer', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='writer', isrc='USSM12001698', old=None, new=['StudioMusician Name'], db_table_name='track', db_field_name='track_writer', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='writer', isrc='USSM12001698', old=None, new=['VocalEngineer Name'], db_table_name='track', db_field_name='track_writer', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='performers', isrc='USSM12001698', old=None, new=[{'birth_name': 'PrimaryMusician Name', 'performer_role_id': 27, 'type': 'primary'}], # noqa db_table_name='track', db_field_name='performers', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='performers', isrc='USSM12001698', old=None, new=[{'birth_name': 'PrimaryMusician Name', 'performer_role_id': 129, 'type': 'primary'}], # noqa db_table_name='track', db_field_name='performers', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='performers', isrc='USSM12001698', old=None, new=[{'birth_name': 'BackgroundVocalist Name', 'performer_role_id': 74, 'type': 'primary'}], # noqa db_table_name='track', db_field_name='performers', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='performers', isrc='USSM12001698', old=None, new=[{'birth_name': 'StudioMusician Name', 'performer_role_id': 27, 'type': 'non-featured'}], # noqa db_table_name='track', db_field_name='performers', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='performers', isrc='USSM12001698', old=None, new=[{'birth_name': 'StudioMusician Name', 'performer_role_id': 10, 'type': 'non-featured'}], # noqa db_table_name='track', db_field_name='performers', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='participants', isrc='USSM12001698', old=None, new=[{'participation': {'id': '82', 'name': 'VocalEngineer Name'}, 'participation_role_name': 'Vocal Engineer', 'sequence_number': 0}], # noqa db_table_name='track', db_field_name='participants', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='participants', isrc='USSM12001698', old=None, new=[{'participation': {'id': '72', 'name': 'StudioMusician Name'}, 'participation_role_name': 'A & R Administrator', 'sequence_number': 1}], # noqa db_table_name='track', db_field_name='participants', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='participants', isrc='USSM12001698', old=None, new=[{'participation': {'id': '72', 'name': 'StudioMusician Name'}, 'participation_role_name': 'A & R Coordinator', 'sequence_number': 2}], # noqa db_table_name='track', db_field_name='participants', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='participants', isrc='USSM12001698', old=None, new=[{'participation': {'id': '102', 'name': 'Resource Contributor Producer'}, 'participation_role_name': 'Recording Engineer', 'sequence_number': 3}], # noqa db_table_name='track', db_field_name='participants', email_customer=False, accept_update=True), ReleaseCorrectionDiffDetail( field_name='performer', isrc='USSM12001698', old=None, new=[{'type': 'performer', 'name': 'Giveon'}], db_table_name='track', db_field_name='track_artist', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='producer', isrc='USSM12001698', old=None, new=[{'type': 'producer', 'name': 'Resource Contributor Producer'}], # noqa db_table_name='track', db_field_name='producer', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='featuring', isrc='USSM12001698', old=None, new=[None], db_table_name='track', db_field_name='featuring', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='remixer', isrc='USSM12001698', old=None, new=[None], db_table_name='track', db_field_name='remixer', email_customer=True, accept_update=True), ]} expected_payload = { 'productId': 234324, 'releaseCorrectionId': 123, 'corrections': [ { 'tableName': 'track', 'fieldName': 'track_artist', 'keyValue': '[{"type": "performer", "name": "Giveon"}]', 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'featuring', 'keyValue': '[]', 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'remixer', 'keyValue': '[]', 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'producer', 'keyValue': '[{"type": "producer", "name": "Resource Contributor Producer"}]', # noqa 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'track_writer', 'keyValue': '["Composer Name", "ComposerLyricist Name", "StudioMusician Name", "VocalEngineer Name"]', # noqa 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'performers', 'keyValue': '[{"birth_name": "PrimaryMusician Name", "performer_role_id": 27, "type": "primary"}, {"birth_name": "PrimaryMusician Name", "performer_role_id": 129, "type": "primary"}, {"birth_name": "BackgroundVocalist Name", "performer_role_id": 74, "type": "primary"}, {"birth_name": "StudioMusician Name", "performer_role_id": 27, "type": "non-featured"}, {"birth_name": "StudioMusician Name", "performer_role_id": 10, "type": "non-featured"}]', # noqa 'keyId': 12345 }, { 'tableName': 'track', 'fieldName': 'participants', 'keyValue': '[{"participation": {"id": "82", "name": "VocalEngineer Name"}, "participation_role_name": "Vocal Engineer", "sequence_number": 0}, {"participation": {"id": "72", "name": "StudioMusician Name"}, "participation_role_name": "A & R Administrator", "sequence_number": 1}, {"participation": {"id": "72", "name": "StudioMusician Name"}, "participation_role_name": "A & R Coordinator", "sequence_number": 2}, {"participation": {"id": "102", "name": "Resource Contributor Producer"}, "participation_role_name": "Recording Engineer", "sequence_number": 3}]', # noqa 'keyId': 12345 } ] } mock_write_rc.assert_called_with(event_details, expected_changes) mock_graphql_execute.assert_called_with( queries.CREATE_PRODUCT_CORRECTION_DETAIL, expected_payload ) @patch('index.graphql_gateway.execute') def test_update_release_correction( mock_graphql_execute, mock_event, s3_release_corrections): """Test update_release_correction.""" state_machine_context = StateMachineSchema().load( mock_event.get('context')) s3_data = S3Schema().load(mock_event.get('context')) s3_track = s3_data.tracks[0] tuid = '1' genres = s3_data.product.genres rc_details = [ ReleaseCorrectionDiffDetail( field_name='performer', isrc='USSM12001698', old=None, new=[{'type': 'performer', 'name': 'Giveon'}], db_table_name='track', db_field_name='track_artist', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='composer', isrc='USSM12001698', old=None, new=[{'type': 'composer', 'name': 'Giveon'}], db_table_name='track', db_field_name='track_artist', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='producer', isrc='USSM12001698', old=None, new=[{'type': 'producer', 'name': 'Resource Contributor Producer'}], # noqa db_table_name='track', db_field_name='producer', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='featuring', isrc='USSM12001698', old=None, new=[None], db_table_name='track', db_field_name='featuring', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='remixer', isrc='USSM12001698', old=None, new=[None], db_table_name='track', db_field_name='remixer', email_customer=True, accept_update=True), ] expected_payload = { 'productId': 234324, 'releaseCorrectionId': 123, 'corrections': [ { 'tableName': 'track', 'fieldName': 'track_artist', 'keyValue': '[{"type": "performer", "name": "Giveon"}]', 'keyId': '1' }, { 'tableName': 'track', 'fieldName': 'featuring', 'keyValue': '[]', 'keyId': '1' }, { 'tableName': 'track', 'fieldName': 'remixer', 'keyValue': '[]', 'keyId': '1' }, { 'tableName': 'track', 'fieldName': 'producer', 'keyValue': '[{"type": "producer", "name": "Resource Contributor Producer"}]', # noqa 'keyId': '1' } ] } index.update_release_correction( state_machine_context, tuid, s3_track, rc_details, genres) mock_graphql_execute.assert_called_with( queries.CREATE_PRODUCT_CORRECTION_DETAIL, expected_payload ) @patch('index.graphql_gateway.execute') def test_update_release_correction_classical( mock_graphql_execute, mock_event, s3_release_corrections): """Test update_release_correction.""" mock_event['context']['product']['genres'] = [{ 'subgenre': None, 'genre': 'CLASSICAL-00' }] state_machine_context = StateMachineSchema().load( mock_event.get('context')) s3_data = S3Schema().load(mock_event.get('context')) s3_track = s3_data.tracks[0] tuid = '1' genres = s3_data.product.genres rc_details = [ ReleaseCorrectionDiffDetail( field_name='performer', isrc='USSM12001698', old=None, new=[{'type': 'performer', 'name': 'Giveon'}], db_table_name='track', db_field_name='track_artist', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='composer', isrc='USSM12001698', old=None, new=[{'type': 'composer', 'name': 'Giveon'}], db_table_name='track', db_field_name='track_artist', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='producer', isrc='USSM12001698', old=None, new=[{'type': 'producer', 'name': 'Resource Contributor Producer'}], # noqa db_table_name='track', db_field_name='producer', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='featuring', isrc='USSM12001698', old=None, new=[None], db_table_name='track', db_field_name='featuring', email_customer=True, accept_update=True), ReleaseCorrectionDiffDetail( field_name='remixer', isrc='USSM12001698', old=None, new=[None], db_table_name='track', db_field_name='remixer', email_customer=True, accept_update=True), ] expected_payload = { 'productId': 234324, 'releaseCorrectionId': 123, 'corrections': [ { 'tableName': 'track', 'fieldName': 'track_artist', 'keyValue': '[{"type": "performer", "name": "Giveon"}, {"type": "composer", "name": "Giveon"}]', # noqa 'keyId': '1' }, { 'tableName': 'track', 'fieldName': 'featuring', 'keyValue': '[]', 'keyId': '1' }, { 'tableName': 'track', 'fieldName': 'remixer', 'keyValue': '[]', 'keyId': '1' }, { 'tableName': 'track', 'fieldName': 'producer', 'keyValue': '[{"type": "producer", "name": "Resource Contributor Producer"}]', # noqa 'keyId': '1' } ] } index.update_release_correction( state_machine_context, tuid, s3_track, rc_details, genres) mock_graphql_execute.assert_called_with( queries.CREATE_PRODUCT_CORRECTION_DETAIL, expected_payload ) @patch('index.graphql_gateway.execute') def test_includes_genre_updates( mock_graphql_response, mock_event, graphql_release_correction): """Test includes_genre_updates method.""" state_machine_context = StateMachineSchema().load( mock_event.get('context')) mock_graphql_response.return_value = graphql_release_correction response = index.includes_genre_updates(state_machine_context) assert response is True @patch('index.graphql_gateway.execute') def test_get_release_corrections( mock_graphql_response, mock_event): """Test get_release_corrections method.""" upc = mock_event['context']['product']['upc'] index.get_release_corrections(upc) mock_graphql_response.assert_called_with( queries.GET_PRODUCT_RELEASE_CORRECTIONS, {'upc': upc} ) def test_sanitize_lyrics(): """Test sanitize_lyrics strips values.""" lyrics = 'Special lyrics ../' expected = 'Special lyrics' sanitized = index.sanitize_lyrics(lyrics) assert sanitized == expected @patch('index.graphql_gateway.execute') def test_update_track_metadata_with_special_lyrics( mock_graphql_execute, mock_update_track_metadata_event, mock_update_track_metadata_payload): """Test update_track_metadata.""" correlation_id = '3f58d406-ef96-4c8b-8c02-5f52504f77aa' upc = '231232563123' tuid = 45677 payload = mock_update_track_metadata_payload context = StateMachineSchema()\ .load(mock_update_track_metadata_event.get('context')) context.ddex_provider = SME_ANALYTICS_PROVIDER parsed_ddex = S3Schema()\ .load(mock_update_track_metadata_event.get('context')) graphql_forbiden_error = graphql.GraphQLError( [{'message': 'HTTP Error 403: Forbidden'}]) mock_graphql_execute.side_effect = [graphql_forbiden_error, None] index.update_track_metadata( context, parsed_ddex.label_participants, upc, tuid, 'R&B', parsed_ddex.tracks[0], {}, correlation_id) mock_graphql_execute.assert_any_call( queries.UPDATE_TRACK_METADATA, {'data': payload} ) payload['update']['body']['lyrics'] = '' # assert that second call after 403 is executed without lyrics mock_graphql_execute.assert_any_call( queries.UPDATE_TRACK_METADATA, {'data': payload} ) @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') @patch('index.update_track_metadata') @patch('index.get_track_name') def test_handler_with_placeholder_track_name( mock_get_track_name, mock_update_track_metadata, mock_load_ddex_json, mock_graphql_execute, mock_event, s3_release_corrections): """Test that meaningful track name is not replaced by palceholder.""" mock_event['track']['name'] = 'Track 1' mock_event['context']['tracks'][0]['track_name'] = 'Track 1' mock_event['context']['ddex_provider'] = SME_ANALYTICS_PROVIDER mock_load_ddex_json.return_value = mock_event.get('context') mock_get_track_name.return_value = 'Meaningful track name' index.handler(mock_event, None) mock_update_track_metadata.assert_called() mock_get_track_name.assert_called_with(45677) args, _ = mock_update_track_metadata.call_args assert args[5].track_name == 'Meaningful track name' @patch('index.graphql_gateway.execute') @patch('index.load_ddex_json') @patch('index.update_track_metadata') @patch('index.get_track_name') def test_handler_with_normal_track_name( mock_get_track_name, mock_update_track_metadata, mock_load_ddex_json, mock_graphql_execute, mock_event, s3_release_corrections): """Test that track name is updated.""" mock_event['track']['name'] = 'Perfect track name' mock_event['context']['tracks'][0]['track_name'] = 'Perfect track name' mock_event['context']['ddex_provider'] = SME_ANALYTICS_PROVIDER mock_load_ddex_json.return_value = mock_event.get('context') mock_get_track_name.return_value = 'Not that perfect track name' index.handler(mock_event, None) mock_update_track_metadata.assert_called() mock_get_track_name.assert_not_called() args, _ = mock_update_track_metadata.call_args assert args[5].track_name == 'Perfect track name' @pytest.mark.parametrize( 'track_name, expected_result', [ ('name', False), ('Tracks of my life', False), ('Tracks of my life 30', False), ('Tracks Of My Tears', False), ('Track 11 (Bonus Track)', False), ('Track about 99 foo bars', False), ('This is my Track 1', False), ('Track', True), ('Track 1', True), ('Track 03', True), ('Track 50', True), ('Track 123', True), ('Track 1995', True), ('track_5', True), ('Tracks', True), ('Track_2', True), ('Track Name', True), ('Track Name 1', True), ('Track Name1', True), ('Track Name2', True), ]) def test_track_name_is_placeholder(track_name, expected_result): """Test track_name_is_placeholder.""" assert index.track_name_is_placeholder(track_name) == expected_result