"""Test parse_ddex handler.""" import json from unittest.mock import MagicMock, patch import pytest from ddex_ingester_common.constants.ddex_providers import ( RISING_88, SME_ANALYTICS_PROVIDER) from ddex_ingester_common.lambda_exceptions import ParseDDEXException from ddex_ingester_common.schemas.ddex_schema import DDEXSchema from ddex_ingester_common.schemas.s3_schema import S3Schema from ddex_ingester_common.utils.read_ddex import combine_ddex_documents from constants.rising_88 import RISING_88_VENDOR_ID from index import format_asset_s3_location, handler @patch('index.delete_all_rc_json_files') @patch('index.create_blank_rc_json') @patch( 'ddex_ingester_common.utils.read_ddex.extract_extended_metadata_location', return_value=None) @patch('index.S3Schema') @patch('index.boto3.client') @patch('ddex_ingester_common.utils.read_ddex.DDEXSchema') @patch('index.StateMachineSchema') @patch('ddex_ingester_common.utils.read_ddex.xmltodict') @patch('ddex_ingester_common.utils.read_ddex.get_is_purged_release', return_value=False) def test_handler( mock_get_is_purged_release, mock_xmltodict, mock_state_machine_schema, mock_ddexschema, mock_boto3, mock_s3schema, mock_extract_metadata, mock_create_rc_json, mock_delete_rc_files, context_object): """Test the main handler.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml' } mock_boto3.return_value = mock_s3 = MagicMock( name=['get_object', 'put_object']) mock_file_object = MagicMock() mock_s3.get_object.return_value = { 'Body': mock_file_object } mock_xmltodict_output = MagicMock() mock_xmltodict.parse = MagicMock(return_value=mock_xmltodict_output) mock_ddexschema_load_output = MagicMock() mock_ddexschema_instance = MagicMock( return_value=mock_ddexschema_load_output) mock_ddexschema.return_value.load = mock_ddexschema_instance mock_state_machine_schema_instance = MagicMock(return_value=context_object) mock_state_machine_schema.return_value.dump = ( mock_state_machine_schema_instance ) mock_ddexschema.return_value.dump = mock_state_machine_schema_instance mock_s3schema.return_value.dump = mock_state_machine_schema_instance response = handler(mock_event, None) mock_s3.get_object.assert_called_with( Bucket=mock_event.get('bucket'), Key='test/ddex.xml' ) mock_s3.put_object.assert_called_with( Bucket=mock_event.get('bucket'), Key='test/parsed_ddex.json', Body=json.dumps(context_object).encode(encoding='UTF-8') ) mock_ddexschema_instance.assert_called_with( mock_xmltodict_output ) mock_state_machine_schema_instance.assert_called_with( mock_ddexschema_load_output ) mock_create_rc_json.assert_called_with(mock_event) assert response == context_object @patch('index.delete_all_rc_json_files') @patch('index.create_blank_rc_json') @patch( 'ddex_ingester_common.utils.read_ddex.extract_extended_metadata_location', return_value=None) @patch('index.S3Schema') @patch('index.boto3.client') @patch('ddex_ingester_common.utils.read_ddex.DDEXSchema') @patch('index.StateMachineSchema') @patch('ddex_ingester_common.utils.read_ddex.xmltodict') @patch('ddex_ingester_common.utils.read_ddex.get_is_purged_release', return_value=False) @patch('index.config') @patch('index.logging_utils') def test_handler_runs_catalog_ingest( mock_logging_utils, mock_config, mock_get_is_purged_release, mock_xmltodict, mock_state_machine_schema, mock_ddexschema, mock_boto3, mock_s3schema, mock_extract_metadata, mock_create_rc_json, mock_delete_rc_files, context_object): """Test the main handler.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml' } mock_boto3.return_value = mock_s3 = MagicMock( name=['get_object', 'put_object']) mock_file_object = MagicMock() mock_s3.get_object.return_value = { 'Body': mock_file_object } mock_xmltodict_output = MagicMock() mock_xmltodict.parse = MagicMock(return_value=mock_xmltodict_output) mock_ddexschema_load_output = MagicMock() mock_ddexschema_instance = MagicMock( return_value=mock_ddexschema_load_output) mock_ddexschema.return_value.load = mock_ddexschema_instance mock_state_machine_schema_instance = MagicMock(return_value=context_object) mock_state_machine_schema.return_value.dump = ( mock_state_machine_schema_instance ) mock_ddexschema.return_value.dump = mock_state_machine_schema_instance mock_s3schema.return_value.dump = mock_state_machine_schema_instance response = handler(mock_event, None) assert response == context_object @patch('index.delete_all_rc_json_files') @patch('index.create_blank_rc_json') @patch( 'ddex_ingester_common.utils.read_ddex.extract_extended_metadata_location', return_value=None) @patch('index.S3Schema') @patch('index.boto3.client') @patch('ddex_ingester_common.utils.read_ddex.PurgedDDEXSchema') @patch('index.StateMachineSchema') @patch('ddex_ingester_common.utils.read_ddex.xmltodict') @patch('ddex_ingester_common.utils.read_ddex.get_is_purged_release', return_value=True) def test_handler_purged_release( mock_get_is_purged_release, mock_xmltodict, mock_state_machine_schema, mock_purged_ddexschema, mock_boto3, mock_s3schema, mock_extract_metadata, mock_create_rc_json, mock_delete_rc_files, context_object): """Test the main handler.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml' } mock_boto3.return_value = mock_s3 = MagicMock( name=['get_object', 'put_object']) mock_file_object = MagicMock() mock_s3.get_object.return_value = { 'Body': mock_file_object } mock_xmltodict_output = MagicMock() mock_xmltodict.parse = MagicMock(return_value=mock_xmltodict_output) mock_purged_ddexschema_load_output = MagicMock() mock_purged_ddexschema_instance = MagicMock( return_value=mock_purged_ddexschema_load_output) mock_purged_ddexschema.return_value.load = mock_purged_ddexschema_instance mock_state_machine_schema_instance = MagicMock(return_value=context_object) mock_state_machine_schema.return_value.dump = ( mock_state_machine_schema_instance ) mock_purged_ddexschema.return_value.dump = \ mock_state_machine_schema_instance mock_s3schema.return_value.dump = mock_state_machine_schema_instance response = handler(mock_event, None) mock_s3.get_object.assert_called_with( Bucket=mock_event.get('bucket'), Key='test/ddex.xml' ) mock_purged_ddexschema_instance.assert_called_with( mock_xmltodict_output ) mock_state_machine_schema_instance.assert_called_with( mock_purged_ddexschema_load_output ) mock_extract_metadata.assert_not_called() assert response == context_object def test_handler_errors_on_other_file_extensions(): """test_handler_errors_on_other_file_extensions. Test the main handler throws exception when dealing with files other than xml. """ mock_event = { 'bucket': 'bucket-name', 'key': 'test/what.flac', } with pytest.raises(ParseDDEXException): handler(mock_event, None) def test_format_asset_s3_location( deserialized_context): """Test formatting asset s3 details.""" format_asset_s3_location(deserialized_context) artwork = deserialized_context.product.artwork track = deserialized_context.tracks[0] assert artwork.bucket == deserialized_context.bucket assert artwork.key == 'sme_ddex/upc/resources/artwork.tif' assert track.asset.bucket == deserialized_context.bucket assert track.asset.key == 'sme_ddex/upc/resources/audio.flac' assert len(deserialized_context.video.assets) == 1 assert deserialized_context.video.assets[0].aspect_ratio == '1.778' def test_format_asset_s3_location_no_asset( deserialized_context): """Test formatting asset s3 details when no asset present.""" deserialized_context.product.artwork = None deserialized_context.tracks[0].asset = None format_asset_s3_location(deserialized_context) assert not deserialized_context.product.artwork assert not deserialized_context.tracks[0].asset @patch('index.delete_all_rc_json_files') @patch('index.create_blank_rc_json') @patch('ddex_ingester_common.utils.read_ddex.extract_extended_doc', return_value='A1234_00.xml') @patch('index.boto3.client') @patch('index.StateMachineSchema') @patch('ddex_ingester_common.utils.read_ddex.xmltodict') @patch('ddex_ingester_common.utils.read_ddex.get_is_purged_release', return_value=False) def test_merge_extended_metadata( mock_get_is_purged_release, mock_xmltodict, mock_state_machine_schema, mock_boto3, mock_extract_doc_data, mock_create_rc_json, mock_delete_rc_files, context_object, ddex_document, extended_metadata): """Test that the extended metadata file is merged into the DDEX.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml' } mock_xmltodict.parse.return_value = ddex_document mock_boto3.return_value = mock_s3 = MagicMock( name=['get_object', 'put_object']) mock_file_object = MagicMock() mock_s3.get_object.return_value = { 'Body': mock_file_object } mock_extract_doc_data.return_value = extended_metadata mock_state_machine_schema_instance = MagicMock(return_value=context_object) mock_state_machine_schema.return_value.dump = ( mock_state_machine_schema_instance ) response = handler(mock_event, None) assert response == context_object def test_format_asset_s3_location_no_preferred(deserialized_context): """Test format_asset_s3_location no preferred aspect ratio found.""" deserialized_context.video.assets[1].aspect_ratio = '2.333' format_asset_s3_location(deserialized_context) assert len(deserialized_context.video.assets) == 1 assert deserialized_context.video.assets[0].aspect_ratio == '1.333' @patch('index.delete_all_rc_json_files') @patch('index.boto3.client') @patch('ddex_ingester_common.utils.read_ddex.extract_extended_doc') @patch('index.create_blank_rc_json') def test_awal_vendor_id( mock_create_blank_rc_json, mock_extended_doc, mock_boto3, mock_delete_rc_files, unparsed_awal_ddex_2): """Test that an AWAL DDEX populates the vendor_id.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml' } vendor_id = 123456 mock_boto3.return_value = mock_s3 = MagicMock( name=['get_object', 'put_object']) mock_read = MagicMock() mock_read.read.return_value = bytes(unparsed_awal_ddex_2, 'utf-8') mock_s3.get_object.return_value = { 'Body': mock_read } mock_extended_doc.return_value = { 'OrchardMessage': { 'OrchardLabelId': vendor_id, 'OrchardSubaccountId': 66049, 'Project': {}, 'Product': { 'Tracks': [] } } } response = handler(mock_event, None) put_object_call = mock_s3.put_object.call_args[1]['Body'].decode('UTF-8') deserialized_call = S3Schema().load(json.loads(put_object_call)) assert deserialized_call.product.vendor_id == vendor_id assert response['product']['vendor_id'] == vendor_id def test_s3_data_dump( ddex_document, extended_metadata ): """Test that s3 schema dumped data.""" combined_doc = combine_ddex_documents( ddex_document, extended_metadata ) data = DDEXSchema().load(combined_doc) s3data = S3Schema().dump(data) assert s3data['product']['vendor_id'] == 15063 @patch('index.delete_all_rc_json_files') @patch('index.boto3.client') @patch('ddex_ingester_common.utils.read_ddex.extract_extended_doc') @patch('index.create_blank_rc_json') def test_88_rising( mock_create_blank_rc_json, mock_extended_doc, mock_boto3, mock_delete_rc_files, unparsed_88_rising_ddex): """Test with the least amount of mocks to help debugging.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/prod-orcd-88rising-drop/ddex.xml', 'ddex_provider': '88RISING', } mock_boto3.return_value = mock_s3 = MagicMock( name=['get_object', 'put_object']) mock_read = MagicMock() mock_read.read.return_value = bytes(unparsed_88_rising_ddex, 'utf-8') mock_s3.get_object.return_value = { 'Body': mock_read } response = handler(mock_event, None) put_object_call = mock_s3.put_object.call_args[1]['Body'].decode('UTF-8') deserialized_call = S3Schema().load(json.loads(put_object_call)) assert deserialized_call.project.project_code assert deserialized_call.project.name assert deserialized_call.project.artist.name assert deserialized_call.product.catalog_number assert deserialized_call.video.product_code assert response['product']['vendor_id'] == RISING_88_VENDOR_ID assert response['product']['subaccount_id'] == 81351 assert response['ddex_provider'] == RISING_88 @patch('index.delete_all_rc_json_files') @patch('index.boto3.client') @patch('ddex_ingester_common.utils.read_ddex.extract_extended_doc') @patch('index.create_blank_rc_json') def test_integration( mock_create_blank_rc_json, mock_extended_doc, mock_boto3, mock_delete_rc_files, unparsed_awal_ddex, extended_awal_ddex): """Test with the least amount of mocks to help debugging.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml' } mock_boto3.return_value = mock_s3 = MagicMock( name=['get_object', 'put_object']) mock_read = MagicMock() mock_read.read.return_value = bytes(unparsed_awal_ddex, 'utf-8') mock_s3.get_object.return_value = { 'Body': mock_read } mock_extended_doc.return_value = extended_awal_ddex response = handler(mock_event, None) put_object_call = mock_s3.put_object.call_args[1]['Body'].decode('UTF-8') deserialized_call = S3Schema().load(json.loads(put_object_call)) assert deserialized_call.product.catalog_number assert response['product']['vendor_id'] @patch('index.delete_all_rc_json_files') @patch('index.boto3.client') @patch('ddex_ingester_common.utils.read_ddex.extract_extended_doc') @patch('index.create_blank_rc_json') def test_awal_takedown_full( mock_create_blank_rc_json, mock_extended_doc, mock_boto3, mock_delete_rc_files, unparsed_awal_ddex_audio_takedown): """Test full handler pass for a Takedown product..""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml' } vendor_id = 123456 mock_boto3.return_value = mock_s3 = MagicMock( name=['get_object', 'put_object']) mock_read = MagicMock() mock_read.read.return_value = ( bytes(unparsed_awal_ddex_audio_takedown, 'utf-8')) mock_s3.get_object.return_value = { 'Body': mock_read } mock_extended_doc.return_value = { 'OrchardMessage': { 'OrchardLabelId': vendor_id, 'OrchardSubaccountId': 66049, 'Project': None, 'Product': { 'Tracks': [] } } } response = handler(mock_event, None) assert response['is_purged_release'] @patch('index.create_blank_rc_json') @patch('index.delete_all_rc_json_files') @patch('index.boto3.client') @patch('index.read_ddex') def test_artwork_ingestion_only_flag( mock_read_ddex, mock_boto3, mock_delete_all_rc_json_files, mock_create_blank_rc_json, ddex_document): """Test artwork_ingestion_only_flag added to context.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml', 'artwork_ingestion_only': True } mock_boto3.return_value = MagicMock( name=['get_object', 'put_object']) ddex_result = DDEXSchema().load(ddex_document) ddex_result.is_purged_release = False mock_read_ddex.return_value = ddex_result response = handler(mock_event, None) assert response['artwork_ingestion_only'] @patch('index.create_blank_rc_json') @patch('index.delete_all_rc_json_files') @patch('index.boto3.client') @patch('index.read_ddex') def test_list_of_tracks_not_added_to_context( mock_read_ddex, mock_boto3, mock_delete_all_rc_json_files, mock_create_blank_rc_json, ddex_document): """Test no tracks added for confidential product.""" mock_event = { 'bucket': 'bucket-name', 'key': 'test/ddex.xml', 'ddex_provider': SME_ANALYTICS_PROVIDER } mock_boto3.return_value = MagicMock( name=['get_object', 'put_object']) ddex_result = DDEXSchema().load(ddex_document) ddex_result.is_purged_release = False ddex_result.product.is_confidential = True mock_read_ddex.return_value = ddex_result assert ddex_result.tracks != [] response = handler(mock_event, None) assert response['tracks'] == []