"""Post Project Functional Test.""" import json from unittest.mock import patch import uuid from owsrequest import request from owsrequest import test_utils import pytest from project_manager.constant import error_const from project_manager.constant import field_const from project_manager.constant import header_const from project_manager.constant.field_const import VENDOR_ID from project_manager.entity.project_post_request import ProjectPostRequest from tests.common_assert import assert_header from tests.utils.project_code import random_project_code @pytest.fixture def not_distributor(): """ows-request spec for a "not distributor" ows-account response.""" return { 'service': 'ows-account', 'path': '/7123/distributor', 'status': 204, 'json': None} def assert_response(header, post_dict, res_json, subaccount_id=1): """Assert response is equal to expected.""" expected_subset = { field_const.CORRELATION_ID_DB: header[header_const.CORRELATION_ID], field_const.PROJECT_CODE: post_dict[field_const.PROJECT_CODE], field_const.PROJECT_NAME: post_dict[field_const.PROJECT_NAME], field_const.SUBACCOUNT_ID: subaccount_id, field_const.VENDOR_ID: 7123} for key, expected_value in expected_subset.items(): assert res_json[key] == expected_value for key in ( field_const.PROJECT_ID, field_const.CREATED_DATE_UTC, field_const.UPDATED_DATE_UTC): assert key in res_json def assert_invalid(res, res_json, missing_field): """Assert response is invalid.""" assert res.status_code == 400 assert res_json[error_const.ERROR_CODE] == error_const.VALIDATION_ERROR for field in missing_field: assert field in res_json[error_const.ERROR_DETAIL] def test_post_project_success_subaccount( client, db_fixture, header_subaccount, get_valid_post_dict, monkeypatch): """Assert that successful POST for subaccount returns 201 with body.""" header_subaccount[header_const.ORCHARD_USER_ID] = 'alw:123' get_specs = [{ 'service': 'ows-account', 'path': '/subaccount/1', 'status': 200, 'json': {'vendor_id': 7123}}] monkeypatch.setattr( request, 'get', test_utils.mock_ows_requests(get_specs)) post_dict = get_valid_post_dict res = client.post( '/project', headers=header_subaccount, data=json.dumps(post_dict)) assert_header(header_subaccount, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 201 assert_response(header_subaccount, post_dict, res_json) @patch( 'project_manager.models.ows_marketing.create_project_highlights') def test_post_project_success_subaccount_with_project_highlights( mock_create_project_highlights, db_fixture, subaccount, client, header_subaccount, monkeypatch, get_valid_post_dict): """Assert that successful POST returns expected response for subaccount.""" header_subaccount[header_const.ORCHARD_USER_ID] = 'alw:123' get_specs = [{ 'service': 'ows-account', 'path': '/subaccount/1', 'status': 200, 'json': {'vendor_id': 7123}}] monkeypatch.setattr( request, 'get', test_utils.mock_ows_requests(get_specs)) project_highlights = 'some project highlight' post_dict = get_valid_post_dict post_dict[field_const.PROJECT_HIGHLIGHTS] = project_highlights res = client.post( '/project', headers=header_subaccount, data=json.dumps(post_dict)) res_json = json.loads(res.data.decode('utf-8')) mock_create_project_highlights.assert_called_once_with( 1, project_highlights) assert_header(header_subaccount, res.headers) assert res.status_code == 201 assert_response(header_subaccount, post_dict, res_json) def test_post_project_subaccountid_with_no_vendorid( client, header_subaccount, monkeypatch, get_valid_post_dict): """Assert error with not found vendor_id for given subaccount_id.""" header_subaccount[header_const.ORCHARD_USER_ID] = 'alw:123' subaccount_id_with_no_vendor_id = 854994669728 get_specs = [{ 'service': 'ows-account', 'path': '/subaccount/{}'.format(subaccount_id_with_no_vendor_id), 'status': 404, 'json': None}] monkeypatch.setattr( request, 'get', test_utils.mock_ows_requests(get_specs)) post_dict = get_valid_post_dict subaccount_id_with_no_vendor_id = 854994669728 header_subaccount[header_const.GRASS_ACCOUNT_ID] = \ subaccount_id_with_no_vendor_id res = client.post( '/project', headers=header_subaccount, data=json.dumps(post_dict)) assert_header(header_subaccount, res.headers) res_json = json.loads(res.data.decode('utf-8')) expected_error_detail =\ "Unable to find vendor_id for subaccount_id '{}'" expected_error_detail = expected_error_detail.format( subaccount_id_with_no_vendor_id) assert_invalid(res, res_json, expected_error_detail) def test_post_project_success_distributor( db_fixture, client, header, monkeypatch): """Assert that successful POST returns correct response for distributor.""" header[header_const.ORCHARD_USER_ID] = 'alw:123' vendor_subaccount = { 'service': 'ows-account', 'path': '/7123/subaccount/2', 'status': 200, 'json': {'vendor_id': 7123}} is_distributor = { 'service': 'ows-account', 'path': '/7123/distributor', 'status': 200, 'json': None} head_specs = [vendor_subaccount, is_distributor] monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests(head_specs)) post_dict = { field_const.PROJECT_CODE: random_project_code(), field_const.PROJECT_NAME: str(uuid.uuid4()), field_const.SUBACCOUNT_ID: 2, field_const.ARTIST_ID: 242131} res = client.post( '/project', headers=header, data=json.dumps(post_dict)) assert_header(header, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 201 assert_response( header, post_dict, res_json, post_dict[field_const.SUBACCOUNT_ID]) assert res_json['project_highlights'] is None def test_post_project_distributor_unauthorized_for_subaccount( db_fixture, client, header, monkeypatch): """Error is returned if distributor posts to an unauthorized subaccount.""" header[header_const.ORCHARD_USER_ID] = 'alw:123' not_subaccount_owner = { 'service': 'ows-account', 'path': '/7123/subaccount/2', 'status': 403, 'json': None} is_distributor_though = { 'service': 'ows-account', 'path': '/7123/distributor', 'status': 200, 'json': None} head_specs = [not_subaccount_owner, is_distributor_though] monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests(head_specs)) post_dict = { field_const.PROJECT_CODE: random_project_code(), field_const.PROJECT_NAME: str(uuid.uuid4()), field_const.SUBACCOUNT_ID: 2, field_const.ARTIST_ID: 242131} res = client.post( '/project', headers=header, data=json.dumps(post_dict)) assert_header(header, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 403 assert error_const.AUTHORIZATION_ERROR ==\ res_json[error_const.ERROR_CODE] assert 'subaccount does not belong to vendor' ==\ res_json[error_const.ERROR_DETAIL] def test_post_project_distributor_missing_subaccount( db_fixture, client, header, monkeypatch): """Error returned if posting project for distributor missing subaccount.""" header[header_const.ORCHARD_USER_ID] = 'alw:123' head_specs = [{ 'service': 'ows-account', 'path': '/7123/distributor', 'status': 200, 'json': None}] monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests(head_specs)) post_dict = { field_const.PROJECT_CODE: random_project_code(), field_const.PROJECT_NAME: str(uuid.uuid4()), field_const.ARTIST_ID: 242131} res = client.post( '/project', headers=header, data=json.dumps(post_dict)) assert_header(header, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 400 assert error_const.VALIDATION_ERROR == res_json[error_const.ERROR_CODE] assert error_const.ERROR_MSG_DISTRIBUTOR_MISSING_SUBACCOUNT ==\ res_json[error_const.ERROR_DETAIL] def test_post_project_orchard_admin_account_distributor_success( client, db_fixture, header_user_oa, get_valid_post_dict, monkeypatch, ): """Posting a project as an Orchard Admin returns expected response.""" head_specs = [ { 'service': 'ows-account', 'path': '/7123/distributor', 'status': 200, 'json': None, }, { 'service': 'ows-account', 'path': '/7123/subaccount/1', 'status': 200, 'json': None, }, ] monkeypatch.setattr(request, 'head', test_utils.mock_ows_requests( head_specs)) # Add subaccount id to POST body. post_dict = get_valid_post_dict post_dict[field_const.VENDOR_ID] = 7123 post_dict[field_const.SUBACCOUNT_ID] = 1 data = json.dumps(post_dict) res = client.post('/project', headers=header_user_oa, data=data) assert_header(header_user_oa, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 201 assert_response(header_user_oa, post_dict, res_json) def test_post_project_orchard_admin_account_subaccount(client, db_fixture, header_user_oa, get_valid_post_dict, monkeypatch): """Posting a project as an OA with subaccount returns expected response.""" head_specs = [ { 'service': 'ows-account', 'path': '/1/distributor', 'status': 200, 'json': None, }, { 'service': 'ows-account', 'path': '/7123/subaccount/1', 'status': 200, 'json': None, }, ] monkeypatch.setattr(request, 'head', test_utils.mock_ows_requests( head_specs)) get_specs = [{ 'service': 'ows-account', 'path': '/subaccount/1', 'status': 200, 'json': {VENDOR_ID: 7123}, }] monkeypatch.setattr(request, 'get', test_utils.mock_ows_requests( get_specs)) # Add subaccount id to POST body. post_dict = get_valid_post_dict post_dict[field_const.SUBACCOUNT_ID] = 1 data = json.dumps(post_dict) res = client.post('/project', headers=header_user_oa, data=data) assert_header(header_user_oa, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 201 assert_response(header_user_oa, post_dict, res_json) def test_post_project_distributor_ows_account_system_error( db_fixture, client, header, monkeypatch): """Posting a project for distributor when an ows-account system errors.""" header[header_const.ORCHARD_USER_ID] = 'alw:123' head_specs = [{ 'service': 'ows-account', 'path': '/7123/distributor', 'status': 500, 'json': {'foo': 'bar'}}] monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests(head_specs)) post_dict = { field_const.PROJECT_CODE: random_project_code(), field_const.PROJECT_NAME: str(uuid.uuid4()), field_const.ARTIST_ID: 242131} res = client.post( '/project', headers=header, data=json.dumps(post_dict)) assert_header(header, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 500 assert error_const.ERROR_CODE_INTERNAL_ERROR ==\ res_json[error_const.ERROR_CODE] assert 'ows-account returned unexpected status_code of 500' ==\ res_json[error_const.ERROR_DETAIL] def test_post_project_nondistributor_including_subaccount_error( db_fixture, client, header, monkeypatch, not_distributor): """Assert error is if nondistributor tries to post subaccount.""" header[header_const.ORCHARD_USER_ID] = 'alw:123' not_for_vendor_either = { 'service': 'ows-account', 'path': '/7123/distributor', 'status': 204, 'json': None} head_specs = [not_distributor, not_for_vendor_either] monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests(head_specs)) post_dict = { field_const.PROJECT_CODE: random_project_code(), field_const.PROJECT_NAME: str(uuid.uuid4()), field_const.SUBACCOUNT_ID: 2, field_const.ARTIST_ID: 242131} res = client.post( '/project', headers=header, data=json.dumps(post_dict)) assert_header(header, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 400 assert error_const.VALIDATION_ERROR ==\ res_json[error_const.ERROR_CODE] assert 'non-distributor cannot create project for subaccount' ==\ res_json[error_const.ERROR_DETAIL] def test_post_project_success_vendor( db_fixture, client, header, monkeypatch, get_valid_post_dict, not_distributor): """Assert that successful POST returns expected response for label.""" header[header_const.ORCHARD_USER_ID] = 'alw:123' monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests([not_distributor])) post_dict = get_valid_post_dict res = client.post( '/project', headers=header, data=json.dumps(post_dict)) assert_header(header, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 201 assert_response( header, post_dict, res_json, ProjectPostRequest.SUBACCOUNT_ID_NULL) assert res_json['project_highlights'] is None def test_post_invalid_header(client, invalid_headers): """Assert that service returns error for various invalid headers.""" res = client.post('/project', headers=invalid_headers.get('header')) res_json = json.loads(res.data.decode('utf-8')) message = res_json.get('message').get(invalid_headers.get('field')) assert message.find(invalid_headers.get('reason')) def test_post_invalid_json_field(client, header, invalid_json_request): """Assert that service returns error for various invalid requests.""" res = client.post( '/project', data=json.dumps(invalid_json_request.get('body')), headers=header) res_json = json.loads(res.data.decode('utf-8')) message = res_json.get('message').get( invalid_json_request.get('field')) assert message.find(invalid_json_request.get('reason')) def test_post_bad_json(client, header, monkeypatch, not_distributor): """Assert invalid response given bad JSON.""" monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests([not_distributor])) res = client.post('/project', data='{{}', headers=header) assert res.status_code == 400 def test_post_duplicate_projectcode_vendorid_subaccountid( db_fixture, client, header, monkeypatch, get_valid_post_dict, not_distributor): """Assert that inserting duplicate request fails.""" header[header_const.ORCHARD_USER_ID] = 'alw:123' monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests([not_distributor])) post_dict = get_valid_post_dict first_request = client.post( '/project', headers=header, data=json.dumps(post_dict)) second_request = client.post( '/project', headers=header, data=json.dumps(post_dict)) response_json = json.loads(second_request.data.decode('utf-8')) project_code_message = response_json['message']['project_code'] assert first_request.status_code == 201 assert_header(header, second_request.headers) assert second_request.status_code == 400 assert response_json['code'] == error_const.VALIDATION_ERROR assert project_code_message['validator'] == 'used' assert project_code_message['validator_value'] is True assert project_code_message[ 'message'] == "Project code '{}' already exists".format( post_dict[field_const.PROJECT_CODE]) def test_post_project_code_leading_trailing_whitespace( db_fixture, client, header, monkeypatch, not_distributor): """Assert that posting a project_code with leading and trailing whitespace. This test checks to see that project name and project code with leading and trailing whitespaces return success and that it removes leading and trailing whitespaces """ header[header_const.ORCHARD_USER_ID] = 'alw:123' monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests([not_distributor])) project_name = 'test project name' project_code = 'test project_code' invalid_json = { field_const.PROJECT_NAME: ' {} \t\n'.format(project_name), field_const.PROJECT_CODE: ' {} \n\t'.format(project_code), field_const.ARTIST_ID: 242131} res = client.post( '/project', headers=header, data=json.dumps(invalid_json)) assert_header(header, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 201 assert res_json[field_const.PROJECT_NAME] == project_name assert res_json[field_const.PROJECT_CODE] == project_code def test_post_project_success_with_orchard_user_id( db_fixture, client, header, monkeypatch): """Assert that successful POST returns correct response for distributor.""" vendor_subaccount = { 'service': 'ows-account', 'path': '/7123/subaccount/2', 'status': 200, 'json': {'vendor_id': 7123} } is_distributor = { 'service': 'ows-account', 'path': '/7123/distributor', 'status': 200, 'json': None } head_specs = [vendor_subaccount, is_distributor] monkeypatch.setattr( request, 'head', test_utils.mock_ows_requests(head_specs)) post_dict = { field_const.PROJECT_CODE: random_project_code(), field_const.PROJECT_NAME: str(uuid.uuid4()), field_const.SUBACCOUNT_ID: 2, field_const.ARTIST_ID: 242131 } orchard_user_id = 'alw:123' header[header_const.ORCHARD_USER_ID] = orchard_user_id res = client.post( '/project', headers=header, data=json.dumps(post_dict)) assert_header(header, res.headers) res_json = json.loads(res.data.decode('utf-8')) assert res.status_code == 201 assert_response( header, post_dict, res_json, post_dict[field_const.SUBACCOUNT_ID]) assert res_json['project_highlights'] is None