"""Tests for handlers.""" import datetime import json from unittest.mock import MagicMock, patch from urllib.parse import urlencode import pytest from oto import response as oto_response from analytics import config, handlers, participant_handlers # noqa: F401 from analytics.api import app from analytics.constants.owner_category import CURATED, EDITORIAL from analytics.handler_utils import _get_global_filters TOP_MOCK_PERMISSIONS = { "permission_label_ids": [], "permission_artist_ids": [], "permission_subaccount_ids": [], "permission_label_participant_ids": [], "permission_feed_ids": [1, 2, 38], } @pytest.fixture def mock_get_permission_values_for_top(): """Mock get_permission_values for top-* handlers (catalog endpoints).""" with patch( "analytics.handlers.get_permission_values", return_value=TOP_MOCK_PERMISSIONS, ): yield TOP_MOCK_PERMISSIONS def test_exception_handler(): with app.app_context(), patch("analytics.handlers.g") as mock_g: """Verify exception_Handler returns 500 status code and json payload.""" message = ( "The server encountered an internal error " "and was unable to complete your request." ) mock_error = MagicMock() server_response = handlers.exception_handler(mock_error) mock_g.log.exception.assert_called_with(mock_error) assert server_response.status_code == 500 response_message = json.loads(server_response.data.decode()) assert response_message["message"] == message assert response_message["code"] == oto_response.error.ERROR_CODE_INTERNAL_ERROR class TestTopSoundRecordings(object): """Test top tracks endpoint.""" countries = ["US"] @pytest.fixture def top_sound_recordings(self): """Mock top sound recordings.""" with patch( "analytics.handlers.top_sound_recordings.get_top_sound_recordings" ) as get_top_sound_recordings: get_top_sound_recordings.return_value = oto_response.Response({}) yield get_top_sound_recordings @pytest.fixture def response( self, client, top_sound_recordings, artist_profiles_request_headers, request_context_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, mock_get_permission_values_for_top, ): """Return response from requesting endpoint.""" return client.get( config.TOP_SOUND_RECORDINGS_PATH, headers=artist_profiles_request_headers ) @pytest.fixture def response_with_countries( self, client, top_sound_recordings, artist_profiles_request_headers, request_context_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, mock_get_permission_values_for_top, ): """Return response from requesting endpoint.""" country_param = "?country_code=US" return client.get( config.TOP_SOUND_RECORDINGS_PATH + country_param, headers=artist_profiles_request_headers, ) @pytest.fixture def response_with_order_by( self, client, top_sound_recordings, artist_profiles_request_headers, request_context_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, mock_get_permission_values_for_top, ): """Return response from requesting endpoint.""" params = "?order_by=streams_28_days" return client.get( config.TOP_SOUND_RECORDINGS_PATH + params, headers=artist_profiles_request_headers, ) @pytest.fixture def response_all_time( self, client, top_sound_recordings, artist_profiles_request_headers, request_context_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, mock_get_permission_values_for_top, ): """Return response from requesting endpoint.""" params = "?order_by=streams_all_time" return client.get( config.TOP_SOUND_RECORDINGS_PATH + params, headers=artist_profiles_request_headers, ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == {} def test_calls_logic_layer( self, response, top_sound_recordings, mock_get_permission_values_for_top ): """Test that get_top_sound_recordings is called from handler.""" top_sound_recordings.assert_called_once_with( { "country_ids": [], "distributors": ["theorchard", "sme", "awal"], "order_by": "streams_7_days", "limit": 25, "offset": 0, "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_top, ) def test_calls_logic_layer_with_countries( self, response_with_countries, top_sound_recordings, mock_get_permission_values_for_top, ): """Test that get_top_sound_recordings is called from handler.""" top_sound_recordings.assert_called_once_with( { "country_ids": self.countries, "distributors": ["theorchard", "sme", "awal"], "order_by": "streams_7_days", "limit": 25, "offset": 0, "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_top, ) def test_calls_logic_layer_with_order_by( self, response_with_order_by, top_sound_recordings, mock_get_permission_values_for_top, ): """Test that get_top_sound_recordings is called from handler.""" top_sound_recordings.assert_called_once_with( { "country_ids": [], "distributors": ["theorchard", "sme", "awal"], "order_by": "streams_28_days", "limit": 25, "offset": 0, "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_top, ) def test_calls_logic_layer_with_all_time( self, response_all_time, top_sound_recordings, mock_get_permission_values_for_top, ): """Test that get_top_sound_recordings is called from handler.""" top_sound_recordings.assert_called_once_with( { "country_ids": [], "distributors": ["theorchard", "sme", "awal"], "order_by": "streams_all_time", "limit": 25, "offset": 0, "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_top, ) def test_verify_profile_headers(self, response, owsrequest_verify_profile_headers): """Test that flask_request.verify_profile_headers is called.""" owsrequest_verify_profile_headers.assert_called_once() def test_get_profile_headers(self, response, owsrequest_get_profile_headers): """Test that flask_request.get_profile_headers is called.""" assert owsrequest_get_profile_headers.call_count == 1 class TestTopMetrics(object): """Test top metrics endpoint.""" @pytest.fixture def top_metrics(self): """Mock top metrics.""" with patch("analytics.handlers.top_metrics.get_top_metrics") as get_top_metrics: get_top_metrics.return_value = oto_response.Response({}) yield get_top_metrics @pytest.fixture def response( self, client, top_metrics, insights_request_headers, request_context_insights_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, owsrequest_get_insights_headers, ): """Return response from requesting endpoint.""" return client.get(config.TOP_METRICS_PATH, headers=insights_request_headers) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == {} def test_calls_logic_layer( self, response, request_context_insights_profile, top_metrics ): """Test that get_top_sound_recordings is called from handler.""" top_metrics.assert_called_once_with( { "upc": None, "country_ids": [], "isrc_country_ids": [], "release_period_start": None, "release_period_end": None, "only_daily_data": False, "only_weekly_data": False, "tiktok_creations_change_threshold": 0, "active_streams_change_threshold": -1, "store_ids": [], "label_ids": [], "subaccount_ids": [], "global_participant_ids": [], "parent_companies": [], "company_brands": [], "service_tier": None, "distributors": ["theorchard", "sme", "awal"], "order_by": "streams_7_days", "order_dir": "DESC", "limit": 50, "offset": 0, "label_manager_id": None, "transfer_product_ownership_enabled": False, }, { "permission_label_ids": [], "permission_artist_ids": [], "permission_subaccount_ids": [], "permission_label_participant_ids": [], "permission_feed_ids": [1, 2, 38], }, ) def test_verify_profile_headers(self, response, owsrequest_verify_profile_headers): """Test that flask_request.verify_profile_headers is called.""" owsrequest_verify_profile_headers.assert_called_once() def test_get_profile_headers(self, response, owsrequest_get_insights_headers): """Test that flask_request.get_profile_headers is called.""" assert owsrequest_get_insights_headers.call_count == 1 class TestTopAccountsMetrics: """Test top accounts metrics endpoint.""" @pytest.fixture def top_accounts_metrics(self): """Mock top accounts metrics.""" with patch( "analytics.handlers.top_accounts_metrics.get_top_accounts_metrics" ) as top_accounts_metrics: top_accounts_metrics.return_value = oto_response.Response({}) yield top_accounts_metrics @pytest.fixture def response( self, client, top_accounts_metrics, insights_request_headers, request_context_insights_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, owsrequest_get_insights_headers, mock_get_permission_values_for_top, ): """Return response from requesting endpoint.""" return client.get( config.TOP_ACCOUNTS_METRICS_PATH, headers=insights_request_headers ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == {} def test_calls_logic_layer( self, response, top_accounts_metrics, mock_get_permission_values_for_top ): """Test that get_top_accounts_metrics is called from handler.""" top_accounts_metrics.assert_called_once_with( { "distributors": ["theorchard", "awal"], "countries": [], "store_ids": [], "label_ids": [], "subaccount_ids": [], "parent_company": None, "company_brand": None, "service_tier": None, "label_manager": None, "include_subaccounts": False, "order_by": "streams_28_days", "order_dir": "DESC", "limit": 25, "offset": 0, "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_top, ) def test_verify_profile_headers(self, response, owsrequest_verify_profile_headers): """Test that flask_request.verify_profile_headers is called.""" owsrequest_verify_profile_headers.assert_called_once() def test_get_profile_headers(self, response, owsrequest_get_insights_headers): """Test that flask_request.get_profile_headers is called.""" assert owsrequest_get_insights_headers.call_count == 1 PRODUCT_MOCK_PERMISSIONS = { "permission_label_ids": [7123], "permission_artist_ids": None, "permission_subaccount_ids": None, "permission_label_participant_ids": None, "permission_feed_ids": [1, 2, 38], } @pytest.fixture def mock_get_permission_values_for_products(): """Mock get_permission_values for product handlers.""" with patch( "analytics.handlers.get_permission_values", return_value=PRODUCT_MOCK_PERMISSIONS, ): yield PRODUCT_MOCK_PERMISSIONS class TestProductMetrics: """Test /product-metrics endpoint.""" @pytest.fixture def mock_payload(self): """Return payload.""" return {"product": "metrics"} @pytest.fixture def get_product_metrics(self, mocker, mock_payload): """Mock streams_by_track logic.""" return mocker.patch( "analytics.handlers.product_metrics.get_product_metrics", return_value=oto_response.Response(mock_payload), ) @pytest.fixture def response( self, client, get_product_metrics, request_headers, mock_get_permission_values_for_products, ): """Return response from requesting endpoint.""" return client.get(config.PRODUCT_METRICS_PATH, headers=request_headers) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload, mock_payload): """Test returns payload.""" assert payload == mock_payload def test_calls_logic_layer( self, response, get_product_metrics, mock_get_permission_values_for_products ): """Test that get_product_metrics is called from handler.""" get_product_metrics.assert_called_once_with( { "distributors": ["theorchard", "sme", "awal"], "country_ids": [], "global_participant_ids": [], "parent_companies": [], "company_brands": [], "service_tier": None, "label_ids": [], "subaccount_ids": [], "fin_label_ids": [], "upper_profit_center_ids": [], "order_by": "streams_7_days", "order_dir": "DESC", "limit": 25, "offset": 0, "multi_product": False, "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_products, ) class TestProduct: """Test product endpoint.""" product_id = "1234abc" @pytest.fixture def mock_payload(self): """Return payload.""" return {"this": "is", "product": "data"} @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) @pytest.fixture def product(self, mock_payload): """Mock product.""" with patch("analytics.handlers.product.get_product") as get_product: get_product.return_value = oto_response.Response(mock_payload) yield get_product @pytest.fixture def response( self, client, product, request_headers, mock_get_permission_values_for_products, owsrequest_verify_grass_headers, owsrequest_get_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_empty_profile_headers, ): """Return response from requesting endpoint.""" return client.get( config.PRODUCT_PATH.replace("", self.product_id), headers=request_headers, ) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload, mock_payload): """Test returns payload.""" assert payload == mock_payload def test_calls_logic_layer( self, response, product, mock_get_permission_values_for_products ): """Test that product is called from handler.""" product.assert_called_once_with( { "product_id": self.product_id, "distributors": ["theorchard", "sme", "awal"], "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_products, ) def test_verify_grass_headers(self, response, owsrequest_verify_grass_headers): """Test that flask_request.verify_grass_headers is called.""" owsrequest_verify_grass_headers.assert_called_once() def test_get_grass_headers(self, response, owsrequest_get_grass_headers): """Test that flask_request.verify_grass_headers is called.""" assert owsrequest_get_grass_headers.call_count == 1 class TestProductMetadata: """Test product metadata endpoint.""" product_id = "1234abc" mock_permissions = { "permission_label_ids": [7123], "permission_artist_ids": None, "permission_subaccount_ids": None, "permission_label_participant_ids": None, "permission_feed_ids": [1, 2, 38], } @pytest.fixture def mock_payload(self): """Return payload.""" return {"this": "is", "product": "metadata"} @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) @pytest.fixture def product_metadata(self, mock_payload): """Mock product metadata.""" with patch( "analytics.handlers.product_metadata.get_product_metadata" ) as get_product_metadata: get_product_metadata.return_value = oto_response.Response(mock_payload) yield get_product_metadata @pytest.fixture def mock_get_permission_values(self): """Mock get_permission_values.""" with patch( "analytics.handlers.get_permission_values", return_value=self.mock_permissions, ): yield @pytest.fixture def response( self, client, product_metadata, mock_get_permission_values, request_headers, owsrequest_verify_grass_headers, owsrequest_get_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_empty_profile_headers, ): """Return response from requesting endpoint.""" return client.get( config.PRODUCT_METADATA_PATH.replace("", self.product_id), headers=request_headers, ) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload, mock_payload): """Test returns payload.""" assert payload == mock_payload def test_calls_logic_layer(self, response, product_metadata): """Test that product_metadata is called from handler.""" product_metadata.assert_called_once_with( {"product_id": self.product_id}, self.mock_permissions ) def test_verify_grass_headers(self, response, owsrequest_verify_grass_headers): """Test that flask_request.verify_grass_headers is called.""" owsrequest_verify_grass_headers.assert_called_once() def test_get_grass_headers(self, response, owsrequest_get_grass_headers): """Test that flask_request.verify_grass_headers is called.""" assert owsrequest_get_grass_headers.call_count == 1 class TestProductTrackMetrics: """Test /metrics-by-track endpoint.""" product_id = "1234abc" @pytest.fixture def mock_payload(self): """Return payload.""" return {"this": "is", "product": "data"} @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) @pytest.fixture def metrics_by_track(self, mock_payload): """Mock product metrics by track.""" with patch( "analytics.handlers.product.get_metrics_by_track" ) as get_metrics_by_track: get_metrics_by_track.return_value = oto_response.Response(mock_payload) yield get_metrics_by_track @pytest.fixture def response( self, client, metrics_by_track, request_headers, mock_get_permission_values_for_products, owsrequest_verify_grass_headers, owsrequest_get_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_empty_profile_headers, ): """Return response from requesting endpoint.""" return client.get( config.PRODUCT_METRICS_BY_TRACK_PATH.replace( "", self.product_id ), headers=request_headers, ) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload, mock_payload): """Test returns payload.""" assert payload == mock_payload def test_calls_logic_layer( self, response, metrics_by_track, mock_get_permission_values_for_products ): """Test that product_metadata is called from handler.""" metrics_by_track.assert_called_once_with( { "product_id": self.product_id, "distributors": ["theorchard", "sme", "awal"], "country_ids": [], "store_ids": [], "order_by": "streams_7_days", "order_dir": "DESC", "limit": 100, "offset": 0, "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_products, ) def test_verify_grass_headers(self, response, owsrequest_verify_grass_headers): """Test that flask_request.verify_grass_headers is called.""" owsrequest_verify_grass_headers.assert_called_once() def test_get_grass_headers(self, response, owsrequest_get_grass_headers): """Test that flask_request.verify_grass_headers is called.""" assert owsrequest_get_grass_headers.call_count == 1 class TestTopMarkets: """Test /sound-recording//top-markets endpoint.""" isrc = "GBUM71105426" store_ids = [1, 286] countries = ["US", "GB"] mock_permissions = { "permission_label_ids": [7123], "permission_artist_ids": None, "permission_subaccount_ids": None, "permission_label_participant_ids": None, "permission_feed_ids": [1, 2, 38], } @pytest.fixture def top_markets(self): """Mock get_top_markets.""" with patch("analytics.logic.top_markets.get_top_markets") as get_top_markets: get_top_markets.return_value = oto_response.Response([{"mock": "payload"}]) yield get_top_markets @pytest.fixture def mock_get_permission_values(self): """Mock get_permission_values.""" with patch( "analytics.handlers.get_permission_values", return_value=self.mock_permissions, ): yield @pytest.fixture def response( self, client, top_markets, mock_get_permission_values, request_headers ): """Return response from requesting endpoint.""" return client.get( config.TOP_MARKETS_PATH.replace("", self.isrc), headers=request_headers, ) @pytest.fixture def response_with_query_string( self, client, top_markets, mock_get_permission_values, request_headers ): """Return response from requesting endpoint.""" data = {"store_ids": self.store_ids, "country_code": self.countries} return client.get( config.TOP_MARKETS_PATH.replace("", self.isrc), query_string=data, headers=request_headers, ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == [{"mock": "payload"}] def test_calls_logic_layer(self, response, top_markets): """Test that get_top_markets called from handler.""" top_markets.assert_called_once_with( { "isrc": self.isrc, "country_ids": [], "store_ids": [], "distributors": ["theorchard", "sme", "awal"], "transfer_product_ownership_enabled": False, }, self.mock_permissions, ) def test_calls_logic_layer_with_query_params( self, response_with_query_string, top_markets ): """Test that get_top_markets called from handler with query params.""" top_markets.assert_called_once_with( { "isrc": self.isrc, "country_ids": self.countries, "store_ids": self.store_ids, "distributors": ["theorchard", "sme", "awal"], "transfer_product_ownership_enabled": False, }, self.mock_permissions, ) class TestFeed(object): """Test feed endpoint.""" feeds = config.FEEDS @pytest.fixture def feeds_details(self): """Mock feed details.""" with patch("analytics.handlers.feed.get_feeds") as get_feeds: get_feeds.return_value = oto_response.Response(self.feeds) yield get_feeds @pytest.fixture def response(self, client, feeds_details, request_headers, request_context): """Return response from requesting endpoint.""" return client.get(config.FEEDS_PATH, headers=request_headers) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == {str(k): v for k, v in self.feeds.items()} def test_calls_logic_layer( self, response, feeds_details, request_headers, request_context ): """Test that get_source_breakdown is called from handler.""" feeds_details.assert_called_once_with() class TestDemographics(object): """Test /sound-recording//demographics and /participant//demographics endpoints.""" isrc = "GBUM71105426" global_participant_id = "e49ea9f9-0a74-4623-b3c6-ff819c7c0a4d" @pytest.fixture def demographics_isrc(self): with patch("analytics.logic.demographics.get_demographics") as get_demographics: get_demographics.return_value = { "isrc": self.isrc, "demographics": {"age": {}, "gender": {}}, "sources": [], } yield get_demographics @pytest.fixture def demographics_gp(self): with patch("analytics.logic.demographics.get_demographics") as get_demographics: get_demographics.return_value = { "global_participant_id": self.global_participant_id, "demographics": {"age": {}, "gender": {}}, "sources": [], } yield get_demographics @pytest.fixture def response_isrc(self, client, demographics_isrc, insights_request_headers): return client.get( config.DEMOGRAPHICS_PATH.replace("", self.isrc), headers=insights_request_headers, ) @pytest.fixture def response_gp(self, client, demographics_gp, insights_request_headers): return client.get( config.PARTICIPANT_DEMOGRAPHICS_PATH.replace( "", self.global_participant_id ), headers=insights_request_headers, ) @pytest.fixture def response_gp_all_time(self, client, demographics_gp, insights_request_headers): """GraphQL all-time shape: ?days=0&start_date=HIGHWATERMARK.""" return client.get( config.PARTICIPANT_DEMOGRAPHICS_PATH.replace( "", self.global_participant_id ) + "?days=0&start_date=HIGHWATERMARK", headers=insights_request_headers, ) @pytest.fixture def response_isrc_all_time( self, client, demographics_isrc, insights_request_headers ): """GraphQL all-time shape: ?days=0&start_date=HIGHWATERMARK.""" return client.get( config.DEMOGRAPHICS_PATH.replace("", self.isrc) + "?days=0&start_date=HIGHWATERMARK", headers=insights_request_headers, ) def test_calls_logic_layer_isrc(self, response_isrc, demographics_isrc): """Handler passes query_params + permissions to logic layer.""" assert demographics_isrc.call_count == 1 query_params, permissions = demographics_isrc.call_args.args assert query_params["query_type"] == "isrc" assert query_params["isrc"] == self.isrc assert query_params["countries"] == [] assert query_params["store_ids"] == [] assert query_params["distributors"] == ["theorchard", "sme", "awal"] assert "permission_label_ids" in permissions def test_calls_logic_layer_gp(self, response_gp, demographics_gp): """Handler passes query_params + permissions to logic layer.""" assert demographics_gp.call_count == 1 query_params, permissions = demographics_gp.call_args.args assert query_params["query_type"] == "global_participant_id" assert query_params["global_participant_id"] == self.global_participant_id assert query_params["countries"] == [] assert query_params["store_ids"] == [] assert query_params["distributors"] == ["theorchard", "sme", "awal"] assert "permission_label_ids" in permissions def test_all_time_params_gp(self, response_gp_all_time, demographics_gp): """?days=0&start_date=HIGHWATERMARK resolves to start_date=ALL_TIME.""" assert demographics_gp.call_count == 1 query_params, _ = demographics_gp.call_args.args assert query_params["start_date"] == "ALL_TIME" assert query_params["end_date"] == datetime.date(2019, 1, 1) def test_all_time_params_isrc(self, response_isrc_all_time, demographics_isrc): """?days=0&start_date=HIGHWATERMARK resolves to start_date=ALL_TIME.""" assert demographics_isrc.call_count == 1 query_params, _ = demographics_isrc.call_args.args assert query_params["start_date"] == "ALL_TIME" assert query_params["end_date"] == datetime.date(2019, 1, 1) def handles_exceptions(self, client, demographics_exception, request_headers): """Test the exceptions route.""" error_response = client.get( config.DEMOGRAPHICS_PATH.replace("", self.isrc), headers=request_headers, ) assert error_response.code == 500 class TestStreamsByStore: """Test /sound-recording//streams-by-store endpoint.""" isrc = "GBUM71105426" mock_permissions = { "permission_label_ids": [7123], "permission_artist_ids": None, "permission_subaccount_ids": None, "permission_label_participant_ids": None, "permission_feed_ids": [1, 2, 38], } @pytest.fixture def streams_by_store(self): """Mock streams_by_store logic.""" with patch( "analytics.logic.streams_by_store.get_streams_by_store" ) as streams_by_store: streams_by_store.return_value = oto_response.Response([{"mock": "payload"}]) yield streams_by_store @pytest.fixture def mock_get_permission_values(self): """Mock get_permission_values.""" with patch( "analytics.handlers.get_permission_values", return_value=self.mock_permissions, ): yield @pytest.fixture def response( self, client, streams_by_store, mock_get_permission_values, request_headers ): """Return response from requesting endpoint.""" return client.get( config.STREAMS_BY_STORE_PATH.replace("", self.isrc), headers=request_headers, ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == [{"mock": "payload"}] def test_calls_logic_layer(self, response, streams_by_store): """Test that streams_by_store is called from handler.""" streams_by_store.assert_called_once_with( { "isrc": self.isrc, "country_ids": [], "store_ids": [], "start_date": None, "end_date": None, "distributors": ["theorchard", "sme", "awal"], "transfer_product_ownership_enabled": False, }, self.mock_permissions, ) class TestStreamsAll: """Test /sound-recording//streams-all endpoint.""" isrc = "GBUM71105426" mock_permissions = { "permission_label_ids": [7123], "permission_artist_ids": None, "permission_subaccount_ids": None, "permission_label_participant_ids": None, "permission_feed_ids": [1, 2, 38], } @pytest.fixture def streams_all(self): """Mock streams_all logic.""" with patch("analytics.logic.streams.get_streams_all") as streams_all: streams_all.return_value = oto_response.Response([{"mock": "payload"}]) yield streams_all @pytest.fixture def mock_get_permission_values(self): """Mock get_permission_values.""" with patch( "analytics.handlers.get_permission_values", return_value=self.mock_permissions, ): yield @pytest.fixture def response( self, client, streams_all, mock_get_permission_values, request_headers ): """Return response from requesting endpoint.""" return client.get( config.STREAMS_ALL_PATH.replace("", self.isrc), headers=request_headers, ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == [{"mock": "payload"}] def test_calls_logic_layer(self, response, streams_all): """Test that streams_all is called from handler.""" streams_all.assert_called_once_with( { "isrc": self.isrc, "country_ids": [], "store_ids": [], "start_date": None, "end_date": None, "distributors": ["theorchard", "sme", "awal"], "transfer_product_ownership_enabled": False, }, self.mock_permissions, ) class TestStreamsBulk: """Test /sound-recording/streams endpoint filtered by country.""" isrc = "GBUM71105426" countries = ["US", "NO", "SE"] store_ids = [] mock_permissions = { "permission_label_ids": [7123], "permission_artist_ids": None, "permission_subaccount_ids": None, "permission_label_participant_ids": None, "permission_feed_ids": [1, 2, 38], } @pytest.fixture def streams(self): """Mock get_streams.""" with patch("analytics.logic.streams_bulk.get_streams_bulk") as get_streams: get_streams.return_value = oto_response.Response([{"mock": "payload"}]) yield get_streams @pytest.fixture def mock_get_permission_values(self): """Mock get_permission_values.""" with patch( "analytics.handlers.get_permission_values", return_value=self.mock_permissions, ): yield @pytest.fixture def response(self, client, streams, mock_get_permission_values, request_headers): """Return response from requesting endpoint.""" query_string = urlencode( list(map(lambda id: ("country_code", id), self.countries)) ) return client.post( config.STREAMS_BULK_PATH + "?" + query_string, json={"isrcs": [self.isrc]}, headers=request_headers, ) def test_calls_logic_layer(self, response, streams): """Test that get_streams_bulk is called from handler.""" streams.assert_called_once_with( { "isrcs": [self.isrc], "country_ids": self.countries, "store_ids": [], "start_date": None, "end_date": None, "distributors": ["theorchard", "sme", "awal"], "transfer_product_ownership_enabled": False, }, self.mock_permissions, ) def test_returns_nothing_with_no_isrcs( self, client, streams, request_headers, request_context ): """Test that get_source_bulk will return nothing with no isrcs.""" response = client.post( config.STREAMS_BULK_PATH, json={"isrcs": []}, headers=request_headers ) assert json.loads(response.data.decode("utf-8")) == {} class TestAggregateStreams: """Test /sound-recording/aggregate-streams endpoint.""" isrc = "GBUM71105426" store_ids = [] mock_permissions = { "permission_label_ids": [7123], "permission_artist_ids": None, "permission_subaccount_ids": None, "permission_label_participant_ids": None, "permission_feed_ids": [1, 2, 38], } @pytest.fixture def aggregate_streams(self): """Mock get_aggregate_streams.""" with patch( "analytics.logic.aggregate_streams.get_aggregate_streams" ) as get_aggregate_streams: get_aggregate_streams.return_value = oto_response.Response( [{"mock": "payload"}] ) yield get_aggregate_streams @pytest.fixture def mock_get_permission_values(self): """Mock get_permission_values.""" with patch( "analytics.handlers.get_permission_values", return_value=self.mock_permissions, ): yield @pytest.fixture def response( self, client, aggregate_streams, mock_get_permission_values, request_headers ): """Return response from requesting endpoint.""" return client.post( config.AGGREGATE_STREAMS_PATH, json={"isrcs": [self.isrc]}, headers=request_headers, ) def test_calls_logic_layer(self, response, aggregate_streams): """Test that get_aggregate_streams is called from handler.""" aggregate_streams.assert_called_once_with( { "isrcs": [self.isrc], "country_ids": [], "store_ids": [], "distributors": ["theorchard", "sme", "awal"], "transfer_product_ownership_enabled": False, }, self.mock_permissions, ) def test_returns_nothing_with_no_isrcs( self, client, aggregate_streams, request_headers, request_context ): """Test get_aggregate_streams returns nothing with no isrcs.""" response = client.post( config.AGGREGATE_STREAMS_PATH, json={"isrcs": []}, headers=request_headers ) assert json.loads(response.data.decode("utf-8")) == {} class TestHighwatermark: """Test Highwatermark endpoint.""" @pytest.fixture def get_highwatermark(self): """Mock get_highwatermark.""" with patch( "analytics.logic.highwatermark.get_highwatermark" ) as get_highwatermark: get_highwatermark.return_value = oto_response.Response( [{"mock": "payload"}] ) yield get_highwatermark @pytest.fixture def response( self, client, get_highwatermark, insights_request_headers, request_context ): """Return response from requesting endpoint.""" query_string = "?type=streams" return client.get( config.HIGHWATERMARK_URL + query_string, headers=insights_request_headers ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == [{"mock": "payload"}] def test_calls_logic_layer( self, response, get_highwatermark, request_headers, request_context ): """Test get_highwatermark() call.""" get_highwatermark.assert_called_once_with("streams") def handles_exceptions(self, client, get_highwatermark, request_headers): """Test the exceptions route, when no types supplied.""" error_response = client.get(config.HIGHWATERMARK_URL, headers=request_headers) assert error_response.code == 500 class TestStores: """Test Stores endpoint.""" @pytest.fixture def mock_stores(self): return [ { "store_id": 286, "store_name": "Spotify", } ] @pytest.fixture def get_stores(self, mock_stores): with patch("analytics.logic.stores.get_stores") as get_stores: get_stores.return_value = mock_stores yield get_stores @pytest.fixture def response(self, client, get_stores, insights_request_headers, request_context): """Return response from requesting endpoint.""" url = config.STORES_URL json = {"store_ids": [286]} return client.post(url, json=json, headers=insights_request_headers) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload, mock_stores): """Test returns payload.""" assert payload == mock_stores def test_calls_logic_layer( self, response, get_stores, request_headers, request_context ): """Test get_stores() call.""" get_stores.assert_called_once() class TestStoreOutages: """Test StoreOutages endpoint.""" @pytest.fixture def mock_store_outages(self): """Mock store outages.""" return [ { "id": 1, "name": "Apple Music", "error": {"types": ["skips_saves"], "code": "unreliable"}, }, {"id": 4, "name": "Napster"}, ] @pytest.fixture def get_store_outages(self, mock_store_outages): """Mock get_highwatermark.""" with patch( "analytics.logic.stores.add_outage_error_to_stores" ) as get_store_outages: get_store_outages.return_value = mock_store_outages yield get_store_outages @pytest.fixture def response( self, client, get_store_outages, insights_request_headers, request_context ): """Return response from requesting endpoint.""" return client.get(config.STORE_OUTAGES_URL, headers=insights_request_headers) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload, mock_store_outages): """Test returns payload.""" assert payload == {"store_outages": mock_store_outages} def test_calls_logic_layer( self, response, get_store_outages, request_headers, request_context ): """Test get_store_outages() call.""" get_store_outages.assert_called_once() class TestGetProductAggregateStreams: """Test /product//aggregate-streams endpoint.""" product_id = "12345" @pytest.fixture def get_product_aggregate_streams(self): """Mock get_aggregate_streams.""" with patch( "analytics.logic.product.get_aggregate_streams" ) as get_aggregate_streams: get_aggregate_streams.return_value = oto_response.Response( {"mock": "payload"} ) yield get_aggregate_streams @pytest.fixture def response( self, client, get_product_aggregate_streams, insights_request_headers, mock_get_permission_values_for_products, ): """Return response from requesting endpoint.""" return client.get( config.PRODUCT_AGGREGATE_STREAMS_PATH.replace( "", self.product_id ), headers=insights_request_headers, ) @pytest.fixture def response_with_countries( self, client, get_product_aggregate_streams, insights_request_headers, mock_get_permission_values_for_products, ): """Return response from requesting endpoint with country_code param.""" return client.get( config.PRODUCT_AGGREGATE_STREAMS_PATH.replace( "", self.product_id ) + "?country_code=US", headers=insights_request_headers, ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == {"mock": "payload"} def test_calls_logic_layer( self, response, get_product_aggregate_streams, mock_get_permission_values_for_products, ): """Test get_product_aggregate_streams call.""" get_product_aggregate_streams.assert_called_once_with( { "product_id": self.product_id, "distributors": ["theorchard", "sme", "awal"], "multi_product": False, "country_ids": [], "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_products, ) def test_calls_logic_layer_with_countries( self, response_with_countries, get_product_aggregate_streams, mock_get_permission_values_for_products, ): """Test get_product_aggregate_streams call passes countries when provided.""" get_product_aggregate_streams.assert_called_once_with( { "product_id": self.product_id, "distributors": ["theorchard", "sme", "awal"], "multi_product": False, "country_ids": ["US"], "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_products, ) def test_normalises_country_codes( self, client, get_product_aggregate_streams, insights_request_headers, mock_get_permission_values_for_products, ): """Test country_code params are upper-cased, stripped, deduped, and sorted.""" client.get( config.PRODUCT_AGGREGATE_STREAMS_PATH.replace( "", self.product_id ) + "?country_code=us&country_code=GB&country_code=us&country_code=%20de%20", headers=insights_request_headers, ) get_product_aggregate_streams.assert_called_once_with( { "product_id": self.product_id, "distributors": ["theorchard", "sme", "awal"], "multi_product": False, "country_ids": ["DE", "GB", "US"], "transfer_product_ownership_enabled": False, }, mock_get_permission_values_for_products, ) class TestSoundRecordingAggregatedStreams: """Test /sound-recording//aggregated-streams endpoint.""" isrc = "USSM12209777" @pytest.fixture def get_aggregated_streams(self): """Mock get_sound_recording_aggregated_streams.""" with patch( "analytics.logic.streams.get_aggregated_streams" ) as get_aggregated_streams: get_aggregated_streams.return_value = {"mock": "payload"} yield get_aggregated_streams @pytest.fixture def response( self, client, get_aggregated_streams, insights_request_headers, request_context, ): """Return response from requesting endpoint.""" return client.get( config.SOUND_RECORDING_AGGREGATED_STREAMS_PATH.replace("", self.isrc) + "?dimension=SOS", headers=insights_request_headers, ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == {"mock": "payload"} def test_calls_logic_layer( self, response, get_aggregated_streams, request_headers, request_context, ): """Test get_product_aggregate_streams call.""" get_aggregated_streams.assert_called_once_with( { "isrc": "USSM12209777", "days_back": 28, "top_size": 5, "dimension": "SOS", "order_by": "streams7Days", "countries": [], "store_ids": [], "transfer_product_ownership_enabled": False, "line_soundcloud_collection_as_active_enabled": False, "published_max_available_date_enabled": False, }, { "permission_label_ids": None, "permission_artist_ids": None, "permission_subaccount_ids": None, "permission_label_participant_ids": None, "permission_feed_ids": [1, 2, 38], }, ) class TestGetGlobalFilters: """Test _get_global_filters function.""" def test_get_global_filters_start_date_all_time(self): """Test _get_global_filters with start_date of ALL_TIME.""" with app.test_request_context("/?start_date=ALL_TIME"): ( countries, store_ids, start_date, end_date, distributors, ) = _get_global_filters() assert countries == [] assert store_ids == [] assert start_date == "ALL_TIME" assert end_date is None assert distributors == ["theorchard", "sme", "awal"] def test_get_global_filters_start_date_end_date(self): """Test _get_global_filters with start_date and end_date.""" with app.test_request_context("/?start_date=2021-10-20&end_date=2021-10-25"): ( countries, store_ids, start_date, end_date, distributors, ) = _get_global_filters() assert countries == [] assert store_ids == [] assert ( start_date == datetime.datetime.strptime("2021-10-20", "%Y-%m-%d").date() ) assert ( end_date == datetime.datetime.strptime("2021-10-25", "%Y-%m-%d").date() ) assert distributors == ["theorchard", "sme", "awal"] def test_get_global_filters_start_date_days(self): """Test _get_global_filters with start_date and days.""" with app.test_request_context("/?start_date=2021-10-20&days=6"): ( countries, store_ids, start_date, end_date, distributors, ) = _get_global_filters() assert countries == [] assert store_ids == [] assert ( start_date == datetime.datetime.strptime("2021-10-20", "%Y-%m-%d").date() ) assert ( end_date == datetime.datetime.strptime("2021-10-25", "%Y-%m-%d").date() ) assert distributors == ["theorchard", "sme", "awal"] def test_demographics_2_handler(client, insights_request_headers, request_context): with patch("analytics.logic.demographics.get_demographics_2") as get_demographics_2: get_demographics_2.return_value = {"items": []} url = "/demographics?isrc=QM6MZ2214882" response = client.get(url, headers=insights_request_headers) assert response.status_code == 200 response_payload = json.loads(response.data.decode("utf-8")) assert response_payload == {"items": []} class TestMarketRanks: """Test /market-ranks endpoint handler.""" @pytest.fixture def get_market_ranks(self): """Mock market ranks logic.""" with patch( "analytics.handlers.market_ranks.get_market_ranks" ) as get_market_ranks: get_market_ranks.return_value = oto_response.Response({}) yield get_market_ranks @pytest.fixture def response( self, client, get_market_ranks, insights_request_headers, request_context_insights_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, owsrequest_get_insights_headers, ): """Return response from requesting endpoint.""" return client.get(config.MARKET_RANKS_PATH, headers=insights_request_headers) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == {} def test_calls_logic_layer( self, response, request_context_insights_profile, get_market_ranks ): """Test that get_top_sound_recordings is called from handler.""" get_market_ranks.assert_called_once_with( { "store_ids": [], "transfer_product_ownership_enabled": False, }, { "permission_label_ids": [], "permission_artist_ids": [], "permission_subaccount_ids": [], "permission_label_participant_ids": [], "permission_feed_ids": [1, 2, 38], }, ) def test_verify_profile_headers(self, response, owsrequest_verify_profile_headers): """Test that flask_request.verify_profile_headers is called.""" owsrequest_verify_profile_headers.assert_called_once() def test_get_profile_headers(self, response, owsrequest_get_insights_headers): """Test that flask_request.get_profile_headers is called.""" assert owsrequest_get_insights_headers.call_count == 1 class TestTadasTrends: """Test /tadas-trends endpoint handler.""" @pytest.fixture def get_tadas_trends(self): """Mock tadas trends logic.""" with patch("analytics.handlers.tadas.get_tadas_trends") as get_tadas_trends: get_tadas_trends.return_value = {"trends": [], "count": 0} yield get_tadas_trends @pytest.fixture def response( self, client, get_tadas_trends, insights_request_headers, request_context_insights_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, owsrequest_get_insights_headers, ): """Return response from requesting endpoint.""" return client.get(config.TADAS_TRENDS_PATH, headers=insights_request_headers) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert payload == {"trends": [], "count": 0} def test_calls_logic_layer( self, response, request_context_insights_profile, get_tadas_trends ): """Test that get_tadas_trends is called from handler.""" get_tadas_trends.assert_called_once_with( { "markets": [], "company_brand_uuids": [], "parent_company_uuids": [], "include_participants": [], "exclude_participants": [], "release_start_date": None, "release_end_date": None, "fin_label_parent_cds": [], "limit": 200, "offset": 0, "order_by": "TADAS_DAYS_TRENDING", "order_dir": "asc", }, { "permission_label_ids": [], "permission_artist_ids": [], "permission_subaccount_ids": [], "permission_label_participant_ids": [], "permission_feed_ids": [1, 2, 38], }, ) @pytest.fixture def response_with_fin_label_parent_cds( self, client, get_tadas_trends, insights_request_headers, request_context_insights_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, owsrequest_get_insights_headers, ): """Return response from requesting endpoint with fin_label_parent_cd values.""" url = ( f"{config.TADAS_TRENDS_PATH}" "?fin_label_parent_cd=AWAL&fin_label_parent_cd=_5020_RECORDS" ) return client.get(url, headers=insights_request_headers) def test_passes_fin_label_parent_cds_to_logic_layer( self, response_with_fin_label_parent_cds, get_tadas_trends ): """Test that fin_label_parent_cd query params flow to the logic layer.""" called_query_params = get_tadas_trends.call_args[0][0] assert called_query_params["fin_label_parent_cds"] == [ "AWAL", "_5020_RECORDS", ] def test_verify_profile_headers(self, response, owsrequest_verify_profile_headers): """Test that flask_request.verify_profile_headers is called.""" owsrequest_verify_profile_headers.assert_called_once() def test_get_profile_headers(self, response, owsrequest_get_insights_headers): """Test that flask_request.get_profile_headers is called.""" assert owsrequest_get_insights_headers.call_count == 1 def test_rejects_end_date_earlier_than_start_date( self, client, insights_request_headers, request_context_insights_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, owsrequest_get_insights_headers, ): """Test that end_date earlier than start_date returns an error.""" url = ( f"{config.TADAS_TRENDS_PATH}" "?release_start_date=2024-12-31" "&release_end_date=2024-01-01" ) response = client.get(url, headers=insights_request_headers) assert response.status_code == 500 payload = json.loads(response.data.decode("utf-8")) assert ( "release_end_date cannot be earlier than release_start_date" in payload.get("message", "") ) def test_rejects_invalid_start_date_format( self, client, insights_request_headers, request_context_insights_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, owsrequest_get_insights_headers, ): """Test that invalid release_start_date format returns an error.""" url = f"{config.TADAS_TRENDS_PATH}?release_start_date=invalid-date" response = client.get(url, headers=insights_request_headers) assert response.status_code == 500 payload = json.loads(response.data.decode("utf-8")) assert "Invalid release_start_date format" in payload.get("message", "") def test_rejects_invalid_end_date_format( self, client, insights_request_headers, request_context_insights_profile, owsrequest_verify_grass_headers, owsrequest_get_empty_grass_headers, owsrequest_verify_profile_headers, owsrequest_get_profile_headers, owsrequest_get_insights_headers, ): """Test that invalid release_end_date format returns an error.""" url = f"{config.TADAS_TRENDS_PATH}?release_end_date=invalid-date" response = client.get(url, headers=insights_request_headers) assert response.status_code == 500 payload = json.loads(response.data.decode("utf-8")) assert "Invalid release_end_date format" in payload.get("message", "") class TestTadasTrendGlobalSoundRecordingByISRC: """Test /tadas/trend-globalsoundrecording-by-isrc/ endpoint handler.""" @patch("analytics.handlers.tadas.get_tadas_trend_globalsoundrecording_by_isrc") def test_get_tadas_trend_by_isrc(self, mock_get_trend, client): """Test getting TADAS trend by ISRC.""" mock_response = {"trends": [{"market": "US", "days_trending": 5}]} mock_get_trend.return_value = mock_response isrc = "USRC11234567" markets = ["US", "GB"] response = client.get( f"/tadas/trend-globalsoundrecording-by-isrc/{isrc}?markets={markets[0]}&markets={markets[1]}" ) assert response.status_code == 200 assert response.json == mock_response expected_query_params = { "isrc": isrc, "markets": markets, } mock_get_trend.assert_called_once() actual_args = mock_get_trend.call_args[0] assert actual_args[0] == expected_query_params assert "permission_label_ids" in actual_args[1] @patch("analytics.handlers.tadas.get_tadas_trend_globalsoundrecording_by_isrc") def test_get_tadas_trend_by_isrc_no_markets(self, mock_get_trend, client): """Test getting TADAS trend by ISRC without specifying markets.""" # Setup mock_response = {"trends": [{"market": "US", "days_trending": 5}]} mock_get_trend.return_value = mock_response isrc = "USRC11234567" # Execute response = client.get(f"/tadas/trend-globalsoundrecording-by-isrc/{isrc}") # Assert assert response.status_code == 200 assert response.json == mock_response expected_query_params = { "isrc": isrc, "markets": [], } mock_get_trend.assert_called_once() actual_args = mock_get_trend.call_args[0] assert actual_args[0] == expected_query_params assert "permission_label_ids" in actual_args[1] class TestTADASDataAvailability: """Test /tadas/data-availability endpoint handler.""" @pytest.fixture def get_tadas_data_availability(self): """Mock tadas data availability logic.""" with patch( "analytics.handlers.tadas.get_tadas_data_availability" ) as get_tadas_data_availability: # Return realistic data that matches the actual implementation get_tadas_data_availability.return_value = { "by_country_tadas_last_available_date": "2023-09-01", "global_tadas_last_available_date": "2023-09-05", } yield get_tadas_data_availability @pytest.fixture def response( self, client, get_tadas_data_availability, insights_request_headers, ): """Return response from requesting endpoint.""" return client.get( config.TADAS_DATA_AVAILABILITY_PATH, headers=insights_request_headers ) @pytest.fixture def payload(self, response): """Return payload.""" return json.loads(response.data.decode("utf-8")) def test_succeeds(self, response): """Test successful response.""" assert response.status_code == 200 def test_returns_payload(self, payload): """Test returns payload.""" assert "by_country_tadas_last_available_date" in payload assert "global_tadas_last_available_date" in payload assert payload["by_country_tadas_last_available_date"] == "2023-09-01" assert payload["global_tadas_last_available_date"] == "2023-09-05" def test_calls_logic_layer(self, response, get_tadas_data_availability): """Test that get_tadas_data_availability is called from handler.""" # The actual implementation calls with empty dictionaries get_tadas_data_availability.assert_called_once_with({}, {}) class TestRepOwners: """Test /tadas/rep-owners endpoint handler.""" @patch("analytics.handlers.tadas.get_rep_owners") def test_get_rep_owners(self, mock_get_rep_owners, client): """Test getting the list of rep owners.""" mock_response = [ {"code": "AWAL", "name": "AWAL Recordings", "label_ids": [1009, 2020]}, {"code": "RED", "name": "RED Music", "label_ids": [3030]}, ] mock_get_rep_owners.return_value = mock_response response = client.get(config.REP_OWNERS_PATH) assert response.status_code == 200 assert response.json == mock_response mock_get_rep_owners.assert_called_once_with({}, {})