"""Functional tests for post artist handler.""" import json import pytest from oto.response import Response from owsrequest import request from unittest.mock import MagicMock from artist.constants import errors from artist.constants import headers as headers_constants from artist.connectors import ows_permissions from tests.testutils import db @pytest.fixture def valid_artist_for_vendor(account_id): """Valid POST data for a client authenticated as a vendor.""" return { 'name': 'Vendor Vinny', 'artist_type': 'film_collection', 'vendor_id': account_id} @pytest.fixture def valid_artist_for_subaccount(account_id): """Valid POST data for a client authenticated as a subaccount.""" return { 'name': 'Subaccount Susan', 'artist_type': 'film_collection', 'subaccount_id': account_id } @db.test_schema def test_post_artist_as_vendor( account_id, client, valid_artist_for_vendor, valid_headers_for_vendor): """Post artist returns a 201 when provided valid vendor headers.""" response = client.post( '/artist', headers=valid_headers_for_vendor, data=json.dumps(valid_artist_for_vendor)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 201 assert isinstance(response_json.get('id'), int) assert response_json.get('name') == valid_artist_for_vendor.get('name') assert response_json.get('artist_type') == valid_artist_for_vendor.get( 'artist_type') @db.test_schema def test_post_artist_as_subaccount( account_id, client, valid_artist_for_subaccount, valid_headers_for_subaccount, mocker): """Post artist returns a 201 when provided valid subaccount headers.""" mock_response = MagicMock(status_code=200) mock_response.json = MagicMock(return_value={'vendor_id': 7123}) mocker.patch.object(request, 'get', return_value=mock_response) response = client.post( '/artist', headers=valid_headers_for_subaccount, data=json.dumps(valid_artist_for_subaccount)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 201 assert isinstance(response_json.get('id'), int) assert response_json.get('name') == valid_artist_for_subaccount.get('name') assert response_json.get('artist_type') == valid_artist_for_subaccount.get( 'artist_type') def test_post_artist_as_wrong_vendor( account_id, client, valid_headers_for_vendor): """Post artist returns 403. Should happen when vendor header does not match the vendor id in the request body. """ artist_data = {'name': 'Arty McArtist', 'vendor_id': account_id + 1} response = client.post( '/artist', headers=valid_headers_for_vendor, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 403 assert response_json.get('code') == errors.OWNERSHIP_ERROR assert response_json.get('message') == errors.VENDOR_DENIED_MESSAGE def test_post_artist_as_wrong_subaccount( account_id, client, valid_headers_for_subaccount): """Post artist returns 403. Should happen when subaccount header does not match the subaccount id in the request body. """ artist_data = {'name': 'Arty McArtist', 'subaccount_id': account_id + 1} response = client.post( '/artist', headers=valid_headers_for_subaccount, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 403 assert response_json.get('code') == errors.OWNERSHIP_ERROR assert response_json.get('message') == errors.VENDOR_DENIED_MESSAGE @db.test_schema def test_post_artist_with_vendor_id_and_no_grass_headers( account_id, client, headers): """Post artist returns 201. This should happen if no Grass authentication headers are given, but vendor_id is in the body. """ artist_data = {'name': 'Arty McArtist', 'vendor_id': 7123} response = client.post( '/artist', headers=headers, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) artist_from_db = db.get_artist_by_id(response_json.get('id')) assert response.status_code == 201 assert artist_from_db.vendor_id == artist_data.get('vendor_id') assert response_json.get('name') == artist_data.get('name') @db.test_schema def test_post_artist_with_subaccount_id_and_no_grass_headers( account_id, client, headers, mocker): """Post artist returns 201. This should happen if no Grass authentication headers are given, but subaccount_id is in the body. """ mock_response = MagicMock(status_code=200) mock_response.json = MagicMock(return_value={'vendor_id': 7123}) mocker.patch.object(request, 'get', return_value=mock_response) artist_data = {'name': 'Arty McArtist', 'subaccount_id': 200} response = client.post( '/artist', headers=headers, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) artist_from_db = db.get_artist_by_id(response_json.get('id')) assert response.status_code == 201 assert artist_from_db.vendor_id == 7123 assert response_json.get('name') == artist_data.get('name') @db.test_schema def test_post_artist_with_no_account( mocker, account_id, client, headers): """Post artist returns 403. This should happen if no Grass authentication headers are given, and neither subaccount_id or vendor_id are in the body. """ artist_data = {'name': 'Arty McArtist'} # Simulate no vendor or subaccount from auth service: mocker.patch.object( ows_permissions, 'get_single_vendor_id_for_label_profile', return_value=None) response = client.post( '/artist', headers=headers, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 403 assert response_json.get('message') == errors.VENDOR_DENIED_MESSAGE assert response_json.get('code') == errors.OWNERSHIP_ERROR def test_post_artist_with_incomplete_grass_headers( account_id, client, headers): """Post artist returns 403 if partial Grass headers are given.""" headers[headers_constants.GRASS_ACCOUNT_ID] = account_id artist_data = { 'name': 'Arty McArtist', 'vendor_id': account_id} response = client.post( '/artist', headers=headers, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 403 assert response_json.get('code') == errors.OWNERSHIP_ERROR assert response_json.get('message') == errors.VENDOR_DENIED_MESSAGE def test_post_artist_invalid_account_type(client, valid_headers_for_vendor): """Post artist returns a 401 if account type invalid.""" valid_headers_for_vendor[headers_constants.GRASS_ACCOUNT_TYPE] = 'invalid' response = client.post( '/artist', headers=valid_headers_for_vendor, data=json.dumps({})) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 400 assert response_json.get('code') == 'validation_error' assert response_json.get('message') == { 'Grass-Account-Type': "'invalid' is not one of ['vendor', 'subaccount']"} def test_post_artist_invalid_account_id(client, valid_headers_for_vendor): """Post artist returns a 401 if account id invalid.""" valid_headers_for_vendor[headers_constants.GRASS_ACCOUNT_ID] = 'invalid' response = client.post( '/artist', headers=valid_headers_for_vendor, data=json.dumps({})) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 400 assert response_json.get('code') == 'validation_error' assert response_json.get('message') == { 'Grass-Account-Id': "'invalid' does not match '^\\\\d+$'"} def test_post_artist_invalid_body(client, valid_headers_for_vendor): """Post artist returns a 400 if body is invalid.""" response = client.post( '/artist', headers=valid_headers_for_vendor, data=json.dumps({})) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 400 assert response_json.get('code') == 'validation_error' assert response_json.get('message') == { 'name': "'name' is a required property"} @db.test_schema def test_post_artist_default_type( account_id, client, valid_headers_for_vendor): """Post artist uses a default artist type of "artist" if none given.""" artist_data = { 'name': 'Arty McArtist', 'vendor_id': account_id} response = client.post( '/artist', headers=valid_headers_for_vendor, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 201 assert response_json.get('artist_type') == 'artist' @db.test_schema def test_post_artist_with_no_vendor_id(client, valid_headers_for_vendor): """Test post artist without vendor id. Post artist is able to create the artist and saves the account id as a vendor id when there is no vendor id provided. """ artist_data = {'name': 'Arty McArtist'} response = client.post( '/artist', headers=valid_headers_for_vendor, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) artist_from_db = db.get_artist_by_id(response_json.get('id')) account_id = valid_headers_for_vendor.get( headers_constants.GRASS_ACCOUNT_ID) assert artist_from_db.vendor_id == account_id assert response.status_code == 201 @db.test_schema def test_post_artist_with_null_subaccount_id( mocker, client, valid_headers_for_subaccount): """Test post artist with null subaccount. Post artist is able to create the artist and save the vendor id associated with the subaccount from the grass headers when there is no subaccount id provided. """ mock_response = MagicMock(status_code=200) mock_response.json = MagicMock(return_value={'vendor_id': 7123}) mocker.patch.object(request, 'get', return_value=mock_response) artist_data = {'name': 'Arty McArtist'} response = client.post( '/artist', headers=valid_headers_for_subaccount, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) artist_from_db = db.get_artist_by_id(response_json.get('id')) assert artist_from_db.vendor_id == 7123 assert response.status_code == 201 @db.test_schema def test_post_artist_with_oa_grass_header_and_vendor_id( account_id, client, valid_headers_for_oa): """Post artist returns 201. When OA you should be able to pass a vendor_id """ artist_data = {'name': 'Arty McArtist', 'vendor_id': 7123} response = client.post( '/artist', headers=valid_headers_for_oa, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) artist_from_db = db.get_artist_by_id(response_json.get('id')) assert response.status_code == 201 assert artist_from_db.vendor_id == artist_data.get('vendor_id') assert response_json.get('name') == artist_data.get('name') @db.test_schema def test_post_artist_with_oa_grass_header_and_subaccount_id( mocker, account_id, client, valid_headers_for_oa): """Post artist returns 201. When OA you should be able to pass a subaccount_id """ mock_response = MagicMock(status_code=200) mock_response.json = MagicMock(return_value={'vendor_id': 7123}) mocker.patch.object(request, 'get', return_value=mock_response) artist_data = {'name': 'Arty McArtist', 'subaccount_id': 200} response = client.post( '/artist', headers=valid_headers_for_oa, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) artist_from_db = db.get_artist_by_id(response_json.get('id')) assert response.status_code == 201 assert artist_from_db.vendor_id == 7123 assert response_json.get('name') == artist_data.get('name') @db.test_schema def test_post_artist_with_oa_grass_header_with_no_account_ids( mocker, account_id, client, valid_headers_for_oa): """Post artist returns 403. When posting an artist without any ids and OA headers a bad request should be triggered during validation """ artist_data = {'name': 'Arty McArtist'} # Simulate no vendor or subaccount from auth service: mocker.patch.object( ows_permissions, 'get_single_vendor_id_for_label_profile', return_value=None) response = client.post( '/artist', headers=valid_headers_for_oa, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 403 assert response_json.get('code') == errors.OWNERSHIP_ERROR assert response_json.get('message') == errors.VENDOR_DENIED_MESSAGE @db.test_schema def test_post_artist_label_profile_single_vendor(client, mocker, artist_name): """Should succeed when LabelProfile has a single accessible vendor.""" mocker.patch("artist.connectors.ows_permissions.get_single_vendor_id_for_label_profile", return_value=555) headers = { "Orchard-Profile-Type": "LabelProfile", "Orchard-Profile-Id": "123", "Content-Type": "application/json" } artist_data = {"name": artist_name} response = client.post("/artist", headers=headers, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response_json.get('name') == artist_name @db.test_schema def test_post_artist_label_profile_no_vendor_with_subaccount(client, mocker, artist_name): """Should succeed when LabelProfile has no vendor but a subaccount with vendor.""" mocker.patch("artist.connectors.ows_permissions.get_single_vendor_id_for_label_profile", return_value=None) mocker.patch("artist.connectors.ows_permissions.get_subaccount_ids_for_label_profile", return_value=[789]) mocker.patch("artist.models.account.get_vendor_id_for_subaccount_id", return_value=Response(message=555)) headers = { "Orchard-Profile-Type": "LabelProfile", "Orchard-Profile-Id": "456", "Content-Type": "application/json" } artist_data = {"name": artist_name} response = client.post("/artist", headers=headers, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 201 assert response_json.get('name') == artist_name @db.test_schema def test_post_artist_label_profile_no_vendor_no_subaccount(client, mocker, artist_name): """Should fail when LabelProfile has neither vendor nor subaccount.""" mocker.patch("artist.connectors.ows_permissions.get_single_vendor_id_for_label_profile", return_value=None) mocker.patch("artist.connectors.ows_permissions.get_subaccount_ids_for_label_profile", return_value=[]) mocker.patch("artist.models.account.get_vendor_id_for_subaccount_id", return_value=Response(message=555)) headers = { "Orchard-Profile-Type": "LabelProfile", "Orchard-Profile-Id": "456", "Content-Type": "application/json" } artist_data = {"name": artist_name} response = client.post("/artist", headers=headers, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 403 assert response_json.get('code') == errors.OWNERSHIP_ERROR assert response_json.get('message') == errors.VENDOR_DENIED_MESSAGE @db.test_schema def test_post_artist_non_label_profile_type(client, artist_name): """Should fail when profile_type is not LabelProfile.""" headers = { "Orchard-Profile-Type": "InsightsProfile", "Orchard-Profile-Id": "456", "Content-Type": "application/json" } artist_data = {"name": artist_name} response = client.post("/artist", headers=headers, data=json.dumps(artist_data)) response_json = json.loads(response.data.decode('utf-8')) assert response.status_code == 403 assert response_json.get('code') == errors.OWNERSHIP_ERROR assert response_json.get('message') == errors.VENDOR_DENIED_MESSAGE