"""Tests for add_products_to_poll API endpoint.""" import json import flask from oto import response as oto_response from oto import status import pytest from availability.connectors import sql from availability.constants import error from availability.constants import stores from availability.models import product as product_model from availability.models import product_in_store as ps_model INVALID_VALUE_ERROR = "{value} is not of type '{type}'" MAX_ITUNES_VENDOR_ID_LENGTH = 25 # have to match DB field length @pytest.fixture def request_data(products_data): """Sample data for add_products_to_poll endpoint.""" return {'products': products_data} @pytest.mark.parametrize( 'string_attribute', ['upc', 'itunes_vendor_id']) def test_add_products_to_poll_validates_string_attributes( client, request_data, string_attribute): """Assert endpoint validates type of string attributes.""" not_string_value = -1 request_data['products'][0][string_attribute] = not_string_value response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') expected_error = INVALID_VALUE_ERROR.format( value=not_string_value, type='string') response_data = json.loads(response.data.decode()) assert response.status_code == 400 assert response_data['code'] == error.BODY_VALIDATION_ERROR assert expected_error in response_data['message'] @pytest.mark.parametrize( 'integer_attribute', ['orchard_product_id', 'product_id']) def test_add_products_to_poll_validates_integer_attributes( client, request_data, integer_attribute): """Assert endpoint validates type of integer attributes value.""" invalid_value = 'not integer' error_message = "'{value}' is not of type '{type}'" request_data['products'][0][integer_attribute] = invalid_value response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') response_data = json.loads(response.data.decode()) expected_error = error_message.format( value=invalid_value, type='number') assert response.status_code == 400 assert response_data['code'] == error.BODY_VALIDATION_ERROR assert expected_error in response_data['message'] def test_add_products_to_poll_validates_store_id(client, request_data): """Assert endpoint allows only certain values for store_id.""" invalid_value = 123456789098765431 error_message = '{value} is greater than the maximum of 1' request_data['products'][0]['store_id'] = invalid_value response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') response_data = json.loads(response.data.decode()) expected_error = error_message.format( value=invalid_value, type='number') assert response.status_code == 400 assert response_data['code'] == error.BODY_VALIDATION_ERROR assert expected_error in response_data['message'] def test_add_products_to_poll_validates_provider(client, request_data): """Assert endpoint validates type of provider value.""" invalid_value = 'invalid provider' error_message = "'{value}' does not match 'orchard'" request_data['products'][0]['provider'] = invalid_value response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') expected_error = error_message.format(value=invalid_value) response_data = json.loads(response.data.decode()) assert response.status_code == 400 assert response_data['code'] == error.BODY_VALIDATION_ERROR assert expected_error in response_data['message'] @pytest.mark.parametrize( 'date_attribute', ['delivery_date', 'sales_start_date']) def test_add_products_to_poll_validates_dates( client, request_data, date_attribute): """Assert endpoint validates type of date attributes.""" invalid_value = 'not date at all' request_data['products'][0][date_attribute] = invalid_value error_message = "'{value}' is not a 'date-time'".format( value=invalid_value) response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') response_data = json.loads(response.data.decode()) assert response.status_code == 400 assert response_data['code'] == error.BODY_VALIDATION_ERROR assert error_message in response_data['message'] def test_add_products_to_poll_validates_force_polling(client, request_data): """Assert endpoint validates type of force_polling value.""" not_bool_value = -1 request_data['products'][0]['force_polling'] = not_bool_value response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') expected_error = INVALID_VALUE_ERROR.format( value=not_bool_value, type='boolean') response_data = json.loads(response.data.decode()) assert response.status_code == 400 assert response_data['code'] == error.BODY_VALIDATION_ERROR assert expected_error in response_data['message'] @pytest.mark.parametrize( 'required_attribute', ['upc', 'product_id', 'orchard_product_id', 'store_id']) def test_add_products_to_poll_required_attributes( client, request_data, required_attribute): """Assert endpoint requires certain attributes.""" del request_data['products'][0][required_attribute] response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') response_data = json.loads(response.data.decode()) expected_error = "'{value}' is a required property".format( value=required_attribute) assert response.status_code == 400 assert response_data['code'] == error.BODY_VALIDATION_ERROR assert expected_error in response_data['message'] def test_add_products_to_poll_pass_data_to_the_logic_layer( client, request_data, mocker): """Assert valid data is passed to the logic layer.""" mock_polling = mocker.patch('availability.handlers.product_submission') mock_polling.submit_to_poll.return_value = oto_response.Response() response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert isinstance(response, flask.Response) mock_polling.submit_to_poll.assert_called_once_with( request_data['products']) def test_add_products_to_poll_returns_204_on_success( client, request_data, mocker): """Assert endpoint returns 204 status on successful submission.""" mock_polling = mocker.patch('availability.handlers.product_submission') mock_polling.submit_to_poll.return_value = oto_response.Response( status=204) response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert response.status_code == 204 def test_add_products_to_poll(client, request_data, test_database): """Assert can save product information.""" response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert isinstance(response, flask.Response) assert response.status_code == status.NO_CONTENT def test_add_products_to_poll_handles_duplicate_products( client, request_data, test_database): """Assert duplicate products are ignored.""" first_response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert first_response.status_code == status.NO_CONTENT duplicate_response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert duplicate_response.status_code == status.NO_CONTENT def test_add_products_to_poll_allows_submission_without_itunes_vendor_id( client, request_data, test_database): """Assert can submit products without itunes_vendor_id.""" for product in request_data['products']: del product['itunes_vendor_id'] response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert response.status_code == status.NO_CONTENT, response.data.decode() def test_add_products_to_poll_allows_submission_with_empty_itunes_vendor_id( client, request_data, test_database): """Assert can submit products without empty itunes_vendor_id.""" for product in request_data['products']: product['itunes_vendor_id'] = '' response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert response.status_code == status.NO_CONTENT, response.data.decode() @pytest.mark.parametrize( 'itunes_vendor_id, expected_status', ( ('1', status.NO_CONTENT), ('0123', status.NO_CONTENT), ('1' * MAX_ITUNES_VENDOR_ID_LENGTH, status.NO_CONTENT), ('1' * (MAX_ITUNES_VENDOR_ID_LENGTH + 1), status.BAD_REQUEST), ('a', status.BAD_REQUEST), ('a' * MAX_ITUNES_VENDOR_ID_LENGTH, status.BAD_REQUEST), ('a' * (MAX_ITUNES_VENDOR_ID_LENGTH + 1), status.BAD_REQUEST), ) ) def test_add_products_to_poll_numeric_itunes_vendor_id( client, request_data, test_database, itunes_vendor_id, expected_status): """Assert can submit products without numeric only itunes_vendor_id.""" del request_data['products'][1] request_data['products'][0]['itunes_vendor_id'] = itunes_vendor_id response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert response.status_code == expected_status, response.data.decode() def test_add_products_to_poll_sets_itunes_vendor_id_to_upc( client, request_data, test_database): """Assert itunes_vendor_id will be set to upc.""" for product in request_data['products']: del product['itunes_vendor_id'] response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert response.status_code == status.NO_CONTENT, response.data.decode() with sql.session_scope() as session: products = session.query(product_model.Product).all() for product in products: assert product.itunes_vendor_id == product.upc def test_add_products_to_poll_sets_itunes_vendor_id_correctly( client, request_data, test_database): """Assert itunes_vendor_id will be set if provided.""" expected_itunes_vendor_id = request_data['products'][0]['itunes_vendor_id'] response = client.post( '/admin/products', data=json.dumps(request_data), content_type='application/json') assert response.status_code == status.NO_CONTENT, response.data.decode() with sql.session_scope() as session: products = session.query(product_model.Product).all() for product in products: assert product.itunes_vendor_id == expected_itunes_vendor_id def test_add_products_to_poll_for_deezer_store( test_database, products_data, client): """Assert can create product with Deezer store_id.""" del products_data[0] deezer_product = products_data[0] deezer_product['store_id'] = stores.STORE_ID_DEEZER response = client.post( '/admin/products', data=json.dumps({'products': [deezer_product]}), content_type='application/json') assert response.status_code == status.NO_CONTENT, response.data.decode() with sql.session_scope() as session: product = session.query(ps_model.ProductInStore).first() assert product.store_id == stores.STORE_ID_DEEZER