"""Unit tests for the analytics_digest utils module.""" import json from unittest.mock import call, MagicMock, patch from oto import response from activity_detector import base_config from activity_detector.flows.analytics_digest import config from activity_detector.flows.analytics_digest import utils from activity_detector.utils import ows_notifications def test_generate_digest(monkeypatch, mock_snowflake): """Test generating the digest.""" monkeypatch.setattr( utils, 'fetch_accounts', MagicMock(return_value=[(1234,), (5678,)])) monkeypatch.setattr( utils, 'fetch_account_name', MagicMock(return_value='ACCOUNT NAME')) monkeypatch.setattr( utils, 'get_top_releases', MagicMock(return_value='TOP RELEASES')) monkeypatch.setattr( utils, 'get_top_tracks', MagicMock(return_value='TOP TRACKS')) monkeypatch.setattr( utils, 'get_top_physical_shipments', MagicMock(return_value='TOP PHYSICAL')) monkeypatch.setattr( utils, 'create_notification', MagicMock(return_value=None)) start_date = '2017-12-22' end_date = '2017-12-29' last_dates_per_store = [ { 'store_name': 'Spotify', 'last_date': '2017-12-29' }, { 'store_name': 'Itunes', 'last_date': '2017-12-29' } ] expected_calls = [ call('label', { 1234: { 'actor': 'The Orchard Activity Detector', 'verb': 'Generated', 'object': 'Digest', 'start_date': start_date, 'end_date': end_date, 'account_name': 'ACCOUNT NAME', 'top_releases': 'TOP RELEASES', 'top_new_releases': 'TOP RELEASES', 'top_tracks': 'TOP TRACKS', 'top_physical_shipments': 'TOP PHYSICAL' } }), call('label', { 5678: { 'actor': 'The Orchard Activity Detector', 'verb': 'Generated', 'object': 'Digest', 'start_date': start_date, 'end_date': end_date, 'account_name': 'ACCOUNT NAME', 'top_releases': 'TOP RELEASES', 'top_new_releases': 'TOP RELEASES', 'top_tracks': 'TOP TRACKS', 'top_physical_shipments': 'TOP PHYSICAL' } }), ] account_type = base_config.ACCOUNT_TYPE_LABEL utils.generate_digest( account_type, start_date, end_date, None, last_dates_per_store) assert utils.create_notification.call_count == 2 utils.create_notification.assert_has_calls(expected_calls, any_order=True) def test_fetch_account_name(mock_snowflake): """Test fetching the account name.""" with patch('activity_detector.flows.analytics_digest.utils.sql_loader', autospec=True) as mock_loader: mock_loader.load_query.return_value = 'SQL {env} {account_id}' mock_snowflake.fetchone.return_value = ('LABEL NAME',) result = utils.fetch_account_name(mock_snowflake.session, 123) mock_loader.load_query.assert_called_with('fetch_account_name') mock_snowflake.session.execute.assert_called_with('SQL test 123') assert result == 'LABEL NAME' def test_fetch_accounts(mock_snowflake): """Test fetching the accounts.""" with patch('activity_detector.flows.analytics_digest.utils.sql_loader', autospec=True) as mock_loader: mock_loader.load_query.return_value = 'SQL' mock_snowflake.fetchall.return_value = 'FETCH ALL' account_type = base_config.ACCOUNT_TYPE_LABEL result = utils.fetch_accounts( account_type, mock_snowflake.session, None) mock_loader.load_query.assert_called_with('fetch_labels') mock_snowflake.session.execute.assert_called_with('SQL') assert result == 'FETCH ALL' def test_fetch_accounts_with_account_ids(mock_snowflake): """Test fetching the accounts from context.""" account_type = base_config.ACCOUNT_TYPE_LABEL account_ids = '123,456' result = utils.fetch_accounts( account_type, mock_snowflake.session, account_ids) assert result == [(123,), (456,)] def test_get_top_releases(monkeypatch): """Test getting the top releases.""" monkeypatch.setattr( utils, 'get_top_releases_dict', MagicMock(return_value='get_top_releases_dict')) monkeypatch.setattr( utils, 'map_top_items_to_list', MagicMock(return_value='map_top_items_to_list')) monkeypatch.setattr( utils, 'sort_top_items_streams', MagicMock(return_value=[])) account_type = base_config.ACCOUNT_TYPE_LABEL account_id = 123 session = 'SESSION' result = utils.get_top_releases(account_type, account_id, session) utils.get_top_releases_dict.assert_called_with( account_type, account_id, session, False) utils.map_top_items_to_list.assert_called_with( 'get_top_releases_dict') utils.sort_top_items_streams.assert_called_with( 'map_top_items_to_list') assert result == [] def test_get_top_releases_dict(monkeypatch, mock_snowflake): """Test getting the top releases dict.""" top_releases_mock = [ ( 123, 'release_123', 'Full Length', 'artist_123', 286, # Spotify 5000, 10000, 2.3, 10.5 ), ( 123, 'release_123', 'Full Length', 'artist_123', 1, # Itunes 2000, 10000, 2.3, 11.5 ), ( 123, 'release_123', 'Full Length', 'artist_123', 348, # Deezer 2000, 10000, 2.3, 12.5 ), ( 123, 'release_123', 'Full Length', 'artist_123', 111, # Other 500, 10000, 2.3, None ), ( 123, 'release_123', 'Full Length', 'artist_123', 222, # Other 500, 10000, 2.3, None ), ( 456, 'release_456', 'Full Length', 'artist_456', 286, # Spotify 500, 1000, None, 10.5 ), ( 456, 'release_456', 'Full Length', 'artist_456', 1, # Itunes 500, 1000, None, 11.5 ), ( 789, 'release_789', 'Full Length', 'artist_789', 348, # Deezer 80, 100, None, 12.5 ), ( 789, 'release_789', 'Full Length', 'artist_789', 111, # Other 20, 1000, None, None ) ] monkeypatch.setattr( utils, 'fetch_top_items', MagicMock(return_value=top_releases_mock)) account_type = base_config.ACCOUNT_TYPE_LABEL account_id = 123 session = mock_snowflake.session is_new_releases = False result = utils.get_top_releases_dict( account_type, account_id, session, is_new_releases) utils.fetch_top_items.assert_called_with( 'release', account_type, account_id, session, is_new_releases) assert result == { 123: { 'release_id': 123, 'release_name': 'release_123', 'release_format': 'Full Length', 'artist_name': 'artist_123', 'total_streams': 10000, 'growth_percentage': 2, 'store_percentages': { 'spotify': { 'percentage': '50%', 'streams_per_store': 5000, 'growth_percentage_per_store': 10 }, 'itunes': { 'percentage': '20%', 'streams_per_store': 2000, 'growth_percentage_per_store': 11 }, 'deezer': { 'percentage': '20%', 'streams_per_store': 2000, 'growth_percentage_per_store': 12 }, 'other': 1000 } }, 456: { 'release_id': 456, 'release_name': 'release_456', 'release_format': 'Full Length', 'artist_name': 'artist_456', 'total_streams': 1000, 'growth_percentage': None, 'store_percentages': { 'spotify': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 10 }, 'itunes': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 } } }, 789: { 'release_id': 789, 'release_name': 'release_789', 'release_format': 'Full Length', 'artist_name': 'artist_789', 'total_streams': 100, 'growth_percentage': None, 'store_percentages': { 'deezer': { 'percentage': '80%', 'streams_per_store': 80, 'growth_percentage_per_store': 12 }, 'other': 20 } } } def test_get_top_tracks_dict(monkeypatch, mock_snowflake): """Test getting the top tracks dict.""" top_tracks_mock = [ ( 'ABC123', 'track_123', json.dumps(['artist_123', 'artist_456']), 286, # Spotify 5000, 10000, 2.3, 10.5 ), ( 'ABC123', 'track_123', json.dumps(['artist_123']), 1, # Itunes 2000, 10000, 2.3, 11.5 ), ( 'ABC123', 'track_123', json.dumps(['artist_123']), 348, # Deezer 2000, 10000, 2.3, 12.5 ), ( 'ABC123', 'track_123', json.dumps(['artist_123']), 111, # Other 500, 10000, 2.3, None ), ( 'ABC123', 'track_123', json.dumps(['artist_123']), 222, # Other 500, 10000, 2.3, None ), ( 'DEF456', 'track_456', json.dumps(['artist_456']), 286, # Spotify 500, 1000, 2.4, 10.5 ), ( 'DEF456', 'track_456', json.dumps(['artist_456']), 1, # Itunes 500, 1000, 2.4, 11.5 ), ( 'GHI789', 'track_789', json.dumps(['artist_789']), 348, # Deezer 80, 100, 2.4, 12.5 ), ( 'GHI789', 'track_789', json.dumps(['artist_789']), 111, # Other 20, 1000, 2.4, None ) ] monkeypatch.setattr( utils, 'fetch_top_items', MagicMock(return_value=top_tracks_mock)) account_type = base_config.ACCOUNT_TYPE_LABEL account_id = 123 session = mock_snowflake.session result = utils.get_top_tracks_dict(account_type, account_id, session) utils.fetch_top_items.assert_called_with( 'track', account_type, account_id, session) assert result == { 'ABC123': { 'isrc': 'ABC123', 'track_name': 'track_123', 'artist_names': ['artist_123', 'artist_456'], 'total_streams': 10000, 'growth_percentage': 2, 'store_percentages': { 'spotify': { 'percentage': '50%', 'streams_per_store': 5000, 'growth_percentage_per_store': 10 }, 'itunes': { 'percentage': '20%', 'streams_per_store': 2000, 'growth_percentage_per_store': 12 }, 'deezer': { 'percentage': '20%', 'streams_per_store': 2000, 'growth_percentage_per_store': 12 }, 'other': 1000 } }, 'DEF456': { 'isrc': 'DEF456', 'track_name': 'track_456', 'artist_names': ['artist_456'], 'total_streams': 1000, 'growth_percentage': 2, 'store_percentages': { 'spotify': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 10 }, 'itunes': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 12 } } }, 'GHI789': { 'isrc': 'GHI789', 'track_name': 'track_789', 'artist_names': ['artist_789'], 'total_streams': 100, 'growth_percentage': 2, 'store_percentages': { 'deezer': { 'percentage': '80%', 'streams_per_store': 80, 'growth_percentage_per_store': 12 }, 'other': 20 } } } def test_get_top_physical_shipments_dict(monkeypatch, mock_snowflake): """Test getting the top physical shipments dict.""" top_physical_mock = [ ( 123, 'product_123', 'CD', ['artist_123'], 123, 10 ), ( 456, 'product_456', 'CD', ['artist_456'], 456, 20 ) ] monkeypatch.setattr( utils, 'fetch_top_items', MagicMock(return_value=top_physical_mock)) account_type = base_config.ACCOUNT_TYPE_LABEL account_id = 123 session = mock_snowflake.session start_date = '2018-07-13' end_date = '2018-07-20' result = utils.get_top_physical_shipments_dict( account_type, account_id, session, start_date, end_date) utils.fetch_top_items.assert_called_with( 'physical_shipment', account_type, account_id, session, False, start_date, end_date) assert result == { 123: { 'raas_no': 123, 'product_name': 'product_123', 'product_type': 'CD', 'artist_name': ['artist_123'], 'total_transactions': 10 }, 456: { 'raas_no': 456, 'product_name': 'product_456', 'product_type': 'CD', 'artist_name': ['artist_456'], 'total_transactions': 20 } } def test_fetch_top_items(mock_snowflake): """Test fetching the top items.""" with patch('activity_detector.flows.analytics_digest.utils.sql_loader', autospec=True) as mock_loader: mock_loader.load_query.return_value = 'SQL' mock_snowflake.fetchall.return_value = 'FETCH ALL' item_type = 'release' account_type = base_config.ACCOUNT_TYPE_LABEL account_id = 123 result = utils.fetch_top_items( item_type, account_type, account_id, mock_snowflake.session) mock_loader.load_query.assert_called_with('fetch_label_top_releases') mock_snowflake.session.execute.assert_called_with( 'SQL', {'label_id': 123}) assert result == 'FETCH ALL' def test_fetch_top_items_is_new_releases(mock_snowflake): """Test fetching the top items with new releases.""" with patch('activity_detector.flows.analytics_digest.utils.sql_loader', autospec=True) as mock_loader: mock_loader.load_query.return_value = 'SQL' mock_snowflake.fetchall.return_value = 'FETCH ALL' item_type = 'release' account_type = base_config.ACCOUNT_TYPE_LABEL account_id = 123 is_new_releases = True result = utils.fetch_top_items( item_type, account_type, account_id, mock_snowflake.session, is_new_releases) mock_loader.load_query.assert_called_with( 'fetch_label_top_new_releases') mock_snowflake.session.execute.assert_called_with( 'SQL', {'label_id': 123}) assert result == 'FETCH ALL' def test_fetch_top_items_physical(mock_snowflake): """Test fetching the top items with physical shipment.""" with patch('activity_detector.flows.analytics_digest.utils.sql_loader', autospec=True) as mock_loader: mock_loader.load_query.return_value = 'SQL {env}' mock_snowflake.fetchall.return_value = 'FETCH ALL' base_config.ENVIRONMENT = 'test' item_type = 'physical_shipment' account_type = base_config.ACCOUNT_TYPE_LABEL account_id = 123 start_date = '2018-07-13' end_date = '2018-07-20' result = utils.fetch_top_items( item_type, account_type, account_id, mock_snowflake.session, False, start_date, end_date) mock_loader.load_query.assert_called_with( 'fetch_label_top_physical_shipments') mock_snowflake.session.execute.assert_called_with( 'SQL test', {'label_id': 123, 'start_date': start_date, 'end_date': end_date}) assert result == 'FETCH ALL' def test_map_top_items_to_list(): """Test mapping top items.""" top_releases_keyed_by_release_id_mock = { 123: { 'release_id': 123, 'release_name': 'release_123', 'release_format': 'Full Length', 'artist_name': ['artist_123'], 'total_streams': 10000, 'store_percentages': { 'spotify': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 }, 'itunes': { 'percentage': '20%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 }, 'deezer': { 'percentage': '20%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 }, 'other': 1000 } }, 456: { 'release_id': 456, 'release_name': 'release_456', 'release_format': 'Full Length', 'artist_name': ['artist_456'], 'total_streams': 1000, 'store_percentages': { 'spotify': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 }, 'itunes': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 } } }, 789: { 'release_id': 789, 'release_name': 'release_789', 'release_format': 'Full Length', 'artist_name': 'artist_789', 'total_streams': 1000, 'store_percentages': { 'spotify': { 'percentage': '100%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 } } } } expected_release_123 = { 'release_id': 123, 'release_name': 'release_123', 'release_format': 'Full Length', 'artist_name': ['artist_123'], 'total_streams': 10000, 'store_percentages': { 'spotify': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 }, 'itunes': { 'percentage': '20%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 }, 'deezer': { 'percentage': '20%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 }, 'other': { 'percentage': '10%', 'streams_per_store': 1000, 'growth_percentage_per_store': None } } } expected_release_456 = { 'release_id': 456, 'release_name': 'release_456', 'release_format': 'Full Length', 'artist_name': ['artist_456'], 'total_streams': 1000, 'store_percentages': { 'spotify': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 }, 'itunes': { 'percentage': '50%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 } } } expected_release_789 = { 'release_id': 789, 'release_name': 'release_789', 'release_format': 'Full Length', 'artist_name': 'artist_789', 'total_streams': 1000, 'store_percentages': { 'spotify': { 'percentage': '100%', 'streams_per_store': 500, 'growth_percentage_per_store': 11 } } } result = utils.map_top_items_to_list( top_releases_keyed_by_release_id_mock) assert len(result) == 3 assert type(result) is list assert expected_release_123 in result assert expected_release_456 in result assert expected_release_789 in result def test_sort_top_items_streams(): """Test sorting the top items streams.""" top_releases = [ { 'release_id': 456, 'release_name': 'release_456', 'release_format': 'Full Length', 'artist_name': ['artist_456'], 'total_streams': 1000, 'store_percentages': { 'spotify': '50%', 'itunes': '50%' } }, { 'release_id': 789, 'release_name': 'release_789', 'release_format': 'Full Length', 'artist_name': 'artist_789', 'total_streams': 100, 'store_percentages': { 'amazon': '80%', 'other': '20%' } }, { 'release_id': 123, 'release_name': 'release_123', 'release_format': 'Full Length', 'artist_name': ['artist_123'], 'total_streams': 10000, 'store_percentages': { 'spotify': '50%', 'itunes': '20%', 'amazon': '20%', 'other': '10%' } } ] result = utils.sort_top_items_streams(top_releases) assert result == [ { 'release_id': 123, 'release_name': 'release_123', 'release_format': 'Full Length', 'artist_name': ['artist_123'], 'total_streams': 10000, 'store_percentages': { 'spotify': '50%', 'itunes': '20%', 'amazon': '20%', 'other': '10%' } }, { 'release_id': 456, 'release_name': 'release_456', 'release_format': 'Full Length', 'artist_name': ['artist_456'], 'total_streams': 1000, 'store_percentages': { 'spotify': '50%', 'itunes': '50%' } }, { 'release_id': 789, 'release_name': 'release_789', 'release_format': 'Full Length', 'artist_name': 'artist_789', 'total_streams': 100, 'store_percentages': { 'amazon': '80%', 'other': '20%' } } ] def test_sort_top_items_transactions(): """Test sorting the top items transactions.""" top_physical_shipments = [ { 'raas_no': 123, 'product_name': 'product_123', 'product_type': 'CD', 'artist_name': ['artist_123'], 'total_transactions': 100 }, { 'raas_no': 456, 'product_name': 'product_456', 'product_type': 'CD', 'artist_name': ['artist_456'], 'total_transactions': 1000 }, { 'raas_no': 789, 'product_name': 'product_789', 'product_type': 'CD', 'artist_name': 'artist_789', 'total_transactions': 10000 } ] result = utils.sort_top_items_transactions(top_physical_shipments) assert result == [ { 'raas_no': 789, 'product_name': 'product_789', 'product_type': 'CD', 'artist_name': 'artist_789', 'total_transactions': 10000 }, { 'raas_no': 456, 'product_name': 'product_456', 'product_type': 'CD', 'artist_name': ['artist_456'], 'total_transactions': 1000 }, { 'raas_no': 123, 'product_name': 'product_123', 'product_type': 'CD', 'artist_name': ['artist_123'], 'total_transactions': 100 } ] def test_create_notification(monkeypatch): """Test creating the notifications.""" monkeypatch.setattr( ows_notifications, 'create_notification', MagicMock(return_value=response.Response())) digest = { 123: { 'actor': 'The Orchard Activity Detector', 'verb': 'Generated', 'object': 'Digest', 'start_date': '2017-12-22', 'end_date': '2017-12-29', 'top_releases': [ { 'release_id': 123, 'release_name': 'release_123', 'release_format': 'Full Length', 'artist_name': ['artist_123'], 'total_streams': 10000, 'store_percentages': { 'spotify': '50%', 'itunes': '20%', 'amazon': '20%', 'other': '10%' } } ], 'top_tracks': [ { 'isrc': 'ABC123', 'track_name': 'track_123', 'artist_name': ['artist_123'], 'total_streams': 10000, 'store_percentages': { 'spotify': '50%', 'itunes': '20%', 'amazon': '20%', 'other': '10%' } } ], 'top_physical_shipments': [ { 'raas_no': 'ABC123', 'product_name': 'product_123', 'product_type': 'CD', 'artist_name': ['artist_123'], 'total_transactions': 10000 } ] } } account_type = base_config.ACCOUNT_TYPE_LABEL utils.create_notification(account_type, digest) ows_notifications.create_notification.assert_called_once_with({ 'feed_name': config.FEED_NAME, 'feed_id': 'vendor_123', 'payload': digest[123] }) def test_create_notification_without_data(monkeypatch): """Test create notification without data.""" monkeypatch.setattr( ows_notifications, 'create_notification', MagicMock()) digest = { 123: { 'actor': 'The Orchard Activity Detector', 'verb': 'Generated', 'object': 'Digest', 'start_date': '2017-12-22', 'end_date': '2017-12-29', 'top_new_releases': [], 'top_releases': [], 'top_tracks': [], 'top_physical_shipments': [] } } account_type = base_config.ACCOUNT_TYPE_LABEL utils.create_notification(account_type, digest) ows_notifications.create_notification.assert_not_called() def test_create_notification_error(monkeypatch): """Test creating the notifications when an error response is returned.""" monkeypatch.setattr( ows_notifications, 'create_notification', MagicMock(return_value=response.create_fatal_response('ERROR'))) digest = { 123: { 'actor': 'The Orchard Activity Detector', 'verb': 'Generated', 'object': 'Digest', 'start_date': '2017-12-22', 'end_date': '2017-12-29', 'top_releases': [ { 'release_id': 123, 'release_name': 'release_123', 'release_format': 'Full Length', 'artist_name': ['artist_123'], 'total_streams': 10000, 'store_percentages': { 'spotify': '50%', 'itunes': '20%', 'amazon': '20%', 'other': '10%' } } ], 'top_tracks': [ { 'isrc': 'ABC123', 'track_name': 'track_123', 'artist_name': ['artist_123'], 'total_streams': 10000, 'store_percentages': { 'spotify': '50%', 'itunes': '20%', 'amazon': '20%', 'other': '10%' } } ] } } account_type = base_config.ACCOUNT_TYPE_LABEL try: utils.create_notification(account_type, digest) except Exception as e: ows_notifications.create_notification.assert_called_once() assert 'ERROR' in str(e)