Skip to content

Commit 8f8b662

Browse files
committed
Merge pull request #13 from dpoirier/add-tests
Fix issue #6.
2 parents de131c2 + f45980f commit 8f8b662

4 files changed

Lines changed: 298 additions & 2 deletions

File tree

README.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,21 @@ value in a `BASKET_URL` environment variable. The default is
5353

5454
.. _Django: https://www.djangoproject.com/
5555

56+
Tests
57+
=====
58+
59+
To run tests::
60+
61+
python setup.py test
62+
5663
Change Log
5764
==========
5865

66+
v0.3.5
67+
------
68+
69+
* Add tests
70+
5971
v0.3.4
6072
------
6173

basket/base.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import json
22
import os
33

4+
# Use Django settings for BASKET_URL if available, but fall back to
5+
# env var or default if not. This lets us test without all the setup
6+
# that we'd otherwise need for Django.
47
try:
58
from django.conf import settings
69
except (ImportError, AttributeError):
10+
# Django not installed
711
settings = None
12+
else:
13+
if not settings.configured:
14+
# Django installed but not initialized
15+
settings = None
816

917
import requests
1018

@@ -25,6 +33,7 @@ def basket_url(method, token=None):
2533

2634
return ('%s/news/%s/%s' % (BASKET_URL, method, token))
2735

36+
2837
def parse_response(res):
2938
"""Parse the result of a basket API call, raise exception on error"""
3039

@@ -123,6 +132,7 @@ def debug_user(email, supertoken):
123132
params={'email': email,
124133
'supertoken': supertoken})
125134

