"""Config containing fixtures. TODO: move this to a fixture file. """ from datetime import date from decimal import Decimal import json from unittest.mock import Mock from MySQLdb import Connection from MySQLdb.cursors import Cursor import pytest from requests import Response @pytest.fixture def digital_revenue_data(): """Stubbing digital revenue data.""" return ( { 'amount': Decimal(10.0000000001), 'data_point': date(2015, 10, 21), 'transaction_type_id': 12}, { 'amount': Decimal(123.456), 'data_point': date(2015, 10, 21), 'transaction_type_id': 16}, { 'amount': Decimal(456.199), 'data_point': date(2015, 10, 22), 'transaction_type_id': 12}) @pytest.fixture def digital_revenue_buckets(digital_revenue_data): """Stubbing digital revenue bucket data.""" return { 'raw': digital_revenue_data, 'bucket': { 'transactional': { date(2015, 10, 21): Decimal(10.0000000001) + Decimal(123.456), date(2015, 10, 22): Decimal('456.199'), }, 'theatrical': { date(2016, 6, 24): Decimal('6707.66'), date(2016, 6, 25): Decimal('8234.14'), }}} @pytest.fixture def digital_transaction_types(): """Stubbing digital transaction types.""" return { 12: 'EST', 16: 'VOD'} @pytest.fixture def cms_api_token_response(cms_api_token): """Convenience requests.Response of a token. Returns: Response: response object with API token data. """ data = {'token_value': cms_api_token} resp = Response() resp._content = json.dumps(data).encode('utf-8') return resp @pytest.fixture def cms_api_token(): """Reference for mocked CMS API token. Returns: str: token value. """ return 'testing token' @pytest.fixture def windows(): """Sample windowing data copied from the sales platform's test API. Returns: dict: raw windowing data. """ return { 'est': { 'start-date': '2015-09-01T00:00:00.000Z', 'end-date': '2015-11-30T00:00:00.000Z' }, 'tvod': { 'start-date': '2015-09-01T00:00:00.000Z', 'end-date': '2015-11-30T00:00:00.000Z' }, 'airline': { 'start-date': '2015-09-01T00:00:00.000Z' }, 'educational': { 'start-date': '2015-11-30T00:00:00.000Z' }, 'fvod': { 'start-date': '2015-08-30T00:00:00.000Z' } } @pytest.fixture def windows_converted(): """Windowing raw data from API fixture after conversion. Returns: list: converted windowing data. """ return [ { 'label': 'EST', 'start_date': date(2015, 9, 1), 'end_date': date(2015, 11, 30) }, { 'label': 'TVOD', 'start_date': date(2015, 9, 1), 'end_date': date(2015, 11, 30) }, { 'label': 'Airline', 'start_date': date(2015, 9, 1) }, { 'label': 'Educational', 'start_date': date(2015, 11, 30) }, { 'label': 'FVOD', 'start_date': date(2015, 8, 30) } ] @pytest.fixture def window_model_raw(): """Windowing model data fixture. Returns: list: windowing model data. """ return [ { 'label': 'EST', 'start_date': date(2015, 9, 1), 'end_date': date(2015, 11, 30), 'status': 'window' }, { 'label': 'TVOD', 'start_date': date(2015, 9, 1), 'end_date': date(2015, 11, 30), 'status': 'window' }, { 'label': 'Airline', 'start_date': date(2015, 9, 1), 'status': 'window' }, { 'label': 'Educational', 'start_date': date(2015, 11, 30), 'status': 'window' }, { 'label': 'FVOD', 'start_date': date(2015, 8, 30), 'status': 'window' } ] @pytest.fixture def window_model_data(window_model_raw): """Window model return data fixture.""" return {'raw': window_model_raw} @pytest.fixture def term_licenses(): """Sample term license data copied form the sales platform's test API. Returns: list: raw term license data. """ return [ { 'contractStatus': 'Executed', 'currency': 'CAD', 'endDate': '2016-11-16T00:00:00.000Z', 'holdbackApplication': ['theatrical'], 'holdbackInMonths': 10, 'licenseRights': ['tvod'], 'paymentInterval': 'Other', 'startDate': '2016-11-16T00:00:00.000Z', 'storeId': '623', 'storeName': 'this thing', 'territories': ['WW'], 'upc': '855743002206', 'value': 1000, 'window': 1}, { 'contractStatus': 'Projected', 'currency': 'GBP', 'endDate': '2016-12-23T00:00:00.000Z', 'holdbackApplication': ['broadcast', 'est'], 'holdbackInMonths': 11, 'licenseRights': ['est'], 'paymentInterval': 'Quarterly', 'startDate': '2016-11-10T00:00:00.000Z', 'storeId': '634', 'storeName': 'that thing', 'territories': ['FR', 'GB', 'US'], 'upc': '855743002206', 'value': 400, 'window': 5}, { 'contractStatus': 'Projected', 'currency': 'GBP', 'endDate': '2016-01-01T00:00:00.000Z', 'holdbackApplication': ['broadcast', 'est'], 'holdbackInMonths': 11, 'licenseRights': ['est', 'broadcast'], 'paymentInterval': 'Quarterly', 'startDate': '2012-01-01T00:00:00.000Z', 'storeId': '634', 'storeName': 'over there', 'territories': [], 'upc': '855743002206', 'value': 640, 'window': 5}, { 'contractStatus': 'Executed', 'currency': 'USD', 'endDate': '2017-02-01T00:00:00.000Z', 'holdbackApplication': ['est', 'svod'], 'holdbackInMonths': 3, 'licenseRights': ['svod'], 'paymentInterval': 'Annually', 'startDate': '2017-01-01T00:00:00.000Z', 'storeId': '642', 'storeName': 'Netflix', 'territories': ['BR', 'DE'], 'upc': '855743002206', 'value': 2000, 'window': 6}] @pytest.fixture def term_licenses_converted(): """Term license data fixture after conversion. Returns: list: converted term license data. """ return [ { 'contractStatus': 'Executed', 'currency': 'CAD', 'endDate': date(2016, 11, 16), 'holdbackApplication': ['theatrical'], 'holdbackInMonths': 10, 'licenseRights': ['tvod'], 'paymentInterval': 'Other', 'payments_schedule': {date(2016, 11, 16): Decimal(1000)}, 'startDate': date(2016, 11, 16), 'storeId': '623', 'storeName': 'this thing', 'territories': ['WW'], 'upc': '855743002206', 'value': 1000, 'window': 1}, { 'contractStatus': 'Projected', 'currency': 'GBP', 'endDate': date(2016, 12, 23), 'holdbackApplication': ['broadcast', 'est'], 'holdbackInMonths': 11, 'licenseRights': ['est'], 'paymentInterval': 'Quarterly', 'payments_schedule': {date(2016, 11, 10): Decimal(400)}, 'startDate': date(2016, 11, 10), 'storeId': '634', 'storeName': 'that thing', 'territories': ['FR', 'GB', 'US'], 'upc': '855743002206', 'value': 400.0, 'window': 5}, { 'contractStatus': 'Projected', 'currency': 'GBP', 'endDate': date(2016, 1, 1), 'holdbackApplication': ['broadcast', 'est'], 'holdbackInMonths': 11, 'licenseRights': ['est', 'broadcast'], 'paymentInterval': 'Quarterly', 'payments_schedule': { date(2012, 1, 1): Decimal(40), date(2012, 4, 1): Decimal(40), date(2012, 7, 1): Decimal(40), date(2012, 10, 1): Decimal(40), date(2013, 1, 1): Decimal(40), date(2013, 4, 1): Decimal(40), date(2013, 7, 1): Decimal(40), date(2013, 10, 1): Decimal(40), date(2014, 1, 1): Decimal(40), date(2014, 4, 1): Decimal(40), date(2014, 7, 1): Decimal(40), date(2014, 10, 1): Decimal(40), date(2015, 1, 1): Decimal(40), date(2015, 4, 1): Decimal(40), date(2015, 7, 1): Decimal(40), date(2015, 10, 1): Decimal(40), }, 'startDate': date(2012, 1, 1), 'storeId': '634', 'storeName': 'over there', 'territories': [], 'upc': '855743002206', 'value': 640, 'window': 5}, { 'contractStatus': 'Executed', 'currency': 'USD', 'endDate': date(2017, 2, 1), 'holdbackApplication': ['est', 'svod'], 'holdbackInMonths': 3, 'licenseRights': ['svod'], 'paymentInterval': 'Annually', 'payments_schedule': {date(2017, 1, 1): Decimal(2000)}, 'startDate': date(2017, 1, 1), 'storeId': '642', 'storeName': 'Netflix', 'territories': ['BR', 'DE'], 'upc': '855743002206', 'value': 2000, 'window': 6}] @pytest.fixture def term_licenses_diff_status(term_licenses_converted): """Term license data fixture after conversion with diff status. Returns: list: converted term license data. """ new_status = { 'contractStatus': 'diffStatus', 'currency': 'USD', 'endDate': date(2015, 10, 1), 'holdbackApplication': ['est', 'svod'], 'holdbackInMonths': 3, 'paymentInterval': 'Annually', 'payments_schedule': {date(2017, 1, 1): Decimal(2)}, 'startDate': date(2015, 1, 1), 'storeId': '642', 'storeName': 'foobar', 'territories': ['BR', 'DE'], 'upc': '855743002206', 'value': 2000, 'window': 6} data = term_licenses_converted data.append(new_status) return data @pytest.fixture def term_licenses_closed(): """Term license data fixture for closed status. Returns: list: closed term license data. """ closed_tl = [{ 'contractStatus': 'Closed', 'currency': 'USD', 'endDate': date(2018, 10, 1), 'holdbackApplication': ['est', 'svod'], 'holdbackInMonths': 3, 'paymentInterval': 'Annually', 'payments_schedule': {date(2017, 1, 1): Decimal(200)}, 'startDate': date(2018, 1, 1), 'storeId': '642', 'storeName': 'foobar', 'territories': ['BR', 'DE'], 'upc': '855743002206', 'value': 10050, 'window': 6}] return closed_tl @pytest.fixture def term_license_windows_data(): """Processed term license windows data reflecting all test fixtures.""" return [ { 'status': 'projected', 'start_date': date(2012, 1, 1), 'end_date': date(2016, 1, 1), 'label': 'Term License 1'}, { 'status': 'projected', 'start_date': date(2016, 11, 10), 'end_date': date(2016, 12, 23), 'label': 'Term License 2'}, { 'status': 'executed', 'start_date': date(2016, 11, 16), 'end_date': date(2016, 11, 16), 'label': 'Term License 3'}, { 'status': 'executed', 'start_date': date(2017, 1, 1), 'end_date': date(2017, 2, 1), 'label': 'Term License 4'}] @pytest.fixture def term_licenses_converted_sorted(term_licenses_converted): """Term licenses converted fixture sorted by startDate. Returns: list: sorted term_licenses_converted fixture data. """ return sorted(term_licenses_converted, key=lambda x: x['startDate']) @pytest.fixture def term_licenses_converted_sorted_without_projected( term_licenses_converted_sorted): """Term licenses converted sorted fixture and w/o Projected status. Returns: list: filtered term_licenses_converted_sorted fixture data. """ return list(filter( lambda x: x['contractStatus'] != 'Projected', term_licenses_converted_sorted)) @pytest.fixture def term_license_and_window_model_sorted( term_license_windows_data, window_model_raw): """Term license and window fixtures sorted by start_date. Returns: list: sorted term license and window fixtures data. """ unsorted_windows = term_license_windows_data + window_model_raw return sorted(unsorted_windows, key=lambda x: x['start_date']) @pytest.fixture def term_licenses_grouped_breakdown(): """Term licenses summed in groups depending on licenseRights field. Returns: dict: breakdown table of term licenses. """ return { 'actuals': { 'non_theatrical_other': Decimal(1000), 'term_license_svod_netflix': Decimal(2000)}, 'projected': { 'non_theatrical_other': Decimal(2040), 'term_license_svod_netflix': Decimal(2000)}} @pytest.fixture def revenue_all_time_raw(): """The expanded version of revenue_all_time(). TODO: when projections buckets gets tested, it needs to be in this NOTE: when using this fixture, be sure to patch the following: constants.SERIES_BUCKETS = { 'odds': {1, 3}, 'evens': {2, 4}} """ # date, revenue evens, revenue odds, projection evens, projection odds data = ( ('2016-01-01', 0, 1, 1, 2), ('2016-01-02', 0, 5, 3, 4), ('2016-01-03', 2, 5, 5, 6), ('2016-01-04', 10, 5, 5, 6), ('2016-01-05', 10, 8, 5, 6), ('2016-01-06', 10, 20, 6, 7), ('2016-01-07', 14, 20, 7, 8), ('2016-01-08', 30, 20, 8, 9), ) output = [] for item in data: out_item = {} out_item['date'] = item[0] out_item['projection'] = { 'evens': Decimal(item[3]), 'odds': Decimal(item[4])} out_item['projection']['total'] = Decimal( sum(out_item['projection'].values())) out_item['revenue'] = { 'evens': Decimal(item[1]), 'odds': Decimal(item[2])} out_item['revenue']['total'] = Decimal( sum(out_item['revenue'].values())) output.append(out_item) return output @pytest.fixture def revenue_all_time_buckets(revenue_all_time): """Stubbing all-time buckets.""" return { 'raw': revenue_all_time, 'bucket': { 'odds': { date(2016, 1, 1): Decimal('1'), date(2016, 1, 2): Decimal('4'), date(2016, 1, 5): Decimal('3'), date(2016, 1, 6): Decimal('12')}, 'evens': { date(2016, 1, 3): Decimal('2'), date(2016, 1, 4): Decimal('8'), date(2016, 1, 7): Decimal('4'), date(2016, 1, 8): Decimal('16')}}} @pytest.fixture def revenue_all_time(): """Revenue for multiple transaction types. This represents data coming from the db with a dict rows. """ db_keys = ('transaction_type_id', 'amount', 'data_point') db_rows = ( (1, 1, date(2016, 1, 1)), (1, 2, date(2016, 1, 2)), (1, 2, date(2016, 1, 2)), (2, 2, date(2016, 1, 3)), (2, 4, date(2016, 1, 4)), (2, 4, date(2016, 1, 4)), (3, 3, date(2016, 1, 5)), (3, 6, date(2016, 1, 6)), (3, 6, date(2016, 1, 6)), (4, 4, date(2016, 1, 7)), (4, 8, date(2016, 1, 8)), (4, 8, date(2016, 1, 8)), ) db_results = [dict(zip(db_keys, db_row)) for db_row in db_rows] return db_results @pytest.fixture def projections_all_time_buckets(projections_all_time): """Return projections all time dailified data organized to buckets.""" return { 'raw': projections_all_time, 'series': { 'odds': { date(2016, 1, 1): Decimal('2.00'), date(2016, 1, 2): Decimal('3.00'), date(2016, 1, 3): Decimal('3.00'), date(2016, 1, 4): Decimal('3.00'), date(2016, 1, 5): Decimal('3.00'), date(2016, 1, 6): Decimal('3.00'), date(2016, 1, 7): Decimal('3.00'), date(2016, 1, 8): Decimal('3.50'), date(2016, 1, 9): Decimal('2.50'), date(2016, 1, 10): Decimal('2.50'), date(2016, 1, 11): Decimal('2.50'), date(2016, 1, 12): Decimal('2.50'), date(2016, 1, 13): Decimal('2.50'), date(2016, 1, 14): Decimal('2.50')}, 'evens': { date(2016, 1, 3): Decimal('8'), date(2016, 1, 4): Decimal('8'), date(2016, 1, 5): Decimal('8'), date(2016, 1, 6): Decimal('8'), date(2016, 1, 7): Decimal('8'), date(2016, 1, 8): Decimal('8'), date(2016, 1, 9): Decimal('8')}}} @pytest.fixture def original_projections_all_time_buckets(projections_all_time): """Return original_projections all time dailified data in buckets.""" return { 'raw': projections_all_time, 'series': { 'one': { date(2016, 1, 1): Decimal('2.00'), date(2016, 1, 2): Decimal('2.00'), date(2016, 1, 3): Decimal('2.00'), date(2016, 1, 4): Decimal('2.00'), date(2016, 1, 5): Decimal('2.00'), date(2016, 1, 6): Decimal('2.00'), date(2016, 1, 7): Decimal('2.00'), date(2016, 1, 8): Decimal('2.50'), date(2016, 1, 9): Decimal('2.50'), date(2016, 1, 10): Decimal('2.50'), date(2016, 1, 11): Decimal('2.50'), date(2016, 1, 12): Decimal('2.50'), date(2016, 1, 13): Decimal('2.50'), date(2016, 1, 14): Decimal('2.50')}, 'two': { date(2016, 1, 3): Decimal('3.00'), date(2016, 1, 4): Decimal('3.00'), date(2016, 1, 5): Decimal('3.00'), date(2016, 1, 6): Decimal('3.00'), date(2016, 1, 7): Decimal('3.00'), date(2016, 1, 8): Decimal('3.00'), date(2016, 1, 9): Decimal('3.00') }, 'three': { date(2016, 1, 2): Decimal('1.00'), date(2016, 1, 3): Decimal('1.00'), date(2016, 1, 4): Decimal('1.00'), date(2016, 1, 5): Decimal('1.00'), date(2016, 1, 6): Decimal('1.00'), date(2016, 1, 7): Decimal('1.00'), date(2016, 1, 8): Decimal('1.00'), }, 'four': { date(2016, 1, 3): Decimal('5.00'), date(2016, 1, 4): Decimal('5.00'), date(2016, 1, 5): Decimal('5.00'), date(2016, 1, 6): Decimal('5.00'), date(2016, 1, 7): Decimal('5.00'), date(2016, 1, 8): Decimal('5.00'), date(2016, 1, 9): Decimal('5.00')}}} @pytest.fixture def projections_all_time(): """Return projections for multiple transaction types. This represents data coming from the db with a dict rows. It can also be used as raw data for original projections, as the transaction type IDs are mapped differently. """ db_keys = ('transaction_type_id', 'amount', 'data_point') db_rows = ( (1, 14, date(2016, 1, 1)), (1, 17.5, date(2016, 1, 8)), (2, 21, date(2016, 1, 3)), (3, 7, date(2016, 1, 2)), (4, 35, date(2016, 1, 3)), ) db_results = [dict(zip(db_keys, db_row)) for db_row in db_rows] return db_results @pytest.fixture def manual_adjustments_raw(): """Return expenses for multiple categories.""" value = {'manual_adjustments': [ { 'amount': '-100', 'apply_to_period': {'month': 1, 'year': 2017}, 'release_manual_adjustments': [ {'amount': -50, 'category': {'category': 'this'}}, {'amount': -50, 'category': {'category': 'that'}}, ]}, { 'amount': '-200', 'apply_to_period': {'month': 2, 'year': 2017}, 'release_manual_adjustments': [ {'amount': -50, 'category': {'category': 'this'}}, {'amount': -50, 'category': {'category': 'that'}}, {'amount': -50, 'category': {'category': 'third'}}, {'amount': -50, 'category': {'category': 'fourth'}}, ]}, { 'amount': '-300', 'apply_to_period': {'month': 3, 'year': 2017}, 'release_manual_adjustments': [ {'amount': -220, 'category': {'category': 'this'}}, {'amount': -80, 'category': {'category': 'that'}}, ]}, { 'amount': '-400', 'apply_to_period': {'month': 4, 'year': 2017}, 'release_manual_adjustments': [ {'amount': -150, 'category': {'category': 'this'}}, {'amount': -50, 'category': {'category': 'that'}}, {'amount': -50, 'category': {'category': 'foo'}}, {'amount': -150, 'category': {'category': 'bar'}}]}]} return value @pytest.fixture def manual_adjustment_rma(): """Release level data from manual_adjustment_rows fixture. category: {date: Decimal} of release manual adjustments. """ value = { 'foo': { date(2017, 4, 1): Decimal(50), }, 'bar': { date(2017, 4, 1): Decimal(150), }, 'this': { date(2017, 1, 1): Decimal(50), date(2017, 2, 1): Decimal(50), date(2017, 3, 1): Decimal(220), date(2017, 4, 1): Decimal(150), }, 'that': { date(2017, 1, 1): Decimal(50), date(2017, 2, 1): Decimal(50), date(2017, 3, 1): Decimal(80), date(2017, 4, 1): Decimal(50), }, 'third': { date(2017, 2, 1): Decimal(50), }, 'fourth': { date(2017, 2, 1): Decimal(50), } } return value def patch_transaction_types(): """Patch.dict override spec. This is the unit test TX id mapping.""" return { 1: 'foo', 2: 'bar', 3: 'baz'} @pytest.fixture def cable_revenue_raw(): """Return raw revenue data for cable.""" return ( { 'transaction_type_id': 42, 'amount': Decimal('111'), 'data_point': date(2015, 10, 20) }, { 'transaction_type_id': 43, 'amount': Decimal('222'), 'data_point': date(2015, 10, 21) }, { 'transaction_type_id': 42, 'amount': Decimal('333'), 'data_point': date(2015, 10, 21) }, { 'transaction_type_id': 42, 'amount': Decimal('444'), 'data_point': date(2015, 10, 22) }) @pytest.fixture def cable_revenue_buckets(cable_revenue_raw): """Return revenue buckets for cable.""" return { 'raw': cable_revenue_raw, 'bucket': { 'transactional': { date(2015, 10, 20): Decimal('111'), date(2015, 10, 21): Decimal('222') + Decimal('333'), date(2015, 10, 22): Decimal('444')}}} @pytest.fixture def cable_analytics_raw(): """Return raw revenue data for cable.""" return ( { 'transaction_type_id': 42, 'amount': Decimal('111'), 'data_point': date(2015, 10, 20) }, { 'transaction_type_id': 43, 'amount': Decimal('222'), 'data_point': date(2015, 10, 21) }, { 'transaction_type_id': 42, 'amount': Decimal('333'), 'data_point': date(2015, 10, 21) }, { 'transaction_type_id': 42, 'amount': Decimal('444'), 'data_point': date(2015, 10, 22) }) @pytest.fixture def cable_analytics_buckets(cable_non_analytics_raw): """Return revenue buckets for cable.""" return { 'raw': cable_non_analytics_raw, 'bucket': { 'transactional': { date(2015, 10, 20): Decimal('111'), date(2015, 10, 21): Decimal('222') + Decimal('333'), date(2015, 10, 22): Decimal('444')}}} @pytest.fixture def cable_non_analytics_raw(): """Return raw revenue data for cable.""" return ( { 'transaction_type_id': 42, 'amount': Decimal('277.32'), 'data_point': date(2015, 10, 21) }, { 'transaction_type_id': 43, 'amount': Decimal('223.57'), 'data_point': date(2015, 10, 22) }, { 'transaction_type_id': 42, 'amount': Decimal('277.32'), 'data_point': date(2015, 10, 22) }, { 'transaction_type_id': 42, 'amount': Decimal('100.50'), 'data_point': date(2015, 10, 23) }) @pytest.fixture def cable_non_analytics_buckets(cable_non_analytics_raw): """Return revenue buckets for cable.""" return { 'raw': cable_non_analytics_raw, 'bucket': { 'transactional': { date(2015, 10, 21): Decimal('277.32'), date(2015, 10, 22): Decimal('223.57') + Decimal('277.32'), date(2015, 10, 23): Decimal('100.50')}}} @pytest.fixture def database_context(): """Convenient mock for testing database context manager logic. Attach the returned value to the patched namespace that contains the api.utils.database object. The yielded cursor and connection objects are mocked and available in the _cursor and _connection attributes. Example: api/models/revenue.py --------------------- def example(): with aurora.context() as (cur, conn): cur.execute('select 1') tests/api/models/test_revenue.py -------------------------------- @patch('api.models.revenue.aurora') def test_example(mock_aurora, database_context): mock_aurora.context = database_context revenue.example() database_context._cursor.execute.assert_called_once_with( 'select 1') Returns: Mock: mocked context manager with cursor and connection mocks. """ cursor = Mock(spec=Cursor) connection = Mock(spec=Connection) context = Mock() context.__enter__ = Mock(return_value=(cursor, connection)) context.__exit__ = Mock() callable_object = Mock() callable_object.return_value = context callable_object._cursor = cursor callable_object._connection = connection return callable_object