"""Tests for availability.logic.countries module.""" import json import os from oto import response as oto_response from oto import status as oto_status import pytest import requests from availability import config from availability.connectors import redis from availability.constants import models from availability.constants import stores from availability.logic import countries from tests import utils ALL_COUNTRIES = ['foo', 'bar', 'baz'] LIVE_COUNTRIES = ['foo'] CARVEOUTS_COUNTRIES = ['bar'] def setup_function(): """Flush redis cache.""" redis.redis_client.flushall() def test_get_all_countries(): """Test that function returns all countries in correct format.""" all_countries = countries.get_all_countries() assert isinstance(all_countries, dict) for k, v in all_countries.items(): assert isinstance(k, int) assert isinstance(v, str) def test_get_ordered_countries_list(): """Test that function order countries in correct order.""" some_countries = {2: 'bar', 3: 'baz', 1: 'foo'} ordered_countries = countries.get_ordered_countries_list(some_countries) assert isinstance(ordered_countries, list) assert ordered_countries == ['foo', 'bar', 'baz'] def test_get_excluded_countries(mocker): """Assert can get excluded countries from ows-carveouts.""" carveouts_response = requests.Response() carveouts_response.status_code = 200 carveouts_response._content = json.dumps( {'1': 'US', '2': 'CA'}).encode('latin_1') owsrequest_mock = mocker.patch( 'availability.logic.countries.request.get', return_value=carveouts_response) response = countries.get_excluded_countries( upc='123456789012', correlation_id='test') owsrequest_mock.assert_called_with( 'ows-carveouts', '/carveout/123456789012/territory', headers={'Correlation-Id': 'test'}) assert isinstance(response, oto_response.Response) assert sorted(response.message) == sorted(['US', 'CA']) def test_get_excluded_countries_cache(mocker): """Assert can get excluded countries from cache.""" carveouts_response = requests.Response() carveouts_response.status_code = 200 carveouts_response._content = json.dumps( {'1': 'US', '2': 'CA'}).encode('latin_1') upc = '123456789012' uri = '/carveout/{}/territory'.format(upc) carv_countries = sorted(['US', 'CA']) cached_countries = sorted(['US', 'CA', 'NZ']) owsrequest_mock = mocker.patch( 'availability.logic.countries.request.get', return_value=carveouts_response) # Calling first time should populate the cache. response = countries.get_excluded_countries(upc=upc, correlation_id='test') owsrequest_mock.assert_called_with( 'ows-carveouts', uri, headers={'Correlation-Id': 'test'}) assert isinstance(response, oto_response.Response) assert response assert sorted(response.message) == carv_countries # Ensure the value is in the cache. assert sorted(json.loads(redis.redis_client.get(uri))) == carv_countries # Modify cached value. redis.redis_client.set(uri, value=json.dumps(cached_countries)) # Raise an exception if we try to hit the endpoint instead of the cache. # This would force get_excluded_countries() to return an error response. owsrequest_mock.side_effect = Exception('Boom!') # Check that we get modified value from the cache on subsequent call. response = countries.get_excluded_countries(upc=upc, correlation_id='test') assert isinstance(response, oto_response.Response) assert response assert sorted(response.message) == cached_countries def test_get_excluded_countries_cache_get_exception(mocker): """Assert can get excluded countries with exception on cache get().""" carveouts_response = requests.Response() carveouts_response.status_code = 200 carveouts_response._content = json.dumps( {'1': 'US', '2': 'CA'}).encode('latin_1') upc = '123456789012' uri = '/carveout/{}/territory'.format(upc) carv_countries = sorted(['US', 'CA']) cached_countries = sorted(['US', 'CA', 'NZ']) owsrequest_mock = mocker.patch( 'availability.logic.countries.request.get', return_value=carveouts_response) mocker.patch( 'availability.logic.countries.redis.redis_client.get', side_effect=Exception('Boom!')) # Modify cached value. redis.redis_client.set(uri, value=json.dumps(cached_countries)) # This call should bypass the cache. response = countries.get_excluded_countries(upc=upc, correlation_id='test') owsrequest_mock.assert_called_with( 'ows-carveouts', uri, headers={'Correlation-Id': 'test'}) # But the value should be updated in the cache. mocker.stopall() assert ( sorted(json.loads(redis.redis_client.get(uri))) == carv_countries) assert isinstance(response, oto_response.Response) assert response assert sorted(response.message) == carv_countries def test_get_excluded_countries_cache_set_exception(mocker): """Assert can get excluded countries with exception on cache set().""" carveouts_response = requests.Response() carveouts_response.status_code = 200 carveouts_response._content = json.dumps( {'1': 'US', '2': 'CA'}).encode('latin_1') upc = '123456789012' uri = '/carveout/{}/territory'.format(upc) cached_countries = sorted(['US', 'CA', 'NZ']) # We do not expect this to be called. mocker.patch( 'availability.logic.countries.request.get', side_effect=Exception('Should not be called')) # Set cached value before the request. redis.redis_client.set(uri, value=json.dumps(cached_countries)) mocker.patch( 'availability.logic.countries.redis.redis_client.set', side_effect=Exception('Boom!')) response = countries.get_excluded_countries(upc=upc, correlation_id='test') # The value should not be updated in the cache. assert ( sorted(json.loads(redis.redis_client.get(uri))) == cached_countries) assert isinstance(response, oto_response.Response) assert response assert sorted(response.message) == cached_countries def test_get_excluded_countries_handles_exception(mocker): """Assert exception caused by request is handled.""" mocker.patch( 'availability.logic.countries.request.get', side_effect=Exception) sentry_capture_exception_mock = mocker.patch( 'availability.logic.countries.capture_exception') response = countries.get_excluded_countries('any upc', 'any id') assert isinstance(response, oto_response.Response) assert response.status == oto_status.INTERNAL_ERROR assert sentry_capture_exception_mock.called def test_get_excluded_countries_handles_json_error(mocker): """Assert JSON error is handled.""" carveouts_response = requests.Response() carveouts_response.status_code = 200 carveouts_response._content = 'not json'.encode('latin_1') loggly_mock = mocker.patch('availability.logic.countries.logger.error') mocker.patch( 'availability.logic.countries.request.get', return_value=carveouts_response) response = countries.get_excluded_countries('any upc', 'any id') assert response.status == oto_status.INTERNAL_ERROR assert loggly_mock.called def test_get_excluded_countries_handles_wrong_response_structure(mocker): """Assert can determine if received not what expected.""" carveouts_response = requests.Response() carveouts_response.status_code = 200 carveouts_response._content = json.dumps( {'territory': {}, 'store': []}).encode('latin_1') loggly_mock = mocker.patch('availability.logic.countries.logger.error') mocker.patch( 'availability.logic.countries.request.get', return_value=carveouts_response) response = countries.get_excluded_countries( upc='123456789012', correlation_id='test') assert response.status == oto_status.INTERNAL_ERROR assert loggly_mock.called def test_get_excluded_countries_handles_error_response_from_carveouts(mocker): """Assert non-200 response from carveouts is handled.""" error_response_message = b'Error description here' carveouts_response = requests.Response() carveouts_response.status_code = oto_status.FORBIDDEN carveouts_response._content = error_response_message mocker.patch( 'availability.logic.countries.request.get', return_value=carveouts_response) response = countries.get_excluded_countries('anything', 'test') assert response.status == oto_status.FORBIDDEN assert response.errors['message'] == error_response_message.decode() @pytest.mark.parametrize( 'get_excluded_countries_raises, store_id, expect_error', ( (True, stores.STORE_ID_ITUNES, False), (True, stores.STORE_ID_SPOTIFY, True), (False, stores.STORE_ID_ITUNES, False), (False, stores.STORE_ID_SPOTIFY, False), ) ) def test_get_expected_countries( get_excluded_countries_raises, store_id, expect_error, mocker): """Test get_expected_countries works as expected.""" excluded_mock = mocker.patch( 'availability.logic.countries.get_excluded_countries') mocker.patch( 'availability.logic.countries.all_countries_ordered', ALL_COUNTRIES) if get_excluded_countries_raises: excluded_mock.return_value = oto_response.create_fatal_response('Boom') else: excluded_mock.return_value = oto_response.Response(CARVEOUTS_COUNTRIES) upc = 'upc string' correlation_id = 'correlation id string' result = countries.get_expected_countries( upc, models.COUNTRIES_SEPARATOR.join(LIVE_COUNTRIES), store_id, correlation_id) if store_id == stores.STORE_ID_SPOTIFY: excluded_mock.assert_called_once_with(upc, correlation_id) assert isinstance(result, oto_response.Response) if expect_error: assert not result.message assert result.errors else: assert result.message == utils.get_expected_countries( store_id=store_id, all_countries=ALL_COUNTRIES, carveouts_countries=CARVEOUTS_COUNTRIES, live_countries=LIVE_COUNTRIES, ) @pytest.mark.parametrize( 'invalid_content', [1, '1', False, True, '', [], {}, ()]) def test_get_excluded_countries_handles_empty_list_in_response( mocker, invalid_content): """VC-1376 Assert invalid content from ows-carveouts is handled.""" carveouts_response = requests.Response() carveouts_response.status_code = 200 carveouts_response._content = json.dumps(invalid_content).encode('latin_1') mocker.patch( 'availability.logic.countries.request.get', return_value=carveouts_response) response = countries.get_excluded_countries('test upc', 'test id') assert response.status == oto_status.OK assert response.message == [] def test_get_expected_countries_excludes_unlaunched_markets_for_spotify( mocker): """Assert Spotify not launched markets excluded from polling.""" not_launched_markets = countries.get_spotify_unlaunched_markets() mocker.patch( 'availability.logic.countries.get_excluded_countries', return_value=oto_response.Response([])) response = countries.get_expected_countries( '1', '', stores.STORE_ID_SPOTIFY, 'test_id') assert all( market not in not_launched_markets for market in response.message) def test_get_unlaunched_markets(): """Assert can get list of unlaunched maarkets for Spotify.""" markets_filepath = os.path.join( config.BASE_DIR, '..', 'spotify_unlaunched_markets.json') with open(markets_filepath) as f: expected_markets = list(json.loads(f.read()).values()) markets = countries.get_spotify_unlaunched_markets() assert markets == expected_markets