"""Test prep_ddex handler.""" from unittest.mock import MagicMock, call, patch import pytest from ddex_ingester_common.lambda_exceptions import LambdaException from ddex_ingester_common.schemas.s3_schema import S3Schema from ddex_ingester_common.schemas.state_machine_schema import \ StateMachineSchema import index from constants.graphql_queries import (CREATE_AUDIO_PRODUCT, CREATE_PROJECT, CREATE_VIDEO_PRODUCT, GET_PROJECT_BY_PROJECT_CODE) from constants.release_type import ALBUM_RELEASE_TYPE, AUDIO_BOOK_RELEASE_TYPE from constants.sql_queries import (CLEAR_PREVIOUS_ADDED_STORE_CARVEOUTS, SELECT_REMAPPED_UPC, UPDATE_REMAPPED_UPC) @patch('index.genre_mapping_util.get_orchard_genre_mapping') @patch('index.load_ddex_json') @patch('index.save_s3_context') @patch('index.prep_store_carveouts') @patch('index.remap_upc') def test_handler( mock_remap_upc, mock_prep_store_carveouts, mock_save_s3_context, mock_load_ddex_json, mock_genre_mapping, context_object, s3_ddex): """Test handler.""" mock_load_ddex_json.return_value = s3_ddex mock_genre_mapping.return_value = { 'genre_id': 20, 'subgenre_id': 662 } context = index.handler(context_object, None) mock_save_s3_context.assert_called_once() assert context['product']['genre_id'] == 20 assert context['product']['subgenre_id'] == 662 @patch('index.genre_mapping_util.get_orchard_genre_mapping') @patch('index.load_ddex_json') @patch('index.save_s3_context') @patch('index.prep_store_carveouts') @patch('index.remap_upc') def test_handler_duplicate_japanese_codes( mock_remap_upc, mock_prep_store_carveouts, mock_save_s3_context, mock_load_ddex_json, mock_genre_mapping, context_object, s3_ddex_japanese_localizations): """Test handler with Japanese duplicate language codes.""" expected_lang_codes = ['RU', 'JA-JPAN'] mock_load_ddex_json.return_value = s3_ddex_japanese_localizations index.handler(context_object, None) mock_save_s3_context.assert_called_once() s3_context = mock_save_s3_context.call_args[0][1] product_title_lang_codes = [ localized_title.language_code.upper() for localized_title in s3_context.product.localized_titles ] for display_artist in s3_context.product.display_artists: display_artist_lang_codes = [ localized_name.language_code.upper() for localized_name in display_artist.localized_names ] assert display_artist_lang_codes == expected_lang_codes, 'Product display artist has mismatching lang codes' # noqa for track in s3_context.tracks: track_title_lang_codes = [ localized_title.language_code.upper() for localized_title in track.localized_titles ] for artist in track.display_artists: track_artist_lang_codes = [ localized_name.language_code.upper() for localized_name in artist.localized_names ] assert track_artist_lang_codes == expected_lang_codes, 'Track artist has mismatching lang codes' # noqa assert track_title_lang_codes == expected_lang_codes, 'Track title has mismatching lang codes' # noqa assert product_title_lang_codes == expected_lang_codes, 'Product title has mismatching lang codes' # noqa def test_prep_lyrics_language(s3_ddex): """Test prep_lyrics_language.""" s3_data = S3Schema().load(s3_ddex) s3_data.tracks[0].content_type = 'Music (instrumental)' index.prep_lyrics_language(s3_data) assert s3_data.tracks[0].lyrics_language == 'N/A' def test_prep_lyrics_language_default(s3_ddex): """Test prep_lyrics_language defaults to metadata_language.""" s3_data = S3Schema().load(s3_ddex) language = 'ENG' s3_data.product.metadata_language = language index.prep_lyrics_language(s3_data) assert s3_data.tracks[0].lyrics_language == language def test_prep_lyrics_language_existing_value(s3_ddex): """Test prep_lyrics_language with an existing lyrics_language.""" s3_data = S3Schema().load(s3_ddex) language = 'ENG' s3_data.tracks[0].lyrics_language = language index.prep_lyrics_language(s3_data) assert s3_data.tracks[0].lyrics_language == language def test_prep_lyrics_language_is_instrumental(s3_ddex): """Test prep_lyrics_language with is_instrumental set to True.""" s3_data = S3Schema().load(s3_ddex) s3_data.tracks[0].is_instrumental = True s3_data.tracks[0].lyrics_language = None index.prep_lyrics_language(s3_data) assert s3_data.tracks[0].lyrics_language == 'N/A' def test_prep_content_type(s3_ddex): """Test prep_content_type.""" s3_data = S3Schema().load(s3_ddex) s3_data.tracks[0].is_instrumental = True s3_data.tracks[0].content_type = None index.prep_content_type(s3_data) assert s3_data.tracks[0].content_type == 'Music (instrumental)' def test_prep_content_type_existing_value(s3_ddex): """Test prep_content_type with an existing content_type.""" s3_data = S3Schema().load(s3_ddex) content_type = 'Content' s3_data.tracks[0].is_instrumental = True s3_data.tracks[0].content_type = content_type index.prep_content_type(s3_data) assert s3_data.tracks[0].content_type == content_type def test_prep_us_publishing_obligation(s3_ddex): """Test prep_us_publishing_obligation.""" s3_data = S3Schema().load(s3_ddex) s3_data.tracks[0].us_publishing_obligation = None index.prep_us_publishing_obligation(s3_data) assert s3_data.tracks[0].us_publishing_obligation is None def test_update_context_with_not_for_distribution(context_object, s3_ddex): """Test update_context_with_not_for_distribution.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) not_for_distribution = 'AccountingDummy' s3_data.product.not_for_distribution = not_for_distribution index.update_context_with_not_for_distribution(context, s3_data) assert context.product.not_for_distribution == 'AccountingDummy' def test_update_context_with_nfd_default(context_object, s3_ddex): """Test default of update_context_with_not_for_distribution.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) default_not_for_distribution = 'N' index.update_context_with_not_for_distribution(context, s3_data) assert context.product.not_for_distribution == default_not_for_distribution def test_prep_audio_book_format_context(context_object, s3_ddex): """Test prep_audio_book_format.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) context.product.release_type = AUDIO_BOOK_RELEASE_TYPE index.prep_audio_book_format(context, s3_data) assert context.product.release_type == ALBUM_RELEASE_TYPE assert s3_data.product.release_type == ALBUM_RELEASE_TYPE def test_prep_audio_book_format_s3_context(context_object, s3_ddex): """Test prep_audio_book_format.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) s3_data.product.release_type = AUDIO_BOOK_RELEASE_TYPE index.prep_audio_book_format(context, s3_data) assert context.product.release_type == ALBUM_RELEASE_TYPE assert s3_data.product.release_type == ALBUM_RELEASE_TYPE def test_prep_audio_book_format_not_audio_book(context_object, s3_ddex): """Test prep_audio_book_format.""" release_type = 'Something' context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) context.product.release_type = release_type s3_data.product.release_type = release_type index.prep_audio_book_format(context, s3_data) assert context.product.release_type == release_type assert s3_data.product.release_type == release_type @patch('ddex_ingester_common.helpers.rds.mysql_connection') def test_prep_store_carveouts( mock_mysql_connection, context_object, s3_ddex): """Test prep_store_carveouts adds needed carveouts.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) upc = s3_data.product.upc mock_rds_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_rds_conn mock_cursor = MagicMock() mock_rds_conn.cursor.return_value.__enter__.return_value = mock_cursor mock_cursor.fetchall.return_value = [] index.prep_store_carveouts(context, s3_data) assert len(s3_data.product.stores) == 4 assert s3_data.product.stores[0].store_id == '1' assert s3_data.product.stores[1].store_id == '286' assert s3_data.product.stores[2].store_id == '173' assert s3_data.product.stores[3].store_id == '186' sql_calls = mock_cursor.execute.call_args_list assert sql_calls == [ call(CLEAR_PREVIOUS_ADDED_STORE_CARVEOUTS, args=upc), ] @patch('ddex_ingester_common.helpers.rds.mysql_connection') def test_prep_store_carveouts_all( mock_mysql_connection, context_object, s3_ddex): """Test prep_store_carveouts adds all needed carveouts.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) s3_data.product.stores = [] upc = s3_data.product.upc mock_rds_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_rds_conn mock_cursor = MagicMock() mock_rds_conn.cursor.return_value.__enter__.return_value = mock_cursor mock_cursor.fetchall.return_value = [] index.prep_store_carveouts(context, s3_data) assert len(s3_data.product.stores) == 0 sql_calls = mock_cursor.execute.call_args_list assert sql_calls == [ call(CLEAR_PREVIOUS_ADDED_STORE_CARVEOUTS, args=upc), ] @patch('index.DEFAULT_PRODUCT_TYPE_STORE_CARVEOUTS', {'AUDIO': []}) @patch('ddex_ingester_common.helpers.rds.mysql_connection') def test_prep_store_carveouts_none( mock_mysql_connection, context_object, s3_ddex): """Test what prep_store_carveouts does if there are no store carveouts.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) nr_stores = len(s3_data.product.stores) upc = s3_data.product.upc mock_rds_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_rds_conn mock_cursor = MagicMock() mock_rds_conn.cursor.return_value.__enter__.return_value = mock_cursor mock_cursor.fetchall.return_value = [] index.prep_store_carveouts(context, s3_data) assert len(s3_data.product.stores) == nr_stores sql_calls = mock_cursor.execute.call_args_list assert sql_calls == [call(CLEAR_PREVIOUS_ADDED_STORE_CARVEOUTS, args=upc)] def test_prep_alphanumeric_product_code(context_object, s3_ddex): """Make sure a non alphanumeric product code is fixed.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) s3_data.product.catalog_number = 'Invalid Product_Code' index.prep_alphanumeric_product_code(context, s3_data) assert s3_data.product.catalog_number == 'Invalid-Product-Code' def test_prep_alphanumeric_product_code_video(context_object, s3_ddex): """Make sure a non alphanumeric product code is fixed for video.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) context.product.release_type = 'VideoSingle' s3_data.video.product_code = 'Invalid Product_Code' index.prep_alphanumeric_product_code(context, s3_data) assert s3_data.video.product_code == 'Invalid-Product-Code' @patch('ddex_ingester_common.helpers.rds.mysql_connection') def test_remap_upc_no_remap( mock_mysql_connection, context_object, s3_ddex): """Test remap_upc with a UPC that does not need remapping.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) expected_upc = context.product.upc mock_rds_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_rds_conn mock_cursor = MagicMock() mock_rds_conn.cursor.return_value.__enter__.return_value = mock_cursor mock_cursor.fetchall.return_value = [] index.remap_upc(context, s3_data) mock_cursor.execute.assert_called_with( SELECT_REMAPPED_UPC, args=(expected_upc, 2) ) mock_cursor.fetchall.assert_called_with() assert context.product.upc == expected_upc assert s3_data.product.upc == expected_upc assert not s3_data.product.manufacturer_upc @patch('ddex_ingester_common.helpers.rds.mysql_connection') def test_remap_upc_remap( mock_mysql_connection, context_object, s3_ddex): """Test remap_upc with a UPC that needs remapping.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) vendor_id = context.product.vendor_id old_upc = context.product.upc new_upc = '231232463123' mock_rds_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_rds_conn mock_cursor = MagicMock() mock_rds_conn.cursor.return_value.__enter__.return_value = mock_cursor mock_cursor.fetchall.return_value = [{ 'remapped_upc': new_upc, 'vendor_id': vendor_id, }] index.remap_upc(context, s3_data) mock_cursor.execute.assert_called_with( SELECT_REMAPPED_UPC, args=(old_upc, 2) ) mock_cursor.fetchall.assert_called_with() assert context.product.upc == new_upc assert s3_data.product.upc == new_upc assert s3_data.product.manufacturer_upc == old_upc @patch('ddex_ingester_common.helpers.rds.mysql_connection') def test_remap_upc_vendor_id_mismatch( mock_mysql_connection, context_object, s3_ddex): """Test remap_upc when the DB vendor id is different from the DDEX.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) mock_rds_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_rds_conn mock_cursor = MagicMock() mock_rds_conn.cursor.return_value.__enter__.return_value = mock_cursor mock_cursor.fetchall.return_value = [{ 'remapped_upc': '231232463123', 'vendor_id': 111, }] with pytest.raises(LambdaException): index.remap_upc(context, s3_data) @patch('ddex_ingester_common.helpers.rds.mysql_connection') @patch('config.graphql_gateway') def test_remap_upc_new_upc_audio( mock_graphql_gateway, mock_mysql_connection, context_object, s3_ddex): """Test remap_upc with a UPC that needs remapping.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) vendor_id = context.product.vendor_id old_upc = context.product.upc new_upc = '231232463123' project_id = 987123 mock_rds_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_rds_conn mock_cursor = MagicMock() mock_rds_conn.cursor.return_value.__enter__.return_value = mock_cursor mock_cursor.fetchall.return_value = [{ 'remapped_upc': None, 'vendor_id': vendor_id, }] mock_graphql_gateway.execute.return_value = { 'data': { 'projectByProjectCode': {}, 'createProject': {'projectId': project_id}, 'createProduct': {'upc': new_upc}, } } index.remap_upc(context, s3_data) sql_calls = mock_cursor.execute.call_args_list assert sql_calls[0] == call( SELECT_REMAPPED_UPC, args=(old_upc, 2) ) assert sql_calls[1] == call( UPDATE_REMAPPED_UPC, args=(new_upc, old_upc, 2) ) mock_cursor.fetchall.assert_called_with() graphql_calls = mock_graphql_gateway.execute.call_args_list assert graphql_calls[0] == call( GET_PROJECT_BY_PROJECT_CODE, { 'projectCode': s3_data.project.project_code, 'accountId': context.product.vendor_id, 'subaccountId': 0, } ) assert graphql_calls[1] == call( CREATE_PROJECT, {'data': { 'projectCode': s3_data.project.project_code, 'name': f'{s3_data.project.project_code} Remap Dummy', 'artistId': 1, 'accountId': context.product.vendor_id, 'subaccountId': 0, }} ) assert graphql_calls[2] == call( CREATE_AUDIO_PRODUCT, {'data': { 'projectId': project_id, 'productName': f'{old_upc} Remap Dummy', 'productHighlights': 'abc', 'format': 'Full Length', 'accountId': context.product.vendor_id, 'subaccountId': None, }} ) assert context.product.upc == new_upc assert s3_data.product.upc == new_upc assert s3_data.product.manufacturer_upc == old_upc @patch('ddex_ingester_common.helpers.rds.mysql_connection') @patch('config.graphql_gateway') def test_remap_upc_new_upc_video( mock_graphql_gateway, mock_mysql_connection, context_object, s3_ddex): """Test remap_upc with a UPC that needs remapping.""" context = StateMachineSchema().load(context_object) s3_data = S3Schema().load(s3_ddex) context.product.release_type = 'VideoSingle' s3_data.product.vendor_id = 36271 vendor_id = context.product.vendor_id old_upc = context.product.upc new_upc = '231232463123' project_id = 987123 mock_rds_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_rds_conn mock_cursor = MagicMock() mock_rds_conn.cursor.return_value.__enter__.return_value = mock_cursor mock_cursor.fetchall.return_value = [{ 'remapped_upc': None, 'vendor_id': vendor_id, }] mock_graphql_gateway.execute.return_value = { 'data': { 'projectByProjectCode': {}, 'createProject': {'projectId': project_id}, 'saveVideoSingleProduct': {'upc': new_upc}, } } index.remap_upc(context, s3_data) sql_calls = mock_cursor.execute.call_args_list assert sql_calls[0] == call( SELECT_REMAPPED_UPC, args=(old_upc, 2) ) assert sql_calls[1] == call( UPDATE_REMAPPED_UPC, args=(new_upc, old_upc, 2) ) mock_cursor.fetchall.assert_called_with() graphql_calls = mock_graphql_gateway.execute.call_args_list assert graphql_calls[0] == call( GET_PROJECT_BY_PROJECT_CODE, { 'projectCode': s3_data.project.project_code, 'accountId': s3_data.product.vendor_id, 'subaccountId': 0, } ) assert graphql_calls[1] == call( CREATE_PROJECT, {'data': { 'projectCode': s3_data.project.project_code, 'name': f'{s3_data.project.project_code} Remap Dummy', 'artistId': 2780527, 'accountId': s3_data.product.vendor_id, 'subaccountId': 0, }} ) assert graphql_calls[2] == call( CREATE_VIDEO_PRODUCT, {'data': {'create': { 'accountId': s3_data.product.vendor_id, 'subaccountId': None, 'projectId': str(project_id), 'typeOfVideo': s3_data.video.video_type, 'isrc': s3_data.video.isrc, }}} ) assert context.product.upc == new_upc assert s3_data.product.upc == new_upc assert s3_data.product.manufacturer_upc == old_upc