"""Functional tests for fetching an validations of an audio product.""" import datetime import json from unittest.mock import MagicMock, patch from flexmock import flexmock from owsrequest import request import pytest from oto import response as oto_response from product_digital import config from product_digital import features from product_digital.constants import error from product_digital.constants import features as feature_names from product_digital.constants import header from product_digital.constants import services from product_digital.models import content_review_account_blocklist 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.factories import release_artist as artist_factory from tests.factories import release_subgenre as subgenre_factory from tests.testutils import db @pytest.fixture def product_id(): """Return a fake product id for testing.""" return 12345 @pytest.fixture def empty_corrections(product_id): """Return empty corrections for a release.""" return { 'release_id': product_id, 'items': [ { 'key_value': '', 'table_name': 'releases', 'field_name': 'meta_language', 'key_id': product_id }, { 'key_value': '', 'table_name': 'releases', 'field_name': 'release_name', 'key_id': product_id }, { 'key_value': [], 'table_name': 'releases', 'field_name': 'performer', 'key_id': product_id }, { 'key_value': [], 'table_name': 'releases', 'field_name': 'featuring', 'key_id': product_id }, { 'key_value': [], 'table_name': 'releases', 'field_name': 'remixer', 'key_id': product_id }, { 'key_value': [], 'table_name': 'releases', 'field_name': 'producer', 'key_id': product_id }, { 'key_value': '', 'table_name': 'releases', 'field_name': 'genre_id', 'key_id': product_id }, { 'key_value': [None], 'table_name': 'releases', 'field_name': 'release_subgenre', 'key_id': product_id }, { 'key_value': '', 'table_name': 'releases', 'field_name': 'label', 'key_id': product_id }, { 'key_value': '', 'table_name': 'releases', 'field_name': 'c_line', 'key_id': product_id } ], 'status': 'active' } @pytest.fixture def release_correction_get_response(empty_corrections): """Return a mock response from ows-product-workflow.""" release_correction_response = MagicMock(status_code=200) release_correction_response.json = MagicMock( return_value=empty_corrections ) return release_correction_response @pytest.fixture def scheduling_and_pricing_validation_error(): """Return a validation message when dates are missing.""" return { 'valid': False, 'errors': { 'release_date': { 'validator_value': True, 'validator': 'required', 'message': "'release_date' is a required property", 'error_code': 'release_date_required', }, 'sale_start_date': { 'validator_value': True, 'validator': 'required', 'message': "'sale_start_date' is a required property", 'error_code': 'sale_start_date_required', } } } @pytest.fixture(autouse=True) def content_review_blocklist_disabled(): """Mock content review blocklist check as not blocked.""" ( flexmock(content_review_account_blocklist) .should_receive('is_vendor_or_subaccount_on_blocklist') .and_return(oto_response.Response(message=False)) ) @pytest.fixture def setup_validation_mock_calls( product_id, get_assets_response, track_validation_response, publishing_obligation_validation_response, orchard_user_id, spatial_assets_message, valid_ows_product_get_response, get_project_response_message, ): """Mock out validation calls for complete product.""" ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/v2/validators/product/{product_id}/artwork'.format( product_id=product_id ), params={} ) .and_return(get_assets_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/v2/validators/product/{product_id}/artwork'.format( product_id=product_id ), params={} ) .and_return(get_assets_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/validators/product/{product_id}/artwork'.format( product_id=product_id ), ) .and_return(get_assets_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{product_id}/tracks/validate'.format( product_id=product_id ), headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', 'Orchard-Identity-Id': '123', }, params={ 'validation_context': 'post_submission', }, ) .and_return(track_validation_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{product_id}/tracks/validate'.format( product_id=product_id ), headers={}, params={ 'validation_context': 'pre_submission', }, ) .and_return(track_validation_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{product_id}/tracks/validate'.format( product_id=product_id ), headers={ header.ORCHARD_USER_ID: orchard_user_id, 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '123', }, params={ 'validation_context': 'pre_submission', }, ) .and_return(track_validation_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{}/tracks/validate/publishing-obligation'.format( product_id ), params={'account_id': '', 'account_type': ''}, headers={ header.ORCHARD_USER_ID: orchard_user_id, 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '123', }, ) .and_return(publishing_obligation_validation_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{}/tracks/validate/publishing-obligation'.format( product_id ), params={'account_id': '', 'account_type': ''}, headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, ) .and_return(publishing_obligation_validation_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{}/tracks/validate/publishing-obligation'.format( product_id ), params={'account_id': '', 'account_type': ''}, headers={} ) .and_return(publishing_obligation_validation_response) ) product_response = MagicMock(status_code=200) product_response.json = MagicMock( return_value=valid_ows_product_get_response) ( flexmock(request) .should_receive('get') .with_args( services.OWS_PRODUCT, '/product/{product_id}'.format( product_id=product_id ) ) .and_return(product_response) ) project_response = MagicMock(status_code=200) project_response.json = MagicMock( return_value=get_project_response_message) ( flexmock(request) .should_receive('get') .with_args( services.OWS_PROJECT_MANAGER, '/project/12345', ) .and_return(project_response) ) account_brand_response = MagicMock(status_code=200) account_brand_response.text = 'brand' ( flexmock(request) .should_receive('get') .with_args( services.OWS_ACCOUNT, '/vendor/brand/9999', ) .and_return(account_brand_response) ) assets_response = MagicMock(status_code=200) assets_response.json = MagicMock(return_value=spatial_assets_message) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/asset/product/{}'.format( product_id ), headers={ header.ORCHARD_USER_ID: orchard_user_id, }, ) .and_return(assets_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/asset/product/{}'.format( product_id ), headers={}, ) .and_return(assets_response) ) ( flexmock(neo4j_vendor) .should_receive('get_recent_awal_artist') .and_return('') ) @pytest.fixture def setup_validation_failure_mock_calls( product_id, get_assets_response, track_validation_response, publishing_obligation_validation_response, orchard_user_id, spatial_assets_message, valid_ows_product_get_response, get_project_response_message, ): """Mock out validation calls for complete product.""" ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/v2/validators/product/{product_id}/artwork'.format( product_id=product_id ), params={} ) .and_return(get_assets_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/v2/validators/product/{product_id}/artwork'.format( product_id=product_id ), params={} ) .and_return(get_assets_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/validators/product/{product_id}/artwork'.format( product_id=product_id ), ) .and_return(get_assets_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{product_id}/tracks/validate'.format( product_id=product_id ), headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, params={ 'validation_context': 'post_submission', }, ) .and_return(MagicMock( status_code=502, json=MagicMock(return_value={ 'code': 'some_error_code_xyz', 'message': 'Failed to get track validations' }) )) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{product_id}/tracks/validate'.format( product_id=product_id ), headers={}, params={ 'validation_context': 'pre_submission', }, ) .and_return(MagicMock( status_code=502, json=MagicMock(return_value={ 'code': 'some_error_code_xyz', 'message': 'Failed to get track validations' }) )) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{product_id}/tracks/validate'.format( product_id=product_id ), headers={ header.ORCHARD_USER_ID: orchard_user_id, 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '123', }, params={ 'validation_context': 'pre_submission', }, ) .and_return(MagicMock( status_code=502, json=MagicMock(return_value={ 'code': 'some_error_code_xyz', 'message': 'Failed to get track validations' }) )) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{}/tracks/validate/publishing-obligation'.format( product_id ), params={'account_id': '', 'account_type': ''}, headers={ header.ORCHARD_USER_ID: orchard_user_id, 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '123', }, ) .and_return(publishing_obligation_validation_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{}/tracks/validate/publishing-obligation'.format( product_id ), params={'account_id': '', 'account_type': ''}, headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, ) .and_return(publishing_obligation_validation_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_TRACK, '/product/{}/tracks/validate/publishing-obligation'.format( product_id ), params={'account_id': '', 'account_type': ''}, headers={} ) .and_return(publishing_obligation_validation_response) ) product_response = MagicMock(status_code=200) product_response.json = MagicMock( return_value=valid_ows_product_get_response) ( flexmock(request) .should_receive('get') .with_args( services.OWS_PRODUCT, '/product/{product_id}'.format( product_id=product_id ) ) .and_return(product_response) ) project_response = MagicMock(status_code=200) project_response.json = MagicMock( return_value=get_project_response_message) ( flexmock(request) .should_receive('get') .with_args( services.OWS_PROJECT_MANAGER, '/project/12345', ) .and_return(project_response) ) account_brand_response = MagicMock(status_code=200) account_brand_response.text = 'brand' ( flexmock(request) .should_receive('get') .with_args( services.OWS_ACCOUNT, '/vendor/brand/9999', ) .and_return(account_brand_response) ) assets_response = MagicMock(status_code=200) assets_response.json = MagicMock(return_value=spatial_assets_message) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/asset/product/{}'.format( product_id ), headers={ header.ORCHARD_USER_ID: orchard_user_id, }, ) .and_return(assets_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/asset/product/{}'.format( product_id ), headers={}, ) .and_return(assets_response) ) @pytest.fixture def cr_rejections_disabled(): """Mock out ff call.""" ( flexmock(features) .should_receive('is_rejections_from_content_review_enabled') .and_return(False) ) @pytest.fixture def primary_artist_mismatch_error_disabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.PRIMARY_ARTIST_ERROR_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def featuring_artist_mismatch_disabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.FEATURING_ARTIST_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def single_track_product_various_artists_disabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.SINGLE_TRACK_PRODUCT_VARIOUS_ARTISTS, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def single_track_product_various_artists_enabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.SINGLE_TRACK_PRODUCT_VARIOUS_ARTISTS, valid_ows_product_get_response['vendor_id'] ) .and_return(True) ) @pytest.fixture def remixer_mismatch_validation_disabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.REMIXER_MISMATCH_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def producer_mismatch_validation_disabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.PRODUCER_MISMATCH_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def product_name_length_validation_disabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.PRODUCT_NAME_LENGTH_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def various_artist_on_featuring_disabled(valid_ows_product_get_response): """Mock out validation calls for various artist on featuring.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.VARIOUS_ARTIST_ON_FEATURING_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def spotify_watchlist_artist_validation_disabled(valid_ows_product_get_response): """Mock out spotify watchlist artist validation feature flag.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.SPOTIFY_WATCHLIST_ARTIST_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def artwork_metadata_mismatch_validation_disabled(valid_ows_product_get_response): """Mock out artwork metadata mismatch validation feature flag.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.ARTWORK_METADATA_MISMATCH_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def preorder_pricing_validation_disabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.PREORDER_PRICING_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def preorder_pricing_validation_enabled(valid_ows_product_get_response): """Mock out validation calls for complete product.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.PREORDER_PRICING_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(True) ) @pytest.fixture def bit_depth_success_response(): """Create fake ows-assets response for valid bit depths.""" return MagicMock(status_code=200) @pytest.fixture def spatial_assets_message(): """Default ows-assets payload used by spatial UPC validation.""" return {'assets': []} @pytest.fixture def spatial_upc_validation_disabled(valid_ows_product_get_response): """Mock spatial UPC validation feature flag as disabled.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.SPATIAL_UPC_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @pytest.fixture def spatial_upc_validation_enabled(valid_ows_product_get_response): """Mock spatial UPC validation feature flag as enabled.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.SPATIAL_UPC_VALIDATION, valid_ows_product_get_response['vendor_id'] ) .and_return(True) ) @pytest.fixture def release_level_lyricist_disabled(valid_ows_product_get_response): """Mock release level lyricist feature flag as disabled.""" ( flexmock(features) .should_receive('is_feature_flag_enabled_for_vendor') .with_args( feature_names.RELEASE_LEVEL_LYRICIST, valid_ows_product_get_response['vendor_id'] ) .and_return(False) ) @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_with_valid_product( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, successful_validation_message, basic_product_tracks_response, setup_validation_mock_calls, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, request_engine, get_project_response_message, valid_ows_tracks_message, monkeypatch, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test that a valid product returns a payload without errors.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body == successful_validation_message @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_post_submission_success( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, successful_validation_message_with_post_submission, setup_validation_mock_calls, empty_success, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test that context query param is passed to ows-track.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) flexmock(request).should_receive('get').with_args( services.OWS_BLOCKLIST_MANAGER, '/validate/{}'.format(product_id), headers={ 'Orchard-Identity-Id': '123', 'Orchard-Profile-Id': '33252352', 'Orchard-Profile-Type': 'ContentProfile', } ).and_return(empty_success) flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(empty_success) # Mock validate_account so the parallel Executor doesn't race on DB reads # (release.get_release) or hit unmocked pythonfeatures from worker threads. flexmock(product_validation).should_receive('validate_account').with_args( product_id ).and_return(oto_response.Response(message={ 'account': {'valid': True, 'errors': []} })) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers={ **valid_headers, 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', 'Orchard-Identity-Id': '123', }, query_string='validation_context=post_submission', ) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body == successful_validation_message_with_post_submission @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_v2_artwork_flag( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, successful_validation_message, setup_validation_mock_calls, orchard_user_id, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test that a check for v2 artwork passes.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) valid_headers[header.ORCHARD_USER_ID] = orchard_user_id (flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/v2/validators/product/{product_id}/artwork'.format( product_id=product_id ), params={}) .once() .and_return(MagicMock(status_code=200))) (flexmock(request) .should_receive('get') .with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id)) .and_return(MagicMock(status_code=404))) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers ) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body == successful_validation_message @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_spatial_upc_required_for_atmos_assets( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, orchard_user_id, setup_validation_mock_calls, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_enabled, release_level_lyricist_disabled, ): """Test that products with atmos assets require a spatial UPC record.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1), ) subgenre = subgenre_factory.ReleaseSubgenreFactory.build(release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build(release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) assets_response = MagicMock(status_code=200) assets_response.json = MagicMock(return_value={'assets': [{'asset_upload_type': 'atmos'}]}) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/asset/product/{}'.format( product_id ), headers={}, ) .and_return(assets_response) ) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers, ) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body.get('validations').get('spatial_upc') == { 'valid': False, 'errors': [ { 'code': error.ERROR_CODE_SPATIAL_UPC_REQUIRED.upper(), 'reason': error.ERROR_MESSAGE_SPATIAL_UPC_REQUIRED, } ], } @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_when_product_not_found( neo4j_exit, neo4j_enter, mock_session, client, product_id, valid_headers, valid_ows_product_get_response, setup_validation_mock_calls, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test validation when no product found.""" get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers) assert get_response.status_code == 404 @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_with_invalid_product( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, setup_validation_mock_calls, scheduling_and_pricing_validation_error, valid_tracks_validation_message, basic_product_tracks_response, valid_ows_product_get_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test that an invalid product returns a payload with an error.""" existing_release = release_factory.ReleaseFactory.build( project_id=12345, release_id=product_id, c_line=None) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers) response_body = json.loads(get_response.data.decode()) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id), params={ 'physical_location_id': config.ASSETS_PHYSICAL_LOCATION_ID }, ) .and_return(bit_depth_success_response) ) invalid_validate_audio_product_body = { 'validations': { 'product_basics': { 'valid': False, 'errors': { 'c_line': { 'message': "'c_line' is a required property", 'validator': 'required', 'validator_value': True, 'error_code': 'c_line_required', } } }, 'tracks': valid_tracks_validation_message, 'publishing_obligation': valid_tracks_validation_message, 'artwork': {'errors': [], 'valid': True}, 'scheduling_and_pricing': scheduling_and_pricing_validation_error}} assert get_response.status_code == 200 assert response_body == invalid_validate_audio_product_body @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_with_invalid_product_multiple_errors( neo4j_exit, neo4j_enter, mock_session, bit_depth_success_response, client, error_tracks_message, product_id, orchard_user_id, scheduling_and_pricing_validation_error, setup_validation_mock_calls, valid_headers, valid_publishing_validation_message, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test that an invalid product returns a payload with multiple errors.""" track_validation_error_response = MagicMock(status_code=200) track_validation_error_response.json = MagicMock( return_value=error_tracks_message) ( flexmock(request).should_receive('get') .with_args( services.OWS_TRACK, '/product/{product_id}/tracks/validate'.format( product_id=product_id ), headers={ header.ORCHARD_USER_ID: orchard_user_id, 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '123', }, params={ 'validation_context': 'pre_submission', }, ) .and_return(track_validation_error_response) ) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id), params={ 'physical_location_id': config.ASSETS_PHYSICAL_LOCATION_ID }, ) .and_return(bit_depth_success_response) ) existing_release = release_factory.ReleaseFactory.build( project_id=12345, release_id=product_id, c_line=None, release_format=None) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request)\ .should_receive('get')\ .with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id))\ .and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) valid_headers[header.ORCHARD_USER_ID] = orchard_user_id get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers) response_body = json.loads(get_response.data.decode()) full_tracks_error = {} full_tracks_error.update(error_tracks_message) full_tracks_error['valid'] = False invalid_validate_audio_product_body = { 'validations': { 'product_basics': { 'valid': False, 'errors': { 'c_line': { 'message': "'c_line' is a required property", 'validator': 'required', 'validator_value': True, 'error_code': 'c_line_required', }, 'format': { 'message': "'format' is a required property", 'validator': 'required', 'validator_value': True, 'error_code': 'format_required', } } }, 'tracks': full_tracks_error, 'publishing_obligation': valid_publishing_validation_message, 'artwork': {'errors': [], 'valid': True}, 'scheduling_and_pricing': scheduling_and_pricing_validation_error}} assert get_response.status_code == 200 assert response_body == invalid_validate_audio_product_body @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_with_error_correction_freedom( neo4j_exit, neo4j_enter, mock_session, bit_depth_success_response, client, error_tracks_message, product_id, orchard_user_id, scheduling_and_pricing_validation_error, setup_validation_mock_calls, valid_headers, valid_publishing_validation_message, release_correction_get_response, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test a product with invalid corrections.""" track_validation_error_response = MagicMock(status_code=200) track_validation_error_response.json = MagicMock( return_value=error_tracks_message) ( flexmock(request).should_receive('get') .with_args( services.OWS_TRACK, '/product/{product_id}/tracks/validate'.format( product_id=product_id ), headers={ header.ORCHARD_USER_ID: orchard_user_id, 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '123', }, params={ 'validation_context': 'pre_submission', }, ) .and_return(track_validation_error_response) ) existing_release = release_factory.ReleaseFactory.build( project_id=12345, release_id=product_id, release_status='in_content') subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) flexmock(request).should_receive('get')\ .with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/correction'.format(product_id))\ .and_return(release_correction_get_response) mock_response = MagicMock(status_code=404) flexmock(request)\ .should_receive('get')\ .with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id))\ .and_return(mock_response) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id), params={ 'physical_location_id': config.ASSETS_PHYSICAL_LOCATION_ID }, ) .and_return(bit_depth_success_response) ) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) valid_headers[header.ORCHARD_USER_ID] = orchard_user_id get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers) response_body = json.loads(get_response.data.decode()) full_tracks_error = {} full_tracks_error.update(error_tracks_message) full_tracks_error['valid'] = False invalid_validate_audio_product_body = { 'validations': { 'product_basics': { 'valid': False, 'errors': { 'c_line': { 'validator': 'required', 'validator_value': True, 'message': "'c_line' is a required property", 'error_code': 'c_line_required', }, 'genre_id': { 'validator': 'required', 'validator_value': True, 'message': "'genre_id' is a required property", 'error_code': 'genre_id_required', }, 'imprint': { 'validator': 'required', 'validator_value': True, 'message': "'imprint' is a required property", 'error_code': 'imprint_required', }, 'meta_language': { 'validator': 'required', 'validator_value': True, 'message': "'meta_language' is a required property", 'error_code': 'meta_language_required', }, 'product_name': { 'validator': 'required', 'validator_value': True, 'message': "'product_name' is a required property", 'error_code': 'product_name_required', }, 'subgenre_id': { 'validator': 'required', 'validator_value': True, 'message': "'subgenre_id' is a required property", 'error_code': 'subgenre_id_required', }, 'primary_artist': { 'validator': 'required', 'validator_value': True, 'message': "'primary_artist' is a required property", 'error_code': 'primary_artist_required', } } }, 'tracks': full_tracks_error, 'publishing_obligation': valid_publishing_validation_message, 'artwork': {'errors': [], 'valid': True}, 'scheduling_and_pricing': scheduling_and_pricing_validation_error}} assert get_response.status_code == 200 assert response_body == invalid_validate_audio_product_body @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_with_composer_required( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, valid_tracks_validation_message, scheduling_and_pricing_validation_error, setup_validation_mock_calls, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test that an invalid product returns a payload with an error.""" existing_release = release_factory.ReleaseFactory.build( project_id=12345, release_id=product_id, genre_id=config.CLASSICAL_GENRE_ID, ) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id, subgenre_id=1040) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers) response_body = json.loads(get_response.data.decode()) invalid_validate_audio_product_body = { 'validations': { 'product_basics': { 'valid': False, 'errors': { 'composer': { 'message': "'composer' is a required property", 'validator': 'required', 'validator_value': True, 'error_code': 'composer_required', } } }, 'tracks': valid_tracks_validation_message, 'publishing_obligation': valid_tracks_validation_message, 'artwork': {'errors': [], 'valid': True}, 'scheduling_and_pricing': scheduling_and_pricing_validation_error}} assert get_response.status_code == 200 assert response_body == invalid_validate_audio_product_body @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_missing_grass_header( neo4j_exit, neo4j_enter, mock_session, client, product_id, valid_headers_for_vendor, primary_artist_mismatch_error_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled): """Test incomplete response with missing grass header.""" invalid_headers = valid_headers_for_vendor del invalid_headers[header.GRASS_ACCOUNT_TYPE] get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=invalid_headers) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 400 assert response_body.get('message') == \ error.ERROR_MESSAGE_INCOMPLETE_GRASS_HEADERS @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_as_vendor_when_not_owner( neo4j_exit, neo4j_enter, mock_session, client, valid_headers_for_vendor, product_id, mocker): """Test fetching product validation as a vendor without ownership.""" head_response = MagicMock(status_code=403) mocker.patch.object(request, 'head', return_value=head_response) existing_release = release_factory.ReleaseFactory.build( release_id=product_id) db.seed_models(existing_release) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers_for_vendor) assert get_response.status_code == 403 @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_with_invalid_product_code( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, setup_validation_mock_calls, scheduling_and_pricing_validation_error, orchard_user_id, valid_tracks_validation_message, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test product validation with invalid product code.""" valid_headers[header.ORCHARD_USER_ID] = orchard_user_id existing_release = release_factory.ReleaseFactory.build( project_id=12345, release_id=product_id, product_code='Test 12') subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers) response_body = json.loads(get_response.data.decode()) invalid_validate_audio_product_body = { 'validations': { 'product_basics': { 'valid': False, 'errors': { 'product_code': { 'message': "'Test 12' does not match '^[a-zA-Z0-9-]*$'", 'validator': 'pattern', 'validator_value': '^[a-zA-Z0-9-]*$', 'error_code': 'product_code_pattern', } } }, 'tracks': valid_tracks_validation_message, 'publishing_obligation': valid_tracks_validation_message, 'artwork': {'errors': [], 'valid': True}, 'scheduling_and_pricing': scheduling_and_pricing_validation_error}} assert get_response.status_code == 200 assert response_body == invalid_validate_audio_product_body @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_number_product_code_in_error_correction( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, setup_validation_mock_calls, orchard_user_id, successful_validation_message, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test product validation with integer product code.""" valid_headers[header.ORCHARD_USER_ID] = orchard_user_id existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) error_correction_response = { 'release_id': product_id, 'items': [ { 'key_value': 12122, 'table_name': 'releases', 'field_name': 'product_code', 'key_id': product_id } ], 'status': 'active' } release_correction_response = MagicMock(status_code=200) release_correction_response.json = MagicMock( return_value=error_correction_response ) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(release_correction_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body == successful_validation_message @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_track_bit_depth_enabled( neo4j_exit, neo4j_enter, mock_session, client, valid_headers_with_orchard_user_id, product_id, valid_tracks_validation_message, setup_validation_mock_calls, basic_product_tracks_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test that track results include bit depth validation.""" bit_depth_response = MagicMock(status_code=200) existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id), params={'physical_location_id': config.ASSETS_PHYSICAL_LOCATION_ID}, ).and_return(bit_depth_response)) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers_with_orchard_user_id) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body.get('validations').get('tracks') == valid_tracks_validation_message @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_track_bit_depth_enabled_feature_flag_error( neo4j_exit, neo4j_enter, mock_session, client, valid_headers_with_orchard_user_id, product_id, valid_tracks_validation_message, setup_validation_mock_calls, valid_ows_product_get_response, cr_rejections_disabled, basic_product_tracks_response, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test that track results include bit depth validation.""" bit_depth_response = MagicMock(status_code=200) existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request)\ .should_receive('get')\ .with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id))\ .and_return(mock_response) flexmock(request)\ .should_receive('get')\ .with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id))\ .and_return(bit_depth_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) vendor_id = valid_ows_product_get_response.get('vendor_id') flexmock(features).should_receive('is_feature_flag_enabled_for_vendor') \ .with_args(feature_names.PRIMARY_ARTIST_ERROR_VALIDATION, vendor_id).and_return(False) flexmock(features).should_receive('is_feature_flag_enabled_for_vendor') \ .with_args(feature_names.SINGLE_TRACK_PRODUCT_VARIOUS_ARTISTS, vendor_id).and_return(False) flexmock(features).should_receive('is_feature_flag_enabled') \ .with_args(feature_names.REJECTIONS_FROM_CONTENTREVIEW, use_account_context=True) \ .and_return(False) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers_with_orchard_user_id) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body.get('validations').get( 'tracks') == valid_tracks_validation_message @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_invalid_meta_language_as_list( neo4j_exit, neo4j_enter, mock_session, client, valid_headers_with_orchard_user_id, product_id, setup_validation_mock_calls, valid_ows_product_get_response, cr_rejections_disabled, featuring_artist_mismatch_disabled, basic_product_tracks_response, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test validating invalid product meta language and formatting as a list.""" bit_depth_response = MagicMock(status_code=200) existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1), meta_language='FOO') subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request)\ .should_receive('get')\ .with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id))\ .and_return(mock_response) flexmock(request)\ .should_receive('get')\ .with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id))\ .and_return(bit_depth_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) vendor_id = valid_ows_product_get_response.get('vendor_id') flexmock(features).should_receive('is_feature_flag_enabled_for_vendor') \ .with_args(feature_names.PRIMARY_ARTIST_ERROR_VALIDATION, vendor_id).and_return(False) flexmock(features).should_receive('is_feature_flag_enabled_for_vendor') \ .with_args(feature_names.SINGLE_TRACK_PRODUCT_VARIOUS_ARTISTS, vendor_id).and_return(False) flexmock(features).should_receive('is_feature_flag_enabled') \ .with_args(feature_names.REJECTIONS_FROM_CONTENTREVIEW, use_account_context=True) \ .and_return(False) get_response = client.get( '/product/audio/{}/validate?format=list'.format(product_id), headers=valid_headers_with_orchard_user_id) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body == { 'validations': { 'is_valid': False, 'errors': [ { 'code': 'META_LANGUAGE_INVALID', 'reason': 'meta_language must be a valid value' } ], 'warnings': [], 'tracks': {} } } @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_invalid_meta_language( neo4j_exit, neo4j_enter, mock_session, client, valid_headers_with_orchard_user_id, product_id, setup_validation_mock_calls, valid_ows_product_get_response, cr_rejections_disabled, featuring_artist_mismatch_disabled, basic_product_tracks_response, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test validating invalid product meta language.""" bit_depth_response = MagicMock(status_code=200) existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1), meta_language='FOO') subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request)\ .should_receive('get')\ .with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id))\ .and_return(mock_response) flexmock(request)\ .should_receive('get')\ .with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id))\ .and_return(bit_depth_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) vendor_id = valid_ows_product_get_response.get('vendor_id') flexmock(features).should_receive('is_feature_flag_enabled_for_vendor') \ .with_args(feature_names.PRIMARY_ARTIST_ERROR_VALIDATION, vendor_id).and_return(False) flexmock(features).should_receive('is_feature_flag_enabled_for_vendor') \ .with_args(feature_names.SINGLE_TRACK_PRODUCT_VARIOUS_ARTISTS, vendor_id).and_return(False) flexmock(features).should_receive('is_feature_flag_enabled') \ .with_args(feature_names.REJECTIONS_FROM_CONTENTREVIEW, use_account_context=True) \ .and_return(False) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers_with_orchard_user_id) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body.get('validations').get('product_basics') == { 'errors': { 'meta_language': { 'error_code': 'meta_language_invalid', 'message': 'meta_language must be a valid value', 'validator': 'invalid', 'validator_value': True } }, 'valid': False } @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_single_track_artist_validations_disabled( neo4j_exit, neo4j_enter, mock_session, client, valid_headers_with_orchard_user_id, product_id, valid_tracks_validation_message, setup_validation_mock_calls, orchard_user_id, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test that track results include bit depth validation.""" product_tracks_response = MagicMock( status_code=200, json=MagicMock(return_value={ 'items': [{ 'tuid': '123', 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry VIII' }] }] }) ) product_tracks_validation_response = MagicMock( status_code=200, json=MagicMock(return_value={ 'valid': True, 'total_tracks': 1, 'valid_tracks': 1, 'errors': [] }) ) existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(product_tracks_response)) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks/validate'.format(product_id), headers={ 'Orchard-User-Id': orchard_user_id, 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '123' }, params={ 'validation_context': 'pre_submission', } ).and_return(product_tracks_validation_response)) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers=valid_headers_with_orchard_user_id) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body.get('validations').get('tracks') == { 'valid': True, 'total_tracks': 1, 'valid_tracks': 1, 'errors': [] } @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validations_invalid_various_artists( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, setup_validation_mock_calls, empty_success, track_validation_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, various_artist_on_featuring_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test that context query param is passed to ows-track.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory( release_id=product_id, artist_name='Various Artists') db.seed_models([existing_release, subgenre, release_artist]) product_tracks_response = MagicMock( status_code=200, json=MagicMock(return_value={ 'items': [{ 'tuid': '123', 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry VIII' }] },{ 'tuid': '234', 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry VII' }] },{ 'tuid': '345', 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry VI' }] } ] }) ) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) flexmock(request).should_receive('get').with_args( services.OWS_BLOCKLIST_MANAGER, '/validate/{}'.format(product_id), ).and_return(empty_success) flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(product_tracks_response) flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks/validate'.format(product_id), headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, params={ 'validation_context': 'post_submission', }, ).and_return(track_validation_response) flexmock(request).should_receive('get').with_args( services.OWS_BLOCKLIST_MANAGER, '/validate/{}'.format(product_id), headers={ 'Orchard-Profile-Id': '33252352', 'Orchard-Profile-Type': 'ContentProfile', } ).and_return(empty_success) # Mock validate_account so the parallel Executor doesn't race on DB reads # (release.get_release) or hit unmocked pythonfeatures from worker threads. flexmock(product_validation).should_receive('validate_account').with_args( product_id ).and_return(oto_response.Response(message={ 'account': {'valid': True, 'errors': []} })) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers={ **valid_headers, 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, query_string='validation_context=post_submission', ) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body.get('validations').get('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' }] } @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validations_valid_various_artists( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, setup_validation_mock_calls, empty_success, track_validation_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, various_artist_on_featuring_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test that context query param is passed to ows-track.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory( release_id=product_id, artist_name='Various Artists') db.seed_models([existing_release, subgenre, release_artist]) product_tracks_response = MagicMock( status_code=200, json=MagicMock(return_value={ 'items': [{ 'tuid': '123', 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry VIII' }] },{ 'tuid': '234', 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry VII' }] },{ 'tuid': '345', 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry VI' }] },{ 'tuid': '456', 'artists': [{ 'type': 'performer', 'name': 'Jarvis Q. Hinkleberry V' }] } ] }) ) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) flexmock(request).should_receive('get').with_args( services.OWS_BLOCKLIST_MANAGER, '/validate/{}'.format(product_id), ).and_return(empty_success) flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(product_tracks_response) flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks/validate'.format(product_id), headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, params={ 'validation_context': 'post_submission', }, ).and_return(track_validation_response) flexmock(request).should_receive('get').with_args( services.OWS_BLOCKLIST_MANAGER, '/validate/{}'.format(product_id), headers={ 'Orchard-Profile-Id': '33252352', 'Orchard-Profile-Type': 'ContentProfile', } ).and_return(empty_success) # Mock validate_account so the parallel Executor doesn't race on DB reads # (release.get_release) or hit unmocked pythonfeatures from worker threads. flexmock(product_validation).should_receive('validate_account').with_args( product_id ).and_return(oto_response.Response(message={ 'account': {'valid': True, 'errors': []} })) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers={ **valid_headers, 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, query_string='validation_context=post_submission', ) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body.get('validations').get('product_artists') == { 'valid': True, 'errors': [] } @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_dataload_product_validation_with_valid_product( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, successful_validation_message, basic_product_tracks_response, setup_validation_mock_calls, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, request_engine, get_project_response_message, valid_ows_tracks_message, monkeypatch, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test dataloader that valid products return payload without errors.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) post_response = client.post( '/product/audio/validate/dataloader', json={'product_ids': [product_id]}, headers=valid_headers ) response_body = json.loads(post_response.data.decode()) assert post_response.status_code == 200 assert response_body == [successful_validation_message] @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_dataload_product_validation_with_invalid_product( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, setup_validation_mock_calls, scheduling_and_pricing_validation_error, valid_tracks_validation_message, basic_product_tracks_response, valid_ows_product_get_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled): """Test dataload invalid products returns a payload with errors.""" existing_release = release_factory.ReleaseFactory.build( project_id=12345, release_id=product_id, c_line=None) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) post_response = client.post( '/product/audio/validate/dataloader', json={'product_ids': [product_id]}, headers=valid_headers ) response_body = json.loads(post_response.data.decode()) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id), params={ 'physical_location_id': config.ASSETS_PHYSICAL_LOCATION_ID }, ) .and_return(bit_depth_success_response) ) invalid_validate_audio_product_body = { 'validations': { 'product_basics': { 'valid': False, 'errors': { 'c_line': { 'message': "'c_line' is a required property", 'validator': 'required', 'validator_value': True, 'error_code': 'c_line_required', } } }, 'tracks': valid_tracks_validation_message, 'publishing_obligation': valid_tracks_validation_message, 'artwork': {'errors': [], 'valid': True}, 'scheduling_and_pricing': scheduling_and_pricing_validation_error}} assert post_response.status_code == 200 assert response_body == [invalid_validate_audio_product_body] @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_dataload_product_validation_post_submission_success( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, successful_validation_message_with_post_submission, setup_validation_mock_calls, empty_success, track_validation_response, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, various_artist_on_featuring_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test dataloader context query param is passed to ows-track.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) flexmock(request).should_receive('get').with_args( services.OWS_BLOCKLIST_MANAGER, '/validate/{}'.format(product_id), headers={ 'Orchard-Profile-Id': '33252352', 'Orchard-Profile-Type': 'ContentProfile', } ).and_return(empty_success) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks/validate'.format(product_id), headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, params={ 'validation_context': 'post_submission', }, ).and_return(track_validation_response) # Mock validate_account so the parallel Executor doesn't race on DB reads # (release.get_release) or hit unmocked pythonfeatures from worker threads. flexmock(product_validation).should_receive('validate_account').with_args( product_id ).and_return(oto_response.Response(message={ 'account': {'valid': True, 'errors': []} })) post_response = client.post( '/product/audio/validate/dataloader', json={'product_ids': [product_id], 'validation_context': 'post_submission'}, headers={ **valid_headers, 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', } ) response_body = json.loads(post_response.data.decode()) assert post_response.status_code == 200 assert response_body == [successful_validation_message_with_post_submission] @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_dataload_product_validation_failure_post_submission( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, successful_validation_message_with_post_submission, setup_validation_failure_mock_calls, empty_success, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_disabled, various_artist_on_featuring_disabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, ): """Test that dataload audio product post submission validation fails.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) flexmock(request).should_receive('get').with_args( services.OWS_BLOCKLIST_MANAGER, '/validate/{}'.format(product_id), headers={ 'Orchard-Profile-Id': '33252352', 'Orchard-Profile-Type': 'ContentProfile', } ).and_return(empty_success) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) # Mock validate_account so the parallel Executor doesn't race on DB reads # (release.get_release) or hit unmocked pythonfeatures from worker threads. flexmock(product_validation).should_receive('validate_account').with_args( product_id ).and_return(oto_response.Response(message={ 'account': {'valid': True, 'errors': []} })) post_response = client.post( '/product/audio/validate/dataloader', json={'product_ids': [product_id], 'validation_context': 'post_submission'}, headers={ **valid_headers, 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', } ) response_body = json.loads(post_response.data.decode()) assert post_response.status_code == 502 assert response_body == { 'code': 'some_error_code_xyz', 'message': 'Failed to get track validations' } @db.test_schema @patch('connector_neo4j.Neo4jSession') @patch('connector_neo4j.Neo4jSession.__enter__') @patch('connector_neo4j.Neo4jSession.__exit__') def test_get_product_validation_post_submission_with_pricing_warnings( neo4j_exit, neo4j_enter, mock_session, client, valid_headers, product_id, successful_validation_message_with_post_submission, empty_success, basic_product_tracks_response, mock_language_model_response, primary_artist_mismatch_error_disabled, single_track_product_various_artists_disabled, cr_rejections_disabled, featuring_artist_mismatch_disabled, remixer_mismatch_validation_disabled, producer_mismatch_validation_disabled, product_name_length_validation_disabled, various_artist_on_featuring_disabled, spotify_watchlist_artist_validation_disabled, artwork_metadata_mismatch_validation_disabled, preorder_pricing_validation_enabled, spatial_upc_validation_disabled, release_level_lyricist_disabled, valid_ows_product_get_response, get_project_response_message, get_assets_response, track_validation_response, publishing_obligation_validation_response, orchard_user_id ): """Test that context query param is passed to ows-track.""" existing_release = release_factory.ReleaseFactory.build( release_id=product_id, release_date=datetime.date(1999, 12, 31), project_id=12345, sale_start_date=datetime.date(2001, 1, 1), preorder_date=datetime.date(2000, 1, 1)) subgenre = subgenre_factory.ReleaseSubgenreFactory.build( release_id=product_id) release_artist = artist_factory.ReleaseArtistFactory.build( release_id=product_id) db.seed_models([existing_release, subgenre, release_artist]) # Setup mocks for product endpoint product_response = MagicMock(status_code=200) product_response.json = MagicMock( return_value=valid_ows_product_get_response) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT, '/product/{}'.format(product_id) ).and_return(product_response) # Setup project response project_response = MagicMock(status_code=200) project_response.json = MagicMock( return_value=get_project_response_message) flexmock(request).should_receive('get').with_args( services.OWS_PROJECT_MANAGER, '/project/12345', ).and_return(project_response) # Setup account response account_brand_response = MagicMock(status_code=200) account_brand_response.text = 'brand' flexmock(request).should_receive('get').with_args( services.OWS_ACCOUNT, '/vendor/brand/9999', ).and_return(account_brand_response) # Setup neo4j vendor mock flexmock(neo4j_vendor).should_receive('get_recent_awal_artist').and_return('') # Setup artwork validators flexmock(request).should_receive('get').with_args( services.OWS_ASSETS, '/v2/validators/product/{}/artwork'.format(product_id), params={} ).and_return(get_assets_response) flexmock(request).should_receive('get').with_args( services.OWS_ASSETS, '/validators/product/{}/artwork'.format(product_id), ).and_return(get_assets_response) (flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks'.format(product_id), ).and_return(basic_product_tracks_response)) # Setup track validation flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks/validate'.format(product_id), headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', 'Orchard-Identity-Id': '123', }, params={ 'validation_context': 'post_submission', }, ).and_return(track_validation_response) # Setup publishing obligation flexmock(request).should_receive('get').with_args( services.OWS_TRACK, '/product/{}/tracks/validate/publishing-obligation'.format(product_id), params={'account_id': '', 'account_type': ''}, headers={ 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', }, ).and_return(publishing_obligation_validation_response) # Setup release-approval endpoint mock_response = MagicMock(status_code=404) flexmock(request).should_receive('get').with_args( services.OWS_PRODUCT_WORKFLOW, '/product/{}/release-approval'.format(product_id), ).and_return(mock_response) # Setup blocklist validation flexmock(request).should_receive('get').with_args( services.OWS_BLOCKLIST_MANAGER, '/validate/{}'.format(product_id), headers={ 'Orchard-Identity-Id': '123', 'Orchard-Profile-Id': '33252352', 'Orchard-Profile-Type': 'ContentProfile', } ).and_return(empty_success) # Setup bit-depth validation bit_depth_response = MagicMock(status_code=200) flexmock(request).should_receive('get').with_args( services.OWS_ASSETS, '/validators/product/{}/bit-depth'.format(product_id), params={'physical_location_id': config.ASSETS_PHYSICAL_LOCATION_ID}, ).and_return(bit_depth_response) # Setup assets response assets_response = MagicMock(status_code=200) assets_response.json = MagicMock(return_value={}) ( flexmock(request) .should_receive('get') .with_args( services.OWS_ASSETS, '/asset/product/{}'.format( product_id ), headers={}, ) .and_return(assets_response) ) # Setup pricing validation mock_pricing_response = MagicMock(status_code=200) mock_pricing_response.json = MagicMock( return_value={ 'valid': False, 'warnings': [{'warning_code': 'code', 'warning_message': 'message'}]} ) flexmock(request).should_receive('get').with_args( services.OWS_PRICING, '/product/{}/validate'.format(product_id), ).and_return(mock_pricing_response) # Mock validate_account so the parallel Executor doesn't race on DB reads # (release.get_release) or hit unmocked pythonfeatures from worker threads. flexmock(product_validation).should_receive('validate_account').with_args( product_id ).and_return(oto_response.Response(message={ 'account': {'valid': True, 'errors': []} })) get_response = client.get( '/product/audio/{}/validate'.format(product_id), headers={ **valid_headers, 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '33252352', 'Orchard-Identity-Id': '123', }, query_string='validation_context=post_submission', ) response_body = json.loads(get_response.data.decode()) assert get_response.status_code == 200 assert response_body['validations']['scheduling_and_pricing'] == { 'valid': True, 'errors': {}, 'warnings': [{'code': 'CODE', 'reason': 'message'}]}