Merge lp:~nataliabidart/ubuntuone-control-panel/raise-and-handle-unauthorized into lp:ubuntuone-control-panel
- raise-and-handle-unauthorized
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Natalia Bidart |
Approved revision: | 135 |
Merged at revision: | 129 |
Proposed branch: | lp:~nataliabidart/ubuntuone-control-panel/raise-and-handle-unauthorized |
Merge into: | lp:ubuntuone-control-panel |
Diff against target: |
757 lines (+218/-44) 14 files modified
ubuntuone/controlpanel/backend.py (+29/-2) ubuntuone/controlpanel/dbus_service.py (+29/-21) ubuntuone/controlpanel/gtk/gui.py (+19/-5) ubuntuone/controlpanel/gtk/package_manager.py (+3/-2) ubuntuone/controlpanel/gtk/tests/test_gui.py (+29/-1) ubuntuone/controlpanel/gtk/tests/test_gui_basic.py (+9/-0) ubuntuone/controlpanel/gtk/tests/test_package_manager.py (+2/-2) ubuntuone/controlpanel/integrationtests/__init__.py (+1/-2) ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+29/-0) ubuntuone/controlpanel/integrationtests/test_webclient.py (+11/-1) ubuntuone/controlpanel/replication_client.py (+1/-1) ubuntuone/controlpanel/tests/test_backend.py (+43/-5) ubuntuone/controlpanel/utils.py (+1/-1) ubuntuone/controlpanel/webclient.py (+12/-1) |
To merge this branch: | bzr merge lp:~nataliabidart/ubuntuone-control-panel/raise-and-handle-unauthorized |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Roberto Alsina (community) | Approve | ||
Roman Yepishev (community) | fieldtest | Approve | |
Review via email: mp+56782@code.launchpad.net |
Commit message
- If servers reply with a 401, clear credentials and ask user to authenticate (LP: #726612).
Description of the change
To test IRL, you need to do a little tweak in the source code due to bug #751895.
- edit ubuntuone/
- killall ubuntuone-
- in 2 terminals, run both the backend and the frontend from this branch:
DEBUG=True PYTHONPATH=. ./bin/ubuntuone
DEBUG=True PYTHONPATH=. ./bin/ubuntuone
While you have your panel opened, go to https:/
You should be redirected to the overview page and you should get your U1 credentials cleared from your local system.
Roberto Alsina (ralsina) wrote : | # |
Roman Yepishev (rye) wrote : | # |
Awesome!
Roberto Alsina (ralsina) : | # |
Natalia Bidart (nataliabidart) wrote : | # |
The attempt to merge lp:~nataliabidart/ubuntuone-control-panel/raise-and-handle-unauthorized into lp:ubuntuone-control-panel failed. Below is the output from the failed tests.
Running test suite for ubuntuone/
Xlib: extension "RANDR" missing on display ":99".
ubuntuone.
BaseTestCase
runTest ... [OK]
ubuntuone.
ControlPanelM
test_
test_
ControlPanelT
test_
test_
test_
test_
test_
test_
test_
test_
test_
test_
test_
test_
test_
ControlPanelW
test_alert ... [OK]
ControlPanelW
test_
test_
test_
test_
test_
test_
test_max_size ... [OK]
test_
test_switch_to ... [OK]
test_
ControlPanelW
test_
test_
test_
test_
test_
test_
test_max_size ....
- 135. By Natalia Bidart
-
Increasing the timeout fr webclient tests.
Preview Diff
1 | === modified file 'ubuntuone/controlpanel/backend.py' |
2 | --- ubuntuone/controlpanel/backend.py 2011-04-03 03:07:53 +0000 |
3 | +++ ubuntuone/controlpanel/backend.py 2011-04-07 15:22:46 +0000 |
4 | @@ -20,14 +20,17 @@ |
5 | """A backend for the Ubuntu One Control Panel.""" |
6 | |
7 | from collections import defaultdict |
8 | +from functools import wraps |
9 | |
10 | from twisted.internet.defer import inlineCallbacks, returnValue |
11 | |
12 | from ubuntuone.controlpanel import dbus_client |
13 | from ubuntuone.controlpanel import replication_client |
14 | from ubuntuone.controlpanel.logger import setup_logging, log_call |
15 | -from ubuntuone.controlpanel.webclient import WebClient, WebClientError |
16 | - |
17 | +# pylint: disable=W0611 |
18 | +from ubuntuone.controlpanel.webclient import (UnauthorizedError, |
19 | + WebClient, WebClientError) |
20 | +# pylint: enable=W0611 |
21 | |
22 | logger = setup_logging('backend') |
23 | |
24 | @@ -71,6 +74,25 @@ |
25 | return result |
26 | |
27 | |
28 | +def process_unauthorized(f): |
29 | + """Decorator to catch UnauthorizedError from the webclient and act upon.""" |
30 | + |
31 | + @inlineCallbacks |
32 | + @wraps(f) |
33 | + def inner(*args, **kwargs): |
34 | + """Handle UnauthorizedError and clear credentials.""" |
35 | + try: |
36 | + result = yield f(*args, **kwargs) |
37 | + except UnauthorizedError, e: |
38 | + logger.exception('process_unauthorized (clearing credentials):') |
39 | + yield dbus_client.clear_credentials() |
40 | + raise e |
41 | + |
42 | + returnValue(result) |
43 | + |
44 | + return inner |
45 | + |
46 | + |
47 | class ControlBackend(object): |
48 | """The control panel backend.""" |
49 | |
50 | @@ -224,6 +246,7 @@ |
51 | returnValue(is_local) |
52 | |
53 | @log_call(logger.debug) |
54 | + @process_unauthorized |
55 | @inlineCallbacks |
56 | def account_info(self): |
57 | """Get the user account info.""" |
58 | @@ -251,6 +274,7 @@ |
59 | returnValue(result) |
60 | |
61 | @log_call(logger.debug) |
62 | + @process_unauthorized |
63 | @inlineCallbacks |
64 | def devices_info(self): |
65 | """Get the user devices info.""" |
66 | @@ -267,6 +291,8 @@ |
67 | |
68 | try: |
69 | devices = yield self.wc.call_api(DEVICES_API) |
70 | + except UnauthorizedError: |
71 | + raise |
72 | except WebClientError: |
73 | logger.exception('devices_info: web client failure:') |
74 | else: |
75 | @@ -345,6 +371,7 @@ |
76 | returnValue(device_id) |
77 | |
78 | @log_call(logger.warning, with_args=False) |
79 | + @process_unauthorized |
80 | @inlineCallbacks |
81 | def remove_device(self, device_id): |
82 | """Remove a device's tokens from the sso server.""" |
83 | |
84 | === modified file 'ubuntuone/controlpanel/dbus_service.py' |
85 | --- ubuntuone/controlpanel/dbus_service.py 2011-03-31 14:43:41 +0000 |
86 | +++ ubuntuone/controlpanel/dbus_service.py 2011-04-07 15:22:46 +0000 |
87 | @@ -32,7 +32,7 @@ |
88 | from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH, |
89 | DBUS_PREFERENCES_IFACE) |
90 | from ubuntuone.controlpanel.backend import ( |
91 | - ControlBackend, filter_field, |
92 | + ControlBackend, filter_field, UnauthorizedError, |
93 | FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED, |
94 | FILE_SYNC_ERROR, FILE_SYNC_IDLE, FILE_SYNC_STARTING, FILE_SYNC_STOPPED, |
95 | FILE_SYNC_SYNCING, |
96 | @@ -78,7 +78,7 @@ |
97 | return result |
98 | |
99 | |
100 | -def transform_failure(f): |
101 | +def transform_failure(f, auth_error=None): |
102 | """Decorator to apply to DBus error signals. |
103 | |
104 | With this call, a Failure is transformed into a string-string dict. |
105 | @@ -88,7 +88,9 @@ |
106 | """Do the Failure transformation.""" |
107 | logger.error('processing failure: %r', error.printTraceback()) |
108 | error_dict = error_handler(error) |
109 | - if _ is not None: |
110 | + if auth_error is not None and error.check(UnauthorizedError): |
111 | + result = auth_error(error_dict) |
112 | + elif _ is not None: |
113 | result = f(_, error_dict) |
114 | else: |
115 | result = f(error_dict) |
116 | @@ -117,19 +119,25 @@ |
117 | super(ControlPanelBackend, self).__init__(*args, **kwargs) |
118 | self.backend = backend |
119 | self.backend.status_changed_handler = self.process_status |
120 | + self.transform = lambda f: transform_failure(f, self.UnauthorizedError) |
121 | logger.debug('ControlPanelBackend: created with %r, %r.\n' |
122 | 'status_changed_handler is %r.', |
123 | args, kwargs, self.process_status) |
124 | |
125 | # pylint: disable=C0103 |
126 | |
127 | + @log_call(logger.error) |
128 | + @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
129 | + def UnauthorizedError(self, error): |
130 | + """The credentials are not valid.""" |
131 | + |
132 | @log_call(logger.debug) |
133 | @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="") |
134 | def account_info(self): |
135 | """Find out the account info for the current logged in user.""" |
136 | d = self.backend.account_info() |
137 | d.addCallback(self.AccountInfoReady) |
138 | - d.addErrback(transform_failure(self.AccountInfoError)) |
139 | + d.addErrback(self.transform(self.AccountInfoError)) |
140 | |
141 | @log_call(logger.debug) |
142 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}") |
143 | @@ -149,7 +157,7 @@ |
144 | """Find out the devices info for the logged in user.""" |
145 | d = self.backend.devices_info() |
146 | d.addCallback(self.DevicesInfoReady) |
147 | - d.addErrback(transform_failure(self.DevicesInfoError)) |
148 | + d.addErrback(self.transform(self.DevicesInfoError)) |
149 | |
150 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") |
151 | def DevicesInfoReady(self, info): |
152 | @@ -170,7 +178,7 @@ |
153 | """Configure a given device.""" |
154 | d = self.backend.change_device_settings(device_id, settings) |
155 | d.addCallback(self.DeviceSettingsChanged) |
156 | - d.addErrback(transform_failure(self.DeviceSettingsChangeError), |
157 | + d.addErrback(self.transform(self.DeviceSettingsChangeError), |
158 | device_id) |
159 | |
160 | @log_call(logger.info, with_args=False) |
161 | @@ -191,7 +199,7 @@ |
162 | """Remove a given device.""" |
163 | d = self.backend.remove_device(device_id) |
164 | d.addCallback(self.DeviceRemoved) |
165 | - d.addErrback(transform_failure(self.DeviceRemovalError), device_id) |
166 | + d.addErrback(self.transform(self.DeviceRemovalError), device_id) |
167 | |
168 | @log_call(logger.warning, with_args=False) |
169 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
170 | @@ -237,7 +245,7 @@ |
171 | """Get the status of the file sync service.""" |
172 | d = self.backend.file_sync_status() |
173 | d.addCallback(self.process_status) |
174 | - d.addErrback(transform_failure(self.FileSyncStatusError)) |
175 | + d.addErrback(self.transform(self.FileSyncStatusError)) |
176 | |
177 | @log_call(logger.debug) |
178 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s") |
179 | @@ -287,7 +295,7 @@ |
180 | """Enable the files service.""" |
181 | d = self.backend.enable_files() |
182 | d.addCallback(lambda _: self.FilesEnabled()) |
183 | - d.addErrback(transform_failure(self.FilesEnableError)) |
184 | + d.addErrback(self.transform(self.FilesEnableError)) |
185 | |
186 | @log_call(logger.debug) |
187 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
188 | @@ -307,7 +315,7 @@ |
189 | """Disable the files service.""" |
190 | d = self.backend.disable_files() |
191 | d.addCallback(lambda _: self.FilesDisabled()) |
192 | - d.addErrback(transform_failure(self.FilesDisableError)) |
193 | + d.addErrback(self.transform(self.FilesDisableError)) |
194 | |
195 | @log_call(logger.debug) |
196 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
197 | @@ -327,7 +335,7 @@ |
198 | """Connect the files service.""" |
199 | d = self.backend.connect_files() |
200 | d.addCallback(lambda _: self.FilesConnected()) |
201 | - d.addErrback(transform_failure(self.FilesConnectError)) |
202 | + d.addErrback(self.transform(self.FilesConnectError)) |
203 | |
204 | @log_call(logger.debug) |
205 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
206 | @@ -347,7 +355,7 @@ |
207 | """Disconnect the files service.""" |
208 | d = self.backend.disconnect_files() |
209 | d.addCallback(lambda _: self.FilesDisconnected()) |
210 | - d.addErrback(transform_failure(self.FilesDisconnectError)) |
211 | + d.addErrback(self.transform(self.FilesDisconnectError)) |
212 | |
213 | @log_call(logger.debug) |
214 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
215 | @@ -367,7 +375,7 @@ |
216 | """Restart the files service.""" |
217 | d = self.backend.restart_files() |
218 | d.addCallback(lambda _: self.FilesRestarted()) |
219 | - d.addErrback(transform_failure(self.FilesRestartError)) |
220 | + d.addErrback(self.transform(self.FilesRestartError)) |
221 | |
222 | @log_call(logger.debug) |
223 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
224 | @@ -387,7 +395,7 @@ |
225 | """Start the files service.""" |
226 | d = self.backend.start_files() |
227 | d.addCallback(lambda _: self.FilesStarted()) |
228 | - d.addErrback(transform_failure(self.FilesStartError)) |
229 | + d.addErrback(self.transform(self.FilesStartError)) |
230 | |
231 | @log_call(logger.debug) |
232 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
233 | @@ -407,7 +415,7 @@ |
234 | """Stop the files service.""" |
235 | d = self.backend.stop_files() |
236 | d.addCallback(lambda _: self.FilesStopped()) |
237 | - d.addErrback(transform_failure(self.FilesStopError)) |
238 | + d.addErrback(self.transform(self.FilesStopError)) |
239 | |
240 | @log_call(logger.debug) |
241 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE) |
242 | @@ -427,7 +435,7 @@ |
243 | """Find out the volumes info for the logged in user.""" |
244 | d = self.backend.volumes_info() |
245 | d.addCallback(self.VolumesInfoReady) |
246 | - d.addErrback(transform_failure(self.VolumesInfoError)) |
247 | + d.addErrback(self.transform(self.VolumesInfoError)) |
248 | |
249 | @log_call(logger.debug) |
250 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a(ssaa{ss})") |
251 | @@ -447,7 +455,7 @@ |
252 | """Configure a given volume.""" |
253 | d = self.backend.change_volume_settings(volume_id, settings) |
254 | d.addCallback(self.VolumeSettingsChanged) |
255 | - d.addErrback(transform_failure(self.VolumeSettingsChangeError), |
256 | + d.addErrback(self.transform(self.VolumeSettingsChangeError), |
257 | volume_id) |
258 | |
259 | @log_call(logger.info) |
260 | @@ -468,7 +476,7 @@ |
261 | """Return the replications info.""" |
262 | d = self.backend.replications_info() |
263 | d.addCallback(self.ReplicationsInfoReady) |
264 | - d.addErrback(transform_failure(self.ReplicationsInfoError)) |
265 | + d.addErrback(self.transform(self.ReplicationsInfoError)) |
266 | |
267 | @log_call(logger.debug) |
268 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}") |
269 | @@ -488,7 +496,7 @@ |
270 | """Configure a given replication.""" |
271 | d = self.backend.change_replication_settings(replication_id, settings) |
272 | d.addCallback(self.ReplicationSettingsChanged) |
273 | - d.addErrback(transform_failure(self.ReplicationSettingsChangeError), |
274 | + d.addErrback(self.transform(self.ReplicationSettingsChangeError), |
275 | replication_id) |
276 | |
277 | @log_call(logger.info) |
278 | @@ -509,7 +517,7 @@ |
279 | """Check if the extension to sync bookmarks is installed.""" |
280 | d = self.backend.query_bookmark_extension() |
281 | d.addCallback(self.QueryBookmarksResult) |
282 | - d.addErrback(transform_failure(self.QueryBookmarksError)) |
283 | + d.addErrback(self.transform(self.QueryBookmarksError)) |
284 | |
285 | @log_call(logger.debug) |
286 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b") |
287 | @@ -529,7 +537,7 @@ |
288 | """Install the extension to sync bookmarks.""" |
289 | d = self.backend.install_bookmarks_extension() |
290 | d.addCallback(lambda _: self.InstallBookmarksSuccess()) |
291 | - d.addErrback(transform_failure(self.InstallBookmarksError)) |
292 | + d.addErrback(self.transform(self.InstallBookmarksError)) |
293 | |
294 | @log_call(logger.info) |
295 | @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="") |
296 | |
297 | === modified file 'ubuntuone/controlpanel/gtk/gui.py' |
298 | --- ubuntuone/controlpanel/gtk/gui.py 2011-04-06 22:25:04 +0000 |
299 | +++ ubuntuone/controlpanel/gtk/gui.py 2011-04-07 15:22:46 +0000 |
300 | @@ -1323,6 +1323,8 @@ |
301 | self.button.connect('clicked', self._on_button_clicked) |
302 | self.pack_start(self.button, expand=False) |
303 | |
304 | + self.show_all() |
305 | + |
306 | self.backend.connect_to_signal('FileSyncStatusDisabled', |
307 | self.on_file_sync_status_disabled) |
308 | self.backend.connect_to_signal('FileSyncStatusStarting', |
309 | @@ -1344,10 +1346,6 @@ |
310 | self.backend.connect_to_signal('FilesDisabled', |
311 | self.on_file_sync_status_disabled) |
312 | |
313 | - self.backend.file_sync_status(reply_handler=NO_OP, |
314 | - error_handler=error_handler) |
315 | - self.show_all() |
316 | - |
317 | def _update_status(self, msg, action, callback, |
318 | icon=None, color=None, tooltip=None): |
319 | """Update the status info.""" |
320 | @@ -1460,6 +1458,11 @@ |
321 | self.backend.stop_files(reply_handler=NO_OP, |
322 | error_handler=error_handler) |
323 | |
324 | + def load(self): |
325 | + """Load the information.""" |
326 | + self.backend.file_sync_status(reply_handler=NO_OP, |
327 | + error_handler=error_handler) |
328 | + |
329 | |
330 | class ManagementPanel(gtk.VBox, ControlPanelMixin): |
331 | """The management panel. |
332 | @@ -1471,6 +1474,7 @@ |
333 | __gsignals__ = { |
334 | 'local-device-removed': (gobject.SIGNAL_RUN_FIRST, |
335 | gobject.TYPE_NONE, ()), |
336 | + 'unauthorized': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()), |
337 | } |
338 | |
339 | QUOTA_LABEL = _('Using %(used)s of %(total)s (%(percentage).0f%%)') |
340 | @@ -1496,6 +1500,8 @@ |
341 | self.on_account_info_ready) |
342 | self.backend.connect_to_signal('AccountInfoError', |
343 | self.on_account_info_error) |
344 | + self.backend.connect_to_signal('UnauthorizedError', |
345 | + self.on_unauthorized_error) |
346 | |
347 | self.quota_progressbar.set_sensitive(False) |
348 | |
349 | @@ -1536,7 +1542,6 @@ |
350 | |
351 | self.services_button.set_name(self.SERVICES_BUTTON_NAME) |
352 | self.services_button.set_tooltip_text(self.SERVICES_BUTTON_TOOLTIP) |
353 | - self.services.load() |
354 | |
355 | self.enable_volumes = lambda: self.volumes_button.set_sensitive(True) |
356 | self.disable_volumes = lambda: self.volumes_button.set_sensitive(False) |
357 | @@ -1571,6 +1576,8 @@ |
358 | """Load the account info and file sync status list.""" |
359 | self.backend.account_info(reply_handler=NO_OP, |
360 | error_handler=error_handler) |
361 | + self.status_label.load() |
362 | + self.services.load() |
363 | |
364 | @log_call(logger.debug) |
365 | def on_account_info_ready(self, info): |
366 | @@ -1586,6 +1593,11 @@ |
367 | """Backend notifies of an error when fetching account info.""" |
368 | self._update_quota(msg='') |
369 | |
370 | + @log_call(logger.error) |
371 | + def on_unauthorized_error(self, error_dict=None): |
372 | + """Backend notifies that credentials are not valid.""" |
373 | + self.emit('unauthorized') |
374 | + |
375 | |
376 | class ControlPanel(gtk.Notebook, ControlPanelMixin): |
377 | """The control panel per se, can be added into any other widget.""" |
378 | @@ -1613,6 +1625,8 @@ |
379 | self.on_show_management_panel) |
380 | self.management.connect('local-device-removed', |
381 | self.on_show_overview_panel) |
382 | + self.management.connect('unauthorized', |
383 | + self.on_show_overview_panel) |
384 | |
385 | self.show() |
386 | self.on_show_overview_panel() |
387 | |
388 | === modified file 'ubuntuone/controlpanel/gtk/package_manager.py' |
389 | --- ubuntuone/controlpanel/gtk/package_manager.py 2011-02-22 19:28:14 +0000 |
390 | +++ ubuntuone/controlpanel/gtk/package_manager.py 2011-04-07 15:22:46 +0000 |
391 | @@ -20,13 +20,14 @@ |
392 | |
393 | import apt |
394 | import aptdaemon.client |
395 | +# pylint: disable=W0404 |
396 | import aptdaemon.enums |
397 | |
398 | try: |
399 | - # Unable to import 'defer', pylint: disable=F0401,E0611 |
400 | + # Unable to import 'defer', pylint: disable=F0401,E0611,W0404 |
401 | from aptdaemon.defer import inline_callbacks, return_value |
402 | except ImportError: |
403 | - # Unable to import 'defer', pylint: disable=F0401,E0611 |
404 | + # Unable to import 'defer', pylint: disable=F0401,E0611,W0404 |
405 | from defer import inline_callbacks, return_value |
406 | from aptdaemon.gtkwidgets import AptProgressBar |
407 | |
408 | |
409 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py' |
410 | --- ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-04-04 21:28:18 +0000 |
411 | +++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-04-07 15:22:46 +0000 |
412 | @@ -1680,8 +1680,9 @@ |
413 | for sig, handlers in matches: |
414 | self.assertEqual(self.ui.backend._signals[sig], handlers) |
415 | |
416 | - def test_file_sync_status_is_requested(self): |
417 | + def test_file_sync_status_is_requested_on_load(self): |
418 | """The file sync status is requested to the backend.""" |
419 | + self.ui.load() |
420 | self.assert_backend_called('file_sync_status', ()) |
421 | |
422 | def test_on_file_sync_status_disabled(self): |
423 | @@ -1912,11 +1913,32 @@ |
424 | self.assertEqual(self.ui.backend._signals['AccountInfoError'], |
425 | [self.ui.on_account_info_error]) |
426 | |
427 | + def test_backend_unauthorized_signal(self): |
428 | + """The proper signals are connected to the backend.""" |
429 | + self.assertEqual(self.ui.backend._signals['UnauthorizedError'], |
430 | + [self.ui.on_unauthorized_error]) |
431 | + |
432 | + def test_no_backend_calls_before_load(self): |
433 | + """No calls are made to the backend before load() is called.""" |
434 | + self.assertEqual(self.ui.backend._called, {}) |
435 | + |
436 | def test_account_info_is_requested_on_load(self): |
437 | """The account info is requested to the backend.""" |
438 | self.ui.load() |
439 | self.assert_backend_called('account_info', ()) |
440 | |
441 | + def test_file_sync_status_info_is_requested_on_load(self): |
442 | + """The file sync status info is requested to the backend.""" |
443 | + self.patch(self.ui.status_label, 'load', self._set_called) |
444 | + self.ui.load() |
445 | + self.assertEqual(self._called, ((), {})) |
446 | + |
447 | + def test_replications_info_is_requested_on_load(self): |
448 | + """The replications info is requested to the backend.""" |
449 | + self.patch(self.ui.services, 'load', self._set_called) |
450 | + self.ui.load() |
451 | + self.assertEqual(self._called, ((), {})) |
452 | + |
453 | def test_dashboard_panel_is_packed(self): |
454 | """The dashboard panel is packed.""" |
455 | self.assertIsInstance(self.ui.dashboard, gui.DashboardPanel) |
456 | @@ -2071,3 +2093,9 @@ |
457 | actual = getattr(self.ui, '%s_button' % tab).get_tooltip_text() |
458 | expected = getattr(self.ui, '%s_BUTTON_TOOLTIP' % tab.upper()) |
459 | self.assertEqual(actual, expected) |
460 | + |
461 | + def test_on_unauthorized_error(self): |
462 | + """On invalid credentials, proper signal is sent.""" |
463 | + self.ui.connect('unauthorized', self._set_called) |
464 | + self.ui.on_unauthorized_error() |
465 | + self.assertEqual(self._called, ((self.ui,), {})) |
466 | |
467 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_gui_basic.py' |
468 | --- ubuntuone/controlpanel/gtk/tests/test_gui_basic.py 2011-04-01 16:10:44 +0000 |
469 | +++ ubuntuone/controlpanel/gtk/tests/test_gui_basic.py 2011-04-07 15:22:46 +0000 |
470 | @@ -206,8 +206,10 @@ |
471 | |
472 | def test_on_show_management_panel(self): |
473 | """A ManagementPanel is shown when the callback is executed.""" |
474 | + self.patch(self.ui.management, 'load', self._set_called) |
475 | self.ui.on_show_management_panel() |
476 | self.assert_current_tab_correct(self.ui.management) |
477 | + self.assertEqual(self._called, ((), {})) |
478 | |
479 | def test_on_show_management_panel_is_idempotent(self): |
480 | """Only one ManagementPanel is shown.""" |
481 | @@ -256,6 +258,13 @@ |
482 | |
483 | self.assert_current_tab_correct(self.ui.overview) |
484 | |
485 | + def test_unauthorized_shows_overview_panel(self): |
486 | + """On 'unauthorized' signal, the overview panel is shown.""" |
487 | + self.ui.overview.emit('credentials-found', True, object()) |
488 | + self.ui.management.emit('unauthorized') |
489 | + |
490 | + self.assert_current_tab_correct(self.ui.overview) |
491 | + |
492 | def test_backend_is_shutdown_on_close(self): |
493 | """When the control panel is closed, the backend is shutdown.""" |
494 | self.ui.emit('destroy') |
495 | |
496 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_package_manager.py' |
497 | --- ubuntuone/controlpanel/gtk/tests/test_package_manager.py 2011-01-03 14:20:12 +0000 |
498 | +++ ubuntuone/controlpanel/gtk/tests/test_package_manager.py 2011-04-07 15:22:46 +0000 |
499 | @@ -21,10 +21,10 @@ |
500 | import collections |
501 | |
502 | try: |
503 | - # Unable to import 'defer', pylint: disable=F0401,E0611 |
504 | + # Unable to import 'defer', pylint: disable=F0401,E0611,W0404 |
505 | from aptdaemon import defer |
506 | except ImportError: |
507 | - # Unable to import 'defer', pylint: disable=F0401,E0611 |
508 | + # Unable to import 'defer', pylint: disable=F0401,E0611,W0404 |
509 | import defer |
510 | |
511 | from ubuntuone.controlpanel.gtk import package_manager |
512 | |
513 | === modified file 'ubuntuone/controlpanel/integrationtests/__init__.py' |
514 | --- ubuntuone/controlpanel/integrationtests/__init__.py 2010-12-08 19:44:53 +0000 |
515 | +++ ubuntuone/controlpanel/integrationtests/__init__.py 2011-04-07 15:22:46 +0000 |
516 | @@ -20,7 +20,6 @@ |
517 | """Integration tests for the Ubuntu One Control Panel.""" |
518 | |
519 | import dbus |
520 | -import dbus.service |
521 | |
522 | from ubuntuone.devtools.testcase import DBusTestCase as TestCase |
523 | |
524 | @@ -31,7 +30,7 @@ |
525 | """A DBus exception to be used in tests.""" |
526 | |
527 | |
528 | -class MockDBusNoMethods(dbus.service.Object): |
529 | +class MockDBusNoMethods(dbus_service.dbus.service.Object): |
530 | """A mock that fails at the DBus layer (because it's got no methods!).""" |
531 | |
532 | |
533 | |
534 | === modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py' |
535 | --- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-03-28 17:25:21 +0000 |
536 | +++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-04-07 15:22:46 +0000 |
537 | @@ -624,6 +624,35 @@ |
538 | error_sig, success_sig, got_error_signal, method, *args) |
539 | |
540 | |
541 | +class OperationsAuthErrorTestCase(OperationsTestCase): |
542 | + """Test for the DBus service operations when UnauthorizedError happens.""" |
543 | + |
544 | + def setUp(self): |
545 | + super(OperationsAuthErrorTestCase, self).setUp() |
546 | + self.patch(MockBackend, 'exception', |
547 | + dbus_service.UnauthorizedError) |
548 | + |
549 | + def assert_correct_method_call(self, success_sig, error_sig, success_cb, |
550 | + method, *args): |
551 | + """Call parent instance expecting UnauthorizedError signal.""" |
552 | + |
553 | + def inner_success_cb(*a): |
554 | + """The success signal was received.""" |
555 | + if len(a) == 1: |
556 | + error_dict = a[0] |
557 | + else: |
558 | + an_id, error_dict = a |
559 | + self.assertEqual(an_id, args[0]) |
560 | + |
561 | + self.assertEqual(error_dict[dbus_service.ERROR_TYPE], |
562 | + 'UnauthorizedError') |
563 | + self.deferred.callback('success') |
564 | + |
565 | + parent = super(OperationsAuthErrorTestCase, self) |
566 | + return parent.assert_correct_method_call( |
567 | + "UnauthorizedError", error_sig, inner_success_cb, method, *args) |
568 | + |
569 | + |
570 | class FileSyncTestCase(BaseTestCase): |
571 | """Test for the DBus service when requesting file sync status.""" |
572 | |
573 | |
574 | === modified file 'ubuntuone/controlpanel/integrationtests/test_webclient.py' |
575 | --- ubuntuone/controlpanel/integrationtests/test_webclient.py 2010-12-02 16:23:03 +0000 |
576 | +++ ubuntuone/controlpanel/integrationtests/test_webclient.py 2011-04-07 15:22:46 +0000 |
577 | @@ -73,6 +73,10 @@ |
578 | devices_resource.contents = SAMPLE_RESOURCE |
579 | root.putChild("devices", devices_resource) |
580 | root.putChild("throwerror", resource.NoResource()) |
581 | + unauthorized = resource.ErrorPage(resource.http.UNAUTHORIZED, |
582 | + "Unauthrorized", "Unauthrorized") |
583 | + root.putChild("unauthorized", unauthorized) |
584 | + |
585 | site = server.Site(root) |
586 | application = service.Application('web') |
587 | self.service_collection = service.IServiceCollection(application) |
588 | @@ -96,7 +100,7 @@ |
589 | class WebClientTestCase(TestCase): |
590 | """Test for the webservice client.""" |
591 | |
592 | - timeout = 5 |
593 | + timeout = 8 |
594 | |
595 | def setUp(self): |
596 | super(WebClientTestCase, self).setUp() |
597 | @@ -123,6 +127,12 @@ |
598 | yield self.assertFailure(self.wc.call_api("throwerror"), |
599 | webclient.WebClientError) |
600 | |
601 | + @inlineCallbacks |
602 | + def test_unauthorized(self): |
603 | + """Detect when a request failed with UNAUTHORIZED.""" |
604 | + yield self.assertFailure(self.wc.call_api("unauthorized"), |
605 | + webclient.UnauthorizedError) |
606 | + |
607 | |
608 | class OAuthTestCase(TestCase): |
609 | """Test for the oauth signing code.""" |
610 | |
611 | === modified file 'ubuntuone/controlpanel/replication_client.py' |
612 | --- ubuntuone/controlpanel/replication_client.py 2011-01-06 20:40:15 +0000 |
613 | +++ ubuntuone/controlpanel/replication_client.py 2011-04-07 15:22:46 +0000 |
614 | @@ -57,7 +57,7 @@ |
615 | if replication_module is None: |
616 | # delay import in case DC is not installed at module import time |
617 | # Unable to import 'desktopcouch.application.replication_services' |
618 | - # pylint: disable=F0401 |
619 | + # pylint: disable=W0404 |
620 | from desktopcouch.application.replication_services \ |
621 | import ubuntuone as replication_module |
622 | try: |
623 | |
624 | === modified file 'ubuntuone/controlpanel/tests/test_backend.py' |
625 | --- ubuntuone/controlpanel/tests/test_backend.py 2011-04-03 03:07:53 +0000 |
626 | +++ ubuntuone/controlpanel/tests/test_backend.py 2011-04-07 15:22:46 +0000 |
627 | @@ -59,7 +59,6 @@ |
628 | SHARES_PATH_LINK, |
629 | TOKEN, |
630 | ) |
631 | -from ubuntuone.controlpanel.webclient import WebClientError |
632 | |
633 | |
634 | class MockWebClient(object): |
635 | @@ -73,8 +72,10 @@ |
636 | |
637 | def call_api(self, method): |
638 | """Get a given url from the webservice.""" |
639 | - if self.failure: |
640 | - return defer.fail(WebClientError(self.failure)) |
641 | + if self.failure == 401: |
642 | + return defer.fail(backend.UnauthorizedError(self.failure)) |
643 | + elif self.failure: |
644 | + return defer.fail(backend.WebClientError(self.failure)) |
645 | else: |
646 | result = simplejson.loads(self.results[method]) |
647 | return defer.succeed(result) |
648 | @@ -326,7 +327,20 @@ |
649 | """The account_info method exercises its errback.""" |
650 | # pylint: disable=E1101 |
651 | self.be.wc.failure = 404 |
652 | - yield self.assertFailure(self.be.account_info(), WebClientError) |
653 | + yield self.assertFailure(self.be.account_info(), |
654 | + backend.WebClientError) |
655 | + |
656 | + @inlineCallbacks |
657 | + def test_account_info_fails_with_unauthorized(self): |
658 | + """The account_info clears the credentials on unauthorized.""" |
659 | + # pylint: disable=E1101 |
660 | + self.be.wc.failure = 401 |
661 | + d = defer.Deferred() |
662 | + self.patch(backend.dbus_client, 'clear_credentials', |
663 | + lambda: d.callback('called')) |
664 | + yield self.assertFailure(self.be.account_info(), |
665 | + backend.UnauthorizedError) |
666 | + yield d |
667 | |
668 | |
669 | class BackendDevicesTestCase(BackendBasicTestCase): |
670 | @@ -362,6 +376,18 @@ |
671 | 'web client failure')) |
672 | |
673 | @inlineCallbacks |
674 | + def test_devices_info_fails_with_unauthorized(self): |
675 | + """The devices_info clears the credentials on unauthorized.""" |
676 | + # pylint: disable=E1101 |
677 | + self.be.wc.failure = 401 |
678 | + d = defer.Deferred() |
679 | + self.patch(backend.dbus_client, 'clear_credentials', |
680 | + lambda: d.callback('called')) |
681 | + yield self.assertFailure(self.be.devices_info(), |
682 | + backend.UnauthorizedError) |
683 | + yield d |
684 | + |
685 | + @inlineCallbacks |
686 | def test_devices_info_if_files_disable(self): |
687 | """The devices_info returns device only info if files is disabled.""" |
688 | yield self.be.disable_files() |
689 | @@ -436,7 +462,19 @@ |
690 | # pylint: disable=E1101 |
691 | self.be.wc.failure = 404 |
692 | yield self.assertFailure(self.be.remove_device(self.local_token), |
693 | - WebClientError) |
694 | + backend.WebClientError) |
695 | + |
696 | + @inlineCallbacks |
697 | + def test_remove_device_fails_with_unauthorized(self): |
698 | + """The remove_device clears the credentials on unauthorized.""" |
699 | + # pylint: disable=E1101 |
700 | + self.be.wc.failure = 401 |
701 | + d = defer.Deferred() |
702 | + self.patch(backend.dbus_client, 'clear_credentials', |
703 | + lambda: d.callback('called')) |
704 | + yield self.assertFailure(self.be.remove_device(self.local_token), |
705 | + backend.UnauthorizedError) |
706 | + yield d |
707 | |
708 | @inlineCallbacks |
709 | def test_remove_device_does_not_log_device_id(self): |
710 | |
711 | === modified file 'ubuntuone/controlpanel/utils.py' |
712 | --- ubuntuone/controlpanel/utils.py 2010-12-08 13:56:25 +0000 |
713 | +++ ubuntuone/controlpanel/utils.py 2011-04-07 15:22:46 +0000 |
714 | @@ -52,7 +52,7 @@ |
715 | |
716 | # otherwise, try to load PROJECT_DIR from installation path |
717 | try: |
718 | - # pylint: disable=F0401, E0611 |
719 | + # pylint: disable=F0401, E0611, W0404 |
720 | from ubuntuone.controlpanel.constants import PROJECT_DIR |
721 | return PROJECT_DIR |
722 | except ImportError: |
723 | |
724 | === modified file 'ubuntuone/controlpanel/webclient.py' |
725 | --- ubuntuone/controlpanel/webclient.py 2011-03-31 18:40:33 +0000 |
726 | +++ ubuntuone/controlpanel/webclient.py 2011-04-07 15:22:46 +0000 |
727 | @@ -32,10 +32,18 @@ |
728 | logger = setup_logging('webclient') |
729 | |
730 | |
731 | +# full list of status codes |
732 | +# http://library.gnome.org/devel/libsoup/stable/libsoup-2.4-soup-status.html |
733 | + |
734 | + |
735 | class WebClientError(Exception): |
736 | """An http error happened while calling the webservice.""" |
737 | |
738 | |
739 | +class UnauthorizedError(WebClientError): |
740 | + """The request ended with bad_request, unauthorized or forbidden.""" |
741 | + |
742 | + |
743 | class WebClient(object): |
744 | """A client for the u1 webservice.""" |
745 | |
746 | @@ -55,7 +63,10 @@ |
747 | result = simplejson.loads(data) |
748 | d.callback(result) |
749 | else: |
750 | - e = WebClientError(msg.status_code, data) |
751 | + if msg.status_code in (401,): |
752 | + e = UnauthorizedError(msg.status_code, data) |
753 | + else: |
754 | + e = WebClientError(msg.status_code, data) |
755 | d.errback(e) |
756 | |
757 | def _call_api_with_creds(self, credentials, api_name): |
+1