Skip to content

Commit c71cf4d

Browse files
Merge pull request #2 from hyperwallet/master
Merge master
2 parents 5901b48 + 1fb46bd commit c71cf4d

9 files changed

Lines changed: 198 additions & 14 deletions

File tree

CHANGELOG.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
Changelog
22
=========
33

4-
1.1.4 (current)
4+
1.1.5 (current)
5+
------------------
6+
7+
- Added Client Token endpoint
8+
9+
1.1.4 (2018-12-04)
510
------------------
611

712
- Added PayPal account endpoint

hyperwallet/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
__email__ = 'devsupport@hyperwallet.com'
77
__copyright__ = 'Copyright (c) 2017 Hyperwallet'
88
__license__ = 'MIT'
9-
__version__ = '1.1.4'
9+
__version__ = '1.1.5'
1010
__url__ = 'https://github.com/hyperwallet/python-sdk'
1111
__download_url__ = 'https://pypi.python.org/pypi/hyperwallet-sdk'
1212
__description__ = 'A Python wrapper around the Hyperwallet API'
@@ -21,6 +21,7 @@
2121
PrepaidCard, # noqa
2222
PaperCheck, # noqa
2323
Transfer, # noqa
24+
ClientToken, # noqa
2425
PayPalAccount, # noqa
2526
Payment, # noqa
2627
Balance, # noqa

hyperwallet/api.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
PrepaidCard,
1515
PaperCheck,
1616
Transfer,
17+
ClientToken,
1718
PayPalAccount,
1819
Payment,
1920
Balance,
@@ -1654,6 +1655,32 @@ def listPayPalAccounts(self,
16541655

16551656
'''
16561657
1658+
ClientToken
1659+
1660+
'''
1661+
1662+
def getClientToken(self,
1663+
userToken=None):
1664+
'''
1665+
Get a ClientToken.
1666+
:param userToken:
1667+
A user token. **REQUIRED**
1668+
:returns:
1669+
A ClientToken.
1670+
'''
1671+
1672+
if not userToken:
1673+
raise HyperwalletException('userToken is required')
1674+
1675+
response = self.apiClient.doPost(
1676+
os.path.join('users', userToken, 'client-token'),
1677+
None
1678+
)
1679+
1680+
return ClientToken(response)
1681+
1682+
'''
1683+
16571684
Payments
16581685
https://portal.hyperwallet.com/docs/api/v3/resources/payments
16591686

hyperwallet/models.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,34 @@ def __repr__(self):
125125
)
126126

127127

128+
class ClientToken(HyperwalletModel):
129+
'''
130+
The ClientToken Model.
131+
132+
:param data:
133+
A dictionary containing the attributes for the Client Token.
134+
'''
135+
136+
def __init__(self, data):
137+
'''
138+
Create a new Client Token with the provided attributes.
139+
'''
140+
141+
super(ClientToken, self).__init__(data)
142+
143+
self.defaults = {
144+
'value': None,
145+
}
146+
147+
for (param, default) in self.defaults.items():
148+
setattr(self, param, data.get(param, default))
149+
150+
def __repr__(self):
151+
return "ClientToken({value})".format(
152+
value=self.value
153+
)
154+
155+
128156
class TransferMethod(HyperwalletModel):
129157
'''
130158
The TransferMethod Model.

hyperwallet/tests/test_api.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,30 @@ def test_list_paypal_accounts_success(self, mock_get):
13661366

13671367
'''
13681368
1369+
ClientToken
1370+
1371+
'''
1372+
1373+
def test_get_client_token_fail_need_user_token(self):
1374+
1375+
with self.assertRaises(HyperwalletException) as exc:
1376+
self.api.getClientToken()
1377+
1378+
self.assertEqual(exc.exception.message, 'userToken is required')
1379+
1380+
@mock.patch('hyperwallet.utils.ApiClient._makeRequest')
1381+
def test_get_client_token_success(self, mock_post):
1382+
1383+
client_token_data = {
1384+
'value': 'test-value'
1385+
}
1386+
mock_post.return_value = client_token_data
1387+
response = self.api.getClientToken('user-token')
1388+
1389+
self.assertTrue(response.value, client_token_data.get('value'))
1390+
1391+
'''
1392+
13691393
Payments
13701394
13711395
'''

hyperwallet/tests/test_client.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ def test_receive_non_json_response(self, session_mock):
4545

4646
session_mock.return_value = mock.MagicMock(
4747
status_code=404,
48-
content=data
48+
content=data,
49+
headers={
50+
"Content-Type": "application/json"
51+
}
4952
)
5053

5154
with self.assertRaises(HyperwalletAPIException) as exc:
@@ -62,13 +65,18 @@ def test_receive_valid_json_error_response(self, session_mock):
6265
data = {
6366
"errors": [{
6467
"message": "Houston, we have a problem",
65-
"code": "FORBIDDEN"
68+
"code": "FORBIDDEN",
69+
"relatedResources": ["trm-f3d38df1-adb7-4127-9858-e72ebe682a79",
70+
"trm-601b1401-4464-4f3f-97b3-09079ee7723b"]
6671
}]
6772
}
6873

6974
session_mock.return_value = mock.MagicMock(
7075
status_code=400,
71-
content=json.dumps(data)
76+
content=json.dumps(data),
77+
headers={
78+
"Content-Type": "application/json"
79+
}
7280
)
7381

7482
with self.assertRaises(HyperwalletAPIException) as exc:
@@ -79,6 +87,16 @@ def test_receive_valid_json_error_response(self, session_mock):
7987
'FORBIDDEN'
8088
)
8189

