"""Unit tests for product logic.""" from contextlib import nullcontext as does_not_raise import copy import datetime import json from unittest.mock import ANY, call, MagicMock, Mock from unittest.mock import NonCallableMagicMock from flexmock import flexmock from oto import error as oto_error from oto import response from owsrequest import request from owsrequest import test_utils import pytest import requests import sqlalchemy from flask import g from product_digital.api import app from product_digital.constants import error from product_digital.constants import services from product_digital.logic import product from product_digital.models import audio_product from product_digital.models import mkt_priority from product_digital.models import ows_pricing from product_digital.models import ows_product from product_digital.models import ows_product_physical from product_digital.models import ows_product_workflow from product_digital.models import ows_track from product_digital.models import product_events from product_digital.models import product_provided_store_artists from product_digital.models import project from product_digital.models import release from product_digital.models import release_artist from product_digital.models import release_status from product_digital.models import release_subgenre from product_digital.models import neo4j_vendor from product_digital.validation import audio_product as product_validation from tests.factories import release as release_factory from tests.testutils import db from product_digital.logic.validate_audio_product_feature_flag_context import ValidateAudioProductFeatureFlagContext from product_digital import features DATE_OBJECTS_FOR_RESPONSE_MESSAGE = [ datetime.date(2016, 12, 12), datetime.date(2016, 12, 13), datetime.date(2016, 12, 14)] DATE_STRINGS_FOR_RESPONSE_MESSAGE = ['2016-12-12', '2016-12-13', '2016-12-14'] @pytest.fixture(autouse=True) def app_context_with_ows(): """Provide Flask app context and mock g.ows for tests.""" with app.app_context(): g.ows = Mock() g.ows.log = Mock() yield @pytest.fixture def get_project_response_message(): """Return expected response from project get project method.""" return { 'project_name': 'this project', 'project_code': 'ABC123', 'project_id': 12345, 'vendor_id': 9999, 'subaccount_id': 0, } @pytest.fixture def get_products_response(): """Return expected response from project get products method.""" mock_response = Mock() mock_response.message = [ { 'artist_id': 2627435, 'description': None, 'distribution_format_id': 1, 'format': 'Single', 'genre_id': 7, 'c_line': '2015 CTC Management', 'imprint': 'SABA JOON', 'manufacturer_upc': '5054227075452', 'meta_language': 'ENG', 'p_line': '2015 SABA JOON', 'preorder_date': None, 'product_code': 'AW5075452', 'previewable': None, 'product_id': 12345, 'product_name': 'Coffee', 'product_type_id': 1, 'project_id': 4884401, 'product_artists': [ { 'role': 'primary_artist', 'name': 'Saba Joon', 'id': 7734309, 'artist_info_id': 2627435, }, { 'role': 'featuring', 'name': 'FlyHats', 'id': 7734310, 'artist_info_id': 2627436 }, { 'role': 'featuring', 'name': 'Cobe Jones', 'id': 7734311, 'artist_info_id': 2627437 } ], 'delivered_version': None, 'release_date': '2015-08-18', 'release_status': 'in_content', 'is_error_correction_and_action_required': False, 'sale_start_date': '2015-08-18', 'special_instructions': None, 'subaccount_id': None, 'subgenre_id': 756, 'upc': '5054227075452', 'vendor_catalog_number': 'EXP272852', 'version': None, 'not_for_distribution': 'N', 'vendor_release_identifier': 'EXPV231215' }, { 'artist_id': 2627491, 'description': None, 'distribution_format_id': 1, 'format': 'Single', 'genre_id': 6, 'c_line': '2015 6Villa', 'imprint': '6Villa', 'manufacturer_upc': '5054227078088', 'meta_language': 'ENG', 'p_line': '2015 6Villa', 'preorder_date': None, 'product_code': 'AW5078088', 'previewable': None, 'product_id': 121346, 'product_name': 'Dope House', 'product_type_id': 1, 'project_id': 4884477, 'product_artists': [ { 'role': 'primary_artist', 'name': '6Villa', 'id': 7734390, 'artist_info_id': 2627491 } ], 'delivered_version': None, 'release_date': '2015-09-14', 'release_status': 'in_content', 'is_error_correction_and_action_required': False, 'sale_start_date': '2015-09-14', 'special_instructions': None, 'subaccount_id': None, 'subgenre_id': 739, 'upc': '5054227078088', 'vendor_catalog_number': 'EXP279151', 'version': None, 'not_for_distribution': 'N', 'vendor_release_identifier': 'EXPV238347' } ] return mock_response @pytest.fixture def get_project_response_message_with_subaccount(get_project_response_message): """Return project response with subaccount_id.""" get_project_response_message.update({'subaccount_id': 1}) get_project_response_message.update({'artist_id': 1212}) return get_project_response_message @pytest.fixture def product_id(): """Return a fake product id for testing.""" return 4321 @pytest.fixture def album_pricing_family_id(): """Return the integer for the pricing family for albums.""" return 2 @pytest.fixture def track_pricing_family_id(): """Return the integer for the pricing family for tracks.""" return 3 @pytest.fixture def product_basics_validation_error(): """Error message returned when product basics fails.""" return { 'product_name': { 'message': "'product_name' is a required property", 'validator': 'required', 'validator_value': True }, 'artist_id': { 'message': "'artist_id' is a required property", 'validator': 'required', 'validator_value': True } } @pytest.fixture def base_product_data_for_update(base_product_data_with_vendor): """Base product data for update requests.""" del base_product_data_with_vendor['project_id'] return base_product_data_with_vendor def _valid_validation_message(validation_type): """Validation message for validation type.""" return { validation_type: { 'valid': True, 'errors': {}}} def _invalid_validation_message(validation_type): """Validation message for validation type.""" return { validation_type: { 'valid': False, 'errors': {'error_message': 'some error'}}} @pytest.fixture def valid_product_basics_message(): """Validation message for product basics.""" return _valid_validation_message('product_basics') @pytest.fixture def invalid_product_basics_message(): """Validation message for product basics failure.""" return _invalid_validation_message('product_basics') @pytest.fixture def valid_product_basics_response(valid_product_basics_message): """Validation response for product dates.""" return response.Response(valid_product_basics_message) @pytest.fixture def valid_product_dates_message(): """Validation message for product dates.""" return _valid_validation_message('scheduling_and_pricing') @pytest.fixture def invalid_product_dates_message(): """Validation message for product dates failure.""" return _invalid_validation_message('scheduling_and_pricing') @pytest.fixture def valid_product_dates_response(valid_product_dates_message): """Validation response for product dates.""" return response.Response(valid_product_dates_message) @pytest.fixture def valid_spatial_upc_message(): """Validation message when spatial UPC is valid.""" return {'spatial_upc': {'valid': True, 'errors': []}} @pytest.fixture def valid_spatial_upc_response(valid_spatial_upc_message): """Validation response when spatial UPC is valid.""" return response.Response(valid_spatial_upc_message) @pytest.fixture def invalid_spatial_upc_message(): """Validation message when spatial UPC is invalid.""" return {'spatial_upc': { 'valid': False, 'errors': [{ 'code': 'SPATIAL_UPC_REQUIRED', 'reason': 'A spatial UPC record is required for products with spatial audio assets.', }], }} @pytest.fixture def valid_artwork_message(): """Validation message when artwork is valid.""" return {'artwork': {'valid': True, 'errors': []}} @pytest.fixture def invalid_artwork_message(): """Validation message when artwork is invalid.""" return {'artwork': {'valid': False, 'errors': ['Artwork Error']}} @pytest.fixture def valid_artwork_response(valid_artwork_message): """Validation message for artwork.""" return response.Response(valid_artwork_message) @pytest.fixture def valid_track_level_validations(): """Validation success tracks and publishing obligations.""" return { 'valid': True, 'total_tracks': 12, 'valid_tracks': 12, 'errors': []} @pytest.fixture def invalid_track_level_validations(): """Validation failure for tracks and publishing obligations.""" return { 'valid': False, 'total_tracks': 12, 'valid_tracks': 11, 'errors': [ {'tuid': 1, 'error_message': 'some error message'}] } @pytest.fixture def valid_tracks_message(valid_track_level_validations): """Validation message when tracks are valid.""" return {'tracks': valid_track_level_validations} @pytest.fixture def invalid_tracks_message(invalid_track_level_validations): """Validation messag ewhen tracks are invalid.""" return {'tracks': invalid_track_level_validations} @pytest.fixture def valid_tracks_response(valid_tracks_message): """Validation response for tracks.""" return response.Response(valid_tracks_message) @pytest.fixture def valid_get_tracks_response(): """get_track_by_product_id response for ows-track.""" return response.Response(message={ 'items': [{ 'tuid': 123, 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry VII'} ] }] }) @pytest.fixture def valid_publishing_obligation_message(valid_track_level_validations): """Validation message when publishing obligation is valid.""" return {'publishing_obligation': valid_track_level_validations} @pytest.fixture def invalid_publishing_obligation_message(invalid_track_level_validations): """Validation message when publishing obligation is valid.""" return {'publishing_obligation': invalid_track_level_validations} @pytest.fixture def valid_publishing_obligation_response(valid_publishing_obligation_message): """Validation response for publishing obligation.""" return response.Response(valid_publishing_obligation_message) @pytest.fixture def valid_validate_product_blocklist_response(): """Validation response when product has no blocked words.""" return response.Response(_valid_validation_message('product_blocklist')) @pytest.fixture def invalid_validate_product_blocklist_response(): """Validation response when product has blocked words.""" return response.Response(_invalid_validation_message('product_blocklist')) @pytest.fixture def valid_validate_account_response(): """Validation response for account with no errors.""" return response.Response({ 'account': { 'valid': True, 'errors': [] } }) @pytest.fixture def invalid_validate_account_response(): """Validation response for account with errors.""" return response.Response({ 'account': { 'valid': False, 'errors': [ { 'code': 'account_blocklist', 'reason': ( 'This content is from a blocked account and cannot be approved. ' 'Please reach out to QC, Operations or Contract Admin for more information.' ) } ], } }) @pytest.fixture def valid_validate_product_artists_response(): """Validation response for account with no errors.""" return response.Response({ 'product_artists': { 'valid': True, 'errors': [], 'warnings': [] } }) @pytest.fixture def invalid_validate_product_artists_response(): """Validation response for account with errors.""" return response.Response({ 'product_artists': { 'valid': True, 'errors': [], 'warnings:': [ { 'code': 'VARIOUS_ARTIST_MISMATCH', 'reason': 'If "Various Artists" is listed as a product primary artist,' ' there must be more than 3 unique artists on the track level' } ] } }) @pytest.fixture def expected_validation_message( valid_product_basics_message, valid_product_dates_message, valid_artwork_message, valid_tracks_message, valid_publishing_obligation_message): """Expected complete validation message.""" validation_dict = {} validation_dict.update(valid_product_basics_message) validation_dict.update(valid_product_dates_message) validation_dict.update(valid_artwork_message) validation_dict.update(valid_tracks_message) validation_dict.update(valid_publishing_obligation_message) return { 'validations': validation_dict} @pytest.fixture def setup_validation_mocks_as_valid( product_model_response, valid_product_basics_response, valid_validate_product_artists_response, valid_product_dates_response, valid_artwork_response, valid_tracks_response, valid_publishing_obligation_response, product_id, orchard_user_id, project_model_response, valid_get_tracks_response): """Run Flexmock setup for validations call when all is valid.""" # Begin mocks for product_digital.validation.audio_product.validate_account (flexmock(audio_product) .should_receive('get_product') .with_args(product_id) .and_return(product_model_response)) (flexmock(project) .should_receive('get_project_by_id') .with_args(5678) .and_return(project_model_response)) # End mocks for product_digital.validation.audio_product.validate_account (flexmock(audio_product) .should_receive('get_product') .with_args(product_id, orchard_user_id) .and_return(product_model_response)) (flexmock(ows_track) .should_receive('get_tracks_by_product_id') .and_return(valid_get_tracks_response)) (flexmock(product_validation).should_receive( 'validate_product_basics').and_return(valid_product_basics_response)) (flexmock(product_validation) .should_receive('validate_product_artists') .and_return(valid_validate_product_artists_response)) (flexmock(product_validation).should_receive( 'validate_product_dates').and_return(valid_product_dates_response)) (flexmock(product_validation) .should_receive('validate_artwork') .and_return(valid_artwork_response)) (flexmock(product_validation).should_receive( 'validate_tracks').and_return(valid_tracks_response)) (flexmock(product_validation).should_receive( 'validate_publishing_obligation').and_return( valid_publishing_obligation_response)) (flexmock(neo4j_vendor).should_receive( 'get_recent_awal_artist').and_return('')) @pytest.fixture def new_product_release_data( base_product_data_with_vendor, get_project_response_message ): """Model data used to create a new product.""" release_data = {} release_data.update(base_product_data_with_vendor) release_data.update({ 'artist_id': get_project_response_message.get('artist_id'), 'distribution_format_id': 1, 'product_type_id': 1, 'release_status': 'label_processing', 'vendor_catalog_number': get_project_response_message.get( 'project_code'), 'upc': None }) return release_data @pytest.fixture def validation_400_response(): """Return a mock response representing a failed completeness check.""" return response.Response({ 'validations': { 'scheduling_and_pricing': { 'valid': True, 'errors': {} }, 'publishing_obligation': { 'valid_tracks': 1, 'valid': True, 'total_tracks': 1, 'errors': [] }, 'tracks': { 'valid_tracks': 1, 'valid': True, 'total_tracks': 1, 'errors': [] }, 'artwork': { 'valid': True, 'errors': [] }, 'product_basics': { 'valid': False, 'errors': { 'primary_artist': { 'message': "'primary_artist' is a required property", 'validator': 'required', 'validator_value': True } } } } }) @pytest.fixture def product_workflow_unsubmit_400_response(): """Return an error response emulating a failed product workflow request.""" return response.create_error_response( status=400, code='ows_product_workflow_error', message='Product is not checked_in') @pytest.fixture def emit_digital_product_event_mock(mocker): """Mock product_events.emit_digital_product_event call.""" return mocker.patch.object(product_events, 'emit_digital_product_event') def test_create_with_upc( base_product_data_with_vendor, expected_create_product_response_message, get_project_response_message, mocker, new_product_release_data, test_request_context ): """Check if upc available when included in create payload.""" mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.Response()) mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message)) mocker.patch.object( ows_product, 'check_upc_available', return_value=response.Response()) mocker.patch.object( audio_product, 'create', return_value=response.Response( status=201, message=expected_create_product_response_message)) mocker.patch.object( release_status, 'create', return_value=response.Response()) mocker.patch.object( product, '_return_upc_value_and_update_status', return_value='111111111111') mocker.patch.object( product_provided_store_artists, 'create', return_value=response.Response()) explicit_upc = expected_create_product_response_message.get('upc') base_product_data_with_vendor.update({'upc': explicit_upc}) new_product_release_data.update({'upc': explicit_upc}) product_response = product.create(base_product_data_with_vendor) project.get_project_by_id.assert_called_with( get_project_response_message.get('project_id')) audio_product.create.assert_called_with(new_product_release_data) product._return_upc_value_and_update_status.assert_not_called() assert release_status.create.called assert product_response.status == 201 assert product_response.message == expected_create_product_response_message def test_emit_event_on_create( mocker, test_request_context, base_product_data_with_vendor, expected_create_product_response_message, emit_digital_product_event_mock): """Test event is sent on product create.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( product_validation, 'validate_product_on_create', return_value=response.Response()) mocker.patch.object( product, '_create_product_models', return_value=response.Response( message=expected_create_product_response_message)) product.create(base_product_data_with_vendor) emit_digital_product_event_mock.assert_called_once_with( product_id=expected_create_product_response_message['product_id'], upc=expected_create_product_response_message['upc'], product_name=base_product_data_with_vendor.get('product_name'), operation_type=product_events.OperationType.CREATE, operation_context=product_events.OperationContext.NEW) def test_create_without_upc( base_product_data_with_vendor, expected_create_product_response_message, get_project_response_message, mocker, new_product_release_data, test_request_context ): """Test correct payload and response on product create, upc missing.""" mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.Response()) mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message)) mocker.patch.object( audio_product, 'create', return_value=response.Response( status=201, message=expected_create_product_response_message)) mocker.patch.object( release_status, 'create', return_value=response.Response()) provisioned_upc = '111111111111' mocker.patch.object( product, '_return_upc_value_and_update_status', return_value=provisioned_upc) mocker.patch.object( product_provided_store_artists, 'create', return_value=response.Response()) del base_product_data_with_vendor['upc'] new_product_release_data.update({'upc': provisioned_upc}) product_response = product.create(base_product_data_with_vendor) audio_product.create.assert_called_with(new_product_release_data) assert product._return_upc_value_and_update_status.called assert product_response.status == 201 assert product_response.message == expected_create_product_response_message def test_create_upc_is_none( base_product_data_with_vendor, expected_create_product_response_message, get_project_response_message, new_product_release_data, mocker, test_request_context ): """Test correct payload and response on product create, upc = None.""" mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.Response()) mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message)) mocker.patch.object( audio_product, 'create', return_value=response.Response( status=201, message=expected_create_product_response_message)) mocker.patch.object( release_status, 'create', return_value=response.Response()) provisioned_upc = '111111111111' mocker.patch.object( product, '_return_upc_value_and_update_status', return_value=provisioned_upc) mocker.patch.object( product_provided_store_artists, 'create', return_value=response.Response()) base_product_data_with_vendor['upc'] = None new_product_release_data.update({'upc': provisioned_upc}) product_response = product.create(base_product_data_with_vendor) audio_product.create.assert_called_with(new_product_release_data) assert product_response.status == 201 assert product_response.message == expected_create_product_response_message def test_create_with_upc_but_upc_taken( base_product_data_with_vendor, mocker, upc_not_available, test_request_context ): """Test correct payload and response on product create.""" mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.Response()) mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( ows_product, 'check_upc_available', return_value=response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message=error.ERROR_MESSAGE_UPC_NOT_AVAILABLE, status=403)) mocker.patch.object(release, 'create') mocker.patch.object(release_status, 'create') product_response = product.create(base_product_data_with_vendor) release.create.assert_not_called() release_status.create.assert_not_called() assert product_response.status == 400 assert product_response.errors.get('message') == upc_not_available def test_create_unsuccessful( base_product_data_with_vendor, mocker, get_project_response_message, test_request_context ): """Test that an error message is returned when create fails.""" mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.Response()) mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message)) mocker.patch.object( ows_product, 'check_upc_available', return_value=response.Response()) mocker.patch.object( release, 'create', return_value=response.create_fatal_response()) mocker.patch.object(release_status, 'create') product_response = product.create(base_product_data_with_vendor) release_status.create.assert_not_called() assert product_response.status == 500 @pytest.mark.parametrize('project_status,product_expected_status', [(403, 403), (500, 500)]) def test_create_when_ownership_check_unsuccessful( base_product_data_with_vendor, mocker, product_expected_status, project_status, test_request_context ): """Test correct payload and response on product create.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.Response(status=project_status)) mocker.patch.object(release, 'create') mocker.patch.object(release_status, 'create') product_response = product.create(base_product_data_with_vendor) release.create.assert_not_called() release_status.create.assert_not_called() assert product_response.status == product_expected_status @pytest.mark.parametrize('project_status,product_expected_status', [(400, 400), (500, 500)]) def test_create_when_get_project_unsuccessful( base_product_data_with_vendor, mocker, product_expected_status, project_status, test_request_context ): """Test correct payload and response on product create.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response(status=project_status)) mocker.patch.object(release, 'create') mocker.patch.object(release_status, 'create') mocker.patch.object( product_validation, 'validate_product_on_create', return_value=response.Response() ) product_response = product.create(base_product_data_with_vendor) release.create.assert_not_called() release_status.create.assert_not_called() assert product_response.status == product_expected_status def test_create_when_all_account_info_missing( base_product_data, test_request_context): """Test that bad request error returned when missing account info.""" product_response = product.create(base_product_data) assert product_response.status == 400 product_response_error = product_response.errors.get('message') assert product_response_error == 'No vendor or subaccount given' def test_create_when_single_account_info_prop_missing( base_product_data, test_request_context): """Test that bad request error returned when missing account info.""" base_product_data['account_type'] = 'vendor' product_response = product.create(base_product_data) assert product_response.status == 400 product_response_error = product_response.errors.get('message') assert product_response_error == 'No vendor or subaccount given' def test_create_product_code_is_not_available( mocker, base_product_data_with_vendor, get_project_response_message, product_code_not_available, test_request_context): """Return 400 if product code is not available.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response()) mocker.patch.object( ows_product, 'check_upc_available', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message)) mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.create_error_response( 'some_code', error.ERROR_MESSAGE_PRODUCT_CODE_NOT_AVAILABLE)) product_response = product.create(base_product_data_with_vendor) assert product_response.status == 400 assert product_response.errors == { 'code': error.ERROR_CODE_BAD_REQUEST, 'message': product_code_not_available} def test_create_product_code_and_upc_are_not_available( mocker, base_product_data_with_vendor, get_project_response_message, upc_not_available, product_code_not_available, test_request_context): """Return 400 if product code is not available.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message)) mocker.patch.object( ows_product, 'check_upc_available', return_value=response.create_error_response( 'some_code', error.ERROR_MESSAGE_UPC_NOT_AVAILABLE, 403)) mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.create_error_response( 'some_code', error.ERROR_MESSAGE_PRODUCT_CODE_NOT_AVAILABLE, 400)) product_response = product.create(base_product_data_with_vendor) expected_message = upc_not_available.copy() expected_message.update(product_code_not_available) assert product_response.status == 400 assert product_response.errors == { 'code': error.ERROR_CODE_BAD_REQUEST, 'message': expected_message} def test_copy_product_invalid_product_id(product_id, base_release_data): """Ensure that not valid product id is not being copied.""" invalid_response = response.create_error_response( code='not_found', message='Release not found') flexmock(release).should_receive('get_release').and_return( invalid_response) result = product.copy_product(product_id, base_release_data) assert not result assert result.errors['code'] == invalid_response.errors['code'] assert result.errors['message'] == invalid_response.errors['message'] def test_copy_product_excludes_fields( copy_product_basics, expected_create_product_response_message, get_project_response_message, product_id, test_request_context ): """Test that excluded fields are not copied over.""" excluded_fields = ( 'product_id', 'upc', 'display_upc', 'product_code', 'format', 'version', ) release_subgenre_data = { 'release_subgenre_id': 1, 'upc': 987654321, 'subgenre_id': 1, 'release_id': 1 } copy_subgenre_reposne = response.Response(message=release_subgenre_data) artist_1 = { 'release_artist_id': 1, 'release_id': product_id, 'upc': 12345678, 'role': 'performer', 'artist_name': 'Test artist'} release_artists_data = [artist_1] release_artist_response = response.Response( message=release_artists_data) new_product_id = 2 create_data = copy.deepcopy(expected_create_product_response_message) create_data['product_id'] = new_product_id create_data_response = response.Response(message=create_data) expected_new_product_data = copy.deepcopy( expected_create_product_response_message) for field in excluded_fields: expected_new_product_data[field] = None expected_new_product_data.update(copy_product_basics) expected_new_product_data['release_status'] = 'label_processing' release_response = response.Response( message=expected_create_product_response_message) (flexmock(audio_product) .should_receive('get_product') .with_args(product_id) .and_return(release_response)) (flexmock(project) .should_receive('get_project_by_id') .with_args(expected_new_product_data['project_id']) .once() .and_return(response.Response(message=get_project_response_message))) (flexmock(audio_product) .should_receive('create') .with_args(expected_new_product_data) .once() .and_return(create_data_response)) (flexmock(product) .should_receive('copy_release_subgenre') .with_args(product_id, create_data) .and_return(copy_subgenre_reposne)) (flexmock(product) .should_receive('copy_release_artist') .with_args(product_id, create_data) .and_return(release_artist_response)) (flexmock(product) .should_receive('_create_release_status_entry') .with_args(new_product_id, 'label_processing', 9999, 'vendor') .once()) (flexmock(product_provided_store_artists) .should_receive('create') .and_return(response.Response())) result = product.copy_product(product_id, copy_product_basics) assert result def test_copy_product_returns_new_product_message( base_release_data, expected_create_release_response_message, get_project_response_message, product_id, test_request_context ): """Test error response is returned if fetching a copy has failed.""" release_response = response.Response( message=expected_create_release_response_message) (flexmock(audio_product) .should_receive('get_product') .and_return(release_response)) invalid_new_product = response.create_error_response( code='invalid_product', message='Invalid new product') (flexmock(audio_product) .should_receive('create') .and_return(invalid_new_product)) (flexmock(project) .should_receive('get_project_by_id') .and_return(response.Response(message=get_project_response_message))) result = product.copy_product(product_id, base_release_data) assert not result def test_emit_event_on_copy( mocker, test_request_context, product_id, base_product_data, expected_create_product_response_message, emit_digital_product_event_mock ): """Test event is sent on product copy.""" mocker.patch.object( audio_product, 'get_product', return_value=response.Response( message=dict())) mocker.patch.object( product, '_create_product_models', return_value=response.Response( message=expected_create_product_response_message)) mocker.patch.object( product, 'copy_release_subgenre', return_value=response.Response( message=dict())) mocker.patch.object( product, 'copy_release_artist', return_value=response.Response()) mocker.patch.object( audio_product, '_format_product_artists', return_value=response.Response()) product.copy_product(product_id, base_product_data) emit_digital_product_event_mock.assert_called_once_with( product_id=expected_create_product_response_message['product_id'], upc=expected_create_product_response_message['upc'], product_name=base_product_data.get('product_name'), operation_type=product_events.OperationType.CREATE, operation_context=product_events.OperationContext.COPY) def test_copy_release_subgenre(): """Test copy_release_subgenre.""" source_release_id = 1 dest_release_id = 2 dest_release_data = { 'product_id': dest_release_id, 'upc': 1234567} release_subgenre_data = { 'release_subgenre_id': 1, 'upc': 987654321, 'subgenre_id': 1, 'release_id': 1 } expected_call_data = copy.deepcopy(release_subgenre_data) expected_call_data.pop('release_subgenre_id', None) expected_call_data['upc'] = dest_release_data['upc'] expected_call_data['release_id'] = dest_release_id expected_save_release_return_data = copy.deepcopy(expected_call_data) save_release_response_data = copy.deepcopy( expected_save_release_return_data) save_release_response = response.Response( message=save_release_response_data) release_subgenre_response = response.Response( message=release_subgenre_data) (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(source_release_id) .and_return(release_subgenre_response)) (flexmock(release_subgenre) .should_receive('save_release_subgenre') .with_args(expected_call_data) .and_return(save_release_response) .once()) result = product.copy_release_subgenre( source_release_id, dest_release_data) assert result assert result.message == expected_save_release_return_data def test_copy_release_subgenre_invalid_response(): """Test copy_release_subgenre fails when subgenre response is invalid.""" source_release_id = 1 dest_release_id = 2 dest_release_data = { 'product_id': dest_release_id, 'upc': 1234567} release_subgenre_response = response.create_not_found_response( 'Not found') (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(source_release_id) .and_return(release_subgenre_response)) (flexmock(release_subgenre) .should_receive('save_release_subgenre') .never()) result = product.copy_release_subgenre( source_release_id, dest_release_data) assert not result def test_copy_release_artist(): """Test copy_release_artist.""" source_release_id = 1 dest_release_id = 2 dest_release_data = { 'product_id': dest_release_id, 'upc': 1234567} artist_1 = { 'release_artist_id': 1, 'release_id': source_release_id, 'upc': 12345678, 'role': 'performer', 'artist_name': 'Test artist'} artist_2 = copy.deepcopy(artist_1) artist_2['release_artist_id'] = 2 release_artists_data = [artist_1, artist_2] release_artist_response = response.Response( message=release_artists_data) expected_copy_response_data = copy.deepcopy(release_artists_data) copy_response_data = copy.deepcopy(expected_copy_response_data) copy_release_response = response.Response( message=copy_response_data) expected_artist_data = copy.deepcopy(release_artists_data) for artist_data in expected_artist_data: artist_data.pop('release_artist_id', None) (flexmock(release_artist) .should_receive('get_release_artists_by_release_id') .with_args(source_release_id) .and_return(release_artist_response) .once()) (flexmock(release_artist) .should_receive('save_for_release_id') .with_args( dest_release_id, dest_release_data['upc'], expected_artist_data) .and_return(copy_release_response) .once()) result = product.copy_release_artist( source_release_id, dest_release_data) assert result assert result.message == expected_copy_response_data def test_copy_release_artist_invalid_release_artist_response(): """Test copy_release_artist.""" source_release_id = 1 dest_release_id = 2 dest_release_data = { 'product_id': dest_release_id, 'upc': 1234567} release_artist_response = response.create_not_found_response( message='Invalid') (flexmock(release_artist) .should_receive('get_release_artists_by_release_id') .with_args(source_release_id) .and_return(release_artist_response) .once()) (flexmock(release_artist) .should_receive('save_for_release_id') .never()) result = product.copy_release_artist(source_release_id, dest_release_data) assert not result assert result.errors['message'] == 'Invalid' def test_update_audio_product_success( mocker, base_product_data_for_update, product_model_response, expected_product_logic_response_message, product_id,): """Test for correct payload and response status on successful update.""" mocker.patch.object( audio_product, 'update', return_value=product_model_response) mocker.patch.object(product_validation, 'validate_product_on_update', return_value=response.Response()) product_response = product.update_audio_product( product_id, base_product_data_for_update) assert product_response.status == 200 assert product_response.message == expected_product_logic_response_message def test_update_audio_product_success_for_oa( mocker, base_product_data_for_update, product_model_response, expected_product_logic_response_message, product_id,): """Test for correct payload and response status on successful update.""" mocker.patch.object( audio_product, 'update', return_value=product_model_response) mocker.patch.object(product_validation, 'validate_product_on_update', return_value=response.Response()) product_response = product.update_audio_product( product_id, base_product_data_for_update, 'oa:562') assert product_response.status == 200 assert product_response.message == expected_product_logic_response_message def test_update_audio_product_not_found( mocker, base_product_data_for_update, product_id): """Test that a 404 is returned when the product is not found.""" mocker.patch.object(product_validation, 'validate_product_on_update', return_value=response.Response()) mocker.patch.object( audio_product, 'update', return_value=response.Response(status=404)) product_response = product.update_audio_product( product_id, base_product_data_for_update) assert product_response.status == 404 def test_emit_event_on_update( mocker, base_product_data_for_update, product_id, expected_product_logic_response_message, emit_digital_product_event_mock): """Test event is sent on product update.""" mocker.patch.object( product_validation, 'validate_product_on_update', return_value=response.Response()) mocker.patch.object( audio_product, 'update', return_value=response.Response( message=expected_product_logic_response_message)) product.update_audio_product( product_id, base_product_data_for_update) emit_digital_product_event_mock.assert_called_once_with( product_id=expected_product_logic_response_message['product_id'], upc=expected_product_logic_response_message['upc'], product_name=expected_product_logic_response_message['product_name'], operation_type=product_events.OperationType.UPDATE) def test_update_audio_product_error( base_product_data_for_update, mocker, product_id): """Test that an error message and status are returned if update fails.""" mocker.patch.object(audio_product, 'update', return_value=response.create_fatal_response('uh-oh')) mocker.patch.object(product_validation, 'validate_product_on_update', return_value=response.Response()) product_response = product.update_audio_product( product_id, base_product_data_for_update) assert product_response.status == 500 assert product_response.errors == { 'code': 'internal_error', 'message': 'uh-oh'} def test_update_audio_product_validation_fails( base_product_data_with_vendor, mocker, product_id): """Test that update does not happen if validation fails.""" validation_error = response.Response(status=400, errors={'foo': 'bar'}) mocker.patch.object(product_validation, 'validate_product_on_update', return_value=validation_error) mocker.spy(audio_product, 'update') product_response = product.update_audio_product( product_id, base_product_data_with_vendor) assert product_response == validation_error assert audio_product.update.call_count == 0 def test_update_when_all_account_info_missing( product_id, base_product_data, mocker): """Test that bad request error returned when missing account info.""" mocker.patch.object(product_validation, 'validate_product_on_update', return_value=response.Response()) product_response = product.update_audio_product( product_id, base_product_data, orchard_user_id='alw:1') assert product_response.status == 400 product_response_error = product_response.errors.get('message') assert product_response_error == 'No vendor or subaccount given' def test_update_when_single_account_info_prop_missing( product_id, base_product_data, mocker): """Test that bad request error returned when missing account info.""" mocker.patch.object(product_validation, 'validate_product_on_update', return_value=response.Response()) base_product_data['account_type'] = 'vendor' product_response = product.update_audio_product( product_id, base_product_data, orchard_user_id='alw:1') assert product_response.status == 400 product_response_error = product_response.errors.get('message') assert product_response_error == 'No vendor or subaccount given' def test_update_audio_product_product_code_is_not_available( mocker, base_product_data_with_vendor, product_id, product_code_not_available): """Return 400 if product code is not available.""" mocker.patch.object( product_validation, 'validate_product_on_update', return_value=response.create_error_response( code=error.ERROR_CODE_BAD_REQUEST, message=product_code_not_available)) mocker.spy(audio_product, 'update') mocker.patch.object( release, 'get_release', return_value=response.Response(message={})) product_response = product.update_audio_product( product_id, base_product_data_with_vendor) assert product_response.status == 400 assert product_response.errors.get('message') == product_code_not_available assert audio_product.update.call_count == 0 def test_update_audio_product_date_blank( mocker, product_model_response_message, base_product_data_for_update, expected_product_logic_response_message): """Test a successful update of an audio product setting date to Null.""" base_product_data_for_update['preorder_date'] = None product_model_response_message['preorder_date'] = None expected_product_logic_response_message['preorder_date'] = None mocker.patch.object(product_validation, 'validate_product_on_update', return_value=response.Response()) mocker.patch.object( audio_product, 'update', return_value=response.Response( message=product_model_response_message)) product_response = product.update_audio_product( product_id, base_product_data_for_update) assert product_response.status == 200 assert product_response.message == expected_product_logic_response_message def test_get_audio_product( mocker, product_id, product_model_response, expected_product_logic_response_message): """Test that an audio product is returned with correct formatting.""" mocker.patch.object( audio_product, 'get_product', return_value=product_model_response) product_response = product.get_audio_product(product_id) assert product_response.status == 200 assert product_response.message == expected_product_logic_response_message def test_get_audio_product_not_found(mocker, product_id): """Test that a not found response is returned if a product isn't found.""" audio_product_response = response.create_not_found_response() mocker.patch.object( audio_product, 'get_product', return_value=audio_product_response) product_response = product.get_audio_product(product_id) assert product_response.status == 404 assert product_response.errors.get('code') == \ oto_error.ERROR_CODE_NOT_FOUND def test_get_audio_product_error(mocker, product_id): """Test that a not found response is returned if a product isn't found.""" audio_product_response = response.create_fatal_response() mocker.patch.object( audio_product, 'get_product', return_value=audio_product_response) product_response = product.get_audio_product(product_id) assert product_response.status == 500 assert product_response.errors.get('code') == \ oto_error.ERROR_CODE_INTERNAL_ERROR def test_validate_audio_product_when_valid( product_id, account_id, account_type, orchard_user_id, vendor_id, setup_validation_mocks_as_valid, expected_validation_message): """When valid returns a 200 response with expected payload.""" with app.test_request_context(): validate_audio_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) ) assert validate_audio_product_response.status == 200 assert validate_audio_product_response.message == \ expected_validation_message def test_validate_audio_product_artwork_options( product_id, account_id, account_type, orchard_user_id, vendor_id, setup_validation_mocks_as_valid, expected_validation_message, valid_artwork_response): """Test that check_v2_artwork are passed along.""" (flexmock(product_validation) .should_receive('validate_artwork') .once() .with_args( product_id) .and_return(valid_artwork_response)) with app.test_request_context(): result = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) ) assert result.status == 200 assert result.message == expected_validation_message def test_validation_when_basics_validation_failure( product_id, account_id, account_type, vendor_id, orchard_user_id, setup_validation_mocks_as_valid, invalid_product_basics_message): """Test basics validation failure.""" (flexmock(product_validation).should_receive( 'validate_product_basics').and_return( response.Response(invalid_product_basics_message))) with app.test_request_context(): validate_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) ) validations = validate_product_response.message.get('validations') assert validate_product_response.status == 200 assert validations.get('product_basics') == \ invalid_product_basics_message.get('product_basics') def test_basics_validation_failure_with_invalid_product_code( product_id, account_id, account_type, vendor_id, orchard_user_id, setup_validation_mocks_as_valid, invalid_product_basics_message): """Test validation failure with invalid product code.""" (flexmock(product_validation) .should_receive('validate_product_basics') .and_return(response.Response(invalid_product_basics_message))) with app.test_request_context(): validate_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) ) validations = validate_product_response.message.get('validations') assert validate_product_response.status == 200 assert validations.get('product_basics') == \ invalid_product_basics_message.get('product_basics') def test_validation_when_dates_validation_failure( product_id, account_id, account_type, vendor_id, orchard_user_id, setup_validation_mocks_as_valid, invalid_product_dates_message): """Test basics validation failure.""" (flexmock(product_validation).should_receive( 'validate_product_dates').and_return( response.Response(invalid_product_dates_message))) with app.test_request_context(): validate_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) ) validations = validate_product_response.message.get('validations') assert validate_product_response.status == 200 assert validations.get('product_dates') == \ invalid_product_dates_message.get('product_dates') def test_validation_when_track_validation_failure( product_id, account_id, account_type, vendor_id, orchard_user_id, setup_validation_mocks_as_valid, invalid_tracks_message): """Test tracks validation failure.""" (flexmock(product_validation).should_receive('validate_tracks').and_return( response.Response(invalid_tracks_message))) with app.test_request_context(): validate_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext()) validations = validate_product_response.message.get('validations') assert validate_product_response.status == 200 assert validations.get('tracks') == invalid_tracks_message.get('tracks') def test_validation_when_publishing_validation_failure( product_id, invalid_publishing_obligation_message, orchard_user_id, account_id, vendor_id, account_type, setup_validation_mocks_as_valid): """Test publishing obligation validation failure.""" (flexmock(product_validation).should_receive( 'validate_publishing_obligation').and_return( response.Response(invalid_publishing_obligation_message))) with app.test_request_context(): validate_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) ) validations = validate_product_response.message.get('validations') assert validate_product_response.status == 200 assert validations.get('publishing_obligation') == \ invalid_publishing_obligation_message.get('publishing_obligation') def test_validation_when_artwork_validation_failure( product_id, account_id, account_type, vendor_id, orchard_user_id, setup_validation_mocks_as_valid, invalid_artwork_message): """Test artwork validation failure.""" (flexmock(product_validation).should_receive( 'validate_artwork').and_return( response.Response(invalid_artwork_message))) with app.test_request_context(): validate_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) ) validations = validate_product_response.message.get('validations') assert validate_product_response.status == 200 assert validations.get('artwork') == invalid_artwork_message.get('artwork') def test_validation_when_spatial_upc_validation_failure( product_id, account_id, account_type, vendor_id, orchard_user_id, setup_validation_mocks_as_valid, invalid_spatial_upc_message): """Test spatial UPC validation failure.""" (flexmock(product_validation).should_receive( 'validate_spatial_upc').and_return( response.Response(invalid_spatial_upc_message))) with app.test_request_context(): ff_context = ValidateAudioProductFeatureFlagContext( vendor_id=vendor_id, spatial_upc_validation=True) validate_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ff_context, ) validations = validate_product_response.message.get('validations') assert validate_product_response.status == 200 assert validations.get('spatial_upc') == \ invalid_spatial_upc_message.get('spatial_upc') def test_validation_when_basics_and_tracks_validation_failure( product_id, account_id, account_type, vendor_id, orchard_user_id, setup_validation_mocks_as_valid, invalid_product_basics_message, invalid_tracks_message): """Test basics and publishing obligation validation failure.""" (flexmock(product_validation).should_receive( 'validate_product_basics').and_return( response.Response(invalid_product_basics_message))) (flexmock(product_validation).should_receive( 'validate_tracks').and_return( response.Response(invalid_tracks_message))) with app.test_request_context(): validate_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) ) validations = validate_product_response.message.get('validations') assert validate_product_response.status == 200 assert validations.get('product_basics') == \ invalid_product_basics_message.get('product_basics') assert validations.get('tracks') == invalid_tracks_message.get('tracks') def test_validate_audio_product_when_product_not_found( product_id, account_id, account_type, vendor_id, orchard_user_id, setup_validation_mocks_as_valid): """When product not found return 404.""" (flexmock(audio_product).should_receive('get_product') .with_args(product_id, orchard_user_id) .and_return(response.create_not_found_response())) with app.test_request_context(): validate_audio_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id)) assert validate_audio_product_response.status == 404 def test_validation_with_post_submission_invalid( account_id, account_type, orchard_user_id, product_id, vendor_id, invalid_validate_product_blocklist_response, invalid_validate_account_response, invalid_validate_product_artists_response, setup_validation_mocks_as_valid ): """Test post_submission validation with invalid result. Test that validation adds post_submission validation when requested and all post_submission validation types are not valid. """ (flexmock(product_validation) .should_receive('validate_product_blocklist') .and_return(invalid_validate_product_blocklist_response)) (flexmock(product_validation) .should_receive('validate_account') .and_return(invalid_validate_account_response)) (flexmock(product_validation) .should_receive('validate_product_artists') .and_return(invalid_validate_product_artists_response)) with app.test_request_context(): validate_audio_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, validation_context='post_submission', ff_context=ValidateAudioProductFeatureFlagContext( vendor_id=vendor_id, ) ) validations = validate_audio_product_response.message.get('validations') product_blocklist_message = invalid_validate_product_blocklist_response.message.get('product_blocklist') account_message = invalid_validate_account_response.message.get('account') primary_artist_message = invalid_validate_product_artists_response.message.get('product_artists') assert validate_audio_product_response.status == 200 assert validations.get('product_blocklist') == product_blocklist_message assert validations.get('account') == account_message assert validations.get('product_artists') == primary_artist_message def test_validation_with_post_submission_valid( account_id, account_type, orchard_user_id, product_id, vendor_id, valid_validate_product_blocklist_response, valid_validate_account_response, valid_validate_product_artists_response, setup_validation_mocks_as_valid ): """Test post_submission validation with valid result. Test that validation adds post_submission validation when requested and all post_submission validation types are valid. """ (flexmock(product_validation) .should_receive('validate_product_blocklist') .and_return(valid_validate_product_blocklist_response)) (flexmock(product_validation) .should_receive('validate_account') .and_return(valid_validate_account_response)) (flexmock(product_validation) .should_receive('validate_product_artists') .and_return(valid_validate_product_artists_response)) with app.test_request_context(): validate_audio_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, validation_context='post_submission', ff_context=ValidateAudioProductFeatureFlagContext( vendor_id=vendor_id, ) ) validations = validate_audio_product_response.message.get('validations') product_blocklist_message = valid_validate_product_blocklist_response.message.get('product_blocklist') account_message = valid_validate_account_response.message.get('account') primary_artist_message = valid_validate_product_artists_response.message.get('product_artists') assert validate_audio_product_response.status == 200 assert validations.get('product_blocklist') == product_blocklist_message assert validations.get('account') == account_message assert validations.get('product_artists') == primary_artist_message def test_validation_with_post_submission_artwork_metadata_warning( account_id, account_type, orchard_user_id, product_id, vendor_id, valid_validate_product_blocklist_response, valid_validate_account_response, valid_validate_product_artists_response, setup_validation_mocks_as_valid, ): """Test post_submission artwork metadata warnings are added to artwork compliance.""" artwork_metadata_warning = { 'code': 'ARTWORK_METADATA_MISMATCH', 'reason': json.dumps({ 'matches': [{ 'metadata_value': 'Track Name', 'metadata_field': 'track_name', 'fuzzy_matches_found': ['Trak Name'], }] }), } (flexmock(product_validation) .should_receive('validate_product_blocklist') .and_return(valid_validate_product_blocklist_response)) (flexmock(product_validation) .should_receive('validate_account') .and_return(valid_validate_account_response)) (flexmock(product_validation) .should_receive('validate_product_artists') .and_return(valid_validate_product_artists_response)) (flexmock(product_validation) .should_receive('validate_artwork_compliance') .and_return(response.Response({ 'artwork_compliance': { 'valid': True, 'errors': [], 'warnings': [artwork_metadata_warning], } }))) with app.test_request_context(): validate_audio_product_response = product.validate_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, validation_context='post_submission', ff_context=ValidateAudioProductFeatureFlagContext( vendor_id=vendor_id, ) ) validations = validate_audio_product_response.message.get('validations') assert validate_audio_product_response.status == 200 assert validations.get('artwork_compliance') == { 'valid': True, 'errors': [], 'warnings': [artwork_metadata_warning], } def test_expected_body_returned_for_get_product_scheduling_and_pricing( track_pricing_overrides_payload, product_pricing_tier_by_product, track_pricing_tier_by_product, product_pricing_overrides_payload, product_id, album_pricing_family_id, track_pricing_family_id, get_tiers_for_albums_message, get_tiers_for_tracks_message): """Test that ows_pricing functions are called to obtain product pricing.""" existing_release = release_factory.ReleaseFactory.build().to_dict() (flexmock(release).should_receive('get_release').and_return( response.Response(message=existing_release)).once()) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( album_pricing_family_id).and_return( response.Response(message=get_tiers_for_albums_message))) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( track_pricing_family_id).and_return( response.Response(message=get_tiers_for_tracks_message))) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, album_pricing_family_id).and_return( response.Response( message=product_pricing_tier_by_product)).once()) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, 3).and_return(response.Response( message=track_pricing_tier_by_product)).once()) (flexmock(ows_pricing).should_receive( 'get_active_product_overrides').and_return( response.Response(message=product_pricing_overrides_payload)).once()) (flexmock(ows_pricing).should_receive('get_track_overrides').and_return( response.Response(message=track_pricing_overrides_payload)).once()) pricing_and_scheduling_result = product.get_product_scheduling_and_pricing( product_id) tier_id = 'orchard_pricing_tier_id' expected_payoad = { 'sale_start_date': existing_release['sale_start_date'], 'release_date': existing_release['release_date'], 'preorder_date': existing_release['preorder_date'], 'previewable': existing_release['itunes_previewable'], 'album_pricing_tier': { 'id': product_pricing_tier_by_product.get(tier_id), 'name': product_pricing_tier_by_product.get('name')}, 'track_pricing_tier': { 'id': track_pricing_tier_by_product.get(tier_id), 'name': track_pricing_tier_by_product.get('name')}, 'album_override_items': product_pricing_overrides_payload.get('items'), 'track_override_items': track_pricing_overrides_payload.get('items')} assert pricing_and_scheduling_result.status == 200 assert pricing_and_scheduling_result.message == expected_payoad def test_error_result_when_error_from_get_release(product_id): """Test that an error is returned when error from release.get_release.""" (flexmock(release).should_receive('get_release').and_return( response.create_fatal_response()).once()) pricing_and_scheduling_result = product.get_product_scheduling_and_pricing( product_id) assert pricing_and_scheduling_result.status == 500 assert pricing_and_scheduling_result.errors == { 'code': 'internal_error', 'message': None} def test_error_result_when_error_from_get_album_pricing_tier( get_tiers_for_albums_message, get_tiers_for_tracks_message, product_id, album_pricing_family_id, track_pricing_family_id): """Test that an error is returned when error from get album tier.""" existing_release = release_factory.ReleaseFactory.build().to_dict() (flexmock(release).should_receive('get_release').and_return( response.Response(message=existing_release)).once()) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( album_pricing_family_id).and_return( response.Response(message=get_tiers_for_albums_message))) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( track_pricing_family_id).and_return( response.Response(message=get_tiers_for_tracks_message))) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, 2).and_return( response.create_fatal_response()).once()) pricing_and_scheduling_result = product.get_product_scheduling_and_pricing( product_id) assert pricing_and_scheduling_result.status == 500 assert pricing_and_scheduling_result.errors == { 'code': 'internal_error', 'message': None} def test_error_result_when_error_from_get_track_pricing_tier( product_pricing_tier_payload, get_tiers_for_albums_message, get_tiers_for_tracks_message, product_id, album_pricing_family_id, track_pricing_family_id): """Test that an error is returned when error from get track tier.""" existing_release = release_factory.ReleaseFactory.build().to_dict() (flexmock(release).should_receive('get_release').and_return( response.Response(message=existing_release)).once()) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( album_pricing_family_id).and_return( response.Response(message=get_tiers_for_albums_message))) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( track_pricing_family_id).and_return( response.Response(message=get_tiers_for_tracks_message))) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, 2).and_return(response.Response( message=product_pricing_tier_payload)).once()) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, 3).and_return( response.create_fatal_response()).once()) pricing_and_scheduling_result = product.get_product_scheduling_and_pricing( product_id) assert pricing_and_scheduling_result.status == 500 assert pricing_and_scheduling_result.errors == { 'code': 'internal_error', 'message': None} def test_error_result_when_error_from_get_active_product_overrides( product_pricing_tier_payload, track_pricing_tier_payload, get_tiers_for_albums_message, get_tiers_for_tracks_message, product_id, album_pricing_family_id, track_pricing_family_id): """Test that an error is returned when error from get product overrides.""" existing_release = release_factory.ReleaseFactory.build().to_dict() (flexmock(release).should_receive('get_release').and_return( response.Response(message=existing_release)).once()) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( album_pricing_family_id).and_return( response.Response(message=get_tiers_for_albums_message))) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( track_pricing_family_id).and_return( response.Response(message=get_tiers_for_tracks_message))) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, 2).and_return(response.Response( message=product_pricing_tier_payload)).once()) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, 3).and_return(response.Response( message=track_pricing_tier_payload)).once()) (flexmock(ows_pricing).should_receive( 'get_active_product_overrides').and_return( response.create_fatal_response()).once()) pricing_and_scheduling_result = product.get_product_scheduling_and_pricing( product_id) assert pricing_and_scheduling_result.status == 500 assert pricing_and_scheduling_result.errors == { 'code': 'internal_error', 'message': None} def test_error_result_when_error_from_get_track_overrides( product_pricing_tier_payload, track_pricing_tier_payload, product_pricing_overrides_payload, product_id, album_pricing_family_id, get_tiers_for_albums_message, get_tiers_for_tracks_message, track_pricing_family_id): """Test that an error is returned when error from get track overrides.""" existing_release = release_factory.ReleaseFactory.build().to_dict() (flexmock(release).should_receive('get_release').and_return( response.Response(message=existing_release)).once()) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( album_pricing_family_id).and_return( response.Response(message=get_tiers_for_albums_message))) (flexmock(ows_pricing).should_receive('get_pricing_tiers').with_args( track_pricing_family_id).and_return( response.Response(message=get_tiers_for_tracks_message))) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, 2).and_return(response.Response( message=product_pricing_tier_payload)).once()) (flexmock(ows_pricing).should_receive( 'get_pricing_tier_by_product').with_args( product_id, 3).and_return(response.Response( message=track_pricing_tier_payload)).once()) (flexmock(ows_pricing).should_receive( 'get_active_product_overrides').and_return( response.Response(message=product_pricing_overrides_payload)).once()) (flexmock(ows_pricing).should_receive('get_track_overrides').and_return( response.create_fatal_response()).once()) pricing_and_scheduling_result = product.get_product_scheduling_and_pricing( product_id) assert pricing_and_scheduling_result.status == 500 assert pricing_and_scheduling_result.errors == { 'code': 'internal_error', 'message': None} @db.test_schema def test_submit_audio_product_success( account_id, account_type, product_id, project_id, test_request_context, orchard_user_id, vendor_id, request_engine): """Test that a successful response is returned.""" identity_id = 'valid-identity' request_engine[services.OWS_PRODUCT_REVIEW].add_spec( 'GET', '/review-queue/product/4321/eligibility-check', 'ineligible') ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) (flexmock(product) .should_receive('validate_audio_product') .with_args( product_id, account_type, account_id, vendor_id, orchard_user_id, ff_context=ff_context) .and_return(response.Response(message={'validations': []}))) (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='label_processing', project_id=project_id, release_id=product_id))) (flexmock(project) .should_receive('get_project_by_id') .with_args(project_id) .and_return(response.Response(message={'vendor_id': account_id}))) (flexmock(ows_product_workflow) .should_receive('product_submission') .with_args(product_id, account_type, account_id, identity_id=identity_id) .and_return(response.Response(message={ 'release_approval': { 'release_approval_id': 1, 'release_id': product_id, 'status': 'checked_in' }}))) (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(product_id).and_return(False)) (flexmock(release_status).should_receive('create')) result = product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, identity_id=identity_id, ff_context=ff_context ) assert result.status == 200 assert result.message['release_status'] == 'transfer_to_content' @db.test_schema def test_submit_audio_product_fail( account_id, account_type, product_id, project_id, test_request_context, orchard_user_id, vendor_id, request_engine): """Test that an error response is returned.""" identity_id = 'valid-identity' request_engine[services.OWS_PRODUCT_REVIEW].add_spec( 'GET', '/review-queue/product/4321/eligibility-check', 'ineligible') ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) (flexmock(product) .should_receive('validate_audio_product') .with_args( product_id, account_type, account_id, vendor_id, orchard_user_id, ff_context=ff_context) .and_return(response.Response(message={'validations': []}))) (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='label_processing', project_id=project_id, release_id=product_id))) (flexmock(project) .should_receive('get_project_by_id') .with_args(project_id) .and_return(response.Response(message={'vendor_id': account_id}))) (flexmock(ows_product_workflow) .should_receive('product_submission') .with_args(product_id, account_type, account_id, identity_id=identity_id) .and_return(response.Response(status=400, message='oopsies'))) (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(product_id).and_return(False)) (flexmock(release_status).should_receive('create')) result = product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, identity_id=identity_id, ff_context=ff_context ) assert result.status == 400 assert result.message == 'oopsies' def test_emit_event_on_submit( mocker, product_id, account_type, account_id, orchard_user_id, vendor_id, expected_product_logic_response_message, emit_digital_product_event_mock): """Test event is sent on product submit.""" mocker.patch.object( product, 'validate_audio_product', return_value=response.Response(message={'validations': []})) mocker.patch.object( product, 'product_submission_workflow', return_value=response.Response( message=expected_product_logic_response_message)) product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext() ) emit_digital_product_event_mock.assert_called_once_with( product_id=expected_product_logic_response_message['product_id'], upc=expected_product_logic_response_message['upc'], product_name=expected_product_logic_response_message['product_name'], operation_type=product_events.OperationType.UPDATE, operation_context=product_events.OperationContext.SUBMIT) @db.test_schema def test_submit_audio_product_success_error_correction( account_id, account_type, product_id, project_id, orchard_user_id, vendor_id, request_engine, test_request_context ): """Ensure response contains correction details.""" identity_id = 'valid-identity' request_engine[services.OWS_PRODUCT_REVIEW].add_spec( 'GET', '/review-queue/product/4321/eligibility-check', 'ineligible') ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) (flexmock(product) .should_receive('validate_audio_product') .with_args( product_id, account_type, account_id, vendor_id, orchard_user_id, ff_context=ff_context) .and_return(response.Response(message={'validations': []}))) (flexmock(release) .should_receive('get_release_instance') .and_return( MagicMock(release_status='label_processing', project_id=project_id, release_id=product_id))) (flexmock(project) .should_receive('get_project_by_id') .with_args(project_id) .and_return(response.Response(message={'vendor_id': account_id}))) correction = { 'release_correction_id': 1, 'status': 'submitted', 'items': [ { 'release_correction_detail_id': 1, 'table_name': 'releases', 'field_name': 'primary', 'key_id': 288212, 'key_value': 'HEY HEY HEY' }, { 'release_correction_detail_id': 2, 'table_name': 'releases', 'field_name': 'primary', 'key_id': 288213, 'key_value': 'EYH EYH EYH' }, { 'release_correction_detail_id': 3, 'table_name': 'releases', 'field_name': 'primary', 'key_id': 288214, 'key_value': 'HYE HYE HYE' } ] } (flexmock(ows_product_workflow) .should_receive('product_submission') .with_args(product_id, account_type, account_id, identity_id=identity_id) .and_return(response.Response(message={ 'latest_correction': correction, 'release_approval': { 'release_approval_id': 1, 'release_id': product_id, 'status': 'checked_in' } }) )) (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(product_id).and_return(False)) (flexmock(release_status).should_receive('create')) result = product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, identity_id=identity_id, ff_context=ff_context ) assert result.status == 200 assert result.message['release_status'] == 'transfer_to_content' assert result.message['corrections'] == correction @db.test_schema def test_submit_audio_product_v2_artwork_enabled( account_id, account_type, product_id, project_id, orchard_user_id, vendor_id, request_engine, test_request_context): """Test that v2 artwork is checked and v1 artwork is also required.""" identity_id = 'valid-identity' request_engine[services.OWS_PRODUCT_REVIEW].add_spec( 'GET', '/review-queue/product/4321/eligibility-check', 'ineligible') ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) (flexmock(product) .should_receive('validate_audio_product') .with_args( product_id=product_id, account_type=account_type, account_id=account_id, vendor_id=vendor_id, orchard_user_id=orchard_user_id, ff_context=ff_context) .and_return(response.Response(message={'validations': []}))) (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='label_processing', project_id=project_id, release_id=product_id))) (flexmock(project) .should_receive('get_project_by_id') .with_args(project_id) .and_return(response.Response(message={'vendor_id': account_id}))) (flexmock(ows_product_workflow) .should_receive('product_submission') .with_args( product_id, account_type, account_id, identity_id=identity_id) .and_return(response.Response(message={ 'release_approval': { 'release_approval_id': 1, 'release_id': product_id, 'status': 'checked_in' }}))) (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(product_id).and_return(False)) (flexmock(release_status).should_receive('create')) result = product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, identity_id=identity_id, ff_context=ff_context ) assert result.status == 200 assert result.message['release_status'] == 'transfer_to_content' @db.test_schema def test_submit_audio_product_v2_artwork_submission_enabled( account_id, account_type, product_id, project_id, orchard_user_id, vendor_id, test_request_context): """Test that v2 artwork is checked and v1 artwork is also required.""" identity_id = 'valid-identity' ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) (flexmock(product) .should_receive('validate_audio_product') .with_args( product_id=product_id, account_type=account_type, account_id=account_id, vendor_id=vendor_id, orchard_user_id=orchard_user_id, ff_context=ff_context) .and_return(response.Response(message={'validations': []}))) (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='label_processing', project_id=project_id, release_id=product_id))) (flexmock(project) .should_receive('get_project_by_id') .with_args(project_id) .and_return(response.Response(message={'vendor_id': account_id}))) (flexmock(ows_product_workflow) .should_receive('product_submission') .with_args( product_id, account_type, account_id, identity_id=identity_id) .and_return(response.Response(message={ 'release_approval': { 'release_approval_id': 1, 'release_id': product_id, 'status': 'checked_in' }}))) (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(product_id).and_return(False)) (flexmock(release_status).should_receive('create')) (flexmock(request) .should_receive('get') .with_args( services.OWS_PRODUCT_REVIEW, '/review-queue/product/4321/eligibility-check') .and_return(test_utils.MockOwsResponse(status_code=200, json='eligible'))) result = product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, identity_id=identity_id, ff_context=ff_context ) assert result.status == 200 assert result.message['release_status'] == 'transfer_to_content' def test_submit_audio_product_incomplete_v2_artwork_enabled( account_id, account_type, product_id, project_id, orchard_user_id, vendor_id, validation_400_response): """Test that a 400 is returned along with the full set of validations.""" (flexmock(product) .should_receive('validate_audio_product') .and_return(validation_400_response)) result = product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, ff_context=ValidateAudioProductFeatureFlagContext() ) assert result.status == 400 assert result.errors == { 'code': 'validation_error', 'message': validation_400_response.message } @db.test_schema def test_diyfilter_submit_diy_audio_product_success( mocker, account_id, account_type, product_id, project_id, orchard_user_id, vendor_id, test_request_context, request_engine): """Test that release is not added to the approval queue if it's DIY.""" mock_validation_response = MagicMock(status_code=200) mock_validation_response.json = MagicMock(return_value={}) mocker.patch.object(request, 'get', return_value=mock_validation_response) ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) (flexmock(product) .should_receive('validate_audio_product') .with_args( product_id, account_type, account_id, vendor_id, orchard_user_id, ff_context=ff_context) .and_return(response.Response(message={'validations': []}))) (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='label_processing', project_id=project_id, release_id=product_id))) (flexmock(project) .should_receive('get_project_by_id') .with_args(project_id) .and_return(response.Response(message={'vendor_id': account_id}))) (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(product_id).and_return(False)) (flexmock(release_status).should_receive('create')) mocker.patch.object( test_utils, 'MockOwsResponse', new=MagicMock( return_value=NonCallableMagicMock( status_code=200, json=MagicMock( side_effect=requests.exceptions.JSONDecodeError( 'oopsies', '', 0), ) ), ), ) request_engine[services.OWS_PRODUCT_WORKFLOW].add_spec( 'POST', '/product/{}/submission'.format(product_id)) result = product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, ff_context=ff_context ) assert result.status == 200 assert result.message['release_status'] == 'transfer_to_content' @db.test_schema def test_diyfilter_submit_regular_audio_product_success( mocker, account_id, account_type, product_id, project_id, orchard_user_id, vendor_id, test_request_context): """Test that release is processed as usual if it's not DIY.""" identity_id = 'valid-identity' mock_validation_response = MagicMock(status_code=200) mock_validation_response.json = MagicMock(return_value={}) mocker.patch.object(request, 'get', return_value=mock_validation_response) ff_context=ValidateAudioProductFeatureFlagContext(vendor_id=vendor_id) (flexmock(product) .should_receive('validate_audio_product') .with_args( product_id, account_type, account_id, vendor_id, orchard_user_id=orchard_user_id, ff_context=ff_context) .and_return(response.Response(message={'validations': []}))) (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='label_processing', project_id=project_id, release_id=product_id))) (flexmock(project) .should_receive('get_project_by_id') .with_args(project_id) .and_return(response.Response(message={'vendor_id': account_id}))) (flexmock(request) .should_receive('get') .with_args( services.OWS_PRODUCT_REVIEW, '/review-queue/product/4321/eligibility-check') .and_return(test_utils.MockOwsResponse(status_code=200, json='eligible'))) (flexmock(ows_product_workflow) .should_receive('product_submission') .with_args(product_id, account_type, account_id, identity_id=identity_id) .and_return(response.Response(message={ 'release_approval': { 'release_approval_id': 1, 'release_id': product_id, 'status': 'checked_in' }}))) (flexmock(release_subgenre) .should_receive('get_release_subgenre_by_release_id') .with_args(product_id).and_return(False)) (flexmock(release_status).should_receive('create')) result = product.submit_audio_product( product_id, account_type, account_id, vendor_id, orchard_user_id, identity_id=identity_id, ff_context=ff_context ) assert result.status == 200 assert result.message['release_status'] == 'transfer_to_content' def test_format_instant_grats_response(instant_grats_message): """Test util function formatting instant grats response.""" tracks_data = { '1': {'track_name': 'Track'}, '3': {'track_name': 'Track3'}} expected_result = { 'items': [{'stores': [ {'store_id': 1, 'store_name': 'Store1'}, {'store_id': 2, 'store_name': 'Store2'}], 'track': {'track_name': 'Track', 'tuid': '1'}, 'date': '2017-09-26T22:27:52.695829'}, {'stores': [{'store_id': 1, 'store_name': 'Store1'}], 'track': {'track_name': 'Track3', 'tuid': '3'}, 'date': '2017-09-26T22:27:52.29'}, {'stores': [{'store_id': 2, 'store_name': 'Store2'}], 'track': {'track_name': 'Track3', 'tuid': '3'}, 'date': '2018-09-26T22:27:52.695829'}]} stores_data = {'1': 'Store1', '2': 'Store2'} result = product._format_instant_grats_response( instant_grats_message, tracks_data, stores_data) assert result.message == expected_result def test_get_product_instant_grats_track_data_not_found( instant_grats_message): """Test get_product_instant_grats if track data for a grat not found.""" get_grats_response = response.Response(instant_grats_message) error_msg = 'No data for provided track found' product_id = 1 tuid_without_data = instant_grats_message.get('items')[0]['tuid'] tuid_not_fetched = instant_grats_message.get('items')[1]['tuid'] get_track_response = response.create_not_found_response(error_msg) (flexmock(ows_track) .should_receive('get_track_by_track_id') .with_args(track_id=tuid_without_data) .and_return(get_track_response)) (flexmock(ows_track) .should_receive('get_track_by_track_id') .with_args(track_id=tuid_not_fetched) .never()) (flexmock(ows_track) .should_receive('get_instant_grats_by_product_id') .with_args(product_id) .and_return(get_grats_response)) result = product.get_product_instant_grats(product_id) assert not result assert result.errors['message'] == error_msg def test_is_product_submitted_for_submitted_product( mocker): """Test is_product_submitted. Asserts returns status 400 for already submitted product. """ submitted_product_id = 1234 mocker.patch.object( release, 'get_release', return_value=response.Response(message={ 'release_status': 'transfer_to_content' })) validation_response = product.is_product_submitted(submitted_product_id) assert validation_response.status == 400 assert validation_response.message == error.\ ERROR_MESSAGE_PRODUCT_ALREADY_SUBMITTED def test_is_product_submitted_for_not_submitted_product(mocker): """Test is_product_submitted. Asserts returns status 200 for not submitted product. """ label_processing_product_id = 1234 mocker.patch.object( release, 'get_release', return_value=response.Response(message={ 'release_status': 'label_processing' })) validation_response = product.\ is_product_submitted(label_processing_product_id) assert validation_response.status == 200 def test_is_product_submitted_for_no_product_found(mocker): """Test is_product_submitted returns status 404 if product not found.""" product_id = 1234 mocker.patch.object( release, 'get_release', return_value=response.Response(status=404)) validation_response = product.is_product_submitted(product_id) assert validation_response.status == 404 def test_get_mkt_priority( mocker, product_id, mkt_priority_model_response, expected_mkt_priority_logic_response_message): """Test that an mkt priority is returned with correct formatting.""" mocker.patch.object( mkt_priority, 'get_mkt_priority_by_release_id', return_value=mkt_priority_model_response) mkt_priority_response = product.get_mkt_priority(product_id) assert mkt_priority_response.message == expected_mkt_priority_logic_response_message def test_get_bulk_mkt_priority( mocker, mkt_priority_model_response, expected_mkt_priority_logic_response_message): """Test that bulk mkt priorities are returned with correct formatting.""" product_ids = [1, 2, 3] mocker.patch.object( mkt_priority, 'get_bulk_mkt_priority_by_release_ids', return_value=mkt_priority_model_response) mkt_priority_response = product.get_bulk_mkt_priority(product_ids) assert mkt_priority_response.message == expected_mkt_priority_logic_response_message def test_set_mkt_priority( mocker, product_id, mkt_priority_model_response, expected_mkt_priority_logic_response_message): """Test that an mkt priority is returned with correct formatting.""" mocker.patch.object( mkt_priority, 'set_mkt_priority_by_release_id', return_value='1') country_id = 1 priority = 'a' mkt_priority_response = product.set_mkt_priority(product_id, country_id, priority, 1) assert mkt_priority_response == '1' def test_delete_mkt_priority( mocker, product_id, mkt_priority_model_response, expected_mkt_priority_logic_response_message): """Test that an mkt priority is returned with correct formatting.""" mocker.patch.object( mkt_priority, 'delete_mkt_priority_by_release_id', return_value='1') country_id = 2 mkt_priority_response = product.delete_mkt_priority(product_id, country_id) assert mkt_priority_response == '1' def test_get_mkt_priority_for_no_priority( mocker, mkt_priority_model_responsee_for_no_data, expected_mkt_priority_logic_response_message_for_no_data): """Test that an mkt priority is returned with correct formatting.""" mocker.patch.object( mkt_priority, 'get_mkt_priority_by_release_id', return_value=mkt_priority_model_responsee_for_no_data) mkt_priority_response = product.get_mkt_priority('123') assert mkt_priority_response.message == expected_mkt_priority_logic_response_message_for_no_data @pytest.fixture def pricing_tier_options(get_pricing_from_bff_message): """A list of pricing tier options.""" return get_pricing_from_bff_message['pricing_tier_options']['items'] def test_update_audio_product_project_subaccount_success_for_oa( mocker, base_product_data_to_update_project_subaccount, product_model_response, expected_product_logic_response_message, product_id, get_project_response_message_with_subaccount): """Test for correct payload and response status on successful update.""" mocker.patch.object( audio_product, 'update', return_value=product_model_response) mocker.patch.object(product_validation, 'validate_product_on_update', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message_with_subaccount)) product_response = product.update_audio_product( product_id, base_product_data_to_update_project_subaccount, 'oa:562') assert product_response.status == 200 assert product_response.message == expected_product_logic_response_message def test_unsubmit_product_wrong_status(product_id, project_id, account_id): """Test unsubmit product with the wrong status.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='label_processing', project_id=project_id, release_id=product_id))) product_response = product.unsubmit_audio_product(product_id, account_id) assert not product_response def test_unsubmit_product_workflow_failure(product_id, project_id, account_id, account_type, product_workflow_unsubmit_400_response): """Test unsubmit product with a product workflow failure.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='transfer_to_content', project_id=project_id, release_id=product_id))) (flexmock(ows_product_workflow) .should_receive('product_unsubmission') .with_args(product_id, account_id, account_type) .and_return(product_workflow_unsubmit_400_response)) (flexmock(product) .should_receive('_create_release_status_entry') .never()) product_response = product.unsubmit_audio_product(product_id, account_id) assert not product_response def test_unsubmit_product_workflow_not_found(product_id, project_id, account_id, account_type): """Test unsubmit product proceeds when workflow unsubmission returns 404.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='transfer_to_content', project_id=project_id, release_id=product_id))) (flexmock(ows_product_workflow) .should_receive('product_unsubmission') .with_args(product_id, account_id, account_type) .and_return(response.Response(status=404, errors={'message': 'Not found'}))) (flexmock(product) .should_receive('_create_release_status_entry') .with_args(product_id, 'label_processing', account_id, 'vendor', session=sqlalchemy.orm.session.Session) .once()) product_response = product.unsubmit_audio_product(product_id, account_id) assert product_response assert product_response.message == 'product unsubmitted' def test_unsubmit_product_success(product_id, project_id, account_id, account_type): """Test unsubmit product success.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock(release_status='transfer_to_content', project_id=project_id, release_id=product_id))) (flexmock(ows_product_workflow) .should_receive('product_unsubmission') .with_args(product_id, account_id, account_type) .and_return(response.Response(message={ 'release_approval': { 'release_approval_id': 1, 'release_id': product_id, 'status': 'checked_in' }}))) (flexmock(product) .should_receive('_create_release_status_entry') .with_args(product_id, 'label_processing', 12345, 'vendor', session=sqlalchemy.orm.session.Session) .once()) product_response = product.unsubmit_audio_product(product_id, account_id) assert product_response def test_emit_event_on_unsubmit( mocker, project_id, product_id, account_id, # product_id, account_type, account_id, orchard_user_id, # expected_product_logic_response_message, emit_digital_product_event_mock): """Test event is sent on product unsubmit.""" mocker.patch.object( release, 'get_release_instance', return_value=MagicMock( release_status='transfer_to_content', project_id=project_id, release_id=product_id)) mocker.patch.object( ows_product_workflow, 'product_unsubmission', return_value=response.Response(message={ 'release_approval': { 'release_approval_id': 1, 'release_id': product_id, 'status': 'checked_in' }})) mocker.patch.object(product, '_create_release_status_entry') product.unsubmit_audio_product(product_id, account_id) assert product._create_release_status_entry.mock_calls == [ call(product_id, 'label_processing', 12345, 'vendor', session=ANY) ] assert isinstance( product._create_release_status_entry.mock_calls[0].kwargs['session'], sqlalchemy.orm.session.Session, ) emit_digital_product_event_mock.assert_called_once_with( product_id=product_id, operation_type=product_events.OperationType.UPDATE, operation_context=product_events.OperationContext.UNSUBMIT) def test_reject_product_wrong_status(product_id, project_id, account_id): """Test reject product with the wrong status.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock( release_status='label_processing', project_id=project_id, release_id=product_id))) product_response = product.reject_audio_product_from_productreview( product_id) assert not product_response def test_reject_product_success( product_id, project_id, account_id, account_type): """Test reject product success.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock( release_status='transfer_to_content', project_id=project_id, release_id=product_id))) (flexmock(ows_product_workflow) .should_receive('get_last_release_correction') .never()) (flexmock(ows_product_workflow) .should_receive('update_release_correction') .never()) (flexmock(product) .should_receive('_create_release_status_entry') .with_args(product_id, 'label_processing', 179, 'oa', session=sqlalchemy.orm.session.Session) .once()) product_response = product.reject_audio_product_from_productreview( product_id) assert product_response def test_reject_product_error_correction(product_id, project_id): """Test error corrected product success.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock( release_status='in_content', project_id=project_id, release_id=product_id))) (flexmock(ows_product_workflow) .should_receive('get_last_release_correction') .with_args(product_id) .and_return(response.Response({'release_correction_id': 1, 'status': 'submitted'}))) (flexmock(ows_product_workflow) .should_receive('update_release_correction') .with_args(1, { 'status': 'active', 'last_updated_by': 179, 'last_updated_type': 'oa' }).and_return(response.Response())) (flexmock(product) .should_receive('_create_release_status_entry') .never()) product_response = product.reject_audio_product_from_productreview( product_id) assert product_response.status == 200 assert product_response.message == 'product rejected' def test_reject_product_error_correction_not_found(product_id, project_id): """Test error corrected reject product 404 failure.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock( release_status='in_content', project_id=project_id, release_id=product_id))) (flexmock(ows_product_workflow) .should_receive('get_last_release_correction') .with_args(product_id) .and_return(response.Response(message={'code': 'not_found'}, status=404))) (flexmock(ows_product_workflow) .should_receive('update_release_correction') .never()) (flexmock(product) .should_receive('_create_release_status_entry') .never()) product_response = product.reject_audio_product_from_productreview( product_id) assert product_response.status == 400 def test_reject_product_error_correction_failure(product_id, project_id): """Test error corrected reject product returns error if failure to get correction.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock( release_status='in_content', project_id=project_id, release_id=product_id))) (flexmock(ows_product_workflow) .should_receive('get_last_release_correction') .with_args(product_id) .and_return(response.Response(message={'code': 'some error'}, status=500))) (flexmock(ows_product_workflow) .should_receive('update_release_correction') .never()) (flexmock(product) .should_receive('_create_release_status_entry') .never()) product_response = product.reject_audio_product_from_productreview( product_id) assert product_response.status == 500 def test_approve_product_wrong_status( product_id, project_id): """Test approve product with the wrong status.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock( release_status='label_processing', project_id=project_id, release_id=product_id))) (flexmock(release_status).should_receive('create')) product_response = product.approve_audio_product_from_productreview( product_id) assert not product_response def test_approve_product_success( product_id, project_id, account_type): """Test approve product success.""" (flexmock(release) .should_receive('get_release_instance') .and_return(MagicMock( release_status='transfer_to_content', project_id=project_id, release_id=product_id))) (flexmock(product) .should_receive('_create_release_status_entry') .with_args(product_id, 'in_content', 179, 'oa', session=sqlalchemy.orm.session.Session) .once()) product_response = product.approve_audio_product_from_productreview( product_id) assert product_response.message == 'product approved' def test_emit_event_on_approve_audio_product_from_productreview( mocker, product_id, project_id, emit_digital_product_event_mock): """Test event is sent on product approve from product review.""" mocker.patch.object( release, 'get_release_instance', return_value=MagicMock( release_status='transfer_to_content', project_id=project_id, release_id=product_id)) mocker.patch.object(product, '_create_release_status_entry') product.approve_audio_product_from_productreview(product_id) assert product._create_release_status_entry.mock_calls == [ call(product_id, 'in_content', 179, 'oa', session=ANY) ] assert isinstance( product._create_release_status_entry.mock_calls[0].kwargs['session'], sqlalchemy.orm.session.Session, ) emit_digital_product_event_mock.assert_called_once_with( product_id=product_id, operation_type=product_events.OperationType.UPDATE, operation_context=product_events.OperationContext.APPROVE) def test_update_not_for_distribution(mocker, product_id): """Test that a product dict is returned with correct formatting.""" mocker.patch.object( release, 'update_not_for_distribution', return_value=response.Response(status=200, message={'key': 'value'}) ) product_id = 1 not_for_distribution = 'TestValue' update_response = product.update_not_for_distribution(product_id, not_for_distribution) assert update_response.message == {'key': 'value'} def test_emit_event_on_update_not_for_distribution( mocker, product_id, emit_digital_product_event_mock): """Test event is sent on product update_not_for_distribution.""" mocker.patch.object( release, 'update_not_for_distribution', return_value=response.Response(status=200, message={'key': 'value'}) ) product.update_not_for_distribution(product_id, 'test') emit_digital_product_event_mock.assert_called_once_with( product_id=product_id, operation_type=product_events.OperationType.UPDATE) def test_update_not_for_distribution_with_release_status(mocker, product_id): """Test an optional update to release_status.""" product_id = 1 not_for_distribution = 'TestValue' release_status = 'test_status' mock_update_model = mocker.patch.object( release, 'update_not_for_distribution', return_value=response.Response(status=200, message={ 'not_for_distribution': not_for_distribution, 'release_status': release_status }) ) mock_create_status_entry = mocker.patch.object(product, '_create_release_status_entry') update_response = product.update_not_for_distribution( product_id, not_for_distribution, release_status ) mock_update_model.assert_called_once_with(1, 'TestValue', 'test_status') mock_create_status_entry.assert_called_once() assert update_response.message == { 'not_for_distribution': 'TestValue', 'release_status': 'test_status' } def test_approve(mocker, product_id): """Test that a product dict is returned with correct formatting.""" product_id = 1 mocker.patch.object( release_status, 'create', return_value=response.Response()) (flexmock(release) .should_receive('update_release_status') .with_args(product_id, 'in_content') .and_return({'key': 'value'})) (flexmock(product) .should_receive('_create_release_status_entry') .with_args(product_id, 'in_content', 100, 'oa') .once()) update_response = product.approve(product_id, 100, 'oa') assert update_response == {'key': 'value'} def test_emit_event_on_approve( mocker, product_id, emit_digital_product_event_mock): """Test event is sent on product approve.""" mocker.patch.object(release, 'update_release_status') mocker.patch.object( release_status, 'create', return_value=response.Response()) product.approve(product_id, 100, 'oa') emit_digital_product_event_mock.assert_called_once_with( product_id=product_id, operation_type=product_events.OperationType.UPDATE, operation_context=product_events.OperationContext.APPROVE) def test_create_with_project_subaccount_id( base_product_data_with_vendor, expected_create_product_response_message, expected_create_product_provided_store_artists_response_message, get_project_response_message_with_subaccount, mocker, new_product_release_data, test_request_context ): """Check subaccount_id available when not included in create payload.""" mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.Response()) mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message_with_subaccount)) mocker.patch.object( ows_product, 'check_upc_available', return_value=response.Response()) mocker.patch.object( audio_product, 'create', return_value=response.Response( status=201, message=expected_create_product_response_message)) mocker.patch.object( release_status, 'create', return_value=response.Response()) mocker.patch.object( product, '_return_upc_value_and_update_status', return_value='111111111111') mocker.patch.object( product_provided_store_artists, 'create', return_value=response.Response( status=201, message=expected_create_product_provided_store_artists_response_message)) explicit_upc = expected_create_product_response_message.get('upc') base_product_data_with_vendor.update({'upc': explicit_upc}) new_product_release_data.update({'upc': explicit_upc}) new_product_release_data.update({'subaccount_id': 1}) product_response = product.create(base_product_data_with_vendor) project.get_project_by_id.assert_called_with( get_project_response_message_with_subaccount.get('project_id')) audio_product.create.assert_called_with(new_product_release_data) product._return_upc_value_and_update_status.assert_not_called() assert release_status.create.called assert product_response.status == 201 assert product_response.message == expected_create_product_response_message def test_create_with_product_subaccount_id( base_product_data_with_vendor, expected_create_product_response_message, expected_create_product_provided_store_artists_response_message, get_project_response_message, mocker, new_product_release_data, test_request_context ): """Check subaccount_id available when included in create payload.""" mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.Response()) mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message)) mocker.patch.object( ows_product, 'check_upc_available', return_value=response.Response()) mocker.patch.object( audio_product, 'create', return_value=response.Response( status=201, message=expected_create_product_response_message)) mocker.patch.object( release_status, 'create', return_value=response.Response()) mocker.patch.object( product, '_return_upc_value_and_update_status', return_value='111111111111') mocker.patch.object( product_provided_store_artists, 'create', return_value=response.Response( status=201, message=expected_create_product_provided_store_artists_response_message)) explicit_upc = expected_create_product_response_message.get('upc') base_product_data_with_vendor.update({'upc': explicit_upc}) base_product_data_with_vendor.update({'subaccount_id': 2}) new_product_release_data.update({'upc': explicit_upc}) new_product_release_data.update({'subaccount_id': 2}) product_response = product.create(base_product_data_with_vendor) project.get_project_by_id.assert_called_with( get_project_response_message.get('project_id')) audio_product.create.assert_called_with(new_product_release_data) product._return_upc_value_and_update_status.assert_not_called() assert release_status.create.called assert product_response.status == 201 assert product_response.message == expected_create_product_response_message def test_create_product_provided_store_artists_entry( base_product_data_with_vendor, expected_create_product_response_message, expected_create_product_provided_store_artists_response_message, get_project_response_message, mocker, new_product_release_data, test_request_context ): """Check if _create_product_provided_store_artists_entry is called when feauture is enabled.""" mocker.patch.object( ows_product, 'is_product_code_available', return_value=response.Response()) mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( project, 'get_project_by_id', return_value=response.Response( status=200, message=get_project_response_message)) mocker.patch.object( ows_product, 'check_upc_available', return_value=response.Response()) mocker.patch.object( audio_product, 'create', return_value=response.Response( status=201, message=expected_create_product_response_message)) mocker.patch.object( release_status, 'create', return_value=response.Response()) mocker.patch.object( product, '_return_upc_value_and_update_status', return_value='111111111111') mocker.patch.object( product_provided_store_artists, 'create', return_value=response.Response( status=201, message=expected_create_product_provided_store_artists_response_message)) explicit_upc = expected_create_product_response_message.get('upc') base_product_data_with_vendor.update({'upc': explicit_upc}) new_product_release_data.update({'upc': explicit_upc}) product_response = product.create(base_product_data_with_vendor) assert product_provided_store_artists.create.called assert product_response.status == 201 assert product_response.message == expected_create_product_response_message def test_delete_success( expected_create_release_response_message, account_type, account_id, mocker, test_request_context ): """Check audio_product.delete is successfully called.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( release, 'get_release', return_value=response.Response( status=200, message=expected_create_release_response_message)) mocker.patch.object( audio_product, 'delete', return_value=response.Response(message={'status': 'ok'})) release_id = expected_create_release_response_message.get('release_id') delete_response = product.delete( release_id, account_type=account_type, account_id=account_id) assert audio_product.delete.called assert delete_response.status == 200 assert delete_response.message.get('status') == 'ok' def test_delete_product_not_found_error( expected_create_release_response_message, account_type, account_id, mocker, test_request_context ): """Check audio_product.delete is not called when release is missing.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( release, 'get_release', return_value=response.create_not_found_response()) mocker.patch.object( audio_product, 'delete', return_value=response.Response(message={'status': 'ok'})) release_id = expected_create_release_response_message.get('release_id') delete_response = product.delete( release_id, account_type=account_type, account_id=account_id) assert not audio_product.delete.called assert delete_response.status == 404 def test_delete_project_ownership_error( expected_create_release_response_message, account_type, account_id, mocker, test_request_context ): """Check audio_product.delete not called when ownership is incorrect.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.create_error_response( code='authorization_error', message='Project is not owned by account', status=403)) mocker.patch.object( release, 'get_release', return_value=response.Response( status=200, message=expected_create_release_response_message)) mocker.patch.object( audio_product, 'delete', return_value=response.Response(message={'status': 'ok'})) release_id = expected_create_release_response_message.get('release_id') delete_response = product.delete( release_id, account_type=account_type, account_id=account_id) assert not audio_product.delete.called assert delete_response.status == 403 def test_delete_invalid_release_status_error( expected_create_release_response_message, account_type, account_id, mocker, test_request_context ): """Check audio_product.delete is not called when status is incorrect.""" mocker.patch.object( project, 'check_project_ownership', return_value=response.Response()) mocker.patch.object( release, 'get_release', return_value=response.Response( status=200, message={'product_id': 123, 'release_status': 'in_content'})) mocker.patch.object( audio_product, 'delete', return_value=response.Response(message={'status': 'ok'})) release_id = expected_create_release_response_message.get('release_id') delete_response = product.delete( release_id, account_type=account_type, account_id=account_id) assert not audio_product.delete.called assert delete_response.status == 400 @pytest.mark.parametrize(( 'test_description', 'changed_by_type', 'kwargs', 'expected_raise', 'expected_release_status_calls', ), [ ( 'changed_by_type is oa', 'oa', {}, does_not_raise(), [ call( { 'release_id': 123, 'status': 'in_content', 'changed_by': 179, 'changed_by_type': 'oa' }, session=None ) ], ), ( 'changed_by_type is vendor', 'vendor', {}, does_not_raise(), [ call( { 'release_id': 123, 'status': 'in_content', 'changed_by': 179, 'changed_by_type': 'vendor' }, session=None ) ], ), ( 'changed_by_type is system', 'system', {}, does_not_raise(), [ call( { 'release_id': 123, 'status': 'in_content', 'changed_by': 179, 'changed_by_type': 'system' }, session=None ) ], ), ( 'changed_by_type is invalid', 'invalid', {}, pytest.raises( Exception, match='^Invalid changed_by_type, must be one of: oa, vendor, system$', ), [], ), ( 'session is passed in', 'oa', {'session': 'session passed in'}, does_not_raise(), [ call( { 'release_id': 123, 'status': 'in_content', 'changed_by': 179, 'changed_by_type': 'oa' }, session='session passed in' ) ], ), ]) def test__create_release_status_entry( mocker, test_description, changed_by_type, kwargs, expected_raise, expected_release_status_calls, ): """Test _create_release_status_entry.""" mocker.patch.object(release_status, 'create') with expected_raise: product._create_release_status_entry( release_id=123, status='in_content', changed_by=179, changed_by_type=changed_by_type, **kwargs ) assert release_status.create.mock_calls == expected_release_status_calls def test_delete_mkt_priority_for_product( mocker, product_id, mkt_priority_model_response, expected_mkt_priority_logic_response_message): """Test that an mkt priority is returned with correct formatting.""" mocker.patch.object( mkt_priority, 'delete_mkt_priority_for_product', return_value=response.Response()) mkt_priority_response = product.delete_mkt_priority_for_product(product_id) assert mkt_priority_response.status == 200 def test_dataload_products( mocker, get_products_response): """Test that products are returned with correct formatting.""" product_ids = [12345, 12346] mocker.patch.object( audio_product, 'get_products', return_value=get_products_response) product_response = product.get_audio_products(product_ids) assert product_response.message == { 'products': [ { 'artist_id': 2627435, 'description': None, 'distribution_format_id': 1, 'format': 'Single', 'genre_id': 7, 'c_line': '2015 CTC Management', 'imprint': 'SABA JOON', 'manufacturer_upc': '5054227075452', 'meta_language': 'ENG', 'p_line': '2015 SABA JOON', 'preorder_date': None, 'product_code': 'AW5075452', 'previewable': None, 'product_id': 12345, 'product_name': 'Coffee', 'product_type_id': 1, 'project_id': 4884401, 'product_artists': [ { 'role': 'primary_artist', 'name': 'Saba Joon', 'id': 7734309, 'artist_info_id': 2627435, }, { 'role': 'featuring', 'name': 'FlyHats', 'id': 7734310, 'artist_info_id': 2627436 }, { 'role': 'featuring', 'name': 'Cobe Jones', 'id': 7734311, 'artist_info_id': 2627437 } ], 'delivered_version': None, 'release_date': '2015-08-18', 'release_status': 'in_content', 'is_error_correction_and_action_required': False, 'sale_start_date': '2015-08-18', 'special_instructions': None, 'subaccount_id': None, 'subgenre_id': 756, 'upc': '5054227075452', 'vendor_catalog_number': 'EXP272852', 'version': None, 'not_for_distribution': 'N', 'vendor_release_identifier': 'EXPV231215' }, { 'artist_id': 2627491, 'description': None, 'distribution_format_id': 1, 'format': 'Single', 'genre_id': 6, 'c_line': '2015 6Villa', 'imprint': '6Villa', 'manufacturer_upc': '5054227078088', 'meta_language': 'ENG', 'p_line': '2015 6Villa', 'preorder_date': None, 'product_code': 'AW5078088', 'previewable': None, 'product_id': 121346, 'product_name': 'Dope House', 'product_type_id': 1, 'project_id': 4884477, 'product_artists': [ { 'role': 'primary_artist', 'name': '6Villa', 'id': 7734390, 'artist_info_id': 2627491 } ], 'delivered_version': None, 'release_date': '2015-09-14', 'release_status': 'in_content', 'is_error_correction_and_action_required': False, 'sale_start_date': '2015-09-14', 'special_instructions': None, 'subaccount_id': None, 'subgenre_id': 739, 'upc': '5054227078088', 'vendor_catalog_number': 'EXP279151', 'version': None, 'not_for_distribution': 'N', 'vendor_release_identifier': 'EXPV238347' } ] } def test_dataload_audio_product_validation_when_valid( mocker, product_id, account_id, account_type, orchard_user_id, setup_validation_mocks_as_valid, expected_validation_message): """When valid returns a 200 response with expected payload.""" mocker.patch.object( ows_product, 'get_product', side_effect=[response.Response(message={ 'vendor_id': 123 })] ) mocker.patch.object( features, 'is_feature_flag_enabled_for_vendor', side_effect=[True] + [False] * 11 ) with app.test_request_context(): dataloader_response = product.dataload_product_audio_validation( product_ids=[product_id], account_type=account_type, account_id=account_id, response_format='original', orchard_user_id=orchard_user_id ) assert dataloader_response.status == 200 assert dataloader_response.message == [expected_validation_message] def test_dataload_audio_product_validation_product_not_found( product_id, account_id, account_type, orchard_user_id, setup_validation_mocks_as_valid): """When product not found return 404.""" (flexmock(ows_product).should_receive('get_product') .with_args(product_id) .and_return(response.create_not_found_response())) with app.test_request_context(): dataloader_response = product.dataload_product_audio_validation( product_ids=[product_id], account_type=account_type, account_id=account_id, response_format='original', orchard_user_id=orchard_user_id ) assert dataloader_response.status == 404 def test_update_audio_product_uses_physical_supplychain_update( mocker, base_product_data_for_update, product_id): """Test physical product update path when feature flag is enabled.""" base_product_data_for_update['account_type'] = 'vendor' base_product_data_for_update['account_id'] = 1234 base_product_data_for_update['context_type'] = 'physical' base_product_data_for_update['sale_start_date'] = '2024-01-15' base_product_data_for_update['release_date'] = '2024-02-01' physical_update_response = response.Response( message={ 'sale_start_date': '2024-01-15', 'release_date': '2024-02-01', }) mocker.patch.object( product_validation, 'validate_product_on_update', return_value=response.Response()) mocker.patch.object( features, 'is_feature_flag_enabled_for_vendor', return_value=True) mock_update_product_physical = mocker.patch.object( ows_product_physical, 'update_product_physical', return_value=physical_update_response) mocker.patch.object( audio_product, 'update', return_value=response.Response(message={ 'product_id': product_id, 'upc': '123456789012', 'product_name': 'Test Product', 'sale_start_date': '2024-01-15', 'release_date': '2024-02-01', })) mocker.patch.object(product_events, 'emit_digital_product_event') product_response = product.update_audio_product( product_id, base_product_data_for_update) mock_update_product_physical.assert_called_once_with( base_product_data_for_update, product_id, '', 'vendor', 1234) assert product_response.status == 200