135+
126136
def get_newsletters():
127137
"""Returns data about the newsletters that basket knows about.
128138
Format is a list of dictionaries.

basket/tests.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
import json
2+
from unittest import TestCase
3+
4+
from requests.exceptions import ConnectionError, Timeout
5+
from mock import Mock, patch
6+
7+
from basket import (BasketException, debug_user, get_newsletters, request,
8+
subscribe, unsubscribe, update_user, user)
9+
from basket.base import basket_url, parse_response
10+
11+
12+
# Warning: there are two request() methods, one in basket-client and
13+
# one in the requests library. Pay attention, it's very confusing.
14+
15+
class TestBasketClient(TestCase):
16+
17+
def test_basket_url_no_token(self):
18+
"""Form basket URL properly when no token used"""
19+
with patch('basket.base.BASKET_URL', new="BASKET_URL"):
20+
result = basket_url("METHOD")
21+
self.assertEqual("BASKET_URL/news/METHOD/", result)
22+
23+
def test_basket_url_with_token(self):
24+
"""Form basket URL properly when token used"""
25+
with patch('basket.base.BASKET_URL', new="BASKET_URL"):
26+
result = basket_url("METHOD", "TOKEN")
27+
self.assertEqual("BASKET_URL/news/METHOD/TOKEN/", result)
28+
29+
def test_response_not_200(self):
30+
"""parse_response() raises exception on non-200 status code"""
31+
res = Mock(status_code=666)
32+
with self.assertRaises(BasketException):
33+
parse_response(res)
34+
35+
def test_response_error(self):
36+
"""parse_response() raises exception on status=error"""
37+
content = json.dumps({'status': 'error', 'desc': 'ERROR'})
38+
res = Mock(status_code=200, content=content)
39+
with self.assertRaises(BasketException):
40+
parse_response(res)
41+
42+
def test_response_content(self):
43+
"""parse_response() returns parsed response content if no error"""
44+
data = {u'status': u'ok', u'foo': u'bar'}
45+
content = json.dumps(data)
46+
res = Mock(status_code=200, content=content)
47+
result = parse_response(res)
48+
self.assertEqual(data, result)
49+
50+
def test_request(self):
51+
"""
52+
request() calls requests.request() with the expected parms
53+
if everything is normal, and returns the expected result.
54+
"""
55+
response_data = {u'status': u'ok', u'foo': u'bar'}
56+
action, method, token = "ACTION", "METHOD", "TOKEN"
57+
url = basket_url(action, token)
58+
with patch('basket.base.requests.request', autospec=True) \
59+
as request_call:
60+
request_call.return_value = Mock(status_code=200,
61+
content=json.dumps(response_data))
62+
result = request(method, action, data="DATA",
63+
token=token, params="PARAMS")
64+
65+
request_call.assert_called_with(method, url, data="DATA",
66+
params="PARAMS", timeout=10)
67+
self.assertEqual(response_data, result)
68+
69+
def test_request_newsletters_string(self):
70+
"""
71+
If request is passed newsletters as a string, newsletters is passed
72+
along unaltered.
73+
"""
74+
input_data = {'newsletters': 'one,two'}
75+
action, method, token = "ACTION", "METHOD", "TOKEN"
76+
url = basket_url(action, token)
77+
content = {'one': 100, 'two': 200}
78+
with patch('basket.base.requests.request', autospec=True) \
79+
as request_call:
80+
request_call.return_value = Mock(status_code=200,
81+
content=json.dumps(content))
82+
result = request(method, action, data=input_data,
83+
token=token, params="PARAMS")
84+
85+
request_call.assert_called_with(method, url, data=input_data,
86+
params="PARAMS", timeout=10)
87+
self.assertEqual(content, result)
88+
89+
def test_request_newsletters_non_string(self):
90+
"""
91+
If request is passed newsletters as a non-string, newsletters is
92+
converted to a comma-separated string
93+
"""
94+
response_data = {u'status': u'ok', u'foo': u'bar'}
95+
input_data = {'newsletters': ['one', 'two'], 'thing': 1}
96+
expected_input_data = input_data.copy()
97+
newsletters = ','.join(input_data['newsletters'])
98+
expected_input_data['newsletters'] = newsletters
99+
action, method, token = "ACTION", "METHOD", "TOKEN"
100+
url = basket_url(action, token)
101+
with patch('basket.base.requests.request', autospec=True) \
102+
as request_call:
103+
request_call.return_value = Mock(status_code=200,
104+
content=json.dumps(response_data))
105+
result = request(method, action, data=input_data,
106+
token=token, params="PARAMS")
107+
108+
request_call.assert_called_with(method, url, data=expected_input_data,
109+
params="PARAMS", timeout=10)
110+
self.assertEqual(response_data, result)
111+
112+
def test_request_conn_error(self):
113+
"""
114+
If requests throws a ConnectionError, it's converted to
115+
a BasketException
116+
"""
117+
input_data = {'newsletters': ['one', 'two'], 'thing': 1}
118+
expected_input_data = input_data.copy()
119+
newsletters = ','.join(input_data['newsletters'])
120+
expected_input_data['newsletters'] = newsletters
121+
action, method, token = "ACTION", "METHOD", "TOKEN"
122+
with patch('basket.base.requests.request', autospec=True) \
123+
as request_call:
124+
request_call.side_effect = ConnectionError
125+
with self.assertRaises(BasketException):
126+
request(method, action, data=input_data,
127+
token=token, params="PARAMS")
128+
129+
def test_request_timeout(self):
130+
"""
131+
If requests times out, it's converted to
132+
a BasketException
133+
"""
134+
input_data = {'newsletters': ['one', 'two'], 'thing': 1}
135+
expected_input_data = input_data.copy()
136+
newsletters = ','.join(input_data['newsletters'])
137+
expected_input_data['newsletters'] = newsletters
138+
action, method, token = "ACTION", "METHOD", "TOKEN"
139+
with patch('basket.base.requests.request', autospec=True) \
140+
as request_call:
141+
request_call.side_effect = Timeout
142+
with self.assertRaises(BasketException):
143+
request(method, action, data=input_data,
144+
token=token, params="PARAMS")
145+
146+
def test_subscribe(self):
147+
"""
148+
subscribe calls request with the expected parms and returns the result
149+
"""
150+
email = "user1@example.com"
151+
newsletters = ['news1', 'news2']
152+
kwargs = {
153+
'arg1': 100,
154+
'arg2': 200,
155+
}
156+
expected_kwargs = kwargs.copy()
157+
expected_kwargs['email'] = email
158+
expected_kwargs['newsletters'] = newsletters
159+
with patch('basket.base.request', autospec=True) as request_call:
160+
request_call.return_value = Mock()
161+
result = subscribe(email, newsletters, **kwargs)
162+
163+
request_call.assert_called_with('post', 'subscribe',
164+
data=expected_kwargs)
165+
self.assertEqual(request_call.return_value, result)
166+
167+
def test_unsubscribe(self):
168+
"""
169+
unsubscribe calls request with the expected parms, returns the result
170+
"""
171+
email = "user1@example.com"
172+
newsletters = ['news1', 'news2']
173+
token = "TOKEN"
174+
optout = False
175+
expected_data = {
176+
'email': email,
177+
'newsletters': newsletters,
178+
}
179+
with patch('basket.base.request', autospec=True) as request_call:
180+
request_call.return_value = Mock()
181+
result = unsubscribe(token, email, newsletters, optout)
182+
183+
request_call.assert_called_with('post', 'unsubscribe',
184+
data=expected_data,
185+
token=token)
186+
self.assertEqual(request_call.return_value, result)
187+
188+
def test_unsubscribe_optout(self):
189+
"""
190+
unsubscribe calls request with the expected parms and returns the
191+
result. optout is passed if true, instead of newsletters.
192+
"""
193+
email = "user1@example.com"
194+
newsletters = ['news1', 'news2']
195+
token = "TOKEN"
196+
optout = True
197+
expected_data = {
198+
'email': email,
199+
'optout': 'Y'
200+
}
201+
with patch('basket.base.request', autospec=True) as request_call:
202+
request_call.return_value = Mock()
203+
result = unsubscribe(token, email, newsletters, optout)
204+
205+
request_call.assert_called_with('post', 'unsubscribe',
206+
data=expected_data,
207+
token=token)
208+
self.assertEqual(request_call.return_value, result)
209+
210+
def test_unsubscribe_bad_args(self):
211+
"""
212+
unsubscribe must be passed newsletters or optout, or it raises
213+
BasketException
214+
"""
215+
email = "user1@example.com"
216+
newsletters = None
217+
token = "TOKEN"
218+
optout = None
219+
with patch('basket.base.request', autospec=True):
220+
with self.assertRaises(BasketException):
221+
unsubscribe(token, email, newsletters, optout)
222+
223+
def test_user(self):
224+
"""
225+
user passes the expected args to request() and returns the result.
226+
"""
227+
token = "TOKEN"
228+
with patch('basket.base.request', autospec=True) as request_call:
229+
request_call.return_value = Mock()
230+
result = user(token)
231+
request_call.assert_called_with('get', 'user', token=token)
232+
self.assertEqual(request_call.return_value, result)
233+
234+
def test_update_user(self):
235+
"""
236+
update_user passes the expected args to request(), returns the result.
237+
"""
238+
token = "TOKEN"
239+
kwargs = {
240+
'one': 100,
241+
'two': 200,
242+
}
243+
with patch('basket.base.request', autospec=True) as request_call:
244+
request_call.return_value = Mock()
245+
result = update_user(token, **kwargs)
246+
request_call.assert_called_with('post', 'user', data=kwargs,
247+
token=token)
248+
self.assertEqual(request_call.return_value, result)
249+
250+
def test_debug_user(self):
251+
"""
252+
debug_user passes the expected args to request(), returns the result.
253+
"""
254+
email = "user@example.com"
255+
supertoken = "STOKEN"
256+
params = {'email': email, 'supertoken': supertoken}
257+
with patch('basket.base.request', autospec=True) as request_call:
258+
request_call.return_value = Mock()
259+
result = debug_user(email, supertoken)
260+
request_call.assert_called_with('get', 'debug-user', params=params)
261+
self.assertEqual(request_call.return_value, result)
262+
263+
def test_get_newsletters(self):
264+
"""
265+
get_newsletters passes the expected args to request() and returns
266+
the 'newsletters' part of what it returns
267+
"""
268+
with patch('basket.base.request', autospec=True) as request_call:
269+
request_call.return_value = {'newsletters': 'FOO BAR'}
270+
result = get_newsletters()
271+
request_call.assert_called_with('get', 'newsletters')
272+
self.assertEqual('FOO BAR', result)

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
setup(
55
name='basket-client',
6-
version='0.3.4',
6+
version='0.3.5',
77
description="A Python client for Mozilla's basket service.",
88
long_description=open('README.rst').read(),
99
author='Michael Kelly and contributors',
@@ -25,5 +25,7 @@
2525
"Topic :: Communications",
2626
'Topic :: Software Development :: Libraries',
2727
],
28-
keywords=['mozilla', 'basket']
28+
keywords=['mozilla', 'basket'],
29+
test_suite="basket.tests.TestBasketClient",
30+
tests_require=['mock==1.0.1'],
2931
)

0 commit comments

Comments
 (0)