90+
self.assertEqual(
91+
exc.exception.message.get('errors')[0].get('relatedResources')[0],
92+
'trm-f3d38df1-adb7-4127-9858-e72ebe682a79'
93+
)
94+
95+
self.assertEqual(
96+
exc.exception.message.get('errors')[0].get('relatedResources')[1],
97+
'trm-601b1401-4464-4f3f-97b3-09079ee7723b'
98+
)
99+
82100
@mock.patch('requests.Session.request')
83101
def test_receive_valid_json_response(self, session_mock):
84102

@@ -88,7 +106,10 @@ def test_receive_valid_json_response(self, session_mock):
88106

89107
session_mock.return_value = mock.MagicMock(
90108
status_code=200,
91-
content=json.dumps(data)
109+
content=json.dumps(data),
110+
headers={
111+
"Content-Type": "application/json"
112+
}
92113
)
93114

94115
encoded = json.dumps(data)
@@ -100,6 +121,29 @@ def test_receive_valid_json_response(self, session_mock):
100121
json.loads(encoded)
101122
)
102123

124+
@mock.patch('requests.Session.request')
125+
def test_receive_json_error_response_when_content_type_is_not_valid(self, session_mock):
126+
127+
data = {
128+
'key': 'value'
129+
}
130+
131+
session_mock.return_value = mock.MagicMock(
132+
status_code=200,
133+
content=json.dumps(data),
134+
headers={
135+
"Content-Type": "wrongContentType"
136+
}
137+
)
138+
139+
with self.assertRaises(HyperwalletAPIException) as exc:
140+
self.client._makeRequest()
141+
142+
self.assertEqual(
143+
exc.exception.message,
144+
'Invalid Content-Type specified in Response Header'
145+
)
146+
103147

104148
if __name__ == '__main__':
105149
unittest.main()

hyperwallet/tests/test_encryption.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
from jwcrypto.common import json_encode
1010
from hyperwallet.exceptions import HyperwalletException
1111
from hyperwallet.utils.encryption import Encryption
12-
from django.core.validators import URLValidator
13-
from django.core.exceptions import ValidationError
12+
from six.moves.urllib.parse import urlparse
1413

1514

1615
class EncryptionTest(unittest.TestCase):
@@ -158,19 +157,37 @@ def test_should_throw_exception_when_jws_signature_has_expired(self):
158157
self.assertEqual(exc.exception.message, 'JWS signature has expired, checked by [exp] JWS header')
159158

