"""conftest.py. This file gets picked up when running py.test tests: http://pytest.org/latest/writing_plugins.html#conftest """ from copy import deepcopy from unittest.mock import MagicMock import datetime import application from oto import response from owslogger import flask_logger from owsrequest import request from owsrequest import test_utils from owsrequest.constants import headers from owsresponse.response import Response import pytest from sqlalchemy import text from ows_product_physical.constant import error from ows_product_physical.constant import field from ows_product_physical.constant import header from ows_product_physical.constant import success from ows_product_physical.features import pythonfeatures from tests.utils import db class MockLogAdaptor: """Mocking Class for Logger. This class replaces the ows logger class, so it's easier to use and test in our codebase. """ def error(self, message, *args, **kwargs): """Log errors. Args: message (str): the message to log. args (list): additional arguments. kwargs (dict): additional named arguments. """ pass def warning(self, message, *args, **kwargs): """Log Warnings. Args: message (str): the message to log. args (list): additional arguments. kwargs (dict): additional named arguments. """ pass def info(self, message, *args, **kwargs): """Log information. Args: message (str): the message to log. args (list): additional arguments. kwargs (dict): additional named arguments. """ pass class FeatureEngineAdaptor: """Mocking Class for Feature Engine. Patching the real feature flags client. """ def __init__(self): """Setting empty feature_flags by default.""" self.feature_flags = {} def force_flag(self, flag_name, flag_value): """Force a flag value. Args: flag_name (str): the name of the flag. flag_value (bool): the value of the flag. Set it to true for getting "enabled" value on getting the flag. Set false for getting "control". """ self.feature_flags[flag_name] = flag_value def get(self, flag_name): """Get a flag. Args: flag_name (str): the name of the flag to get. """ return self.feature_flags.get(flag_name) @pytest.fixture def feature_engine(mocker): """Fixture to mock feature flags. Use it in the following way: def test_muy_feature(feature_engine): feature_engine.force_flag('my_flag', True) # run code using the flag you set. """ _feature_engine = FeatureEngineAdaptor() def get_single_feature(flag_name, *args, **kwargs): return Response( status=200, message=("enabled" if _feature_engine.get(flag_name) else "control") ) mocker.patch.object( pythonfeatures, "get_single_feature", side_effect=get_single_feature) yield _feature_engine @pytest.fixture def valid_artist_id(): """Return a test artist_id.""" return 123 @pytest.fixture def valid_create_response(): """Return a valid general create response with an insert id. Returns: Response: a valid Response """ return response.Response( message={'status': 'ok', 'lastrowid': 47}, status=201) @pytest.fixture def valid_create_response_error(): """Return a general create response error response. Returns: Response: a valid error Response """ return response.create_error_response( code=error.INTERNAL_ERROR, message='mysql error', status=400) @pytest.fixture def valid_vendor_id(): """Return a test vendor id.""" return 7123 @pytest.fixture def valid_subaccount_id(): """Return a test subaccount.""" return 555 @pytest.fixture def valid_project_code(): """Return a test product code.""" return 'CDR1222' @pytest.fixture def valid_get_product_physical_packaging_response(): """Return a valid packaging response.""" data = [ { 'id': 1, 'name': 'Brilliant Case (Jewel Case size, Holds 2 CDs)' }, { 'id': 2, 'name': 'Blister Pack' }, { 'id': 3, 'name': 'Blu-Ray Packaging' }, { 'id': 4, 'name': 'Box Set' }, { 'id': 5, 'name': 'Custom Packaging' }, { 'id': 6, 'name': 'Clam Shell' }] return data @pytest.fixture def current_date(): """Today.""" return datetime.date.today() @pytest.fixture def valid_placeholder_upc(): """Placeholder upc.""" return 20000000000042 @pytest.fixture def valid_sale_start_date(current_date): """Valid sale start date.""" start = current_date + datetime.timedelta(days=2) return start + datetime.timedelta(days=60) @pytest.fixture def valid_release_date(current_date): """Valid release date.""" return current_date + datetime.timedelta(days=60) @pytest.fixture def valid_date_changed(current_date): """Valid changed date.""" return current_date + datetime.timedelta(days=2) @pytest.fixture def valid_display_upc(): """Valid display upc.""" return '0000007686986' @pytest.fixture def valid_country_of_origin(): """Valid country of origin.""" return 3 @pytest.fixture def valid_release_upc(): """Return a valid UPC in test release database.""" return 666677778888 @pytest.fixture def post_data_no_upcish(valid_sale_start_date): """Return a dict with valid post fields except for UPC and DISPLAY_UPC.""" return { field.ARTIST_IS_INDIVIDUAL: 'Y', field.CLINE: '1986 Record Label', field.PROJECT_ID: 1234, field.PRODUCT_NAME: 'Test Release 1234', field.PRIMARY_ARTIST: 'Primary Artist', field.LABEL: 'The Hit Factory', field.VERSION: 'Mexican', field.GENRE_ID: 56, field.SUBGENRE_ID: 56, field.RELEASE_DATE: '2016-08-08', field.SALE_START_DATE: valid_sale_start_date.isoformat(), field.PRODUCT_TYPE: 'Catalog', field.DISTRIBUTION_FORMAT_ID: 2, field.PRODUCT_CODE: 'ABCD1234', field.PACKAGING_ID: 2, field.EXCLUSIVE_FOR: 'Taco Bell', field.INITIAL_STOCK: 4500, field.UNITS_PER_SET: 2, field.BOX_LOT: 30, field.PRICING: 9.99, field.END_DATE: '2016-12-12', field.DISCOUNT: 'discount 15', field.EXPLICIT: 'Y', field.MANUFACTURING_OBLIGATION: 'N', field.SPECIAL_INSTRUCTIONS: 'not for human ingestion', field.DESCRIPTION: 'Pretty good', field.PLINE: '2001 Demo Publishing', field.PRODUCT_HIGHLIGHTS: 'Product Highlight Description', field.DISPLAY_CONFIGURATION: '2 x CD + Viking Ship', field.JAPAN_DISTRIBUTION: 'yes_other', field.EDITION: 'normal_edition'} @pytest.fixture def valid_post_product_data(post_data_no_upcish, valid_display_upc): """Return a dict representing JSON sent in post body for POST /product. Returns: data (dict): a dict containing an example successful json post body for POST /product """ data = deepcopy(post_data_no_upcish) data[field.DISPLAY_UPC] = valid_display_upc return data @pytest.fixture def valid_post_product_sql_object(valid_post_product_data): """Valid post product SQL object.""" data = deepcopy(valid_post_product_data) class MockSqlObj: _mapping = data return MockSqlObj() @pytest.fixture def valid_release_data_by_subaccount(db_release_id_with_tracks): """Return a collection of release id and subaccount_id. The result of a successful call to `persister.get_release_by_product_code` Returns: data (list): list of dicts containing release_id and subaccount_ids. """ return [ {'release_id': db_release_id_with_tracks, 'subaccount_id': 666}, {'release_id': 3, 'subaccount_id': 666}, {'release_id': 5, 'subaccount_id': 667}, {'release_id': 6, 'subaccount_id': 668} ] @pytest.fixture def valid_create_product_fields( valid_display_upc, valid_sale_start_date, valid_placeholder_upc): """Return a dict representing JSON sent in post body for POST /product. Returns: data (dict): a dict containing an example successful json post body for POST /product """ data = { field.ARTIST_IS_INDIVIDUAL: 'Y', field.CLINE: '1986 Record Label', field.PROJECT_ID: 1234, field.UPC: valid_placeholder_upc, field.DISPLAY_UPC: valid_display_upc, field.MANUFACTURER_UPC: '3210987654321', field.PRODUCT_NAME: 'Test Release 1234', field.PRIMARY_ARTIST: 'Primary Artist', field.LABEL: 'The Hit Factory', field.VERSION: 'Mexican', field.GENRE_ID: 56, field.SUBGENRE_ID: 56, field.RELEASE_DATE: '2016-08-08', field.SALE_START_DATE: valid_sale_start_date.isoformat(), field.PRODUCT_TYPE: 'Catalog', field.DISTRIBUTION_FORMAT_ID: 2, field.PRODUCT_CODE: 'ABCD1234', field.PACKAGING_ID: 2, field.EXCLUSIVE_FOR: 'Taco Bell', field.INITIAL_STOCK: 4500, field.UNITS_PER_SET: 2, field.BOX_LOT: 30, field.PRICING: 9.99, field.END_DATE: '2016-12-12', field.DISCOUNT: 'discount 15', field.WHOLESALE_PRICE: 13.2, field.EXPLICIT: 'Y', field.SPECIAL_INSTRUCTIONS: 'not for human ingestion', field.DESCRIPTION: 'Pretty good', field.PLINE: '2001 Demo Publishing', field.PRODUCT_HIGHLIGHTS: 'Product Highlight Description', field.DISPLAY_CONFIGURATION: '2 x CD + Viking Ship'} return data @pytest.fixture def valid_create_physical_product_fields(db_release_id): """Return a dict representing data to create a physical product.""" data = { 'individual': 'Y', field.RELEASE_ID: db_release_id, field.PACKAGING_ID: 2, field.EXCLUSIVE_FOR: 'Taco Bell', field.INITIAL_STOCK: 4500, field.UNITS_PER_SET: 2, field.BOX_LOT: 30, field.PRICING: 9.99, field.END_DATE: '2016-12-12', field.DISCOUNT: 'discount 15', field.WHOLESALE_PRICE: 13.2, field.EXPLICIT: 'Y', field.MANUFACTURING_OBLIGATION: 'Y', field.PLINE: '2001 Demo Publishing', field.PRODUCTION_NOTES: 'Product Highlight Description', field.DISPLAY_CONFIGURATION: '2 x CD + Viking Ship', field.JAPAN_DISTRIBUTION: 'no', field.EDITION: 'normal_edition'} return data @pytest.fixture def valid_create_physical_product_change_history_fields_artwork( db_release_id, valid_date_changed): """Return a dict representing data for a physical change history row.""" data = { field.ARTWORKPATH: '/path/to/artwork', field.PRODUCT_ID: db_release_id, field.FIELD_NAME: 'artwork', field.DATE_CHANGED: valid_date_changed.isoformat()} return data @pytest.fixture def invalid_create_physical_product_change_history_field_name(): """Return a dict representing invalid POST data.""" return {field.UPC: 111222333445, field.FIELD_NAME: 'artworkz'} @pytest.fixture def invalid_create_physical_product_change_history_upc(): """Return a dict representing invalid POST upc data.""" return {field.UPC: 1112223334498, field.FIELD_NAME: 'artwork'} @pytest.fixture def valid_post_physical_product_change_history_fields_artwork(): """Return a dict representing valid POST data.""" return {field.UPC: 111222333445, field.FIELD_NAME: 'artwork'} @pytest.fixture def valid_create_physical_product_change_history_fields_pricing( db_release_id, valid_date_changed): """Return a dict representing data for a physical change history row.""" data = { field.PRODUCT_ID: db_release_id, field.FIELD_NAME: 'wholesale_price', field.DATE_CHANGED: valid_date_changed.isoformat(), field.STORE_ID: 2, field.NEW_PRICE: '25.00'} return data @pytest.fixture def valid_post_physical_product_change_history_fields_pricing(): """Return a dict representing POST data for a physical history change.""" data = { field.FIELD_NAME: 'wholesale_price', field.STORE_ID: 2, field.NEW_PRICE: '25.00'} return data @pytest.fixture def valid_create_release_fields(valid_sale_start_date): """Return a dict representing data for a release.""" data = { field.RELEASE_ID: 1, field.UPC: 111222333444, field.DISPLAY_UPC: '111222333444', field.PRODUCT_NAME: 'The Answer to Everything', field.ARTIST_ID: 12, field.SUBACCOUNT_ID: 666, field.LABEL: 'Mexicali Blues Records', field.RELEASE_DATE: '2016-03-31', field.GENRE_ID: 12, 'new_release': 'Catalog', field.DISTRIBUTION_FORMAT_ID: 1, field.CLINE: '2016 Demo Records', field.SALE_START_DATE: valid_sale_start_date.isoformat(), field.PRODUCT_CODE: 'PHYS1', field.VERSION: 'Mexican Version', field.RELEASE_STATUS: 'orchard_processing', field.PROJECT_ID: 1234, field.SPECIAL_INSTRUCTIONS: 'Doubles as a plate for your burritos', field.DESCRIPTION: 'Pretty good.', field.NOT_FOR_DISTRIBUTION: field.REASON_NO, field.VENDOR_CATALOG_NUMBER: 'PHYS1'} return data @pytest.fixture def valid_put_product_data(): """Return a dict representing JSON sent in post body for PUT /product. Returns: data (dict): a dict containing an example successful json post body for PUT /product """ data = { field.PRODUCT_NAME: 'Test Release 1234', field.SPECIAL_INSTRUCTIONS: None, field.GENRE_ID: 2, field.SUBGENRE_ID: 355, field.DISPLAY_CONFIGURATION: '2 x CD + Viking Ship', field.COUNTRY_OF_ORIGIN: 3, } return data @pytest.fixture def valid_post_product_data_required_fields_only(valid_post_product_data): """Return a dict representing JSON sent in post body. For POST /product, with required fields only. Returns: data (dict): a dict containing an example successful json post body for POST /product with required fields only. """ product = valid_post_product_data data = { field.PROJECT_ID: product.get(field.PROJECT_ID), field.PRODUCT_NAME: product.get(field.PRODUCT_NAME), field.PRIMARY_ARTIST: product.get(field.PRIMARY_ARTIST), field.LABEL: product.get(field.LABEL), field.GENRE_ID: product.get(field.GENRE_ID), field.SUBGENRE_ID: product.get(field.SUBGENRE_ID), field.RELEASE_DATE: product.get(field.RELEASE_DATE), field.SALE_START_DATE: product.get(field.SALE_START_DATE), field.PRODUCT_TYPE: product.get(field.PRODUCT_TYPE), field.DISTRIBUTION_FORMAT_ID: product.get( field.DISTRIBUTION_FORMAT_ID), field.PACKAGING_ID: product.get(field.PACKAGING_ID), field.PRODUCT_CODE: product.get(field.PRODUCT_CODE), field.WHOLESALE_PRICE: product.get(field.WHOLESALE_PRICE), field.DESCRIPTION: product.get(field.DESCRIPTION), field.PLINE: product.get(field.PLINE), field.NOT_FOR_DISTRIBUTION: field.REASON_NO} return data @pytest.fixture def valid_post_product_sql_object_required_fields_only( valid_post_product_data ): """Valid post product SQL object with required fields only.""" data = deepcopy(valid_post_product_data) class MockSqlObj: _mapping = data return MockSqlObj() @pytest.fixture def valid_put_product_tracks_data(): """Return a dict representing JSON sent in PUT body. For PUT /product/{productId}/tracks. """ data = { 'items': [ { 'track_number': 1, 'track_name': 'Hightail it to Chalupa-ville', 'performer': ['Dave Matthews'], 'isrc': 'MEXTUNES0001', 'disc': 1, 'length': '00:07:23' }, { 'track_number': 2, 'track_name': 'I Met a Gordita in Tijuana', 'performer': ['Johnny Cash'], 'isrc': 'MEXTUNES0002', 'disc': 1, 'length': '00:05:12' }, { 'track_number': 3, 'track_name': 'Yo Soy Loco Con Los Doritos Locos Tacos', 'performer': ['Vanilla Ice'], 'length': '00:05:12' }, { 'track_number': 1, 'track_name': 'Holy Frijoles', 'performer': ['Vanilla Ice'], 'length': '01:23:47', 'isrc': 'MEXTUNES0003', 'disc': 2 }, { 'track_number': 2, 'track_name': 'Cross the Border to Taco Town', 'performer': ['Vanilla Ice'], 'isrc': 'MEXTUNES0004', 'disc': 2, 'length': '00:05:12' }, { 'track_number': 3, 'track_name': 'El Burro y El Burrito', 'performer': ['Vanilla Ice'], 'isrc': 'MEXTUNES0005', 'disc': 2, 'length': '00:05:12' }]} return data @pytest.fixture def valid_put_product_tracks_response_data(): """Return a dict representing JSON sent in PUT body. For PUT /product/{productId}/tracks. """ data = { 'items': [ { 'track_number': 1, 'track_name': 'Hightail it to Chalupa-ville', 'performer': ['Dave Matthews'], 'side': None, 'song_writers': [], 'isrc': 'MEXTUNES0001', 'disc': 1, 'length': { 'formatted': '00:07:23', 'hours': 0, 'minutes': 7, 'seconds': 23 }, 'third_party_publisher': 'N', 'us_publishing_obligation': None, 'publisher_names': [] }, { 'track_number': 2, 'track_name': 'I Met a Gordita in Tijuana', 'performer': ['Johnny Cash'], 'side': None, 'song_writers': [], 'isrc': 'MEXTUNES0002', 'disc': 1, 'length': { 'formatted': '00:05:12', 'hours': 0, 'minutes': 5, 'seconds': 12}, 'third_party_publisher': 'N', 'us_publishing_obligation': None, 'publisher_names': [] }, { 'track_number': 3, 'track_name': 'Yo Soy Loco Con Los Doritos Locos Tacos', 'performer': ['Vanilla Ice'], 'side': None, 'song_writers': [], 'length': { 'formatted': '00:05:12', 'hours': 0, 'minutes': 5, 'seconds': 12}, 'disc': 1, 'isrc': None, 'third_party_publisher': 'N', 'us_publishing_obligation': None, 'publisher_names': [] }, { 'track_number': 1, 'track_name': 'Holy Frijoles', 'performer': ['Vanilla Ice'], 'side': None, 'song_writers': [], 'length': { 'formatted': '01:23:47', 'hours': 1, 'minutes': 23, 'seconds': 47 }, 'third_party_publisher': 'N', 'us_publishing_obligation': None, 'publisher_names': [], 'isrc': 'MEXTUNES0003', 'disc': 2 }, { 'track_number': 2, 'track_name': 'Cross the Border to Taco Town', 'performer': ['Vanilla Ice'], 'side': None, 'song_writers': [], 'isrc': 'MEXTUNES0004', 'disc': 2, 'length': { 'formatted': '00:05:12', 'hours': 0, 'minutes': 5, 'seconds': 12}, 'third_party_publisher': 'N', 'us_publishing_obligation': None, 'publisher_names': [] }, { 'track_number': 3, 'track_name': 'El Burro y El Burrito', 'performer': ['Vanilla Ice'], 'side': None, 'song_writers': [], 'isrc': 'MEXTUNES0005', 'disc': 2, 'length': { 'formatted': '00:05:12', 'hours': 0, 'minutes': 5, 'seconds': 12}, 'third_party_publisher': 'N', 'us_publishing_obligation': None, 'publisher_names': [] }]} return data @pytest.fixture def valid_update_product_tracks_data(): """Return a dict representing JSON sent in PUT body. For POST /product/{productId}/tracks. """ data = {'items': [{ 'track_id': 1, 'track_name': 'New York to New Dorp', 'performer': ['SBTRKT'], 'length': '00:01:00' }]} return data @pytest.fixture def valid_update_tracks_data_with_nulls(): """Return a dict representing JSON sent in PUT body. For POST /product/{productId}/tracks. """ data = {'items': [{ 'track_id': 1, 'track_name': 'New York to New Dorp', 'performer': ['SBTRKT'], 'isrc': None, 'disc': None, 'length': '00:01:00' }]} return data @pytest.fixture def invalid_post_product_data(): """Return a dict representing JSON sent in post body for POST /product. * missing UPC * product name is a number instead of string * product code is longer than 10 characters (16) Returns: data (dict): a dict containing an example invalid json post body for POST /product """ data = { field.PROJECT_ID: 1234, field.PRODUCT_NAME: 555, field.PRODUCT_CODE: '123456789ABCDEFG'} return data @pytest.fixture def post_product_data_validation_schema(): """Return JSON Draft3 Schema for validating a POST /product request. Returns: schema (dict): JSON Draft3 Validation Schema """ schema = { '$schema': 'http://json-schema.org/draft-03/schema', 'properties': { 'project_id': { 'required': True, 'type': 'integer', 'blank': True }, 'product_name': { 'required': True, 'type': 'string', 'maxLength': 255 }, 'product_code': { 'type': 'string', 'required': True, 'blank': False, 'maxLength': 10 }, 'upc': { 'required': True, 'type': 'string' }, 'wholesale_price': { 'required': True, 'type': 'number', 'maximum': 9999.99, 'minimum': 0 } }, 'additionalProperties': False, 'required': True, 'type': 'object' } return schema @pytest.fixture def example_data_to_validate(): """Return some data to validate against the example validation schema. Returns: data (dict): a dict containing example data that validates against the example schema """ data = { 'apple': 1234, 'orange': '007686986', 'banana': 'nine', 'mango': 'Mexican'} return data @pytest.fixture def example_validation_schema(): """Return example JSON Draft3 Schema for testing the validator. Returns: schema (dict): JSON Draft3 Validation Schema """ schema = { '$schema': 'http://json-schema.org/draft-03/schema', 'properties': { 'apple': { 'required': True, 'type': 'integer', 'blank': True }, 'orange': { 'required': True, 'type': 'string', 'maxLength': 255 }, 'banana': { 'type': 'string', 'required': True, 'blank': False, 'maxLength': 10 }, 'mango': { 'required': True, 'type': 'string' } }, 'additionalProperties': False, 'required': True, 'type': 'object' } return schema @pytest.fixture def client(): """Return flask test client. Returns: flask client: flask test client """ return application.app.test_client() @pytest.fixture def get_header_validation_schema(): """Return typical schema for GET headers. Returns: (dict): schema """ schema = { 'properties': { header.GRASS_ACCOUNT_TYPE: { 'required': True, 'example': 'vendor', 'description': 'vendor | subaccount', 'type': 'string', 'pattern': '(vendor)|(subaccount)'}, header.GRASS_ACCOUNT_ID: { 'required': True, 'type': 'string', 'pattern': r'^\d+$', 'description': 'vendor_id | subaccount_id'}, header.CORRELATION_ID: { 'required': False, 'type': 'string', 'description': 'UUID' } }, 'required': True, 'type': 'object', '$schema': 'http://json-schema.org/draft-03/schema' } return schema @pytest.fixture def valid_get_header(valid_grass_account_vendor_id): """Return valid GET request headers. Returns: (dict): header dict """ data = { header.CORRELATION_ID: '1234567890-1234567890', header.GRASS_ACCOUNT_TYPE: 'vendor', header.GRASS_ACCOUNT_ID: str(valid_grass_account_vendor_id)} return data @pytest.fixture def distribution_profile_header(valid_grass_account_vendor_id): """Return valid DistributionProfile headers. Returns: (dict): header dict """ return { header.CORRELATION_ID: '1234567890-1234567890', headers.ORCHARD_PROFILE_ID: '137587', headers.ORCHARD_PROFILE_TYPE: 'DistributionProfile', headers.ORCHARD_PROFILE_UUID: '44941da3-d050-429f-a6b9-3aec9c5c3e81', headers.ORCHARD_IDENTITY_ID: '47d9a1be-ad2e-48cf-a848-6aecfb2dd026', headers.ORCHARD_ROLES: 'manage_nr_deliveries,deliver_physical_audio' } @pytest.fixture def post_header_validation_schema(): """Return typical schema for POST headers. Returns: (dict): schema """ schema = { 'properties': { header.GRASS_ACCOUNT_TYPE: { 'required': True, 'example': 'vendor', 'description': 'vendor | subaccount', 'type': 'string', 'pattern': '(vendor)|(subaccount)'}, header.CONTENT_TYPE: { 'required': True, 'pattern': 'application/json', 'type': 'string'}, header.GRASS_ACCOUNT_ID: { 'required': True, 'type': 'string', 'pattern': r'^\d+$', 'description': 'vendor_id | subaccount_id'}, header.CORRELATION_ID: { 'required': False, 'type': 'string', 'description': 'UUID' } }, 'required': True, 'type': 'object', '$schema': 'http://json-schema.org/draft-03/schema' } return schema @pytest.fixture def valid_post_header(valid_grass_account_vendor_id): """Return valid POST request headers. Returns: (dict): header dict """ data = { header.CONTENT_TYPE: 'application/json', header.CORRELATION_ID: '1234567890', header.GRASS_ACCOUNT_TYPE: 'vendor', header.GRASS_ACCOUNT_ID: str(valid_grass_account_vendor_id), header.ORCHARD_USER_ID: 'alw:12345' } return data @pytest.fixture def valid_put_header(valid_post_header): """Return valid PUT request headers. Returns: (dict): header dict """ return valid_post_header @pytest.fixture def valid_copy_header(valid_post_header): """Return a valid header object for copying a product.""" del valid_post_header['Grass-Account-Type'] del valid_post_header['Grass-Account-Id'] return valid_post_header @pytest.fixture def db_release_id(): """Return known test release with data in db.""" return 42 @pytest.fixture def db_release_id_with_tracks(): """Return known test release with data in db.""" return 1 @pytest.fixture def db_release_id_without_display_upc(): """Return known test release with data in db.""" return 474 @pytest.fixture def db_release_id_with_no_tracks(): """Return known test release with data in db.""" return 4 @pytest.fixture def db_release_id_with_history(): """Return known test release with data in db.""" return 2 @pytest.fixture def db_project_id(): """Return known test project id with data in db.""" return 1234 @pytest.fixture def db_releases( db_release_id, db_project_id, valid_sale_start_date, db_release_id_with_tracks, db_release_id_with_no_tracks, db_release_id_with_history, db_release_id_without_display_upc, valid_placeholder_upc, valid_release_date): """Return test data injected into database for releases table.""" return [{ 'release_id': db_release_id, 'upc': 666677778888, 'display_upc': '0666677778888', 'manufacturer_upc': '555577778888', 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 666, 'label': 'Mexicali Blues Records', 'release_date': valid_release_date.isoformat(), 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 12, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'DEMO0002', 'version': 'Mexican Version', 'release_status': 'label_processing', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'DEMO0002', 'country_of_origin': 3 }, { 'release_id': db_release_id_with_tracks, 'upc': 111222333444, 'display_upc': '111222333444', 'manufacturer_upc': '555577778888', 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 666, 'label': 'Mexicali Blues Records', 'release_date': '2016-03-31', 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 1, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'PHYS1', 'version': 'Mexican Version', 'release_status': 'orchard_processing', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'PHYS1', 'country_of_origin': 3 }, { 'release_id': db_release_id_with_history, 'upc': 111222333445, 'display_upc': '111222333445', 'manufacturer_upc': '555577778888', 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 666, 'label': 'Mexicali Blues Records', 'release_date': '2016-03-31', 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 1, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'PHYS2', 'version': 'Mexican Version', 'release_status': 'in_content', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'PHYS2', 'country_of_origin': 3 }, { 'release_id': 3, 'upc': 111222333446, 'display_upc': '111222333446', 'manufacturer_upc': '555577778888', 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 666, 'label': 'Mexicali Blues Records', 'release_date': '2016-03-31', 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 2, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'PHYS1', 'version': 'Mexican Version', 'release_status': 'orchard_processing', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'DIG1', 'country_of_origin': 3 }, { 'release_id': db_release_id_with_no_tracks, 'upc': 111222333447, 'display_upc': '111222333447', 'manufacturer_upc': '555577778888', 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 666, 'label': 'Mexicali Blues Records', 'release_date': '2016-03-31', 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 3, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'MV1', 'version': 'Mexican Version', 'release_status': 'orchard_processing', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'MV1', 'country_of_origin': 3 }, { 'release_id': 5, 'upc': 111222333448, 'display_upc': '111222333448', 'manufacturer_upc': '555577778888', 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 667, 'label': 'Mexicali Blues Records', 'release_date': '2016-03-31', 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 1, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'PHYS1', 'version': 'Mexican Version', 'release_status': 'orchard_processing', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'PHYS1', 'country_of_origin': 3 }, { 'release_id': 6, 'upc': 111222333449, 'display_upc': '111222333449', 'manufacturer_upc': '555577778888', 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 668, 'label': 'Mexicali Blues Records', 'release_date': '2016-03-31', 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 1, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'PHYS1', 'version': 'Mexican Version', 'release_status': 'label_processing', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'PHYS1', 'country_of_origin': 3 }, { 'release_id': db_release_id_without_display_upc, 'upc': valid_placeholder_upc, 'display_upc': None, 'manufacturer_upc': None, 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 668, 'label': 'Mexicali Blues Records', 'release_date': '2016-03-31', 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 1, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'PHYS12321', 'version': 'Mexican Version', 'release_status': 'label_processing', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'PHYS12321', 'country_of_origin': 3 }] @pytest.fixture def db_release( db_release_id, db_project_id, valid_sale_start_date): """Return test data injected into database for releases table.""" return { 'release_id': db_release_id, 'upc': 100075897922, 'display_upc': '0666677778888', 'manufacturer_upc': '555577778888', 'release_name': 'The Answer to Everything', 'artist_id': 12, 'subaccount_id': 666, 'label': 'Mexicali Blues Records', 'release_date': '2016-03-31', 'genre_id': 12, 'format': 'Full Length', 'new_release': 'Catalog', 'distribution_format_id': 12, 'c_line': '2016 Demo Records', 'sale_start_date': valid_sale_start_date.isoformat(), 'product_code': 'DEMO0002', 'version': 'Mexican Version', 'release_status': 'in_content', 'project_id': db_project_id, 'special_instructions': 'Doubles as a plate for your burritos', 'description': 'Pretty good.', 'not_for_distribution': 'N', 'vendor_catalog_number': 'DEMO0002' } @pytest.fixture def db_product_physical( db_release_id, db_release_id_with_no_tracks, db_release_id_without_display_upc, db_release_id_with_history): """Return test data injected into database for product_physical table.""" return [{ 'id': 1393292, 'release_id': db_release_id, 'packaging_id': 2, 'exclusive_for': 'Something', 'initial_stock': 5000, 'units_per_set': 12, 'box_lot': 13, 'pricing': 13.99, 'end_date': None, 'discount': 'Some discount', 'individual': 'Y', 'wholesale_price': 14.32, 'explicit': 'N', 'manufacturing_obligation': 'N', 'pline': '1999 Helloworld Productions', 'production_notes': 'Production Notes', 'display_configuration': '2 x CD + Viking Ship', 'japan_distribution': 'no', 'edition': 'normal_edition' }, { 'id': 1393293, 'release_id': 1, 'packaging_id': 2, 'exclusive_for': 'Place', 'initial_stock': 5858, 'units_per_set': 44, 'box_lot': 13, 'pricing': 13.99, 'end_date': None, 'discount': 'Some discount', 'individual': 'Y', 'wholesale_price': 14.32, 'explicit': 'N', 'manufacturing_obligation': 'N', 'pline': '1999 Helloworld Productions', 'production_notes': 'Production Notes', 'display_configuration': '', 'japan_distribution': 'no', 'edition': 'normal_edition' }, { 'id': 1393294, 'release_id': db_release_id_with_no_tracks, 'packaging_id': 2, 'exclusive_for': 'Something', 'initial_stock': 5000, 'units_per_set': 12, 'box_lot': 13, 'pricing': 13.99, 'end_date': None, 'discount': 'Some discount', 'individual': 'Y', 'wholesale_price': 14.32, 'explicit': 'N', 'manufacturing_obligation': 'N', 'pline': '1999 Helloworld Productions', 'production_notes': 'Production Notes', 'display_configuration': '2 x CD + Viking Ship', 'japan_distribution': 'no', 'edition': 'normal_edition' }, { 'id': 1393295, 'release_id': db_release_id_without_display_upc, 'packaging_id': 2, 'exclusive_for': 'Something', 'initial_stock': 5000, 'units_per_set': 12, 'box_lot': 13, 'pricing': 13.99, 'end_date': None, 'discount': 'Some discount', 'individual': 'Y', 'wholesale_price': 14.32, 'explicit': 'N', 'manufacturing_obligation': 'N', 'pline': '1999 Helloworld Productions', 'production_notes': 'Production Notes', 'display_configuration': '2 x CD + Viking Ship', 'japan_distribution': 'no', 'edition': 'normal_edition' }, { 'id': 1393297, 'release_id': db_release_id_with_history, 'packaging_id': 2, 'exclusive_for': 'Something', 'initial_stock': 5000, 'units_per_set': 12, 'box_lot': 20, 'pricing': 21.99, 'end_date': None, 'discount': 'Some discount', 'individual': 'Y', 'wholesale_price': 16.32, 'explicit': 'N', 'manufacturing_obligation': 'N', 'pline': '1999 Helloworld Productions', 'production_notes': 'Production Notes', 'display_configuration': '2 x CD + Viking Ship', 'japan_distribution': 'no', 'edition': 'normal_edition' }] @pytest.fixture def db_projects(db_project_id, valid_grass_account_vendor_id): """Return test data injected into database for projects table.""" return [ { 'project_id': db_project_id, 'project_code': '723724506329', 'vendor_id': valid_grass_account_vendor_id, 'subaccount_id': 666, 'project_name': 'Massenet: Werther', 'created_date_utc': '2016-03-31 15:52:23.000000', 'updated_date_utc': '2016-03-31 15:52:23.000000', 'correlation_id': None }, { 'project_id': 1235, 'project_code': '723724506329', 'vendor_id': valid_grass_account_vendor_id, 'subaccount_id': 577, 'project_name': 'Massenet: Werther', 'created_date_utc': '2016-03-31 15:52:23.000000', 'updated_date_utc': '2016-03-31 15:52:23.000000', 'correlation_id': None } ] @pytest.fixture def db_release_artist_names(): """Return release artist names used for test release.""" return ['Hawaiian People', 'James Cook'] @pytest.fixture def db_release_artists( db_release_id, db_release_artist_names, db_release_id_with_history): """Return test data injected into database for release_artists table.""" values = [ {'artist_name': 'Mike Jones', 'release_id': 1001, 'upc': 888831283041}, {'artist_name': 'Mike Jones', 'release_id': 1002, 'upc': 889845667810}, {'artist_name': 'Mike Jones', 'release_id': 1003, 'upc': 889845667872}, {'artist_name': 'John Mike', 'release_id': 1011, 'upc': 823623000680}, {'artist_name': 'John Mike', 'release_id': 1012, 'upc': 803680422151}, {'artist_name': 'Prince', 'release_id': db_release_id_with_history, 'upc': 111222333445}] for_release = [{ 'artist_name': artist_name, 'release_id': db_release_id, 'upc': 803680422151, } for artist_name in db_release_artist_names] return values + for_release @pytest.fixture def db_distribution_formats(): """Return test data injected into the distribution_format table.""" return [ {'distribution_format_id': 1, 'context_type': 'physical'}, {'distribution_format_id': 2, 'context_type': 'digital'}, {'distribution_format_id': 3, 'context_type': 'music video'} ] @pytest.fixture def db_release_subgenre(db_release_id, db_release_id_with_history): """Return test data injected into the release_subgenre table.""" return [{ 'id': 1, 'release_id': db_release_id, 'upc': 666677778888, 'subgenre_id': 56 }, { 'id': 2, 'release_id': db_release_id_with_history, 'upc': 111222333445, 'subgenre_id': 56 }] @pytest.fixture def db_tracks(db_releases): """Return test data injected in the track table.""" release = db_releases[1] return [{ 'id': 1, 'release_id': release['release_id'], 'track_name': 'First track', 'track_id': 1, 'upc': release['upc'], 'isrc': 'SOMEISRC123', 'cd': 1, 'third_party_publisher': 'Y', 'us_publishing_obligation': 'ControlledByYourLabel', }, { 'id': 2, 'release_id': release['release_id'], 'track_name': 'Second track', 'track_id': 2, 'upc': release['upc'], 'cd': 1 }] @pytest.fixture def db_physical_tracks(db_releases): """Return test data injected in the track table.""" return [{ 'track_physical_id': 1, 'track_id': 1, 'side': None, }, { 'track_physical_id': 2, 'track_id': 2, 'side': None }] @pytest.fixture def db_track_writers(db_releases): """Return test data injected in the track writers table.""" return [{ 'track_writer_id': 1, 'writer_name': 'George', 'track_id': 1, 'upc': 1, 'cd': 1, 'unique_track_id': 1, }, { 'track_writer_id': 2, 'writer_name': 'Sally', 'track_id': 1, 'upc': 1, 'cd': 1, 'unique_track_id': 1, }] @pytest.fixture def db_track_artists(db_tracks): """Return test data injected in the track table.""" return [{ 'id': i, 'track_id': track['id'], 'name': 'Artist {}'.format(track['id']), 'type': 'performer' } for i, track in enumerate(db_tracks)] @pytest.fixture def db_track_publishers(db_tracks): """Return test data injected in the track_publisher table.""" return [{ 'track_publisher_id': i, 'unique_track_id': track['id'], 'publisher_name': 'Publisher {}'.format(track['id']), 'upc': 22223333 } for i, track in enumerate(db_tracks)] @pytest.fixture def db_product_physical_packaging(): """Data for insert into product_physical_packaing table.""" return [ { 'name': 'Brilliant Case (Jewel Case size, Holds 2 CDs)', 'display_flag': 'Y' }, { 'name': 'Blister Pack', 'display_flag': 'Y' } ] @pytest.fixture def db_with_data( db_fixture, db_releases, db_projects, db_release_artists, db_distribution_formats, db_product_physical, db_release_subgenre, db_tracks, db_track_artists, db_track_publishers, db_physical_tracks, db_product_physical_packaging, db_product_physical_supply_chain_info, db_track_writers, db_product_physical_supply_chain_metadata, db_product_distribution): """Fixture that sets up database and injects test data.""" db.populate_table('project', db_projects) db.populate_table('releases', db_releases) db.populate_table('release_artist', db_release_artists) db.populate_table('distribution_format', db_distribution_formats) db.populate_table('product_physical', db_product_physical) db.populate_table('release_subgenre', db_release_subgenre) db.populate_table('track', db_tracks) db.populate_table('track_physical', db_physical_tracks) db.populate_table('track_writer', db_track_writers) db.populate_table('track_artist', db_track_artists) db.populate_table('track_publisher', db_track_publishers) db.populate_table( 'product_physical_packaging', db_product_physical_packaging) db.populate_table( 'product_physical_supply_chain_info', db_product_physical_supply_chain_info) db.populate_table( 'product_physical_supply_chain_metadata', db_product_physical_supply_chain_metadata) db.populate_table( 'product_distribution', db_product_distribution) @pytest.fixture def db_fixture(): """Setup tables in test database.""" db.create_project_table() db.create_release_approval_queue_table() db.create_product_physical_packaging_table() db.create_releases_table() db.create_release_status_table() db.create_product_physical_table() db.create_release_subgenre_table() db.create_release_artist_table() db.create_track_table() db.create_track_physical_table() db.create_track_writer_table() db.create_track_artist_table() db.create_distribution_format_table() db.create_track_publisher_table() db.create_product_physical_change_history() db.create_product_physical_supply_chain_info() db.create_product_physical_supply_chain_metadata() db.create_product_distribution() db.create_delivery_history_table() yield db.drop_product_distribution() db.drop_product_physical_supply_chain_metadata() db.drop_product_physical_supply_chain_info() db.drop_product_physical_change_history() db.drop_track_publisher_table() db.drop_distribution_format_table() db.drop_track_artist_table() db.drop_track_writer_table() db.drop_track_physical_table() db.drop_track_table() db.drop_release_artist_table() db.drop_release_subgenre_table() db.drop_product_physical_table() db.drop_release_status_table() db.drop_releases_table() db.drop_product_physical_packaging_table() db.drop_release_approval_queue_table() db.drop_project_table() db.drop_delivery_history_table() @pytest.fixture def delivery_db_fixture(): """Setup delivery tables in test database.""" db.create_all_delivery_tables() @pytest.fixture def context(): """Return flask app context, including a mock logger. Returns: AppContext: flask app context, including a mock logger. """ context = application.app.app_context() context.g.ows = flask_logger.Ows() context.g.ows.log = MockLogAdaptor() return context @pytest.fixture def mock_app(): """Mock app.""" with application.app.app_context() as context: context.g.ows = flask_logger.Ows() context.g.ows.log = MockLogAdaptor() context.g.request_context = MagicMock() yield context @pytest.fixture def delete_header_validation_schema(): """Return typical schema for DELETE headers. Returns: (dict): schema """ schema = { 'properties': { header.GRASS_ACCOUNT_TYPE: { 'required': True, 'example': 'vendor', 'description': 'vendor | subaccount', 'type': 'string', 'pattern': '(vendor)|(subaccount)'}, header.GRASS_ACCOUNT_ID: { 'required': True, 'type': 'string', 'pattern': r'^\d+$', 'description': 'vendor_id | subaccount_id'}, header.CORRELATION_ID: { 'required': False, 'type': 'string', 'description': 'UUID' } }, 'required': True, 'type': 'object', '$schema': 'http://json-schema.org/draft-03/schema' } return schema @pytest.fixture def valid_delete_header(valid_grass_account_vendor_id): """Return valid PUT request headers. Returns: (dict): header dict """ data = { header.CORRELATION_ID: '1234567890', header.GRASS_ACCOUNT_TYPE: 'vendor', header.GRASS_ACCOUNT_ID: str(valid_grass_account_vendor_id)} return data @pytest.fixture def valid_grass_account_vendor_id(): """Vendor id for testing.""" return 25257 @pytest.fixture def valid_oa_orchard_user_id(): """Orchard user id for testing.""" return 'oa:1234' @pytest.fixture def valid_alw_orchard_user_id(): """Orchard user id for testing.""" return 'alw:123' @pytest.fixture def fixture_response_created(): """Create a response.Response fixture for generic CREATED.""" return response.Response( message={'status': 'ok', 'lastrowid': 47}, status=success.CREATED_CODE) @pytest.fixture def fixture_response_ok(): """Create a response.Response fixture for generic ok.""" return response.Response( message={'status': 'ok'}, status=success.SUCCESS_CODE) @pytest.fixture def fixture_response_error(): """Create a response.Response fixture for generic error.""" return response.create_error_response( code='error_code', message='error_message') @pytest.fixture def fixture_persister_response_error(): """Create a response.Response fixture for generic error.""" return response.create_error_response( code=error.INTERNAL_ERROR, message='mysql error', status=500) @pytest.fixture def valid_physical_releases(): """List of physical releases.""" return [{ field.RELEASE_ID: 1, field.UPC: 111222333444, field.DISPLAY_UPC: '111222333444', field.MANUFACTURER_UPC: '555577778888', field.PRODUCT_NAME: 'The Answer to Everything', field.ARTIST_ID: 12, field.SUBACCOUNT_ID: 666, field.LABEL: 'Mexicali Blues Records', field.RELEASE_DATE: '2016-03-31', field.GENRE_ID: 12, 'new_release': 'Catalog', field.DISTRIBUTION_FORMAT_ID: 1, field.CLINE: '2016 Demo Records', field.SALE_START_DATE: '2016-03-31', field.PRODUCT_CODE: 'PHYS1', field.VERSION: 'Mexican Version', field.RELEASE_STATUS: 'orchard_processing', field.PROJECT_ID: 1234, field.SPECIAL_INSTRUCTIONS: 'Doubles as a plate for your burritos', field.DESCRIPTION: 'Pretty good.', field.NOT_FOR_DISTRIBUTION: 'N', field.VENDOR_CATALOG_NUMBER: 'PHYS1' }, { field.RELEASE_ID: 2, field.UPC: 111222333445, field.DISPLAY_UPC: '111222333445', field.MANUFACTURER_UPC: '555577778888', field.PRODUCT_NAME: 'The Answer to Everything', field.ARTIST_ID: 12, field.SUBACCOUNT_ID: 666, field.LABEL: 'Mexicali Blues Records', field.RELEASE_DATE: '2016-03-31', field.GENRE_ID: 12, 'new_release': 'Catalog', field.DISTRIBUTION_FORMAT_ID: 1, field.CLINE: '2016 Demo Records', field.SALE_START_DATE: '2016-03-31', field.PRODUCT_CODE: 'PHYS2', field.VERSION: 'Mexican Version', field.RELEASE_STATUS: 'orchard_processing', field.PROJECT_ID: 1234, field.SPECIAL_INSTRUCTIONS: 'Doubles as a plate for your burritos', field.DESCRIPTION: 'Pretty good.', field.NOT_FOR_DISTRIBUTION: 'N', field.VENDOR_CATALOG_NUMBER: 'PHYS2' }] @pytest.fixture def single_track_data(): """Data for a single track.""" return [ { 'track_number': 1, 'track_name': 'First track', 'performer': ['John Doe'], 'isrc': 'US123456789', 'disc': 1, 'length': '1:12:32', 'us_publishing_obligation': 'Composition', 'third_party_publisher': 'Y' } ] @pytest.fixture def single_track_minimal_data(): """Minimal data to successfully create a track.""" return [ { 'track_number': 1, 'track_name': 'First track', 'performer': ['John Doe'] } ] @pytest.fixture def minimal_product_for_tracks(): """Minimal product data for tracks creation.""" return { 'product_id': 42, 'upc': 666677778888 } @pytest.fixture def multiple_tracks_data(): """Data for multiple tracks.""" return [ { 'track_number': 1, 'track_name': 'First track', 'performer': ['John Doe'], 'isrc': 'US123456789', 'disc': 1, 'length': '1:12:32', 'us_publishing_obligation': 'Composition', 'third_party_publisher': 'Y' }, { 'track_number': 2, 'track_name': 'Second track', 'performer': ['John Doe'], 'isrc': 'US123456789X', 'disc': 1, 'length': '0:04:23', 'us_publishing_obligation': 'ControlledByYourLabel', 'third_party_publisher': 'Y' } ] @pytest.fixture def multiple_tracks_for_update(multiple_tracks_data): """Multiple tracks used for testing update.""" multiple_tracks_data.append({ 'track_number': 3, 'track_name': 'Third Track', 'performer': ['John Doe Brown'], 'isrc': 'US987654321X', 'disc': 1, 'length': '0:04:23', 'us_publishing_obligation': 'PublicDomain', 'third_party_publisher': 'N' }) return multiple_tracks_data def _map_isrc(track_with_index): """Map isrc value to lowercase.""" track_with_index[1].update( {'isrc': 'usa00000000' + str(track_with_index[0] + 1)}) return track_with_index[1] @pytest.fixture def tracks_with_lowercase_isrc(multiple_tracks_for_update): """Tracklist for testing lowercase isrc conversion.""" return list(map(_map_isrc, enumerate(multiple_tracks_for_update))) @pytest.fixture def valid_put_product_publishing_obligation_data(): """Return a dict representing JSON sent in PUT body. For PUT /product/{productId}/tracks/publishing-obligation. """ data = [ { 'track_id': 1, 'us_publishing_obligation': 'Composition', 'third_party_publisher': 'Y', 'publisher_names': ['Brown', 'White']}, { 'track_id': 2, 'us_publishing_obligation': 'Composition', 'third_party_publisher': 'N', 'publisher_names': []}, { 'track_id': 3, 'us_publishing_obligation': 'PublicDomain' } ] return data class OwsRequestMockBuilder: """Utility to build mocking setup for owsrequest.""" def __init__(self, mocker): """Initializer.""" self.specs = {method: [] for method in ['get', 'post', 'head', 'put']} self.mocker = mocker def mock_response( self, service, path, method='get', status=200, json=None): """Add a spec to the list of available methods.""" spec = {'service': service, 'path': path, 'status': status} if json is not None: spec['json'] = json self.specs[method].append(spec) self.apply() return self def add(self, spec_with_method): """Legacy implementation of mock_response.""" method = spec_with_method.pop('method') self.specs[method].append(spec_with_method) self.apply() return self def apply(self): """Set up actual mock with added specs.""" for method in ['get', 'post', 'head', 'put']: specs = self.specs[method] requests = test_utils.mock_ows_requests(specs) self.mocker.patch.object(request, method, requests) @pytest.fixture def ows_request_mocker(mocker): """Fixture for mocking requests.""" return OwsRequestMockBuilder(mocker) @pytest.fixture def distribution_format_response(): """Return a valid distribution format response.""" distribution_format_response = { 'distribution_format_id': 74, 'distribution_format_media_id': 3, 'distribution_format_media_format_id': 2, 'context_type': 'physical', 'display_flag': 'Y' } return distribution_format_response @pytest.fixture def supply_chain_default_response(): """Return a valid supply chain default response.""" supply_chain_defaults_response = { '738': [ { 'distribution_format_media_id': 7, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 1, 'is_returnable': 0, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 2, 'is_returnable': 0, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 3, 'is_returnable': 0, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 1, 'is_returnable': 1, 'return_disposition': 'Keep' } ], '739': [ { 'distribution_format_media_id': 7, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 1, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 2, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 3, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 1, 'is_returnable': 1, 'return_disposition': 'Keep' } ] } return supply_chain_defaults_response @pytest.fixture def valid_default_data(): """Return a dict for supply chain info.""" defaults_data = { 'product_id': 1, 'returnability': 'Y', 'return_disposition': 'Keep', 'store_id': 738, 'updated_date': '2017-12-06 11:49:26' } return defaults_data @pytest.fixture def db_product_physical_supply_chain_info(): """Return a dict for supply chain info.""" defaults_data = [{ 'product_id': 1, 'returnability': 'Y', 'return_disposition': 'Keep', 'store_id': 738, 'updated_date': '2017-12-06 11:49:26' }] return defaults_data @pytest.fixture def valid_get_supply_chain_info_response(): """Return a valid supply chain info response.""" data = [ { 'product_physical_supply_chain_id': 5, 'product_id': 2189079, 'returnability': 'N', 'return_disposition': 'Keep', 'store_id': 738 }, { 'product_physical_supply_chain_id': 6, 'product_id': 2189079, 'returnability': 'N', 'return_disposition': 'Keep', 'store_id': 739 } ] return data @pytest.fixture def existing_product_response(): """Return a valid existing product response.""" product_response = { 'project_id': 3809219, 'product_name': 'Deceiver Of The Gods', 'release_date': '2013-06-25', 'context_type': 'physical', 'distribution_format_id': 74, 'product_id': 2189079, 'deletions': 'N', 'status': 'in_content', 'product_type_id': 1, 'upc': 20000000041313, 'subaccount_id': 7505, 'vendor_id': 21966 } return product_response @pytest.fixture def supply_chain_response_after_insertion(): """Return a valid supply chain returnability response.""" supply_chain_response = [ { 'id': 6155, 'product_id': 2189079, 'returnability': 'N', 'return_disposition': 'Keep', 'store_id': '738', 'updated_date': '2018-03-08 12:24:44' }, { 'id': 6156, 'product_id': 2189079, 'returnability': 'Y', 'return_disposition': 'Keep', 'store_id': '739', 'updated_date': '2018-03-08 12:24:45' } ] return supply_chain_response @pytest.fixture def supply_chain_default_repsponse_with_message(): """Valid supply chain response storewise.""" supply_chain_defaults_response = { '738': [ { 'distribution_format_media_id': 7, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 1, 'is_returnable': 0, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 2, 'is_returnable': 0, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 3, 'is_returnable': 0, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 1, 'is_returnable': 1, 'return_disposition': 'Keep' } ], '739': [ { 'distribution_format_media_id': 7, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 1, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 2, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 3, 'is_returnable': 1, 'return_disposition': 'Keep' }, { 'distribution_format_media_id': 1, 'is_returnable': 1, 'return_disposition': 'Keep' } ] } return response.Response( message=supply_chain_defaults_response) @pytest.fixture def supply_chain_response_single_insertion(): """Return a valid single insertion response.""" supply_chain_response = { 'id': 6155, 'product_id': 2189079, 'returnability': 'N', 'return_disposition': 'Keep', 'store_id': '738', 'updated_date': '2018-03-08 12:24:44' } return supply_chain_response @pytest.fixture def supply_chain_response_insertion_array(): """Return a valid supply chain default response.""" supply_chain_response = [ { 'id': 6155, 'product_id': 2189079, 'returnability': 'N', 'return_disposition': 'Keep', 'store_id': '738', 'updated_date': '2018-03-08 12:24:44' }, { 'id': 6155, 'product_id': 2189079, 'returnability': 'N', 'return_disposition': 'Keep', 'store_id': '738', 'updated_date': '2018-03-08 12:24:44' } ] return supply_chain_response @pytest.fixture def supply_chain_defults_mock_response(): """Return response for suplly_chain_defaults records.""" return response.Response( message={ '738': [ {'return_disposition': 'Keep', 'is_returnable': 1, 'distribution_format_media_id': None}, {'return_disposition': 'Keep', 'is_returnable': 0, 'distribution_format_media_id': 1}, {'return_disposition': 'Keep', 'is_returnable': 0, 'distribution_format_media_id': 2}, {'return_disposition': 'Keep', 'is_returnable': 0, 'distribution_format_media_id': 3}, {'return_disposition': 'Keep', 'is_returnable': 1, 'distribution_format_media_id': 4} ] } ) @pytest.fixture def product_physical_supply_chain_info_record(): """Return product_physical_supply_chain_info record created.""" return response.Response( message={ 'id': 1, 'product_id': 123, 'returnability': 'N', 'return_disposition': 'Keep', 'store_id': '738', 'updated_date': '2018-04-12 23:49:35' } ) @pytest.fixture def product_physical_supply_chain_info_default_record(): """Return product_physical_supply_chain_info with default values.""" return response.Response( message={ 'id': 1, 'product_id': 123, 'returnability': 'Y', 'return_disposition': 'Keep', 'store_id': '738', 'updated_date': '2018-04-12 23:49:35' } ) @pytest.fixture def response_without_supply_chain_metadata(): """Return product_physical_supply_chain_metadata with default values.""" return {'project_id': 47, 'product_id': 1999, 'product_highlights': 'Test'} @pytest.fixture def expected_response_for_supply_chain_metadata(): """Return product_physical_supply_chain_metadata with default values.""" supply_chain_metadata_response = [ { 'id': 1, 'product_id': 1900609, 'store_id': 6, 'embargo_date': None, 'release_date': None, 'sale_start_date': None, 'initial_stock': None, 'is_deleted': '0', 'date_added': '2018-09-02 00:00:00', 'date_updated': '2018-09-02 00:00:00' } ] return supply_chain_metadata_response @pytest.fixture def expected_response_for_supply_chain_metadata_with_initial_stock(): """Return product_physical_supply_chain_metadata with default values.""" supply_chain_metadata_response = [ { 'id': 1, 'product_id': 1900609, 'store_id': 6, 'embargo_date': None, 'release_date': None, 'sale_start_date': None, 'initial_stock': 50, 'is_deleted': '0', 'date_added': '2018-09-02 00:00:00', 'date_updated': '2018-09-02 00:00:00' } ] return supply_chain_metadata_response @pytest.fixture def expected_response_for_metadata_with_supply_chain_info(): """Return product_physical_supply_chain_metadata with default values.""" supply_chain_metadata_response = [ { 'id': 1, 'product_id': 1900609, 'store_id': 738, 'embargo_date': None, 'release_date': None, 'sale_start_date': None, 'initial_stock': None, 'is_deleted': '0', 'date_added': '2018-09-02 00:00:00', 'date_updated': '2018-09-02 00:00:00', 'returnability': 'N', 'return_disposition': 'Keep' } ] return supply_chain_metadata_response @pytest.fixture def db_product_physical_supply_chain_metadata(): """Return a dict for supply chain metadata.""" defaults_data = [{ 'id': 1, 'product_id': 1900609, 'store_id': 6, 'embargo_date': '2018-09-02', 'release_date': '2018-09-02', 'sale_start_date': '2018-09-02', 'date_added': '2018-09-02 00:00:00', 'date_updated': '2018-09-02 00:00:00' }] return defaults_data @pytest.fixture def data_to_be_inserted(): """Return a dict for to be updated data.""" supply_chain_metadata = { 'metadata': [ { 'store_id': '6', 'sale_start_date': '2018-10-19', 'embargo_date': '2018-10-20' }, { 'store_id': '738', 'release_date': '2018-10-19', 'sale_start_date': '2018-10-20', 'embargo_date': '2018-10-21' } ] } return supply_chain_metadata @pytest.fixture def updated_data(): """Updated metadata response.""" updated_data = [ { 'date_updated': '2018-10-15 09:57:25', 'sale_start_date': '2018-10-19', 'embargo_date': '2018-10-20', 'store_id': '740', 'date_added': '2018-10-15 09:57:25', 'product_id': 1900609 }, { 'date_updated': '2018-10-15 09:57:25', 'release_date': '2018-10-19', 'sale_start_date': '2018-10-20', 'embargo_date': '2018-10-21', 'store_id': '738', 'date_added': '2018-10-15 09:57:25', 'product_id': 1900609 } ] return updated_data @pytest.fixture def data_returned_for_existing_metadata(): """Updated metadata response.""" updated_data = { 'date_updated': '2018-10-30 13:22:27', 'release_date': '2018-02-19', 'id': 39, 'store_id': '740', 'product_id': 1900609 } return updated_data @pytest.fixture def transactional_data(): """Transaction related data to be sent on update.""" date_updated = datetime.datetime(2018, 10, 30, 13, 22, 27) updated_data = { 'date_updated': date_updated, 'release_date': '2018-02-19' } return updated_data @pytest.fixture def data_returned_for_new_metadata(): """Updated metadata response.""" new_metadata = { 'date_updated': '2018-10-30 13:38:55', 'release_date': '2018-02-19', 'sale_start_date': '2018-02-20', 'embargo_date': '2018-02-21' } return new_metadata @pytest.fixture def created_metadata(): """Updated metadata response.""" created_data = { 'date_updated': '2018-10-30 13:38:55', 'release_date': '2018-02-19', 'sale_start_date': '2018-02-20', 'embargo_date': '2018-02-21' } return created_data @pytest.fixture def data_returned_after_create_mapping(): """Updated metadata response.""" mapped_data = { 'date_updated': '2018-10-30 13:22:27', 'release_date': '2018-02-19', 'id': 39, 'store_id': '740', 'product_id': 1900609, 'date_added': '2018-10-30 13:22:27', } return mapped_data @pytest.fixture def product_physical_supply_chain_record_for_insert(): """Return product_physical_supply_chain_info with default values.""" return [ { 'returnability': 'Y', 'return_disposition': 'Keep', 'store_id': '738' } ] @pytest.fixture def product_physical_supply_chain_record(): """Create a response.Response fixture for generic ok.""" return response.Response( message={ 'id': 1, 'product_id': 123, 'returnability': 'Y', 'return_disposition': 'Keep', 'store_id': '738', 'updated_date': '2018-04-12 23:49:35' }, status=200) @pytest.fixture def product_physical_supply_chain_update_record(): """Return product_physical_supply_chain_info record created.""" return { 'supplychain_info': [ { 'return_disposition': 'Scrap', 'returnability': 'N', 'store_id': '738' } ] } @pytest.fixture def not_found_status(): """Create a response.Response fixture for generic ok.""" return response.Response(status=404) @pytest.fixture def change_sale_start_date_fields(): """Return updated record in product_physical_change_history.""" sale_start_date_fields = { 'field_name': 'sale_start_date', 'old_sale_start_date': '2018-08-02', 'new_sale_start_date': '2018-08-02', 'store_id': 738 } return sale_start_date_fields @pytest.fixture def change_release_date_fields(): """Return updated record in product_physical_change_history.""" release_date_fields = { 'field_name': 'release_date', 'old_sale_start_date': '2018-08-02', 'new_sale_start_date': '2018-08-02', 'store_id': 738 } return release_date_fields @pytest.fixture def change_embargo_date_fields(): """Return updated record in product_physical_change_history.""" release_date_fields = { 'field_name': 'embargo_date', 'old_sale_start_date': '2018-08-02', 'new_sale_start_date': '2018-08-02', 'store_id': 738 } return release_date_fields @pytest.fixture def get_product_distribution_response(): """Return a valid product distribution response.""" return response.Response( message={'items': [{ 'product_distribution_id': 1, 'product_id': 1234, 'distribute_to': 'JP' }]}, status=200) @pytest.fixture def db_product_distribution(): """Return a dict for product distribution metadata.""" distribution_data = [{ 'product_distribution_id': 1, 'product_id': 42, 'distribute_to': 'JP', 'last_updated': '2018-10-30 13:22:27', 'updated_by': 562, 'user_type': 'oa' }] return distribution_data @pytest.fixture def product_distribution_data(): """Return a dict to save data in product_distribution. Returns: data (dict): Returns dict of product_distribution to save. """ return { 'product_id': 12345, 'distribute_to': 'AB', 'updated_by': 123, 'user_type': 'alw' } @pytest.fixture def order(): """Return a model payload of a single order. Returns: data (dict): returns a dict for a single order """ return { 'delivery_store_id': 1705, 'type': 'new', 'status': 'in_progress', 'created_at': '2023-03-03', 'created_by': 3423432, 'last_updated': '2023-03-03', 'output_file': 'new.csv', 'cmo_template_version': 1, 'products': [ { 'product_id': 23245, 'status': 'delivered' }, { 'product_id': 23246, 'status': 'failed' } ] } @pytest.fixture def orders(): """Return a model payload of orders. Returns: data (dict): returns dict of orders """ return { 'total': 234, 'items': [ { 'order_id': 1, 'delivery_store_id': 1705, 'type': 'new', 'status': 'in_progress', 'created_at': '2023-03-03', 'created_by': 3423432, 'last_updated': '2023-03-03', 'output_file': 'new.csv', 'cmo_template_version': 1, 'products': [ { 'product_id': 23245, 'status': 'delivered' }, { 'product_id': 23246, 'status': 'failed' } ] }, { 'order_id': 2, 'delivery_store_id': 1705, 'type': 'update', 'status': 'complete', 'created_at': '2023-03-03', 'created_by': 3423432, 'last_updated': '2023-03-03', 'output_file': 'update.csv', 'cmo_template_version': 3, 'products': [ { 'product_id': 23245, 'status': 'delivered' }, { 'product_id': 23246, 'status': 'failed' } ] }, { 'order_id': 3, 'delivery_store_id': 1705, 'type': 'takedown', 'status': 'cancelled', 'created_at': '2023-03-03', 'created_by': 3423432, 'last_updated': '2023-03-03', 'output_file': 'takedown.csv', 'cmo_template_version': 3, 'products': [ { 'product_id': 23245, 'status': 'delivered' }, { 'product_id': 23246, 'status': 'failed' } ] }, { 'order_id': 4, 'delivery_store_id': 1705, 'type': 'new', 'status': 'ready_for_delivery', 'created_at': '2023-03-03', 'created_by': 3423432, 'last_updated': '2023-03-03', 'output_file': 'ready.csv', 'cmo_template_version': 3, 'products': [ { 'product_id': 23245, 'status': 'delivered' }, { 'product_id': 23246, 'status': 'failed' } ] }, { 'order_id': 5, 'delivery_store_id': 1901, 'type': 'new', 'status': 'complete', 'created_at': '2024-01-08', 'created_by': 3423432, 'last_updated': '2025-03-06', 'output_file': 'metadata.zip', 'cmo_template_version': 3, 'products': [ { 'product_id': 23245, 'status': 'delivered' }, { 'product_id': 23246, 'status': 'failed' } ] }, ] } @pytest.fixture def complete_db_orders_as_object(): """Return test Order objects.""" return [ MagicMock( order_id=2, cmm_id=1705, delivery_type='update', status='complete', output_file='update.csv', cmo_template_version=3, created_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', created_date=datetime.datetime.fromisoformat('2023-03-03'), last_updated=datetime.datetime.fromisoformat('2023-03-03'), last_updated_by='cabc0fe4-0cbf-4ce1-b750-130f22bcd8d7', products=[], ), MagicMock( order_id=5, cmm_id=1901, delivery_type='new', status='complete', output_file='metadata.zip', cmo_template_version=3, created_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', created_date=datetime.datetime.fromisoformat('2024-01-08'), last_updated=datetime.datetime.fromisoformat('2025-03-06'), last_updated_by='bacc06e9-0cef-4ce3-l250-70py22bcd8d7', products=[], ), ] @pytest.fixture def delivery_orders(): """Return test data for orders table.""" return [ dict( order_id=1, cmm_id=1610, delivery_type='new', status='in_progress', created_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', created_date=datetime.datetime.fromisoformat('2023-01-04'), last_updated_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', last_updated=datetime.datetime.fromisoformat('2023-01-04'), ), dict( order_id=2, cmm_id=1705, delivery_type='new', status='cancelled', created_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', created_date=datetime.datetime.fromisoformat('2022-12-12'), last_updated_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', last_updated=datetime.datetime.fromisoformat('2022-12-12'), ), dict( order_id=3, cmm_id=1705, delivery_type='update', status='in_progress', created_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', created_date=datetime.datetime.fromisoformat('2023-01-01'), last_updated_by='cabc0fe4-0cbf-4ce1-b750-130f22bcd8d7', last_updated=datetime.datetime.fromisoformat('2023-01-11'), ), dict( order_id=4, cmm_id=1705, delivery_type='takedown', status='complete', output_file='metadata.zip', created_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', created_date=datetime.datetime.fromisoformat('2023-02-12'), last_updated_by='cabc0fe4-0cbf-4ce1-b750-130f22bcd8d7', last_updated=datetime.datetime.fromisoformat('2023-02-13'), ), dict( order_id=5, cmm_id=1901, delivery_type='new', status='complete', output_file='metadata.zip', created_by='47d9a1be-ad2e-48cf-a848-6aecfb2dd026', created_date=datetime.datetime.fromisoformat('2025-02-16'), last_updated_by='bacc06e9-0cef-4ce3-l250-70py22bcd8d7', last_updated=datetime.datetime.fromisoformat('2025-02-17'), ) ] @pytest.fixture def delivery_order_products(): """Return test data for OrderProduct table.""" return [ dict(order_id=1, order_product_id=1, product_id=1931104, ), dict(order_id=2, order_product_id=2, product_id=1917531, ), dict(order_id=2, order_product_id=3, product_id=1931137, ), dict(order_id=3, order_product_id=4, product_id=1930275, ), dict(order_id=4, order_product_id=5, product_id=1917531, ), dict(order_id=4, order_product_id=6, product_id=1931104, ), dict(order_id=4, order_product_id=7, product_id=1930285, ), dict(order_id=5, order_product_id=8, product_id=1930285, ), dict(order_id=5, order_product_id=9, product_id=1930288, ), dict(order_id=5, order_product_id=12, product_id=1930289, ) ] with db.db_session() as session: session.execute(text('PRAGMA foreign_keys = ON;'))