Merge lp:~nataliabidart/ubuntuone-control-panel/device-list-type-error into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 124
Merged at revision: 121
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/device-list-type-error
Merge into: lp:ubuntuone-control-panel
Diff against target: 349 lines (+106/-19)
6 files modified
ubuntuone/controlpanel/backend.py (+31/-5)
ubuntuone/controlpanel/dbus_client.py (+0/-1)
ubuntuone/controlpanel/dbus_service.py (+11/-8)
ubuntuone/controlpanel/tests/__init__.py (+10/-0)
ubuntuone/controlpanel/tests/test_backend.py (+50/-2)
ubuntuone/controlpanel/webclient.py (+4/-3)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/device-list-type-error
Reviewer Review Type Date Requested Status
Martin Albisetti (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+55808@code.launchpad.net

Commit message

- Made the backend robust against possible None values (or any non basestring instance) sent from the API server (LP: #745790).

Description of the change

There is no good way of IRL test this since we would need to force a response from our API servers to send a null device name, for example.

If you insist, you should temporarly change the line 275 in backend.py and set something like:

local_device["name"] = None

and then run both the backend and UI from this branch.

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

Looks great!

review: Approve
124. By Natalia Bidart

Typo.

Revision history for this message
Martin Albisetti (beuno) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ubuntuone/controlpanel/backend.py'
--- ubuntuone/controlpanel/backend.py 2011-03-29 17:10:59 +0000
+++ ubuntuone/controlpanel/backend.py 2011-03-31 21:12:36 +0000
@@ -61,6 +61,16 @@
61 return 'True' if value else ''61 return 'True' if value else ''
6262
6363
64def filter_field(info, field):
65 """Return a copy of 'info' where each item has 'field' hidden."""
66 result = []
67 for item in info:
68 item = item.copy()
69 item[field] = '<hidden>'
70 result.append(item)
71 return result
72
73
64class ControlBackend(object):74class ControlBackend(object):
65 """The control panel backend."""75 """The control panel backend."""
6676
@@ -178,6 +188,13 @@
178 # di["available_services"] = ""188 # di["available_services"] = ""
179 # di["enabled_services"] = ""189 # di["enabled_services"] = ""
180190
191 # make a sanity check
192 for key, val in di.iteritems():
193 if not isinstance(val, basestring):
194 logger.warning('_process_device_web_info: (key %r), '
195 'val %r is not a basestring.', key, val)
196 di[key] = repr(val)
197
181 if is_local: # prepend the local device!198 if is_local: # prepend the local device!
182 result.insert(0, di)199 result.insert(0, di)
183 else:200 else:
@@ -197,7 +214,6 @@
197 dtype, did = self.type_n_id(device_id)214 dtype, did = self.type_n_id(device_id)
198 local_token = yield self.get_token()215 local_token = yield self.get_token()
199 is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token)216 is_local = (dtype == DEVICE_TYPE_COMPUTER and did == local_token)
200 logger.info('device_is_local: result %r, ', is_local)
201 returnValue(is_local)217 returnValue(is_local)
202218
203 @log_call(logger.debug)219 @log_call(logger.debug)
@@ -238,6 +254,10 @@
238 show_notifs = yield dbus_client.show_all_notifications_enabled()254 show_notifs = yield dbus_client.show_all_notifications_enabled()
239 limits = yield dbus_client.get_throttling_limits()255 limits = yield dbus_client.get_throttling_limits()
240256
257 logger.debug('devices_info: file sync enabled? %s limit_bw %s, limits '
258 '%s, show_notifs %s',
259 enabled, limit_bw, limits, show_notifs)
260
241 try:261 try:
242 devices = yield self.wc.call_api(DEVICES_API)262 devices = yield self.wc.call_api(DEVICES_API)
243 except WebClientError:263 except WebClientError:
@@ -247,6 +267,8 @@
247 limit_bw, limits,267 limit_bw, limits,
248 show_notifs)268 show_notifs)
249 if result is None:269 if result is None:
270 logger.info('devices_info: result is None after calling '
271 'devices/ API, building the local device.')
250 credentials = yield dbus_client.get_credentials()272 credentials = yield dbus_client.get_credentials()
251 local_device = {}273 local_device = {}
252 local_device["type"] = DEVICE_TYPE_COMPUTER274 local_device["type"] = DEVICE_TYPE_COMPUTER
@@ -264,6 +286,10 @@
264 local_device[UPLOAD_KEY] = str(upload)286 local_device[UPLOAD_KEY] = str(upload)
265 local_device[DOWNLOAD_KEY] = str(download)287 local_device[DOWNLOAD_KEY] = str(download)
266 result = [local_device]288 result = [local_device]
289 else:
290 logger.info('devices_info: result is not None after calling '
291 'devices/ API: %r',
292 filter_field(result, field='device_id'))
267293
268 returnValue(result)294 returnValue(result)
269295
@@ -275,7 +301,7 @@
275 return DEVICE_TYPE_PHONE, device_id[5:]301 return DEVICE_TYPE_PHONE, device_id[5:]
276 return "No device", device_id302 return "No device", device_id
277303
278 @log_call(logger.info)304 @log_call(logger.info, with_args=False)
279 @inlineCallbacks305 @inlineCallbacks
280 def change_device_settings(self, device_id, settings):306 def change_device_settings(self, device_id, settings):
281 """Change the settings for the given device."""307 """Change the settings for the given device."""
@@ -311,7 +337,7 @@
311 # still pending: more work on the settings dict (LP: #673674)337 # still pending: more work on the settings dict (LP: #673674)
312 returnValue(device_id)338 returnValue(device_id)
313339
314 @log_call(logger.warning)340 @log_call(logger.warning, with_args=False)
315 @inlineCallbacks341 @inlineCallbacks
316 def remove_device(self, device_id):342 def remove_device(self, device_id):
317 """Remove a device's tokens from the sso server."""343 """Remove a device's tokens from the sso server."""
@@ -322,8 +348,8 @@
322 yield self.wc.call_api(api)348 yield self.wc.call_api(api)
323349
324 if is_local:350 if is_local:
325 logger.warning('remove_device: device is local, id %r, '351 logger.warning('remove_device: device is local! removing and '
326 'clearing credentials.', device_id)352 'clearing credentials.')
327 yield dbus_client.clear_credentials()353 yield dbus_client.clear_credentials()
328354
329 returnValue(device_id)355 returnValue(device_id)
330356
=== modified file 'ubuntuone/controlpanel/dbus_client.py'
--- ubuntuone/controlpanel/dbus_client.py 2011-02-16 15:56:43 +0000
+++ ubuntuone/controlpanel/dbus_client.py 2011-03-31 21:12:36 +0000
@@ -64,7 +64,6 @@
6464
65 def found_credentials(app_name, creds):65 def found_credentials(app_name, creds):
66 """Credentials have been found."""66 """Credentials have been found."""
67 logger.debug('credentials were found for app_name %r.', app_name)
68 if app_name == APP_NAME:67 if app_name == APP_NAME:
69 logger.info('credentials were found! (%r).', APP_NAME)68 logger.info('credentials were found! (%r).', APP_NAME)
70 d.callback(creds)69 d.callback(creds)
7170
=== modified file 'ubuntuone/controlpanel/dbus_service.py'
--- ubuntuone/controlpanel/dbus_service.py 2011-03-28 17:25:21 +0000
+++ ubuntuone/controlpanel/dbus_service.py 2011-03-31 21:12:36 +0000
@@ -32,7 +32,8 @@
32from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,32from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
33 DBUS_PREFERENCES_IFACE)33 DBUS_PREFERENCES_IFACE)
34from ubuntuone.controlpanel.backend import (34from ubuntuone.controlpanel.backend import (
35 ControlBackend, FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED,35 ControlBackend, filter_field,
36 FILE_SYNC_DISABLED, FILE_SYNC_DISCONNECTED,
36 FILE_SYNC_ERROR, FILE_SYNC_IDLE, FILE_SYNC_STARTING, FILE_SYNC_STOPPED,37 FILE_SYNC_ERROR, FILE_SYNC_IDLE, FILE_SYNC_STARTING, FILE_SYNC_STOPPED,
37 FILE_SYNC_SYNCING,38 FILE_SYNC_SYNCING,
38 MSG_KEY, STATUS_KEY,39 MSG_KEY, STATUS_KEY,
@@ -85,6 +86,7 @@
85 """86 """
86 def inner(error, _=None):87 def inner(error, _=None):
87 """Do the Failure transformation."""88 """Do the Failure transformation."""
89 logger.error('processing failure: %r', error.printTraceback())
88 error_dict = error_handler(error)90 error_dict = error_handler(error)
89 if _ is not None:91 if _ is not None:
90 result = f(_, error_dict)92 result = f(_, error_dict)
@@ -149,10 +151,11 @@
149 d.addCallback(self.DevicesInfoReady)151 d.addCallback(self.DevicesInfoReady)
150 d.addErrback(transform_failure(self.DevicesInfoError))152 d.addErrback(transform_failure(self.DevicesInfoError))
151153
152 @log_call(logger.debug)
153 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")154 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
154 def DevicesInfoReady(self, info):155 def DevicesInfoReady(self, info):
155 """The info for the devices is available right now."""156 """The info for the devices is available right now."""
157 logger.debug('DevicesInfoReady: args %r',
158 filter_field(info, field='device_id'))
156159
157 @log_call(logger.error)160 @log_call(logger.error)
158 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")161 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
@@ -161,7 +164,7 @@
161164
162 #---165 #---
163166
164 @log_call(logger.info)167 @log_call(logger.info, with_args=False)
165 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")168 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
166 def change_device_settings(self, device_id, settings):169 def change_device_settings(self, device_id, settings):
167 """Configure a given device."""170 """Configure a given device."""
@@ -170,19 +173,19 @@
170 d.addErrback(transform_failure(self.DeviceSettingsChangeError),173 d.addErrback(transform_failure(self.DeviceSettingsChangeError),
171 device_id)174 device_id)
172175
173 @log_call(logger.info)176 @log_call(logger.info, with_args=False)
174 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")177 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
175 def DeviceSettingsChanged(self, device_id):178 def DeviceSettingsChanged(self, device_id):
176 """The settings for the device were changed."""179 """The settings for the device were changed."""
177180
178 @log_call(logger.error)181 @log_call(logger.error, with_args=False)
179 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")182 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
180 def DeviceSettingsChangeError(self, device_id, error):183 def DeviceSettingsChangeError(self, device_id, error):
181 """Problem changing settings for the device."""184 """Problem changing settings for the device."""
182185
183 #---186 #---
184187
185 @log_call(logger.warning)188 @log_call(logger.warning, with_args=False)
186 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s")189 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s")
187 def remove_device(self, device_id):190 def remove_device(self, device_id):
188 """Remove a given device."""191 """Remove a given device."""
@@ -190,12 +193,12 @@
190 d.addCallback(self.DeviceRemoved)193 d.addCallback(self.DeviceRemoved)
191 d.addErrback(transform_failure(self.DeviceRemovalError), device_id)194 d.addErrback(transform_failure(self.DeviceRemovalError), device_id)
192195
193 @log_call(logger.warning)196 @log_call(logger.warning, with_args=False)
194 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")197 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
195 def DeviceRemoved(self, device_id):198 def DeviceRemoved(self, device_id):
196 """The removal for the device was completed."""199 """The removal for the device was completed."""
197200
198 @log_call(logger.error)201 @log_call(logger.error, with_args=False)
199 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")202 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
200 def DeviceRemovalError(self, device_id, error):203 def DeviceRemovalError(self, device_id, error):
201 """Problem removing the device."""204 """Problem removing the device."""
202205
=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
--- ubuntuone/controlpanel/tests/__init__.py 2011-03-29 11:38:39 +0000
+++ ubuntuone/controlpanel/tests/__init__.py 2011-03-31 21:12:36 +0000
@@ -110,6 +110,16 @@
110]110]
111"""111"""
112112
113EMPTY_DESCRIPTION_JSON = """
114[
115 {
116 "token": "ABCDEF01234token",
117 "description": null,
118 "kind": "Computer"
119 }
120]
121"""
122
113LOCAL_DEVICE = {123LOCAL_DEVICE = {
114 'is_local': 'True',124 'is_local': 'True',
115 'configurable': 'True',125 'configurable': 'True',
116126
=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
--- ubuntuone/controlpanel/tests/test_backend.py 2011-03-29 14:48:28 +0000
+++ ubuntuone/controlpanel/tests/test_backend.py 2011-03-31 21:12:36 +0000
@@ -30,6 +30,7 @@
30from ubuntuone.controlpanel import backend, replication_client30from ubuntuone.controlpanel import backend, replication_client
31from ubuntuone.controlpanel.backend import (bool_str,31from ubuntuone.controlpanel.backend import (bool_str,
32 ACCOUNT_API, DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,32 ACCOUNT_API, DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
33 DEVICE_TYPE_COMPUTER,
33 FILE_SYNC_DISABLED,34 FILE_SYNC_DISABLED,
34 FILE_SYNC_DISCONNECTED,35 FILE_SYNC_DISCONNECTED,
35 FILE_SYNC_ERROR,36 FILE_SYNC_ERROR,
@@ -41,6 +42,7 @@
41 MSG_KEY, STATUS_KEY,42 MSG_KEY, STATUS_KEY,
42)43)
43from ubuntuone.controlpanel.tests import (TestCase,44from ubuntuone.controlpanel.tests import (TestCase,
45 EMPTY_DESCRIPTION_JSON,
44 EXPECTED_ACCOUNT_INFO,46 EXPECTED_ACCOUNT_INFO,
45 EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,47 EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,
46 EXPECTED_DEVICES_INFO,48 EXPECTED_DEVICES_INFO,
@@ -251,7 +253,7 @@
251 self.patch(backend, "WebClient", MockWebClient)253 self.patch(backend, "WebClient", MockWebClient)
252 self.patch(backend, "dbus_client", MockDBusClient())254 self.patch(backend, "dbus_client", MockDBusClient())
253 self.patch(backend, "replication_client", MockReplicationClient())255 self.patch(backend, "replication_client", MockReplicationClient())
254 self.local_token = "Computer" + TOKEN["token"]256 self.local_token = DEVICE_TYPE_COMPUTER + TOKEN["token"]
255 self.be = backend.ControlBackend()257 self.be = backend.ControlBackend()
256258
257 self.memento = MementoHandler()259 self.memento = MementoHandler()
@@ -380,9 +382,35 @@
380 self.assertEqual(result, expected)382 self.assertEqual(result, expected)
381383
382 @inlineCallbacks384 @inlineCallbacks
385 def test_devices_info_when_token_name_is_empty(self):
386 """The devices_info can handle empty token names."""
387 # pylint: disable=E1101
388 self.be.wc.results[DEVICES_API] = EMPTY_DESCRIPTION_JSON
389 result = yield self.be.devices_info()
390 expected = {'configurable': '',
391 'device_id': 'ComputerABCDEF01234token',
392 'is_local': '', 'name': 'None',
393 'type': DEVICE_TYPE_COMPUTER}
394 self.assertEqual(result, [expected])
395 self.assertTrue(self.memento.check_warning('name', 'None'))
396
397 @inlineCallbacks
398 def test_devices_info_does_not_log_device_id(self):
399 """The devices_info does not log the device_id."""
400 # pylint: disable=E1101
401 self.be.wc.results[DEVICES_API] = SAMPLE_DEVICES_JSON
402 yield self.be.devices_info()
403
404 dids = (d['device_id'] for d in EXPECTED_DEVICES_INFO)
405 device_id_logged = all(all(did not in r.getMessage()
406 for r in self.memento.records)
407 for did in dids)
408 self.assertTrue(device_id_logged)
409
410 @inlineCallbacks
383 def test_remove_device(self):411 def test_remove_device(self):
384 """The remove_device method calls the right api."""412 """The remove_device method calls the right api."""
385 dtype, did = "Computer", "SAMPLE-TOKEN"413 dtype, did = DEVICE_TYPE_COMPUTER, "SAMPLE-TOKEN"
386 device_id = dtype + did414 device_id = dtype + did
387 apiurl = DEVICE_REMOVE_API % (dtype.lower(), did)415 apiurl = DEVICE_REMOVE_API % (dtype.lower(), did)
388 # pylint: disable=E1101416 # pylint: disable=E1101
@@ -411,6 +439,16 @@
411 WebClientError)439 WebClientError)
412440
413 @inlineCallbacks441 @inlineCallbacks
442 def test_remove_device_does_not_log_device_id(self):
443 """The remove_device does not log the device_id."""
444 device_id = DEVICE_TYPE_COMPUTER + TOKEN['token']
445 yield self.be.remove_device(device_id)
446
447 device_id_logged = all(device_id not in r.getMessage()
448 for r in self.memento.records)
449 self.assertTrue(device_id_logged)
450
451 @inlineCallbacks
414 def test_change_show_all_notifications(self):452 def test_change_show_all_notifications(self):
415 """The device settings are updated."""453 """The device settings are updated."""
416 backend.dbus_client.show_all_notifications = False454 backend.dbus_client.show_all_notifications = False
@@ -465,6 +503,16 @@
465 self.assertEqual(backend.dbus_client.limits["upload"], -1)503 self.assertEqual(backend.dbus_client.limits["upload"], -1)
466 self.assertEqual(backend.dbus_client.limits["download"], -1)504 self.assertEqual(backend.dbus_client.limits["download"], -1)
467505
506 @inlineCallbacks
507 def test_changing_settings_does_not_log_device_id(self):
508 """The change_device_settings does not log the device_id."""
509 device_id = 'yadda-yadda'
510 yield self.be.change_device_settings(device_id, {})
511
512 device_id_logged = all(device_id not in r.getMessage()
513 for r in self.memento.records)
514 self.assertTrue(device_id_logged)
515
468516
469class BackendVolumesTestCase(BackendBasicTestCase):517class BackendVolumesTestCase(BackendBasicTestCase):
470 """Volumes tests for the backend."""518 """Volumes tests for the backend."""
471519
=== modified file 'ubuntuone/controlpanel/webclient.py'
--- ubuntuone/controlpanel/webclient.py 2010-12-18 16:21:36 +0000
+++ ubuntuone/controlpanel/webclient.py 2011-03-31 21:12:36 +0000
@@ -48,7 +48,7 @@
4848
49 def _handler(self, session, msg, d):49 def _handler(self, session, msg, d):
50 """Handle the result of an http message."""50 """Handle the result of an http message."""
51 logger.debug("WebClient: got http response %d for uri %r",51 logger.debug("got http response %d for uri %r",
52 msg.status_code, msg.get_uri().to_string(False))52 msg.status_code, msg.get_uri().to_string(False))
53 data = msg.response_body.data53 data = msg.response_body.data
54 if msg.status_code == 200:54 if msg.status_code == 200:
@@ -62,7 +62,7 @@
62 """Get a given url from the webservice with credentials."""62 """Get a given url from the webservice with credentials."""
63 url = (self.base_url + api_name).encode('utf-8')63 url = (self.base_url + api_name).encode('utf-8')
64 method = "GET"64 method = "GET"
65 logger.debug("WebClient: getting url: %s, %s", method, url)65 logger.debug("getting url: %s, %s", method, url)
66 msg = Soup.Message.new(method, url)66 msg = Soup.Message.new(method, url)
67 add_oauth_headers(msg.request_headers.append, method, url, credentials)67 add_oauth_headers(msg.request_headers.append, method, url, credentials)
68 d = defer.Deferred()68 d = defer.Deferred()
@@ -71,7 +71,8 @@
7171
72 def call_api(self, api_name):72 def call_api(self, api_name):
73 """Get a given url from the webservice."""73 """Get a given url from the webservice."""
74 logger.debug("WebClient: calling api: %s", api_name)74 # this may log device ID's, but only for removals, which is OK
75 logger.debug("calling api: %s", api_name)
75 d = self.get_credentials()76 d = self.get_credentials()
76 d.addCallback(self._call_api_with_creds, api_name)77 d.addCallback(self._call_api_with_creds, api_name)
77 return d78 return d

Subscribers

People subscribed via source and target branches