Merge lp:~elopio/cloudspacesclient/cloudspacer into lp:cloudspacesclient
- cloudspacer
- Merge into trunk
Proposed by
Leo Arias
Status: | Superseded |
---|---|
Proposed branch: | lp:~elopio/cloudspacesclient/cloudspacer |
Merge into: | lp:cloudspacesclient |
Diff against target: |
804 lines (+170/-388) 7 files modified
src/cloudspacesclient/__init__.py (+0/-7) src/cloudspacesclient/client.py (+0/-104) src/cloudspacesclient/tests/conformance/test_cloudspaces_api.py (+107/-108) src/cloudspacesclient/tests/unit/test_client.py (+0/-124) src/cloudspacesclient/tests/unit/test_schemas.py (+20/-4) src/cloudspacesclient/tests/unit/test_ubuntuone.py (+31/-27) src/cloudspacesclient/ubuntuone.py (+12/-14) |
To merge this branch: | bzr merge lp:~elopio/cloudspacesclient/cloudspacer |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu One hackers | Pending | ||
Review via email:
|
This proposal has been superseded by a proposal from 2013-11-29.
Commit message
Use cloudspacer as the client.
Description of the change
This branch ports the code to python3-only.
It requires a small fix in ssoclient to work with python3, so please don't merge it yet.
To post a comment you must log in.
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/cloudspacesclient/__init__.py' |
2 | --- src/cloudspacesclient/__init__.py 2013-11-20 05:06:14 +0000 |
3 | +++ src/cloudspacesclient/__init__.py 2013-11-29 08:45:45 +0000 |
4 | @@ -14,10 +14,3 @@ |
5 | # |
6 | # You should have received a copy of the GNU General Public License |
7 | # along with cloudspacesclient. If not, see <http://www.gnu.org/licenses/>. |
8 | - |
9 | -__all__ = [ |
10 | - 'APIException', |
11 | - 'CloudspacesAPIClient' |
12 | -] |
13 | - |
14 | -from client import APIException, CloudspacesAPIClient |
15 | |
16 | === removed file 'src/cloudspacesclient/client.py' |
17 | --- src/cloudspacesclient/client.py 2013-11-28 11:37:30 +0000 |
18 | +++ src/cloudspacesclient/client.py 1970-01-01 00:00:00 +0000 |
19 | @@ -1,104 +0,0 @@ |
20 | -# Copyright (C) 2013 Canonical Ltd. |
21 | -# |
22 | -# This file is part of cloudspacesclient. |
23 | -# |
24 | -# cloudspacesclient is free software: you can redistribute it and/or modify |
25 | -# it under the terms of the GNU General Public License as published by |
26 | -# the Free Software Foundation, either version 3 of the License, or |
27 | -# (at your option) any later version. |
28 | -# |
29 | -# cloudspacesclient is distributed in the hope that it will be useful, |
30 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
31 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
32 | -# GNU General Public License for more details. |
33 | -# |
34 | -# You should have received a copy of the GNU General Public License |
35 | -# along with cloudspacesclient. If not, see <http://www.gnu.org/licenses/>. |
36 | - |
37 | -"""Cloudspaces API client library.""" |
38 | - |
39 | -import json |
40 | -import urlparse |
41 | -import requests |
42 | -import json |
43 | - |
44 | -JSON_MIME_TYPE = 'application/json' |
45 | - |
46 | - |
47 | -class APIException(Exception): |
48 | - """Exception raised when there is an error calling the Cloudspaces API.""" |
49 | - |
50 | - |
51 | -class CloudspacesAPIClient(object): |
52 | - |
53 | - content_type = JSON_MIME_TYPE |
54 | - |
55 | - def __init__(self, service_root_url, auth): |
56 | - super(CloudspacesAPIClient, self).__init__() |
57 | - self.service_root_url = service_root_url |
58 | - self.auth = auth |
59 | - |
60 | - def get_user_representation(self): |
61 | - """Return the representation of the currently authorized user.""" |
62 | - response = requests.get( |
63 | - self.service_root_url, auth=self.auth, headers=self._get_headers()) |
64 | - if not response.ok: |
65 | - raise APIException( |
66 | - "{0}: {1}".format(response.status_code, response.reason)) |
67 | - return response.json() |
68 | - |
69 | - def _get_headers(self): |
70 | - return { |
71 | - 'Content-Type': self.content_type, |
72 | - 'Accept': self.content_type |
73 | - } |
74 | - |
75 | - def create_folder(self, folder_name, parent_node_id=""): |
76 | - """Create a folder with the name folder_name |
77 | - :parameter folder_name: the name of the folder to create |
78 | - :parameter parent_node_id: the id of the parent folder |
79 | - """ |
80 | - params = {'folder_name': folder_name, 'parent_node_id': parent_node_id} |
81 | - # convert parameters to json string |
82 | - json_params = json.dumps(params) |
83 | - url = urlparse.urljoin(self.service_root_url, 'metadata/') |
84 | - # send the post request |
85 | - response = requests.post( |
86 | - url, data=json_params, auth=self.auth, headers=self._get_headers()) |
87 | - if not response.ok: |
88 | - raise APIException( |
89 | - "{0}: {1}".format(response.status_code, response.reason)) |
90 | - return response.json() |
91 | - |
92 | - def get_metadata(self, node_id): |
93 | - """Return the metadata for a file or folder. |
94 | - |
95 | - :parameter node_id: the id of a file or folder. |
96 | - |
97 | - """ |
98 | - url = urlparse.urljoin( |
99 | - self.service_root_url, 'metadata/{}'.format(node_id)) |
100 | - response = requests.get( |
101 | - url, auth=self.auth, headers=self._get_headers()) |
102 | - if not response.ok: |
103 | - raise APIException( |
104 | - "{0}: {1}".format(response.status_code, response.reason)) |
105 | - return response.json() |
106 | - |
107 | - def create_file(self, file_name, folder_node_id=''): |
108 | - """Create a new file. |
109 | - |
110 | - :parameter file_name: the name of the file to create. |
111 | - :parameter folder_node_id: the id of the folder where the file will be |
112 | - created. The default value creates the file on the root node. |
113 | - |
114 | - """ |
115 | - url = urlparse.urljoin( |
116 | - self.service_root_url, 'content/{}'.format(folder_node_id)) |
117 | - response = requests.post( |
118 | - url, auth=self.auth, headers=self._get_headers(), |
119 | - data=json.dumps({'file_name': file_name})) |
120 | - if not response.ok: |
121 | - raise APIException( |
122 | - "{0}: {1}".format(response.status_code, response.reason)) |
123 | - return response.json() |
124 | |
125 | === modified file 'src/cloudspacesclient/tests/conformance/test_cloudspaces_api.py' |
126 | --- src/cloudspacesclient/tests/conformance/test_cloudspaces_api.py 2013-11-28 11:37:30 +0000 |
127 | +++ src/cloudspacesclient/tests/conformance/test_cloudspaces_api.py 2013-11-29 08:45:45 +0000 |
128 | @@ -19,13 +19,14 @@ |
129 | |
130 | import os |
131 | import unittest |
132 | -import urlparse |
133 | +import urllib.parse |
134 | from datetime import datetime |
135 | |
136 | import validictory |
137 | +import cloudspacer.cooked |
138 | from dateutil import parser, tz |
139 | |
140 | -from cloudspacesclient import client, config, schemas, ubuntuone |
141 | +from cloudspacesclient import config, schemas, ubuntuone |
142 | |
143 | |
144 | class CloudspacesAPITestCase(unittest.TestCase): |
145 | @@ -42,13 +43,13 @@ |
146 | self.test_user = self.test_server.create_test_user() |
147 | self.addCleanup(self.test_server.delete_test_user, self.test_user) |
148 | |
149 | - auth = self.test_server.get_auth_credentials(self.test_user) |
150 | - self.service_root = urlparse.urljoin( |
151 | + auth_session = self.test_server.get_auth_session(self.test_user) |
152 | + self.service_root = urllib.parse.urljoin( |
153 | self.cloudspaces_server_url, 'api/cloudspaces/') |
154 | # TODO the client class should be defined in a config file, to make |
155 | # it easy to test different Cloudspaces clients. |
156 | - self.cloudspaces_api_client = client.CloudspacesAPIClient( |
157 | - self.service_root, auth) |
158 | + self.cloudspaces_api_client = cloudspacer.cooked.CloudSpacer( |
159 | + self.service_root, auth_session) |
160 | |
161 | def _get_now_datetime(self): |
162 | now = datetime.now(tz.tzutc()) |
163 | @@ -92,7 +93,7 @@ |
164 | self._assert_resource_path(node_id, resource_path) |
165 | |
166 | def _assert_resource_path(self, node_id, resource_path): |
167 | - path = urlparse.urlparse(self.service_root).path |
168 | + path = urllib.parse.urlparse(self.service_root).path |
169 | self.assertEqual( |
170 | resource_path, '{0}metadata/{1}'.format(path, node_id)) |
171 | |
172 | @@ -105,96 +106,23 @@ |
173 | validictory.validate( |
174 | metadata, schemas.GET_FOLDER_METADATA_RESPONSE_SCHEMA) |
175 | |
176 | + expected = dict( |
177 | + is_deleted=False, is_folder=True, is_root=True, filename=filename, |
178 | + node_id=node_id, path=self.test_server.get_root_path()) |
179 | + self._assert_json_object_values(expected, metadata) |
180 | + |
181 | self._assert_normal_folder_keys_not_in_root(metadata) |
182 | self._assert_content_path(node_id, metadata.get('content_path')) |
183 | + self._assert_resource_path(node_id, metadata.get('resource_path')) |
184 | # TODO Check that the contents are not present because the folder is |
185 | # empty. --elopio - 2013-11-25 |
186 | - self.assertTrue(metadata.get('is_folder')) |
187 | - self.assertFalse(metadata.get('is_deleted')) |
188 | - self.assertTrue(metadata.get('is_root')) |
189 | # TODO The spec doesn't include it in the filename in the examples. |
190 | # --elopio - 2013-11-25 |
191 | - self.assertEqual(metadata.get('filename'), filename) |
192 | - self.assertEqual(metadata.get('node_id'), node_id) |
193 | # TODO The spec says the path shouldn't be returned on the root |
194 | # metadata, but I think it's useful. Should we change the spec? |
195 | # --elopio - 2013-11-25 |
196 | - self.assertEqual( |
197 | - metadata.get('path'), self.test_server.get_root_path()) |
198 | - self._assert_resource_path(node_id, metadata.get('resource_path')) |
199 | # TODO Check the volume_id field. --elopio - 2013-11-25 |
200 | |
201 | - def test_create_folder_in_root(self): |
202 | - # get the root node id and root folder name |
203 | - root_node_id, root_name = self._get_root_info() |
204 | - # create the folder |
205 | - folder_name = 'root_folder_1' |
206 | - metadata = self.cloudspaces_api_client.create_folder( |
207 | - folder_name, root_node_id) |
208 | - # validate the folder properties from response |
209 | - validictory.validate( |
210 | - metadata, schemas.GET_FOLDER_METADATA_RESPONSE_SCHEMA) |
211 | - self.assertTrue(metadata.get('is_folder')) |
212 | - self.assertFalse(metadata.get('is_deleted')) |
213 | - self.assertFalse(metadata.get('is_root')) |
214 | - self.assertEqual(metadata.get('path'), root_name) |
215 | - self.assertEqual(metadata.get('filename'), folder_name) |
216 | - self.assertEqual(metadata.get('parent_node_id'), root_node_id) |
217 | - self.assertEqual(metadata.get('version'), 1) |
218 | - |
219 | - def test_create_folder_no_parent_id(self): |
220 | - # get the root node id and root folder name |
221 | - root_node_id, root_name = self._get_root_info() |
222 | - # create the folder without specifying the parent folder id |
223 | - # this should add folder to root level by default |
224 | - folder_name = 'folder_no_parent_id' |
225 | - metadata = self.cloudspaces_api_client.create_folder(folder_name) |
226 | - # validate the folder properties from response |
227 | - validictory.validate( |
228 | - metadata, schemas.GET_FOLDER_METADATA_RESPONSE_SCHEMA) |
229 | - self.assertTrue(metadata.get('is_folder')) |
230 | - self.assertFalse(metadata.get('is_deleted')) |
231 | - self.assertFalse(metadata.get('is_root')) |
232 | - self.assertEqual(metadata.get('path'), root_name) |
233 | - self.assertEqual(metadata.get('filename'), folder_name) |
234 | - self.assertEqual(metadata.get('parent_node_id'), root_node_id) |
235 | - self.assertEqual(metadata.get('version'), 1) |
236 | - |
237 | - def test_create_child_folder(self): |
238 | - # get the root node id and root folder name |
239 | - root_node_id, root_name = self._get_root_info() |
240 | - parent_folder_name = 'parent_folder_1' |
241 | - # create the parent folder |
242 | - metadata = self.cloudspaces_api_client.create_folder( |
243 | - parent_folder_name, root_node_id) |
244 | - # validate the parent folder properties from response |
245 | - validictory.validate( |
246 | - metadata, schemas.GET_FOLDER_METADATA_RESPONSE_SCHEMA) |
247 | - self.assertTrue(metadata.get('is_folder')) |
248 | - self.assertFalse(metadata.get('is_deleted')) |
249 | - self.assertFalse(metadata.get('is_root')) |
250 | - self.assertEqual(metadata.get('path'), root_name) |
251 | - self.assertEqual(metadata.get('filename'), parent_folder_name) |
252 | - self.assertEqual(metadata.get('parent_node_id'), root_node_id) |
253 | - self.assertEqual(metadata.get('version'), 1) |
254 | - |
255 | - # Now create a sub folder under the parent folder |
256 | - child_folder_name = 'child_folder_1' |
257 | - parent_node_id = metadata.get('node_id') |
258 | - metadata = self.cloudspaces_api_client.create_folder( |
259 | - child_folder_name, parent_node_id) |
260 | - # validate the sub-folder properties from response |
261 | - validictory.validate( |
262 | - metadata, schemas.GET_FOLDER_METADATA_RESPONSE_SCHEMA) |
263 | - self.assertTrue(metadata.get('is_folder')) |
264 | - self.assertFalse(metadata.get('is_deleted')) |
265 | - self.assertFalse(metadata.get('is_root')) |
266 | - self.assertEqual(metadata.get('path'), '{0}/{1}'.format( |
267 | - root_name, parent_folder_name)) |
268 | - self.assertEqual(metadata.get('filename'), child_folder_name) |
269 | - self.assertEqual(metadata.get('parent_node_id'), parent_node_id) |
270 | - self.assertEqual(metadata.get('version'), 2) |
271 | - |
272 | def _get_root_info(self): |
273 | user_rep = self.cloudspaces_api_client.get_user_representation() |
274 | root_volume = self._get_root_volume(user_rep.get('volumes')) |
275 | @@ -206,6 +134,21 @@ |
276 | # FIXME The first volume returned is not necessarily the root. |
277 | # --elopio - 2013-11-23 |
278 | return volumes[0] |
279 | + |
280 | + def _assert_json_object_values(self, expected, actual): |
281 | + """Assert the values of the returned json object. |
282 | + |
283 | + It checks that all the items in ``expected`` are in ``actual`` with |
284 | + the same value. It doesn't check that ``actual`` has no extra values. |
285 | + |
286 | + """ |
287 | + for key, expected_value in expected.items(): |
288 | + actual_value = actual.get(key) |
289 | + self.assertEqual( |
290 | + expected_value, actual_value, |
291 | + 'Wrong value returned on the field {0}. ' |
292 | + 'Expected: {1}. Received: {2}'.format( |
293 | + key, expected_value, actual_value)) |
294 | |
295 | def _assert_normal_folder_keys_not_in_root(self, response_json): |
296 | normal_folder_keys_not_in_root = [ |
297 | @@ -218,38 +161,94 @@ |
298 | "The root folder shouldn't include the {} key".format(key)) |
299 | |
300 | def _assert_content_path(self, node_id, content_path): |
301 | - path = urlparse.urlparse(self.service_root).path |
302 | + path = urllib.parse.urlparse(self.service_root).path |
303 | self.assertEqual( |
304 | content_path, '{0}content/{1}'.format(path, node_id)) |
305 | |
306 | + def test_create_folder_in_root(self): |
307 | + # get the root node id and root folder name |
308 | + root_node_id, root_name = self._get_root_info() |
309 | + # create the folder |
310 | + folder_name = 'root_folder_1' |
311 | + |
312 | + metadata = self.cloudspaces_api_client.make_folder( |
313 | + root_node_id, folder_name) |
314 | + # validate the folder properties from response |
315 | + validictory.validate( |
316 | + metadata, schemas.GET_FOLDER_METADATA_RESPONSE_SCHEMA) |
317 | + |
318 | + expected = dict( |
319 | + is_folder=True, is_deleted=False, is_root=False, path=root_name, |
320 | + filename=folder_name, parent_node_id=root_node_id, version=1) |
321 | + self._assert_json_object_values(expected, metadata) |
322 | + |
323 | + def test_create_folder_no_parent_id(self): |
324 | + # get the root node id and root folder name |
325 | + root_node_id, root_name = self._get_root_info() |
326 | + # create the folder without specifying the parent folder id |
327 | + # this should add folder to root level by default |
328 | + folder_name = 'folder_no_parent_id' |
329 | + |
330 | + metadata = self.cloudspaces_api_client.make_folder("", folder_name) |
331 | + # validate the folder properties from response |
332 | + validictory.validate( |
333 | + metadata, schemas.GET_FOLDER_METADATA_RESPONSE_SCHEMA) |
334 | + |
335 | + expected = dict( |
336 | + is_folder=True, is_deleted=False, is_root=False, path=root_name, |
337 | + filename=folder_name, parent_node_id=root_node_id, version=1) |
338 | + self._assert_json_object_values(expected, metadata) |
339 | + |
340 | + def test_create_child_folder(self): |
341 | + # get the root node id and root folder name |
342 | + root_node_id, root_name = self._get_root_info() |
343 | + parent_folder_name = 'parent_folder_1' |
344 | + # create the parent folder |
345 | + parent_metadata = self.cloudspaces_api_client.make_folder( |
346 | + root_node_id, parent_folder_name) |
347 | + # Now create a sub folder under the parent folder |
348 | + child_folder_name = 'child_folder_1' |
349 | + parent_node_id = parent_metadata.get('node_id') |
350 | + child_folder_path = '{0}/{1}'.format(root_name, parent_folder_name) |
351 | + |
352 | + metadata = self.cloudspaces_api_client.make_folder( |
353 | + parent_node_id, child_folder_name) |
354 | + # validate the sub-folder properties from response |
355 | + validictory.validate( |
356 | + metadata, schemas.GET_FOLDER_METADATA_RESPONSE_SCHEMA) |
357 | + |
358 | + expected = dict( |
359 | + is_folder=True, is_deleted=False, is_root=False, |
360 | + path=child_folder_path, filename=child_folder_name, version=2) |
361 | + self._assert_json_object_values(expected, metadata) |
362 | + |
363 | def test_create_new_file(self): |
364 | - root_node_id, path = self._get_root_info() |
365 | - |
366 | - metadata = self.cloudspaces_api_client.create_file( |
367 | - 'test.txt', root_node_id) |
368 | + root_node_id, root_path = self._get_root_info() |
369 | + test_file_name = 'test.txt' |
370 | + test_file_mimetype = 'text/plain' |
371 | + |
372 | + metadata = self.cloudspaces_api_client.make_file( |
373 | + root_node_id, test_file_name) |
374 | + |
375 | + validictory.validate( |
376 | + metadata, schemas.GET_FILE_METADATA_RESPONSE_SCHEMA) |
377 | + |
378 | + expected = dict( |
379 | + hash=None, filename=test_file_name, is_deleted=False, |
380 | + is_folder=False, mimetype=test_file_mimetype, |
381 | + parent_node_id=root_node_id, path=root_path, size=0, version=1) |
382 | + self._assert_json_object_values(expected, metadata) |
383 | + |
384 | file_node_id = metadata.get('node_id') |
385 | - |
386 | - validictory.validate( |
387 | - metadata, schemas.GET_FILE_METADATA_RESPONSE_SCHEMA) |
388 | - |
389 | self._assert_content_path(file_node_id, metadata.get('content_path')) |
390 | - # TODO update the spec because it says checksum instead of hash. |
391 | - # --elopio - 2013-11-27 |
392 | - self.assertEqual(metadata.get('hash'), None) |
393 | - # TODO The spec doesn't include it in the filename in the examples. |
394 | - # --elopio - 2013-11-25 |
395 | - self.assertEqual(metadata.get('filename'), 'test.txt') |
396 | - self.assertFalse(metadata.get('is_deleted')) |
397 | - self.assertFalse(metadata.get('is_folder')) |
398 | - self.assertEqual(metadata.get('mimetype'), 'text/plain') |
399 | - self.assertEqual(metadata.get('parent_node_id'), root_node_id) |
400 | - self.assertEqual(metadata.get('path'), path) |
401 | self._assert_resource_path(file_node_id, metadata.get('resource_path')) |
402 | - self.assertEqual(metadata.get('size'), 0) |
403 | # Assert that the file has just been created. |
404 | self._assert_datetime_limits( |
405 | parser.parse(metadata.get('server_modified')), self.start_datetime, |
406 | self._get_now_datetime()) |
407 | - self.assertEqual(metadata.get('version'), 1) |
408 | + # TODO update the spec because it says checksum instead of hash. |
409 | + # --elopio - 2013-11-27 |
410 | + # TODO The spec doesn't include it in the filename in the examples. |
411 | + # --elopio - 2013-11-25 |
412 | # TODO check the user_id. --elopio - 2013-11-27 |
413 | # TODO Check the volume_id field. --elopio - 2013-11-27 |
414 | |
415 | === removed file 'src/cloudspacesclient/tests/unit/test_client.py' |
416 | --- src/cloudspacesclient/tests/unit/test_client.py 2013-11-27 06:14:16 +0000 |
417 | +++ src/cloudspacesclient/tests/unit/test_client.py 1970-01-01 00:00:00 +0000 |
418 | @@ -1,124 +0,0 @@ |
419 | -# Copyright (C) 2013 Canonical Ltd. |
420 | -# |
421 | -# This file is part of cloudspacesclient. |
422 | -# |
423 | -# cloudspacesclient is free software: you can redistribute it and/or modify |
424 | -# it under the terms of the GNU General Public License as published by |
425 | -# the Free Software Foundation, either version 3 of the License, or |
426 | -# (at your option) any later version. |
427 | -# |
428 | -# cloudspacesclient is distributed in the hope that it will be useful, |
429 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
430 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
431 | -# GNU General Public License for more details. |
432 | -# |
433 | -# You should have received a copy of the GNU General Public License |
434 | -# along with cloudspacesclient. If not, see <http://www.gnu.org/licenses/>. |
435 | - |
436 | -"""Unit tests for the Cloudspaces API client.""" |
437 | - |
438 | -import json |
439 | -import unittest |
440 | - |
441 | -import mock |
442 | - |
443 | -import cloudspacesclient |
444 | -from cloudspacesclient.tests.unit import utils |
445 | - |
446 | -import testscenarios |
447 | - |
448 | - |
449 | -# List of API Methods, each item is a tuple with the method name, the request |
450 | -# type and the list of required arguments. |
451 | -_API_METHODS = [ |
452 | - ('get_user_representation', 'get', []), |
453 | - ('get_metadata', 'get', ['dummynodeid']), |
454 | - ('create_file', 'post', ['dummyfilename']) |
455 | -] |
456 | - |
457 | - |
458 | -class CloudspacesAPIClientResponseTestCase( |
459 | - testscenarios.TestWithScenarios): |
460 | - |
461 | - def setUp(self): |
462 | - super(CloudspacesAPIClientResponseTestCase, self).setUp() |
463 | - self.api_client = cloudspacesclient.CloudspacesAPIClient( |
464 | - 'dummy url', 'dummy auth') |
465 | - |
466 | - scenarios = [ |
467 | - (api_method, dict( |
468 | - api_method=api_method, request_type=request_type, args=args)) |
469 | - for (api_method, request_type, args) in _API_METHODS |
470 | - ] |
471 | - |
472 | - def test_call_api_method_should_return_json(self): |
473 | - with mock.patch('requests.' + self.request_type) as request_mock: |
474 | - request_mock.return_value = utils.RequestResponseMock( |
475 | - ok=True, content=json.dumps("{'test': 'content'}")) |
476 | - response = getattr(self.api_client, self.api_method)(*self.args) |
477 | - |
478 | - self.assertEqual(response, "{'test': 'content'}") |
479 | - |
480 | - def test_call_api_method_failure_should_raise_exception(self): |
481 | - with mock.patch('requests.' + self.request_type) as request_mock: |
482 | - request_mock.return_value = utils.RequestResponseMock( |
483 | - ok=False, status_code='test error code', reason='test reason') |
484 | - with self.assertRaises(cloudspacesclient.APIException) as e: |
485 | - getattr(self.api_client, self.api_method)(*self.args) |
486 | - |
487 | - self.assertEqual(str(e.exception), 'test error code: test reason') |
488 | - |
489 | - |
490 | -class CloudspacesAPIClientTestCase(unittest.TestCase): |
491 | - |
492 | - def setUp(self): |
493 | - super(CloudspacesAPIClientTestCase, self).setUp() |
494 | - self.api_client = cloudspacesclient.CloudspacesAPIClient( |
495 | - 'http://example.com/cloudspaces/', 'test auth') |
496 | - self.request_headers = { |
497 | - 'Content-Type': 'application/json', |
498 | - 'Accept': 'application/json' |
499 | - } |
500 | - |
501 | - def test_get_user_representation_request(self): |
502 | - with mock.patch('requests.get') as get_mock: |
503 | - get_mock.return_value = utils.RequestResponseMock( |
504 | - ok=True, content=json.dumps("{'test': 'content'}")) |
505 | - self.api_client.get_user_representation() |
506 | - |
507 | - get_mock.assert_called_once_with( |
508 | - 'http://example.com/cloudspaces/', auth='test auth', |
509 | - headers=self.request_headers) |
510 | - |
511 | - def test_get_metadata_request(self): |
512 | - with mock.patch('requests.get') as get_mock: |
513 | - get_mock.return_value = utils.RequestResponseMock( |
514 | - ok=True, content=json.dumps("{'test': 'content'}")) |
515 | - self.api_client.get_metadata('testnodeid') |
516 | - |
517 | - get_mock.assert_called_once_with( |
518 | - 'http://example.com/cloudspaces/metadata/testnodeid', |
519 | - auth='test auth', headers=self.request_headers) |
520 | - |
521 | - def test_create_file_with_folder_id(self): |
522 | - with mock.patch('requests.post') as post_mock: |
523 | - post_mock.return_value = utils.RequestResponseMock( |
524 | - ok=True, content=json.dumps("{'test': 'content'}")) |
525 | - self.api_client.create_file( |
526 | - 'test file.txt', folder_node_id='testfoldernodeid') |
527 | - |
528 | - post_mock.assert_called_once_with( |
529 | - 'http://example.com/cloudspaces/content/testfoldernodeid', |
530 | - auth='test auth', headers=self.request_headers, |
531 | - data=json.dumps({'file_name': 'test file.txt'})) |
532 | - |
533 | - def test_create_file_without_folder_id(self): |
534 | - with mock.patch('requests.post') as post_mock: |
535 | - post_mock.return_value = utils.RequestResponseMock( |
536 | - ok=True, content=json.dumps("{'test': 'content'}")) |
537 | - self.api_client.create_file('test file.txt') |
538 | - |
539 | - post_mock.assert_called_once_with( |
540 | - 'http://example.com/cloudspaces/content/', |
541 | - auth='test auth', headers=self.request_headers, |
542 | - data=json.dumps({'file_name': 'test file.txt'})) |
543 | |
544 | === modified file 'src/cloudspacesclient/tests/unit/test_schemas.py' |
545 | --- src/cloudspacesclient/tests/unit/test_schemas.py 2013-11-27 15:47:59 +0000 |
546 | +++ src/cloudspacesclient/tests/unit/test_schemas.py 2013-11-29 08:45:45 +0000 |
547 | @@ -22,6 +22,7 @@ |
548 | |
549 | import testscenarios |
550 | import validictory |
551 | +from validictory import validator |
552 | |
553 | from cloudspacesclient import schemas |
554 | |
555 | @@ -183,12 +184,14 @@ |
556 | |
557 | class ResponseSchemaTestCase(unittest.TestCase): |
558 | |
559 | - def assert_raises_validation_error(self, json, schema): |
560 | + def assert_raises_validation_error( |
561 | + self, json, schema, validator_cls=validator.SchemaValidator): |
562 | self.assertRaises( |
563 | validictory.ValidationError, |
564 | validictory.validate, |
565 | json, |
566 | - schema |
567 | + schema, |
568 | + validator_cls=validator_cls |
569 | ) |
570 | |
571 | |
572 | @@ -258,6 +261,12 @@ |
573 | return wrong_types |
574 | |
575 | |
576 | +class _SchemaValidatorWithoutFormatValidation(validator.SchemaValidator): |
577 | + |
578 | + def register_format_validator(self, format_name, format_validator_fun): |
579 | + pass |
580 | + |
581 | + |
582 | class WrongPropertiesValuesTestCase( |
583 | testscenarios.TestWithScenarios, ResponseSchemaTestCase): |
584 | |
585 | @@ -270,7 +279,7 @@ |
586 | value=wrong_value)) |
587 | for object_ in _OBJECTS |
588 | for property_, types in ( |
589 | - object_.get('required_properties').items() + |
590 | + object_.get('required_properties').items() | |
591 | object_.get('optional_properties').items() |
592 | ) |
593 | for wrong_type, wrong_value in _get_wrong_types(types).items() |
594 | @@ -282,7 +291,14 @@ |
595 | |
596 | def test_json_with_wrong_property_value(self): |
597 | self.json[self.property_] = self.value |
598 | - self.assert_raises_validation_error(self.json, self.schema) |
599 | + # XXX We use a validator without format validation because sometimes |
600 | + # python will change the order of the schema and validictory will check |
601 | + # the format before the type, raising the wrong error. |
602 | + # Bug reported: https://github.com/sunlightlabs/validictory/issues/64 |
603 | + # We should change to the default validator once the bug is fixed. |
604 | + # --elopio - 2013-11-29 |
605 | + self.assert_raises_validation_error( |
606 | + self.json, self.schema, _SchemaValidatorWithoutFormatValidation) |
607 | |
608 | |
609 | class ObjectExtraPropertiesTestCase( |
610 | |
611 | === modified file 'src/cloudspacesclient/tests/unit/test_ubuntuone.py' |
612 | --- src/cloudspacesclient/tests/unit/test_ubuntuone.py 2013-11-20 05:06:14 +0000 |
613 | +++ src/cloudspacesclient/tests/unit/test_ubuntuone.py 2013-11-29 08:45:45 +0000 |
614 | @@ -17,7 +17,6 @@ |
615 | |
616 | """Unit tests for the Ubuntu One server adapter for Cloudspaces tests.""" |
617 | |
618 | -import contextlib |
619 | import unittest |
620 | |
621 | import mock |
622 | @@ -119,7 +118,7 @@ |
623 | 'Failed to register SSO user. ' + str(sso_exception)) |
624 | self.assertFalse(self.server.create_u1_user_mock.called) |
625 | |
626 | - def test_get_auth_credentials_should_return_oauth1(self): |
627 | + def test_get_auth_session_should_return_oauth1session(self): |
628 | self.server.sso_login_mock.return_value = { |
629 | 'consumer_key': 'test consumer key', |
630 | 'consumer_secret': 'test consumer secret', |
631 | @@ -128,7 +127,7 @@ |
632 | } |
633 | user = ubuntuone.User.make_unique(unique_id='id') |
634 | |
635 | - auth = self.server.get_auth_credentials(user) |
636 | + session = self.server.get_auth_session(user) |
637 | |
638 | self.server.sso_login_mock.assert_called_once_with( |
639 | { |
640 | @@ -137,26 +136,28 @@ |
641 | 'token_name': 'Cloudspaces API test', |
642 | } |
643 | ) |
644 | - self.assertIsInstance(auth, requests_oauthlib.OAuth1) |
645 | - self.assertEqual(auth.client.client_key, 'test consumer key') |
646 | - self.assertEqual(auth.client.client_secret, 'test consumer secret') |
647 | - self.assertEqual(auth.client.resource_owner_key, 'test token key') |
648 | - self.assertEqual( |
649 | - auth.client.resource_owner_secret, 'test token secret') |
650 | + self.assertIsInstance(session, requests_oauthlib.OAuth1Session) |
651 | + self.assertEqual( |
652 | + session._client.client.client_key, 'test consumer key') |
653 | + self.assertEqual( |
654 | + session._client.client.client_secret, 'test consumer secret') |
655 | + self.assertEqual( |
656 | + session._client.client.resource_owner_key, 'test token key') |
657 | + self.assertEqual( |
658 | + session._client.client.resource_owner_secret, 'test token secret') |
659 | |
660 | def test_get_u1_api_client(self): |
661 | - with contextlib.nested( |
662 | - mock.patch('cloudspacesclient.config.get'), |
663 | - mock.patch.object(self.server, 'get_auth_credentials') |
664 | - ) as (config_mock, credentials_mock): |
665 | - config_mock.return_value = 'http://example.com' |
666 | - credentials_mock.return_value = 'test credentials' |
667 | - client = self.server._get_u1_api_client('dummy user') |
668 | + with mock.patch('cloudspacesclient.config.get') as config_mock: |
669 | + with mock.patch.object( |
670 | + self.server, 'get_auth_session') as session_mock: |
671 | + config_mock.return_value = 'http://example.com' |
672 | + session_mock.return_value = 'test session' |
673 | + client = self.server._get_u1_api_client('dummy user') |
674 | |
675 | self.assertIsInstance(client, ubuntuone.UbuntuOneAPIClient) |
676 | self.assertEqual( |
677 | client.service_root_url, 'http://example.com/api/') |
678 | - self.assertEqual(client.auth, 'test credentials') |
679 | + self.assertEqual(client.session, 'test session') |
680 | |
681 | def test_create_test_user_should_create_user_in_u1(self): |
682 | self.server.create_test_user() |
683 | @@ -178,25 +179,28 @@ |
684 | |
685 | def setUp(self): |
686 | super(UbuntuOneAPITestCase, self).setUp() |
687 | + self.session_mock = mock.Mock() |
688 | self.api_client = ubuntuone.UbuntuOneAPIClient( |
689 | - 'dummy url', 'dummy auth') |
690 | + 'dummy url', self.session_mock) |
691 | |
692 | def test_get_account_info_should_return_request(self): |
693 | - with mock.patch('requests.get') as get_mock: |
694 | - get_mock.return_value = utils.RequestResponseMock( |
695 | - ok=True, status_code=200, content='test content') |
696 | - response = self.api_client.get_account_info() |
697 | + mock_attributes = {'get.return_value': utils.RequestResponseMock( |
698 | + ok=True, status_code=200, content='test content') |
699 | + } |
700 | + self.session_mock.configure_mock(**mock_attributes) |
701 | + response = self.api_client.get_account_info() |
702 | |
703 | self.assertTrue(response.ok) |
704 | self.assertEqual(response.status_code, 200) |
705 | self.assertEqual(response.content, 'test content') |
706 | |
707 | def test_get_account_info_failure(self): |
708 | - with mock.patch('requests.get') as get_mock: |
709 | - get_mock.return_value = utils.RequestResponseMock( |
710 | - ok=False, status_code='test error code', reason='test reason') |
711 | - with self.assertRaises(ubuntuone.APIException) as e: |
712 | - self.api_client.get_account_info() |
713 | + mock_attributes = {'get.return_value': utils.RequestResponseMock( |
714 | + ok=False, status_code='test error code', reason='test reason') |
715 | + } |
716 | + self.session_mock.configure_mock(**mock_attributes) |
717 | + with self.assertRaises(ubuntuone.APIException) as e: |
718 | + self.api_client.get_account_info() |
719 | |
720 | self.assertEqual(str(e.exception), 'test error code: test reason') |
721 | |
722 | |
723 | === modified file 'src/cloudspacesclient/ubuntuone.py' |
724 | --- src/cloudspacesclient/ubuntuone.py 2013-11-26 16:13:41 +0000 |
725 | +++ src/cloudspacesclient/ubuntuone.py 2013-11-29 08:45:45 +0000 |
726 | @@ -17,10 +17,9 @@ |
727 | |
728 | """Ubuntu One server adapter for Cloudspaces tests.""" |
729 | |
730 | -import urlparse |
731 | +import urllib.parse |
732 | import uuid |
733 | |
734 | -import requests |
735 | import requests_oauthlib |
736 | import ssoclient.v2 |
737 | |
738 | @@ -89,7 +88,7 @@ |
739 | |
740 | def _get_sso_api_client(self): |
741 | return ssoclient.v2.V2ApiClient( |
742 | - urlparse.urljoin(self._get_sso_server_url(), 'api/v2')) |
743 | + urllib.parse.urljoin(self._get_sso_server_url(), 'api/v2')) |
744 | |
745 | def _get_sso_server_url(self): |
746 | return config.get('ubuntuone', 'sso_server_url') |
747 | @@ -102,9 +101,9 @@ |
748 | 'Failed to create U1 user. ' + str(e)) |
749 | |
750 | def _get_u1_api_client(self, user): |
751 | - url = urlparse.urljoin(self._get_ubuntuone_server_url(), 'api/') |
752 | - auth = self.get_auth_credentials(user) |
753 | - return UbuntuOneAPIClient(url, auth) |
754 | + url = urllib.parse.urljoin(self._get_ubuntuone_server_url(), 'api/') |
755 | + auth_session = self.get_auth_session(user) |
756 | + return UbuntuOneAPIClient(url, auth_session) |
757 | |
758 | def _get_ubuntuone_server_url(self): |
759 | return config.get('ubuntuone', 'ubuntuone_server_url') |
760 | @@ -113,8 +112,8 @@ |
761 | # Currently we can't delete users from U1 and SSO. |
762 | pass |
763 | |
764 | - def get_auth_credentials(self, user): |
765 | - """Return the auth credentials to sign the Cloudspaces API requests. |
766 | + def get_auth_session(self, user): |
767 | + """Return the auth session to sign the Cloudspaces API requests. |
768 | |
769 | :parameter user: An object with the user information. |
770 | |
771 | @@ -124,7 +123,7 @@ |
772 | password=user.password, |
773 | token_name='Cloudspaces API test') |
774 | response = self._get_sso_api_client().login(data) |
775 | - return requests_oauthlib.OAuth1( |
776 | + return requests_oauthlib.OAuth1Session( |
777 | response.get('consumer_key'), response.get('consumer_secret'), |
778 | response.get('token_key'), response.get('token_secret')) |
779 | |
780 | @@ -141,10 +140,10 @@ |
781 | |
782 | content_type = JSON_MIME_TYPE |
783 | |
784 | - def __init__(self, service_root_url, auth): |
785 | + def __init__(self, service_root_url, session): |
786 | super(UbuntuOneAPIClient, self).__init__() |
787 | self.service_root_url = service_root_url |
788 | - self.auth = auth |
789 | + self.session = session |
790 | |
791 | def create_user(self): |
792 | """Create the authenticated user in Ubuntu One, if it doesn't exist.""" |
793 | @@ -153,9 +152,8 @@ |
794 | |
795 | def get_account_info(self): |
796 | """Get the account information of the authenticated user.""" |
797 | - url = urlparse.urljoin(self.service_root_url, 'account/') |
798 | - response = requests.get( |
799 | - url, auth=self.auth, headers=self._get_headers()) |
800 | + url = urllib.parse.urljoin(self.service_root_url, 'account/') |
801 | + response = self.session.get(url, headers=self._get_headers()) |
802 | if not response.ok: |
803 | raise APIException( |
804 | "{0}: {1}".format(response.status_code, response.reason)) |