Skip to content

Commit 015b7a8

Browse files
author
Teun Zengerink
authored
Merge pull request #7 from usabilla/increase-test-coverage
Increase test coverage
2 parents ec7086e + d7f3df6 commit 015b7a8

4 files changed

Lines changed: 103 additions & 50 deletions

File tree

CHANGES.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ Changelog
33

44
Here you find a full list of changes.
55

6+
Version 1.2.2
7+
-------------
8+
9+
- Automatically deploy new versions to PyPI
10+
611
Version 1.2.1
712
-------------
813

9-
- Add usabilla to PyPi
14+
- Add usabilla to PyPI
1015

1116
Version 1.2.0
1217
-------------

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ requests==2.11.1
55
sh==1.11
66
six==1.10.0
77
urllib3==1.17
8+
mock==2.0.0

tests.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
"""Unit tests for the Usabilla API."""
2-
31
import sys
42
import unittest
53
import usabilla as ub
64

5+
from mock import Mock
76
from unittest import TestCase, main as unittest_main
87

98

@@ -40,6 +39,18 @@ def test_empty_credentials_exception(self):
4039
ub.Credentials(2, '')
4140

4241

42+
class TestGeneralError(TestCase):
43+
44+
def setUp(self):
45+
self.error = ub.GeneralError('type', 'message')
46+
47+
def test_str(self):
48+
self.assertEqual(str(self.error), 'type (message)')
49+
50+
def test_repr(self):
51+
self.assertEqual(repr(self.error), 'GeneralError(type=type)')
52+
53+
4354
class TestClient(TestCase):
4455

4556
def setUp(self):
@@ -75,5 +86,46 @@ def test_query_parameters(self):
7586
self.client.set_query_parameters(params)
7687
self.assertEqual(self.client.get_query_parameters(), 'limit=1&since=1235454')
7788

89+
def test_check_resource_validity(self):
90+
with self.assertRaises(ub.GeneralError):
91+
self.client.check_resource_validity('nonexisting', 'nonexisting', 'nonexisting')
92+
with self.assertRaises(ub.GeneralError):
93+
self.client.check_resource_validity('live', 'nonexisting', 'nonexisting')
94+
with self.assertRaises(ub.GeneralError):
95+
self.client.check_resource_validity('live', 'websites', 'nonexisting')
96+
self.assertEqual(self.client.check_resource_validity('live', 'websites', 'button'), '/live/websites/button')
97+
98+
def test_handle_id(self):
99+
url = '/live/websites/button/:id/feedback'
100+
with self.assertRaises(ub.GeneralError):
101+
self.client.handle_id(url, '')
102+
self.assertEqual(self.client.handle_id(url, '*'), '/live/websites/button/%2A/feedback')
103+
self.assertEqual(self.client.handle_id(url, 42), '/live/websites/button/42/feedback')
104+
105+
def test_item_iterator(self):
106+
items = ['one', 'two', 'three', 'four']
107+
has_more = {'hasMore': True, 'items': items[:2], 'lastTimestamp': 1400000000001}
108+
no_more = {'hasMore': False, 'items': items[2:], 'lastTimestamp': 1400000000002}
109+
self.client.set_query_parameters = Mock()
110+
self.client.send_signed_request = Mock(side_effect=[has_more, no_more])
111+
index = 0
112+
for item in self.client.item_iterator('/some/url'):
113+
self.assertEqual(item, items[index])
114+
index += 1
115+
self.client.set_query_parameters.assert_called_with({'since': 1400000000002})
116+
self.assertEqual(self.client.send_signed_request.call_count, 2)
117+
self.client.send_signed_request.side_effect = ub.GeneralError('mocked', 'error')
118+
for item in self.client.item_iterator('/some/url'):
119+
raise ub.GeneralError('should not', 'come here')
120+
121+
def test_get_resource(self):
122+
self.client.item_iterator = Mock()
123+
self.client.send_signed_request = Mock()
124+
self.client.get_resource('live', 'websites', 'feedback', 42)
125+
self.client.send_signed_request.assert_called_with('/live/websites/button/42/feedback')
126+
self.client.get_resource('live', 'websites', 'button', None, True)
127+
self.client.item_iterator.assert_called_with('/live/websites/button')
128+
129+
78130
if __name__ == '__main__':
79131
unittest_main()

usabilla.py

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"""Usabilla API Python Client."""
2-
31
# Copyright (c) 2016 Usabilla.com
42
#
53
# Permission is hereby granted, free of charge, to any person obtaining a
@@ -74,7 +72,7 @@ class APIClient(object):
7472
"""
7573

7674
resources = {
77-
'scopes' : {
75+
'scopes' : {
7876
'live': {
7977
'products': {
8078
'websites': {
@@ -104,23 +102,23 @@ class APIClient(object):
104102
}
105103
}
106104
}
107-
105+
108106
""" Scope constants """
109107
SCOPE_LIVE = 'live'
110-
108+
111109
""" Product contants """
112110
PRODUCT_WEBSITES = 'websites'
113111
PRODUCT_EMAIL = 'email'
114112
PRODUCT_APPS = 'apps'
115-
113+
116114
""" Resource contants """
117115
RESOURCE_FEEDBACK = 'feedback'
118116
RESOURCE_APP = 'app'
119117
RESOURCE_BUTTON = 'button'
120-
RESOURCE_CAMPAIGN = 'campaign'
118+
RESOURCE_CAMPAIGN = 'campaign'
121119
RESOURCE_CAMPAIGN_RESULT = 'campaign_result'
122120
RESOURCE_CAMPAIGN_STATS = 'campaign_stats'
123-
RESOURCE_INPAGE = 'inpage'
121+
RESOURCE_INPAGE = 'inpage'
124122
RESOURCE_INPAGE_RESULT = 'inpage_result'
125123

126124
method = 'GET'
@@ -148,7 +146,7 @@ def set_query_parameters(self, parameters):
148146
:param parameters: A `dict` representing the query parameters to be used for the request.
149147
:type parameters: dict
150148
"""
151-
149+
152150
self.query_parameters = urllib.urlencode(OrderedDict(sorted(parameters.items())))
153151