160159
def __getJwkKeySet(self, location):
161-
160+
'''
161+
Retrieves JWK key data from given location.
162+
163+
:param location:
164+
Location(can be a URL or path to file) of JWK key data. **REQUIRED**
165+
:returns:
166+
JWK key set found at given location.
167+
'''
162168
try:
163-
URLValidator()(location)
164-
except ValidationError:
169+
url = urlparse(location)
170+
if url.scheme and url.netloc and url.path:
171+
return requests.get(location).text
172+
raise HyperwalletException('Failed to parse url from string = ' + location)
173+
except Exception as e:
165174
if os.path.isfile(location):
166175
with open(location) as f:
167176
return f.read()
168177
else:
169178
raise HyperwalletException('Wrong JWK key set location path = ' + location)
170179

171-
return requests.get(location).text
172-
173180
def __findJwkKeyByAlgorithm(self, jwkKeySet, algorithm):
181+
'''
182+
Finds JWK key by given algorithm.
183+
184+
:param jwkKeySet:
185+
JSON representation of JWK key set. **REQUIRED**
186+
:param algorithm:
187+
Algorithm of the JWK key to be found in key set. **REQUIRED**
188+
:returns:
189+
JWK key with given algorithm.
190+
'''
174191

175192
try:
176193
keySet = json.loads(jwkKeySet)

hyperwallet/tests/test_models.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
PrepaidCard,
1313
PaperCheck,
1414
Transfer,
15+
ClientToken,
1516
PayPalAccount,
1617
Payment,
1718
Balance,
@@ -73,6 +74,10 @@ def setUp(self):
7374
'createdOn': '2017-01-01'
7475
}
7576

77+
self.client_token_data = {
78+
'value': 'test-client-token'
79+
}
80+
7681
self.transfer_method_data = {
7782
'token': 'trm-12345',
7883
'createdOn': '2017-01-01'
@@ -243,6 +248,23 @@ def test_transfer_model(self):
243248

244249
'''
245250
251+
ClientToken
252+
253+
'''
254+
255+
def test_client_token_model(self):
256+
257+
test_client_token = ClientToken(self.client_token_data)
258+
259+
self.assertEqual(
260+
test_client_token.__repr__(),
261+
'ClientToken({value})'.format(
262+
value=self.client_token_data.get('value')
263+
)
264+
)
265+
266+
'''
267+
246268
PayPal Account
247269
248270
'''

hyperwallet/utils/apiclient.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def __init__(self, username, password, server, encryptionData=None):
4141
# Hyperwallet SDK.
4242
self.baseHeaders = {
4343
'User-Agent': 'Hyperwallet Python SDK v{}'.format(__version__),
44-
'Accept': 'application/json',
44+
'Accept': 'application/jose+json' if self.encrypted else 'application/json',
4545
'Content-Type': 'application/jose+json' if self.encrypted else 'application/json'
4646
}
4747

@@ -113,6 +113,8 @@ def _makeRequest(self,
113113
if response.status_code is 204:
114114
return {}
115115

116+
self.__checkResponseHeaderContentType(response)
117+
116118
content = response.content
117119
if hasattr(content, 'decode'): # Python 2
118120
content = content.decode('utf-8')
@@ -193,6 +195,20 @@ def doPut(self, partialUrl, data):
193195
data=json.dumps(data).encode('utf-8')
194196
)
195197

198+
def __checkResponseHeaderContentType(self, response):
199+
'''
200+
Check response header Content-Type.
201+
202+
:param response:
203+
Response to be checked. **REQUIRED**
204+
'''
205+
206+
if response is None:
207+
return
208+
contentType = response.headers['Content-Type']
209+
if (not self.encrypted and contentType != 'application/json') or (self.encrypted and contentType != 'application/jose+json'):
210+
raise HyperwalletAPIException('Invalid Content-Type specified in Response Header')
211+
196212
def __getRequestData(self, data):
197213
'''
198214
If encryption is enabled try to encrypt request data, otherwise no action required.

0 commit comments

Comments
 (0)