"""Test handler.""" from unittest.mock import MagicMock, patch from constants.status import EMAIL_FRIENDLY_STATUS from ddex_ingester_common.release_correction.release_correction_diffs import ( ReleaseCorrectionDiffDetail) import index from index import ( build_general_fail_payload, build_recipient_list, build_submit_validation_errors_payload, build_submit_video_validation_failure_payload, extract_submit_validation_errors, format_correction_field_name, generate_known_error_template, S3Schema, StateMachineSchema ) from mako.lookup import TemplateLookup import pytest def generate_standard_test_template(template_name, context, s3_data, **kwargs): """Generate test templates.""" mylookup = TemplateLookup(directories=['constants/templates']) base_template = mylookup.get_template(template_name) complete_template = base_template.render( upc=context.product.upc, sony_product_id=context.product.catalog_number, title=context.product.product_name, artist=context.product.display_artist_name, format=s3_data.product.release_type, owner=context.maintenance_owner, label=context.orchard_label, status=EMAIL_FRIENDLY_STATUS.get(context.product.status), thread_id=context.message_thread_id, grid=context.product.grid, nfd=context.product.not_for_distribution, # Fields specific to some templates errors=kwargs.get('errors'), vidops='vidops@theorchard.com', orch_proj_code=kwargs.get('orch_proj_code'), ddex_proj_code=kwargs.get('ddex_proj_code'), old_start_date=kwargs.get('old_start_date'), new_start_date=kwargs.get('new_start_date'), start_date=kwargs.get('start_date'), orig_release_date=kwargs.get('orig_release_date'), release_date=kwargs.get('release_date'), carveouts_updated=kwargs.get('carveouts_updated'), carveouts_old=kwargs.get('carveouts_old'), carveouts_new=kwargs.get('carveouts_new'), carveouts_added=kwargs.get('carveouts_added'), carveouts_removed=kwargs.get('carveouts_removed'), rc_errors=kwargs.get('rc_errors'), rc_audited_updates=kwargs.get('rc_audited_updates'), product_artist_updates=kwargs.get('product_artist_updates'), track_artist_updates=kwargs.get('track_artist_updates'), rc_warnings=kwargs.get('rc_warnings'), audio_asset_updates=kwargs.get('audio_asset_updates', []), publisher_warnings=kwargs.get('publisher_warnings', []), genre_warnings=kwargs.get('genre_warnings', []), video_update_warnings=kwargs.get('video_update_warnings', []), ).replace('\n', '') return complete_template def test_extract_submit_validation_errors_returns_empty_on_general_fail( context_parallel_fail): """Test errors are extracted.""" errors = extract_submit_validation_errors(context_parallel_fail['errors']) assert errors == [] def test_extract_submit_validation_errors_returns_validation_errors( context_submit_product_validation_results, build_errors): """Test extraction of validation result errors.""" errors = extract_submit_validation_errors( context_submit_product_validation_results['errors']) expected = build_errors assert errors == expected def test_extract_submit_validation_errors_chinese_localization( chinese_language_errors): """Test extraction of validation errors with chinese localization error.""" expected_result = ['ISRC: US3452345325: Simplified and Traditional variations are required when delivering Chinese content. One can be entered as the meta language while adding the other as a localization or both can be entered as localizations.'] # noqa result = extract_submit_validation_errors(chinese_language_errors) assert result == expected_result @patch('index.load_ddex_json') def test_build_template_with_validation_errors( mock_load_ddex_json, context_submit_product_validation_results, build_errors): """Test the building of the HTML payload.""" context = StateMachineSchema().load( context_submit_product_validation_results) s3_data = S3Schema().load(context_submit_product_validation_results) mock_load_ddex_json.return_value = s3_data email_payload = build_submit_validation_errors_payload( context, build_errors, s3_data) complete_template = generate_standard_test_template( 'submit_validation_fail.mak', context, s3_data, errors=build_errors) assert complete_template == email_payload @patch('index.load_ddex_json') def test_build_template_with_general_errors(mock_load_ddex_json, context_parallel_fail): """Test the building of the HTML payload.""" context = StateMachineSchema().load(context_parallel_fail) s3_data = S3Schema().load(context_parallel_fail) mock_load_ddex_json.return_value = s3_data email_payload = build_general_fail_payload( context, s3_data ) complete_template = generate_standard_test_template( 'generic_failure.mak', context, s3_data) assert complete_template == email_payload @patch('index.load_ddex_json') def test_generate_known_error_template_process_participants( mock_load_ddex_json, context_parallel_fail): """Test generate_known_error_template with a generic exception.""" expected_subject = 'Failure to ingest SME Interop Product 886447094297 - Test Artist - Seksikäs-Suklaa & Dosdela' # noqa: E501 expected_error_message = 'Different spotify_uri for participant "AYA": "spotify:artist:6qI2JDpIy2zx81uUdGqx64" and "spotify:artist:1IPTC92TkaOIMj9Gohi8MF"' # noqa: E501 error = { 'Error': 'ProcessParticipantsException', 'Cause': '{\"errorMessage\": \"Different spotify_uri for participant \\\"AYA\\\": \\\"spotify:artist:6qI2JDpIy2zx81uUdGqx64\\\" and \\\"spotify:artist:1IPTC92TkaOIMj9Gohi8MF\\\"\", \"errorType\": \"ProcessParticipantsException\", \"requestId\": \"6cc9a4b3-9575-4004-ba98-7c886a1df328\", \"stackTrace\": [\" File \\\"/var/lang/lib/python3.11/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 169, in sentry_handler\\n reraise(*exc_info)\\n\", \" File \\\"/var/lang/lib/python3.11/site-packages/sentry_sdk/_compat.py\\\", line 127, in reraise\\n raise value\\n\", \" File \\\"/var/lang/lib/python3.11/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 160, in sentry_handler\\n return handler(aws_event, aws_context, *args, **kwargs)\\n\", \" File \\\"/var/task/index.py\\\", line 45, in handler\\n participants = get_participants(parsed_ddex)\\n\", \" File \\\"/var/task/index.py\\\", line 119, in get_participants\\n check_participant_ids(\\n\", \" File \\\"/var/task/index.py\\\", line 190, in check_participant_ids\\n raise ProcessParticipantsException(message)\\n\"]}' # noqa: E501 } context = StateMachineSchema().load(context_parallel_fail) s3_data = S3Schema().load(context_parallel_fail) mock_load_ddex_json.return_value = s3_data email_payload, email_subject = generate_known_error_template( context, error, s3_data, {} ) assert email_subject == expected_subject assert expected_error_message in email_payload @patch('index.load_ddex_json') def test_generate_known_error_template_generic_exception( mock_load_ddex_json, context_parallel_fail): """Test generate_known_error_template with a generic exception.""" expected_subject = 'Failure to ingest SME Interop Product 886447094297 - Test Artist - Seksikäs-Suklaa & Dosdela' # noqa: E501 error = { 'Error': 'Exception', 'Cause': "{\"errorMessage\": \"Invalid language code LA\", \"errorType\": \"Exception\", \"requestId\": \"f0e0c976-bb3c-45b8-8330-b8a18e8c4d86\", \"stackTrace\": [\" File \\\"/var/lang/lib/python3.11/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 169, in sentry_handler\\n reraise(*exc_info)\\n\", \" File \\\"/var/lang/lib/python3.11/site-packages/sentry_sdk/_compat.py\\\", line 127, in reraise\\n raise value\\n\", \" File \\\"/var/lang/lib/python3.11/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 160, in sentry_handler\\n return handler(aws_event, aws_context, *args, **kwargs)\\n\", \" File \\\"/var/task/index.py\\\", line 51, in handler\\n validation_results = validate(load_ddex_json(event), rules)\\n\", \" File \\\"/var/task/index.py\\\", line 79, in validate\\n validation_results.append(rule(data))\\n\", \" File \\\"/var/lang/lib/python3.11/site-packages/ddex_ingester_common/validation/rules.py\\\", line 273, in validate_product_localization_language_codes\\n return validate_language_codes(\\n\", \" File \\\"/var/lang/lib/python3.11/site-packages/ddex_ingester_common/validation/rules.py\\\", line 374, in validate_language_codes\\n language_id = metadata_helper.get_language_id(\\n\", \" File \\\"/var/lang/lib/python3.11/site-packages/ddex_ingester_common/helpers/metadata.py\\\", line 154, in get_language_id\\n raise Exception(f'Invalid language code {language_code}')\\n\"]}" # noqa: E501 } context = StateMachineSchema().load(context_parallel_fail) s3_data = S3Schema().load(context_parallel_fail) mock_load_ddex_json.return_value = s3_data email_payload, email_subject = generate_known_error_template( context, error, s3_data, {} ) assert email_subject == expected_subject assert 'Invalid language code LA' in email_payload @patch('index.load_ddex_json') def test_build_template_with_video_validation_error( mock_load_ddex_json, context_submit_video_validation_failure): """Test the building of the HTML payload.""" context = StateMachineSchema().load( context_submit_video_validation_failure) s3_data = S3Schema().load(context_submit_video_validation_failure) mock_load_ddex_json.return_value = s3_data email_payload = build_submit_video_validation_failure_payload( context, s3_data ) complete_template = generate_standard_test_template( 'submit_video_validation_fail.mak', context, s3_data) assert complete_template == email_payload @patch('index.load_ddex_json') def test_build_template_with_general_errors_and_unknown_fields( mock_load_ddex_json, context_submit_product_validation_results): """Test the building of the HTML payload.""" context = StateMachineSchema().load( context_submit_product_validation_results) s3_data = S3Schema().load(context_submit_product_validation_results) mock_load_ddex_json.return_value = s3_data context.product.upc = None context.product.product_name = None context.product.display_artist_name = None s3_data.product.release_type = None context.maintenance_owner = None email_payload = build_general_fail_payload( context, s3_data ) complete_template = generate_standard_test_template( 'generic_failure.mak', context, s3_data) assert complete_template == email_payload @pytest.mark.parametrize('test_input,expected', [ (False, ['phanly@theorchard.com']), (True, [])]) @patch('index.load_rc_json') @patch('index.is_qa_env') @patch('index.check_for_email_cc_list', return_value='["qa-ddex-ingester@theorchard.com"]') @patch('helpers.email.get_email_addresses_for_major_label', return_value=None) @patch('index.send_email') @patch('index.retrieve_product_data') @patch('index.load_ddex_json') def test_qa_env_only_mails_support( mock_load_ddex_json, mock_product_data, mock_send_email, mock_get_email_addresses_for_major_label, mock_check_cc_list, mock_is_qa_env, mock_rc_json, context_in_content_update_success, graphql_response, test_input, expected): """Test that emails will only be sent to support in the QA env.""" mock_load_ddex_json.return_value = context_in_content_update_success mock_is_qa_env.return_value = test_input mock_contact = graphql_response['label'] index.retrieve_contact_details = MagicMock(return_value=mock_contact) index.handler(context_in_content_update_success, None) mock_send_email.assert_called_once() assert mock_send_email.mock_calls[0][1][3] == expected def test_artwork_errors_extracted_successfully(artwork_error_data, artwork_errors): """Test that artwork errors are extracted properly.""" errors = extract_submit_validation_errors(artwork_error_data['errors']) expected = artwork_errors assert errors == expected @patch('index.load_rc_json') @patch('index.retrieve_product_data') @patch('index.load_ddex_json') @patch('index.exit_without_email') def test_context_success_exits_gracefully( mock_exit_without_email, mock_load_ddex_json, mock_product_data, mock_rc_json, context_ingestion_success, graphql_response ): """Test that the handler exits gracefully on successful ingestion.""" context = index.StateMachineSchema().load(context_ingestion_success) expected_return = index.StateMachineSchema().dump(context) mock_product_data.return_value = graphql_response s3_data = context_ingestion_success mock_load_ddex_json.return_value = s3_data mock_exit_without_email.return_value = expected_return result = index.handler(context_ingestion_success, None) assert result == expected_return mock_exit_without_email.assert_called_once() @patch('index.retrieve_product_data') @patch('index.retrieve_release_date', return_value='2016-01-02') @patch('index.load_ddex_json') @patch('index.load_rc_json', return_value={}) def test_in_content_path_returns_success( mock_rc_json, mock_load_ddex_json, mock_retrieve_release_date, mock_product_data, context_in_content_update_success, graphql_response): """Test the return values provided by successful in content ingestion.""" context_in_content_update_success['warnings'] = [ { 'field': 'carveouts', 'warning_type': 'ReleaseCorrectionUpdateException', 'exception': "Product Carveouts update attempt. DDEX values ['AD', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW'] does not match The Orchard's Carveouts ['TA']" # noqa } ] warnings = ['Carveouts'] context = StateMachineSchema().load(context_in_content_update_success) s3_data = S3Schema().load(context_in_content_update_success) mock_product_data.return_value = graphql_response mock_load_ddex_json.return_value = s3_data expected_subject = 'Update to In Content SME Interop Product 886448369561 - Test Artist - Seksikäs-Suklaa & Dosdela' # noqa email_payload, subject = index.in_content_ddex_email_path( context, s3_data, graphql_response ) asset_updates = [] asset_updates.append('Audio asset updated. (USSM12001699)') asset_updates.append('Audio asset updated. (USSM12001611)') complete_template = generate_standard_test_template( 'update_success.mak', context, s3_data, old_start_date='2020-12-21', new_start_date='2010-12-21', orig_release_date='2016-01-01', release_date='2016-01-02', carveouts_updated=True, carveouts_old='AE, UE', carveouts_new='DE, GB, SE, US', carveouts_added='DE, GB, SE, US', carveouts_removed='AE, UE', rc_warnings=warnings, new_artwork=True, audio_asset_updates=asset_updates) assert complete_template == email_payload assert subject == expected_subject @patch('index.load_ddex_json') def test_in_processing_path_returns_val_errors( mock_load_ddex_json, context_submit_product_validation_results, graphql_response, build_errors): """Test in processing path returns validation result errors.""" context = StateMachineSchema().load( context_submit_product_validation_results) s3_data = S3Schema().load(context_submit_product_validation_results) mock_load_ddex_json.return_value = s3_data expected_subject = 'Failure to submit SME Interop Product 886447094297 - Test Artist - Seksikäs-Suklaa & Dosdela' # noqa email_payload, subject = index.processing_ddex_email_path( context, s3_data, graphql_response ) complete_template = generate_standard_test_template( 'submit_validation_fail.mak', context, s3_data, errors=build_errors) assert complete_template == email_payload assert subject == expected_subject @patch('index.load_ddex_json') def test_video_validation_error_path( mock_load_ddex_json, context_submit_video_validation_failure, graphql_response ): """Test in processing path returns validation result errors.""" context = StateMachineSchema().load( context_submit_video_validation_failure) s3_data = S3Schema().load(context_submit_video_validation_failure) mock_load_ddex_json.return_value = s3_data expected_subject = 'Failure to submit SME Interop Product 886448369561 - Giveon - Seksikäs-Suklaa & Dosdela' # noqa email_payload, subject = index.video_product_path( context, s3_data, graphql_response ) complete_template = generate_standard_test_template( 'submit_video_validation_fail.mak', context, s3_data) assert complete_template == email_payload assert subject == expected_subject @patch('index.load_ddex_json') def test_in_processing_path_returns_gen_errors(mock_load_ddex_json, context_parallel_fail, graphql_response): """Test in processing path returns validation result errors.""" context = StateMachineSchema().load(context_parallel_fail) s3_data = S3Schema().load(context_parallel_fail) mock_load_ddex_json.return_value = s3_data expected_subject = 'Failure to ingest SME Interop Product 886447094297 - Test Artist - Seksikäs-Suklaa & Dosdela' # noqa email_payload, subject = index.processing_ddex_email_path( context, s3_data, graphql_response ) complete_template = generate_standard_test_template( 'generic_failure.mak', context, s3_data) assert complete_template == email_payload assert subject == expected_subject @patch('index.load_ddex_json') @patch('index.load_rc_json', return_value={}) def test_in_content_path_returns_fail(mock_rc_json, mock_load_ddex_json, context_in_content_update_fail, graphql_response): """Test the return values provided by successful in content ingestion.""" context = StateMachineSchema().load(context_in_content_update_fail) s3_data = S3Schema().load(context_in_content_update_fail) mock_load_ddex_json.return_value = s3_data expected_subject = 'Invalid Update to In Content SME Interop Product 886448369561 - Test Artist - Seksikäs-Suklaa & Dosdela' # noqa email_payload, subject = index.in_content_ddex_email_path( context, s3_data, graphql_response ) complete_template = generate_standard_test_template( 'update_failure.mak', context, s3_data) assert complete_template == email_payload assert subject == expected_subject @patch('index.load_ddex_json') def test_no_orchard_id_error_payload(mock_load_ddex_json, context_product_not_found, graphql_response): """Test that OrchardProductNotFound produces correct email template.""" context = StateMachineSchema().load(context_product_not_found) s3_data = S3Schema().load(context_product_not_found) mock_load_ddex_json.return_value = s3_data expected_subject = 'Failure to find SME Interop Product 886447094297 - Test Artist - Seksikäs-Suklaa & Dosdela' # noqa email_payload, subject = index.processing_ddex_email_path( context, s3_data, graphql_response ) complete_template = generate_standard_test_template( 'no_product_found.mak', context, s3_data) assert complete_template == email_payload assert subject == expected_subject @patch('index.load_ddex_json') def test_multiple_generic_fails_returns_generic_error( mock_load_ddex_json, context_multiple_generic_errors, graphql_response): """Test that multiple unknown errors result in a generic fail email.""" context = StateMachineSchema().load(context_multiple_generic_errors) s3_data = S3Schema().load(context_multiple_generic_errors) mock_load_ddex_json.return_value = s3_data email_payload, email_subject = index.processing_ddex_email_path( context, s3_data, graphql_response ) expected_subject = 'Failure to ingest SME Interop Product 886447094297 - Test Artist - Seksikäs-Suklaa & Dosdela' # noqa complete_template = generate_standard_test_template( 'generic_failure.mak', context, s3_data) assert complete_template == email_payload assert email_subject == expected_subject @pytest.mark.parametrize( 'context_known_error', ['LookupSwitchboardDealException'], indirect=True) def test_no_swb_deal_returns_template( context_known_error, graphql_response): """Test that no SWB deal error returns appropriate template.""" context = StateMachineSchema().load(context_known_error) s3_data = S3Schema().load(context_known_error) expected_subject = "No Switchboard Deal for SME Interop Product 884977807875 - Michael Jackson - Michael Jackson's Vision" # noqa email_payload, email_subject = index.processing_ddex_email_path( context, s3_data, graphql_response ) complete_template = generate_standard_test_template( 'no_swb_deal_found.mak', context, s3_data) assert complete_template == email_payload assert email_subject == expected_subject @pytest.mark.parametrize( 'context_known_error', ['LookupSwitchboardInactiveDealException'], indirect=True) def test_inactive_swb_deal_returns_template( context_known_error, graphql_response): """Test that an inactive SWB deal error returns appropriate template.""" context = StateMachineSchema().load(context_known_error) s3_data = S3Schema().load(context_known_error) expected_subject = "No Active Switchboard Deal for SME Interop Product 884977807875 - Michael Jackson - Michael Jackson's Vision" # noqa email_payload, email_subject = index.processing_ddex_email_path( context, s3_data, graphql_response ) complete_template = generate_standard_test_template( 'inactive_swb_deal_found.mak', context, s3_data) assert complete_template == email_payload assert email_subject == expected_subject @patch('index.load_lambda_rc_json_files') @patch('index.retrieve_vidops_email', return_value='vidops@theorchard.com') @patch('index.load_ddex_json') def test_video_update_in_content_update_fail(mock_load_ddex_json, mock_retrieve_vidops_email, mock_load_rc_files, context_video_in_content, graphql_response): """Test that correct payload is returned for in_content video updates.""" context = StateMachineSchema().load(context_video_in_content) s3_data = S3Schema().load(context_video_in_content) mock_load_ddex_json.return_value = s3_data expected_subject = "Failure to update In Content SME Interop Product 886448369561 - Test Artist - Seksikäs-Suklaa & Dosdela" # noqa graphql_response['status'] = 'in_content' context.product.status = 'in_content' rc = [ [ 'videoAsset', None, 'Old Version', 'New Version', 'releases', 'version', True, True ], [ 'releaseDate', None, '2001-01-01', '2012-12-12', 'releases', 'label', True, True ] ] email_payload, email_subject = index.build_video_update_payload( context, s3_data, rc ) warnings = [ { 'field': 'Video Asset', 'current_value': 'Old Version', 'new_value': 'New Version' }, { 'field': 'Release Date', 'current_value': '2001-01-01', 'new_value': '2012-12-12' } ] complete_template = generate_standard_test_template( 'fail_update_video.mak', context, s3_data, video_update_warnings=warnings) assert complete_template == email_payload assert email_subject == expected_subject @patch('index.build_general_fail_payload') @patch('index.load_ddex_json') def test_video_general_fail_scenario( mock_load_ddex_json, mock_build_general_fail_payload, context_video_in_content, graphql_response ): """Test that generic Video fails are reported correctly.""" context = StateMachineSchema().load(context_video_in_content) s3_data = S3Schema().load(context_video_in_content) s3_data = context_video_in_content mock_load_ddex_json.return_value = s3_data mock_build_general_fail_payload.return_value = '' context.errors = [ { 'Error': 'UnknownFailReason' }, { 'Error': 'AnotherReason' } ] index.video_product_path( context, s3_data, graphql_response ) mock_build_general_fail_payload.assert_called_once() @patch('index.load_ddex_json') def test_project_code_mismatch_fail( mock_load_ddex_json, context_video_in_content, graphql_response ): """Test that project code mismatches are reported correctly.""" context = StateMachineSchema().load(context_video_in_content) s3_data = S3Schema().load(context_video_in_content) mock_load_ddex_json.return_value = s3_data context.errors = [ { 'Error': 'ProjectCodeMismatchException' }, { 'Error': 'AnotherReason' } ] expected_subject = "Failure to ingest SME Interop Product 886448369561 - Test Artist - Seksikäs-Suklaa & Dosdela" # noqa email_payload, email_subject = index.video_product_path( context, s3_data, graphql_response ) complete_template = generate_standard_test_template( 'fail_project_code_mismatch.mak', context, s3_data, orch_proj_code=graphql_response['project']['projectCode'], ddex_proj_code=s3_data.project.project_code) assert complete_template == email_payload assert email_subject == expected_subject @pytest.mark.parametrize('new, expected', [ ( ['AE', 'DE'], { 'carveouts_updated': True, 'carveouts_old': 'AE, UE', 'carveouts_new': 'AE, DE', 'carveouts_added': 'DE', 'carveouts_removed': 'UE' } ), ( ['AE', 'DE', 'UE'], { 'carveouts_updated': True, 'carveouts_old': 'AE, UE', 'carveouts_new': 'AE, DE, UE', 'carveouts_added': 'DE', 'carveouts_removed': '' } ), ( [], { 'carveouts_updated': True, 'carveouts_old': 'AE, UE', 'carveouts_new': '', 'carveouts_added': '', 'carveouts_removed': 'AE, UE' } ), ( ['AE', 'UE'], { 'carveouts_updated': None, 'carveouts_old': '', 'carveouts_new': '', 'carveouts_added': '', 'carveouts_removed': '' } )]) def test_format_carveout_changes(new, expected): """Test the format_carveout_changes method.""" orig = ['AE', 'UE'] response = index.format_carveout_changes(orig, new) assert response == expected assert response['carveouts_updated'] is expected['carveouts_updated'] @pytest.mark.parametrize('orig, new, expected', [ (['AE', 'DE'], ['AE', 'DE'], False), (['AE', 'DE'], ['AE', 'UE'], True), ([], [], False)]) def test_carveouts_changed(orig, new, expected): """Test carveouts_changed method.""" response = index.carveouts_changed(orig, new) assert response is expected @patch('index.load_rc_json') @patch('index.load_ddex_json') def test_build_release_correction_fail_payload( mock_load_ddex_json, mock_rc_json, context_video_in_content, graphql_response): """Test build_release_correction_fail_payload method.""" context_video_in_content['warnings'] = [ { 'field': 'carveouts', 'warning_type': 'ReleaseCorrectionUpdateException', 'exception': "Product Carveouts update attempt. DDEX values ['AD', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW'] does not match The Orchard's Carveouts ['TA']" # noqa }, { 'field': 'publisherNames', 'warning_type': 'ReleaseCorrectionUpdateException', 'exception': {'isrc': 'QZCDB2000001', 'current_values': ['Publisher 1', 'Publisher 2', 'Publisher 3'], 'new_values': ['Publisher 1', 'Publisher 2']} # noqa }, { 'field': 'publisherNames', 'warning_type': 'ReleaseCorrectionUpdateException', 'exception': {'isrc': 'QZCDB2000002', 'current_values': ['Publisher 3', 'Publisher 4', 'Publisher 3'], 'new_values': ['Publisher 2', 'Publisher 3']} # noqa }, ] context = StateMachineSchema().load(context_video_in_content) s3_data = S3Schema().load(context_video_in_content) rc_json = { 'changes': [ [ 'format', 'Single', 'SomeDustyVinyl', 'releases', 'format', True, False ], [ 'other_field', 'OldValue', 'NewValue', 'releases', 'other_field', True, False ], ] } mock_rc_json.return_value = rc_json context.errors['Error'] = 'ReleaseCorrectionUpdateException' mock_load_ddex_json.return_value = s3_data email_payload = index.build_release_correction_fail_payload( context, s3_data, graphql_response ) formatted_errors = set( format_correction_field_name(ReleaseCorrectionDiffDetail(*change)) for change in rc_json.get('changes') ) formatted_errors.add('Carveouts') publisher_warnings = [ { 'isrc': 'Publishers (QZCDB2000001)', 'current_values': 'Publisher 1
Publisher 2
Publisher 3
', # noqa 'new_values': 'Publisher 1
Publisher 2
' }, { 'isrc': 'Publishers (QZCDB2000002)', 'current_values': 'Publisher 3
Publisher 4
Publisher 3
', # noqa 'new_values': 'Publisher 2
Publisher 3
' } ] carveout_data = { 'carveouts_updated': True, 'carveouts_old': 'AE, CA', 'carveouts_new': 'AD, AF, AG, AI, AL, AM, AN, AO, AQ, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BM, BN, BO, BR, BS, BT, BV, BW, BY, BZ, CA, CC, CD, CF, CG, CH, CI, CK, CL, CM, CN, CO, CR, CS, CU, CV, CX, CY, CZ, DE, DJ, DK, DM, DO, DZ, EC, EE, EG, EH, ER, ES, ET, FI, FJ, FK, FM, FO, FR, GA, GB, GD, GE, GF, GG, GH, GI, GL, GM, GN, GP, GQ, GR, GS, GT, GU, GW, GY, HK, HM, HN, HR, HT, HU, ID, IE, IL, IM, IN, IO, IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, LA, LB, LC, LI, LK, LR, LS, LT, LU, LV, LY, MA, MC, MD, MG, MH, MK, ML, MM, MN, MO, MP, MQ, MR, MS, MT, MU, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, PA, PE, PF, PG, PH, PK, PL, PM, PN, PR, PS, PT, PW, PY, QA, RE, RO, RU, RW, SA, SB, SC, SD, SE, SG, SH, SI, SJ, SK, SL, SM, SN, SO, SR, ST, SV, SY, SZ, TC, TD, TF, TG, TH, TJ, TK, TL, TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WF, WS, YE, YT, ZA, ZM, ZW', # noqa 'carveouts_added': 'AD, AF, AG, AI, AL, AM, AN, AO, AQ, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BM, BN, BO, BR, BS, BT, BV, BW, BY, BZ, CC, CD, CF, CG, CH, CI, CK, CL, CM, CN, CO, CR, CS, CU, CV, CX, CY, CZ, DE, DJ, DK, DM, DO, DZ, EC, EE, EG, EH, ER, ES, ET, FI, FJ, FK, FM, FO, FR, GA, GB, GD, GE, GF, GG, GH, GI, GL, GM, GN, GP, GQ, GR, GS, GT, GU, GW, GY, HK, HM, HN, HR, HT, HU, ID, IE, IL, IM, IN, IO, IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, LA, LB, LC, LI, LK, LR, LS, LT, LU, LV, LY, MA, MC, MD, MG, MH, MK, ML, MM, MN, MO, MP, MQ, MR, MS, MT, MU, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, PA, PE, PF, PG, PH, PK, PL, PM, PN, PR, PS, PT, PW, PY, QA, RE, RO, RU, RW, SA, SB, SC, SD, SE, SG, SH, SI, SJ, SK, SL, SM, SN, SO, SR, ST, SV, SY, SZ, TC, TD, TF, TG, TH, TJ, TK, TL, TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WF, WS, YE, YT, ZA, ZM, ZW', # noqa 'carveouts_removed': 'AE' } complete_template = generate_standard_test_template( 'release_correction_failure.mak', context, s3_data, rc_errors=formatted_errors, publisher_warnings=publisher_warnings, carveouts_updated=carveout_data.get('carveouts_updated'), carveouts_old=carveout_data.get('carveouts_old'), carveouts_new=carveout_data.get('carveouts_new'), carveouts_added=carveout_data.get('carveouts_added'), carveouts_removed=carveout_data.get('carveouts_removed'), ) assert complete_template == email_payload @patch('index.retrieve_release_date', return_value='2016-01-02') @patch('index.load_ddex_json') def test_build_release_correction_update_payload( mock_rc_json, mock_check_release_date, context_in_content_update_success, graphql_response): """Test build_in_content_success_payload method with RC.""" context_in_content_update_success['warnings'] = [ { 'field': 'carveouts', 'warning_type': 'ReleaseCorrectionUpdateException', 'exception': "Product Carveouts update attempt. DDEX values ['AD', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW'] does not match The Orchard's Carveouts ['TA']" # noqa }, { 'field': 'publisherNames', 'warning_type': 'ReleaseCorrectionUpdateException', 'exception': {'isrc': 'QZCDB2000001', 'current_values': ['Publisher 1', 'Publisher 2', 'Publisher 3'], 'new_values': ['Publisher 1', 'Publisher 2']} # noqa }, { 'field': 'publisherNames', 'warning_type': 'ReleaseCorrectionUpdateException', 'exception': {'isrc': 'QZCDB2000002', 'current_values': ['Publisher 3', 'Publisher 4', 'Publisher 3'], 'new_values': ['Publisher 2', 'Publisher 3']} # noqa }, ] context = StateMachineSchema().load(context_in_content_update_success) s3_data = S3Schema().load(context_in_content_update_success) rc_json = [ [ 'productVersion', None, 'old version', 'New Version', 'releases', 'version', True, True ], [ 'imprint', None, 'old imprint', 'New Imprint Music Co.', 'releases', 'label', True, True ], [ 'performer', None, None, 'new performer field', 'releases', 'performer', True, True ], [ 'featuring', None, None, 'new featuring field', 'releases', 'featuring', True, True ], [ 'featuring', None, None, 'new track featuring field', 'track', 'featuring', True, True ], [ 'genreId', None, 12, 20, 'releases', 'genre_id', True, True ], [ 'subgenreId', None, 982, [223], 'releases', 'release_subgenre', True, True ], [ 'trackName', 'USSM12001698', 'old name', 'new name', 'track', 'track_name', True, True ], [ 'performer', 'USSM12001698', None, [{'type': 'performer', 'name': 'Giveon'}], 'track', 'track_artist', True, True ], [ 'writer', 'USSM12001698', None, ['Composer Name'], 'track', 'track_writer', True, True ], [ 'trackGrats', None, {'33421938': '2020-01-01', '33421930': '2020-01-02'}, {'33421938': '2020-03-01', '33421930': '2020-03-02', '33421933': '2020-05-02'}, 'releases', 'instant_grats', True, False ] ] email_payload = index.build_in_content_success_payload( context, s3_data, graphql_response, rc_json ) rc_audited_updates = [ { 'field_name': 'Genre', 'old_value': 'Classical', 'new_value': 'Blues' }, { 'field_name': 'Imprint', 'old_value': 'old imprint', 'new_value': 'New Imprint Music Co.' }, { 'field_name': 'Instant grat (TUID: 33421930)', 'old_value': '2020-01-02', 'new_value': '2020-03-02' }, { 'field_name': 'Instant grat (TUID: 33421933)', 'old_value': 'No Value Found', 'new_value': '2020-05-02' }, { 'field_name': 'Instant grat (TUID: 33421938)', 'old_value': '2020-01-01', 'new_value': '2020-03-01' }, { 'field_name': 'Product Version', 'old_value': 'old version', 'new_value': 'New Version' }, { 'field_name': 'Subgenre', 'old_value': 'Classic Blues', 'new_value': 'Boogie Woogie' }, { 'field_name': 'Track Name (USSM12001698)', 'old_value': 'old name', 'new_value': 'new name' }, ] product_artist_updates = ['Product Artists have been updated.'] track_artist_updates = ['Track Artists have been updated. (USSM12001698)'] asset_updates = [] asset_updates.append('Audio asset updated. (USSM12001699)') asset_updates.append('Audio asset updated. (USSM12001611)') publisher_warnings = [ { 'isrc': 'Publishers (QZCDB2000001)', 'current_values': 'Publisher 1
Publisher 2
Publisher 3
', # noqa 'new_values': 'Publisher 1
Publisher 2
' }, { 'isrc': 'Publishers (QZCDB2000002)', 'current_values': 'Publisher 3
Publisher 4
Publisher 3
', # noqa 'new_values': 'Publisher 2
Publisher 3
' } ] complete_template = generate_standard_test_template( 'update_success.mak', context, s3_data, rc_audited_updates=rc_audited_updates, product_artist_updates=product_artist_updates, track_artist_updates=track_artist_updates, old_start_date='2020-12-21', new_start_date='2010-12-21', orig_release_date='2016-01-01', release_date='2016-01-02', carveouts_updated=True, carveouts_old='AE, UE', carveouts_new='DE, GB, SE, US', carveouts_added='DE, GB, SE, US', carveouts_removed='AE, UE', rc_warnings=['Carveouts'], new_artwork=True, audio_asset_updates=asset_updates, publisher_warnings=publisher_warnings,) assert complete_template == email_payload @patch('index.load_ddex_json') def test_build_validation_rule_rejection_payload( mock_load_ddex_json, context_validation_rule_rejection, graphql_response): """Test build_release_correction_fail_payload method.""" context = StateMachineSchema().load(context_validation_rule_rejection) s3_data = S3Schema().load(context_validation_rule_rejection) mock_load_ddex_json.return_value = s3_data email_payload = index.build_validation_rule_rejection_payload( context, s3_data, graphql_response ) errors = [ 'Invalid genre', 'Orchard Label ID is missing', 'Project artist is missing', ] complete_template = generate_standard_test_template( 'validation_rule_rejection.mak', context, s3_data, errors=errors) assert complete_template == email_payload @patch('index.load_ddex_json') def test_build_set_track_metadata_fail_payload( mock_load_ddex_json, context_validation_rule_rejection, graphql_response): """Test build_set_track_metadata_fail_payload method.""" context = StateMachineSchema().load(context_validation_rule_rejection) s3_data = S3Schema().load(context_validation_rule_rejection) mock_load_ddex_json.return_value = s3_data error = { 'Error': 'SetTrackMetadataException', 'Cause': "{\"errorMessage\": \"{'url': 'https://qa-ows-track.theorchard.io/track/33466504?get_artist_info_ids_from_existing_rows=1', 'status': 400, 'statusText': 'BAD REQUEST', 'body': {'code': 'validation_error', 'message': {'explicit': ['Swear word found']}}, 'ISRC': 'FIMRR1800137'}\", \"errorType\": \"SetTrackMetadataException\", \"stackTrace\": [\" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 161, in sentry_handler\\n reraise(*exc_info)\\n\", \" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/_compat.py\\\", line 54, in reraise\\n raise value\\n\", \" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 152, in sentry_handler\\n return handler(aws_event, aws_context, *args, **kwargs)\\n\", \" File \\\"/var/task/index.py\\\", line 151, in handler\\n raise SetTrackMetadataException(graphql_response) from err\\n\"]}" # noqa } email_payload = index.build_set_track_metadata_fail_payload( context, s3_data, graphql_response, error ) errors = [ 'Swear word found on the lyrics of non explicit track with ISRC: FIMRR1800137.', # noqa ] complete_template = generate_standard_test_template( 'generic_failure.mak', context, s3_data, errors=errors) assert complete_template == email_payload @patch('index.load_ddex_json') def test_build_poll_video_fatal_exception_payload( mock_load_ddex_json, context_validation_rule_rejection, graphql_response): """Test build_poll_video_fatal_exception_payload method.""" context = StateMachineSchema().load(context_validation_rule_rejection) s3_data = S3Schema().load(context_validation_rule_rejection) mock_load_ddex_json.return_value = s3_data error = { 'Error': 'PollVideoFatalException', 'Cause': '{\"errorMessage\": \"validate_analysis failed. Errors: Cannot convert video to a standard resolution.\", \"errorType\": \"RuntimeError\", \"stackTrace\": [\" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 161, in sentry_handler\\n reraise(*exc_info)\\n\", \" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/_compat.py\\\", line 54, in reraise\\n raise value\\n\", \" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 152, in sentry_handler\\n return handler(aws_event, aws_context, *args, **kwargs)\\n\", \" File \\\"/var/task/index.py\\\", line 47, in handler\\n workflow_result = get_workflow_status(context.video.workflow_id)\\n\", \" File \\\"/var/task/index.py\\\", line 77, in get_workflow_status\\n raise RuntimeError(\\n\"]}' # noqa } email_payload = index.build_poll_video_fatal_exception_payload( context, s3_data, graphql_response, error ) errors = [ 'Cannot convert video to a standard resolution.' ] complete_template = generate_standard_test_template( 'generic_failure.mak', context, s3_data, errors=errors) assert complete_template == email_payload @patch('index.load_ddex_json') def test_build_poll_video_fatal_exception_payload_multiple_errors( mock_load_ddex_json, context_validation_rule_rejection, graphql_response): """Test build_poll_video_fatal_exception_payload method.""" context = StateMachineSchema().load(context_validation_rule_rejection) s3_data = S3Schema().load(context_validation_rule_rejection) mock_load_ddex_json.return_value = s3_data error = { 'Error': 'PollVideoFatalException', 'Cause': '{\"errorMessage\": \"validate_analysis failed. Errors: Cannot convert video to a standard resolution. | Another error\", \"errorType\": \"RuntimeError\", \"stackTrace\": [\" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 161, in sentry_handler\\n reraise(*exc_info)\\n\", \" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/_compat.py\\\", line 54, in reraise\\n raise value\\n\", \" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 152, in sentry_handler\\n return handler(aws_event, aws_context, *args, **kwargs)\\n\", \" File \\\"/var/task/index.py\\\", line 47, in handler\\n workflow_result = get_workflow_status(context.video.workflow_id)\\n\", \" File \\\"/var/task/index.py\\\", line 77, in get_workflow_status\\n raise RuntimeError(\\n\"]}' # noqa } email_payload = index.build_poll_video_fatal_exception_payload( context, s3_data, graphql_response, error ) errors = [ 'Cannot convert video to a standard resolution.', 'Another error' ] complete_template = generate_standard_test_template( 'generic_failure.mak', context, s3_data, errors=errors) assert complete_template == email_payload @patch('index.load_ddex_json') def test_build_poll_video_fatal_exception_payload_malformed_error( mock_load_ddex_json, context_validation_rule_rejection, graphql_response): """Test build_poll_video_fatal_exception_payload method.""" context = StateMachineSchema().load(context_validation_rule_rejection) s3_data = S3Schema().load(context_validation_rule_rejection) mock_load_ddex_json.return_value = s3_data error = { 'Error': 'PollVideoFatalException', 'Cause': '{\"errorMessage\": \"This: is: madness.::| \", \"errorType\": \"RuntimeError\", \"stackTrace\": [\" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 161, in sentry_handler\\n reraise(*exc_info)\\n\", \" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/_compat.py\\\", line 54, in reraise\\n raise value\\n\", \" File \\\"/var/lang/lib/python3.8/site-packages/sentry_sdk/integrations/aws_lambda.py\\\", line 152, in sentry_handler\\n return handler(aws_event, aws_context, *args, **kwargs)\\n\", \" File \\\"/var/task/index.py\\\", line 47, in handler\\n workflow_result = get_workflow_status(context.video.workflow_id)\\n\", \" File \\\"/var/task/index.py\\\", line 77, in get_workflow_status\\n raise RuntimeError(\\n\"]}' # noqa } email_payload = index.build_poll_video_fatal_exception_payload( context, s3_data, graphql_response, error ) assert not email_payload @pytest.mark.parametrize('value,expected', [ (['length one list'], 'length one list'), ([2], 2), (None, None)]) def test_format_detail_values_for_email(value, expected): """Test format_detail_values_for_email method.""" result = index.format_detail_values_for_email(value) assert result == expected @pytest.mark.parametrize(('context_fixture, graphql_fixture,' 'expected_recipients'), [ ('context', 'graphql_response', [ 'qa-ddex-ingester@theorchard.com']), ('context_with_deal_coordinators', 'graphql_response', [ 'qa-ddex-ingester@theorchard.com', 'awesome-coordinator@theorchard.com' ]) ]) @patch('index.check_for_email_cc_list', return_value=['qa-ddex-ingester@theorchard.com']) @patch('helpers.email.get_email_addresses_for_major_label', return_value=None) def test_build_recipient_list(mock_get_email_addresses_for_major_label, mock_email_cc_list, context_fixture, graphql_fixture, expected_recipients, request): """Test build_recipient_list.""" context = request.getfixturevalue(context_fixture) graphql = request.getfixturevalue(graphql_fixture) output = build_recipient_list( StateMachineSchema().load(context), graphql ) assert output[0] == expected_recipients @patch('index.load_rc_json') @patch('index.is_qa_env') @patch('index.check_for_email_cc_list', return_value='["qa-ddex-ingester@theorchard.com"]') @patch('index.send_email') @patch('index.retrieve_product_data') @patch('index.load_ddex_json') @patch('index.build_in_content_success_payload', wraps=index.build_in_content_success_payload) @patch('index.exit_without_email') @patch('index.compare_release_dates', return_value=None) @patch('index.format_carveout_changes', return_value={}) def test_in_content_update_no_changes_triggers_no_email( mock_format_carveout_changes, mock_compare_release_dates, mock_exit_without_email, mock_in_content_success, mock_load_ddex_json, mock_product_data, mock_send_email, mock_check_cc_list, mock_is_qa_env, mock_rc_json, context_in_content_update_success, graphql_response,): """Test build_in_content_success_payload can trigger no email.""" context_in_content_update_success['tracks'] = [] context_in_content_update_success['product']['artwork'] = None context = index.StateMachineSchema().load( context_in_content_update_success) expected_return = index.StateMachineSchema().dump(context) mock_product_data.return_value = graphql_response s3_data = context_in_content_update_success mock_load_ddex_json.return_value = s3_data mock_exit_without_email.return_value = expected_return result = index.handler(context_in_content_update_success, None) mock_in_content_success.assert_called_once() mock_exit_without_email.assert_called_once() assert result == expected_return def test_create_email_template_for_known_error_double_nested_list( context_in_content_update_success, graphql_response): """Test create_email_template_for_known_error with a double nested list.""" context = index.StateMachineSchema().load( context_in_content_update_success) s3_data = context_in_content_update_success errors = [ [ { 'code': 'MISSING_COMPOSER', 'reason': 'Field `composer` is required' } ] ] result = index.create_email_template_for_known_error( context, errors, s3_data, graphql_response ) assert result @patch('index.check_for_email_cc_list', return_value=['qa-ddex-ingester@theorchard.com']) @patch('index.boto3.client') @patch('index.send_email') def test_no_context_failure( mock_send_email, mock_boto3_client, mock_check_cc_list, ddex_xml, fail_before_parsing_ddex_event): """Test the handling of errors without enough data in the event context.""" mock_s3_client = MagicMock(name='get_object') mock_fileobject = MagicMock(name='read') mock_fileobject.read.return_value = ddex_xml mock_s3_client.get_object.return_value = { 'Body': mock_fileobject } mock_boto3_client.return_value = mock_s3_client expected_email_body = '

