"""Lambda test module.""" import datetime import importlib from unittest import mock from unittest.mock import call from unittest.mock import patch from unittest.mock import PropertyMock import pytest from freezegun import freeze_time from freezegun.api import FakeDatetime # type: ignore[attr-defined] from src import app as index from soundrecording_utils.metadata.types import Asset from soundrecording_utils.metadata.types import OrchardSoundRecording from soundrecording_utils.metadata.types import TrackConnection @patch('src.app.s3_delivery_audit.write_sr_delivery_audit_xml') @patch('src.app.s3_sound_recordings.get_sound_recording_version') @patch('src.app.generate_ern_srr') @patch('src.app._select_upload_asset') @patch('src.app.s3_assets.download_asset') @patch('src.app._generate_batch_id') @patch('src.common.connectors.sftp.Connection') @patch('src.app._sftp_conn_info') @patch('src.app._sftp_remote_dirnames') @freeze_time('2019-06-15 12:00:00') def test_handler_with_asset( mock_sftp_dirnames, mock_conn_info, mock_sftp, mock_batch_id, mock_download, mock_select_asset, mock_generate_ern_srr, mock_s3_get, mock_write_to_s3, mock_asset): """Test handler function with upload of asset.""" mock_sftp_dirnames.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_ern_srr.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', # noqa:E501 'batch123/isrc-123/isrc-123.xml', 'batch123/BatchComplete_batch123.xml' ] } } assert mock_write_to_s3.call_args_list == [ call( b'XML Result', 'batch123/isrc-123/isrc-123.xml', '6ff851fa-d83b-4e6e-9888-66077de9529a' ) ] 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_ern_srr.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' } ) ] assert mock_sftp_dirnames.call_args_list == [call('batch123', 'isrc-123')] assert mock_sftp.call_args_list == [ call(**mock_conn_info.return_value) ] assert mock_sftp.return_value.upload_files.call_args_list == [ call([ ( b'music', 'batch123/isrc-123/resources/90e575bd-94a5-4a3e-9f1f-dade36feb8c3.flac' # noqa:E501 ), ( b'XML Result', 'batch123/isrc-123/isrc-123.xml' ), ( b'', 'batch123/BatchComplete_batch123.xml' ) ]) ] @patch('src.app.s3_delivery_audit.write_sr_delivery_audit_xml') @patch('src.app.s3_sound_recordings.get_sound_recording_version') @patch('src.app.generate_ern_srr') @patch('src.app._select_upload_asset') @patch('src.app.s3_assets.download_asset') @patch('src.app._generate_batch_id') @patch('src.common.connectors.sftp.Connection') @patch('src.app._sftp_conn_info') @patch('src.app._sftp_remote_dirnames') @freeze_time('2019-06-15 12:00:00') def test_handler_without_asset( mock_sftp_dirnames, mock_conn_info, mock_sftp, mock_batch_id, mock_download, mock_select_asset, mock_generate_ern_srr, mock_s3_get, mock_write_to_s3, mock_asset): """Test handler function without upload of asset.""" mock_sftp_dirnames.return_value = ( 'batch123', 'batch123/isrc-123', 'batch123/isrc-123/resources' ) mock_batch_id.return_value = 'batch123' mock_s3_get.return_value = ( """ { "track_connection": { "tracks": [] }, "assets": [], "isrc": "isrc-123" } """, 'blerg' ) mock_generate_ern_srr.return_value = 'XML Result' mock_select_asset.return_value = mock_asset input_event = { 'sound_recording': { 'id': 'sr-123', 'version': 'v-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/BatchComplete_batch123.xml' ] } } assert mock_s3_get.called assert not mock_select_asset.called assert not mock_download.called assert mock_write_to_s3.call_args_list == [ call( b'XML Result', 'batch123/isrc-123/isrc-123.xml', '6ff851fa-d83b-4e6e-9888-66077de9529a' )] assert mock_generate_ern_srr.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' } ) ] assert mock_sftp_dirnames.call_args_list == [call('batch123', 'isrc-123')] assert mock_sftp.call_args_list == [ call(**mock_conn_info.return_value) ] assert mock_sftp.return_value.upload_files.call_args_list == [ call([ ( b'XML Result', 'batch123/isrc-123/isrc-123.xml' ), ( b'', 'batch123/BatchComplete_batch123.xml' ) ]) ] @patch( 'src.app.config.META_SFTP_BASE_DIRNAME', new_callable=PropertyMock(return_value='') ) @patch( 'src.app.config.RESOURCES_DIRNAME', new_callable=PropertyMock(return_value='resources') ) def test_sftp_remote_dirnames_default(mock_resources_dir, mock_base_dir): """Test default remote dirnames.""" (batch_dir, release_dir, resources_dir) = index._sftp_remote_dirnames( 'batch123', 'isrc123' ) assert batch_dir == 'batch123' assert release_dir == 'batch123/isrc123' assert resources_dir == 'batch123/isrc123/resources' @patch( 'src.app.config.META_SFTP_BASE_DIRNAME', new_callable=PropertyMock(return_value='temp-fingerprinting') ) @patch( 'src.app.config.RESOURCES_DIRNAME', new_callable=PropertyMock(return_value='resources') ) def test_sftp_remote_dirnames_base(mock_resources_dir, mock_base_dir): """Test override base remote dirnames.""" (batch_dir, release_dir, resources_dir) = index._sftp_remote_dirnames( 'batch123', 'isrc123' ) assert batch_dir == 'temp-fingerprinting/batch123' assert release_dir == 'temp-fingerprinting/batch123/isrc123' assert resources_dir == 'temp-fingerprinting/batch123/isrc123/resources' @patch( 'src.app.config.META_SFTP_BASE_DIRNAME', new_callable=PropertyMock(return_value='temp-fingerprinting/') ) @patch( 'src.app.config.RESOURCES_DIRNAME', new_callable=PropertyMock(return_value='resources/') ) def test_sftp_remote_dirnames_rstrip(mock_resources_dir, mock_base_dir): """Test rstrip remote dirnames.""" (batch_dir, release_dir, resources_dir) = index._sftp_remote_dirnames( 'batch123', 'isrc123' ) assert batch_dir == 'temp-fingerprinting/batch123' assert release_dir == 'temp-fingerprinting/batch123/isrc123' assert resources_dir == 'temp-fingerprinting/batch123/isrc123/resources' @patch('src.app.logger.exception') @patch('src.app.s3_sound_recordings.get_sound_recording_version') @patch('src.app.generate_ern_srr') @patch('sentry_sdk.init') def test_handler_exception( mock_sentry, mock_generate_ern_srr, mock_s3_get, mock_logger_exception): """Test handler function exception.""" mock_s3_get.return_value = ( """ { "track_connection": { "tracks": [] }, "assets": [], "isrc": "isrc-123" } """, 'blerg' ) input_event = { 'sound_recording': { 'id': 'sr-123', 'version': 'v-123' }, 'upload_asset': False, 'execution_name': '6ff851fa-d83b-4e6e-9888-66077de9529a' } test_exception = Exception('test') mock_generate_ern_srr.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_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 = ( """ { "track_connection": { "tracks": [] }, "assets": [], "isrc": "isrc-123" } """, 'blerg' ) input_event = { 'sound_recording': { 'id': 'sr-123', 'version': 'v-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) ] @patch('config.secrets_manager_client') @patch( 'config.META_SFTP_HOST', new_callable=PropertyMock(return_value='meta-host') ) @patch( 'config.META_SFTP_PORT', new_callable=PropertyMock(return_value=123) ) @patch( 'config.META_SFTP_USERNAME', new_callable=PropertyMock(return_value='meta-user') ) def test_sftp_conn_info( mock_fb_port, mock_fb_host, mock_fb_username, mock_secrets): """Test generate connection info object.""" mock_secret_values = { 'SENTRY_DSN': '', 'META_SFTP_PKEY': 'meta-test-private-key' } mock_secrets.get_cred.side_effect = lambda x: mock_secret_values[x] importlib.reload(index) result = index._sftp_conn_info() assert result == { 'hostname': 'meta-host', 'port': 123, 'username': 'meta-user', 'pkey': 'meta-test-private-key' } 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', 'META_SFTP_PKEY': 'meta-test-private-key' } 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'), mock.call('META_SFTP_PKEY') ] @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': '', 'META_SFTP_USERNAME': 'meta-user', 'META_SFTP_PKEY': 'meta-test-private-key' } 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, 'META_SFTP_USERNAME': 'meta-user', 'META_SFTP_PKEY': 'meta-test-private-key' } 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