"""Lambda test module.""" import datetime import importlib from unittest import mock from unittest.mock import call from unittest.mock import patch import pytest from freezegun import freeze_time from freezegun.api import FakeDatetime # type: ignore[attr-defined] from soundrecording_utils.constants.ddex.constants import RuleService from soundrecording_utils.metadata.types import Asset from soundrecording_utils.metadata.types import OrchardSoundRecording from soundrecording_utils.metadata.types import TrackConnection from src import app as index @patch('src.app.s3_delivery_audit.write_sr_delivery_audit_xml') @patch('src.app.sftp_asset_delivery.write_file') @patch('src.app.s3_sound_recordings.get_latest_sound_recording_version') @patch('src.app.generate_ddex') @patch('src.app._select_upload_asset') @patch('src.app.s3_assets.download_asset') @patch('src.app._generate_batch_id') @patch('src.app._s3_remote_foldernames') @freeze_time('2019-06-15 12:00:00') def test_handler_with_asset( mock_s3_foldernames, mock_batch_id, mock_download, mock_select_asset, mock_generate_ddex, mock_s3_get, mock_write_to_sftp, mock_write_to_s3_audit, mock_asset): """Test handler function with upload of asset.""" mock_s3_foldernames.return_value = ( 'batch123', 'batch123/isrc-123', 'batch123/isrc-123/resources' ) mock_batch_id.return_value = 'batch123' mock_download.return_value = b'music' mock_s3_get.return_value = ( 'dummy OSR ID', """ { "track_connection": { "tracks": [] }, "assets": [], "isrc": "isrc-123" } """, False ) mock_generate_ddex.return_value = 'XML Result' mock_select_asset.return_value = mock_asset input_event = { 'sound_recording': { 'id': 'sr-123' }, 'upload_asset': True, 'execution_name': '6ff851fa-d83b-4e6e-9888-66077de9529a' } result = index.handler(input_event, None) assert result == { 'details': { 'batch_id': 'batch123', 'filenames': [ 'batch123/isrc-123/resources/90e575bd-94a5-4a3e-9f1f-dade36feb8c3.flac', # noqa:E501 'batch123/isrc-123/isrc-123.xml', 'batch123/delivery.complete' ] } } assert mock_write_to_s3_audit.call_args_list == [ call( local=b'XML Result', remote='batch123/isrc-123/isrc-123.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a', service='youtube' ) ] assert mock_write_to_sftp.call_args_list == [ call( file_bytes=b'XML Result', subdir='batch123/isrc-123/isrc-123.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), call( file_bytes=b'music', subdir='batch123/isrc-123/resources/90e575bd-94a5-4a3e-9f1f-dade36feb8c3.flac', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), call( file_bytes=b'', subdir='batch123/delivery.complete', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), ] assert mock_s3_get.call_args_list == [ call('sr-123') ] assert mock_select_asset.call_args_list == [ call( OrchardSoundRecording( track_connection=TrackConnection(tracks=[]), assets=[], isrc='isrc-123' ) ) ] assert mock_generate_ddex.call_args_list == [ call( OrchardSoundRecording( track_connection=TrackConnection(tracks=[]), assets=[], isrc='isrc-123' ), mock_asset, { 'timestamp': FakeDatetime(2019, 6, 15, 12, 0, tzinfo=datetime.timezone.utc), # noqa:E501 'message_thread_id': 1, 'message_id': 'batch123' }, 'lambda-sr-delivery-youtube', RuleService.YOUTUBE, selected_version='3.8', takedown=False ) ] assert mock_s3_foldernames.call_args_list == [ call('batch123', 'isrc-123')] @patch('src.app.s3_delivery_audit.write_sr_delivery_audit_xml') @patch('src.app.sftp_asset_delivery.write_file') @patch('src.app.s3_sound_recordings.get_latest_sound_recording_version') @patch('src.app.generate_ddex') @patch('src.app._select_upload_asset') @patch('src.app.s3_assets.download_asset') @patch('src.app._generate_batch_id') @patch('src.app._s3_remote_foldernames') @freeze_time('2019-06-15 12:00:00') def test_handler_with_many_isrcs_no_asset( mock_s3_foldernames, mock_batch_id, mock_download, mock_select_asset, mock_generate_ddex, mock_s3_get, mock_write_to_sftp, mock_write_to_s3_audit, mock_asset): """Test handler function with no asset many ISRCs.""" mock_s3_foldernames.return_value = ( 'batch123', 'batch123/isrc-123', 'batch123/isrc-123/resources' ) mock_batch_id.return_value = 'batch123' mock_download.return_value = b'music' mock_s3_get.return_value = ( 'dummy OSR ID', """ { "track_connection": { "tracks": [ { "isrc": "isrc-456", "tuid": "40387305", "product": { "id": "4634679", "display_upc": "0726449910280", "status": "in_content", "release_date": "2023-08-31", "ingestion_completed": "2023-08-21", "sale_start_date": "2023-08-31", "label": { "name": "Tratore2018", "type": "Subaccount", "uuid": "6ac1ea3d-ae70-41ba-8bee-8e0bac9a0526", "id": { "vendor": 21069, "subaccount": 34103 }, "rules": [], "vendor": { "rules": [] } }, "subgenre": { "id": 997, "name": "Electronic", "genre": { "name": "Electronic", "id": 2 } } }, "name": "Track 2", "territories": [], "participations": [], "explicit": false, "primary": false }, { "isrc": "isrc-123", "tuid": "40387399", "product": { "id": "4634679", "display_upc": "0726449910280", "status": "in_content", "release_date": "2023-08-31", "ingestion_completed": "2023-08-21", "sale_start_date": "2023-08-31", "label": { "name": "Tratore2018", "type": "Subaccount", "uuid": "6ac1ea3d-ae70-41ba-8bee-8e0bac9a0526", "id": { "vendor": 21069, "subaccount": 34103 }, "rules": [], "vendor": { "rules": [] } }, "subgenre": { "id": 997, "name": "Electronic", "genre": { "name": "Electronic", "id": 2 } } }, "name": "Track 1", "territories": [], "participations": [], "explicit": false, "primary": true } ] }, "assets": [], "isrc": "isrc-123" } """, False ) mock_generate_ddex.return_value = 'XML Result' mock_select_asset.return_value = mock_asset input_event = { 'sound_recording': { 'id': 'sr-123' }, 'upload_asset': False, 'execution_name': '6ff851fa-d83b-4e6e-9888-66077de9529a' } result = index.handler(input_event, None) assert result == { 'details': { 'batch_id': 'batch123', 'filenames': [ 'batch123/isrc-456/isrc-456.xml', 'batch123/isrc-123/isrc-123.xml', 'batch123/delivery.complete' ] } } assert mock_write_to_s3_audit.call_args_list == [ call( local=b'XML Result', remote='batch123/isrc-123/isrc-123.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a', service='youtube' ) ] assert mock_write_to_sftp.call_args_list == [ call( file_bytes=b'XML Result', subdir='batch123/isrc-456/isrc-456.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), call( file_bytes=b'XML Result', subdir='batch123/isrc-123/isrc-123.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), call( file_bytes=b'', subdir='batch123/delivery.complete', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), ] assert mock_s3_get.call_args_list == [ call('sr-123') ] assert not mock_select_asset.called assert mock_generate_ddex.called assert mock_s3_foldernames.call_args_list == [ call('batch123', 'isrc-123')] @patch('src.app.s3_delivery_audit.write_sr_delivery_audit_xml') @patch('src.app.sftp_asset_delivery.write_file') @patch('src.app.s3_sound_recordings.get_sound_recording_version') @patch('src.app.generate_ddex') @patch('src.app._select_upload_asset') @patch('src.app.s3_assets.download_asset') @patch('src.app._generate_batch_id') @patch('src.app._s3_remote_foldernames') @freeze_time('2019-06-15 12:00:00') def test_handler_with_asset_and_sr_version( mock_s3_foldernames, mock_batch_id, mock_download, mock_select_asset, mock_generate_ddex, mock_s3_get, mock_write_to_sftp, mock_write_to_s3_audit, mock_asset): """Test handler function with upload of asset.""" mock_s3_foldernames.return_value = ( 'batch123', 'batch123/isrc-123', 'batch123/isrc-123/resources' ) mock_batch_id.return_value = 'batch123' mock_download.return_value = b'music' mock_s3_get.return_value = ( """ { "track_connection": { "tracks": [] }, "assets": [], "isrc": "isrc-123" } """, 'blerg' ) mock_generate_ddex.return_value = 'XML Result' mock_select_asset.return_value = mock_asset input_event = { 'sound_recording': { 'id': 'sr-123', 'version': 'v-123' }, 'upload_asset': True, 'execution_name': '6ff851fa-d83b-4e6e-9888-66077de9529a' } result = index.handler(input_event, None) assert result == { 'details': { 'batch_id': 'batch123', 'filenames': [ 'batch123/isrc-123/resources/90e575bd-94a5-4a3e-9f1f-dade36feb8c3.flac', 'batch123/isrc-123/isrc-123.xml', 'batch123/delivery.complete' ] } } assert mock_write_to_s3_audit.call_args_list == [ call( local=b'XML Result', remote='batch123/isrc-123/isrc-123.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a', service='youtube' ) ] assert mock_s3_get.call_args_list == [ call('sr-123', 'v-123') ] assert mock_select_asset.call_args_list == [ call( OrchardSoundRecording( track_connection=TrackConnection(tracks=[]), assets=[], isrc='isrc-123' ) ) ] assert mock_generate_ddex.call_args_list == [ call( OrchardSoundRecording( track_connection=TrackConnection(tracks=[]), assets=[], isrc='isrc-123' ), mock_asset, { 'timestamp': FakeDatetime(2019, 6, 15, 12, 0, tzinfo=datetime.timezone.utc), # noqa:E501 'message_thread_id': 1, 'message_id': 'batch123' }, 'lambda-sr-delivery-youtube', RuleService.YOUTUBE, selected_version='3.8', takedown=False ) ] assert mock_write_to_sftp.call_args_list == [ call( file_bytes=b'XML Result', subdir='batch123/isrc-123/isrc-123.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), call( file_bytes=b'music', subdir='batch123/isrc-123/resources/90e575bd-94a5-4a3e-9f1f-dade36feb8c3.flac', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), call( file_bytes=b'', subdir='batch123/delivery.complete', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), ] assert mock_s3_foldernames.call_args_list == [ call('batch123', 'isrc-123')] @patch('src.app.s3_delivery_audit.write_sr_delivery_audit_xml') @patch('src.app.sftp_asset_delivery.write_file') @patch('src.app.s3_sound_recordings.get_latest_sound_recording_version') @patch('src.app.generate_ddex') @patch('src.app._select_upload_asset') @patch('src.app.s3_assets.download_asset') @patch('src.app._generate_batch_id') @patch('src.app._s3_remote_foldernames') @freeze_time('2019-06-15 12:00:00') def test_handler_without_asset( mock_s3_foldernames, mock_batch_id, mock_download, mock_select_asset, mock_generate_ddex, mock_s3_get, mock_write_to_sftp, mock_write_to_s3_audit, mock_asset): """Test handler function without upload of asset.""" mock_s3_foldernames.return_value = ( 'batch123', 'batch123/isrc-123', 'batch123/isrc-123/resources' ) mock_batch_id.return_value = 'batch123' mock_s3_get.return_value = ( 'dummy OSR ID', """ { "track_connection": { "tracks": [] }, "assets": [], "isrc": "isrc-123" } """, False ) mock_generate_ddex.return_value = 'XML Result' mock_select_asset.return_value = mock_asset input_event = { 'sound_recording': { 'id': 'sr-123' }, 'upload_asset': False, 'execution_name': '6ff851fa-d83b-4e6e-9888-66077de9529a' } result = index.handler(input_event, None) assert result == { 'details': { 'batch_id': 'batch123', 'filenames': [ 'batch123/isrc-123/isrc-123.xml', 'batch123/delivery.complete' ] } } assert mock_s3_get.called assert not mock_select_asset.called assert not mock_download.called assert mock_write_to_s3_audit.call_args_list == [ call( local=b'XML Result', remote='batch123/isrc-123/isrc-123.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a', service='youtube' )] assert mock_generate_ddex.call_args_list == [ call( OrchardSoundRecording( track_connection=TrackConnection(tracks=[]), assets=[], isrc='isrc-123' ), None, { 'timestamp': FakeDatetime(2019, 6, 15, 12, 0, tzinfo=datetime.timezone.utc), # noqa:E501 'message_thread_id': 1, 'message_id': 'batch123', }, 'lambda-sr-delivery-youtube', RuleService.YOUTUBE, selected_version='3.8', takedown=False ) ] assert mock_write_to_sftp.call_args_list == [ call( file_bytes=b'XML Result', subdir='batch123/isrc-123/isrc-123.xml', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ), call( file_bytes=b'', subdir='batch123/delivery.complete', step_function_execution_id='6ff851fa-d83b-4e6e-9888-66077de9529a' ) ] assert mock_s3_foldernames.call_args_list == [ call('batch123', 'isrc-123')] @patch('src.app.logger.exception') @patch('src.app.s3_sound_recordings.get_latest_sound_recording_version') @patch('src.app.generate_ddex') @patch('sentry_sdk.init') def test_handler_exception( mock_sentry, mock_generate_ddex, mock_s3_get, mock_logger_exception): """Test handler function exception.""" mock_s3_get.return_value = ( 'dummy OSR ID', """ { "track_connection": { "tracks": [] }, "assets": [], "isrc": "isrc-123" } """, False ) input_event = { 'sound_recording': { 'id': 'sr-123' }, 'upload_asset': False, 'execution_name': '6ff851fa-d83b-4e6e-9888-66077de9529a' } test_exception = Exception('test') mock_generate_ddex.side_effect = test_exception with pytest.raises(Exception): index.handler(input_event, None) mock_logger_exception.assert_called_once_with('test') assert not mock_sentry.called @patch('src.app.logger.exception') @patch('src.app.s3_sound_recordings.get_latest_sound_recording_version') @patch('sentry_sdk.init') def test_handler_exception_silent( mock_sentry, mock_s3_get, mock_logger_exception): """Test handler function exception not sent to setnry.""" mock_s3_get.return_value = ( 'dummy OSR ID', """ { "track_connection": { "tracks": [] }, "assets": [], "isrc": "isrc-123" } """, False ) input_event = { 'sound_recording': { 'id': 'sr-123' }, 'upload_asset': True, 'execution_name': '6ff851fa-d83b-4e6e-9888-66077de9529a' } with pytest.raises(index.NoAssets): index.handler(input_event, None) assert not mock_logger_exception.called assert mock_sentry.called @patch('src.app.random') @freeze_time('2019-06-15 12:45:17') def test_batch_id(mock_random): """Test batch id generate based on time and random.""" mock_random.randint.return_value = 10 result = index._generate_batch_id() assert result == '201906151245170010' assert mock_random.randint.call_args_list == [ call(0, 9999) ] def test_select_asset_flac(): """Test primary file extension.""" result = index._select_upload_asset( OrchardSoundRecording( track_connection=TrackConnection(tracks=[]), assets=[ Asset(uuid='111', filename='aaa', extension='wav'), Asset(uuid='222', filename='bbb', extension='mp3'), Asset(uuid='333', filename='ccc', extension='flac') ], isrc='isrc-123' ) ) assert result == Asset(uuid='333', filename='ccc', extension='flac') def test_select_asset_wav(): """Test secondary file extension.""" result = index._select_upload_asset( OrchardSoundRecording( track_connection=TrackConnection(tracks=[]), assets=[ Asset(uuid='111', filename='aaa', extension='wav'), Asset(uuid='222', filename='bbb', extension='mp3') ], isrc='isrc-123' ) ) assert result == Asset(uuid='111', filename='aaa', extension='wav') def test_select_asset_error(): """Test no valid file extension.""" with pytest.raises(index.NoAssets): index._select_upload_asset( OrchardSoundRecording( track_connection=TrackConnection(tracks=[]), assets=[ Asset(uuid='222', filename='bbb', extension='mp3') ], isrc='isrc-123' ) ) @patch('config.secrets_manager_client') @patch('sentry_sdk.init') def test_handler_init(mock_sentry, mock_secrets): """Test sentry init with dsn.""" mock_secret_values = { 'SENTRY_DSN': 'fake-dsn', } mock_secrets.get_cred.side_effect = lambda x: mock_secret_values[x] assert not mock_sentry.called importlib.reload(index) assert mock_sentry.called assert mock_secrets.get_cred.call_args_list == [ mock.call('SENTRY_DSN'), ] @patch('config.secrets_manager_client') @patch('sentry_sdk.init') def test_handler_init_empty_sentry(mock_sentry, mock_secrets): """Test sentry init with dsn.""" mock_secret_values = { 'SENTRY_DSN': '', } mock_secrets.get_cred.side_effect = lambda x: mock_secret_values[x] assert not mock_sentry.called importlib.reload(index) assert not mock_sentry.called @patch('config.secrets_manager_client') @patch('sentry_sdk.init') def test_handler_init_none_sentry(mock_sentry, mock_secrets): """Test sentry init with dsn.""" mock_secret_values = { 'SENTRY_DSN': None, } mock_secrets.get_cred.side_effect = lambda x: mock_secret_values[x] assert not mock_sentry.called importlib.reload(index) assert not mock_sentry.called