Dear users,

We have failed to read the DDEX file for the product with GRID: A10301A00035480592 and UPC: 195497918690.

UPC Grid Sony Product ID Title Artist Orchard Label Sony Maint. Owner Format Status Not For Distribution
195497918690 A10301A00035480592 G0100035480592 Unknown Unknown Unknown Unknown Unknown Unknown Unknown

The failure was caused by the error:
"Invalid language code MUL"

Please update the Product\'s Metadata Language in the source system and try the ingestion again.

' # noqa: E501 expected_email_subject =\ 'Failure to ingest SME Interop Product 195497918690' index.handler(fail_before_parsing_ddex_event, {}) mock_send_email.assert_called_with( ['qa-ddex-ingester@theorchard.com'], expected_email_body, expected_email_subject, [] ) @patch('index.check_for_email_cc_list', return_value=['qa-ddex-ingester@theorchard.com']) @patch('index.boto3.client') @patch('index.send_email') def test_no_context_failure_missing_error_message( mock_send_email, mock_boto3_client, mock_check_cc_list, ddex_xml, fail_before_parsing_ddex_event): """Test handling errors without context and missing the error message.""" mock_s3_client = MagicMock(name='get_object') mock_fileobject = MagicMock(name='read') mock_fileobject.read.return_value = ddex_xml mock_s3_client.get_object.return_value = {'Body': mock_fileobject} mock_boto3_client.return_value = mock_s3_client expected_email_body = '

