"""Functional tests for post product handler.""" import json from unittest.mock import MagicMock from oto import response from owsrequest import flask_request from owsrequest import request from owsrequest import test_utils import pytest from product_digital import api from product_digital.constants import error from product_digital.constants import header from product_digital.constants import services from product_digital.logic import product from product_digital.models import release from product_digital.validation import\ audio_product as audio_product_validation from tests.testutils import db def _head_spec_happy_path(headers, product_data): """Head specs for the happy path. Args: headers (dict): dictionary representing header data. product_data (dict): dictionary representing product_data Returns: dict: a specification dictionary to be used with ows-request test_utils """ account_type = headers.get(header.GRASS_ACCOUNT_TYPE) if account_type is None: account_type = product_data.get('account_type') account_id = headers.get(header.GRASS_ACCOUNT_ID) if account_id is None: account_id = product_data.get('account_id') return [{ 'service': services.OWS_PROJECT_MANAGER, 'path': '/ownership/{}/{}/project/{}'.format( account_type, account_id, product_data.get('project_id')), 'status': 200 }, { 'service': 'ows-product', 'path': '/{}/{}/product_code/{}'.format( account_type, account_id, product_data.get('product_code')), 'status': 404 }, { 'service': 'ows-product', 'path': '/upc/available/{}'.format( product_data.get('upc')), 'status': 200}] @pytest.fixture def get_project_data(): """Return expected response from project get project method.""" return { 'artist_id': 178508, 'project_name': 'this project', 'project_code': 'ABC123', 'project_id': 12345, 'vendor_id': 9999, 'subaccount_id': 0, } @pytest.fixture def mock_head_200(): """Return MagicMock with status of 200.""" return MagicMock(status_code=200) @pytest.fixture def mock_head_403(): """Return MagicMock with status of 403.""" return MagicMock(status_code=403) @pytest.fixture def mock_head_404(): """Return MagicMock with status of 404.""" return MagicMock(status_code=404) @pytest.fixture def mock_500(): """Return MagicMock with status of 500.""" return MagicMock(status_code=500) @pytest.fixture def mock_get_project_200(get_project_data): """Return MagicMock with status of 200 and expected get_project json.""" return MagicMock(status_code=200, json=MagicMock(return_value=get_project_data)) @pytest.fixture def mock_get_upc_200(): """Return response.Response with status of 200 and expected upc json.""" return response.Response(message='123456789015') @db.test_schema def test_post_product_as_microservice_upc_available( expected_create_product_response_message, client, valid_headers, base_product_data_with_vendor, mocker, mock_get_project_200): """Test result when posting as a microservice and UPC available. Test result when ownership, upc_available, and get_project all succeed. """ mocker.patch.object(request, 'head', test_utils.mock_ows_requests( _head_spec_happy_path(valid_headers, base_product_data_with_vendor))) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 201 assert response_json == expected_create_product_response_message @db.test_schema def test_post_product_as_microservice_upc_available_version_null( expected_create_product_response_message, client, valid_headers, base_product_data_with_vendor, mocker, mock_get_project_200): """Test result when posting as a microservice and UPC available. Test result when ownership, upc_available, and get_project all succeed. """ mocker.patch.object(request, 'head', test_utils.mock_ows_requests( _head_spec_happy_path(valid_headers, base_product_data_with_vendor))) mocker.patch.object(request, 'get', return_value=mock_get_project_200) base_product_data_with_vendor['version'] = None expected_create_product_response_message['version'] = None client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 201 assert response_json == expected_create_product_response_message def test_post_product_as_microservice_upc_not_available( client, valid_headers, base_product_data_with_vendor, mocker, upc_not_available, mock_get_project_200): """Test result when posting as a microservice and UPC unavailable. Test result when ownership and get_project succeed, upc_available fails. """ head_spec = _head_spec_happy_path( valid_headers, base_product_data_with_vendor) head_spec[2]['status'] = 403 mocker.patch.object(request, 'head', test_utils.mock_ows_requests( head_spec)) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) response_json = json.loads(client_response.data.decode('utf-8')) response_message = response_json.get('message') assert client_response.status_code == 400 assert response_message == upc_not_available def test_post_product_as_microservice_product_code_not_unique( client, valid_headers, base_product_data_with_vendor, mocker, product_code_not_available, mock_get_project_200): """Test result when posting as a microservice and product_code taken.""" head_spec = _head_spec_happy_path( valid_headers, base_product_data_with_vendor) head_spec[1]['status'] = 200 mocker.patch.object(request, 'head', test_utils.mock_ows_requests( head_spec)) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) response_json = json.loads(client_response.data.decode('utf-8')) response_message = response_json.get('message') assert client_response.status_code == 400 assert response_message == product_code_not_available @db.test_schema def test_post_product_with_no_product_code( client, valid_headers, base_product_data_with_vendor, mocker, product_code_not_available, mock_get_project_200): """Test result when posting as a microservice and product_code omitted.""" account_type = 'vendor' account_id = base_product_data_with_vendor.get('account_id') upc = base_product_data_with_vendor.get('upc') project_id = base_product_data_with_vendor.get('project_id') del (base_product_data_with_vendor['product_code']) head_specs = [{ 'service': services.OWS_PROJECT_MANAGER, 'path': '/ownership/{}/{}/project/{}'.format( account_type, account_id, project_id), 'status': 200 }, { 'service': 'ows-product', 'path': '/upc/available/{}'.format(upc), 'status': 200}] mocker.patch.object(request, 'head', test_utils.mock_ows_requests( head_specs)) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) assert client_response.status_code == 201 def test_post_product_as_microservice_product_code_and_upc_validation_fail( client, valid_headers, base_product_data_with_vendor, mocker, product_code_not_available, upc_not_available, mock_get_project_200): """Test result when posting as microservice, upc and product_code taken.""" head_spec = _head_spec_happy_path( valid_headers, base_product_data_with_vendor) head_spec[1]['status'] = 200 head_spec[2]['status'] = 403 mocker.patch.object(request, 'head', test_utils.mock_ows_requests( head_spec)) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) response_json = json.loads(client_response.data.decode('utf-8')) response_message = response_json.get('message') expected_message = product_code_not_available.copy() expected_message.update(upc_not_available) assert client_response.status_code == 400 assert response_message == expected_message @db.test_schema def test_post_product_as_microservice_upc_not_a_number( client, valid_headers, base_product_data_with_vendor, mocker, upc_not_available, mock_get_project_200): """Test result when posting as a microservice and UPC unavailable. Test result when ownership and get_project succeed, upc_available fails. """ base_product_data_with_vendor['upc'] = 'DEFGHIJKLMNO' head_spec = _head_spec_happy_path( valid_headers, base_product_data_with_vendor) head_spec[2]['status'] = 404 mocker.patch.object(request, 'head', test_utils.mock_ows_requests( head_spec)) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) response_json = json.loads(client_response.data.decode('utf-8')) response_message = response_json.get('message') assert client_response.status_code == 400 assert response_message == { 'upc': { 'validator': 'used', 'validator_value': True, 'message': error.ERROR_MESSAGE_UPC_NOT_VALID, 'error_code': 'upc_used', } } def test_post_product_as_microservice_upc_fatal_error_returned( client, valid_headers, base_product_data_with_vendor, mocker, mock_get_project_200): """Test result when posting as a microservice when UPC returns fatal. Test result when ownership and get_project succeed, upc_available fails. """ head_spec = _head_spec_happy_path( valid_headers, base_product_data_with_vendor) head_spec[2]['status'] = 500 mocker.patch.object(request, 'head', test_utils.mock_ows_requests( head_spec)) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) assert client_response.status_code == 500 def test_post_product_as_microservice_product_code_fatal_error_returned( client, valid_headers, base_product_data_with_vendor, mocker, mock_get_project_200): """Test result when posting as a microservice when product_code is fatal. Test result when ownership and get_project succeed, upc_available fails. """ head_spec = _head_spec_happy_path( valid_headers, base_product_data_with_vendor) head_spec[1]['status'] = 500 mocker.patch.object(request, 'head', test_utils.mock_ows_requests( head_spec)) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) assert client_response.status_code == 500 @db.test_schema def test_post_product_as_microservice_without_upc( expected_create_product_response_message, client, valid_headers, base_product_data_with_vendor, mocker, mock_get_project_200): """Test successful result when posting as a microservice without upc.""" mocker.patch.object(request, 'head', test_utils.mock_ows_requests( _head_spec_happy_path(valid_headers, base_product_data_with_vendor))) mocker.patch.object(request, 'get', return_value=mock_get_project_200) del base_product_data_with_vendor['upc'] mocker.patch.object( product, '_return_upc_value_and_update_status', return_value='123456789015') expected_create_product_response_message['upc'] = '123456789015' client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 201 assert response_json == expected_create_product_response_message @db.test_schema def test_post_product_as_vendor( expected_create_product_response_message, client, base_product_data, valid_headers_for_vendor, mocker, mock_get_project_200): """Test successful result when posting as a vendor.""" mocker.patch.object(request, 'head', test_utils.mock_ows_requests( _head_spec_happy_path(valid_headers_for_vendor, base_product_data))) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 201 assert response_json == expected_create_product_response_message @db.test_schema def test_post_product_as_vendor_without_upc( expected_create_product_response_message, client, base_product_data, valid_headers_for_vendor, mocker, mock_get_project_200): """Test successful result when posting as a vendor without a upc.""" del base_product_data['upc'] mocker.patch.object(request, 'head', test_utils.mock_ows_requests( _head_spec_happy_path(valid_headers_for_vendor, base_product_data))) mocker.patch.object(request, 'get', return_value=mock_get_project_200) mocker.patch.object( product, '_return_upc_value_and_update_status', return_value='123456789015') expected_create_product_response_message['upc'] = '123456789015' client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 201 assert response_json == expected_create_product_response_message @db.test_schema def test_post_product_as_vendor_with_upc_as_empty_string( expected_create_product_response_message, client, base_product_data, account_id, mocker, valid_headers_for_vendor, mock_get_project_200): """Test successful result when posting as a vendor with a blank upc.""" base_product_data['upc'] = '' mocker.patch.object(request, 'head', test_utils.mock_ows_requests( _head_spec_happy_path(valid_headers_for_vendor, base_product_data))) mocker.patch.object(request, 'get', return_value=mock_get_project_200) mocker.patch.object( product, '_return_upc_value_and_update_status', return_value='123456789015') expected_create_product_response_message['upc'] = '123456789015' client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 201 assert response_json == expected_create_product_response_message @db.test_schema def test_post_product_as_subaccount( expected_create_product_response_message, client, base_product_data, valid_headers_for_subaccount, mocker, mock_get_project_200): """Test successful result when posting as a subaccount.""" mocker.patch.object( request, 'head', test_utils.mock_ows_requests(_head_spec_happy_path( valid_headers_for_subaccount, base_product_data))) mocker.patch.object(request, 'get', return_value=mock_get_project_200) client_response = client.post( '/product/audio', headers=valid_headers_for_subaccount, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 201 assert response_json == expected_create_product_response_message def test_post_product_as_wrong_vendor( valid_headers_for_vendor, base_product_data, client, mocker, mock_head_403): """Test authorization error returned when posting as wrong vendor.""" mocker.patch.object(request, 'head', return_value=mock_head_403) client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 403 assert response_json.get('message') == error.ERROR_MESSAGE_NOT_OWNER def test_post_product_as_wrong_subaccount( valid_headers_for_subaccount, client, base_product_data, mocker, mock_head_403): """Test authorization errror returned when posting as wrong subaccount.""" mocker.patch.object(request, 'head', return_value=mock_head_403) client_response = client.post( '/product/audio', headers=valid_headers_for_subaccount, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 403 assert response_json.get('message') == error.ERROR_MESSAGE_NOT_OWNER def test_post_product_when_ownership_fail( valid_headers_for_subaccount, client, base_product_data, mocker, mock_500): """Test 500 response when ownership check returns fatal response.""" mocker.patch.object(request, 'head', return_value=mock_500) client_response = client.post( '/product/audio', headers=valid_headers_for_subaccount, data=json.dumps(base_product_data)) assert client_response.status_code == 500 def test_post_product_as_microservice_when_project_not_found( mocker, mock_head_200, base_product_data_with_vendor, client, valid_headers): """Test error result when project not found from project microservice.""" mocker.patch.object(request, 'head', return_value=mock_head_200) mocker.patch.object( request, 'get', return_value=MagicMock(status_code=404)) mocker.patch.object( audio_product_validation, 'validate_product_on_create', return_value=mock_head_200 ) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 400 assert response_json.get('message') == 'Invalid project id' def test_post_product_as_microservice_when_project_fatal_response( mocker, base_product_data_with_vendor, client, valid_headers, mock_head_200, mock_500): """Test error result when error result from project microservice.""" mocker.patch.object(request, 'head', return_value=mock_head_200) mocker.patch.object(request, 'get', return_value=mock_500) mocker.patch.object( audio_product_validation, 'validate_product_on_create', return_value=mock_head_200 ) client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) assert client_response.status_code == 500 def test_post_product_with_no_account_info( client, valid_headers, base_product_data): """Test missing account info error when no account info included.""" client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 400 assert response_json.get('message') == error.ERROR_MESSAGE_NO_ACCOUNT_GIVEN def test_post_product_with_incomplete_grass_headers( client, valid_headers, base_product_data, account_id): """Test error when one of two grass headers missing.""" valid_headers[header.GRASS_ACCOUNT_ID] = account_id client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 400 assert response_json.get('message') == \ error.ERROR_MESSAGE_INCOMPLETE_GRASS_HEADERS def test_post_product_with_invalid_account_type( client, valid_headers_for_vendor, base_product_data, account_id): """Test error when invalid grass account type included.""" valid_headers_for_vendor[header.GRASS_ACCOUNT_TYPE] = 'invalid' client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) message = response_json.get('message') assert client_response.status_code == 400 assert message['Grass-Account-Type']['message'] == \ "'invalid' is not one of ['vendor', 'subaccount']" def test_post_product_as_vendor_with_missing_properties( client, valid_headers_for_vendor, base_product_data, account_id): """Test validation error when missing required properties.""" del base_product_data['project_id'] client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) message = response_json.get('message') assert client_response.status_code == 400 assert message['project_id']['message'] == \ "'project_id' is a required property" def test_post_product_as_vendor_when_upc_too_long( client, valid_headers_for_vendor, base_product_data, account_id): """Test validation error when missing required properties.""" base_product_data['upc'] = '0123456789012345' client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) message = response_json.get('message') assert client_response.status_code == 400 assert message['upc']['message'] == "'0123456789012345' is too long" def test_post_product_as_vendor_when_upc_too_short( client, valid_headers_for_vendor, base_product_data, account_id): """Test validation error when missing required properties.""" base_product_data['upc'] = '0123' client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) message = response_json.get('message') assert client_response.status_code == 400 assert message['upc']['message'] == "'0123' is too short" @db.test_schema def test_post_product_as_vendor_with_additional_properties( client, valid_headers_for_vendor, base_product_data, account_id, mocker, mock_get_project_200): """Test Additional properties included are not saved with project data.""" mocker.patch.object( request, 'head', test_utils.mock_ows_requests(_head_spec_happy_path( valid_headers_for_vendor, base_product_data))) mocker.patch.object(request, 'get', return_value=mock_get_project_200) base_product_data['project_facial_hair'] = 'mustachioed' client_response = client.post( '/product/audio', headers=valid_headers_for_vendor, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) assert client_response.status_code == 201 assert 'project_facial_hair' not in response_json def test_post_product_as_microservice_when_get_upc_fail( mocker, base_product_data_with_vendor, client, valid_headers, mock_get_project_200): """Test fail result when posting as a microservice upc fail.""" head_specs = [{ 'service': services.OWS_PROJECT_MANAGER, 'path': '/ownership/vendor/12345/project/12345', 'status': 200 }] mocker.patch.object( request, 'head', test_utils.mock_ows_requests(head_specs)) mocker.patch.object(request, 'get', return_value=mock_get_project_200) mocker.patch.object( release, 'get_upc', return_value=response.create_fatal_response( message='mysql error')) del base_product_data_with_vendor['upc'] client_response = client.post( '/product/audio', headers=valid_headers, data=json.dumps(base_product_data_with_vendor)) assert client_response.status_code == 500 def test_post_product_with_vendor_id_and_oa_grass_headers( mocker, test_request_context, client, base_product_data, valid_oa_headers_with_orchard_user_id): """Test successful result when posting as a OA account with vendor details.""" mocker.patch.object(flask_request, 'verify_grass_headers', MagicMock(wraps=response.Response) ) mocker.patch.object(product, 'create', MagicMock(return_value=response.Response(status=201)) ) base_product_data['account_id'] = 5000 with api.app.test_request_context(): client_response = client.post( '/product/audio', headers=valid_oa_headers_with_orchard_user_id, data=json.dumps(base_product_data)) product.create.assert_called_once() product_data = product.create.call_args[1]['product_data'] assert product_data['account_id'] == base_product_data['account_id'] assert product_data['account_type'] == header.GRASS_ACCOUNT_TYPE_VENDOR assert client_response.status_code == 201 def test_post_product_with_subaccount_id_and_oa_grass_headers( mocker, test_request_context, client, base_product_data, valid_oa_headers_with_orchard_user_id): """Test successful result when posting as a OA account with subaccount details.""" mocker.patch.object(flask_request, 'verify_grass_headers', MagicMock(wraps=response.Response) ) mocker.patch.object(product, 'create', MagicMock(return_value=response.Response(status=201)) ) base_product_data['subaccount_id'] = 5001 base_product_data['account_id'] = 5000 with api.app.test_request_context(): client_response = client.post( '/product/audio', headers=valid_oa_headers_with_orchard_user_id, data=json.dumps(base_product_data)) product.create.assert_called_once() product_data = product.create.call_args[1]['product_data'] assert product_data['account_id'] == base_product_data['subaccount_id'] assert product_data['account_type'] == header.GRASS_ACCOUNT_TYPE_SUBACCOUNT assert client_response.status_code == 201 def test_post_product_with_only_oa_grass_headers( mocker, test_request_context, client, base_product_data, valid_oa_headers_with_orchard_user_id): """Test unsuccessful result when posting as a OA account without account details.""" mocker.patch.object(flask_request, 'verify_grass_headers', MagicMock(wraps=response.Response) ) mocker.patch.object(product, 'create', MagicMock(wraps=product.create) ) with api.app.test_request_context(): client_response = client.post( '/product/audio', headers=valid_oa_headers_with_orchard_user_id, data=json.dumps(base_product_data)) response_json = json.loads(client_response.data.decode('utf-8')) product.create.assert_called_once() assert client_response.status_code == 400 assert response_json['message'] == error.ERROR_MESSAGE_NO_ACCOUNT_GIVEN assert response_json['code'] == error.VALIDATION_ERROR