Skip to content

Commit eaa0b0c

Browse files
committed
[1.0.1] Fix single entity request after API changes
1 parent f5e1de2 commit eaa0b0c

File tree

6 files changed

+128
-22
lines changed

6 files changed

+128
-22
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to this project will be documented here.
44

5+
## [1.0.1] - 2019-10-14
6+
### Fixed
7+
- Correctly return entities when a single one is requested using a resource id
8+
reflecting changes in HelpScout's API responses.
9+
510
## [1.0.0] - 2019-09-18
611
### Changed
712
- HelpScoutEndpointRequester now returns subentities for methods named other

helpscout/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from helpscout.client import HelpScout # noqa
22

33

4-
__version__ = '1.0.0'
4+
__version__ = '1.0.1'

helpscout/client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
logger = logging.getLogger('HelpScout')
1919
EmbeddedKey = '_embedded'
20+
PageKey = 'page'
2021

2122

2223
class HelpScout:
@@ -169,9 +170,11 @@ def hit_(self, endpoint, method, resource_id=None, data=None, params=None):
169170
params = '&'.join('%s=%s' % (k, v) for k, v in params.items())
170171
url = '%s?%s' % (url, params)
171172
headers = self._authentication_headers()
173+
logger.debug('Request: %s %s' % (method, url))
172174
r = getattr(requests, method)(url, headers=headers, json=data)
173-
logger.debug('%s %s' % (method, url))
174175
ok, status_code = r.ok, r.status_code
176+
logger.debug(
177+
'Received: %s %s (%s - %s)' % (method, url, ok, status_code))
175178
if status_code in (201, 204):
176179
yield
177180
elif ok:
@@ -204,7 +207,7 @@ def _results_with_pagination(self, response, method):
204207
dict
205208
The dictionary response from help scout.
206209
"""
207-
if EmbeddedKey not in response:
210+
if EmbeddedKey not in response or PageKey not in response:
208211
yield response
209212
return
210213
if isinstance(response[EmbeddedKey], list):

helpscout/model.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ def cls(cls, entity_name, key):
6161
-------
6262
type: The object's class
6363
"""
64+
if '/' in entity_name:
65+
parts = entity_name.rsplit('/')
66+
entity_name = parts[-2] if len(parts) % 2 == 0 else parts[-1]
67+
if '/' in key:
68+
parts = key.rsplit('/')
69+
key = parts[-2] if len(parts) % 2 == 0 else parts[-1]
6470
plural_letters = (-2 if entity_name.endswith('es') else
6571
-1 if entity_name.endswith('s') else
6672
None)

tests/helpscout/test_client.py

Lines changed: 93 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def test_hit_no_access_token_ok(self):
8989
hs_path = 'helpscout.client.HelpScout.'
9090
hs = self._get_client()
9191
with patch('helpscout.client.requests') as requests, \
92-
patch('helpscout.client.logger') as logger, \
92+
patch('helpscout.client.logger'), \
9393
patch(hs_path + '_authenticate') as auth, \
9494
patch(hs_path + '_authentication_headers') as auth_headers, \
9595
patch(hs_path + '_results_with_pagination') as pages:
@@ -102,7 +102,6 @@ def test_hit_no_access_token_ok(self):
102102
# Asserts
103103
auth.assert_called_once()
104104
auth_headers.assert_called_once()
105-
logger.debug.assert_called_once_with(method + ' ' + full_url)
106105
requests.get.assert_called_once_with(
107106
full_url, headers=headers, json=None)
108107
response.json.assert_called_once()
@@ -123,11 +122,18 @@ def test_hit_ok(self):
123122
response = requests.get.return_value = MagicMock()
124123
response.ok = True
125124
response.json.return_value = json_response = {'a': 'b'}
125+
response.status_code = 200
126126
list(hs.hit_(endpoint, method))
127127
# Asserts
128128
auth.assert_not_called()
129129
auth_headers.assert_called_once()
130-
logger.debug.assert_called_once_with(method + ' ' + full_url)
130+
log_msg_body = method + ' ' + full_url
131+
self.assertEqual(
132+
logger.debug.call_args_list,
133+
[call('Request: ' + log_msg_body),
134+
call('Received: ' + log_msg_body + ' (True - 200)'),
135+
]
136+
)
131137
requests.get.assert_called_once_with(
132138
full_url, headers=headers, json=None)
133139
response.json.assert_called_once()
@@ -148,15 +154,23 @@ def test_hit_resource_id_ok(self):
148154
response = requests.get.return_value = MagicMock()
149155
response.ok = True
150156
response.json.return_value = json_response = {'a': 'b'}
151-
list(hs.hit_(endpoint, method, resource_id))
157+
response.status_code = 200
158+
ret = list(hs.hit_(endpoint, method, resource_id))
152159
# Asserts
153160
auth.assert_not_called()
154161
auth_headers.assert_called_once()
155-
logger.debug.assert_called_once_with(method + ' ' + full_url)
162+
log_msg_body = method + ' ' + full_url
163+
self.assertEqual(
164+
logger.debug.call_args_list,
165+
[call('Request: ' + log_msg_body),
166+
call('Received: ' + log_msg_body + ' (True - 200)'),
167+
]
168+
)
156169
requests.get.assert_called_once_with(
157170
full_url, headers=headers, json=None)
158171
response.json.assert_called_once()
159-
pages.assert_called_once_with(json_response, method)
172+
pages.assert_not_called()
173+
self.assertEqual(ret, [json_response])
160174