Dear users,

We have failed to read the DDEX file for the product with GRID: A10301A00035480592 and UPC: 195497918690.

UPC Grid Sony Product ID Title Artist Orchard Label Sony Maint. Owner Format Status Not For Distribution
195497918690 A10301A00035480592 G0100035480592 Unknown Unknown Unknown Unknown Unknown Unknown Unknown

Due to this we have no more information about the product or what caused the failure.

A support engineer will look into this and report back, there\'s no action needed on your part.

' # noqa: E501 expected_email_subject =\ 'Failure to ingest SME Interop Product 195497918690' fail_before_parsing_ddex_event['errors']['Cause'] = None index.handler(fail_before_parsing_ddex_event, {}) mock_send_email.assert_called_with( ['qa-ddex-ingester@theorchard.com'], expected_email_body, expected_email_subject, [] ) @patch('index.exit_without_email') def test_purged_release_success( mock_exit_without_email, context_ingestion_success): """Test handling a successful ingest of a purged release DDEX.""" context = context_ingestion_success.copy() context['is_purged_release'] = True index.handler(context, {}) mock_exit_without_email.assert_called() @patch('index.handle_no_context_failure') def test_purged_release_failure( mock_handle_no_context_failure, context_ingestion_success): """Test handling a failed ingest of a purged release DDEX.""" context = context_ingestion_success.copy() context['is_purged_release'] = True context['errors'] = {'error': 'message'} index.handler(context, {}) mock_handle_no_context_failure.assert_called() @patch('index.config') def test_check_for_email_cc_list_without_ddex_provider(mock_config, context): """Test check_for_email_cc_list without ddex_provider.""" mock_config.REPORT_ERRORS_CC = '["qa-ddex-ingester@theorchard.com"]' mock_config.REPORT_ERRORS_SME_ANALYTICS_DUMMY_EMAIL = ( '["qa-ddex-ingest-analytics-dummy-events@sonymusic-pde.com"]') result = index.check_for_email_cc_list('') assert result == ['qa-ddex-ingester@theorchard.com'] @patch('index.config') def test_check_for_email_cc_list_with_ddex_provider(mock_config, context): """Test check_for_email_cc_list with ddex_provider.""" mock_config.REPORT_ERRORS_CC = '["qa-ddex-ingester@theorchard.com"]' mock_config.REPORT_ERRORS_SME_ANALYTICS_DUMMY_EMAIL = ( '["qa-ddex-ingest-analytics-dummy-events@sonymusic-pde.com"]') result = index.check_for_email_cc_list('SME') assert result == ['qa-ddex-ingester@theorchard.com'] @patch('index.config') def test_check_for_email_cc_list_with_ddex_provider_sme_analytics( mock_config, context): """Test check_for_email_cc_list with ddex_provider SME_ANALYTICS.""" mock_config.REPORT_ERRORS_CC = '["qa-ddex-ingester@theorchard.com"]' mock_config.REPORT_ERRORS_SME_ANALYTICS_DUMMY_EMAIL = ( '["qa-ddex-ingest-analytics-dummy-events@sonymusic-pde.com"]') result = index.check_for_email_cc_list('SME_ANALYTICS_PROVIDER') assert result == [ 'qa-ddex-ingest-analytics-dummy-events@sonymusic-pde.com']