"""Lambda test module.""" import collections import datetime import json import boto3 import moto import pytest from constants import common_fields from constants import fields import dynamodb_config import index import sql_queries ORDER_IDS = (123, 124) UPCS = (111111111111, 2222222222222) @pytest.fixture def ar_order_details(reverse=False): """Return get order query sample data.""" order_ids = ORDER_IDS if not reverse else reversed(ORDER_IDS) return [ { fields.AR_EO_ORDER_ID: order_id, fields.AR_EO_ENCODER_ID: 18, fields.AR_EO_META_UPDATE: 'N', fields.AR_EO_PRIORITY: 1, fields.AR_EO_ENTRY_DATE: datetime.datetime( 2017, 8, 28, 15, 11, 52), fields.AR_EO_USER_ID: 1, fields.AR_EO_STORE_ID: 1, fields.AR_EO_UPC: upc, } for order_id in order_ids for upc in UPCS ] @pytest.fixture def encoding_order_dict(reverse=False): """Return encoding order data.""" order_ids = ORDER_IDS if not reverse else reversed(ORDER_IDS) return collections.OrderedDict([ (order_id, { fields.AR_EO_ORDER_ID: order_id, fields.AR_EO_ENCODER_ID: 18, fields.AR_EO_META_UPDATE: 'N', fields.AR_EO_PRIORITY: 1, fields.AR_EO_ENTRY_DATE: datetime.datetime( 2017, 8, 28, 15, 11, 52), fields.AR_EO_USER_ID: 1, fields.EO_DETAILS: [ { fields.AR_EO_STORE_ID: 1, fields.AR_EO_UPC: upc } for upc in UPCS ] }) for order_id in order_ids] ) @pytest.fixture def dynamo_dict(): """Return encoding(vector) order dynamo dict.""" return { common_fields.VO_ENCODER_ID: 18, common_fields.VO_ORDER_ID: '123', common_fields.VO_CREATED_AT: '2017-08-28T15:11:52', common_fields.DDB_VO_BUCKET_NAME: 'dev-vector-order', common_fields.VO_PRIORITY: 1, common_fields.DDB_VO_S3_KEY: 123, common_fields.VO_META_UPDATE: 'N', common_fields.VO_USER_ID: 1 } @pytest.fixture def s3_dict(): """Return encoding(vector) order s3 dict fixture.""" return { common_fields.VO_ENCODER_ID: 18, common_fields.VO_ORDER_ID: '123', common_fields.VO_CREATED_AT: '2017-08-28T15:11:52', common_fields.S3_VO_VALIDATED: { 2222222222222: [1], 111111111111: [1] }, common_fields.S3_VO_PRODUCTS: [111111111111, 2222222222222], common_fields.VO_PRIORITY: 1, common_fields.S3_VO_STORES: [1], common_fields.VO_META_UPDATE: 'N', common_fields.VO_USER_ID: 1 } def test_order_to_dynamo_dict(encoding_order_dict): """Test index.order_to_full_dict function.""" order = next(iter(encoding_order_dict.values())) ddb_dict = index.order_to_dynamo_dict(order) assert common_fields.VO_ORDER_ID in ddb_dict assert common_fields.VO_CREATED_AT in ddb_dict assert common_fields.VO_PRIORITY in ddb_dict assert common_fields.VO_ENCODER_ID in ddb_dict assert common_fields.VO_META_UPDATE in ddb_dict assert common_fields.DDB_VO_BUCKET_NAME in ddb_dict assert common_fields.DDB_VO_S3_KEY in ddb_dict assert common_fields.VO_USER_ID in ddb_dict assert isinstance(ddb_dict[common_fields.VO_ORDER_ID], str) assert isinstance(ddb_dict[common_fields.DDB_VO_S3_KEY], str) @pytest.mark.parametrize( 'test_input, test_result', [('1 , 2 ', [1, 2]), (' 6 , 7', [6, 7])]) def test_get_encoder_ids(mocker, test_input, test_result): """Test index.get_encoder_ids function.""" mocked_config = mocker.patch('index.config') mocked_config.ORDER_ENCODER_ID_LIST = test_input result = index.get_encoder_ids() assert result == test_result @pytest.mark.parametrize('test_input', ['1.2', 'aaa', '8;7']) def test_get_encoder_ids_error(mocker, test_input): """Test index.get_encoder_ids, error case.""" mocked_config = mocker.patch('index.config') mocked_config.ORDER_ENCODER_ID_LIST = test_input with pytest.raises(ValueError): index.get_encoder_ids() def test_order_to_full_dict(encoding_order_dict): """Test index.order_to_full_dict function.""" details = next(iter(encoding_order_dict.values())) s3_dict = index.order_to_full_dict(details) assert common_fields.VO_ORDER_ID in s3_dict assert common_fields.VO_CREATED_AT in s3_dict assert common_fields.VO_PRIORITY in s3_dict assert common_fields.VO_ENCODER_ID in s3_dict assert common_fields.VO_META_UPDATE in s3_dict assert common_fields.S3_VO_PRODUCTS in s3_dict assert common_fields.S3_VO_STORES in s3_dict assert common_fields.S3_VO_VALIDATED in s3_dict assert common_fields.VO_USER_ID in s3_dict assert isinstance(s3_dict[common_fields.VO_ORDER_ID], str) def test_get_s3_key(mocker): """Test index.get_s3_key function.""" mocked_s3_config = mocker.patch('index.s3_config') mocked_s3_config.ORDERS_S3_PREFIX = 'prefix' result = index.get_s3_key('2000') assert result == 'prefix/2000.json' @moto.mock_s3 def test_put_to_s3(s3_dict): """Test index.put_to_s3 function.""" s3_bucket_name = 'bucket' s3_key = 'key' # Get S3 connection mocked by moto. conn = boto3.resource('s3') # Pre-create the bucket. conn.create_bucket(Bucket=s3_bucket_name) index.put_to_s3(s3_bucket_name, s3_key, json.dumps(s3_dict)) # Read data from the mocked S3 object. s3_obj = conn.Object(s3_bucket_name, s3_key).get() body = s3_obj['Body'].read().decode() assert body == json.dumps(s3_dict) assert s3_obj['ContentType'] == 'application/json' @moto.mock_dynamodb2 def test_put_to_dynamo_data(dynamo_dict): """Test index.put_to_dynamo function creates expected data in the DB.""" eo_id = dynamo_dict[common_fields.VO_ORDER_ID] # get dynamodb mock dynamodb = boto3.resource('dynamodb') # create table mock table = dynamodb.create_table( TableName=dynamodb_config.ORDERS_DDB_TABLE, KeySchema=[ { 'AttributeName': common_fields.VO_ORDER_ID, 'KeyType': 'HASH' } ], AttributeDefinitions=[ { 'AttributeName': common_fields.VO_ORDER_ID, 'AttributeType': 'S' } ], ProvisionedThroughput={ 'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1 } ) index.put_to_dynamo(dynamo_dict) # read data from mock item = table.get_item(Key={common_fields.VO_ORDER_ID: str(eo_id)})['Item'] dynamo_eo_dict = dict(item) assert dynamo_eo_dict == dynamo_dict def test_get_dynamodb(mocker): """Test DynamoDB resource helper uses proper configuration.""" max_attempts = 42 mocked_config = mocker.patch('index.config') mocked_config.DDB_WRITE_MAX_ATTEMPTS = max_attempts mocked_botocore_config_instance = mocker.Mock() mocked_botocore_config = mocker.patch('index.botocore_config.Config') mocked_botocore_config.return_value = mocked_botocore_config_instance mocked_dynamodb = mocker.Mock() mocked_resource = mocker.patch('boto3.resource') mocked_resource.return_value = mocked_dynamodb assert index.get_dynamodb() == mocked_dynamodb mocked_botocore_config.assert_called_with( retries={'max_attempts': max_attempts}) mocked_resource.assert_called_with( 'dynamodb', config=mocked_botocore_config_instance) def patch_session_scope(mocker, return_value): """Patch session scope function.""" # session mock mocked_session = mocker.Mock() mocked_session.execute.return_value = return_value # context mock mocked_context = mocker.Mock() mocked_context.__enter__ = mocker.Mock( return_value=mocked_session) mocked_context.__exit__ = mocker.Mock(return_value=None) # session scope function mock mocked_session_scope = mocker.patch( 'connectors.art_relations.session_scope') mocked_session_scope.return_value = mocked_context return mocked_session @pytest.mark.parametrize( 'ar_order_details_ordered, encoding_order_dict_ordered', ( (ar_order_details(), encoding_order_dict()), (ar_order_details(reverse=True), encoding_order_dict(reverse=True)), ) ) def test_get_orders( mocker, ar_order_details_ordered, encoding_order_dict_ordered): """Test index.get_orders function.""" # patch session scope mocked_session = patch_session_scope(mocker, ar_order_details_ordered) sql_text = 'test' encoder_ids = [1, 2] # Does not matter of this matches the fixtures. limit = 1 mocked_sqlalchemy_text = mocker.patch('index.sqlalchemy.text') mocked_sqlalchemy_text.return_value = sql_text mocked_get_encoder_ids = mocker.patch('index.get_encoder_ids') mocked_get_encoder_ids.return_value = encoder_ids mocked_config = mocker.patch('index.config') mocked_config.ORDERS_AR_SELECT_LIMIT = limit result = index.get_orders() assert list(result.items()) == list(encoding_order_dict_ordered.items()) assert mocked_session.execute.call_args[0] == ( sql_text, {'limit': limit, 'in_values': tuple(encoder_ids)}) def test_update_order_status(mocker): """Test index.update_order_status function.""" # patch session scope mocked_session = patch_session_scope(mocker, None) sql_text = 'test' mocked_sqlalchemy_text = mocker.patch('index.sqlalchemy.text') mocked_sqlalchemy_text.return_value = sql_text index.update_order_status(1, 'Y') assert mocked_session.execute.call_count == 1 assert mocked_session.execute.call_args[0] == ( sql_text, {'status': 'Y', 'order_id': 1}) assert mocked_sqlalchemy_text.call_args[0] == ( sql_queries.UPDATE_ENCODING_ORDER_STATUS,) def test_handler(mocker, encoding_order_dict, s3_dict, dynamo_dict): """Test main handler method.""" mocked_get_orders = mocker.patch('index.get_orders') mocked_get_orders.return_value = encoding_order_dict mocked_order_to_dynamo_dict = mocker.patch('index.order_to_dynamo_dict') mocked_order_to_dynamo_dict.return_value = dynamo_dict mocked_order_to_full_dict = mocker.patch('index.order_to_full_dict') mocked_order_to_full_dict.return_value = s3_dict mocked_put_to_s3 = mocker.patch('index.put_to_s3') mocked_put_to_dynamo = mocker.patch('index.put_to_dynamo') mocked_update_order_status = mocker.patch('index.update_order_status') index.handler(None, None) orders_count = len(encoding_order_dict) assert mocked_get_orders.call_count == 1 assert mocked_order_to_dynamo_dict.call_count == orders_count assert mocked_order_to_full_dict.call_count == orders_count assert mocked_put_to_s3.call_count == orders_count assert mocked_put_to_dynamo.call_count == orders_count assert mocked_update_order_status.call_count == orders_count