154152
def get_query_parameters(self):
@@ -169,7 +167,7 @@ def send_signed_request(self, scope):
169167
:type scope: str
170168
171169
:returns: A `dict` of the data.
172-
:rtype: dict
170+
:rtype: dict
173171
"""
174172
if self.credentials.client_key is None or self.credentials.secret_key is None:
175173
raise GeneralError('Invalid Access Key.', 'The Access Key supplied is invalid.')
@@ -194,7 +192,7 @@ def send_signed_request(self, scope):
194192

195193
# Create payload hash (hash of the request body content).
196194
payload_hash = hashlib.sha256(''.encode('utf-8')).hexdigest()
197-
195+
198196
# Combine elements to create canonical request.
199197
canonical_request = '{method}\n{uri}\n{query}\n{can_headers}\n{signed_headers}\n{hash}'.format(
200198
method=self.method,
@@ -234,52 +232,52 @@ def send_signed_request(self, scope):
234232
)
235233

236234
headers = {'date': usbldate, 'Authorization': authorization_header}
237-
235+
238236
# Send the request.
239-
request_url = self.host + scope + '?' + canonical_querystring
237+
request_url = self.host + scope + '?' + canonical_querystring
240238
r = requests.get(self.host_protocol + request_url, headers=headers)
241-
242-
239+
240+
243241
if r.status_code != 200:
244242
return r
245243
else:
246244
return r.json()
247-
248245

249-
def check_resource_validity(self,scope,product,resource):
250-
"""Checks whether the resource exists
251-
246+
247+
def check_resource_validity(self, scope, product, resource):
248+
"""Checks whether the resource exists
249+
252250
:param scope: A `string` that specifies the resource scope
253251
:param product: A `string` that specifies the product type
254252
:param resource: A `string` that specifies the resource type
255-
253+
256254
:type scope: str
257255
:type product: str
258256
:type resource: str
259-
257+
260258
:returns: An `string` that represents the resource request url
261259
:rtype: string
262260
"""
263261
if scope not in self.resources['scopes'].keys():
264262
raise GeneralError('invalid scope', 'Invalid scope name')
265263
if product not in self.resources['scopes'][scope]['products'].keys():
266-
raise GeneralError('invalid product', 'Invalid product name')
264+
raise GeneralError('invalid product', 'Invalid product name')
267265
if resource not in self.resources['scopes'][scope]['products'][product]['resources'].keys():
268-
raise GeneralError('invalid resource', 'Invalid resource name')
269-
266+
raise GeneralError('invalid resource', 'Invalid resource name')
267+
270268
url = '/' + scope + '/' + product + self.resources['scopes'][scope]['products'][product]['resources'][resource]
271-
269+
272270
return url
273-
274-
def handle_id(self,url,resource_id):
271+
272+
def handle_id(self, url, resource_id):
275273
"""Replaces the :id pattern in the url
276-
274+
277275
:param url: A `string` that specifies the resource request url
278276
:param resource_id: A `string` that specifies the resource id
279-
277+
280278
:type url: str
281279
:type resource_id: str
282-
280+
283281
:returns: An `string` that represents the resource request url
284282
:rtype: string
285283
"""
@@ -288,18 +286,18 @@ def handle_id(self,url,resource_id):
288286
raise GeneralError('invalid id', 'Invalid resource ID')
289287
if resource_id == '*':
290288
resource_id = '%2A'
291-
289+
292290
url = url.replace(':id', str(resource_id))
293-
291+
294292
return url
295-
293+
296294
def item_iterator(self, url):
297295
"""Get items using an iterator.
298-
296+
299297
:param url: A `string` that specifies the resource request url
300-
298+
301299
:type url: str
302-
300+
303301
:returns: An `generator` that yeilds the requested data.
304302
:rtype: generator
305303
"""
@@ -313,32 +311,29 @@ def item_iterator(self, url):
313311
self.set_query_parameters({'since': results['lastTimestamp']})
314312
except:
315313
return
316-
317-
314+
315+
318316
def get_resource(self, scope, product, resource, resource_id=None, iterate=False):
319317
"""Retrieves resources of the specified type
320-
318+
321319
:param scope: A `string` that specifies the resource scope
322320
:param product: A `string` that specifies the product type
323321
:param resource: A `string` that specifies the resource type
324322
:param resource_id: A `string` that specifies the resource id
325-
:param iterate: A `boolean` that specifies whether the you want to use an iterator
326-
323+
:param iterate: A `boolean` that specifies whether the you want to use an iterator
324+
327325
:type scope: str
328326
:type product: str
329327
:type resource: str
330328
:type resource_id: str
331-
:type iterate: bool
332-
329+
:type iterate: bool
330+
333331
:returns: An `generator` that yeilds the requested data or a single resource
334332
:rtype: generator or single resource
335333
"""
336334
url = self.handle_id(self.check_resource_validity(scope, product, resource), resource_id)
337-
335+
338336
if iterate:
339337
return self.item_iterator(url)
340338
else:
341339
return self.send_signed_request(url)
342-
343-
344-

0 commit comments

Comments
 (0)