Merge lp:~nataliabidart/ubuntuone-control-panel/use-webclient into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 265
Merged at revision: 258
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/use-webclient
Merge into: lp:ubuntuone-control-panel
Diff against target: 2046 lines (+478/-1084)
24 files modified
setup.py (+0/-1)
ubuntuone/controlpanel/backend.py (+46/-27)
ubuntuone/controlpanel/gui/__init__.py (+33/-50)
ubuntuone/controlpanel/gui/qt/folders.py (+2/-7)
ubuntuone/controlpanel/gui/qt/gotoweb.py (+2/-12)
ubuntuone/controlpanel/gui/qt/gui.py (+3/-3)
ubuntuone/controlpanel/gui/qt/main/__init__.py (+5/-14)
ubuntuone/controlpanel/gui/qt/main/linux.py (+11/-3)
ubuntuone/controlpanel/gui/qt/main/windows.py (+21/-0)
ubuntuone/controlpanel/gui/qt/systray.py (+4/-5)
ubuntuone/controlpanel/gui/qt/tests/__init__.py (+29/-21)
ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py (+5/-52)
ubuntuone/controlpanel/gui/qt/tests/test_signin.py (+2/-5)
ubuntuone/controlpanel/gui/qt/tests/test_start.py (+89/-91)
ubuntuone/controlpanel/gui/qt/tests/test_systray.py (+0/-2)
ubuntuone/controlpanel/gui/tests/test_url_sign.py (+0/-102)
ubuntuone/controlpanel/tests/test_backend.py (+66/-1)
ubuntuone/controlpanel/tests/test_web_client.py (+107/-84)
ubuntuone/controlpanel/web_client.py (+53/-61)
ubuntuone/controlpanel/web_client/libsoup.py (+0/-133)
ubuntuone/controlpanel/web_client/tests/__init__.py (+0/-19)
ubuntuone/controlpanel/web_client/tests/test_libsoup.py (+0/-106)
ubuntuone/controlpanel/web_client/tests/test_txwebclient.py (+0/-177)
ubuntuone/controlpanel/web_client/txwebclient.py (+0/-108)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/use-webclient
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) Approve
Roberto Alsina (community) Approve
Review via email: mp+91676@code.launchpad.net

Commit message

 - Replaced custom webclient with the one from ubuntu-sso-client
   (LP: #926311).
- Removed the dependency on qt4reactor for Linux implementation.

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) wrote :

Looks good to me....

review: Approve
Revision history for this message
Natalia Bidart (nataliabidart) wrote :
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Code looks nice, all tests pass, tested IRL on linux.

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :

The attempt to merge lp:~nataliabidart/ubuntuone-control-panel/use-webclient into lp:ubuntuone-control-panel failed. Below is the output from the failed tests.

*** Running test suite for ubuntuone/controlpanel ***

Traceback (most recent call last):
  File "/usr/bin/u1trial", line 325, in <module>
    main()
  File "/usr/bin/u1trial", line 305, in main
    suite = trial_runner.get_suite(config)
  File "/usr/bin/u1trial", line 184, in get_suite
    config['ignore-paths']))
  File "/usr/bin/u1trial", line 168, in _collect_tests
    module_suite = self._load_unittest(filepath)
  File "/usr/bin/u1trial", line 108, in _load_unittest
    module = __import__(modpath, None, None, [""])
  File "/home/tarmac/cache/ubuntuone-control-panel/trunk/ubuntuone/controlpanel/tests/test_web_client.py", line 24, in <module>
    from ubuntu_sso.utils.webclient.tests import BaseMockWebServer
