aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2017-10-07 22:38:43 +0000
committerMike Crute <mike@crute.us>2017-10-07 22:38:43 +0000
commitc57ed164a699c1e7f57897c0ee24f6bf88d4b236 (patch)
treedbf421f6d8689274b73b249bece0f9ecc5cf49ed
parenta8932e9f004a83cae58ed0b086271e2191a04b83 (diff)
downloadpydora-c57ed164a699c1e7f57897c0ee24f6bf88d4b236.tar.bz2
pydora-c57ed164a699c1e7f57897c0ee24f6bf88d4b236.tar.xz
pydora-c57ed164a699c1e7f57897c0ee24f6bf88d4b236.zip
Add transport tests, 100% pandora coverage!
-rwxr-xr-xsetup.py1
-rw-r--r--tests/test_pandora/test_transport.py295
-rw-r--r--tests/test_pandora/test_utils.py22
3 files changed, 315 insertions, 3 deletions
diff --git a/setup.py b/setup.py
index 94c2ad6..1ea1076 100755
--- a/setup.py
+++ b/setup.py
@@ -46,6 +46,7 @@ requires = {
46 "tests_require": [ 46 "tests_require": [
47 "mock>=2,<3", 47 "mock>=2,<3",
48 "coverage>=4.1,<5", 48 "coverage>=4.1,<5",
49 "cryptography>=2,<3",
49 ], 50 ],
50 "install_requires": [ 51 "install_requires": [
51 "requests>=2,<3", 52 "requests>=2,<3",
diff --git a/tests/test_pandora/test_transport.py b/tests/test_pandora/test_transport.py
index 5474a56..4717617 100644
--- a/tests/test_pandora/test_transport.py
+++ b/tests/test_pandora/test_transport.py
@@ -1,11 +1,16 @@
1import sys
1import time 2import time
3import json
4import random
5import requests
2from unittest import TestCase 6from unittest import TestCase
3from pandora.errors import InvalidAuthToken, PandoraException 7from pandora.py2compat import Mock, call, patch
4
5from pandora.py2compat import Mock, call
6 8
9from pandora.errors import InvalidAuthToken, PandoraException
7from tests.test_pandora.test_clientbuilder import TestSettingsDictBuilder 10from tests.test_pandora.test_clientbuilder import TestSettingsDictBuilder
8 11
12import pandora.transport as t
13
9 14
10class SysCallError(Exception): 15class SysCallError(Exception):
11 pass 16 pass
@@ -13,6 +18,17 @@ class SysCallError(Exception):
13 18
14class TestTransport(TestCase): 19class TestTransport(TestCase):
15 20
21 def test_test_url_should_return_true_if_request_okay(self):
22 transport = t.APITransport(Mock())
23 transport._http = Mock()
24 transport._http.head.return_value = Mock(
25 status_code=requests.codes.not_found)
26
27 self.assertFalse(transport.test_url("foo"))
28
29 transport._http.head.return_value = Mock(status_code=requests.codes.OK)
30 self.assertTrue(transport.test_url("foo"))
31
16 def test_call_should_retry_max_times_on_sys_call_error(self): 32 def test_call_should_retry_max_times_on_sys_call_error(self):
17 with self.assertRaises(SysCallError): 33 with self.assertRaises(SysCallError):
18 client = TestSettingsDictBuilder._build_minimal() 34 client = TestSettingsDictBuilder._build_minimal()
@@ -57,3 +73,276 @@ class TestTransport(TestCase):
57 client.transport._start_request.assert_has_calls([call("method")]) 73 client.transport._start_request.assert_has_calls([call("method")])
58 assert client.transport._start_request.call_count == 2 74 assert client.transport._start_request.call_count == 2
59 assert client._authenticate.call_count == 1 75 assert client._authenticate.call_count == 1
76
77 def test_complete_request(self):
78 transport = t.APITransport(Mock())
79 transport._http = Mock()
80
81 http_result = Mock()
82 http_result.content = b'{"stat":"ok","result":"bar"}'
83 transport._http.post.return_value = http_result
84
85 self.assertEqual(
86 "bar", transport(t.APITransport.NO_ENCRYPT[0], foo="bar"))
87
88
89class TestTransportSetters(TestCase):
90
91 def setUp(self):
92 self.cryptor = Mock()
93 self.transport = t.APITransport(self.cryptor)
94
95 def test_set_partner(self):
96 self.cryptor.decrypt_sync_time.return_value = 456
97
98 self.transport.set_partner({
99 "syncTime": "123",
100 "partnerAuthToken": "partner_auth_token",
101 "partnerId": "partner_id",
102 })
103
104 self.cryptor.decrypt_sync_time.assert_called_with("123")
105 self.assertEqual("partner_auth_token", self.transport.auth_token)
106 self.assertEqual("partner_id", self.transport.partner_id)
107 self.assertEqual(
108 "partner_auth_token", self.transport.partner_auth_token)
109
110 self.transport.start_time = 10
111 with patch.object(time, "time", return_value=30):
112 self.assertEqual(476, self.transport.sync_time)
113
114 def test_set_user(self):
115 self.transport.set_user({
116 "userId": "user",
117 "userAuthToken": "auth",
118 })
119
120 self.assertEqual("user", self.transport.user_id)
121 self.assertEqual("auth", self.transport.user_auth_token)
122 self.assertEqual("auth", self.transport.auth_token)
123
124 def test_getting_auth_token_no_login(self):
125 self.assertIsNone(self.transport.auth_token)
126 self.assertIsNone(self.transport.sync_time)
127
128
129class TestDelayExponential(TestCase):
130
131 def test_fixed_delay(self):
132 self.assertEqual(8, t.delay_exponential(2, 2, 3))
133
134 def test_random_delay(self):
135 with patch.object(random, "random", return_value=10):
136 self.assertEqual(20, t.delay_exponential("rand", 2, 2))
137
138 def test_fails_with_base_zero_or_below(self):
139 with self.assertRaises(ValueError):
140 t.delay_exponential(0, 1, 1)
141
142 with self.assertRaises(ValueError):
143 t.delay_exponential(-1, 1, 1)
144
145
146class TestRetries(TestCase):
147
148 def test_no_retries_returns_none(self):
149 @t.retries(0)
150 def foo():
151 return True
152
153 self.assertIsNone(foo())
154
155
156class TestParseResponse(TestCase):
157
158 VALID_MSG_NO_BODY_JSON = b'{"stat":"ok"}'
159 VALID_MSG_JSON = b'{"stat":"ok", "result":{"foo":"bar"}}'
160 ERROR_MSG_JSON = b'{"stat":"err", "code":1001, "message":"Details"}'
161
162 def setUp(self):
163 self.transport = t.APITransport(Mock())
164
165 def test_with_valid_response(self):
166 res = self.transport._parse_response(self.VALID_MSG_JSON)
167 self.assertEqual({ "foo": "bar" }, res)
168
169 def test_with_valid_response_no_body(self):
170 res = self.transport._parse_response(self.VALID_MSG_NO_BODY_JSON)
171 self.assertIsNone(res)
172
173 def test_with_error_response(self):
174 with self.assertRaises(InvalidAuthToken) as ex:
175 self.transport._parse_response(self.ERROR_MSG_JSON)
176
177 self.assertEqual(1001, ex.exception.code)
178 self.assertEqual("Details", ex.exception.extended_message)
179
180
181class TestTransportRequestPrep(TestCase):
182
183 def setUp(self):
184 self.cryptor = Mock()
185 self.transport = t.APITransport(self.cryptor)
186
187 def test_start_request(self):
188 self.transport.start_time = 10
189 self.transport._start_request("method_name")
190 self.assertEqual(10, self.transport.start_time)
191
192 def test_start_request_with_reset(self):
193 self.transport.reset = Mock()
194 self.transport._start_request(self.transport.REQUIRE_RESET[0])
195 self.transport.reset.assert_called_with()
196
197 def test_start_request_without_time(self):
198 with patch.object(time, "time", return_value=10.0):
199 self.transport._start_request("method_name")
200 self.assertEqual(10, self.transport.start_time)
201
202 def test_make_http_request(self):
203 # url, data, params
204 http = Mock()
205 retval = Mock()
206 retval.content = "foo"
207 http.post.return_value = retval
208
209 self.transport._http = http
210 res = self.transport._make_http_request(
211 "/url", b"data", { "a":None, "b":"c" })
212
213 http.post.assert_called_with("/url", data=b"data", params={"b":"c"})
214 retval.raise_for_status.assert_called_with()
215
216 self.assertEqual("foo", res)
217
218 def test_build_data_not_logged_in(self):
219 self.cryptor.encrypt = lambda x: x
220
221 self.transport.partner_auth_token = "pat"
222 self.transport.server_sync_time = 123
223 self.transport.start_time = 23
224
225 with patch.object(time, "time", return_value=20):
226 val = self.transport._build_data("foo", {"a":"b", "c":None})
227
228 val = json.loads(val)
229 self.assertEqual("b", val["a"])
230 self.assertEqual("pat", val["partnerAuthToken"])
231 self.assertEqual(120, val["syncTime"])
232
233 def test_build_data_no_encrypt(self):
234 self.transport.user_auth_token = "uat"
235 self.transport.partner_auth_token = "pat"
236 self.transport.server_sync_time = 123
237 self.transport.start_time = 23
238
239 with patch.object(time, "time", return_value=20):
240 val = self.transport._build_data(
241 t.APITransport.NO_ENCRYPT[0], {"a":"b", "c":None})
242
243 val = json.loads(val)
244 self.assertEqual("b", val["a"])
245 self.assertEqual("uat", val["userAuthToken"])
246 self.assertEqual(120, val["syncTime"])
247
248
249# All Cryptor implementations must pass these test cases unmodified
250class CommonCryptorTestCases(object):
251
252 def test_decrypt_invalid_padding(self):
253 with self.assertRaises(ValueError):
254 data = b"12345678\x00"
255 self.assertEqual(b"12345678\x00", self.cryptor.decrypt(data))
256
257 def test_decrypt_strip_padding(self):
258 data = b"123456\x02\x02"
259 self.assertEqual(b"123456", self.cryptor.decrypt(data))
260
261 def test_decrypt_preserve_padding(self):
262 data = b"123456\x02\x02"
263 self.assertEqual(b"123456\x02\x02", self.cryptor.decrypt(data, False))
264
265 def test_encrypt(self):
266 data = "123456"
267 self.assertEqual(b"123456\x02\x02", self.cryptor.encrypt(data))
268
269
270class TestPurePythonBlowfishCryptor(TestCase, CommonCryptorTestCases):
271
272 def setUp(self):
273 # Ugh... blowfish can't even be *imported* in python2
274 if sys.version_info.major == 2:
275 t.blowfish = Mock()
276
277 self.cipher = Mock()
278 self.cipher.decrypt_ecb = lambda x: [x]
279 self.cipher.encrypt_ecb = lambda x: [x]
280 self.cryptor = t.PurePythonBlowfish("keys")
281 self.cryptor.cipher = self.cipher
282
283
284class TestCryptographyBlowfish(TestCase, CommonCryptorTestCases):
285
286 class FakeCipher(object):
287
288 def update_into(self, val, buf):
289 for i, v in enumerate(val):
290 buf[i] = v
291 return len(val)
292
293 def finalize(self):
294 return b""
295
296 def setUp(self):
297 self.cipher = Mock()
298 self.cipher.encryptor.return_value = self.FakeCipher()
299 self.cipher.decryptor.return_value = self.FakeCipher()
300 self.cryptor = t.CryptographyBlowfish("keys")
301 self.cryptor.cipher = self.cipher
302
303
304class TestEncryptor(TestCase):
305
306 ENCODED_JSON = "7b22666f6f223a22626172227d"
307 UNENCODED_JSON = b'{"foo":"bar"}'
308 EXPECTED_TIME = 4111
309 ENCODED_TIME = "31353037343131313539"
310
311 class NoopCrypto(object):
312
313 def __init__(self, key):
314 pass
315
316 def decrypt(self, data, strip_padding=True):
317 return data.decode("ascii")
318
319 def encrypt(self, data):
320 return data
321
322 def setUp(self):
323 self.cryptor = t.Encryptor("in", "out", self.NoopCrypto)
324
325 def test_decrypt(self):
326 self.assertEqual(
327 { "foo": "bar" }, self.cryptor.decrypt(self.ENCODED_JSON))
328
329 def test_encrypt(self):
330 self.assertEqual(
331 self.ENCODED_JSON.encode("ascii"),
332 self.cryptor.encrypt(self.UNENCODED_JSON))
333
334 def test_decrypt_sync_time(self):
335 self.assertEqual(
336 self.EXPECTED_TIME,
337 self.cryptor.decrypt_sync_time(self.ENCODED_TIME))
338
339
340class TestDefaultStrategy(TestCase):
341
342 def test_blowfish_not_available(self):
343 del sys.modules["pandora.transport"]
344 sys.modules["blowfish"] = None
345
346 import pandora.transport as t
347 self.assertIsNone(t.blowfish)
348 self.assertIs(t._default_crypto, t.CryptographyBlowfish)
diff --git a/tests/test_pandora/test_utils.py b/tests/test_pandora/test_utils.py
new file mode 100644
index 0000000..2b4ca83
--- /dev/null
+++ b/tests/test_pandora/test_utils.py
@@ -0,0 +1,22 @@
1import warnings
2from unittest import TestCase
3from pandora.py2compat import patch
4
5from pandora import util
6
7
8class TestDeprecatedWarning(TestCase):
9
10 def test_warning(self):
11 class Bar(object):
12
13 @util.deprecated("1.0", "2.0", "Don't use this")
14 def foo(self):
15 pass
16
17 with patch.object(warnings, "warn") as wmod:
18 Bar().foo()
19
20 wmod.assert_called_with(
21 ("foo is deprecated as of version 1.0 and will be removed in "
22 "version 2.0. Don't use this"), DeprecationWarning)