Merge lp:~elopio/cloudspacesclient/cloudspacer into lp:cloudspacesclient

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
Reviewer Review Type Date Requested Status
Ubuntu One hackers Pending
Review via email: mp+197145@code.launchpad.net

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.
33. By Leo Arias

Fixed pep8.

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))

Subscribers

People subscribed via source and target branches

to all changes: