diff --git a/changelly_api/api.py b/changelly_api/api.py index b156495..a4c7096 100644 --- a/changelly_api/api.py +++ b/changelly_api/api.py @@ -5,17 +5,18 @@ import requests +from changelly_api.conf import * from changelly_api.exceptions import * class ChangellyAPI: - def __init__(self, api_key, secret, url='https://api.changelly.com'): + def __init__(self, api_key, api_secret, url=API_ROOT_URL): self._api_key = api_key - self._secret = secret + self._api_secret = api_secret self._url = url def _generate_sign(self, json_data): - return hmac.new(self._secret.encode('utf-8'), json_data.encode('utf-8'), hashlib.sha512).hexdigest() + return hmac.new(self._api_secret.encode('utf-8'), json_data.encode('utf-8'), hashlib.sha512).hexdigest() def _parse_response(self, response): if response.status_code == 401: @@ -30,6 +31,15 @@ def _parse_response(self, response): raise JSONResponseParseError('Error parsing JSON response') if data.get('error'): + + if data['error']['code'] == INVALID_AMOUNT_ERROR_CODE: + value = float(data['error']['message'][34:36]) + + if 'maximal' in data['error']['message']: + raise AmountGreaterThanMaximum(value) + elif 'minimal' in data['error']['message']: + raise AmountLessThanMinimum(value) + raise ChangellyAPIError(data['error']) return data.get('result') @@ -44,10 +54,12 @@ def _make_request(self, method, params=None): serialized_data = json.dumps(message) - sign = hmac.new(self._secret.encode('utf-8'), serialized_data.encode('utf-8'), hashlib.sha512).hexdigest() - - headers = {'api-key': self._api_key, 'sign': sign, 'Content-type': 'application/json'} - response = requests.post(self._url, headers=headers, data=serialized_data) + headers = {'api-key': self._api_key, 'sign': self._generate_sign(serialized_data), + 'Content-type': 'application/json'} + try: + response = requests.post(self._url, headers=headers, data=serialized_data) + except requests.RequestException as error: + raise ChangellyAPIError(f'Unknown error occurred during request: {error}') return self._parse_response(response) diff --git a/changelly_api/conf.py b/changelly_api/conf.py new file mode 100644 index 0000000..facb789 --- /dev/null +++ b/changelly_api/conf.py @@ -0,0 +1,3 @@ +API_ROOT_URL = 'https://api.changelly.com' + +INVALID_AMOUNT_ERROR_CODE = -32600 diff --git a/changelly_api/exceptions.py b/changelly_api/exceptions.py index 197bc23..bb14791 100644 --- a/changelly_api/exceptions.py +++ b/changelly_api/exceptions.py @@ -2,6 +2,19 @@ class ChangellyAPIError(Exception): pass +class InvalidAmount(ChangellyAPIError): + def __init__(self, threshold_value: float): + self.threshold_value = threshold_value + + +class AmountGreaterThanMaximum(InvalidAmount): + pass + + +class AmountLessThanMinimum(InvalidAmount): + pass + + class JSONResponseParseError(ChangellyAPIError): pass diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5bb6b04 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,31 @@ +import pytest + +from changelly_api import ChangellyAPI + + +@pytest.fixture(scope='module') +def api(): + return ChangellyAPI('key', 'secret') + + +@pytest.fixture() +def get_fix_rate_for_amount_data(): + return { + 'response': { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": "test", + "from": "btc", + "to": "eth", + "result": "48.520938768637152700759353642", + "amountFrom": "1", + "amountTo": "48.520938768637152700" + } + }, + 'request': { + 'from': 'btc', + 'to': 'eth', + 'amountFrom': 1, + } + } diff --git a/tests/generate_sign_test.py b/tests/generate_sign_test.py index 9587765..95f897d 100644 --- a/tests/generate_sign_test.py +++ b/tests/generate_sign_test.py @@ -1,14 +1,5 @@ import json -import pytest - -from changelly_api import ChangellyAPI - - -@pytest.fixture(scope='module') -def api(): - return ChangellyAPI('key', 'secret') - def test_generate_sign(api): data = { diff --git a/tests/get_fix_rate_for_amount_test.py b/tests/get_fix_rate_for_amount_test.py new file mode 100644 index 0000000..1acf344 --- /dev/null +++ b/tests/get_fix_rate_for_amount_test.py @@ -0,0 +1,50 @@ +import pytest +import requests_mock + +from changelly_api.conf import API_ROOT_URL +from changelly_api.exceptions import AmountGreaterThanMaximum, AmountLessThanMinimum + + +@requests_mock.Mocker(kw='requests_mock') +def test(api, get_fix_rate_for_amount_data, **kwargs): + r_mock = kwargs['requests_mock'] + r_mock.post(API_ROOT_URL, json=get_fix_rate_for_amount_data['response']) + response = api.get_fix_rate_for_amount(get_fix_rate_for_amount_data['request']) + + assert response == get_fix_rate_for_amount_data['response']['result'] + + +@requests_mock.Mocker(kw='requests_mock') +def test_invalid_minimum_amount(api, get_fix_rate_for_amount_data, **kwargs): + minimum_amount = 10 + r_mock = kwargs['requests_mock'] + data = { + 'error': { + 'code': -32600, + 'message': f'invalid amount: minimal amount is {minimum_amount}' + } + } + r_mock.post(API_ROOT_URL, json=data) + + with pytest.raises(AmountLessThanMinimum) as error: + api.get_fix_rate_for_amount(get_fix_rate_for_amount_data['request']) + + assert error.value.threshold_value == minimum_amount + + +@requests_mock.Mocker(kw='requests_mock') +def test_invalid_maximum_amount(api, get_fix_rate_for_amount_data, **kwargs): + maximum_amount = 10 + r_mock = kwargs['requests_mock'] + response = { + 'error': { + 'code': -32600, + 'message': f'invalid amount: maximal amount is {maximum_amount}' + } + } + r_mock.post(API_ROOT_URL, json=response) + + with pytest.raises(AmountGreaterThanMaximum) as error: + api.get_fix_rate_for_amount(get_fix_rate_for_amount_data['request']) + + assert error.value.threshold_value == maximum_amount diff --git a/tests/requirements.txt b/tests/requirements.txt index 6665529..bbb162c 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,5 @@ pytest-cov codecov -pytest \ No newline at end of file +pytest +mock +requests_mock \ No newline at end of file