"""Lambda Test Module.""" from unittest import mock from unittest.mock import MagicMock import json import index import pytest LAMBDA_NAME = 'ripper-feeder-test' STEP_FUNCTION_ARN = \ 'arn:aws:states:us-east-2:123456789012:stateMachine:test-ripper-feeder-sfn' BUCKET_NAME = 'test-ripper-feeder-incoming' KEY_NAME = 'test.wav' JPG_KEY_NAME = 'test.jpg' s3_trigger_record = { 'Records': [ { 'eventVersion': '2.1', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-2', 'eventTime': '2020-05-07T04:53:04.976Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': { 'principalId': 'AWS:USERID12345566' }, 'requestParameters': { 'sourceIPAddress': '169.254.0.1' }, 'responseElements': { 'x-amz-request-id': 'ID23932482394723943', 'x-amz-id-2': 'ID2390423402342304234023402' }, 's3': { 's3SchemaVersion': '1.0', 'configurationId': 'tf-s3-lambda-20200507000956467600000003', 'bucket': { 'name': BUCKET_NAME, 'ownerIdentity': { 'principalId': 'ASJASDASK8979' }, 'arn': 'arn:aws:s3:::{}'.format(BUCKET_NAME) }, 'object': { 'key': KEY_NAME, 'size': 44144, 'eTag': 'tagasd0asdj0acascasociasnoasnimd', 'sequencer': '000000000000000001' } } } ] } jpg_s3_trigger_record = { 'Records': [ { 'eventVersion': '2.1', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-2', 'eventTime': '2020-05-07T04:53:04.976Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': { 'principalId': 'AWS:USERID12345566' }, 'requestParameters': { 'sourceIPAddress': '169.254.0.1' }, 'responseElements': { 'x-amz-request-id': 'ID23932482394723943', 'x-amz-id-2': 'ID2390423402342304234023402' }, 's3': { 's3SchemaVersion': '1.0', 'configurationId': 'tf-s3-lambda-20200507000956467600000003', 'bucket': { 'name': BUCKET_NAME, 'ownerIdentity': { 'principalId': 'ASJASDASK8979' }, 'arn': 'arn:aws:s3:::{}'.format(BUCKET_NAME) }, 'object': { 'key': JPG_KEY_NAME, 'size': 44144, 'eTag': 'tagasd0asdj0acascasociasnoasnimd', 'sequencer': '000000000000000001' } } } ] } sqs_trigger_record = { 'Records': [ { 'messageId': '059f36b4-87a3-44ab-83d2-661975830a7d', 'receiptHandle': 'AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...', 'body': 'Test message.', 'attributes': { 'ApproximateReceiveCount': '1', 'SentTimestamp': '1545082649183', 'SenderId': 'AIDAIENQZJOLO23YVJ4VO', 'ApproximateFirstReceiveTimestamp': '1545082649185' }, 'messageAttributes': {}, 'md5OfBody': 'e4e68fb7bd0e697a0ae8f1bb342846b3', 'eventSource': 'aws:sqs', 'eventSourceARN': 'arn:aws:sqs:us-east-2:123456789012:da-queue', 'awsRegion': 'us-east-2' }, { 'messageId': '2e1424d4-f796-459a-8184-9c92662be6da', 'receiptHandle': 'AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...', 'body': 'Test message.', 'attributes': { 'ApproximateReceiveCount': '1', 'SentTimestamp': '1545082650636', 'SenderId': 'AIDAIENQZJOLO23YVJ4VO', 'ApproximateFirstReceiveTimestamp': '1545082650649' }, 'messageAttributes': {}, 'md5OfBody': 'e4e68fb7bd0e697a0ae8f1bb342846b3', 'eventSource': 'aws:sqs', 'eventSourceARN': 'arn:aws:sqs:us-east-2:123456789012:da-queue', 'awsRegion': 'us-east-2' } ] } empty_trigger_record = { 'Records': [] } head_object_response = { 'ResponseMetadata': { 'RequestId': '0AD959EDF0404637', 'HostId': 'UW/hpssDduNFqe7THauSIgq7pcpE0shkoOxKL3/JZZlVocKKI23ddpqRUVRTCvHP/WSJRtnxg78=', # noqa 'HTTPStatusCode': 200, 'HTTPHeaders': { 'x-amz-id-2': 'UW/hpssDduNFqe7THauSIgq7pcpE0shkoOxKL3/JZZlVocKKI23ddpqRUVRTCvHP/WSJRtnxg78=', # noqa 'x-amz-request-id': '0AD959EDF0404637', 'date': 'Thu, 07 May 2020 04:53:11 GMT', 'last-modified': 'Thu, 07 May 2020 04:53:07 GMT', 'etag': 'b621f2b227ae32819bd4908f6e7219ec', 'x-amz-meta-sharename': 'target_dir', 'x-amz-meta-upc': '0123456789012', 'x-amz-meta-host': '169.254.0.1', 'x-amz-meta-path': 'WAV/{upc}/{upc}', 'accept-ranges': 'bytes', 'content-type': 'binary/octet-stream', 'content-length': '44144', 'server': 'AmazonS3' }, 'RetryAttempts': 0 }, 'AcceptRanges': 'bytes', 'LastModified': 'Thu, 07 May 2020 04:53:07 GMT', 'ContentLength': 44144, 'ETag': 'b621f2b227ae32819bd4908f6e7219ec', 'ContentType': 'binary/octet-stream', 'Metadata': { 'sharename': 'target_dir', 'upc': '0123456789012', 'host': '169.254.0.1', 'path': 'WAV/{upc}/{upc}.wav' } } converted_head_object_response = { 'ResponseMetadata': { 'RequestId': '0AD959EDF0404637', 'HostId': 'UW/hpssDduNFqe7THauSIgq7pcpE0shkoOxKL3/JZZlVocKKI23ddpqRUVRTCvHP/WSJRtnxg78=', # noqa 'HTTPStatusCode': 200, 'HTTPHeaders': { 'x-amz-id-2': 'UW/hpssDduNFqe7THauSIgq7pcpE0shkoOxKL3/JZZlVocKKI23ddpqRUVRTCvHP/WSJRtnxg78=', # noqa 'x-amz-request-id': '0AD959EDF0404637', 'date': 'Thu, 07 May 2020 04:53:11 GMT', 'last-modified': 'Thu, 07 May 2020 04:53:07 GMT', 'etag': 'b621f2b227ae32819bd4908f6e7219ec', 'x-amz-meta-sharename': 'target_dir', 'x-amz-meta-upc': '0123456789012', 'x-amz-meta-host': '169.254.0.1', 'x-amz-meta-path': 'WAV/{upc}/{upc}', 'accept-ranges': 'bytes', 'content-type': 'binary/octet-stream', 'content-length': '44144', 'server': 'AmazonS3' }, 'RetryAttempts': 0 }, 'AcceptRanges': 'bytes', 'LastModified': 'Thu, 07 May 2020 04:53:07 GMT', 'ContentLength': 44144, 'ETag': 'b621f2b227ae32819bd4908f6e7219ec', 'ContentType': 'binary/octet-stream', 'Metadata': { 'sharename': 'target_dir', 'upc': '0123456789012', 'host': '169.254.0.1', 'path': 'WAV/{upc}/{upc}.wav', 'converted': 'PCM CONVERSION' } } jpg_head_object_response = { 'ResponseMetadata': { 'RequestId': '0AD959EDF0404637', 'HostId': 'UW/hpssDduNFqe7THauSIgq7pcpE0shkoOxKL3/JZZlVocKKI23ddpqRUVRTCvHP/WSJRtnxg78=', # noqa # noqa 'HTTPStatusCode': 200, 'HTTPHeaders': { 'x-amz-id-2': 'UW/hpssDduNFqe7THauSIgq7pcpE0shkoOxKL3/JZZlVocKKI23ddpqRUVRTCvHP/WSJRtnxg78=', # noqa # noqa 'x-amz-request-id': '0AD959EDF0404637', 'date': 'Thu, 07 May 2020 04:53:11 GMT', 'last-modified': 'Thu, 07 May 2020 04:53:07 GMT', 'etag': 'b621f2b227ae32819bd4908f6e7219ec', 'x-amz-meta-sharename': 'target_dir', 'x-amz-meta-upc': '0123456789012', 'x-amz-meta-host': '169.254.0.1', 'x-amz-meta-path': 'WAV/{upc}/{upc}', 'accept-ranges': 'bytes', 'content-type': 'binary/octet-stream', 'content-length': '44144', 'server': 'AmazonS3' }, 'RetryAttempts': 0 }, 'AcceptRanges': 'bytes', 'LastModified': 'Thu, 07 May 2020 04:53:07 GMT', 'ContentLength': 44144, 'ETag': 'b621f2b227ae32819bd4908f6e7219ec', 'ContentType': 'binary/octet-stream', 'Metadata': { 'sharename': 'target_dir', 'upc': '0123456789012', 'host': '169.254.0.1', 'path': 'WAV/{upc}/{upc}.jpg' } } step_params = { 'upc': '0123456789012', 's3_bucket': 'test-ripper-feeder-incoming', 's3_key': 'test.wav', 'host': '169.254.0.1', 'destination_filepath': 'WAV/{upc}/{upc}.wav', 'destination_filename': '{upc}.wav', 'sharename': 'target_dir' } jpg_step_params = { 'upc': '0123456789012', 's3_bucket': 'test-ripper-feeder-incoming', 's3_key': 'test.tif', 'host': '169.254.0.1', 'destination_filepath': 'WAV/{upc}/{upc}.tif', 'destination_filename': '{upc}.tif', 'sharename': 'target_dir' } @pytest.mark.parametrize( 'event, call_count', [(s3_trigger_record, 1), (sqs_trigger_record, 0), (empty_trigger_record, 0)]) def test_handler(mocker, event, call_count): """Test main handler().""" mock_process_event = mocker.patch('index.process_s3_event') index.handler(event, None) assert mock_process_event.call_count == call_count @pytest.mark.parametrize( 'event, bucket, key', [(s3_trigger_record, BUCKET_NAME, KEY_NAME)]) def test_process_s3_event(mocker, event, bucket, key): """Test process_s3_event().""" mock_start_execution = mocker.patch('index.boto3.client') mock_generate_step_params = mocker.patch('index.generate_step_params') mocker.patch('wav_utils.BytesIO') mocker.patch('wav_utils.wave') mock_which = mocker.patch('index.which') mock_which.return_value = True mock_generate_step_params.return_value = step_params index.process_s3_event(event['Records'][0]) assert mock_generate_step_params.call_count == 1 assert mock_start_execution.call_count == 2 @pytest.mark.parametrize( 'bucket, key', [(BUCKET_NAME, KEY_NAME)]) @mock.patch('index.boto3.client') def test_generate_step_params(mock_s3_client, bucket, key): """Test generate_step_params().""" mock_s3_client.return_value.\ head_object.return_value = head_object_response index.generate_step_params(bucket, key) assert mock_s3_client.return_value.head_object.call_count == 1 # TODO: Test return value for step_params(). def test_handler_end_to_end(mocker): """Test main handler() end-to-end.""" mock_s3_client = mocker.patch('index.boto3.client') mocker.patch('wav_utils.BytesIO') mocker.patch('wav_utils.wave') mock_get_wav = mocker.patch('index.get_wav_from_s3') mock_check_wav = mocker.patch('index.check_wav') file_fixed = False check_fixed = False mock_get_wav.return_value = (MagicMock(), file_fixed, 'Fix Message') mock_check_wav.return_value = (MagicMock(), check_fixed, 'Check Message') # Override head_object called mock_s3_client.return_value. \ head_object.return_value = head_object_response # Call handler index.handler(s3_trigger_record, None) # Slice off args and kwargs sfn_execution_args, sfn_execution_kwargs = \ mock_s3_client.return_value.start_execution.call_args # Assertions assert sfn_execution_kwargs['input'] == json.dumps(step_params) assert sfn_execution_kwargs['stateMachineArn'] == STEP_FUNCTION_ARN @mock.patch('index.get_image_from_s3') @mock.patch('index.save_image_to_s3') @mock.patch('index.boto3.client') def test_handler_end_to_end_jpg( mock_s3_client, mock_save_to_s3, mock_get_from_s3): """Test main handler() end-to-end.""" # Override head_object called mock_s3_client.return_value. \ head_object.return_value = jpg_head_object_response # Call handler index.handler(jpg_s3_trigger_record, None) # Slice off args and kwargs sfn_execution_args, sfn_execution_kwargs = \ mock_s3_client.return_value.start_execution.call_args # Assertions assert sfn_execution_kwargs['input'] == json.dumps(jpg_step_params) assert sfn_execution_kwargs['stateMachineArn'] == STEP_FUNCTION_ARN assert mock_save_to_s3.call_count == 1 def test_handler_end_to_end_wav_conversion(mocker): """Test main handler() end-to-end with converted wav.""" mock_get_wav = mocker.patch('index.get_wav_from_s3') mock_save_wav = mocker.patch('index.save_wav_to_s3') mock_check_wav = mocker.patch('index.check_wav') mocker.patch('index.get_image_from_s3') mock_s3_client = mocker.patch('index.boto3.client') mock_which = mocker.patch('index.which') mock_which.return_value = True file_fixed = True check_fixed = False # Override head_object called mock_s3_client.return_value. \ head_object.return_value = head_object_response mock_get_wav.return_value = (MagicMock(), file_fixed, 'Fix Message') mock_check_wav.return_value = (MagicMock(), check_fixed, 'Check Message') # Call handler index.handler(s3_trigger_record, None) # Assertions assert mock_s3_client.return_value.start_execution.call_count == 0 assert mock_save_wav.call_count == 1 assert mock_check_wav.call_count == 1 assert mock_get_wav.call_count == 1 def test_handler_end_to_end_wav_no_conversion(mocker): """Test main handler() end-to-end with converted wav.""" # mock_get_wav = mocker.patch('index.get_wav_from_s3') mock_save_wav = mocker.patch('index.save_wav_to_s3') mock_check_wav = mocker.patch('index.check_wav') mocker.patch('index.get_image_from_s3') mock_s3_client = mocker.patch('index.boto3.client') check_fixed = False # Override head_object called mock_s3_client.return_value. \ head_object.return_value = converted_head_object_response mock_check_wav.return_value = (MagicMock(), check_fixed, 'Check Message') # Call handler index.handler(s3_trigger_record, None) # Slice off args and kwargs sfn_execution_args, sfn_execution_kwargs = \ mock_s3_client.return_value.start_execution.call_args # Assertions assert sfn_execution_kwargs['input'] == json.dumps(step_params) assert sfn_execution_kwargs['stateMachineArn'] == STEP_FUNCTION_ARN assert mock_save_wav.call_count == 0 assert mock_check_wav.call_count == 0