1
# -*- coding: utf-8 -*-
3
# Authors: Alejandro J. Cura <alecu@canonical.com>
4
# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
6
# Copyright 2010 Canonical Ltd.
8
# This program is free software: you can redistribute it and/or modify it
9
# under the terms of the GNU General Public License version 3, as published
10
# by the Free Software Foundation.
12
# This program is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranties of
14
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15
# PURPOSE. See the GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License along
18
# with this program. If not, see <http://www.gnu.org/licenses/>.
20
"""Tests for the control panel backend DBus service."""
25
from twisted.internet import defer
26
from twisted.python.failure import Failure
28
from ubuntuone.controlpanel import dbus_service
29
from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
30
DBUS_PREFERENCES_IFACE)
31
from ubuntuone.controlpanel.integrationtests import TestCase
34
SAMPLE_ACCOUNT_INFO = {
35
"quota_used": "12345",
36
"quota_total": "54321",
37
"type": "paying customer",
38
"name": "john carlos",
39
"email": "john@carlos.com",
42
SAMPLE_DEVICES_INFO = [
44
"device_token": "token-1",
45
"name": "Ubuntu One @ darkstar",
46
"date_added": "2008-12-06T18:15:38.0",
49
"limit_bandwidth": "1",
50
"max_upload_speed": "12345",
51
"max_download_speed": "54321",
52
"available_services": "files, contacts, music, bookmarks",
53
"enabled_services": "files, music",
56
"device_token": "token-2",
57
"name": "Ubuntu One @ brightmoon",
58
"date_added": "2010-09-22T20:45:38.0",
61
"available_services": "files, contacts, bookmarks",
62
"enabled_services": "files, bookmarks",
66
SAMPLE_VOLUMES_INFO = [
68
"volume_id": "volume-0",
69
"path": "/home/user/Documents",
70
"suggested_path": "~/Documents",
74
"volume_id": "volume-1",
75
"path": "/home/user/Music",
76
"suggested_path": "~/Music",
80
"volume_id": "volume-2",
81
"path": "/home/user/Pictures/Photos",
82
"suggested_path": "~/Pictures/Photos",
88
class DBusServiceMainTestCase(mocker.MockerTestCase):
89
"""Tests for the main function."""
91
def test_dbus_service_main(self):
92
"""The main method starts the loop and hooks up to DBus."""
93
rs_name = "ubuntuone.controlpanel.dbus_service.register_service"
94
rs = self.mocker.replace(rs_name)
96
self.mocker.result(True)
97
rml_name = "ubuntuone.controlpanel.dbus_service.run_mainloop"
98
rml = self.mocker.replace(rml_name)
100
pb_name = "ubuntuone.controlpanel.dbus_service.publish_backend"
101
pb = self.mocker.replace(pb_name)
106
def test_dbus_service_cant_register(self):
107
"""The main method can't start the loop."""
108
rs_name = "ubuntuone.controlpanel.dbus_service.register_service"
109
rs = self.mocker.replace(rs_name)
111
self.mocker.result(False)
116
class MockBackend(object):
117
"""A mock backend."""
120
def _process(self, result):
121
"""Process the request with the given result."""
123
# pylint: disable=E1102
124
return defer.fail(self.exception(result))
125
return defer.succeed(result)
127
def account_info(self):
128
"""Get the user account info."""
129
return self._process(SAMPLE_ACCOUNT_INFO)
131
def devices_info(self):
132
"""Get the user devices info."""
133
return self._process(SAMPLE_DEVICES_INFO)
135
def change_device_settings(self, token, settings):
136
"""Configure a given device."""
137
return self._process(token)
139
def remove_device(self, token):
140
"""Remove a device's tokens from the sso server."""
141
return self._process(token)
143
def file_sync_status(self):
144
"""Return the status of the file sync service."""
145
return self._process((True, "Synchronizing"))
147
def volumes_info(self):
148
"""Get the user volumes info."""
149
return self._process(SAMPLE_VOLUMES_INFO)
151
def change_volume_settings(self, volume_id, settings):
152
"""Configure a given volume."""
153
return self._process(volume_id)
155
def query_bookmark_extension(self):
156
"""True if the bookmark extension has been installed."""
157
return self._process(False)
159
def install_bookmarks_extension(self):
160
"""Install the extension to sync bookmarks."""
161
return self._process(None)
164
class DBusServiceTestCase(TestCase):
165
"""Test for the DBus service."""
170
"""Initialize each test run."""
171
super(DBusServiceTestCase, self).setUp()
172
dbus_service.init_mainloop()
174
def test_register_service(self):
175
"""The DBus service is successfully registered."""
176
ret = dbus_service.register_service()
179
def test_cant_register_twice(self):
180
"""The DBus service can't register if it already did."""
181
ret = dbus_service.register_service()
183
ret = dbus_service.register_service()
184
self.assertFalse(ret)
185
#pylint: disable=W0612
186
test_cant_register_twice.skip = "Must run 2nd check in another process."
188
def test_dbus_busname_created(self):
189
"""The DBus BusName is created."""
190
busname = dbus_service.get_busname()
191
self.assertEqual(busname.get_name(), DBUS_BUS_NAME)
193
def test_error_handler_with_failure(self):
194
"""Ensure to build a string-string dict to pass to error signals."""
195
error = dbus_service.Failure(TypeError('oh no!'))
196
expected = dbus_service.utils.failure_to_error_dict(error)
198
result = dbus_service.error_handler(error)
200
self.assertEqual(expected, result)
202
def test_error_handler_with_exception(self):
203
"""Ensure to build a string-string dict to pass to error signals."""
204
error = TypeError('oh no, no again!')
205
expected = dbus_service.utils.exception_to_error_dict(error)
207
result = dbus_service.error_handler(error)
209
self.assertEqual(expected, result)
211
def test_error_handler_with_string_dict(self):
212
"""Ensure to build a string-string dict to pass to error signals."""
213
expected = {'test': 'me'}
215
result = dbus_service.error_handler(expected)
217
self.assertEqual(expected, result)
219
def test_error_handler_with_non_string_dict(self):
220
"""Ensure to build a string-string dict to pass to error signals."""
221
expected = {'test': 0, 'quƩ?': None,
222
10: 'foo\xffbar', True: u'ƱoƱo'}
224
result = dbus_service.error_handler(expected)
225
expected = dict(map(lambda x: x if isinstance(x, unicode) else
226
str(x).decode('utf8', 'replace'), i)
227
for i in expected.iteritems())
229
self.assertEqual(expected, result)
231
def test_error_handler_default(self):
232
"""Ensure to build a string-string dict to pass to error signals."""
233
msg = 'Got unexpected error argument %r' % None
234
expected = {dbus_service.utils.ERROR_TYPE: 'UnknownError',
235
dbus_service.utils.ERROR_MESSAGE: msg}
237
result = dbus_service.error_handler(None)
239
self.assertEqual(expected, result)
242
class OperationsTestCase(TestCase):
243
"""Test for the DBus service operations."""
248
super(OperationsTestCase, self).setUp()
249
dbus_service.init_mainloop()
250
be = dbus_service.publish_backend(MockBackend())
251
self.addCleanup(be.remove_from_connection)
252
bus = dbus.SessionBus()
253
obj = bus.get_object(bus_name=DBUS_BUS_NAME,
254
object_path=DBUS_PREFERENCES_PATH,
255
follow_name_owner_changes=True)
256
self.backend = dbus.Interface(object=obj,
257
dbus_interface=DBUS_PREFERENCES_IFACE)
258
self.deferred = defer.Deferred()
263
super(OperationsTestCase, self).tearDown()
265
def got_error(self, *a):
266
"""Some error happened in the DBus call."""
267
self.deferred.errback(*a)
269
def ignore(self, *a):
270
"""Do nothing with the returned value."""
272
def errback_on_error(self, f):
273
"""Call the given 'f' but errback self.deferred on any error."""
276
"""Call the given 'f' but errback self.deferred on any error."""
279
except Exception, e: # pylint: disable=W0703
280
self.deferred.errback(Failure(e))
284
def assert_correct_method_call(self, success_sig, error_sig, success_cb,
286
"""Connect 'success_cb' with 'success_sig', and call 'method'.
288
'error_sig' will be connected to the class' error handler.
290
'success_cb' should fire 'self.deferred' on success. If 'success_cb'
291
fails due to an failed assertion, the deferred will be errback'd.
294
success_cb = self.errback_on_error(success_cb)
295
res = self.backend.connect_to_signal(success_sig, success_cb)
296
self.addCleanup(res.remove)
298
res = self.backend.connect_to_signal(error_sig, self.got_error)
299
self.addCleanup(res.remove)
301
method = self.errback_on_error(method)
302
method(*args, reply_handler=self.ignore, error_handler=self.got_error)
306
def test_account_info_returned(self):
307
"""The account info is successfully returned."""
309
def got_signal(account_info):
310
"""The correct signal was fired."""
311
self.assertIn("quota_used", account_info)
312
self.assertIn("quota_total", account_info)
313
self.assertIn("type", account_info)
314
self.assertIn("name", account_info)
315
self.assertIn("email", account_info)
316
self.deferred.callback("success")
318
args = ("AccountInfoReady", "AccountInfoError", got_signal,
319
self.backend.account_info)
320
return self.assert_correct_method_call(*args)
322
def test_devices_info_returned(self):
323
"""The devices info is successfully returned."""
325
def got_signal(devices_list):
326
"""The correct signal was fired."""
327
for device_info in devices_list:
328
self.assertIn("device_token", device_info)
329
self.assertIn("name", device_info)
330
self.assertIn("date_added", device_info)
331
self.assertIn("type", device_info)
332
self.assertIn("configurable", device_info)
333
if int(device_info["configurable"]):
334
self.assertIn("limit_bandwidth", device_info)
335
self.assertIn("max_upload_speed", device_info)
336
self.assertIn("max_download_speed", device_info)
337
self.assertIn("available_services", device_info)
338
self.assertIn("enabled_services", device_info)
339
self.deferred.callback("success")
341
args = ("DevicesInfoReady", "DevicesInfoError", got_signal,
342
self.backend.devices_info)
343
return self.assert_correct_method_call(*args)
345
def test_change_device_settings(self):
346
"""The device settings are successfully changed."""
347
sample_token = "token-1"
349
def got_signal(token):
350
"""The correct token was received."""
351
self.assertEqual(token, sample_token)
352
self.deferred.callback("success")
355
"enabled_services": "files, contacts",
357
args = ("DeviceSettingsChanged", "DeviceSettingsChangeError",
358
got_signal, self.backend.change_device_settings,
359
sample_token, settings)
360
return self.assert_correct_method_call(*args)
362
def test_remove_device(self):
363
"""The device is removed."""
364
sample_token = "token-1"
366
def got_signal(token):
367
"""The correct token was received."""
368
self.assertEqual(token, sample_token)
369
self.deferred.callback("success")
371
args = ("DeviceRemoved", "DeviceRemovalError", got_signal,
372
self.backend.remove_device, sample_token)
373
return self.assert_correct_method_call(*args)
375
def test_file_sync_status(self):
376
"""The file sync status is reported."""
378
def got_signal(enabled, status):
379
"""The correct status was received."""
380
self.assertEqual(enabled, True)
381
self.assertEqual(status, "Synchronizing")
382
self.deferred.callback("success")
384
args = ("FileSyncStatusReady", "FileSyncStatusError", got_signal,
385
self.backend.file_sync_status)
386
return self.assert_correct_method_call(*args)
388
def test_volumes_info(self):
389
"""The volumes info is reported."""
391
def got_signal(volumes_dict):
392
"""The correct info was received."""
393
self.assertEqual(volumes_dict, SAMPLE_VOLUMES_INFO)
394
self.deferred.callback("success")
396
args = ("VolumesInfoReady", "VolumesInfoError", got_signal,
397
self.backend.volumes_info)
398
return self.assert_correct_method_call(*args)
400
def test_change_volume_settings(self):
401
"""The volume settings are successfully changed."""
402
expected_volume_id = SAMPLE_VOLUMES_INFO[0]['volume_id']
404
def got_signal(volume_id):
405
"""The correct volume was changed."""
406
self.assertEqual(volume_id, expected_volume_id)
407
self.deferred.callback("success")
409
args = ("VolumeSettingsChanged", "VolumeSettingsChangeError",
410
got_signal, self.backend.change_volume_settings,
411
expected_volume_id, {'subscribed': '0'})
412
return self.assert_correct_method_call(*args)
414
def test_query_bookmarks_extension(self):
415
"""The bookmarks extension is queried."""
417
def got_signal(enabled):
418
"""The correct status was received."""
419
self.assertEqual(enabled, False)
420
self.deferred.callback("success")
422
args = ("QueryBookmarksResult", "QueryBookmarksError", got_signal,
423
self.backend.query_bookmark_extension)
424
return self.assert_correct_method_call(*args)
426
def test_install_bookmarks_extension(self):
427
"""The bookmarks extension is installed."""
430
"""The extension was installed."""
431
self.deferred.callback("success")
433
args = ("InstallBookmarksSuccess", "InstallBookmarksError", got_signal,
434
self.backend.install_bookmarks_extension)
435
return self.assert_correct_method_call(*args)
438
class OperationsErrorTestCase(OperationsTestCase):
439
"""Test for the DBus service operations when there is an error."""
442
super(OperationsErrorTestCase, self).setUp()
443
self.patch(MockBackend, 'exception', AssertionError)
445
def assert_correct_method_call(self, success_sig, error_sig, success_cb,
447
"""Call parent instance swapping success_sig with error_sig.
449
This is because we want to succeed the test when the error signal was
454
def got_error_signal(error_dict):
455
"""The error signal was received."""
456
self.assertEqual(error_dict[dbus_service.utils.ERROR_TYPE],
458
self.deferred.callback("success")
460
return super(OperationsErrorTestCase, self).assert_correct_method_call(
461
error_sig, success_sig, got_error_signal, method, *args)