"""CMS utility tests.""" from copy import deepcopy from datetime import date from datetime import datetime from datetime import timedelta from decimal import Decimal from unittest.mock import Mock from unittest.mock import patch from pytest import raises from api.exceptions import CMSAPIError from api.exceptions import CMSOAuthError from api.utils import cms @patch('api.utils.cms.requests') def test_client_default(requests): """Test default state and behavior or a client.""" client = cms.Client('test-id', 'test-secret', 'test-domain') expected_endpoint_oauth = 'https://test-domain/api/oauth2/get-token' assert client.endpoint_oauth == expected_endpoint_oauth assert client.token_is_expired @patch('api.utils.cms.requests') def test_client_token(requests, cms_api_token, cms_api_token_response): """Test client token lookup.""" requests.post.return_value = cms_api_token_response client = cms.Client('test-id', 'test-secret', 'test-domain') assert client.token == cms_api_token @patch('api.utils.cms.datetime') @patch('api.utils.cms.requests') def test_client_token_expiration(requests, mock_datetime): """Test token expiration and refreshes from a client.""" token_values = [ 'take this token to the test', 'tell the teller that this token ticked'] responses = [] for value in token_values: response = Mock() response.json.return_value = {'token_value': value} responses.append(response) requests.post.side_effect = responses now = datetime.now() mock_datetime.now.side_effect = [ # called on token update via .token property now, # called for .token_is_expired property check now + timedelta(hours=12), # called on token update via .token property now + timedelta(hours=24), # like above, also called on update, for new timestamp now] client = cms.Client('test-id', 'test-secret', 'test-domain') assert client.token_is_expired assert client.token == token_values[0] assert not client.token_is_expired assert client.token == token_values[1] @patch('api.utils.cms.requests') def test_client_token_error(requests): """Test token error.""" response = Mock() response.json.return_value = {'error': 'this is an error to test'} requests.post.return_value = response client = cms.Client('test-id', 'test-secret', 'test-domain') with raises(CMSOAuthError): client.update_token() @patch('api.utils.cms.requests') def test_client_oauth_headers(requests, cms_api_token, cms_api_token_response): """Test token oauth header generation.""" requests.post.return_value = cms_api_token_response client = cms.Client('test-id', 'test-secret', 'test-domain') expected = {'Authorization': 'Bearer ' + cms_api_token} assert client.oauth_headers == expected @patch('api.utils.cms.requests') def test_client_get_windows( requests, cms_api_token_response, windows, windows_converted): """Test client api request for window.""" requests.post.return_value = cms_api_token_response windows_response = Mock() windows_response.json.return_value = windows requests.get.return_value = windows_response client = cms.Client('test-id', 'test-secret', 'test-domain') result = client.get_windows('12345') assert list(result) == windows_converted @patch('api.utils.cms.requests') def test_client_get_windows_error( requests, cms_api_token_response, windows): """Test client api request with an error.""" requests.post.return_value = cms_api_token_response windows_response = Mock() windows_response.json.return_value = {'error': 'test for this'} requests.get.return_value = windows_response client = cms.Client('test-id', 'test-secret', 'test-domain') with raises(CMSAPIError): results = client.get_windows('12345') next(results) @patch('api.utils.cms.requests') def test_client_get_windows_html_error( requests, cms_api_token_response): """Test client api request with a 502 html error.""" requests.post.return_value = cms_api_token_response windows_response = Mock() windows_response.return_value = '502' requests.get.json.return_value = windows_response client = cms.Client('test-id', 'test-secret', 'test-domain') result = client.get_windows('12345') assert list(result) == [] def test_convert_window(windows, windows_converted): """Test type conversion function.""" for window_converted in windows_converted: label = window_converted['label'] window = windows[label.lower()] result = cms.convert_window(label, window) assert result == window_converted @patch('api.utils.cms.requests') def test_client_get_term_license( requests, cms_api_token_response, term_licenses, term_licenses_converted): """Test client api request for term licenses.""" requests.post.return_value = cms_api_token_response term_licenses_response = Mock() term_licenses_response.json.return_value = term_licenses requests.get.return_value = term_licenses_response client = cms.Client('test-id', 'test-secret', 'test-domain') result = client.get_term_licenses('12345') for i, lic in enumerate(result): assert lic == term_licenses_converted[i] @patch('api.utils.cms.requests') def test_client_get_term_license_error( requests, cms_api_token_response, term_licenses): """Test client api request with an error.""" requests.post.return_value = cms_api_token_response term_licenses_response = Mock() term_licenses_response.json.return_value = {'error': 'test for this'} requests.get.return_value = term_licenses_response client = cms.Client('test-id', 'test-secret', 'test-domain') with raises(CMSAPIError): results = client.get_term_licenses('12345') next(results) @patch('api.utils.cms.requests') def test_client_get_term_license_empty( requests, cms_api_token_response, term_licenses): """Test client api request with empty data.""" requests.post.return_value = cms_api_token_response term_licenses_response = Mock() term_licenses_response.json.return_value = 'that upc is not found' requests.get.return_value = term_licenses_response client = cms.Client('test-id', 'test-secret', 'test-domain') results = client.get_term_licenses('12345') assert list(results) == [] term_licenses_response = Mock() term_licenses_response.json.return_value = [] requests.get.return_value = term_licenses_response results = client.get_term_licenses('12345') assert list(results) == [] def test_convert_term_license(term_licenses, term_licenses_converted): """Test type conversion function.""" term_license = term_licenses[1] result = cms.convert_term_license(term_license) expected = term_licenses_converted[1] assert result == expected @patch('api.utils.cms.Client') def test__get_client(Client): """Test cms client singleton.""" old_client = deepcopy(cms._client) cms._client = None try: Client.return_value = 'test value' cli = cms.get_client() assert cli == 'test value' Client.return_value = 'new value' cli = cms.get_client() assert cli == 'test value' finally: cms._client = deepcopy(old_client) def test_calculate_payments_single(): """Test different single payment scenarios.""" # single payment start = date(2016, 1, 1) end = date(2016, 1, 2) amount = 100000 expected = {start: Decimal(amount)} results = cms.calculate_payments(start, end, amount, 'Annually') assert results == expected results = cms.calculate_payments(start, end, amount, 'Quarterly') assert results == expected results = cms.calculate_payments(start, end, amount, 'Other') assert results == expected start = date(2017, 1, 1) end = date(2018, 1, 1) results = cms.calculate_payments(start, end, amount, 'Annually') assert results == {date(2017, 1, 1): Decimal(amount)} # single payment even for same start and end dates start = date(2016, 1, 1) end = date(2016, 1, 2) results = cms.calculate_payments(start, end, amount, 'Annually') assert results == expected results = cms.calculate_payments(start, end, amount, 'Quarterly') assert results == expected results = cms.calculate_payments(start, end, amount, 'Other') assert results == expected start = date(2016, 1, 1) end = date(2017, 1, 1) amount = 100000 expected = {start: Decimal(amount)} results = cms.calculate_payments(start, end, amount, 'Annually') assert results == expected def test_calculate_payments_multiple_leapyear(): """Test payment calculations over leap years.""" start = date(2016, 1, 1) end = date(2017, 1, 1) amount = 40000 expected = { date(2016, 1, 1): 10000, date(2016, 4, 1): 10000, date(2016, 7, 1): 10000, date(2016, 10, 1): 10000} results = cms.calculate_payments(start, end, amount, 'Quarterly') assert results == expected results = cms.calculate_payments(start, end, amount, 'Other') assert results == expected def test_calculate_payments_no_interval(): """Intentional same day schedule indicating a single payment.""" start = date(2016, 1, 1) end = date(2016, 1, 1) amount = 100000 expected = {start: Decimal(amount)} results = cms.calculate_payments(start, end, amount, 'Annually') assert results == expected results = cms.calculate_payments(start, end, amount, 'Quarterly') assert results == expected results = cms.calculate_payments(start, end, amount, 'Other') assert results == expected def test_calculate_payments_in_full(): """Explicitly indicated single payment.""" start = date(2016, 1, 1) end = date(2016, 1, 1) amount = 100000.0 expected = {start: Decimal(amount)} results = cms.calculate_payments(start, end, amount, 'In Full') for i in results: assert isinstance(results[i], Decimal) assert results == expected def test_date_step_quarterly_or_other(): """Test date steps quarterly steps, including leap years.""" start = date(2016, 1, 1) end = date(2017, 1, 1) expected = [ date(2016, 1, 1), date(2016, 4, 1), date(2016, 7, 1), date(2016, 10, 1)] results = list(cms.date_steps(start, end, 'Quarterly')) assert results == expected results = list(cms.date_steps(start, end, 'Other')) assert results == expected # those edge cases where day is not consistent in months start = date(2015, 11, 29) end = date(2017, 1, 31) expected = [ date(2015, 11, 29), date(2016, 2, 29), date(2016, 5, 29), date(2016, 8, 29), date(2016, 11, 29)] results = list(cms.date_steps(start, end, 'Quarterly')) assert results == expected results = list(cms.date_steps(start, end, 'Other')) assert results == expected start = date(2016, 1, 30) end = date(2017, 1, 30) expected = [ date(2016, 1, 30), date(2016, 4, 30), date(2016, 7, 30), date(2016, 10, 30)] results = list(cms.date_steps(start, end, 'Quarterly')) assert results == expected results = list(cms.date_steps(start, end, 'Other')) assert results == expected start = date(2016, 1, 31) end = date(2017, 1, 31) expected = [ date(2016, 1, 31), date(2016, 5, 1), date(2016, 7, 31), date(2016, 10, 31)] results = list(cms.date_steps(start, end, 'Quarterly')) assert results == expected results = list(cms.date_steps(start, end, 'Other')) assert results == expected start = date(2014, 11, 29) end = date(2018, 1, 31) expected = [ date(2014, 11, 29), date(2015, 3, 1), date(2015, 5, 29), date(2015, 8, 29), date(2015, 11, 29), date(2016, 2, 29), date(2016, 5, 29), date(2016, 8, 29), date(2016, 11, 29), date(2017, 3, 1), date(2017, 5, 29), date(2017, 8, 29), date(2017, 11, 29)] results = list(cms.date_steps(start, end, 'Quarterly')) assert results == expected results = list(cms.date_steps(start, end, 'Other')) assert results == expected def test_date_step_monthly(): """Test date steps monthly steps, including leap years.""" start = date(2016, 1, 1) end = date(2017, 1, 1) results = list(cms.date_steps(start, end, 'Monthly')) expected = [ date(2016, 1, 1), date(2016, 2, 1), date(2016, 3, 1), date(2016, 4, 1), date(2016, 5, 1), date(2016, 6, 1), date(2016, 7, 1), date(2016, 8, 1), date(2016, 9, 1), date(2016, 10, 1), date(2016, 11, 1), date(2016, 12, 1)] assert results == expected # those edge cases where day is not consistent in months start = date(2016, 1, 29) end = date(2017, 1, 31) results = list(cms.date_steps(start, end, 'Monthly')) expected = [ date(2016, 1, 29), date(2016, 2, 29), date(2016, 3, 29), date(2016, 4, 29), date(2016, 5, 29), date(2016, 6, 29), date(2016, 7, 29), date(2016, 8, 29), date(2016, 9, 29), date(2016, 10, 29), date(2016, 11, 29), date(2016, 12, 29), date(2017, 1, 29)] assert results == expected start = date(2016, 1, 30) end = date(2016, 6, 30) results = list(cms.date_steps(start, end, 'Monthly')) expected = [ date(2016, 1, 30), date(2016, 3, 1), date(2016, 3, 30), date(2016, 4, 30), date(2016, 5, 30)] assert results == expected start = date(2016, 1, 31) end = date(2017, 1, 31) results = list(cms.date_steps(start, end, 'Monthly')) expected = [ date(2016, 1, 31), date(2016, 3, 1), date(2016, 3, 31), date(2016, 5, 1), date(2016, 5, 31), date(2016, 7, 1), date(2016, 7, 31), date(2016, 8, 31), date(2016, 10, 1), date(2016, 10, 31), date(2016, 12, 1), date(2016, 12, 31)] assert results == expected start = date(2016, 1, 29) end = date(2016, 9, 29) results = list(cms.date_steps(start, end, 'Monthly')) expected = [ date(2016, 1, 29), date(2016, 2, 29), date(2016, 3, 29), date(2016, 4, 29), date(2016, 5, 29), date(2016, 6, 29), date(2016, 7, 29), date(2016, 8, 29)] assert results == expected def test_date_steps_annually(): """Test date steps with many years including leap years.""" # non leap year start = date(2014, 1, 1) end = date(2015, 1, 1) results = list(cms.date_steps(start, end, 'Annually')) expected = [date(2014, 1, 1)] assert results == expected # some leap years start = date(2010, 1, 1) end = date(2017, 1, 1) results = list(cms.date_steps(start, end, 'Annually')) expected = [ date(2010, 1, 1), date(2011, 1, 1), date(2012, 1, 1), date(2013, 1, 1), date(2014, 1, 1), date(2015, 1, 1), date(2016, 1, 1)] assert results == expected start = date(2012, 2, 29) end = date(2018, 1, 1) results = list(cms.date_steps(start, end, 'Annually')) expected = [ date(2012, 2, 29), date(2013, 3, 1), date(2014, 3, 1), date(2015, 3, 1), date(2016, 2, 29), date(2017, 3, 1)] assert results == expected