ImportError: No module named tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'setup.py'
2--- setup.py 2012-01-26 13:31:22 +0000
3+++ setup.py 2012-02-07 14:03:19 +0000
4@@ -247,7 +247,6 @@
5 'ubuntuone.controlpanel.gui.qt.ui',
6 'ubuntuone.controlpanel.sd_client',
7 'ubuntuone.controlpanel.utils',
8- 'ubuntuone.controlpanel.web_client',
9 ],
10 extra_path='ubuntuone-control-panel',
11 data_files=[
12
13=== modified file 'ubuntuone/controlpanel/backend.py'
14--- ubuntuone/controlpanel/backend.py 2012-01-25 14:49:59 +0000
15+++ ubuntuone/controlpanel/backend.py 2012-02-07 14:03:19 +0000
16@@ -34,41 +34,47 @@
17
18 from ubuntuone.controlpanel import sd_client, replication_client
19 from ubuntuone.controlpanel.logger import setup_logging, log_call
20-# pylint: disable=W0611
21-from ubuntuone.controlpanel.web_client import (UnauthorizedError,
22- web_client_factory, WebClientError)
23-# pylint: enable=W0611
24+from ubuntuone.controlpanel.web_client import (
25+ UnauthorizedError,
26+ WebClient,
27+ WebClientError,
28+)
29
30 logger = setup_logging('backend')
31
32-ACCOUNT_API = "account/"
33-QUOTA_API = "quota/"
34-DEVICES_API = "1.0/devices/"
35-DEVICE_REMOVE_API = "1.0/devices/remove/%s/%s"
36-DEVICE_TYPE_PHONE = "Phone"
37-DEVICE_TYPE_COMPUTER = "Computer"
38-AUTOCONNECT_KEY = 'autoconnect'
39-SHOW_ALL_NOTIFICATIONS_KEY = 'show_all_notifications'
40-SHARE_AUTOSUBSCRIBE_KEY = 'share_autosubscribe'
41-UDF_AUTOSUBSCRIBE_KEY = 'udf_autosubscribe'
42-LIMIT_BW_KEY = 'limit_bandwidth'
43-UPLOAD_KEY = "max_upload_speed"
44-DOWNLOAD_KEY = "max_download_speed"
45-
46-FILE_SYNC_DISABLED = 'file-sync-disabled'
47-FILE_SYNC_DISCONNECTED = 'file-sync-disconnected'
48-FILE_SYNC_ERROR = 'file-sync-error'
49-FILE_SYNC_IDLE = 'file-sync-idle'
50-FILE_SYNC_STARTING = 'file-sync-starting'
51-FILE_SYNC_STOPPED = 'file-sync-stopped'
52-FILE_SYNC_SYNCING = 'file-sync-syncing'
53-FILE_SYNC_UNKNOWN = 'file-sync-unknown'
54+ACCOUNT_API = u"account/"
55+DEVICES_API = u"1.0/devices/"
56+DEVICE_REMOVE_API = u"1.0/devices/remove/%s/%s"
57+QUOTA_API = u"quota/"
58+
59+DEVICE_TYPE_PHONE = u"Phone"
60+DEVICE_TYPE_COMPUTER = u"Computer"
61+
62+AUTOCONNECT_KEY = u'autoconnect'
63+SHOW_ALL_NOTIFICATIONS_KEY = u'show_all_notifications'
64+SHARE_AUTOSUBSCRIBE_KEY = u'share_autosubscribe'
65+UDF_AUTOSUBSCRIBE_KEY = u'udf_autosubscribe'
66+LIMIT_BW_KEY = u'limit_bandwidth'
67+UPLOAD_KEY = u'max_upload_speed'
68+DOWNLOAD_KEY = u'max_download_speed'
69+
70+FILE_SYNC_DISABLED = u'file-sync-disabled'
71+FILE_SYNC_DISCONNECTED = u'file-sync-disconnected'
72+FILE_SYNC_ERROR = u'file-sync-error'
73+FILE_SYNC_IDLE = u'file-sync-idle'
74+FILE_SYNC_STARTING = u'file-sync-starting'
75+FILE_SYNC_STOPPED = u'file-sync-stopped'
76+FILE_SYNC_SYNCING = u'file-sync-syncing'
77+FILE_SYNC_UNKNOWN = u'file-sync-unknown'
78
79 MSG_KEY = 'message'
80 STATUS_KEY = 'status'
81
82 CONTACTS_PKG = 'thunderbird-couchdb'
83
84+UBUNTUONE_FROM_OAUTH = u'https://one.ubuntu.com/api/1.0/from_oauth/'
85+UBUNTUONE_LINK = u'https://one.ubuntu.com/'
86+
87
88 def append_path_sep(path):
89 """If 'path' does not end with the path separator, append it."""
90@@ -135,7 +141,7 @@
91
92 self.login_client = CredentialsManagementTool()
93 self.sd_client = sd_client.SyncDaemonClient()
94- self.wc = web_client_factory(self.get_credentials)
95+ self.wc = WebClient(self.get_credentials)
96
97 logger.info('ControlBackend: instance started.')
98
99@@ -304,6 +310,19 @@
100 returnValue(path)
101
102 @inlineCallbacks
103+ def build_signed_iri(self, iri):
104+ """Returned an OAuth signed iri."""
105+ credentials = None
106+ if iri.startswith(UBUNTUONE_LINK):
107+ credentials = yield self.get_credentials()
108+
109+ if credentials:
110+ parameters = {'next': iri}
111+ iri = yield self.wc.build_signed_iri(UBUNTUONE_FROM_OAUTH,
112+ parameters)
113+ returnValue(iri)
114+
115+ @inlineCallbacks
116 def get_credentials(self):
117 """Find credentials."""
118 if not self._credentials:
119
120=== modified file 'ubuntuone/controlpanel/gui/__init__.py'
121--- ubuntuone/controlpanel/gui/__init__.py 2011-10-06 20:38:39 +0000
122+++ ubuntuone/controlpanel/gui/__init__.py 2012-02-07 14:03:19 +0000
123@@ -21,56 +21,55 @@
124
125 import gettext
126
127-from oauth import oauth
128+from ubuntuone.controlpanel.backend import UBUNTUONE_LINK
129+
130
131 _ = gettext.gettext
132
133
134-ERROR_COLOR = 'red'
135+ERROR_COLOR = u'red'
136 KILOBYTES = 1024
137 NO_OP = lambda *a, **kw: None
138 # http://design.canonical.com/the-toolkit/ubuntu-logo-and-circle-of-friends/
139-ORANGE = '#DD4814'
140+ORANGE = u'#DD4814'
141 QUOTA_THRESHOLD = 0.95
142 SHARES_MIN_SIZE_FULL = 1048576
143-SUCCESS_COLOR = 'green'
144+SUCCESS_COLOR = u'green'
145
146 ERROR_ICON = u'✘'
147 SYNCING_ICON = u'⇅'
148 IDLE_ICON = u'✔'
149
150-CONTACT_ICON_NAME = 'avatar-default'
151-FOLDER_ICON_NAME = 'folder'
152-SHARE_ICON_NAME = 'folder-remote'
153-MUSIC_ICON_NAME = 'audio-x-generic'
154-
155-CONTACTS_ICON = 'contacts.png'
156-FACEBOOK_LOGO = 'facebook.png'
157-FILES_ICON = 'files.png'
158-OVERVIEW_BANNER = 'overview.png'
159-TWITTER_LOGO = 'twitter.png'
160-MUSIC_STORE_ICON = 'music-store.png'
161-MUSIC_STREAM_ICON = 'music-stream.png'
162-NOTES_ICON = 'notes.png'
163-SERVICES_CONTACTS_ICON = 'services-contacts.png'
164-SERVICES_FILES_EXAMPLE = 'services-files-example.png'
165-SERVICES_FILES_ICON = 'services-files.png'
166-
167-FILE_URI_PREFIX = 'file://'
168-UBUNTUONE_FROM_OAUTH = 'https://one.ubuntu.com/api/1.0/from_oauth/'
169-UBUNTUONE_LINK = 'https://one.ubuntu.com/'
170+CONTACT_ICON_NAME = u'avatar-default'
171+FOLDER_ICON_NAME = u'folder'
172+SHARE_ICON_NAME = u'folder-remote'
173+MUSIC_ICON_NAME = u'audio-x-generic'
174+
175+CONTACTS_ICON = u'contacts.png'
176+FACEBOOK_LOGO = u'facebook.png'
177+FILES_ICON = u'files.png'
178+OVERVIEW_BANNER = u'overview.png'
179+TWITTER_LOGO = u'twitter.png'
180+MUSIC_STORE_ICON = u'music-store.png'
181+MUSIC_STREAM_ICON = u'music-stream.png'
182+NOTES_ICON = u'notes.png'
183+SERVICES_CONTACTS_ICON = u'services-contacts.png'
184+SERVICES_FILES_EXAMPLE = u'services-files-example.png'
185+SERVICES_FILES_ICON = u'services-files.png'
186+
187+FILE_URI_PREFIX = u'file://'
188
189 CONTACTS_LINK = UBUNTUONE_LINK
190-EDIT_ACCOUNT_LINK = UBUNTUONE_LINK + 'account/'
191-EDIT_DEVICES_LINK = EDIT_ACCOUNT_LINK + 'machines/'
192-EDIT_PROFILE_LINK = 'https://login.ubuntu.com/'
193-EDIT_SERVICES_LINK = UBUNTUONE_LINK + 'services'
194-FACEBOOK_LINK = 'http://www.facebook.com/ubuntuone/'
195-GET_SUPPORT_LINK = UBUNTUONE_LINK + 'support/'
196+EDIT_ACCOUNT_LINK = UBUNTUONE_LINK + u'account/'
197+EDIT_DEVICES_LINK = EDIT_ACCOUNT_LINK + u'machines/'
198+EDIT_PROFILE_LINK = u'https://login.ubuntu.com/'
199+EDIT_SERVICES_LINK = UBUNTUONE_LINK + u'services'
200+FACEBOOK_LINK = u'http://www.facebook.com/ubuntuone/'
201+GET_SUPPORT_LINK = UBUNTUONE_LINK + u'support/'
202 LEARN_MORE_LINK = UBUNTUONE_LINK
203-MANAGE_FILES_LINK = UBUNTUONE_LINK + 'files/'
204-RESET_PASSWORD_LINK = EDIT_PROFILE_LINK + '+forgot_password'
205-TWITTER_LINK = 'http://twitter.com/ubuntuone/'
206+MANAGE_FILES_LINK = UBUNTUONE_LINK + u'files/'
207+RESET_PASSWORD_LINK = EDIT_PROFILE_LINK + u'+forgot_password'
208+TWITTER_LINK = u'http://twitter.com/ubuntuone/'
209
210 ALWAYS_SUBSCRIBED = _('Always in sync')
211 CONNECT_BUTTON_LABEL = _('Connect to Ubuntu One')
212@@ -86,7 +85,7 @@
213 'previous values were restored.')
214 DEVICE_CONFIRM_REMOVE = _('Are you sure you want to remove this device '
215 'from Ubuntu One?')
216-DEVICE_REMOVABLE_PREFIX = 'Ubuntu One @ '
217+DEVICE_REMOVABLE_PREFIX = u'Ubuntu One @ '
218 DEVICE_REMOVAL_ERROR = _('The device could not be removed.')
219 DEVICES_BUTTON_TOOLTIP = _('Manage devices registered with your personal '
220 'cloud')
221@@ -185,19 +184,3 @@
222 str_bytes = str_bytes.rstrip('0')
223 str_bytes = str_bytes.rstrip('.')
224 return '%s %s' % (str_bytes, units[unit])
225-
226-
227-def sign_url(url, credentials, timestamp=None):
228- """Sign the URL using the currently available credentials."""
229- consumer = oauth.OAuthConsumer(credentials["consumer_key"],
230- credentials["consumer_secret"])
231- token = oauth.OAuthToken(credentials["token"],
232- credentials["token_secret"])
233- parameters = {'next': url, 'oauth_timestamp': timestamp}
234- request = oauth.OAuthRequest.from_consumer_and_token(
235- http_url=UBUNTUONE_FROM_OAUTH, http_method='GET',
236- oauth_consumer=consumer, token=token,
237- parameters=parameters)
238- sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
239- request.sign_request(sig_method, consumer, token)
240- return request.to_url()
241
242=== modified file 'ubuntuone/controlpanel/gui/qt/folders.py'
243--- ubuntuone/controlpanel/gui/qt/folders.py 2012-01-18 17:40:33 +0000
244+++ ubuntuone/controlpanel/gui/qt/folders.py 2012-02-07 14:03:19 +0000
245@@ -1,8 +1,6 @@
246 # -*- coding: utf-8 -*-
247-
248-# Authors: Alejandro J. Cura <alecu@canonical.com>
249 #
250-# Copyright 2011 Canonical Ltd.
251+# Copyright 2011-2012 Canonical Ltd.
252 #
253 # This program is free software: you can redistribute it and/or modify it
254 # under the terms of the GNU General Public License version 3, as published
255@@ -85,10 +83,7 @@
256 def on_folder_created(self, new_folder):
257 """Reload folder info after folder creation."""
258 self.is_processing = True
259- # hack to ensure that syncdaemon updates the folder list.
260- # pylint: disable=W0404, E1101
261- from twisted.internet import reactor
262- reactor.callLater(2, self.load)
263+ self.load()
264
265 # pylint: disable=E0202
266 @defer.inlineCallbacks
267
268=== modified file 'ubuntuone/controlpanel/gui/qt/gotoweb.py'
269--- ubuntuone/controlpanel/gui/qt/gotoweb.py 2011-10-05 23:12:23 +0000
270+++ ubuntuone/controlpanel/gui/qt/gotoweb.py 2012-02-07 14:03:19 +0000
271@@ -23,8 +23,7 @@
272 from twisted.internet import defer
273
274 from ubuntuone.controlpanel import cache
275-from ubuntuone.controlpanel.gui import qt, sign_url, UBUNTUONE_LINK
276-from ubuntuone.controlpanel.web_client.txwebclient import timestamp_checker
277+from ubuntuone.controlpanel.gui import qt
278
279
280 class GoToWebButton(cache.Cache, QtGui.QPushButton):
281@@ -44,14 +43,5 @@
282 def on_clicked(self):
283 """Open self.uri if not None, do nothing otherwise."""
284 if self.uri is not None:
285-
286- credentials = None
287- if self.uri.startswith(UBUNTUONE_LINK):
288- credentials = yield self.backend.get_credentials()
289-
290- uri = self.uri
291- if credentials:
292- timestamp = yield timestamp_checker.get_faithful_time()
293- uri = yield sign_url(uri, credentials, timestamp)
294-
295+ uri = yield self.backend.build_signed_iri(self.uri)
296 qt.uri_hook(uri)
297
298=== modified file 'ubuntuone/controlpanel/gui/qt/gui.py'
299--- ubuntuone/controlpanel/gui/qt/gui.py 2011-09-15 14:42:24 +0000
300+++ ubuntuone/controlpanel/gui/qt/gui.py 2012-02-07 14:03:19 +0000
301@@ -52,20 +52,20 @@
302 # pylint: enable=C0103
303
304
305-def start(stop, minimized=False, with_icon=False):
306+def start(close_callback, minimized=False, with_icon=False):
307 """Show the UI elements."""
308 # pylint: disable=W0404, F0401
309 if not minimized:
310 if with_icon or minimized:
311 window = MainWindow()
312 else:
313- window = MainWindow(close_callback=stop)
314+ window = MainWindow(close_callback=close_callback)
315 window.show()
316 else:
317 window = None
318 if with_icon or minimized:
319 QtGui.QApplication.instance().setQuitOnLastWindowClosed(False)
320- icon = TrayIcon(window=window)
321+ icon = TrayIcon(window=window, close_callback=close_callback)
322 else:
323 icon = None
324 return icon, window
325
326=== modified file 'ubuntuone/controlpanel/gui/qt/main/__init__.py'
327--- ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-01-19 19:00:54 +0000
328+++ ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-02-07 14:03:19 +0000
329@@ -14,7 +14,7 @@
330 # You should have received a copy of the GNU General Public License along
331 # with this program. If not, see <http://www.gnu.org/licenses/>.
332
333-"""Provide the correct reactor and ui integration."""
334+"""Provide the correct ui main module."""
335
336 import sys
337
338@@ -37,32 +37,23 @@
339
340
341 def main(switch_to='', alert=False, minimized=False, with_icon=False):
342- """Start the Qt reactor and open the main window."""
343+ """Start the Qt mainloop and open the main window."""
344 # The following cannot be imported outside this function
345 # because u1trial already provides a reactor.
346
347- # The main loop MUST be initialized before importing the reactor
348 app = UniqueApplication(sys.argv, "ubuntuone-control-panel")
349 source.main(app)
350+
351 qss = QtCore.QResource(":/ubuntuone.qss")
352 app.setStyleSheet(qss.data())
353
354- # Reimport 'qt4reactor', 'reactor', 'start', pylint: disable=W0404, F0401
355- import qt4reactor
356- qt4reactor.install()
357-
358- from twisted.internet import reactor
359 from ubuntuone.controlpanel.gui.qt.gui import start
360- # pylint: enable=W0404, F0401
361-
362- # Module 'reactor' has no 'run'/'stop' member, pylint: disable=E1101
363
364 # Unused variable 'window', 'icon', pylint: disable=W0612
365- icon, window = start(reactor.stop,
366+ icon, window = start(lambda: source.main_quit(app),
367 minimized=minimized, with_icon=with_icon)
368 # pylint: enable=W0612
369 if icon:
370 app.new_instance.connect(icon.restore_window)
371
372- reactor.run()
373- # pylint: enable=E1101
374+ source.main_start(app)
375
376=== modified file 'ubuntuone/controlpanel/gui/qt/main/linux.py'
377--- ubuntuone/controlpanel/gui/qt/main/linux.py 2012-01-26 12:41:08 +0000
378+++ ubuntuone/controlpanel/gui/qt/main/linux.py 2012-02-07 14:03:19 +0000
379@@ -16,10 +16,18 @@
380
381 """Main method to be used on linux."""
382
383-from dbus.mainloop.qt import DBusQtMainLoop
384-
385
386 def main(app):
387 """Apply style sheet."""
388- # The DBus main loop MUST be initialized before importing the reactor
389+ from dbus.mainloop.qt import DBusQtMainLoop
390 DBusQtMainLoop(set_as_default=True)
391+
392+
393+def main_start(app):
394+ """Start the mainloop."""
395+ app.exec_()
396+
397+
398+def main_quit(app):
399+ """Stop the mainloop."""
400+ app.exit()
401
402=== modified file 'ubuntuone/controlpanel/gui/qt/main/windows.py'
403--- ubuntuone/controlpanel/gui/qt/main/windows.py 2012-01-26 12:41:08 +0000
404+++ ubuntuone/controlpanel/gui/qt/main/windows.py 2012-02-07 14:03:19 +0000
405@@ -24,3 +24,24 @@
406 # Apply font to the entire application
407 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-R.ttf')
408 QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-B.ttf')
409+
410+ import qt4reactor
411+ qt4reactor.install()
412+
413+
414+# Module 'reactor' has no 'run'/'stop' member, pylint: disable=E1101
415+
416+
417+def main_start(app):
418+ """Start the mainloop."""
419+ from twisted.internet import reactor
420+ reactor.run()
421+
422+
423+def main_quit(app):
424+ """Stop the mainloop."""
425+ from twisted.internet import reactor
426+ reactor.stop()
427+
428+
429+# pylint: enable=E1101
430
431=== modified file 'ubuntuone/controlpanel/gui/qt/systray.py'
432--- ubuntuone/controlpanel/gui/qt/systray.py 2011-12-27 13:54:49 +0000
433+++ ubuntuone/controlpanel/gui/qt/systray.py 2012-02-07 14:03:19 +0000
434@@ -25,7 +25,7 @@
435
436 """System notification icon."""
437
438- def __init__(self, window=None):
439+ def __init__(self, window=None, close_callback=lambda: None):
440 super(TrayIcon, self).__init__(None)
441 self.setIcon(QtGui.QIcon(":/u1icon.png"))
442 self.setVisible(True)
443@@ -41,6 +41,8 @@
444 self.context_menu.addAction(self.quit)
445 self.setContextMenu(self.context_menu)
446
447+ self.close_callback = close_callback
448+
449 def on_activated(self, reason):
450 """The user activated the icon."""
451 if reason == self.Trigger: # Left-click
452@@ -73,7 +75,4 @@
453 except:
454 # Maybe it was not running?
455 pass
456- # pylint: disable=W0404
457- from twisted.internet import reactor
458- # pylint: enable=W0404
459- reactor.stop()
460+ self.close_callback()
461
462=== modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py'
463--- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-01-18 19:52:25 +0000
464+++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-02-07 14:03:19 +0000
465@@ -20,7 +20,6 @@
466
467 import logging
468 import os
469-import urllib
470
471 from PyQt4 import QtGui, QtCore
472 from twisted.internet import defer
473@@ -28,8 +27,7 @@
474
475 from ubuntuone.controlpanel import backend, cache
476 from ubuntuone.controlpanel.tests import TestCase, EXPECTED_ACCOUNT_INFO, TOKEN
477-from ubuntuone.controlpanel.gui import qt, UBUNTUONE_FROM_OAUTH
478-from ubuntuone.controlpanel.gui.qt import gotoweb
479+from ubuntuone.controlpanel.gui import qt
480 from ubuntuone.controlpanel.gui.tests import FakedObject, USER_HOME
481
482 # Attribute 'yyy' defined outside __init__, access to a protected member
483@@ -107,19 +105,31 @@
484
485 next_result = []
486 exposed_methods = [
487- 'account_info', # account
488- 'devices_info', 'device_names_info', # devices
489- 'change_device_settings', 'remove_device',
490- 'volumes_info', 'change_volume_settings', # volumes
491- 'create_folder', 'validate_path_for_folder',
492- 'replications_info', 'change_replication_settings', # replications
493- 'file_sync_status', 'enable_files', 'disable_files', # files
494- 'connect_files', 'disconnect_files',
495- 'restart_files', 'start_files', 'stop_files',
496+ 'account_info',
497+ 'build_signed_iri',
498+ 'change_device_settings',
499+ 'change_file_sync_settings',
500+ 'change_replication_settings',
501+ 'change_volume_settings',
502+ 'connect_files',
503+ 'create_folder',
504+ 'device_names_info',
505+ 'devices_info',
506+ 'disable_files',
507+ 'disconnect_files',
508+ 'enable_files',
509 'file_sync_settings_info',
510- 'change_file_sync_settings',
511+ 'file_sync_status',
512+ 'login',
513+ 'remove_device',
514+ 'replications_info',
515+ 'restart_files',
516 'restore_file_sync_settings',
517- 'shutdown', 'login',
518+ 'shutdown',
519+ 'start_files',
520+ 'stop_files',
521+ 'validate_path_for_folder',
522+ 'volumes_info',
523 ]
524
525 def get_credentials(self):
526@@ -131,6 +141,9 @@
527 """Fake home return."""
528 return USER_HOME
529
530+ def build_signed_iri(self, iri):
531+ """Fake iri signing."""
532+
533
534 class CrashyBackendException(Exception):
535 """A faked backend crash."""
536@@ -284,19 +297,14 @@
537
538 def assert_uri_hook_called(self, button, url):
539 """Check that uri_hook was called with 'url' when clicking 'button'."""
540- fake_time = 12345678
541+ self.patch(self.ui.backend, 'next_result', url)
542 self.patch(qt, 'uri_hook', self._set_called)
543- self.patch(gotoweb.timestamp_checker, "get_faithful_time",
544- lambda: defer.succeed(fake_time))
545 button.click()
546
547 self.assertEqual(len(self._called), 2, 'uri_hook must be called.')
548 self.assertEqual(len(self._called[0]), 1, 'uri_hook must be called.')
549 actual_url = self._called[0][0]
550- if actual_url.startswith(UBUNTUONE_FROM_OAUTH):
551- self.assertIn(urllib.urlencode({'next': url}), actual_url)
552- else:
553- self.assertEqual(actual_url, url)
554+ self.assertEqual(actual_url, url)
555
556 def test_init_loads_ui(self, expected_setup_ui=None):
557 """The __init__ method loads the ui."""
558
559=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py'
560--- ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2011-10-06 20:33:42 +0000
561+++ ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2012-02-07 14:03:19 +0000
562@@ -20,7 +20,7 @@
563
564 from twisted.internet import defer
565
566-from ubuntuone.controlpanel.gui import qt, UBUNTUONE_LINK
567+from ubuntuone.controlpanel.gui import qt
568 from ubuntuone.controlpanel.gui.qt import gotoweb as gui
569 from ubuntuone.controlpanel.gui.qt.tests import (
570 BaseTestCase,
571@@ -53,11 +53,14 @@
572
573 def test_open_uri_when_clicked(self):
574 """When clicking the button, the uri is opened."""
575+ expected_iri = 'foo-bar-baz'
576+ self.patch(self.ui.backend, 'build_signed_iri', lambda i: expected_iri)
577 self.patch(qt, 'uri_hook', self._set_called)
578 self.ui.uri = 'yadda-yadda-yoo'
579+
580 self.ui.click()
581
582- self.assertEqual(self._called, ((self.ui.uri,), {}))
583+ self.assertEqual(self._called, ((expected_iri,), {}))
584
585 def test_do_nothing_on_clicked_if_uri_is_none(self):
586 """When clicking the button, if the uri is None, do nothing."""
587@@ -66,53 +69,3 @@
588 self.ui.click()
589
590 self.assertEqual(self._called, False)
591-
592-
593-class SignUrlTestCase(GoToWebButtonTestCase):
594- """The test suite for the sign url management."""
595-
596- @defer.inlineCallbacks
597- def setUp(self):
598- yield super(SignUrlTestCase, self).setUp()
599- self.patch(qt, 'uri_hook', lambda url: None)
600- self.patch(gui, 'sign_url', self._set_called)
601- self.creds = yield self.ui.backend.get_credentials()
602- assert len(self.creds) > 0
603-
604- def test_without_ubuntuone_prefix(self):
605- """If given url is not an ubuntuone url, don't sign it."""
606- self.ui.uri = 'bad_prefix' + UBUNTUONE_LINK
607- self.ui.click()
608-
609- self.assertFalse(self._called)
610-
611- def test_with_ubuntuone_prefix(self):
612- """If given url is an ubuntuone url, sign it."""
613- fake_time = 12345
614- self.ui.uri = UBUNTUONE_LINK + 'foo'
615- self.patch(gui.timestamp_checker, "get_faithful_time",
616- lambda: defer.succeed(fake_time))
617- self.ui.click()
618-
619- expected_call = ((self.ui.uri, self.creds, fake_time), {})
620- self.assertEqual(self._called, expected_call)
621-
622-
623-class SignUrlNoCredsTestCase(SignUrlTestCase):
624- """The test suite for the sign url management when there are no creds."""
625-
626- @defer.inlineCallbacks
627- def setUp(self):
628- yield super(SignUrlNoCredsTestCase, self).setUp()
629- self.patch(self.ui.backend, 'get_credentials', lambda: {})
630-
631- def test_with_ubuntuone_prefix(self):
632- """If given url is an ubuntuone url, don't sign it.
633-
634- Since we have no credentials, the url should not be signed.
635-
636- """
637- self.ui.uri = UBUNTUONE_LINK + 'foo'
638- self.ui.click()
639-
640- self.assertFalse(self._called)
641
642=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_signin.py'
643--- ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2011-09-07 17:56:17 +0000
644+++ ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2012-02-07 14:03:19 +0000
645@@ -20,7 +20,6 @@
646
647 from twisted.internet import defer
648
649-from ubuntuone.controlpanel.gui import qt
650 from ubuntuone.controlpanel.gui.qt import signin as gui
651 from ubuntuone.controlpanel.gui.qt.tests import (
652 CrashyBackend,
653@@ -107,10 +106,8 @@
654
655 def test_forgot_password_button(self):
656 """When clicking the forgot passsword btn, the proper url is opened."""
657- self.patch(qt, 'uri_hook', self._set_called)
658- self.ui.ui.forgot_password_button.click()
659-
660- self.assertEqual(self._called, ((gui.RESET_PASSWORD_LINK,), {}))
661+ self.assert_uri_hook_called(self.ui.ui.forgot_password_button,
662+ gui.RESET_PASSWORD_LINK)
663
664
665 class SignInButtonPanelTestCase(BaseSignInPanelTestCase):
666
667=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_start.py'
668--- ubuntuone/controlpanel/gui/qt/tests/test_start.py 2011-09-15 15:37:04 +0000
669+++ ubuntuone/controlpanel/gui/qt/tests/test_start.py 2012-02-07 14:03:19 +0000
670@@ -1,91 +1,89 @@
671-# -*- coding: utf-8 -*-
672-
673-# Author: Roberto Alsina <roberto.alsina@canonical.com>
674-#
675-# Copyright 2011 Canonical Ltd.
676-#
677-# This program is free software: you can redistribute it and/or modify it
678-# under the terms of the GNU General Public License version 3, as published
679-# by the Free Software Foundation.
680-#
681-# This program is distributed in the hope that it will be useful, but
682-# WITHOUT ANY WARRANTY; without even the implied warranties of
683-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
684-# PURPOSE. See the GNU General Public License for more details.
685-#
686-# You should have received a copy of the GNU General Public License along
687-# with this program. If not, see <http://www.gnu.org/licenses/>.
688-
689-"""Tests for the start function."""
690-
691-from twisted.internet import defer
692-
693-from ubuntuone.controlpanel.gui.qt import gui
694-from ubuntuone.controlpanel.gui.qt.tests import NO_OP
695-from ubuntuone.controlpanel.tests import TestCase
696-
697-
698-class FakeThing(object):
699-
700- """A fake thing."""
701-
702- def __init__(self):
703- self.args = []
704- self.shown = False
705-
706- def __call__(self, *args, **kwargs):
707- self.args.append((args, kwargs))
708- return self
709-
710- def show(self):
711- """Show."""
712- self.shown = True
713-
714-
715-class FakeReactor(object):
716- """A fake reactor."""
717-
718- def run(self):
719- """Start."""
720-
721- def stop(self):
722- """Stop."""
723-
724-
725-class StartTestCase(TestCase):
726- """Test the qt control panel."""
727-
728- @defer.inlineCallbacks
729- def setUp(self):
730- yield super(StartTestCase, self).setUp()
731- self.main_window = FakeThing()
732- self.tray_icon = FakeThing()
733- self.patch(gui, "MainWindow", self.main_window)
734- self.patch(gui, "TrayIcon", self.tray_icon)
735-
736- def test_minimized(self):
737- """Test behaviour with minimized=True."""
738- gui.start(NO_OP, minimized=True, with_icon=True)
739- self.assertEqual(self.tray_icon.args, [((), {'window': None})])
740- self.assertEqual(self.main_window.args, [])
741-
742- def test_with_icon(self):
743- """Test behaviour with with_icon=True."""
744- gui.start(NO_OP, with_icon=True, minimized=False)
745- self.assertEqual(self.main_window.args, [((), {})])
746- self.assertEqual(self.tray_icon.args, [((),
747- {'window': self.main_window})])
748-
749- def test_both_false(self):
750- """Test behaviour when with_icon and minimized are False."""
751- gui.start(NO_OP, with_icon=False, minimized=False)
752- # Should be called
753- self.assertNotEqual(self.main_window.args, [])
754- # Should not be called
755- self.assertEqual(self.tray_icon.args, [])
756-
757- def test_both_true(self):
758- """Test behaviour when with_icon and minimized are True."""
759- gui.start(NO_OP, with_icon=True, minimized=True)
760- self.assertEqual(self.tray_icon.args, [((), {'window': None})])
761- self.assertEqual(self.main_window.args, [])
762+# -*- coding: utf-8 -*-
763+
764+# Author: Roberto Alsina <roberto.alsina@canonical.com>
765+#
766+# Copyright 2011 Canonical Ltd.
767+#
768+# This program is free software: you can redistribute it and/or modify it
769+# under the terms of the GNU General Public License version 3, as published
770+# by the Free Software Foundation.
771+#
772+# This program is distributed in the hope that it will be useful, but
773+# WITHOUT ANY WARRANTY; without even the implied warranties of
774+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
775+# PURPOSE. See the GNU General Public License for more details.
776+#
777+# You should have received a copy of the GNU General Public License along
778+# with this program. If not, see <http://www.gnu.org/licenses/>.
779+
780+"""Tests for the start function."""
781+
782+from twisted.internet import defer
783+
784+from ubuntuone.controlpanel.gui.qt import gui
785+from ubuntuone.controlpanel.tests import TestCase
786+
787+
788+class FakeThing(object):
789+
790+ """A fake thing."""
791+
792+ def __init__(self):
793+ self.args = []
794+ self.shown = False
795+
796+ def __call__(self, *args, **kwargs):
797+ self.args.append((args, kwargs))
798+ return self
799+
800+ def show(self):
801+ """Show."""
802+ self.shown = True
803+
804+
805+class StartTestCase(TestCase):
806+ """Test the qt control panel."""
807+
808+ @defer.inlineCallbacks
809+ def setUp(self):
810+ yield super(StartTestCase, self).setUp()
811+ self.main_window = FakeThing()
812+ self.tray_icon = FakeThing()
813+ self.patch(gui, "MainWindow", self.main_window)
814+ self.patch(gui, "TrayIcon", self.tray_icon)
815+
816+ def close_cb(self):
817+ """A dummy close callback."""
818+
819+ def test_minimized(self):
820+ """Test behaviour with minimized=True."""
821+ gui.start(close_callback=self.close_cb,
822+ minimized=True, with_icon=True)
823+ kwargs = {'close_callback': self.close_cb, 'window': None}
824+ self.assertEqual(self.tray_icon.args, [((), kwargs)])
825+ self.assertEqual(self.main_window.args, [])
826+
827+ def test_with_icon(self):
828+ """Test behaviour with with_icon=True."""
829+ gui.start(close_callback=self.close_cb,
830+ with_icon=True, minimized=False)
831+ kwargs = {'close_callback': self.close_cb, 'window': self.main_window}
832+ self.assertEqual(self.main_window.args, [((), {})])
833+ self.assertEqual(self.tray_icon.args, [((), kwargs)])
834+
835+ def test_both_false(self):
836+ """Test behaviour when with_icon and minimized are False."""
837+ gui.start(close_callback=self.close_cb,
838+ with_icon=False, minimized=False)
839+ # Should be called
840+ self.assertNotEqual(self.main_window.args, [])
841+ # Should not be called
842+ self.assertEqual(self.tray_icon.args, [])
843+
844+ def test_both_true(self):
845+ """Test behaviour when with_icon and minimized are True."""
846+ gui.start(close_callback=self.close_cb,
847+ with_icon=True, minimized=True)
848+ kwargs = {'close_callback': self.close_cb, 'window': None}
849+ self.assertEqual(self.tray_icon.args, [((), kwargs)])
850+ self.assertEqual(self.main_window.args, [])
851
852=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_systray.py'
853--- ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-01-02 19:38:14 +0000
854+++ ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-02-07 14:03:19 +0000
855@@ -19,7 +19,6 @@
856 """Tests for the notification area icon."""
857
858 from PyQt4 import QtGui
859-from twisted.internet import reactor
860 from twisted.internet.defer import inlineCallbacks
861
862 from ubuntuone.controlpanel.gui.qt import systray
863@@ -65,7 +64,6 @@
864 """Quit should call SyncDaemonTool.quit()."""
865 st = FakeSDTool()
866 self.patch(systray, "SyncDaemonTool", lambda: st)
867- self.patch(reactor, "stop", lambda: None)
868 tray = systray.TrayIcon()
869 yield tray.stop()
870 self.assertTrue(st.called)
871
872=== removed file 'ubuntuone/controlpanel/gui/tests/test_url_sign.py'
873--- ubuntuone/controlpanel/gui/tests/test_url_sign.py 2011-10-05 23:12:23 +0000
874+++ ubuntuone/controlpanel/gui/tests/test_url_sign.py 1970-01-01 00:00:00 +0000
875@@ -1,102 +0,0 @@
876-# -*- coding: utf-8 -*-
877-
878-# Authors: Roberto Alsina <roberto.alsina@canonical.com>
879-# Authors: Alejandro J. Cura <alecu@canonical.com>
880-#
881-# Copyright 2011 Canonical Ltd.
882-#
883-# This program is free software: you can redistribute it and/or modify it
884-# under the terms of the GNU General Public License version 3, as published
885-# by the Free Software Foundation.
886-#
887-# This program is distributed in the hope that it will be useful, but
888-# WITHOUT ANY WARRANTY; without even the implied warranties of
889-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
890-# PURPOSE. See the GNU General Public License for more details.
891-#
892-# You should have received a copy of the GNU General Public License along
893-# with this program. If not, see <http://www.gnu.org/licenses/>.
894-
895-"""Tests for the url signing function."""
896-
897-from urlparse import urlparse, parse_qs
898-
899-from ubuntuone.controlpanel.tests import TestCase
900-from ubuntuone.controlpanel.gui import (
901- sign_url,
902- UBUNTUONE_FROM_OAUTH,
903-)
904-
905-TOKEN = {u'consumer_key': u'consumer_key',
906- u'consumer_secret': u'consumer_secret',
907- u'token_name': u'test_token',
908- u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
909- u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
910-
911-SAMPLE_SIGNED = UBUNTUONE_FROM_OAUTH + '?oauth_nonce=' \
912- '36886134&oauth_timestamp=1310671062&oauth_consumer_key=consumer_key&' \
913- 'oauth_signature_method=HMAC-SHA1&next=%2Fblah&oauth_version=1.0&' \
914- 'oauth_token=GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo&' \
915- 'oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo%3D'
916-
917-#pylint: disable=E1101
918-
919-
920-class SignURLTestCase(TestCase):
921-
922- """Test cases for the URL signing function."""
923-
924- def test_is_correct_domain(self):
925- """Test that we are using the right domain."""
926- signed = sign_url("/blah", TOKEN)
927- parsed_signed = urlparse(signed)
928- parsed_sample = urlparse(SAMPLE_SIGNED)
929- self.assertEqual(parsed_signed.netloc, parsed_sample.netloc)
930-
931- def test_is_correct_path(self):
932- """Test that we are using the right path in the URL."""
933- signed = sign_url("/blah", TOKEN)
934- parsed_signed = urlparse(signed)
935- parsed_sample = urlparse(SAMPLE_SIGNED)
936- self.assertEqual(parsed_signed.path, parsed_sample.path)
937-
938- def test_is_correct_scheme(self):
939- """Test that we are using the right scheme."""
940- signed = sign_url("/blah", TOKEN)
941- parsed_signed = urlparse(signed)
942- parsed_sample = urlparse(SAMPLE_SIGNED)
943-
944- self.assertEqual(parsed_signed.scheme, parsed_sample.scheme)
945-
946- def test_correct_query(self):
947- """Test the invariant parts of the signed URL."""
948- signed = sign_url("/blah", TOKEN)
949- parsed_signed = urlparse(signed)
950- parsed_sample = urlparse(SAMPLE_SIGNED)
951- signed_query = parse_qs(parsed_signed.query)
952- sample_query = parse_qs(parsed_sample.query)
953-
954- for key in ('next',
955- 'oauth_consumer_key',
956- 'oauth_signature_method',
957- 'oauth_token',
958- 'oauth_version'):
959- self.assertEqual("%s=%s" % (key, signed_query[key]),
960- "%s=%s" % (key, sample_query[key]))
961-
962- def test_url_with_query(self):
963- """Test that we are using the right scheme."""
964- signed = sign_url("/blah?foo=bar", TOKEN)
965- parsed_signed = urlparse(signed)
966- signed_query = parse_qs(parsed_signed.query)
967-
968- self.assertEqual(signed_query['next'], ['/blah?foo=bar'])
969-
970- def test_uses_timestamper(self):
971- """Test that the signed url is using the server-relative timestamp."""
972- timestamp = 999
973- signed = sign_url("/blah?foo=bar", TOKEN, timestamp)
974- parsed_signed = urlparse(signed)
975- signed_query = parse_qs(parsed_signed.query)
976-
977- self.assertEqual(signed_query['oauth_timestamp'], [str(timestamp)])
978
979=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
980--- ubuntuone/controlpanel/tests/test_backend.py 2012-01-26 12:44:10 +0000
981+++ ubuntuone/controlpanel/tests/test_backend.py 2012-02-07 14:03:19 +0000
982@@ -43,6 +43,8 @@
983 FILE_SYNC_SYNCING,
984 FILE_SYNC_UNKNOWN,
985 MSG_KEY, STATUS_KEY,
986+ UBUNTUONE_FROM_OAUTH,
987+ UBUNTUONE_LINK,
988 )
989 from ubuntuone.controlpanel.tests import (TestCase,
990 EMPTY_DESCRIPTION_JSON,
991@@ -65,6 +67,12 @@
992 USER_HOME,
993 )
994
995+SAMPLE_SIGNED = UBUNTUONE_FROM_OAUTH + '?oauth_nonce=' \
996+ '36886134&oauth_timestamp=1310671062&oauth_consumer_key=consumer_key&' \
997+ 'oauth_signature_method=HMAC-SHA1&next=%2Fblah&oauth_version=1.0&' \
998+ 'oauth_token=GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo&' \
999+ 'oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo%3D'
1000+
1001
1002 # pylint: disable=E1101, W0201, W0212
1003
1004@@ -104,6 +112,13 @@
1005 result = simplejson.loads(self.results[method])
1006 return defer.succeed(result)
1007
1008+ @defer.inlineCallbacks
1009+ def build_signed_iri(self, iri, params):
1010+ """Fake the IRI signing."""
1011+ creds = yield self.get_credentials()
1012+ result = u'%s-%s-%s' % (iri, unicode(creds), unicode(params))
1013+ defer.returnValue(result)
1014+
1015
1016 class MockLoginClient(CallRecorder):
1017 """A mock login_client module."""
1018@@ -347,7 +362,7 @@
1019 @defer.inlineCallbacks
1020 def setUp(self):
1021 yield super(BackendBasicTestCase, self).setUp()
1022- self.patch(backend, "web_client_factory", MockWebClient)
1023+ self.patch(backend, "WebClient", MockWebClient)
1024 self.patch(backend, "CredentialsManagementTool", MockLoginClient)
1025 self.patch(backend.sd_client, "SyncDaemonClient", MockSDClient)
1026 self.patch(backend, "replication_client", MockReplicationClient())
1027@@ -368,12 +383,14 @@
1028 @inlineCallbacks
1029 def test_get_token(self):
1030 """The get_token method returns the right token."""
1031+ self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN))
1032 token = yield self.be.get_token()
1033 self.assertEqual(token, TOKEN["token"])
1034
1035 @inlineCallbacks
1036 def test_device_is_local(self):
1037 """The device_is_local returns the right result."""
1038+ self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN))
1039 result = yield self.be.device_is_local(self.local_token)
1040 self.assertTrue(result)
1041
1042@@ -408,6 +425,54 @@
1043 self.assertIs(self.be.sd_client, self.be.sd_client)
1044
1045
1046+class SignIriTestCase(BackendBasicTestCase):
1047+ """Test cases for the IRI signing function."""
1048+
1049+ @defer.inlineCallbacks
1050+ def setUp(self):
1051+ yield super(SignIriTestCase, self).setUp()
1052+ self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN))
1053+
1054+ @inlineCallbacks
1055+ def test_without_ubuntuone_prefix(self):
1056+ """If given url is not an ubuntuone url, don't sign it."""
1057+ iri = 'bad_prefix' + UBUNTUONE_LINK
1058+ result = yield self.be.build_signed_iri(iri)
1059+
1060+ self.assertEqual(result, iri)
1061+
1062+ @inlineCallbacks
1063+ def test_with_ubuntuone_prefix(self):
1064+ """If given url is an ubuntuone url, sign it."""
1065+ iri = UBUNTUONE_LINK + 'foo'
1066+ result = yield self.be.build_signed_iri(iri)
1067+
1068+ expected = yield self.be.wc.build_signed_iri(UBUNTUONE_FROM_OAUTH,
1069+ {'next': iri})
1070+ self.assertEqual(expected, result)
1071+
1072+
1073+class SignIriNoCredsTestCase(SignIriTestCase):
1074+ """The test suite for the sign url management when there are no creds."""
1075+
1076+ @defer.inlineCallbacks
1077+ def setUp(self):
1078+ yield super(SignIriNoCredsTestCase, self).setUp()
1079+ self.patch(self.be, 'get_credentials', lambda: defer.succeed({}))
1080+
1081+ @inlineCallbacks
1082+ def test_with_ubuntuone_prefix(self):
1083+ """If given url is an ubuntuone url, don't sign it.
1084+
1085+ Since we have no credentials, the url should not be signed.
1086+
1087+ """
1088+ iri = UBUNTUONE_LINK + 'foo'
1089+ result = yield self.be.build_signed_iri(iri)
1090+
1091+ self.assertEqual(result, iri)
1092+
1093+
1094 class BackendCredentialsTestCase(BackendBasicTestCase):
1095 """Credentials tests for the backend."""
1096
1097
1098=== modified file 'ubuntuone/controlpanel/tests/test_web_client.py'
1099--- ubuntuone/controlpanel/tests/test_web_client.py 2011-10-07 14:36:14 +0000
1100+++ ubuntuone/controlpanel/tests/test_web_client.py 2012-02-07 14:03:19 +0000
1101@@ -1,9 +1,6 @@
1102 # -*- coding: utf-8 -*-
1103-
1104-# Authors: Alejandro J. Cura <alecu@canonical.com>
1105-# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
1106 #
1107-# Copyright 2010 Canonical Ltd.
1108+# Copyright 2011-2012 Canonical Ltd.
1109 #
1110 # This program is free software: you can redistribute it and/or modify it
1111 # under the terms of the GNU General Public License version 3, as published
1112@@ -17,26 +14,38 @@
1113 # You should have received a copy of the GNU General Public License along
1114 # with this program. If not, see <http://www.gnu.org/licenses/>.
1115
1116-"""Integration tests for the control panel backend webservice client."""
1117-
1118-from twisted.application import internet, service
1119+"""Integration tests for the webclient."""
1120+
1121+from urlparse import urlparse, parse_qs
1122+
1123 from twisted.internet import defer
1124-from twisted.internet.defer import inlineCallbacks
1125-from twisted.web import server, resource
1126-
1127-from ubuntuone.controlpanel import web_client
1128+from twisted.web import resource
1129+
1130+from ubuntu_sso.utils.webclient.tests import BaseMockWebServer
1131+
1132 from ubuntuone.controlpanel.tests import TestCase
1133+from ubuntuone.controlpanel.web_client import (
1134+ UnauthorizedError,
1135+ WebClient,
1136+ WebClientError,
1137+)
1138
1139
1140 SAMPLE_KEY = "result"
1141 SAMPLE_VALUE = "sample result"
1142 SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE)
1143 SAMPLE_CREDENTIALS = dict(
1144- consumer_key="consumer key",
1145- consumer_secret="consumer secret",
1146- token="the real token",
1147- token_secret="the token secret",
1148+ consumer_key="consumer_key",
1149+ consumer_secret="consumer_secret",
1150+ token="the_real_token",
1151+ token_secret="the_token_secret",
1152 )
1153+SAMPLE_TARGET = u'http://example.com/'
1154+SAMPLE_SIGNED = SAMPLE_TARGET + u'?oauth_nonce=36886134&' \
1155+ 'oauth_timestamp=1328544832&oauth_consumer_key=%s&' \
1156+ 'oauth_signature_method=HMAC-SHA1&next=%%2Fblah&oauth_version=1.0&' \
1157+ 'oauth_token=%s&oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo3D' % \
1158+ (SAMPLE_CREDENTIALS['consumer_key'], SAMPLE_CREDENTIALS['token'])
1159
1160
1161 def sample_get_credentials():
1162@@ -63,11 +72,11 @@
1163 return self.contents
1164
1165
1166-class MockWebService(object):
1167- """A mock webservice for testing"""
1168+class MockWebServer(BaseMockWebServer):
1169+ """A mock webserver for the webclient tests."""
1170
1171- def __init__(self):
1172- """Start up this instance."""
1173+ def get_root_resource(self):
1174+ """Get the root resource with all the children."""
1175 root = resource.Resource()
1176 devices_resource = MockResource()
1177 devices_resource.contents = SAMPLE_RESOURCE
1178@@ -77,91 +86,105 @@
1179 "Unauthrorized", "Unauthrorized")
1180 root.putChild("unauthorized", unauthorized)
1181
1182- site = server.Site(root)
1183- application = service.Application('web')
1184- self.service_collection = service.IServiceCollection(application)
1185- #pylint: disable=E1101
1186- self.tcpserver = internet.TCPServer(0, site)
1187- self.tcpserver.setServiceParent(self.service_collection)
1188- self.service_collection.startService()
1189-
1190- def get_url(self):
1191- """Build the url for this mock server."""
1192- #pylint: disable=W0212
1193- port_num = self.tcpserver._port.getHost().port
1194- return "http://localhost:%d/" % port_num
1195-
1196- def stop(self):
1197- """Shut it down."""
1198- #pylint: disable=E1101
1199- return self.service_collection.stopService()
1200+ return root
1201
1202
1203 class WebClientTestCase(TestCase):
1204 """Test for the webservice client."""
1205
1206- timeout = 8
1207+ timeout = 5
1208
1209- @inlineCallbacks
1210+ @defer.inlineCallbacks
1211 def setUp(self):
1212 yield super(WebClientTestCase, self).setUp()
1213- self.ws = MockWebService()
1214- test_base_url = self.ws.get_url()
1215- self.wc = web_client.web_client_factory(sample_get_credentials,
1216- test_base_url)
1217+ self.ws = MockWebServer()
1218+ self.addCleanup(self.ws.stop)
1219+ self.base_iri = self.ws.get_iri()
1220+
1221+ self.wc = WebClient(sample_get_credentials, base_url=self.base_iri)
1222 self.addCleanup(self.wc.shutdown)
1223- self.addCleanup(self.ws.stop)
1224- web_module = web_client.web_client_module()
1225- if getattr(web_module, "timestamp_checker", None):
1226- fake_timestamp = 12345678
1227- self.patch(web_module.timestamp_checker, "get_faithful_time",
1228- lambda: defer.succeed(fake_timestamp))
1229
1230- @inlineCallbacks
1231+ @defer.inlineCallbacks
1232 def test_get_url(self):
1233 """A method is successfully called in the mock webservice."""
1234 result = yield self.wc.call_api("devices")
1235 self.assertIn(SAMPLE_KEY, result)
1236 self.assertEqual(SAMPLE_VALUE, result[SAMPLE_KEY])
1237
1238- @inlineCallbacks
1239+ @defer.inlineCallbacks
1240 def test_get_url_error(self):
1241 """The errback is called when there's some error."""
1242 yield self.assertFailure(self.wc.call_api("throwerror"),
1243- web_client.WebClientError)
1244+ WebClientError)
1245
1246- @inlineCallbacks
1247+ @defer.inlineCallbacks
1248 def test_unauthorized(self):
1249 """Detect when a request failed with UNAUTHORIZED."""
1250 yield self.assertFailure(self.wc.call_api("unauthorized"),
1251- web_client.UnauthorizedError)
1252-
1253-
1254-class OAuthTestCase(TestCase):
1255- """Test for the oauth signing code."""
1256-
1257- def test_build_oauth_headers(self):
1258- """Build the oauth headers for a sample request."""
1259-
1260- sample_method = "GET"
1261- sample_url = "http://one.ubuntu.com/"
1262- timestamp = 1
1263- headers = web_client.build_oauth_headers(sample_method, sample_url,
1264- SAMPLE_CREDENTIALS, timestamp)
1265- self.assertIn("Authorization", headers)
1266-
1267- def test_add_oauth_headers(self):
1268- """Add the OAuth headers to a request."""
1269-
1270- def sample_build_headers(*a):
1271- """Build some sample headers."""
1272- return {"header1": "h1", "header2": "h2"}
1273-
1274- self.patch(web_client, "build_oauth_headers", sample_build_headers)
1275- test_request_headers = {}
1276- append_method = test_request_headers.__setitem__
1277- timestamp = 12345
1278- web_client.add_oauth_headers(append_method, "GET", "http://this", {},
1279- timestamp)
1280- self.assertIn("header1", test_request_headers)
1281- self.assertIn("header2", test_request_headers)
1282+ UnauthorizedError)
1283+
1284+
1285+class WebClientBuildSignedIriTestCase(WebClientTestCase):
1286+ """Test for the webservice client when signing iris."""
1287+
1288+ # Instance of 'ParseResult' has no 'foo' member
1289+ # pylint: disable=E1101
1290+
1291+ @defer.inlineCallbacks
1292+ def test_is_correct_domain(self):
1293+ """Test that we are using the right domain."""
1294+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET)
1295+ parsed_signed = urlparse(signed)
1296+ parsed_sample = urlparse(SAMPLE_SIGNED)
1297+ self.assertEqual(parsed_signed.netloc, parsed_sample.netloc)
1298+
1299+ @defer.inlineCallbacks
1300+ def test_is_correct_path(self):
1301+ """Test that we are using the right path in the URL."""
1302+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET)
1303+ parsed_signed = urlparse(signed)
1304+ parsed_sample = urlparse(SAMPLE_SIGNED)
1305+ self.assertEqual(parsed_signed.path, parsed_sample.path)
1306+
1307+ @defer.inlineCallbacks
1308+ def test_is_correct_scheme(self):
1309+ """Test that we are using the right scheme."""
1310+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET)
1311+ parsed_signed = urlparse(signed)
1312+ parsed_sample = urlparse(SAMPLE_SIGNED)
1313+
1314+ self.assertEqual(parsed_signed.scheme, parsed_sample.scheme)
1315+
1316+ @defer.inlineCallbacks
1317+ def test_correct_query(self):
1318+ """Test the invariant parts of the signed URL."""
1319+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET,
1320+ params={'next': u'/blah'})
1321+ parsed_signed = urlparse(signed)
1322+ parsed_sample = urlparse(SAMPLE_SIGNED)
1323+ signed_query = parse_qs(parsed_signed.query)
1324+ sample_query = parse_qs(parsed_sample.query)
1325+
1326+ for key in ('next', 'oauth_consumer_key', 'oauth_version',
1327+ 'oauth_signature_method', 'oauth_token'):
1328+ self.assertEqual(map(unicode, signed_query[key]),
1329+ sample_query[key])
1330+
1331+ @defer.inlineCallbacks
1332+ def test_url_with_query(self):
1333+ """Test that we are using the right scheme."""
1334+ signed = yield self.wc.build_signed_iri(SAMPLE_TARGET,
1335+ params={'next': u'/blah?foo=bar'})
1336+ parsed_signed = urlparse(signed)
1337+ signed_query = parse_qs(parsed_signed.query)
1338+
1339+ self.assertEqual(signed_query['next'], [u'/blah?foo=bar'])
1340+
1341+ @defer.inlineCallbacks
1342+ def test_uses_timestamper(self):
1343+ """Test that the signed url is using the serverrelative timestamp."""
1344+ signed = yield self.wc.build_signed_iri(u'/blah?foo=bar')
1345+ parsed_signed = urlparse(signed)
1346+ signed_query = parse_qs(parsed_signed.query)
1347+
1348+ self.assertTrue(signed_query['oauth_timestamp'] is not None)
1349
1350=== removed directory 'ubuntuone/controlpanel/web_client'
1351=== renamed file 'ubuntuone/controlpanel/web_client/__init__.py' => 'ubuntuone/controlpanel/web_client.py'
1352--- ubuntuone/controlpanel/web_client/__init__.py 2011-10-07 14:36:14 +0000
1353+++ ubuntuone/controlpanel/web_client.py 2012-02-07 14:03:19 +0000
1354@@ -1,9 +1,6 @@
1355 # -*- coding: utf-8 -*-
1356-
1357-# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
1358-# Alejandro J. Cura <alecu@canonical.com>
1359 #
1360-# Copyright 2011 Canonical Ltd.
1361+# Copyright 2011-2012 Canonical Ltd.
1362 #
1363 # This program is free software: you can redistribute it and/or modify it
1364 # under the terms of the GNU General Public License version 3, as published
1365@@ -19,60 +16,55 @@
1366
1367 """The web client."""
1368
1369-from oauth import oauth
1370-
1371-
1372-# pylint: disable=W0401, W0614
1373-
1374-
1375-class WebClientError(Exception):
1376- """An http error happened while calling the webservice."""
1377-
1378-
1379-class UnauthorizedError(WebClientError):
1380- """The request ended with bad_request, unauthorized or forbidden."""
1381-
1382-
1383-def build_oauth_headers(method, url, credentials, timestamp):
1384- """Build an oauth request given some credentials."""
1385- consumer = oauth.OAuthConsumer(credentials["consumer_key"],
1386- credentials["consumer_secret"])
1387- token = oauth.OAuthToken(credentials["token"],
1388- credentials["token_secret"])
1389- parameters = {}
1390- if timestamp:
1391- parameters["oauth_timestamp"] = timestamp
1392- request = oauth.OAuthRequest.from_consumer_and_token(
1393- http_url=url,
1394- http_method=method,
1395- parameters=parameters,
1396- oauth_consumer=consumer,
1397- token=token)
1398- sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
1399- request.sign_request(sig_method, consumer, token)
1400- return request.to_header()
1401-
1402-
1403-def add_oauth_headers(append_method, method, url, credentials, timestamp=None):
1404- """Sign a libsoup message with oauth headers."""
1405- headers = build_oauth_headers(method, url, credentials, timestamp)
1406- for key, value in headers.items():
1407- append_method(key, value)
1408-
1409-
1410-def web_client_module():
1411- """Choose the module of the web client."""
1412- # the reactor can only be imported after Qt is initialized
1413- # pylint: disable=W0404
1414- from twisted.internet import reactor
1415- if getattr(reactor, "qApp", None):
1416- from ubuntuone.controlpanel.web_client import txwebclient as web_module
1417- else:
1418- from ubuntuone.controlpanel.web_client import libsoup as web_module
1419- return web_module
1420-
1421-
1422-def web_client_factory(*args, **kwargs):
1423- """Choose the type of the web client dynamically."""
1424- web_module = web_client_module()
1425- return web_module.WebClient(*args, **kwargs)
1426+import simplejson
1427+
1428+from twisted.internet import defer
1429+# need to export the exceptions to avoid API breakage
1430+# pylint: disable=W0611
1431+from ubuntu_sso.utils.webclient import (
1432+ UnauthorizedError,
1433+ WebClientError,
1434+ webclient_factory,
1435+)
1436+# pylint: enable=W0611
1437+
1438+from ubuntuone.controlpanel import WEBSERVICE_BASE_URL
1439+from ubuntuone.controlpanel.logger import setup_logging
1440+
1441+
1442+logger = setup_logging('webclient')
1443+
1444+
1445+class WebClient(object):
1446+ """A client for the u1 webservice."""
1447+
1448+ def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL):
1449+ """Initialize the webclient."""
1450+ self.base_url = base_url
1451+ self.get_credentials = get_credentials
1452+ self.wc = webclient_factory()
1453+ logger.debug("WebClient created: base_url is %r, inner client is %r.",
1454+ self.base_url, self.wc)
1455+
1456+ @defer.inlineCallbacks
1457+ def call_api(self, api_name, extra_headers=None):
1458+ """Call the webservice."""
1459+ # this may log device ID's, but only for removals, which is OK
1460+ logger.debug("calling api: %r", api_name)
1461+ iri = self.base_url + api_name
1462+ credentials = yield self.get_credentials()
1463+ response = yield self.wc.request(iri, extra_headers=extra_headers,
1464+ oauth_credentials=credentials)
1465+ result = simplejson.loads(response.content)
1466+ defer.returnValue(result)
1467+
1468+ @defer.inlineCallbacks
1469+ def build_signed_iri(self, iri, params=None):
1470+ """Build an OAuth iri."""
1471+ credentials = yield self.get_credentials()
1472+ result = yield self.wc.build_signed_iri(iri, credentials, params)
1473+ defer.returnValue(result)
1474+
1475+ def shutdown(self):
1476+ """Shutdown and cleanup."""
1477+ return self.wc.shutdown()
1478
1479=== removed file 'ubuntuone/controlpanel/web_client/libsoup.py'
1480--- ubuntuone/controlpanel/web_client/libsoup.py 2011-10-06 19:56:38 +0000
1481+++ ubuntuone/controlpanel/web_client/libsoup.py 1970-01-01 00:00:00 +0000
1482@@ -1,133 +0,0 @@
1483-# -*- coding: utf-8 -*-
1484-
1485-# Authors: Alejandro J. Cura <alecu@canonical.com>
1486-# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
1487-#
1488-# Copyright 2010 Canonical Ltd.
1489-#
1490-# This program is free software: you can redistribute it and/or modify it
1491-# under the terms of the GNU General Public License version 3, as published
1492-# by the Free Software Foundation.
1493-#
1494-# This program is distributed in the hope that it will be useful, but
1495-# WITHOUT ANY WARRANTY; without even the implied warranties of
1496-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1497-# PURPOSE. See the GNU General Public License for more details.
1498-#
1499-# You should have received a copy of the GNU General Public License along
1500-# with this program. If not, see <http://www.gnu.org/licenses/>.
1501-
1502-"""The control panel backend webservice client."""
1503-
1504-import simplejson
1505-
1506-# pylint: disable=E0611
1507-from gi.repository import Soup, SoupGNOME
1508-from twisted.internet import defer
1509-
1510-from ubuntuone.controlpanel import WEBSERVICE_BASE_URL
1511-from ubuntuone.controlpanel.web_client import (add_oauth_headers,
1512- WebClientError,
1513- UnauthorizedError)
1514-from ubuntuone.controlpanel.logger import setup_logging
1515-from ubuntuone.storageprotocol.utils import BaseTimestampChecker
1516-
1517-logger = setup_logging('webclient')
1518-
1519-# full list of status codes
1520-# http://library.gnome.org/devel/libsoup/stable/libsoup-2.4-soup-status.html
1521-
1522-
1523-class LibsoupTimestampChecker(BaseTimestampChecker):
1524- """Specialized TimestampChecker using libsoup."""
1525-
1526- def __init__(self):
1527- """Initialize this instance."""
1528- super(LibsoupTimestampChecker, self).__init__()
1529- self.session = Soup.SessionAsync()
1530- self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME)
1531-
1532- def _handler(self, session, msg, d):
1533- """Handle the result of an http message."""
1534- logger.debug("got http response %d for uri %r",
1535- msg.status_code, msg.get_uri().to_string(False))
1536- if msg.status_code == 200:
1537- date = msg.response_headers.get("Date")
1538- d.callback(date)
1539- else:
1540- e = WebClientError(msg.status_code, "")
1541- d.errback(e)
1542-
1543- def get_server_date_header(self, server_url):
1544- """Get the server date using twisted webclient."""
1545- method = "HEAD"
1546- msg = Soup.Message.new(method, server_url)
1547- msg.request_headers.append("Cache-Control", "no-cache")
1548- d = defer.Deferred()
1549- self.session.queue_message(msg, self._handler, d)
1550- return d
1551-
1552- def shutdown(self):
1553- """End the soup session for this webclient."""
1554- self.session.abort()
1555-
1556-
1557-# pylint: disable=C0103
1558-timestamp_checker = LibsoupTimestampChecker()
1559-
1560-
1561-class WebClient(object):
1562- """A client for the u1 webservice."""
1563-
1564- def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL):
1565- """Initialize the webclient."""
1566- self.base_url = base_url
1567- self.session = Soup.SessionAsync()
1568- self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME)
1569- self.get_credentials = get_credentials
1570-
1571- def _handler(self, session, msg, d):
1572- """Handle the result of an http message."""
1573- logger.debug("got http response %d for uri %r",
1574- msg.status_code, msg.get_uri().to_string(False))
1575- data = msg.response_body.data
1576- if msg.status_code == 200:
1577- result = simplejson.loads(data)
1578- d.callback(result)
1579- else:
1580- if msg.status_code in (401,):
1581- e = UnauthorizedError(msg.status_code, data)
1582- else:
1583- e = WebClientError(msg.status_code, data)
1584- d.errback(e)
1585-
1586- def _call_api_inner(self, credentials, api_name, timestamp):
1587- """Call the webservice with credentials and timestamp."""
1588- url = (self.base_url + api_name).encode('utf-8')
1589- method = "GET"
1590- logger.debug("getting url: %s, %s", method, url)
1591- msg = Soup.Message.new(method, url)
1592- add_oauth_headers(msg.request_headers.append, method, url,
1593- credentials, timestamp)
1594- d = defer.Deferred()
1595- self.session.queue_message(msg, self._handler, d)
1596- return d
1597-
1598- @defer.inlineCallbacks
1599- def _call_api_add_timestamp(self, credentials, api_name):
1600- """Add the timestamp to the api call."""
1601- timestamp = yield timestamp_checker.get_faithful_time()
1602- result = yield self._call_api_inner(credentials, api_name, timestamp)
1603- defer.returnValue(result)
1604-
1605- def call_api(self, api_name):
1606- """Call the webservice."""
1607- # this may log device ID's, but only for removals, which is OK
1608- logger.debug("calling api: %s", api_name)
1609- d = self.get_credentials()
1610- d.addCallback(self._call_api_add_timestamp, api_name)
1611- return d
1612-
1613- def shutdown(self):
1614- """End the soup session for this webclient."""
1615- self.session.abort()
1616
1617=== removed directory 'ubuntuone/controlpanel/web_client/tests'
1618=== removed file 'ubuntuone/controlpanel/web_client/tests/__init__.py'
1619--- ubuntuone/controlpanel/web_client/tests/__init__.py 2011-09-08 23:52:27 +0000
1620+++ ubuntuone/controlpanel/web_client/tests/__init__.py 1970-01-01 00:00:00 +0000
1621@@ -1,19 +0,0 @@
1622-# -*- coding: utf-8 -*-
1623-
1624-# Authors: Alejandro J. Cura <alecu@canonical.com>
1625-#
1626-# Copyright 2011 Canonical Ltd.
1627-#
1628-# This program is free software: you can redistribute it and/or modify it
1629-# under the terms of the GNU General Public License version 3, as published
1630-# by the Free Software Foundation.
1631-#
1632-# This program is distributed in the hope that it will be useful, but
1633-# WITHOUT ANY WARRANTY; without even the implied warranties of
1634-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1635-# PURPOSE. See the GNU General Public License for more details.
1636-#
1637-# You should have received a copy of the GNU General Public License along
1638-# with this program. If not, see <http://www.gnu.org/licenses/>.
1639-
1640-"""Unit tests for the control panel backend webservice clients."""
1641
1642=== removed file 'ubuntuone/controlpanel/web_client/tests/test_libsoup.py'
1643--- ubuntuone/controlpanel/web_client/tests/test_libsoup.py 2011-10-06 22:27:57 +0000
1644+++ ubuntuone/controlpanel/web_client/tests/test_libsoup.py 1970-01-01 00:00:00 +0000
1645@@ -1,106 +0,0 @@
1646-# -*- coding: utf-8 -*-
1647-
1648-# Authors: Alejandro J. Cura <alecu@canonical.com>
1649-#
1650-# Copyright 2011 Canonical Ltd.
1651-#
1652-# This program is free software: you can redistribute it and/or modify it
1653-# under the terms of the GNU General Public License version 3, as published
1654-# by the Free Software Foundation.
1655-#
1656-# This program is distributed in the hope that it will be useful, but
1657-# WITHOUT ANY WARRANTY; without even the implied warranties of
1658-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1659-# PURPOSE. See the GNU General Public License for more details.
1660-#
1661-# You should have received a copy of the GNU General Public License along
1662-# with this program. If not, see <http://www.gnu.org/licenses/>.
1663-
1664-"""Integration tests for the libsoup based webservice client."""
1665-
1666-import time
1667-
1668-from twisted.application import internet, service
1669-from twisted.internet import defer
1670-from twisted.web import http, resource, server
1671-
1672-from ubuntuone.controlpanel.tests import TestCase
1673-from ubuntuone.controlpanel.web_client.libsoup import LibsoupTimestampChecker
1674-
1675-
1676-class MockResource(resource.Resource):
1677- """A mock resource."""
1678-
1679- isLeaf = True
1680-
1681- def __init__(self):
1682- """Initialize this mock instance."""
1683- resource.Resource.__init__(self)
1684- self.request_headers = []
1685-
1686- # pylint: disable=C0103
1687- def render_GET(self, request):
1688- """Render some content."""
1689- self.request_headers.append(request.requestHeaders)
1690- return "hello!"
1691-
1692-
1693-class MockWebService(object):
1694- """A mock webservice for testing"""
1695-
1696- def __init__(self):
1697- """Start up this instance."""
1698- self.root = MockResource()
1699- site = server.Site(self.root)
1700- application = service.Application('web')
1701- self.service_collection = service.IServiceCollection(application)
1702- #pylint: disable=E1101
1703- self.tcpserver = internet.TCPServer(0, site)
1704- self.tcpserver.setServiceParent(self.service_collection)
1705- self.service_collection.startService()
1706-
1707- def get_url(self):
1708- """Build the url for this mock server."""
1709- #pylint: disable=W0212
1710- port_num = self.tcpserver._port.getHost().port
1711- return "http://localhost:%d/" % port_num
1712-
1713- def stop(self):
1714- """Shut down the service."""
1715- #pylint: disable=E1101
1716- self.service_collection.stopService()
1717-
1718-
1719-class LibsoupTimestampCheckerTestCase(TestCase):
1720- """Tests for LibsoupTimestampChecker."""
1721-
1722- timeout = 3
1723-
1724- @defer.inlineCallbacks
1725- def setUp(self):
1726- yield super(LibsoupTimestampCheckerTestCase, self).setUp()
1727- self.ws = MockWebService()
1728- self.addCleanup(self.ws.stop)
1729-
1730- @defer.inlineCallbacks
1731- def test_gets_server_date(self):
1732- """The server date is gotten right."""
1733- fake_time = 1
1734- self.patch(time, "time", lambda: fake_time)
1735- checker = LibsoupTimestampChecker()
1736- self.addCleanup(checker.shutdown)
1737- d = checker.get_server_date_header(self.ws.get_url())
1738- result = yield d
1739- result_time = http.stringToDatetime(result)
1740- self.assertEqual(result_time, fake_time)
1741-
1742- @defer.inlineCallbacks
1743- def test_server_date_sends_nocache_headers(self):
1744- """Getting the server date sends the no-cache headers."""
1745- checker = LibsoupTimestampChecker()
1746- self.addCleanup(checker.shutdown)
1747- yield checker.get_server_date_header(self.ws.get_url())
1748- self.assertEqual(len(self.ws.root.request_headers), 1)
1749- headers = self.ws.root.request_headers[0]
1750- result = headers.getRawHeaders("Cache-Control")
1751- self.assertEqual(result, ["no-cache"])
1752
1753=== removed file 'ubuntuone/controlpanel/web_client/tests/test_txwebclient.py'
1754--- ubuntuone/controlpanel/web_client/tests/test_txwebclient.py 2011-11-21 13:32:44 +0000
1755+++ ubuntuone/controlpanel/web_client/tests/test_txwebclient.py 1970-01-01 00:00:00 +0000
1756@@ -1,177 +0,0 @@
1757-# -*- coding: utf-8 -*-
1758-
1759-# Authors: Alejandro J. Cura <alecu@canonical.com>
1760-#
1761-# Copyright 2011 Canonical Ltd.
1762-#
1763-# This program is free software: you can redistribute it and/or modify it
1764-# under the terms of the GNU General Public License version 3, as published
1765-# by the Free Software Foundation.
1766-#
1767-# This program is distributed in the hope that it will be useful, but
1768-# WITHOUT ANY WARRANTY; without even the implied warranties of
1769-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1770-# PURPOSE. See the GNU General Public License for more details.
1771-#
1772-# You should have received a copy of the GNU General Public License along
1773-# with this program. If not, see <http://www.gnu.org/licenses/>.
1774-
1775-"""Unit tests for the control panel backend twisted webservice client."""
1776-
1777-import time
1778-
1779-from twisted.application import internet, service
1780-from twisted.internet import defer, reactor
1781-from twisted.internet.defer import inlineCallbacks
1782-from twisted.web import server, resource
1783-from ubuntuone.devtools.testcases import skipIfOS
1784-
1785-from ubuntuone.controlpanel.web_client import txwebclient
1786-from ubuntuone.controlpanel.tests import TestCase
1787-
1788-
1789-SAMPLE_KEY = "result"
1790-SAMPLE_VALUE = "sample result"
1791-SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE)
1792-SAMPLE_CREDENTIALS = dict(
1793- consumer_key="consumer key",
1794- consumer_secret="consumer secret",
1795- token="the real token",
1796- token_secret="the token secret",
1797-)
1798-
1799-
1800-def sample_get_credentials():
1801- """Will return the sample credentials right now."""
1802- return defer.succeed(SAMPLE_CREDENTIALS)
1803-
1804-
1805-class MockResource(resource.Resource):
1806- """A simple web resource."""
1807- isLeaf = True
1808- contents = ""
1809-
1810- # pylint: disable=C0103
1811- # t.w.resource methods have freeform cased names
1812-
1813- def getChild(self, name, request):
1814- """Get a given child resource."""
1815- if name == '':
1816- return self
1817- return resource.Resource.getChild(self, name, request)
1818-
1819- def render_GET(self, request):
1820- """Make a bit of html out of these resource's content."""
1821- return self.contents
1822-
1823-
1824-class MockWebService(object):
1825- """A mock webservice for testing"""
1826-
1827- def __init__(self):
1828- """Start up this instance."""
1829- root = resource.Resource()
1830- devices_resource = MockResource()
1831- devices_resource.contents = SAMPLE_RESOURCE
1832- root.putChild("devices", devices_resource)
1833- root.putChild("throwerror", resource.NoResource())
1834- unauthorized = resource.ErrorPage(resource.http.UNAUTHORIZED,
1835- "Unauthrorized", "Unauthrorized")
1836- root.putChild("unauthorized", unauthorized)
1837-
1838- site = server.Site(root)
1839- application = service.Application('web')
1840- self.service_collection = service.IServiceCollection(application)
1841- #pylint: disable=E1101
1842- self.tcpserver = internet.TCPServer(0, site)
1843- self.tcpserver.setServiceParent(self.service_collection)
1844- self.service_collection.startService()
1845-
1846- def get_url(self):
1847- """Build the url for this mock server."""
1848- #pylint: disable=W0212
1849- port_num = self.tcpserver._port.getHost().port
1850- return "http://localhost:%d/" % port_num
1851-
1852- def stop(self):
1853- """Shut it down."""
1854- #pylint: disable=E1101
1855- return self.service_collection.stopService()
1856-
1857-
1858-class FakeAsyncTimestamper(object):
1859- """A fake timestamp."""
1860-
1861- def __init__(self):
1862- """Initialize this instance."""
1863- self.called = False
1864-
1865- def get_faithful_time(self):
1866- """Return the server checked timestamp."""
1867- self.called = True
1868- return defer.succeed(time.time())
1869-
1870-
1871-class WebClientTestCase(TestCase):
1872- """Test for the webservice client."""
1873-
1874- timeout = 8
1875-
1876- @defer.inlineCallbacks
1877- def setUp(self):
1878- yield super(WebClientTestCase, self).setUp()
1879- self.ws = MockWebService()
1880- test_base_url = self.ws.get_url()
1881- self.wc = txwebclient.WebClient(sample_get_credentials, test_base_url)
1882- self.timestamper = FakeAsyncTimestamper()
1883- self.patch(txwebclient, "timestamp_checker", self.timestamper)
1884- self.addCleanup(self.wc.shutdown)
1885- self.addCleanup(self.ws.stop)
1886-
1887- @inlineCallbacks
1888- def test_get_url(self):
1889- """A method is successfully called in the mock webservice."""
1890- result = yield self.wc.call_api("devices")
1891- self.assertIn(SAMPLE_KEY, result)
1892- self.assertEqual(SAMPLE_VALUE, result[SAMPLE_KEY])
1893-
1894- @inlineCallbacks
1895- def test_call_api_uses_timestamp(self):
1896- """Check that call_api uses the timestamp."""
1897- yield self.wc.call_api("devices")
1898- self.assertTrue(self.timestamper.called,
1899- "The timestamper must be used.")
1900-
1901- @inlineCallbacks
1902- def test_get_url_error(self):
1903- """The errback is called when there's some error."""
1904- yield self.assertFailure(self.wc.call_api("throwerror"),
1905- txwebclient.WebClientError)
1906-
1907- @inlineCallbacks
1908- def test_unauthorized(self):
1909- """Detect when a request failed with UNAUTHORIZED."""
1910- yield self.assertFailure(self.wc.call_api("unauthorized"),
1911- txwebclient.UnauthorizedError)
1912-
1913-
1914-class WebClientShutdownTestCase(TestCase):
1915- """The webclient behaviour during shutdown."""
1916-
1917- @skipIfOS('win32', 'Failing on windows, see LP: #851158.')
1918- @inlineCallbacks
1919- def test_shutdown(self):
1920- """The webclient behaves well during shutdown."""
1921- self.patch(txwebclient, "timestamp_checker", FakeAsyncTimestamper())
1922- d3 = defer.Deferred()
1923- # pylint: disable=E1101
1924- reactor.callLater(1, d3.callback, None)
1925- ws = MockWebService()
1926- test_base_url = ws.get_url()
1927- wc = txwebclient.WebClient(sample_get_credentials, test_base_url)
1928- d1 = wc.call_api("throwerror")
1929- d2 = ws.stop()
1930- wc.shutdown()
1931- yield d2
1932- yield defer.DeferredList([d1, d3], fireOnOneCallback=True,
1933- fireOnOneErrback=True)
1934
1935=== removed file 'ubuntuone/controlpanel/web_client/txwebclient.py'
1936--- ubuntuone/controlpanel/web_client/txwebclient.py 2011-10-05 23:12:23 +0000
1937+++ ubuntuone/controlpanel/web_client/txwebclient.py 1970-01-01 00:00:00 +0000
1938@@ -1,108 +0,0 @@
1939-# -*- coding: utf-8 -*-
1940-
1941-# Authors: Alejandro J. Cura <alecu@canonical.com>
1942-#
1943-# Copyright 2011 Canonical Ltd.
1944-#
1945-# This program is free software: you can redistribute it and/or modify it
1946-# under the terms of the GNU General Public License version 3, as published
1947-# by the Free Software Foundation.
1948-#
1949-# This program is distributed in the hope that it will be useful, but
1950-# WITHOUT ANY WARRANTY; without even the implied warranties of
1951-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1952-# PURPOSE. See the GNU General Public License for more details.
1953-#
1954-# You should have received a copy of the GNU General Public License along
1955-# with this program. If not, see <http://www.gnu.org/licenses/>.
1956-
1957-"""The control panel backend webservice client using twisted.web."""
1958-
1959-import simplejson
1960-
1961-from twisted.internet import defer, reactor
1962-from twisted.web import client, error, http
1963-
1964-from ubuntuone.controlpanel import WEBSERVICE_BASE_URL
1965-from ubuntuone.controlpanel.web_client import (add_oauth_headers,
1966- WebClientError,
1967- UnauthorizedError)
1968-
1969-from ubuntuone.controlpanel.logger import setup_logging
1970-from ubuntuone.storageprotocol.client import TwistedTimestampChecker
1971-
1972-logger = setup_logging('webclient')
1973-# pylint: disable=C0103
1974-timestamp_checker = TwistedTimestampChecker()
1975-
1976-
1977-class WebClient(object):
1978- """A client for the u1 webservice."""
1979-
1980- def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL):
1981- """Initialize the webclient."""
1982- self.base_url = base_url
1983- self.get_credentials = get_credentials
1984- self.running = True
1985- # pylint: disable=E1101
1986- self.trigger_id = reactor.addSystemEventTrigger("before", "shutdown",
1987- self.shutdown)
1988-
1989- def _handle_response(self, result):
1990- """Handle the response of the webservice call."""
1991- return simplejson.loads(result)
1992-
1993- def _handle_error(self, failure):
1994- """Handle an error while calling the webservice."""
1995- if failure.type == error.Error:
1996- exception = failure.value
1997- if exception.status == str(http.UNAUTHORIZED):
1998- raise UnauthorizedError(exception.status, exception.response)
1999- else:
2000- raise WebClientError(exception.status, exception.response)
2001- else:
2002- raise WebClientError(-1, failure)
2003-
2004- def _call_api_inner(self, credentials, api_name, timestamp):
2005- """Call the webservice with credentials and timestamp."""
2006- url = (self.base_url + api_name).encode('utf-8')
2007- method = "GET"
2008- logger.debug("getting url: %s, %s", method, url)
2009- headers = {}
2010- add_oauth_headers(headers.__setitem__, method, url, credentials,
2011- timestamp)
2012- d = client.getPage(url, headers=headers)
2013- d.addCallback(self._handle_response)
2014- d.addErrback(self._handle_error)
2015- return d
2016-
2017- @defer.inlineCallbacks
2018- def _call_api_add_timestamp(self, credentials, api_name):
2019- """Add the timestamp to the api call."""
2020- timestamp = yield timestamp_checker.get_faithful_time()
2021- result = yield self._call_api_inner(credentials, api_name, timestamp)
2022- defer.returnValue(result)
2023-
2024- def call_api(self, api_name):
2025- """Call the webservice."""
2026- # this may log device ID's, but only for removals, which is OK
2027- logger.debug("calling api: %s", api_name)
2028- d = self.get_credentials()
2029- d.addErrback(self._handle_error)
2030- d.addCallback(self._call_api_add_timestamp, api_name)
2031- d2 = defer.Deferred()
2032- d.addCallback(d2.callback)
2033-
2034- def mask_errors_on_shutdown(failure):
2035- """Do not fire the errbacks if we are shutting down."""
2036- if self.running:
2037- d2.errback(failure)
2038-
2039- d.addErrback(mask_errors_on_shutdown)
2040- return d2
2041-
2042- def shutdown(self):
2043- """End the pending webclient calls."""
2044- self.running = False
2045- # pylint: disable=E1101
2046- reactor.removeSystemEventTrigger(self.trigger_id)

Subscribers

People subscribed via source and target branches