"""Tests for Dynamo->S3 lambda.""" import datetime import decimal import time import uuid import boto3 # noqa from moto import mock_s3 # noqa import pytest # noqa import simplejson as json import config # noqa import lambda_dynamo_to_s3 # noqa import util @pytest.fixture def patch_datetime_utcnow(monkeypatch): """Fixture for datetime.datetime.utcnow method.""" mock_date = datetime.datetime(2017, 10, 23, 15, 9, 12, 204904) class mydatetime: """Mock datetime class for testing purposes.""" @classmethod def utcnow(cls): return mock_date monkeypatch.setattr(datetime, 'datetime', mydatetime) @pytest.fixture def dynamo_event(): """Fixture with valid DynamoDB event.""" with open('tests/new_dynamo_event_fixture.json', 'r') as fixture: dynamo_test_event = json.load(fixture, parse_float=decimal.Decimal) return dynamo_test_event @pytest.fixture(params=['old', 'new']) def dynamo_event_name(request): """Fixture type and corresponding feature flag value.""" fixture_name = 'tests/{}_dynamo_event_fixture.json'.format( request.param) return fixture_name @mock_s3 def test_lambda_dynamo_to_s3(dynamo_event_name): """Expect to get json file with required records.""" s3 = boto3.client('s3') s3.create_bucket(Bucket=config.S3_BUCKET_NAME) with open(dynamo_event_name, 'r') as fixture: dynamo_event = json.load(fixture) expected_content = lambda_dynamo_to_s3.parse_dynamo_events(dynamo_event) result = lambda_dynamo_to_s3.handler(dynamo_event, None) created_file = s3.get_object( Bucket=config.S3_BUCKET_NAME, Key=result['created_filename']) content = json.loads(created_file['Body'].read().decode('utf-8')) assert created_file['ResponseMetadata']['HTTPStatusCode'] == 200 assert content == expected_content def test_handler_s3_error(mocker, dynamo_event_name): """Expect to log error from S3.""" s3_error_response = { 'ResponseMetadata': {'HTTPStatusCode': 400} } s3_mock = mocker.patch.object( lambda_dynamo_to_s3.s3, 'put_object', return_value=s3_error_response) mock_sentry = mocker.patch.object( lambda_dynamo_to_s3.sentry.sentry_client, 'captureException') with open(dynamo_event_name, 'r') as fixture: dynamo_event = json.load(fixture) with pytest.raises(util.RetryCountExceededError) as e: lambda_dynamo_to_s3.handler(dynamo_event, None) assert e.value.args[0].args == (s3_error_response,) assert s3_mock.call_count == 10 assert mock_sentry.called def test_handler_sends_errors_to_sentry(mocker, dynamo_event_name): """Expect to handle exception and send it to sentry.""" mocker.patch.object( lambda_dynamo_to_s3.s3, 'put_object', side_effect=Exception) mock_sentry = mocker.patch.object( lambda_dynamo_to_s3.sentry.sentry_client, 'captureException') with open(dynamo_event_name, 'r') as fixture: dynamo_event = json.load(fixture) with pytest.raises(Exception): lambda_dynamo_to_s3.handler(dynamo_event, None) assert mock_sentry.called @pytest.mark.parametrize( 'fixture_type', ['old', 'new']) def test_parse_dynamo_events(fixture_type): """Expect to get formatted events from Dynamo event. Fixture checks for both old and new structures of 'territories'. old: {'AU': {'tuid': 12345}} new: {'AU': [12345]} """ fixture_name = 'tests/{}_dynamo_event_fixture.json'.format(fixture_type) with open(fixture_name, 'r') as fixture: dynamo_events = json.load(fixture) result = lambda_dynamo_to_s3.parse_dynamo_events(dynamo_events) fixture_name = 'tests/{}_expected_parsed_event.json'.format(fixture_type) with open(fixture_name, 'r') as expected_data: expected_result = json.load(expected_data) assert result == expected_result def test_parse_remove_event(): """Expect to handle remove event.""" with open('tests/dynamo_remove_event_fixture.json', 'r') as fixture: dynamo_events = json.load(fixture, parse_float=decimal.Decimal) expected = [{ 'action': 'REMOVE', 'isrc': 'TR3340601592', 'event_time': 1507639440.0, 'data': {} }] result = lambda_dynamo_to_s3.parse_dynamo_events(dynamo_events) assert result == expected def test_parse_single_dynamo_event(dynamo_event): """Expect to get dict with data parsed from event.""" single_event = dynamo_event['Records'][0] result = lambda_dynamo_to_s3.parse_single_dynamo_event(single_event) expected_dict = { 'isrc': 'ES5770604477', 'data': { 'territories': {'PR': [12884743], 'PS': [1234567890]}, 'locked_territories': {} }, 'action': 'MODIFY', 'event_time': 1607639440.0 } assert result == expected_dict def test_make_s3_key(patch_datetime_utcnow, mocker): """Expect to get correct s3 key.""" mocker.patch.object(uuid, 'uuid4', return_value='222') expected_s3_folder = config.DEFAULT_S3_BUCKET_FOLDER expected_dir_name = datetime.date.today().strftime('%Y-%m-%d') expected_name = ( str(time.mktime(datetime.datetime.utcnow().timetuple())) + uuid.uuid4()) expected_filename = '.'.join([expected_name, 'json']) expected_s3_key = '/'.join( [expected_s3_folder, expected_dir_name, expected_filename]) s3_key = lambda_dynamo_to_s3.make_s3_key() assert s3_key == expected_s3_key class SentryException(Exception): """Fake Sentry exception.""" pass def test_sentry_capture_exception_failure(mocker, dynamo_event_name): """Test that an exception will be raised even if sentry fails.""" mocker.patch.object( lambda_dynamo_to_s3.s3, 'put_object', side_effect=Exception) mocker.patch.object( lambda_dynamo_to_s3.sentry.sentry_client, 'captureException', side_effect=SentryException) with open(dynamo_event_name, 'r') as fixture: dynamo_event = json.load(fixture) with pytest.raises(Exception): lambda_dynamo_to_s3.handler(dynamo_event, None)