"""Tests for SQS queue producer functions.""" import json from botocore import exceptions as botocore_exceptions from flexmock import flexmock from oto import response from oto import status as oto_status from availability.connectors.sqs import JSONMessageExt from availability.constants import field_const from availability.constants import models from availability.constants import stores from availability.logic.queue import producer from tests.utils import mock_availability_queue def test_convert_products_to_sqs_messages(): """Assert product_in_store dicts correctly converted to messages.""" products = [{'product_in_store_id': 1}] with mock_availability_queue() as queue: response = producer.convert_products_to_sqs_messages( queue, products, correlation_id='test') response = list(response) message_body = json.dumps(products[0]) expected_message_attrs = { 'Correlation-Id': {'DataType': 'String', 'StringValue': 'test'}} expected_product_in_store_id = 1 assert len(response) == 1 assert response[0][0]['message_body'] == message_body assert response[0][0]['message_attributes'] == expected_message_attrs assert response[0][1] == expected_product_in_store_id def test_put_products_to_poll_messages_to_queue_success(test_database): """Test put_products_to_poll_messages_to_queue puts messages.""" payloads = ['a', 'b', 'c'] products = [{'product_in_store_id': 1}] test_id = 42 correlation_id = '123' with mock_availability_queue(stores.STORE_ID_ITUNES) as queue: products_to_poll_with_countries = iter(payloads) message_body = json.dumps(products[0]) messages_with_product_ids = [ ({ 'message_body': message_body, 'message_attributes': { 'Correlation-Id': { 'StringValue': 'test', 'DataType': 'String' } } }, 42), ({ 'message_body': message_body, 'message_attributes': { 'Correlation-Id': { 'StringValue': 'test', 'DataType': 'String' } } }, 42), ({ 'message_body': message_body, 'message_attributes': { 'Correlation-Id': { 'StringValue': 'test', 'DataType': 'String' } } }, 42)] num_messages = len(payloads) (flexmock(producer) .should_receive('set_product_countries_list') .and_return(products_to_poll_with_countries)) (flexmock(producer) .should_receive('convert_products_to_sqs_messages') .with_args(queue, products_to_poll_with_countries, correlation_id) .and_return(messages_with_product_ids)) (flexmock(producer.task) .should_call('change_status') .with_args(test_id, models.TASK_STATUS_IN_QUEUE) .times(num_messages)) result = producer.put_products_to_poll_messages_to_queue( store_id=stores.STORE_ID_ITUNES, correlation_id=correlation_id) received_messages = queue.receive_messages( MaxNumberOfMessages=num_messages) assert result assert len(received_messages) == num_messages for msg in received_messages: msg = JSONMessageExt(msg) assert msg.get_body() def test_put_products_to_poll_messages_to_queue_error(test_database): """Test put_products_to_poll_messages_to_queue handles Client error.""" success_product_id = 1 error_product_id = 2 (flexmock(producer.logger) .should_receive('error') .once()) (flexmock(producer.task) .should_receive('change_status') .with_args(success_product_id, models.TASK_STATUS_IN_QUEUE) .once()) (flexmock(producer.task) .should_receive('change_status') .with_args(error_product_id, models.TASK_STATUS_IN_QUEUE) .never()) with mock_availability_queue(stores.STORE_ID_ITUNES) as queue: msg = { 'message_body': 'eyJwcm9kdWN0X2luX3N0b3JlX2lkIjogMX0=', 'message_attributes': { 'Correlation-Id': { 'StringValue': 'test', 'DataType': 'String' } } } (flexmock(producer) .should_receive('convert_products_to_sqs_messages') .and_return([(msg, success_product_id), (msg, error_product_id)])) error_response_code = 'fake-error-response-code' error_response = {'Error': {'Code': error_response_code}} operation_name = 'fake-write-operation' (flexmock(queue) .should_receive('send_message') .and_return(None) # Write the element, when called first time. .and_raise(botocore_exceptions.ClientError( # Raise on the next call. error_response, operation_name))) assert producer.put_products_to_poll_messages_to_queue( store_id=stores.STORE_ID_ITUNES, correlation_id='123') def test_put_products_to_poll_messages_to_queue_handles_sqs_error(mocker): """Assert can handle case when SQS not available.""" mocker.patch( 'availability.logic.queue.producer.utils.get_availability_queue', side_effect=ValueError) response = producer.put_products_to_poll_messages_to_queue( store_id=1, correlation_id='1') assert response.status == oto_status.BAD_REQUEST def test_set_product_countries_list(mocker): """Test that generator updates country list.""" products = [ { field_const.UPC: 'upc1', field_const.COUNTRIES: ['foo'], field_const.STORE_ID: 1 }, { field_const.UPC: 'upc2', field_const.COUNTRIES: ['bar'], field_const.STORE_ID: 286 }] correlation_id = 'correlation id' mock_expected_countries = mocker.patch( 'availability.logic.queue.producer.countries.get_expected_countries', return_value=response.Response(['baz'])) result = producer.set_product_countries_list(products, correlation_id) result = list(result) assert len(result) == len(products) call_args_list = [ (( p[field_const.UPC], p[field_const.COUNTRIES], p[field_const.STORE_ID], correlation_id),) for p in products] assert mock_expected_countries.call_args_list == call_args_list for product, original in zip(result, products): assert product == dict(original, countries=['baz']) def test_set_product_countries_list_handle_error(mocker): """Test that generator logs exception and skips the problematic record.""" products = [ { field_const.UPC: 'upc1', field_const.COUNTRIES: ['foo'], field_const.STORE_ID: 1 }, { field_const.UPC: 'upc2', field_const.COUNTRIES: ['bar'], field_const.STORE_ID: 286 }] correlation_id = 'correlation id' error_response = response.create_fatal_response('Boom!') mocker.patch( 'availability.logic.queue.producer.countries.get_expected_countries', side_effect=(response.Response(['foo']), error_response)) mock_sentry_capture_message = mocker.patch( 'availability.logic.queue.producer.capture_message' ) result = producer.set_product_countries_list(products, correlation_id) context = { 'correlation_id': 'correlation id', 'errors': {'code': 'internal_error', 'message': 'Boom!'}, 'product': {'upc': 'upc2', 'countries': ['bar'], 'store_id': 286} } assert len(list(result)) == len(products) - 1 mock_sentry_capture_message.assert_called_once_with( 'Error getting expected countries for a product: ' + str(context) )