from unittest.mock import patch import pytest from analytics.logic.streams import get_aggregated_streams MOCK_AGGREGATED_STREAMS_RESPONSE = [ { "result": """{ "TOPN_ROLLUP": [ { "ID": "mx", "GROWTH_PERCENTAGE_1_DAY": NULL, "GROWTH_PERCENTAGE_7_DAYS": 0.032256, "STREAMS_1_DAY": 13555895, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -168593, "STREAMS_1_DAY_PREV": 13724488, "STREAMS_28_DAYS": 353679176, "STREAMS_7_DAYS": 89667723, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 2801943, "STREAMS_7_DAYS_PREV": 86865780, "STREAMS_ALL_TIME": 23744593252 } ], "TOPN_TIMESERIES": [ { "ID": "mx", "DATE": "2023-04-05", "VALUE": 42242 } ], "MISSING_STORE_DATES": [{"DATE": "2025-11-19", "ID": 453}, {"DATE": "2025-11-20", "ID": 453}], "TOTAL": { "GROWTH_PERCENTAGE_1_DAY": NULL, "GROWTH_PERCENTAGE_7_DAYS": 0.04973, "STREAMS_1_DAY": 57895951, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -2985644, "STREAMS_1_DAY_PREV": 60881595, "STREAMS_28_DAYS": 1565230371, "STREAMS_7_DAYS": 401913178, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 19040233, "STREAMS_7_DAYS_PREV": 382872945, "STREAMS_ALL_TIME": 105457920498 }, "ALL_OTHER_ROLLUP": { "GROWTH_PERCENTAGE_1_DAY": NULL, "GROWTH_PERCENTAGE_7_DAYS": 0.057954, "STREAMS_1_DAY": 170053, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -8325, "STREAMS_1_DAY_PREV": 178378, "STREAMS_28_DAYS": 4563156, "STREAMS_7_DAYS": 1176231, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 64433, "STREAMS_7_DAYS_PREV": 1111798, "STREAMS_ALL_TIME": 468870290 }, "ALL_OTHER_TIMESERIES": [ { "DATE": "2023-04-05", "VALUE": 813 } ] }""" } ] # noqa MOCK_ROWS_AGGREGATED_STREAMS = { "all_other_rollup": { "growth_percentage_1_day": None, "growth_percentage_7_days": 0.057954, "streams_1_day": 170053, "streams_1_day_absolute_change": -8325, "streams_1_day_prev": 178378, "streams_28_days": 4563156, "streams_7_days": 1176231, "streams_7_days_absolute_change": 64433, "streams_7_days_prev": 1111798, "streams_all_time": 468870290, }, "topn_rollup": [ { "growth_percentage_1_day": None, "growth_percentage_7_days": 0.032256, "id": "mx", "streams_1_day": 13555895, "streams_1_day_absolute_change": -168593, "streams_1_day_prev": 13724488, "streams_28_days": 353679176, "streams_7_days": 89667723, "streams_7_days_absolute_change": 2801943, "streams_7_days_prev": 86865780, "streams_all_time": 23744593252, }, ], "all_other_timeseries": [ { "date": "2023-04-05", "value": 813, } ], "topn_timeseries": {"mx": [{"date": "2023-04-05", "value": 42242}]}, "total": { "growth_percentage_1_day": None, "growth_percentage_7_days": 0.04973, "streams_1_day": 57895951, "streams_1_day_absolute_change": -2985644, "streams_1_day_prev": 60881595, "streams_28_days": 1565230371, "streams_7_days": 401913178, "streams_7_days_absolute_change": 19040233, "streams_7_days_prev": 382872945, "streams_all_time": 105457920498, }, } MOCK_AGGREGATED_SOS_STREAMS_RESPONSE = [ { "result": """{ "TOPN_ROLLUP": [ { "GROWTH_PERCENTAGE_1_DAY": -0.158441, "GROWTH_PERCENTAGE_7_DAYS": 0.057612, "ID": "streams_passive", "STREAMS_1_DAY": 921134, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -173422, "STREAMS_1_DAY_PREV": 1094556, "STREAMS_28_DAYS": 28492425, "STREAMS_7_DAYS": 7200341, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 392232, "STREAMS_7_DAYS_PREV": 6808109, "STREAMS_ALL_TIME": 291826088 } ], "TOPN_TIMESERIES": [ { "ID": "STREAMS_ACTIVE", "DATE": "2023-04-05", "VALUE": 42242 }, { "ID": "STREAMS_COLLECTION", "DATE": "2023-04-05", "VALUE": 42242 }, { "ID": "STREAMS_PASSIVE", "DATE": "2023-04-05", "VALUE": 42242 }, { "ID": "STREAMS_UNKNOWN", "DATE": "2023-04-05", "VALUE": 42242 } ], "MISSING_STORE_DATES": [{"DATE": "2025-11-19", "ID": 453}, {"DATE": "2025-11-20", "ID": 453}], "TOTAL": { "GROWTH_PERCENTAGE_1_DAY": -0.172931, "GROWTH_PERCENTAGE_7_DAYS": 0.031672, "STREAMS_1_DAY": 2305614, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -482078, "STREAMS_1_DAY_PREV": 2787692, "STREAMS_28_DAYS": 70515090, "STREAMS_7_DAYS": 17563152, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 539185, "STREAMS_7_DAYS_PREV": 17023967, "STREAMS_ALL_TIME": 875886826 }, "ALL_OTHER_ROLLUP": {}, "ALL_OTHER_TIMESERIES": [ { "DATE": "2023-04-05", "VALUE": 813 } ] }""" } ] # noqa MOCK_ROWS_AGGREGATED_SOS_STREAMS = { "all_other_rollup": {}, "topn_rollup": [ { "growth_percentage_1_day": -0.158441, "growth_percentage_7_days": 0.057612, "id": "streams_passive", "streams_1_day": 921134, "streams_1_day_absolute_change": -173422, "streams_1_day_prev": 1094556, "streams_28_days": 28492425, "streams_7_days": 7200341, "streams_7_days_absolute_change": 392232, "streams_7_days_prev": 6808109, "streams_all_time": 291826088, } ], "all_other_timeseries": [ { "date": "2023-04-05", "value": 813, } ], "topn_timeseries": { "streams_active": [{"date": "2023-04-05", "value": 42242}], "streams_collection": [{"date": "2023-04-05", "value": 42242}], "streams_passive": [{"date": "2023-04-05", "value": 42242}], "streams_unknown": [{"date": "2023-04-05", "value": 42242}], }, "total": { "growth_percentage_1_day": -0.172931, "growth_percentage_7_days": 0.031672, "streams_1_day": 2305614, "streams_1_day_absolute_change": -482078, "streams_1_day_prev": 2787692, "streams_28_days": 70515090, "streams_7_days": 17563152, "streams_7_days_absolute_change": 539185, "streams_7_days_prev": 17023967, "streams_all_time": 875886826, }, } MOCK_AGGREGATED_SOS_V2_STREAMS_SNOWFLAKE_RESULT = [ { "result": """{ "TOPN_ROLLUP": [ { "GROWTH_PERCENTAGE_1_DAY": -0.158441, "GROWTH_PERCENTAGE_7_DAYS": 0.057612, "ID": "streams", "STREAMS_1_DAY": 921134, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -173422, "STREAMS_1_DAY_PREV": 1094556, "STREAMS_28_DAYS": 28492425, "STREAMS_7_DAYS": 7200341, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 392232, "STREAMS_7_DAYS_PREV": 6808109, "STREAMS_ALL_TIME": 291826088 }, { "GROWTH_PERCENTAGE_1_DAY": -0.158441, "GROWTH_PERCENTAGE_7_DAYS": 0.057612, "ID": "streams_sos_spotify_albumpage", "STREAMS_1_DAY": 921134, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -173422, "STREAMS_1_DAY_PREV": 1094556, "STREAMS_28_DAYS": 28492425, "STREAMS_7_DAYS": 7200341, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 392232, "STREAMS_7_DAYS_PREV": 6808109, "STREAMS_ALL_TIME": 291826088 }, { "GROWTH_PERCENTAGE_1_DAY": -0.158441, "GROWTH_PERCENTAGE_7_DAYS": 0.057612, "ID": "streams_sos_spotify_collection", "STREAMS_1_DAY": 921134, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -173422, "STREAMS_1_DAY_PREV": 1094556, "STREAMS_28_DAYS": 28492425, "STREAMS_7_DAYS": 7200341, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 392232, "STREAMS_7_DAYS_PREV": 6808109, "STREAMS_ALL_TIME": 291826088 } ], "TOPN_TIMESERIES": [ { "ID": "STREAMS", "DATE": "2023-04-05", "VALUE": 42242 }, { "ID": "STREAMS_SOS_SPOTIFY_ALBUMPAGE", "DATE": "2023-04-05", "VALUE": 42242 }, { "ID": "STREAMS_SOS_SPOTIFY_COLLECTION", "DATE": "2023-04-05", "VALUE": 42242 } ], "MISSING_STORE_DATES": [{"DATE": "2025-11-19", "ID": 453}, {"DATE": "2025-11-20", "ID": 453}], "TOTAL": { "GROWTH_PERCENTAGE_1_DAY": -0.172931, "GROWTH_PERCENTAGE_7_DAYS": 0.031672, "STREAMS_1_DAY": 2305614, "STREAMS_1_DAY_ABSOLUTE_CHANGE": -482078, "STREAMS_1_DAY_PREV": 2787692, "STREAMS_28_DAYS": 70515090, "STREAMS_7_DAYS": 17563152, "STREAMS_7_DAYS_ABSOLUTE_CHANGE": 539185, "STREAMS_7_DAYS_PREV": 17023967, "STREAMS_ALL_TIME": 875886826 }, "ALL_OTHER_ROLLUP": {}, "ALL_OTHER_TIMESERIES": [ { "DATE": "2023-04-05", "VALUE": 813 } ] }""" } ] # noqa MOCK_AGGREGATED_SOS_V2_STREAMS_RESPONSE = { "all_other_rollup": {}, "topn_rollup": [ { "growth_percentage_1_day": -0.158441, "growth_percentage_7_days": 0.057612, "id": "streams", "streams_1_day": 921134, "streams_1_day_absolute_change": -173422, "streams_1_day_prev": 1094556, "streams_28_days": 28492425, "streams_7_days": 7200341, "streams_7_days_absolute_change": 392232, "streams_7_days_prev": 6808109, "streams_all_time": 291826088, }, { "growth_percentage_1_day": -0.158441, "growth_percentage_7_days": 0.057612, "id": "streams_sos_spotify_albumpage", "streams_1_day": 921134, "streams_1_day_absolute_change": -173422, "streams_1_day_prev": 1094556, "streams_28_days": 28492425, "streams_7_days": 7200341, "streams_7_days_absolute_change": 392232, "streams_7_days_prev": 6808109, "streams_all_time": 291826088, }, { "growth_percentage_1_day": -0.158441, "growth_percentage_7_days": 0.057612, "id": "streams_sos_spotify_collection", "streams_1_day": 921134, "streams_1_day_absolute_change": -173422, "streams_1_day_prev": 1094556, "streams_28_days": 28492425, "streams_7_days": 7200341, "streams_7_days_absolute_change": 392232, "streams_7_days_prev": 6808109, "streams_all_time": 291826088, }, ], "all_other_timeseries": [ { "date": "2023-04-05", "value": 813, } ], "topn_timeseries": { "streams": [{"date": "2023-04-05", "value": 42242}], "streams_sos_spotify_albumpage": [{"date": "2023-04-05", "value": 42242}], "streams_sos_spotify_collection": [{"date": "2023-04-05", "value": 42242}], }, "total": { "growth_percentage_1_day": -0.172931, "growth_percentage_7_days": 0.031672, "streams_1_day": 2305614, "streams_1_day_absolute_change": -482078, "streams_1_day_prev": 2787692, "streams_28_days": 70515090, "streams_7_days": 17563152, "streams_7_days_absolute_change": 539185, "streams_7_days_prev": 17023967, "streams_all_time": 875886826, }, } @pytest.fixture(autouse=True) def mock_get_feeds_with_outages_v2_success(): """Mock successful get_feeds_with_outages_v2 model response.""" with patch( "analytics.utils.data_availability.get_feeds_with_outages_v2" ) as get_feeds_with_outages_v2: get_feeds_with_outages_v2.return_value = [ { "distributor": "sme", "feed_name": "Spotify Streams", "feed_id": 1, "store_id": 286, "store_high_water_mark": "2025-07-13", "update_status": "UPDATED", "streaming_stores_high_watermark": "2025-07-13", "missing_dates_before_store_high_watermark": [], "has_streams_outage": False, }, { "distributor": "sme", "feed_name": "Apple Music", "feed_id": 4, "store_id": 1, "store_high_water_mark": "2025-07-13", "update_status": "UPDATED", "streaming_stores_high_watermark": "2025-07-13", "missing_dates_before_store_high_watermark": [], "has_streams_outage": False, }, ] yield get_feeds_with_outages_v2 @pytest.fixture(autouse=True) def mock_get_max_available_date_success(): """Mock successful get_max_available_date model response.""" with patch( "analytics.utils.data_availability.get_max_available_date" ) as get_max_available_date: get_max_available_date.return_value = "2023-04-05" yield get_max_available_date def test_aggregated_streams_for_isrc(): with patch( "analytics.logic.streams.SoundRecordingAggregatedStreamsByCountryOrStore.execute", # noqa return_value=MOCK_AGGREGATED_STREAMS_RESPONSE, ) as execute: agg = get_aggregated_streams( { "isrc": "UYB282201028", "dimension": "COUNTRY", "order_by": "streams_7_days", "top_size": 1, "days_back": 1, "countries": [], "store_ids": [], }, {}, ) assert execute.call_count == 1 assert agg == MOCK_ROWS_AGGREGATED_STREAMS def test_aggregated_streams_for_product_id(): with patch( "analytics.logic.streams.ProductAggregatedStreamsByCountryOrStore.execute", # noqa return_value=MOCK_AGGREGATED_STREAMS_RESPONSE, ) as execute: agg = get_aggregated_streams( { "product_id": "196626945068", "dimension": "COUNTRY", "order_by": "streams_7_days", "top_size": 1, "days_back": 1, "countries": [], "store_ids": [], }, {}, ) assert execute.call_count == 1 assert agg == MOCK_ROWS_AGGREGATED_STREAMS def test_aggregated_sos_streams_for_isrc(): with patch( "analytics.logic.streams.SoundRecordingAggregatedStreamsBySOS.execute", # noqa return_value=MOCK_AGGREGATED_SOS_STREAMS_RESPONSE, ) as execute: agg = get_aggregated_streams( { "isrc": "UYB282201028", "dimension": "SOS", "order_by": "streams_7_days", "top_size": 1, "days_back": 1, "countries": [], "store_ids": [], }, {}, ) assert execute.call_count == 1 assert agg == MOCK_ROWS_AGGREGATED_SOS_STREAMS def test_aggregated_streams_for_isrc_by_sos_v2(): with patch( "analytics.logic.streams.SoundRecordingAggregatedStreamsBySOSV2.execute", # noqa return_value=MOCK_AGGREGATED_SOS_V2_STREAMS_SNOWFLAKE_RESULT, ) as execute: agg = get_aggregated_streams( { "isrc": "UYB282201028", "dimension": "SOS_V2", "order_by": "streams_7_days", "top_size": 1, "days_back": 1, "countries": [], "store_ids": [286], }, {}, ) assert execute.call_count == 1 assert agg == MOCK_AGGREGATED_SOS_V2_STREAMS_RESPONSE @pytest.mark.parametrize( "query_type,dimension,expected_flag", [ # The flag threads through for every flow and dimension: permissions_filter # v2 is entity-agnostic and every backing rollup (sound-recording, product, # and participant) is in the _PRODUCT_TRANSFER allowlist. ("isrc", "SOS", True), ("isrc", "SOS_V2", True), ("isrc", "STORE", True), ("isrc", "COUNTRY", True), ("product_id", "SOS", True), ("product_id", "STORE", True), ("product_id", "COUNTRY", True), ("global_participant_id", "SOS", True), ("global_participant_id", "STORE", True), ], ) def test_transfer_product_ownership_flag_routing( mocker, query_type, dimension, expected_flag, ): """The flag threads through unchanged for every flow and dimension.""" query_class_by_type = { "isrc": { "SOS": "SoundRecordingAggregatedStreamsBySOS", "SOS_V2": "SoundRecordingAggregatedStreamsBySOSV2", "STORE": "SoundRecordingAggregatedStreamsByCountryOrStore", "COUNTRY": "SoundRecordingAggregatedStreamsByCountryOrStore", }, "product_id": { "SOS": "ProductAggregatedStreamsBySOS", "STORE": "ProductAggregatedStreamsByCountryOrStore", "COUNTRY": "ProductAggregatedStreamsByCountryOrStore", }, "global_participant_id": { "SOS": "GlobalParticipantAggregatedStreamsBySOS", "STORE": "GlobalParticipantAggregatedStreamsByCountryOrStore", }, } class_name = query_class_by_type[query_type][dimension] mock_query = mocker.patch(f"analytics.logic.streams.{class_name}") mock_query.return_value.execute.return_value = [] get_aggregated_streams( { query_type: "ID", "dimension": dimension, "order_by": "streams_7_days", "top_size": 1, "days_back": 1, "countries": [], "store_ids": [], "transfer_product_ownership_enabled": True, }, {}, ) (query_input,), _ = mock_query.call_args assert query_input["transfer_product_ownership_enabled"] is expected_flag