"""Functional tests for updating an audio product via a PUT call.""" import datetime import json import re from unittest import mock from unittest.mock import MagicMock from urllib.parse import urlencode from oto import response from owsrequest import flask_request from owsrequest import request import pytest from product_digital import api from product_digital import features from product_digital.connectors import mysql from product_digital.constants import header as header_constants from product_digital.constants import services from product_digital.logic import product from product_digital.models import ows_product from product_digital.models import ows_product_workflow from product_digital.models import ows_track from product_digital.models import release from product_digital.models import release_status from tests.factories import release as release_factory from tests.factories import release_subgenre as release_subgenre_factory from tests.factories.release_artist import ReleaseArtistFactory from tests.testutils import db @pytest.fixture def product_id(): """Return a fake product id for testing.""" return 12345 @db.test_schema def test_put_product_with_valid_product_payload_no_highlights_included( client, product_id, valid_headers, valid_product_payload, mocker, empty_success ): """Test that a c-line with an valid format is accepted.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_info_id=1234, release_artist_id=1) db.seed_models([existing_release, existing_release_artist]) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(valid_product_payload)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 200 assert response_body == { 'artist_id': 3030, 'delivered_version': None, 'description': 'Unlistenable, to be honest.', 'distribution_format_id': 1, 'format': 'Full Length', 'genre_id': 2828, 'c_line': '2016 DIG For Gold Records', 'imprint': 'DIG For Gold Records', 'manufacturer_upc': '123456789012', 'meta_language': 'SUX', 'p_line': '2020 PLINE', 'previewable': 'yes', 'preorder_date': valid_product_payload['preorder_date'], 'product_artists': valid_product_payload['product_artists'], 'product_code': 'AProduct123', 'product_name': 'Eggensperger And Heydt Yodel The Classics', 'product_type_id': existing_release.product_type_id, 'project_id': existing_release.project_id, 'product_id': 12345, 'release_date': valid_product_payload['release_date'], 'release_status': 'label_processing', 'is_error_correction_and_action_required': False, 'sale_start_date': valid_product_payload['sale_start_date'], 'special_instructions': 'Listen at low volume. Better yet, hit mute.', 'subaccount_id': None, 'subgenre_id': 13, 'upc': existing_release.display_upc, 'vendor_catalog_number': existing_release.vendor_catalog_number, 'version': 'Delux', 'not_for_distribution': 'N', } @db.test_schema @pytest.mark.parametrize('blank_value', ['', None]) def test_put_product_with_valid_product_payload_blank_preorder_date( client, product_id, valid_headers, valid_product_payload, mocker, blank_value, empty_success): """Test that a c-line with an valid format is accepted.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_info_id=1234, release_artist_id=1) db.seed_models([existing_release, existing_release_artist]) valid_product_payload['previewable'] = 'no' valid_product_payload['preorder_date'] = blank_value put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(valid_product_payload)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 200 assert response_body == { 'artist_id': 3030, 'delivered_version': None, 'description': 'Unlistenable, to be honest.', 'distribution_format_id': 1, 'format': 'Full Length', 'genre_id': 2828, 'c_line': '2016 DIG For Gold Records', 'imprint': 'DIG For Gold Records', 'manufacturer_upc': '123456789012', 'meta_language': 'SUX', 'p_line': '2020 PLINE', 'previewable': 'no', 'preorder_date': None, 'product_artists': valid_product_payload['product_artists'], 'product_code': 'AProduct123', 'product_name': 'Eggensperger And Heydt Yodel The Classics', 'product_type_id': existing_release.product_type_id, 'project_id': existing_release.project_id, 'product_id': 12345, 'release_date': valid_product_payload['release_date'], 'release_status': 'label_processing', 'is_error_correction_and_action_required': False, 'sale_start_date': valid_product_payload['sale_start_date'], 'special_instructions': 'Listen at low volume. Better yet, hit mute.', 'subaccount_id': None, 'subgenre_id': 13, 'upc': existing_release.display_upc, 'vendor_catalog_number': existing_release.vendor_catalog_number, 'version': 'Delux', 'not_for_distribution': 'N', } artist_a = { 'id': 1, 'name': 'Hello', 'role': 'primary_artist', 'artist_info_id': 1234 } artist_b = { 'id': 2, 'name': 'def', 'role': 'primary_artist', 'artist_info_id': 1235 } @pytest.mark.parametrize( ( 'test_description', 'query_params', 'expected_update_artists_response', 'expected_refetch_artists_response', ), [( 'Test with no query params.', {}, [artist_a], [artist_a], ), ( 'Test with query param is_non_destructive_update=applesauce_bananas', {'is_non_destructive_update': 'applesauce_bananas'}, [artist_a], [artist_a], ), ( 'Test with query param is_non_destructive_update=true', {'is_non_destructive_update': 'true'}, [artist_a], [artist_a, artist_b], )]) @db.test_schema def test_put_product_with_product_artists_without_artist_info_id( client, product_id, valid_headers, valid_product_payload, mocker, empty_success, test_description, query_params, expected_update_artists_response, expected_refetch_artists_response, ): """Test that product_artists without artist_info_id uses db value.""" valid_product_payload = valid_product_payload.copy() valid_product_payload['product_artists'] = [ {'id': 1, 'name': 'Hello', 'role': 'primary_artist'}] mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_name='abc', artist_info_id=1234, release_artist_id=1) another_existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_name='def', artist_info_id=1235, release_artist_id=2) db.seed_models([ existing_release, existing_release_artist, another_existing_release_artist, ]) expected_update_response_body = { 'artist_id': 3030, 'delivered_version': None, 'description': 'Unlistenable, to be honest.', 'distribution_format_id': 1, 'format': 'Full Length', 'genre_id': 2828, 'c_line': '2016 DIG For Gold Records', 'imprint': 'DIG For Gold Records', 'manufacturer_upc': '123456789012', 'meta_language': 'SUX', 'p_line': '2020 PLINE', 'previewable': 'yes', 'preorder_date': valid_product_payload['preorder_date'], 'product_artists': expected_update_artists_response, 'product_code': 'AProduct123', 'product_name': 'Eggensperger And Heydt Yodel The Classics', 'product_type_id': existing_release.product_type_id, 'project_id': existing_release.project_id, 'product_id': 12345, 'release_date': valid_product_payload['release_date'], 'release_status': 'label_processing', 'is_error_correction_and_action_required': False, 'sale_start_date': valid_product_payload['sale_start_date'], 'special_instructions': 'Listen at low volume. Better yet, hit mute.', 'subaccount_id': None, 'subgenre_id': 13, 'upc': existing_release.display_upc, 'vendor_catalog_number': existing_release.vendor_catalog_number, 'version': 'Delux', 'not_for_distribution': 'N', } expected_refetch_response_body = expected_update_response_body.copy() expected_refetch_response_body['product_artists'] = ( expected_refetch_artists_response) expected_refetch_response_body['catalog_transfer_info'] = None put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(valid_product_payload), query_string=urlencode(query_params), ) update_response_body = json.loads(put_response.data.decode()) refetch_response_body = json.loads( client.get( '/product/audio/{}'.format(product_id), headers=valid_headers ).data.decode()) assert put_response.status_code == 200 assert expected_update_response_body == update_response_body assert expected_refetch_response_body == refetch_response_body @db.test_schema def test_put_product_with_product_artists_with_artist_info_id_none( client, product_id, valid_headers, valid_product_payload, mocker, empty_success ): """Test that product_artists with null artist_info_id updates.""" valid_product_payload = valid_product_payload.copy() valid_product_payload['product_artists'] = [ {'id': 1, 'name': 'Hello', 'role': 'primary_artist', 'artist_info_id': None}] mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_info_id=1234, release_artist_id=1) db.seed_models([existing_release, existing_release_artist]) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(valid_product_payload)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 200 assert response_body == { 'artist_id': 3030, 'delivered_version': None, 'description': 'Unlistenable, to be honest.', 'distribution_format_id': 1, 'format': 'Full Length', 'genre_id': 2828, 'c_line': '2016 DIG For Gold Records', 'imprint': 'DIG For Gold Records', 'manufacturer_upc': '123456789012', 'meta_language': 'SUX', 'p_line': '2020 PLINE', 'previewable': 'yes', 'preorder_date': valid_product_payload['preorder_date'], 'product_artists': valid_product_payload['product_artists'], 'product_code': 'AProduct123', 'product_name': 'Eggensperger And Heydt Yodel The Classics', 'product_type_id': existing_release.product_type_id, 'project_id': existing_release.project_id, 'product_id': 12345, 'release_date': valid_product_payload['release_date'], 'release_status': 'label_processing', 'is_error_correction_and_action_required': False, 'sale_start_date': valid_product_payload['sale_start_date'], 'special_instructions': 'Listen at low volume. Better yet, hit mute.', 'subaccount_id': None, 'subgenre_id': 13, 'upc': existing_release.display_upc, 'vendor_catalog_number': existing_release.vendor_catalog_number, 'version': 'Delux', 'not_for_distribution': 'N', } @db.test_schema def test_put_product_with_vendor_release_identifier( client, product_id, valid_headers, valid_product_payload, mocker, empty_success ): """Test that vendor_release_identifier with an valid format is accepted.""" valid_product_payload['vendor_release_identifier'] = 'G0something' mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_info_id=1234, release_artist_id=1) db.seed_models([existing_release, existing_release_artist]) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(valid_product_payload)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 200 assert response_body == { 'artist_id': 3030, 'delivered_version': None, 'description': 'Unlistenable, to be honest.', 'distribution_format_id': 1, 'format': 'Full Length', 'genre_id': 2828, 'c_line': '2016 DIG For Gold Records', 'imprint': 'DIG For Gold Records', 'manufacturer_upc': '123456789012', 'meta_language': 'SUX', 'p_line': '2020 PLINE', 'previewable': 'yes', 'preorder_date': valid_product_payload['preorder_date'], 'product_artists': valid_product_payload['product_artists'], 'product_code': 'AProduct123', 'product_name': 'Eggensperger And Heydt Yodel The Classics', 'product_type_id': existing_release.product_type_id, 'project_id': existing_release.project_id, 'product_id': 12345, 'release_date': valid_product_payload['release_date'], 'release_status': 'label_processing', 'is_error_correction_and_action_required': False, 'sale_start_date': valid_product_payload['sale_start_date'], 'special_instructions': 'Listen at low volume. Better yet, hit mute.', 'subaccount_id': None, 'subgenre_id': 13, 'upc': existing_release.display_upc, 'vendor_catalog_number': existing_release.vendor_catalog_number, 'vendor_release_identifier': valid_product_payload[ 'vendor_release_identifier'], 'version': 'Delux', 'not_for_distribution': 'N', } def test_put_product_with_invalid_cline(client, product_id, valid_headers): """Test that a c-line with an invalid format is rejected.""" put_body = {'c_line': 'marmosets do not have wisdom teeth'} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) message_regex = re.compile(' does not match ') assert put_response.status_code == 400 assert message_regex.search(response_body['message']['c_line']['message']) def test_put_product_with_invalid_previewable( client, product_id, valid_headers): """Test that an invalid previewable property is rejected.""" put_body = {'previewable': 'you betcha'} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) expected_error = "'you betcha' is not one of ['yes', 'no', None]" assert put_response.status_code == 400 assert response_body['message']['previewable']['message'] == expected_error def test_put_product_format_not_in_enum(client, product_id, valid_headers): """Test that format is rejected when not one of the allowed values.""" put_body = {'format': 'betamax'} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) expected_error =\ "'betamax' is not one of ['Single', 'EP', 'Full Length', None]" assert put_response.status_code == 400 assert response_body['message']['format']['message'] == expected_error def test_put_product_with_blank_manufacturer_upc( client, product_id, valid_headers): """Test that a blank manufacturer UPC is rejected.""" put_body = {'manufacturer_upc': ''} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) expected_error = "None is not of type 'string'" assert put_response.status_code == 400 assert response_body['message']['manufacturer_upc']['message'] == \ expected_error def test_put_product_manufacturer_upc_too_short( client, product_id, valid_headers): """Test that manufacturer UPC is rejected if too short.""" put_body = {'manufacturer_upc': '12345678901'} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) expected_error = "'12345678901' is too short" assert put_response.status_code == 400 assert response_body['message']['manufacturer_upc']['message'] == \ expected_error def test_put_product_manufacturer_upc_too_long( client, product_id, valid_headers): """Test that manufacturer UPC is rejected if too long.""" put_body = {'manufacturer_upc': '12345678901234'} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) expected_error = "'12345678901234' is too long" assert put_response.status_code == 400 assert response_body['message']['manufacturer_upc']['message'] == \ expected_error @db.test_schema def test_put_product_product_code_blank( client, product_id, valid_headers_for_vendor, mocker, empty_success ): """Test that product code is accepted if blank.""" mocker.patch.object(request, 'head', side_effect=[ MagicMock(status_code=200), MagicMock(status_code=404)]) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( features, 'is_rejections_from_content_review_enabled', return_value=False) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) db.seed_models(existing_release) put_body = {'product_code': ''} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 200 assert response_body.get('product_code') is None def test_put_product_product_name_blank( client, product_id, valid_headers): """Test that product name is rejected if blank.""" put_body = {'product_name': ''} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) expected_error = "None is not of type 'string'" assert put_response.status_code == 400 assert response_body['message']['product_name']['message'] == \ expected_error @db.test_schema def test_put_product_no_grass_headers( client, product_id, valid_headers, mocker, valid_product_payload, empty_success ): """Test that a request is allowed if no Grass headers are given.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_info_id=1234, release_artist_id=1) db.seed_models([existing_release, existing_release_artist]) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(valid_product_payload)) assert put_response.status_code == 200 def test_put_product_incomplete_grass_headers( client, product_id, valid_headers_for_vendor, valid_product_payload): """Test that a request is rejected with incomplete Grass headers.""" del (valid_headers_for_vendor[header_constants.GRASS_ACCOUNT_TYPE]) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps(valid_product_payload)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 400 assert response_body == { 'code': 'authorization_error', 'message': 'incomplete account headers'} def test_put_product_vendor_not_owner( client, mocker, product_id, valid_headers_for_vendor, valid_product_payload): """Test that request is rejected if Grass vendor is not product owner.""" mocker.patch.object( request, 'head', return_value=mock.MagicMock(status_code=403)) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps(valid_product_payload)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 403 assert response_body == { 'code': 'authorization_error', 'message': 'Product is not owned by account'} @db.test_schema def test_put_product_dates_valid( client, product_id, valid_headers_for_vendor, mocker, valid_product_payload, empty_success): """Test that a request with a valid dates is accepted.""" mocker.patch.object(request, 'head', side_effect=[ MagicMock(status_code=200), MagicMock(status_code=404)]) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( features, 'is_rejections_from_content_review_enabled', return_value=False) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_info_id=1234, release_artist_id=1) db.seed_models([existing_release, existing_release_artist]) put_body = valid_product_payload put_body.update({ 'preorder_date': '2016-05-23', 'release_date': '2016-05-24', 'sale_start_date': '2016-05-25'}) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps(put_body)) assert put_response.status_code == 200 def test_put_product_invalid_date_format(client, product_id, valid_headers): """Test that a request with valid dates is rejected.""" put_body = { 'preorder_date': 'any day now', 'release_date': 'a week from last thursday', 'sale_start_date': 'never'} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) message_regex = re.compile(' does not match ') assert put_response.status_code == 400 assert message_regex.search( response_body['message']['preorder_date']['message']) assert message_regex.search( response_body['message']['release_date']['message']) assert message_regex.search( response_body['message']['sale_start_date']['message']) def test_put_product_nonexistent_dates( client, product_id, valid_headers, mocker, invalid_date_message, valid_product_payload): """Test that a request with bogus dates is rejected.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) put_body = valid_product_payload put_body.update({ 'preorder_date': '1999-13-32', 'release_date': '1999-00-00', 'sale_start_date': '2017-02-29'}) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 400 invalid_date_message.update({'error_code': 'invalid_preorder_date'}) assert response_body['message']['preorder_date'] == invalid_date_message invalid_date_message.update({'error_code': 'invalid_release_date'}) assert response_body['message']['release_date'] == invalid_date_message invalid_date_message.update({'error_code': 'invalid_sale_start_date'}) assert response_body['message']['sale_start_date'] == invalid_date_message def test_put_product_invalid_product_code( client, product_id, valid_headers_for_vendor, mocker, product_code_not_available, valid_product_payload): """Test that an invalid product response is returned.""" mocker.patch.object( request, 'head', return_value=MagicMock(status_code=200)) put_body = valid_product_payload put_body.update({'product_code': 'ABC123'}) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 400 assert response_body['message'] == product_code_not_available assert response_body['code'] == 'bad_request' @db.test_schema def test_put_product_no_primary_artist( client, product_id, valid_headers, mocker, valid_product_payload, empty_success ): """Test that a request with no primary artist is accepted.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) db.seed_models(existing_release) product_artists = [{'role': 'featuring', 'name': 'featuring artist'}] put_body = valid_product_payload put_body.update({'product_artists': product_artists}) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) for artist in response_body.get('product_artists'): assert artist.get('role') != 'primary' @db.test_schema def test_put_product_with_unrequired_fields( client, product_id, valid_headers_for_vendor, mocker, empty_success ): """Test that a put request is successful when nulling unrequired fields.""" mocker.patch.object(request, 'head', side_effect=[ MagicMock(status_code=200), MagicMock(status_code=404)]) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( features, 'is_rejections_from_content_review_enabled', return_value=False) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_subgenre = release_subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) db.seed_models(existing_release) db.seed_models(existing_subgenre) put_body = { 'description': None, 'c_line': None, 'imprint': None, 'meta_language': None, 'preorder_date': None, 'previewable': None, 'product_artists': [], 'product_code': None, 'release_date': None, 'sale_start_date': None, 'special_instructions': None, 'version': None, 'subgenre_id': None, 'genre_id': None} put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers_for_vendor, data=json.dumps(put_body)) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 200 assert put_body.items() <= response_body.items() @db.test_schema def test_put_response_body_matches_get_response_body( client, product_id, valid_headers, valid_product_payload, mocker, empty_success ): """Test that put response matches a get response for same product.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'get', return_value=empty_success) mocker.patch.object( ows_product_workflow, 'get_last_release_approval_queue', return_value=MagicMock(status_code=200, message=None)) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_info_id=1234, release_artist_id=1) db.seed_models([existing_release, existing_release_artist]) put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(valid_product_payload)) get_response = client.get( '/product/audio/{}'.format(product_id), headers=valid_headers) put_response_body = json.loads(put_response.data.decode()) get_response_body = json.loads(get_response.data.decode()) assert put_response.status_code == 200 # catalog_transfer_info is GET-only for now, not part of the PUT contract. get_response_body.pop('catalog_transfer_info', None) assert put_response_body == get_response_body @db.test_schema def test_put_response_body_matches_get_response_body_when_action_required( client, request_engine, orchard_user_id, product_id, product_workflow_rejections_response, release_approval_get_message, release_approval_get_response, valid_headers, valid_product_payload, get_project_response_message, mock_vendor_company_brand, mocker, mock_vendor_response ): """Test put response and get response match when action required.""" # update_product mocking setup request_engine[services.OWS_PRODUCT].add_spec( 'HEAD', '/vendor/123/product_code/AProduct123', status=404) request_engine[services.OWS_PRODUCT_WORKFLOW].add_spec( 'GET', '/product/{}/release-approval'.format(product_id), release_approval_get_response.json()) request_engine[services.OWS_PRODUCT_WORKFLOW].add_spec( 'GET', '/release-approval/{}/rejections'.format( release_approval_get_message.get('release_approval_id')), product_workflow_rejections_response.json()) request_engine[services.OWS_PROJECT_MANAGER].add_spec( 'GET', '/project/12345', get_project_response_message) request_engine[services.OWS_ACCOUNT].add_spec( 'GET', '/vendor/brand/9999', mock_vendor_company_brand) request_engine[services.OWS_ACCOUNT].add_spec( 'GET', '/vendor/9999', mock_vendor_response) mocker.patch.object( ows_track, 'change_genre', return_value=MagicMock(status_code=200, message=None)) existing_release = release_factory.ReleaseFactory.build( project_id=12345, release_id=product_id) existing_release_artist = ReleaseArtistFactory.build( release_id=product_id, artist_info_id=1234, release_artist_id=1) db.seed_models([existing_release, existing_release_artist]) valid_headers[header_constants.ORCHARD_USER_ID] = orchard_user_id put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_headers, data=json.dumps(valid_product_payload)) get_response = client.get( '/product/audio/{}'.format(product_id), headers=valid_headers) put_response_body = json.loads(put_response.data.decode()) get_response_body = json.loads(get_response.data.decode()) assert put_response.status_code == 200 # catalog_transfer_info is GET-only for now, not part of the PUT contract. get_response_body.pop('catalog_transfer_info', None) assert put_response_body == get_response_body @db.test_schema def test_put_unsubmit_product(client, product_id, valid_headers_for_vendor, request_engine, mocker): """Test unsubmitting a submitted product in error_correction.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'head', return_value=MagicMock( status_code=200)) request_engine[services.OWS_PRODUCT_WORKFLOW].add_spec( 'POST', '/product/{}/unsubmit'.format(product_id), {'message': 'release approval deleted'}) existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_status='transfer_to_content') release_status_one = release_factory.ReleaseStatusFactory.build( release_id=product_id, status='label_processing', date=datetime.datetime.today() ) release_status_two = release_factory.ReleaseStatusFactory.build( release_id=product_id, status='transfer_to_content', date=datetime.datetime.today() ) db.seed_models([existing_release, release_status_one, release_status_two]) put_response = client.put( '/product/audio/{}/unsubmit'.format(product_id), headers=valid_headers_for_vendor) response_body = put_response.data.decode() assert put_response.status_code == 200 assert response_body == 'product unsubmitted' with mysql.db_session() as session: updated_release = session.query(release.Release).filter_by(release_id=product_id).first() assert updated_release.release_status == 'label_processing' release_status_entry = session.query(release_status.ReleaseStatus).filter_by(release_id=product_id).all() assert len(release_status_entry) == 3 assert release_status_entry[2].status == 'label_processing' @db.test_schema def test_put_unsubmit_product_error_correction( client, product_id, valid_headers_for_vendor, request_engine, mocker): """Test unsubmitting a submitted product.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'head', return_value=MagicMock( status_code=200)) request_engine[services.OWS_PRODUCT_WORKFLOW].add_spec( 'POST', '/product/{}/unsubmit'.format(product_id), {'message': 'release approval deleted'}) existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_status='in_content') release_status_one = release_factory.ReleaseStatusFactory.build( release_id=product_id, status='label_processing', date=datetime.datetime.today() ) release_status_two = release_factory.ReleaseStatusFactory.build( release_id=product_id, status='transfer_to_content', date=datetime.datetime.today() ) release_status_three = release_factory.ReleaseStatusFactory.build( release_id=product_id, status='transfer_to_content', date=datetime.datetime.today(), changed_by_type='oa' ) db.seed_models([existing_release, release_status_one, release_status_two, release_status_three]) put_response = client.put( '/product/audio/{}/unsubmit'.format(product_id), headers=valid_headers_for_vendor) response_body = put_response.data.decode() assert put_response.status_code == 200 assert response_body == 'product unsubmitted' with mysql.db_session() as session: updated_release = session.query(release.Release).filter_by(release_id=product_id).first() assert updated_release.release_status == 'in_content' release_status_entry = session.query(release_status.ReleaseStatus).filter_by(release_id=product_id).all() assert len(release_status_entry) == 4 assert release_status_entry[3].status == 'in_content' assert release_status_entry[3].changed_by_type == 'vendor' @db.test_schema def test_put_unsubmit_product_workflow_error( client, product_id, valid_headers_for_vendor, request_engine, mocker): """Test unsubmitting a submitted product with product workflow errors.""" mocker.patch.object(request, 'head', return_value=MagicMock( status_code=404)) mocker.patch.object(request, 'head', return_value=MagicMock( status_code=200)) request_engine[services.OWS_PRODUCT_WORKFLOW].add_spec( 'POST', '/product/{}/unsubmit'.format(product_id), 'Product is not checked_in', status=400) existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_status='transfer_to_content') db.seed_models(existing_release) put_response = client.put( '/product/audio/{}/unsubmit'.format(product_id), headers=valid_headers_for_vendor) response_body = json.loads(put_response.data.decode()) assert put_response.status_code == 400 assert response_body == { 'code': 'ows_product_workflow_error', 'message': 'Product is not checked_in' } def test_put_product_with_oa_grass_headers_and_vendor_product( mocker, test_request_context, client, product_id, valid_product_payload, base_product_data, valid_oa_headers_with_orchard_user_id, valid_ows_product_get_response): """Validate call args when updating as a OA account against a vendor product.""" mocker.patch.object(flask_request, 'verify_grass_headers', MagicMock(wraps=response.Response) ) mocker.patch.object(flask_request, 'verify_grass_ownership', MagicMock(return_value=response.Response()) ) mocker.patch.object(ows_product, 'get_product', MagicMock(return_value=response.Response(message=valid_ows_product_get_response)) ) mocker.patch.object(product, 'update_audio_product', MagicMock(return_value=response.Response(status=200)) ) valid_product_payload.pop('account_id', None) valid_product_payload.pop('account_type', None) with api.app.test_request_context(): client.put( '/product/audio/{}'.format(product_id), headers=valid_oa_headers_with_orchard_user_id, data=json.dumps(valid_product_payload)) ows_product.get_product.assert_called_once_with(product_id) product.update_audio_product.assert_called_once() product_put_data = product.update_audio_product.call_args[1]['product_data'] assert product_put_data['account_id'] == valid_ows_product_get_response['vendor_id'] assert product_put_data['account_type'] == header_constants.GRASS_ACCOUNT_TYPE_VENDOR def test_put_product_with_oa_grass_headers_and_subaccount_product( mocker, test_request_context, client, product_id, valid_product_payload, base_product_data, valid_oa_headers_with_orchard_user_id, valid_ows_product_get_response_with_subaccount): """Validate call args when updating as a OA account against a subaccount product.""" mocker.patch.object(flask_request, 'verify_grass_headers', MagicMock(wraps=response.Response) ) mocker.patch.object(flask_request, 'verify_grass_ownership', MagicMock(return_value=response.Response()) ) mocker.patch.object(ows_product, 'get_product', MagicMock(return_value=response.Response( message=valid_ows_product_get_response_with_subaccount)) ) mocker.patch.object(product, 'update_audio_product', MagicMock(return_value=response.Response(status=200)) ) valid_product_payload.pop('account_id', None) valid_product_payload.pop('account_type', None) with api.app.test_request_context(): client.put( '/product/audio/{}'.format(product_id), headers=valid_oa_headers_with_orchard_user_id, data=json.dumps(valid_product_payload)) ows_product.get_product.assert_called_once_with(product_id) product.update_audio_product.assert_called_once() product_put_data = product.update_audio_product.call_args[1]['product_data'] assert product_put_data['account_id'] == valid_ows_product_get_response_with_subaccount['subaccount_id'] assert product_put_data['account_type'] == header_constants.GRASS_ACCOUNT_TYPE_SUBACCOUNT def test_put_product_with_oa_grass_headers_and_invalid_product( mocker, test_request_context, client, product_id, valid_product_payload, base_product_data, valid_oa_headers_with_orchard_user_id, valid_ows_product_get_response_with_subaccount): """Validate call args when updating as a OA account against an invalid product.""" mocker.patch.object(flask_request, 'verify_grass_headers', MagicMock(wraps=response.Response) ) mocker.patch.object(flask_request, 'verify_grass_ownership', MagicMock(return_value=response.Response()) ) mocker.patch.object(ows_product, 'get_product', MagicMock(return_value=response.Response(status=404)) ) mocker.patch.object(product, 'update_audio_product', MagicMock(wrap=product.update_audio_product) ) valid_product_payload.pop('account_id', None) valid_product_payload.pop('account_type', None) with api.app.test_request_context(): put_response = client.put( '/product/audio/{}'.format(product_id), headers=valid_oa_headers_with_orchard_user_id, data=json.dumps(valid_product_payload)) ows_product.get_product.assert_called_once_with(product_id) product.update_audio_product.assert_not_called() assert put_response.status_code == 404