"""Unit tests for the top tracks logic layer.""" from decimal import Decimal from unittest.mock import patch from oto import response as oto_response import pytest from analytics.logic import top_tracks @pytest.fixture def mock_top_tracks_model_payload(): """Mock payload for top tracks model.""" return [ { 'isrc': 'USA561066580', 'track_name': 'Wild Eyes', 'artist_name': 'Local Natives', 'streams': 500000, 'growth_percentage': Decimal('16'), 'product_id': 238117, 'subaccount_names': 'Subaccount 1,Subaccount 2' }, { 'isrc': 'USJMZ1800003', 'track_name': 'The Day I Die', 'artist_name': 'ISLAND', 'streams': 400000, 'growth_percentage': Decimal('16.25'), 'product_id': 2127000, 'subaccount_names': None } ] @pytest.fixture def mock_get_image_locations_payload(): """Mock payload for image locations.""" return { '238117': 'https://imagelocation.com/238117.jpg', '2127000': 'https://imagelocation.com/2127000.jpg' } @pytest.fixture def expected_payload(): """Return expected payload from logic.""" return { 'items': [ { 'isrc': 'USA561066580', 'trackName': 'Wild Eyes', 'artistName': 'Local Natives', 'streams': 500000, 'imageLocation': 'https://imagelocation.com/238117.jpg', 'growthPercentage': 16, 'subaccountNames': 'Subaccount 1,Subaccount 2' }, { 'isrc': 'USJMZ1800003', 'trackName': 'The Day I Die', 'artistName': 'ISLAND', 'streams': 400000, 'imageLocation': 'https://imagelocation.com/2127000.jpg', 'growthPercentage': 16.25, 'subaccountNames': None } ] } @pytest.fixture def mock_get_top_tracks_model_success(mock_top_tracks_model_payload): """Mock successful get_top_tracks model response.""" with patch('analytics.models.top_tracks.get_top_tracks') as get_top_tracks: get_top_tracks.return_value = mock_top_tracks_model_payload yield get_top_tracks @pytest.fixture def mock_get_top_tracks_model_failure(): """Mock failure get_top_tracks model response.""" with patch('analytics.models.top_tracks.get_top_tracks') as get_top_tracks: get_top_tracks.return_value = [] yield get_top_tracks @pytest.fixture def mock_get_image_locations_success(mock_get_image_locations_payload): """Mock successful get_image_locations response.""" with patch( 'analytics.models.ows_assets.' 'get_image_locations') as get_image_locations: get_image_locations.return_value = oto_response.Response( mock_get_image_locations_payload) yield get_image_locations @pytest.fixture def mock_get_image_locations_failure(mock_get_image_locations_payload): """Mock failure get_image_locations response.""" with patch( 'analytics.models.ows_assets.' 'get_image_locations') as get_image_locations: get_image_locations.return_value = oto_response.Response(status=404) yield get_image_locations class TestGetTopTracksWithLabel: """Test get_top_tracks with label.""" account_type = 'vendor' account_id = 7123 limit = 10 subaccountid = None @pytest.fixture def top_tracks_response( self, mock_get_top_tracks_model_success, mock_get_image_locations_success): """Run get_top_tracks.""" return top_tracks.get_top_tracks( account_type=self.account_type, account_id=self.account_id, limit=self.limit) def test_succeeds( self, top_tracks_response, expected_payload): """Test successful response.""" assert top_tracks_response.status == 200 assert top_tracks_response.message == expected_payload def test_get_top_tracks_is_called( self, top_tracks_response, mock_get_top_tracks_model_success): """Test top tracks model is called with correct arguments.""" mock_get_top_tracks_model_success.assert_called_once_with( self.account_id, self.subaccountid, self.limit) def test_fails_when_top_tracks_model_fails( self, mock_get_top_tracks_model_failure, expected_payload): """Test failure response when top track model fails.""" response = top_tracks.get_top_tracks( account_type=self.account_type, account_id=self.account_id, limit=self.limit) assert response.status == 200 expected_payload['items'] = [] assert response.message == expected_payload def test_fails_when_get_image_locations_fails( self, mock_get_top_tracks_model_success, mock_get_image_locations_failure): """Test failure response when get image locations fails.""" response = top_tracks.get_top_tracks( account_type=self.account_type, account_id=self.account_id, limit=self.limit) assert response.status == 404 class TestGetTopTracksWithSubaccount: """Test get_top_tracks with subaccount.""" account_type = 'subaccount' account_id = 123 limit = 10 vendor_id = 7123 @pytest.fixture def mock_get_vendor(self): """Mock ows_account.get_vendor_id.""" with patch('analytics.models.ows_account.' 'get_vendor_id_by_subaccount_id') as get_vendor_id: get_vendor_id.return_value = oto_response.Response(self.vendor_id) yield get_vendor_id @pytest.fixture def top_tracks_response( self, mock_get_top_tracks_model_success, mock_get_image_locations_success, mock_get_vendor): """Run get_top_tracks.""" return top_tracks.get_top_tracks( account_type=self.account_type, account_id=self.account_id, limit=self.limit) def test_succeeds( self, top_tracks_response, expected_payload, mock_get_vendor): """Test successful response.""" assert top_tracks_response.status == 200 assert top_tracks_response.message == expected_payload def test_get_top_tracks_is_called( self, top_tracks_response, mock_get_top_tracks_model_success, mock_get_vendor): """Test top tracks model is called with correct arguments.""" mock_get_top_tracks_model_success.assert_called_once_with( self.vendor_id, self.account_id, self.limit) def test_fails_when_top_tracks_model_fails( self, mock_get_top_tracks_model_failure, expected_payload, mock_get_vendor): """Test failure response when top track model fails.""" response = top_tracks.get_top_tracks( account_type=self.account_type, account_id=self.account_id, limit=self.limit) assert response.status == 200 expected_payload['items'] = [] assert response.message == expected_payload def test_fails_when_get_image_locations_fails( self, mock_get_top_tracks_model_success, mock_get_image_locations_failure, mock_get_vendor): """Test failure response when get image locations fails.""" response = top_tracks.get_top_tracks( account_type=self.account_type, account_id=self.account_id, limit=self.limit) assert response.status == 404