161175
def test_hit_params_dict_ok(self):
162176
params, params_str = {'embed': 'threads'}, '?embed=threads'
@@ -174,11 +188,18 @@ def test_hit_params_dict_ok(self):
174188
response = requests.get.return_value = MagicMock()
175189
response.ok = True
176190
response.json.return_value = json_response = {'a': 'b'}
191+
response.status_code = 200
177192
list(hs.hit_(endpoint, method, None, params=params))
178193
# Asserts
179194
auth.assert_not_called()
180195
auth_headers.assert_called_once()
181-
logger.debug.assert_called_once_with(method + ' ' + full_url)
196+
log_msg_body = method + ' ' + full_url
197+
self.assertEqual(
198+
logger.debug.call_args_list,
199+
[call('Request: ' + log_msg_body),
200+
call('Received: ' + log_msg_body + ' (True - 200)'),
201+
]
202+
)
182203
requests.get.assert_called_once_with(
183204
full_url, headers=headers, json=None)
184205
response.json.assert_called_once()
@@ -200,15 +221,23 @@ def test_hit_resource_id_with_params_dict_ok(self):
200221
response = requests.get.return_value = MagicMock()
201222
response.ok = True
202223
response.json.return_value = json_response = {'a': 'b'}
203-
list(hs.hit_(endpoint, method, resource_id, params=params))
224+
response.status_code = 200
225+
ret = list(hs.hit_(endpoint, method, resource_id, params=params))
204226
# Asserts
205227
auth.assert_not_called()
206228
auth_headers.assert_called_once()
207-
logger.debug.assert_called_once_with(method + ' ' + full_url)
229+
log_msg_body = method + ' ' + full_url
230+
self.assertEqual(
231+
logger.debug.call_args_list,
232+
[call('Request: ' + log_msg_body),
233+
call('Received: ' + log_msg_body + ' (True - 200)'),
234+
]
235+
)
208236
requests.get.assert_called_once_with(
209237
full_url, headers=headers, json=None)
210238
response.json.assert_called_once()
211-
pages.assert_called_once_with(json_response, method)
239+
pages.assert_not_called()
240+
self.assertEqual(ret, [json_response])
212241

213242
def test_hit_resource_id_with_params_str_ok(self):
214243
params_str = 'embed=threads'
@@ -227,15 +256,24 @@ def test_hit_resource_id_with_params_str_ok(self):
227256
response = requests.get.return_value = MagicMock()
228257
response.ok = True
229258
response.json.return_value = json_response = {'a': 'b'}
230-
list(hs.hit_(endpoint, method, resource_id, params=params_str))
259+
response.status_code = 200
260+
ret = list(
261+
hs.hit_(endpoint, method, resource_id, params=params_str))
231262
# Asserts
232263
auth.assert_not_called()
233264
auth_headers.assert_called_once()
234-
logger.debug.assert_called_once_with(method + ' ' + full_url)
265+
log_msg_body = method + ' ' + full_url
266+
self.assertEqual(
267+
logger.debug.call_args_list,
268+
[call('Request: ' + log_msg_body),
269+
call('Received: ' + log_msg_body + ' (True - 200)'),
270+
]
271+
)
235272
requests.get.assert_called_once_with(
236273
full_url, headers=headers, json=None)
237274
response.json.assert_called_once()
238-
pages.assert_called_once_with(json_response, method)
275+
pages.assert_not_called()
276+
self.assertEqual(ret, [json_response])
239277

