"""Unit tests for product logic helpers and query-class wiring.""" import datetime from unittest.mock import MagicMock, patch import pytest from analytics.api import app from analytics.logic import product @pytest.fixture(autouse=True) def mock_redis_cache(): """Bypass `cache_in_redis` decorators by forcing cache miss on every call.""" with patch("analytics.utils.cache.redis.client", new=MagicMock(get=lambda _: None)): yield MODEL_PAYLOAD = [ { "product_id": 111, "track_id": 1, "isrc": "GBZUZ1", "download_activity_date": datetime.date(2018, 10, 15), "streams": 1, "max_available_date": datetime.date(2018, 10, 24), }, { "product_id": 111, "track_id": 2, "isrc": "GBZUZ2", "download_activity_date": datetime.date(2018, 10, 24), "streams": 20, "max_available_date": datetime.date(2018, 10, 24), }, ] def test__get_max_available_date(): """Returns the first row's max_available_date or None on empty input.""" assert product._get_max_available_date(MODEL_PAYLOAD) == datetime.date(2018, 10, 24) assert product._get_max_available_date([]) is None def test__group_tracks_streams_by_track_id(): """Groups by track_id and folds in all-time streams from the second source.""" track_streams_all_time = {"GBZUZ1": 1000, "GBZUZ2": 2000} result = product._group_tracks_streams_by_track_id( list(MODEL_PAYLOAD), track_streams_all_time ) assert result == [ { "product_id": 111, "track_id": 1, "isrc": "GBZUZ1", "streams": {"aggregate": {"all_time": 1000}}, "max_available_date": datetime.date(2018, 10, 24), }, { "product_id": 111, "track_id": 2, "isrc": "GBZUZ2", "streams": {"aggregate": {"all_time": 2000}}, "max_available_date": datetime.date(2018, 10, 24), }, ] @pytest.fixture def base_query_params(): return {"product_id": 1, "distributors": ["theorchard"]} @pytest.fixture def base_permissions(): return { "permission_label_ids": [7123], "permission_subaccount_ids": [], "permission_artist_ids": [], "permission_label_participant_ids": [], "permission_feed_ids": [1, 2, 38], } class TestGetProduct: """Mock the new query classes and verify get_product wires them correctly.""" def test_succeeds(self, base_query_params, base_permissions): with ( app.test_request_context(), patch("analytics.logic.product.ProductTracks") as tracks_cls, patch("analytics.logic.product.ProductStreamsAllTime") as all_time_cls, patch( "analytics.logic.product.add_outage_error_to_stores", return_value=[{"id": 286, "name": "Spotify"}], ), patch("analytics.logic.product.store_availability") as store_av, ): store_av.get_sources.return_value = [{"id": 286, "name": "Spotify"}] store_av.get_store_ids.return_value = [1, 286] tracks_cls.return_value.execute.return_value = [ { "product_id": 1, "track_id": 1, "isrc": "GBZUZ1", "download_activity_date": datetime.date(2018, 10, 15), "streams": 1, "max_available_date": datetime.date(2018, 10, 24), }, ] all_time_cls.return_value.execute.return_value = [ { "isrc": "GBZUZ1", "track_id": 1, "all_time": 1000, "growth_percentage": 0.1234, }, ] response = product.get_product(base_query_params, base_permissions) assert response.status == 200 assert response.message["product_id"] == "1" assert response.message["streams"]["aggregate"]["all_time"] == 1000 assert ( response.message["streams"]["aggregate"]["growth_percentage"] == 0.1234 ) def test_empty_streams_short_circuits(self, base_query_params, base_permissions): with ( app.test_request_context(), patch("analytics.logic.product.ProductTracks") as tracks_cls, patch("analytics.logic.product.ProductStreamsAllTime") as all_time_cls, patch( "analytics.logic.product.add_outage_error_to_stores", return_value=[{"id": 286, "name": "Spotify"}], ), patch("analytics.logic.product.store_availability") as store_av, ): store_av.get_sources.return_value = [{"id": 286, "name": "Spotify"}] store_av.get_store_ids.return_value = [1, 286] tracks_cls.return_value.execute.return_value = [] all_time_cls.return_value.execute.return_value = [] response = product.get_product(base_query_params, base_permissions) assert response.status == 200 assert response.message["tracks"] == [] assert response.message["streams"]["aggregate"]["all_time"] is None class TestGetMetricsByTrack: """Mock ProductMetricsByTrack.""" def test_returns_tracks(self, base_permissions): query_params = { "product_id": 1, "distributors": ["theorchard"], "country_ids": [], "store_ids": [1, 286], "order_by": "streams_7_days", "order_dir": "DESC", "limit": 100, "offset": 0, } with ( app.test_request_context(), patch("analytics.logic.product.ProductMetricsByTrack") as cls, patch("analytics.logic.product.store_availability") as store_av, ): store_av.get_store_ids.return_value = [1, 286] cls.return_value.execute.return_value = [ { "track_id": "1", "tuid": "t1", "streams_1_day": 1, "growth_percentage_1_day": 0, "streams_7_days": 7, "growth_percentage_7_days": 0, "streams_28_days": 28, "growth_percentage_28_days": 0, "streams_all_time": 100, } ] response = product.get_metrics_by_track(query_params, base_permissions) assert response.status == 200 assert response.message["product_id"] == "1" assert len(response.message["tracks"]) == 1 def test_no_store_ids_after_intersection_returns_empty(self, base_permissions): query_params = { "product_id": 1, "distributors": ["theorchard"], "country_ids": [], "store_ids": [9999], # not in available set "order_by": "streams_7_days", "order_dir": "DESC", "limit": 100, "offset": 0, } with ( app.test_request_context(), patch("analytics.logic.product.ProductMetricsByTrack") as cls, patch("analytics.logic.product.store_availability") as store_av, ): store_av.get_store_ids.return_value = [1, 286] response = product.get_metrics_by_track(query_params, base_permissions) assert response.status == 200 assert response.message["tracks"] == [] cls.assert_not_called() class TestGetAggregateStreams: """Mock ProductAggregateStreams.""" def test_with_data(self, base_permissions): query_params = { "product_id": 1, "distributors": ["theorchard"], "multi_product": False, "country_ids": [], } with ( app.test_request_context(), patch("analytics.logic.product.ProductAggregateStreams") as cls, patch("analytics.logic.product.store_availability") as store_av, ): store_av.get_store_ids.return_value = [1, 286] cls.return_value.execute.return_value = [ { "product_id": 1, "streams_all_time": 1234, "growth_percentage_7_days": 0.42, } ] response = product.get_aggregate_streams(query_params, base_permissions) assert response.status == 200 assert response.message["streams_all_time"] == 1234 assert response.message["growth_percentage_7_days"] == 0.42 def test_no_data_returns_zero_defaults(self, base_permissions): query_params = { "product_id": 1, "distributors": ["theorchard"], "multi_product": False, "country_ids": [], } with ( app.test_request_context(), patch("analytics.logic.product.ProductAggregateStreams") as cls, patch("analytics.logic.product.store_availability") as store_av, ): store_av.get_store_ids.return_value = [1, 286] cls.return_value.execute.return_value = [] response = product.get_aggregate_streams(query_params, base_permissions) assert response.status == 200 assert response.message["streams_all_time"] == 0 assert response.message["growth_percentage_7_days"] is None