240278
def test_hit_post_ok(self):
241279
endpoint, method = 'users', 'post'
@@ -257,7 +295,13 @@ def test_hit_post_ok(self):
257295
# Asserts
258296
auth.assert_not_called()
259297
auth_headers.assert_called_once()
260-
logger.debug.assert_called_once_with(method + ' ' + full_url)
298+
log_msg_body = method + ' ' + full_url
299+
self.assertEqual(
300+
logger.debug.call_args_list,
301+
[call('Request: ' + log_msg_body),
302+
call('Received: ' + log_msg_body + ' (True - 201)'),
303+
]
304+
)
261305
requests.post.assert_called_once_with(
262306
full_url, headers=headers, json=None)
263307
response.json.assert_not_called()
@@ -284,7 +328,13 @@ def test_hit_delete_ok(self):
284328
# Asserts
285329
auth.assert_not_called()
286330
auth_headers.assert_called_once()
287-
logger.debug.assert_called_once_with(method + ' ' + full_url)
331+
log_msg_body = method + ' ' + full_url
332+
self.assertEqual(
333+
logger.debug.call_args_list,
334+
[call('Request: ' + log_msg_body),
335+
call('Received: ' + log_msg_body + ' (True - 204)'),
336+
]
337+
)
288338
requests.delete.assert_called_once_with(
289339
full_url, headers=headers, json=None)
290340
response.json.assert_not_called()
@@ -311,7 +361,13 @@ def test_hit_patch_ok(self):
311361
# Asserts
312362
auth.assert_not_called()
313363
auth_headers.assert_called_once()
314-
logger.debug.assert_called_once_with(method + ' ' + full_url)
364+
log_msg_body = method + ' ' + full_url
365+
self.assertEqual(
366+
logger.debug.call_args_list,
367+
[call('Request: ' + log_msg_body),
368+
call('Received: ' + log_msg_body + ' (True - 204)'),
369+
]
370+
)
315371
requests.patch.assert_called_once_with(
316372
full_url, headers=headers, json=None)
317373
response.json.assert_not_called()
@@ -337,9 +393,15 @@ def test_hit_token_expired(self):
337393
list(hs.hit_(endpoint, method))
338394
# Asserts
339395
self.assertEqual(auth_headers.call_count, 2)
396+
log_msg_body = method + ' ' + full_url
340397
self.assertEqual(
341398
logger.debug.call_args_list,
342-
[call(method + ' ' + full_url) for _ in range(2)])
399+
[call('Request: ' + log_msg_body),
400+
call('Received: ' + log_msg_body + ' (False - 401)'),
401+
call('Request: ' + log_msg_body),
402+
call('Received: ' + log_msg_body + ' (True - 200)'),
403+
]
404+
)
343405
self.assertEqual(
344406
requests.get.call_args_list,
345407
[call(full_url, headers=headers, json=None) for _ in range(2)])
@@ -367,9 +429,15 @@ def test_hit_rate_limit_exceeded(self):
367429
list(hs.hit_(endpoint, method))
368430
# Asserts
369431
self.assertEqual(auth_headers.call_count, 2)
432+
log_msg_body = method + ' ' + full_url
370433
self.assertEqual(
371434
logger.debug.call_args_list,
372-
[call(method + ' ' + full_url) for _ in range(2)])
435+
[call('Request: ' + log_msg_body),
436+
call('Received: ' + log_msg_body + ' (False - 429)'),
437+
call('Request: ' + log_msg_body),
438+
call('Received: ' + log_msg_body + ' (True - 200)'),
439+
]
440+
)
373441
self.assertEqual(
374442
requests.get.call_args_list,
375443
[call(full_url, headers=headers, json=None) for _ in range(2)])
@@ -401,7 +469,13 @@ def test_hit_exception(self):
401469
list(hs.hit_(endpoint, method))
402470
# Asserts
403471
auth_headers.assert_called_once()
404-
logger.debug.assert_called_once_with(method + ' ' + full_url)
472+
log_msg_body = method + ' ' + full_url
473+
self.assertEqual(
474+
logger.debug.call_args_list,
475+
[call('Request: ' + log_msg_body),
476+
call('Received: ' + log_msg_body + ' (False - 500)'),
477+
]
478+
)
405479
requests.get.assert_called_once_with(
406480
full_url, headers=headers, json=None)
407481
response.json.assert_not_called()

tests/helpscout/test_model.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,24 @@ def test_class_superclass(self):
5656
cls = HelpScoutObject.cls('users', 'users')
5757
self.assertTrue(issubclass(cls, HelpScoutObject))
5858

59+
def test_cls_name_general_endpoint_top_level(self):
60+
cls = HelpScoutObject.cls('users', 'users')
61+
self.assertTrue(cls.__name__, 'users')
62+
63+
def test_cls_name_top_level_resource_id(self):
64+
cls = HelpScoutObject.cls('users/120', 'users/120')
65+
self.assertTrue(cls.__name__, 'users')
66+
67+
def test_cls_name_down_level_general_endpoint(self):
68+
endpoint = 'conversations/120/threads'
69+
cls = HelpScoutObject.cls(endpoint, endpoint)
70+
self.assertTrue(cls.__name__, 'threads')
71+
72+
def test_cls_name_down_level_resource_id(self):
73+
endpoint = 'conversations/120/threads/4'
74+
cls = HelpScoutObject.cls(endpoint, endpoint)
75+
self.assertTrue(cls.__name__, 'threads')
76+
5977
def test_str(self):
6078
cls = HelpScoutObject.cls('users', 'users')
6179
user = cls({'id': 12, 'name': 'Mike'})

0 commit comments

Comments
 (0)