Merge lp:~nataliabidart/ubuntuone-control-panel/replication-to-the-backend into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 45
Merged at revision: 44
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/replication-to-the-backend
Merge into: lp:ubuntuone-control-panel
Diff against target: 1056 lines (+677/-172)
7 files modified
ubuntuone/controlpanel/backend.py (+41/-1)
ubuntuone/controlpanel/dbus_service.py (+41/-0)
ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+55/-7)
ubuntuone/controlpanel/replication_client.py (+115/-0)
ubuntuone/controlpanel/tests/__init__.py (+149/-1)
ubuntuone/controlpanel/tests/test_backend.py (+116/-163)
ubuntuone/controlpanel/tests/test_replication_client.py (+160/-0)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/replication-to-the-backend
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+45439@code.launchpad.net

Commit message

Desktopcouch replication service is managed in the backend and exposed in the DBus service (LP: #696782).

Description of the change

There is no easy way to test this except by running the backen with:

DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-backend

and using d-feet to play with the replication methods. The merge proposal:

https://code.launchpad.net/~nataliabidart/ubuntuone-control-panel/start-dc-on-backend/+merge/45440

provides the GUI that accesses this service directly.

To post a comment you must log in.
45. By Natalia Bidart

Forgot some files!

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Minor point: I don't like exclude/replicate as complementary method names, I'd prefer exclude/include or maybe even include_in_replication/exclude_from_replication for clarity.

Revision history for this message
Eric Casteleijn (thisfred) wrote :

I'm not sure I understand the 'dependency' field in the replication record. Bookmarks and contacts should be replicated, or at least replicatable, regardless of what is installed. Am I missing the point?

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Looks good otherwise.

review: Approve
Revision history for this message
Roberto Alsina (ralsina) wrote :

Approved. I agree with Eric and Natalia (on IRC) that the spec needs some improvements because what dbs you replicate shouldn't depend on installed packages, but this is according to the current spec.

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 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/backend.py 2011-01-06 20:44:10 +0000
@@ -22,6 +22,7 @@
22from twisted.internet.defer import inlineCallbacks, returnValue22from twisted.internet.defer import inlineCallbacks, returnValue
2323
24from ubuntuone.controlpanel import dbus_client24from ubuntuone.controlpanel import dbus_client
25from ubuntuone.controlpanel import replication_client
25from ubuntuone.controlpanel.logger import setup_logging, log_call26from ubuntuone.controlpanel.logger import setup_logging, log_call
26from ubuntuone.controlpanel.webclient import WebClient27from ubuntuone.controlpanel.webclient import WebClient
2728
@@ -48,6 +49,9 @@
48MSG_KEY = 'message'49MSG_KEY = 'message'
49STATUS_KEY = 'status'50STATUS_KEY = 'status'
5051
52BOOKMARKS_PKG = 'xul-ext-bindwood'
53CONTACTS_PKG = 'evolution-couchdb'
54
5155
52def bool_str(value):56def bool_str(value):
53 """Return the string representation of a bool (dbus-compatible)."""57 """Return the string representation of a bool (dbus-compatible)."""
@@ -300,7 +304,7 @@
300304
301 """305 """
302 if 'subscribed' in settings:306 if 'subscribed' in settings:
303 subscribed = settings['subscribed']307 subscribed = bool(settings['subscribed'])
304 if subscribed:308 if subscribed:
305 yield self.subscribe_volume(volume_id)309 yield self.subscribe_volume(volume_id)
306 else:310 else:
@@ -321,6 +325,42 @@
321 yield dbus_client.unsubscribe_folder(volume_id)325 yield dbus_client.unsubscribe_folder(volume_id)
322326
323 @log_call(logger.debug)327 @log_call(logger.debug)
328 @inlineCallbacks
329 def replications_info(self):
330 """Get the user replications info."""
331 replications = yield replication_client.get_replications()
332 exclusions = yield replication_client.get_exclusions()
333
334 result = []
335 for rep in replications:
336 dependency = ''
337 if rep == replication_client.BOOKMARKS:
338 dependency = BOOKMARKS_PKG
339 elif rep == replication_client.CONTACTS:
340 dependency = CONTACTS_PKG
341
342 repd = {
343 "replication_id": rep,
344 "name": rep, # this may change to be more user friendly
345 "enabled": bool_str(rep not in exclusions),
346 "dependency": dependency,
347 }
348 result.append(repd)
349
350 returnValue(result)
351
352 @log_call(logger.info)
353 @inlineCallbacks
354 def change_replication_settings(self, replication_id, settings):
355 """Change the settings for the given replication."""
356 if 'enabled' in settings:
357 if bool(settings['enabled']):
358 yield replication_client.replicate(replication_id)
359 else:
360 yield replication_client.exclude(replication_id)
361 returnValue(replication_id)
362
363 @log_call(logger.debug)
324 def query_bookmark_extension(self):364 def query_bookmark_extension(self):
325 """True if the bookmark extension has been installed."""365 """True if the bookmark extension has been installed."""
326 # still pending (LP: #673672)366 # still pending (LP: #673672)
327367
=== modified file 'ubuntuone/controlpanel/dbus_service.py'
--- ubuntuone/controlpanel/dbus_service.py 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/dbus_service.py 2011-01-06 20:44:10 +0000
@@ -339,6 +339,47 @@
339339
340 @log_call(logger.debug)340 @log_call(logger.debug)
341 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")341 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
342 def replications_info(self):
343 """Return the replications info."""
344 d = self.backend.replications_info()
345 d.addCallback(self.ReplicationsInfoReady)
346 d.addErrback(transform_failure(self.ReplicationsInfoError))
347
348 @log_call(logger.debug)
349 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
350 def ReplicationsInfoReady(self, info):
351 """The replications info is ready."""
352
353 @log_call(logger.error)
354 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
355 def ReplicationsInfoError(self, error):
356 """Problem getting the replications info."""
357
358 #---
359
360 @log_call(logger.info)
361 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
362 def change_replication_settings(self, replication_id, settings):
363 """Configure a given replication."""
364 d = self.backend.change_replication_settings(replication_id, settings)
365 d.addCallback(self.ReplicationSettingsChanged)
366 d.addErrback(transform_failure(self.ReplicationSettingsChangeError),
367 replication_id)
368
369 @log_call(logger.info)
370 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
371 def ReplicationSettingsChanged(self, replication_id):
372 """The settings for the replication were changed."""
373
374 @log_call(logger.error)
375 @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
376 def ReplicationSettingsChangeError(self, replication_id, error):
377 """Problem changing settings for the replication."""
378
379 #---
380
381 @log_call(logger.debug)
382 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
342 def query_bookmark_extension(self):383 def query_bookmark_extension(self):
343 """Check if the extension to sync bookmarks is installed."""384 """Check if the extension to sync bookmarks is installed."""
344 d = self.backend.query_bookmark_extension()385 d = self.backend.query_bookmark_extension()
345386
=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py'
--- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-01-06 20:44:10 +0000
@@ -84,6 +84,11 @@
84 },84 },
85]85]
8686
87SAMPLE_REPLICATIONS_INFO = [
88 {'replication_id': 'yadda', 'wait for it': 'awesome'},
89 {'replication_id': 'yoda', 'something else': 'awesome'},
90]
91
8792
88class DBusServiceMainTestCase(mocker.MockerTestCase):93class DBusServiceMainTestCase(mocker.MockerTestCase):
89 """Tests for the main function."""94 """Tests for the main function."""
@@ -166,6 +171,19 @@
166 """Configure a given volume."""171 """Configure a given volume."""
167 return self._process(volume_id)172 return self._process(volume_id)
168173
174 def replications_info(self):
175 """Start the replication exclusion service if needed.
176
177 Return the replication info, which is a dictionary of (replication
178 name, enabled).
179
180 """
181 return self._process(SAMPLE_REPLICATIONS_INFO)
182
183 def change_replication_settings(self, replication_id, settings):
184 """Configure a given replication."""
185 return self._process(replication_id)
186
169 def query_bookmark_extension(self):187 def query_bookmark_extension(self):
170 """True if the bookmark extension has been installed."""188 """True if the bookmark extension has been installed."""
171 return self._process(False)189 return self._process(False)
@@ -258,13 +276,13 @@
258 self.assertEqual(expected, result)276 self.assertEqual(expected, result)
259277
260278
261class OperationsTestCase(TestCase):279class BaseTestCase(TestCase):
262 """Test for the DBus service operations."""280 """Base test case for the DBus service."""
263281
264 timeout = 3282 timeout = 3
265283
266 def setUp(self):284 def setUp(self):
267 super(OperationsTestCase, self).setUp()285 super(BaseTestCase, self).setUp()
268 dbus_service.init_mainloop()286 dbus_service.init_mainloop()
269 be = dbus_service.publish_backend(MockBackend())287 be = dbus_service.publish_backend(MockBackend())
270 self.addCleanup(be.remove_from_connection)288 self.addCleanup(be.remove_from_connection)
@@ -279,7 +297,7 @@
279 def tearDown(self):297 def tearDown(self):
280 self.backend = None298 self.backend = None
281 self.deferred = None299 self.deferred = None
282 super(OperationsTestCase, self).tearDown()300 super(BaseTestCase, self).tearDown()
283301
284 def got_error(self, *a):302 def got_error(self, *a):
285 """Some error happened in the DBus call."""303 """Some error happened in the DBus call."""
@@ -322,6 +340,10 @@
322340
323 return self.deferred341 return self.deferred
324342
343
344class OperationsTestCase(BaseTestCase):
345 """Test for the DBus service operations."""
346
325 def test_account_info_returned(self):347 def test_account_info_returned(self):
326 """The account info is successfully returned."""348 """The account info is successfully returned."""
327349
@@ -416,9 +438,9 @@
416 def test_volumes_info(self):438 def test_volumes_info(self):
417 """The volumes info is reported."""439 """The volumes info is reported."""
418440
419 def got_signal(volumes_dict):441 def got_signal(volumes):
420 """The correct info was received."""442 """The correct info was received."""
421 self.assertEqual(volumes_dict, SAMPLE_VOLUMES_INFO)443 self.assertEqual(volumes, SAMPLE_VOLUMES_INFO)
422 self.deferred.callback("success")444 self.deferred.callback("success")
423445
424 args = ("VolumesInfoReady", "VolumesInfoError", got_signal,446 args = ("VolumesInfoReady", "VolumesInfoError", got_signal,
@@ -439,6 +461,32 @@
439 expected_volume_id, {'subscribed': ''})461 expected_volume_id, {'subscribed': ''})
440 return self.assert_correct_method_call(*args)462 return self.assert_correct_method_call(*args)
441463
464 def test_replications_info(self):
465 """The replications info is reported."""
466
467 def got_signal(replications):
468 """The correct info was received."""
469 self.assertEqual(replications, SAMPLE_REPLICATIONS_INFO)
470 self.deferred.callback("success")
471
472 args = ("ReplicationsInfoReady", "ReplicationsInfoError", got_signal,
473 self.backend.replications_info)
474 return self.assert_correct_method_call(*args)
475
476 def test_change_replication_settings(self):
477 """The replication settings are successfully changed."""
478 expected_replication_id = SAMPLE_REPLICATIONS_INFO[0]['replication_id']
479
480 def got_signal(replication_id):
481 """The correct replication was changed."""
482 self.assertEqual(replication_id, expected_replication_id)
483 self.deferred.callback("success")
484
485 args = ("ReplicationSettingsChanged", "ReplicationSettingsChangeError",
486 got_signal, self.backend.change_replication_settings,
487 expected_replication_id, {'enabled': ''})
488 return self.assert_correct_method_call(*args)
489
442 def test_query_bookmarks_extension(self):490 def test_query_bookmarks_extension(self):
443 """The bookmarks extension is queried."""491 """The bookmarks extension is queried."""
444492
@@ -495,7 +543,7 @@
495 error_sig, success_sig, got_error_signal, method, *args)543 error_sig, success_sig, got_error_signal, method, *args)
496544
497545
498class FileSyncTestCase(OperationsTestCase):546class FileSyncTestCase(BaseTestCase):
499 """Test for the DBus service when requesting file sync status."""547 """Test for the DBus service when requesting file sync status."""
500548
501 def assert_correct_status_signal(self, status, sync_signal,549 def assert_correct_status_signal(self, status, sync_signal,
502550
=== added file 'ubuntuone/controlpanel/replication_client.py'
--- ubuntuone/controlpanel/replication_client.py 1970-01-01 00:00:00 +0000
+++ ubuntuone/controlpanel/replication_client.py 2011-01-06 20:44:10 +0000
@@ -0,0 +1,115 @@
1# -*- coding: utf-8 -*-
2
3# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
4#
5# Copyright 2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""Client to use replication services."""
20
21from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
22
23from ubuntuone.controlpanel.logger import setup_logging
24
25
26logger = setup_logging('replication_client')
27
28BOOKMARKS = 'bookmarks'
29CONTACTS = 'contacts'
30# we should get this list from somewhere else
31REPLICATIONS = set([BOOKMARKS, CONTACTS])
32
33
34class ReplicationError(Exception):
35 """A replication error."""
36
37
38class NoPairingRecord(ReplicationError):
39 """There is no pairing record."""
40
41
42class InvalidIdError(ReplicationError):
43 """The replication id is not valid."""
44
45
46class NotExcludedError(ReplicationError):
47 """The replication can not be replicated since is not excluded."""
48
49
50class AlreadyExcludedError(ReplicationError):
51 """The replication can not be excluded since is already excluded."""
52
53
54def get_replication_proxy(replication_module=None):
55 """Return a proxy to the replication client."""
56 d = Deferred()
57 if replication_module is None:
58 # delay import in case DC is not installed at module import time
59 # Unable to import 'desktopcouch.application.replication_services'
60 # pylint: disable=F0401
61 from desktopcouch.application.replication_services \
62 import ubuntuone as replication_module
63 try:
64 result = replication_module.ReplicationExclusion()
65 except ValueError:
66 d.errback(NoPairingRecord())
67 else:
68 d.callback(result)
69
70 return d
71
72
73@inlineCallbacks
74def get_replications():
75 """Retrieve the list of replications."""
76 yield get_replication_proxy()
77 returnValue(REPLICATIONS)
78
79
80@inlineCallbacks
81def get_exclusions():
82 """Retrieve the list of exclusions."""
83 proxy = yield get_replication_proxy()
84 result = proxy.all_exclusions()
85 returnValue(result)
86
87
88@inlineCallbacks
89def replicate(replication_id):
90 """Remove replication_id from the exclusions list."""
91 replications = yield get_replications()
92 if replication_id not in replications:
93 raise InvalidIdError(replication_id)
94
95 exclusions = yield get_exclusions()
96 if replication_id not in exclusions:
97 raise NotExcludedError(replication_id)
98
99 proxy = yield get_replication_proxy()
100 yield proxy.replicate(replication_id)
101
102
103@inlineCallbacks
104def exclude(replication_id):
105 """Add replication_id to the exclusions list."""
106 replications = yield get_replications()
107 if replication_id not in replications:
108 raise InvalidIdError(replication_id)
109
110 exclusions = yield get_exclusions()
111 if replication_id in exclusions:
112 raise AlreadyExcludedError(replication_id)
113
114 proxy = yield get_replication_proxy()
115 yield proxy.exclude(replication_id)
0116
=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
--- ubuntuone/controlpanel/tests/__init__.py 2010-12-20 16:11:13 +0000
+++ ubuntuone/controlpanel/tests/__init__.py 2011-01-06 20:44:10 +0000
@@ -24,9 +24,157 @@
24TOKEN = {u'consumer_key': u'xQ7xDAz',24TOKEN = {u'consumer_key': u'xQ7xDAz',
25 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',25 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
26 u'token_name': u'test',26 u'token_name': u'test',
27 u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',27 u'token': u'ABCDEF01234-localtoken',
28 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}28 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
2929
30SAMPLE_ACCOUNT_JSON = """
31{
32 "username": "andrewpz",
33 "openid": "https://login.launchpad.net/+id/abcdefg",
34 "first_name": "Andrew P.",
35 "last_name": "Zoilo",
36 "couchdb": {
37 "host": "https://couchdb.one.ubuntu.com",
38 "root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
39 "dbpath": "u/abc/def/12345"
40 },
41 "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
42 "email": "andrewpz@protocultura.net",%s
43 "nickname": "Andrew P. Zoilo",
44 "id": 12345,
45 "subscription": {
46 "upgrade_available": false,
47 "description": "Paid Plan, 50 GB of storage",
48 "trial": false,
49 "started": "2010-03-24T18:38:38Z",
50 "is_paid": true,
51 "expires": null,
52 "qty": 1,
53 "price": 0.0,
54 "currency": null,
55 "id": 654321,
56 "name": "50 GB"
57 }
58}
59"""
60
61CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
62SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
63
64SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
65SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
66
67
68SAMPLE_QUOTA_JSON = """
69{
70 "total": 53687091200,
71 "used": 2350345156
72}
73"""
74
75EXPECTED_ACCOUNT_INFO = {
76 "quota_used": "2350345156",
77 "quota_total": "53687091200",
78 "type": "Paid Plan, 50 GB of storage",
79 "name": "Andrew P. Zoilo",
80 "email": "andrewpz@protocultura.net",
81}
82
83EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
84 "quota_used": "2350345156",
85 "quota_total": "53687091200",
86 "type": CURRENT_PLAN,
87 "name": "Andrew P. Zoilo",
88 "email": "andrewpz@protocultura.net",
89}
90
91SAMPLE_DEVICES_JSON = """
92[
93 {
94 "token": "ABCDEF01234token",
95 "description": "Ubuntu One @ darkstar",
96 "kind": "Computer"
97 },
98 {
99 "token": "ABCDEF01234-localtoken",
100 "description": "Ubuntu One @ localhost",
101 "kind": "Computer"
102 },
103 {
104 "kind": "Phone",
105 "description": "Nokia E65",
106 "id": 1000
107 }
108]
109"""
110
111EXPECTED_DEVICES_INFO = [
112 {
113 "device_id": "ComputerABCDEF01234token",
114 "name": "Ubuntu One @ darkstar",
115 "type": "Computer",
116 "is_local": '',
117 "configurable": '',
118 },
119 {
120 'is_local': 'True',
121 'configurable': 'True',
122 'device_id': 'ComputerABCDEF01234-localtoken',
123 'limit_bandwidth': '',
124 'max_download_speed': '-1',
125 'max_upload_speed': '-1',
126 'name': 'Ubuntu One @ localhost',
127 'type': 'Computer'
128 },
129 {
130 "device_id": "Phone1000",
131 "name": "Nokia E65",
132 "type": "Phone",
133 "configurable": '',
134 "is_local": '',
135 },
136]
137
138SAMPLE_FOLDERS = [
139 {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
140 u'path': u'/home/tester/Public', u'subscribed': u'True',
141 u'suggested_path': u'~/Public',
142 u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
143 {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
144 u'path': u'/home/tester/Documents', u'subscribed': u'',
145 u'suggested_path': u'~/Documents',
146 u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
147 {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
148 u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
149 u'suggested_path': u'~/Pictures/Photos',
150 u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
151]
152
153SAMPLE_SHARES = [
154 {u'accepted': u'True', u'access_level': u'View',
155 u'free_bytes': u'39892622746', u'generation': u'2704',
156 u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
157 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
158 u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
159 u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
160 {u'accepted': u'True', u'access_level': u'Modify',
161 u'free_bytes': u'39892622746', u'generation': u'2704',
162 u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
163 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
164 u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
165 u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
166]
167
168SAMPLE_SHARED = [
169 {u'accepted': u'True', u'access_level': u'View',
170 u'free_bytes': u'', u'generation': u'',
171 u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
172 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
173 u'path': u'/home/tester/Ubuntu One/bar',
174 u'type': u'Shared',
175 u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
176]
177
30178
31class TestCase(BaseTestCase):179class TestCase(BaseTestCase):
32 """Basics for testing."""180 """Basics for testing."""
33181
=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
--- ubuntuone/controlpanel/tests/test_backend.py 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/tests/test_backend.py 2011-01-06 20:44:10 +0000
@@ -25,9 +25,9 @@
25from twisted.internet.defer import inlineCallbacks25from twisted.internet.defer import inlineCallbacks
26from ubuntuone.devtools.handlers import MementoHandler26from ubuntuone.devtools.handlers import MementoHandler
2727
28from ubuntuone.controlpanel import backend28from ubuntuone.controlpanel import backend, replication_client
29from ubuntuone.controlpanel.backend import (ACCOUNT_API,29from ubuntuone.controlpanel.backend import (bool_str,
30 DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,30 ACCOUNT_API, DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
31 FILE_SYNC_DISABLED,31 FILE_SYNC_DISABLED,
32 FILE_SYNC_DISCONNECTED,32 FILE_SYNC_DISCONNECTED,
33 FILE_SYNC_ERROR,33 FILE_SYNC_ERROR,
@@ -37,160 +37,21 @@
37 FILE_SYNC_UNKNOWN,37 FILE_SYNC_UNKNOWN,
38 MSG_KEY, STATUS_KEY,38 MSG_KEY, STATUS_KEY,
39)39)
4040from ubuntuone.controlpanel.tests import (TestCase,
41from ubuntuone.controlpanel.tests import TestCase41 EXPECTED_ACCOUNT_INFO,
42 EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,
43 EXPECTED_DEVICES_INFO,
44 SAMPLE_ACCOUNT_NO_CURRENT_PLAN,
45 SAMPLE_ACCOUNT_WITH_CURRENT_PLAN,
46 SAMPLE_DEVICES_JSON,
47 SAMPLE_FOLDERS,
48 SAMPLE_QUOTA_JSON,
49 SAMPLE_SHARED,
50 SAMPLE_SHARES,
51 TOKEN,
52)
42from ubuntuone.controlpanel.webclient import WebClientError53from ubuntuone.controlpanel.webclient import WebClientError
4354
44SAMPLE_CREDENTIALS = {"token": "ABC1234DEF"}
45
46SAMPLE_ACCOUNT_JSON = """
47{
48 "username": "andrewpz",
49 "openid": "https://login.launchpad.net/+id/abcdefg",
50 "first_name": "Andrew P.",
51 "last_name": "Zoilo",
52 "couchdb": {
53 "host": "https://couchdb.one.ubuntu.com",
54 "root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
55 "dbpath": "u/abc/def/12345"
56 },
57 "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
58 "email": "andrewpz@protocultura.net",%s
59 "nickname": "Andrew P. Zoilo",
60 "id": 12345,
61 "subscription": {
62 "upgrade_available": false,
63 "description": "Paid Plan, 50 GB of storage",
64 "trial": false,
65 "started": "2010-03-24T18:38:38Z",
66 "is_paid": true,
67 "expires": null,
68 "qty": 1,
69 "price": 0.0,
70 "currency": null,
71 "id": 654321,
72 "name": "50 GB"
73 }
74}
75"""
76
77CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
78SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
79
80SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
81SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
82
83
84SAMPLE_QUOTA_JSON = """
85{
86 "total": 53687091200,
87 "used": 2350345156
88}
89"""
90
91EXPECTED_ACCOUNT_INFO = {
92 "quota_used": "2350345156",
93 "quota_total": "53687091200",
94 "type": "Paid Plan, 50 GB of storage",
95 "name": "Andrew P. Zoilo",
96 "email": "andrewpz@protocultura.net",
97}
98
99EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
100 "quota_used": "2350345156",
101 "quota_total": "53687091200",
102 "type": CURRENT_PLAN,
103 "name": "Andrew P. Zoilo",
104 "email": "andrewpz@protocultura.net",
105}
106
107SAMPLE_DEVICES_JSON = """
108[
109 {
110 "token": "ABCDEF01234token",
111 "description": "Ubuntu One @ darkstar",
112 "kind": "Computer"
113 },
114 {
115 "token": "ABC1234DEF",
116 "description": "Ubuntu One @ localhost",
117 "kind": "Computer"
118 },
119 {
120 "kind": "Phone",
121 "description": "Nokia E65",
122 "id": 1000
123 }
124]
125"""
126
127EXPECTED_DEVICES_INFO = [
128 {
129 "device_id": "ComputerABCDEF01234token",
130 "name": "Ubuntu One @ darkstar",
131 "type": "Computer",
132 "is_local": '',
133 "configurable": '',
134 },
135 {
136 'is_local': 'True',
137 'configurable': 'True',
138 'device_id': 'ComputerABC1234DEF',
139 'limit_bandwidth': '',
140 'max_download_speed': '-1',
141 'max_upload_speed': '-1',
142 'name': 'Ubuntu One @ localhost',
143 'type': 'Computer'
144 },
145 {
146 "device_id": "Phone1000",
147 "name": "Nokia E65",
148 "type": "Phone",
149 "configurable": '',
150 "is_local": '',
151 },
152]
153
154SAMPLE_FOLDERS = [
155 {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
156 u'path': u'/home/tester/Public', u'subscribed': u'True',
157 u'suggested_path': u'~/Public',
158 u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
159 {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
160 u'path': u'/home/tester/Documents', u'subscribed': u'',
161 u'suggested_path': u'~/Documents',
162 u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
163 {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
164 u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
165 u'suggested_path': u'~/Pictures/Photos',
166 u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
167]
168
169SAMPLE_SHARES = [
170 {u'accepted': u'True', u'access_level': u'View',
171 u'free_bytes': u'39892622746', u'generation': u'2704',
172 u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
173 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
174 u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
175 u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
176 {u'accepted': u'True', u'access_level': u'Modify',
177 u'free_bytes': u'39892622746', u'generation': u'2704',
178 u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
179 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
180 u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
181 u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
182]
183
184SAMPLE_SHARED = [
185 {u'accepted': u'True', u'access_level': u'View',
186 u'free_bytes': u'', u'generation': u'',
187 u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
188 u'other_username': u'otheruser', u'other_visible_name': u'Other User',
189 u'path': u'/home/tester/Ubuntu One/bar',
190 u'type': u'Shared',
191 u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
192]
193
19455
195class MockWebClient(object):56class MockWebClient(object):
196 """A mock webclient."""57 """A mock webclient."""
@@ -213,7 +74,7 @@
213class MockDBusClient(object):74class MockDBusClient(object):
214 """A mock dbus_client module."""75 """A mock dbus_client module."""
21576
216 creds = SAMPLE_CREDENTIALS77 creds = TOKEN
217 throttling = False78 throttling = False
218 limits = {"download": -1, "upload": -1}79 limits = {"download": -1, "upload": -1}
219 file_sync = True80 file_sync = True
@@ -292,6 +153,36 @@
292 return SAMPLE_SHARED153 return SAMPLE_SHARED
293154
294155
156class MockReplicationClient(object):
157 """A mock replication_client module."""
158
159 BOOKMARKS = 'awesome'
160 CONTACTS = 'legendary'
161
162 replications = set([BOOKMARKS, CONTACTS, 'other'])
163 exclusions = set([CONTACTS])
164
165 def get_replications(self):
166 """Grab the list of replications in this machine."""
167 return MockReplicationClient.replications
168
169 def get_exclusions(self):
170 """Grab the list of exclusions in this machine."""
171 return MockReplicationClient.exclusions
172
173 def replicate(self, replication_id):
174 """Remove replication_id from the exclusions list."""
175 if replication_id not in MockReplicationClient.replications:
176 raise replication_client.ReplicationError(replication_id)
177 MockReplicationClient.exclusions.remove(replication_id)
178
179 def exclude(self, replication_id):
180 """Add replication_id to the exclusions list."""
181 if replication_id not in MockReplicationClient.replications:
182 raise replication_client.ReplicationError(replication_id)
183 MockReplicationClient.exclusions.add(replication_id)
184
185
295class BackendBasicTestCase(TestCase):186class BackendBasicTestCase(TestCase):
296 """Simple tests for the backend."""187 """Simple tests for the backend."""
297188
@@ -301,13 +192,14 @@
301 super(BackendBasicTestCase, self).setUp()192 super(BackendBasicTestCase, self).setUp()
302 self.patch(backend, "WebClient", MockWebClient)193 self.patch(backend, "WebClient", MockWebClient)
303 self.patch(backend, "dbus_client", MockDBusClient())194 self.patch(backend, "dbus_client", MockDBusClient())
304 self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"]195 self.patch(backend, "replication_client", MockReplicationClient())
196 self.local_token = "Computer" + TOKEN["token"]
305 self.be = backend.ControlBackend()197 self.be = backend.ControlBackend()
306198
307 self.memento = MementoHandler()199 self.memento = MementoHandler()
308 backend.logger.addHandler(self.memento)200 backend.logger.addHandler(self.memento)
309201
310 MockDBusClient.creds = SAMPLE_CREDENTIALS202 MockDBusClient.creds = TOKEN
311203
312 def test_backend_creation(self):204 def test_backend_creation(self):
313 """The backend instance is successfully created."""205 """The backend instance is successfully created."""
@@ -317,7 +209,7 @@
317 def test_get_token(self):209 def test_get_token(self):
318 """The get_token method returns the right token."""210 """The get_token method returns the right token."""
319 token = yield self.be.get_token()211 token = yield self.be.get_token()
320 self.assertEqual(token, SAMPLE_CREDENTIALS["token"])212 self.assertEqual(token, TOKEN["token"])
321213
322 @inlineCallbacks214 @inlineCallbacks
323 def test_device_is_local(self):215 def test_device_is_local(self):
@@ -388,12 +280,12 @@
388 result = yield self.be.remove_device(device_id)280 result = yield self.be.remove_device(device_id)
389 self.assertEqual(result, device_id)281 self.assertEqual(result, device_id)
390 # credentials were not cleared282 # credentials were not cleared
391 self.assertEqual(MockDBusClient.creds, SAMPLE_CREDENTIALS)283 self.assertEqual(MockDBusClient.creds, TOKEN)
392284
393 @inlineCallbacks285 @inlineCallbacks
394 def test_remove_device_clear_credentials_if_local_device(self):286 def test_remove_device_clear_credentials_if_local_device(self):
395 """The remove_device method clears the credentials if is local."""287 """The remove_device method clears the credentials if is local."""
396 apiurl = DEVICE_REMOVE_API % ('computer', SAMPLE_CREDENTIALS['token'])288 apiurl = DEVICE_REMOVE_API % ('computer', TOKEN['token'])
397 # pylint: disable=E1101289 # pylint: disable=E1101
398 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON290 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
399 yield self.be.remove_device(self.local_token)291 yield self.be.remove_device(self.local_token)
@@ -487,10 +379,10 @@
487 """The volume settings can be changed."""379 """The volume settings can be changed."""
488 fid = '0123-4567'380 fid = '0123-4567'
489381
490 yield self.be.change_volume_settings(fid, {'subscribed': True})382 yield self.be.change_volume_settings(fid, {'subscribed': 'True'})
491 self.assertEqual(MockDBusClient.subscribed_folders, [fid])383 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
492384
493 yield self.be.change_volume_settings(fid, {'subscribed': False})385 yield self.be.change_volume_settings(fid, {'subscribed': ''})
494 self.assertEqual(MockDBusClient.subscribed_folders, [])386 self.assertEqual(MockDBusClient.subscribed_folders, [])
495387
496 @inlineCallbacks388 @inlineCallbacks
@@ -671,3 +563,64 @@
671563
672 self.be.disable_files()564 self.be.disable_files()
673 self.assertFalse(MockDBusClient.file_sync)565 self.assertFalse(MockDBusClient.file_sync)
566
567
568class BackendReplicationsTestCase(BackendBasicTestCase):
569 """Replications tests for the backend."""
570
571 @inlineCallbacks
572 def test_replications_info(self):
573 """The replications_info method exercises its callback."""
574 result = yield self.be.replications_info()
575
576 # replications_info will use exclusions information
577 expected = []
578 for name in MockReplicationClient.replications:
579 enabled = bool_str(name not in MockReplicationClient.exclusions)
580 dependency = ''
581 if name == MockReplicationClient.BOOKMARKS:
582 dependency = backend.BOOKMARKS_PKG
583 elif name == MockReplicationClient.CONTACTS:
584 dependency = backend.CONTACTS_PKG
585
586 item = {'replication_id': name, 'name': name,
587 'enabled': enabled, 'dependency': dependency}
588 expected.append(item)
589 self.assertEqual(sorted(expected), sorted(result))
590
591 @inlineCallbacks
592 def test_change_replication_settings(self):
593 """The replication settings can be changed."""
594 rid = '0123-4567'
595 MockReplicationClient.replications.add(rid)
596 self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
597
598 yield self.be.change_replication_settings(rid, {'enabled': ''})
599 self.assertIn(rid, MockReplicationClient.exclusions)
600
601 yield self.be.change_replication_settings(rid, {'enabled': 'True'})
602 self.assertNotIn(rid, MockReplicationClient.exclusions)
603
604 @inlineCallbacks
605 def test_change_replication_settings_not_in_replications(self):
606 """The settings can not be changed for an item not in replications."""
607 rid = '0123-4567'
608 assert rid not in MockReplicationClient.replications
609
610 d = self.be.change_replication_settings(rid, {'enabled': 'True'})
611 yield self.assertFailure(d, replication_client.ReplicationError)
612
613 d = self.be.change_replication_settings(rid, {'enabled': ''})
614 yield self.assertFailure(d, replication_client.ReplicationError)
615
616 @inlineCallbacks
617 def test_change_replication_settings_no_setting(self):
618 """The change replication settings does not fail on empty settings."""
619 rid = '0123-4567'
620 MockReplicationClient.replications.add(rid)
621 self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
622
623 prior = MockReplicationClient.exclusions.copy()
624 yield self.be.change_replication_settings(rid, {})
625
626 self.assertEqual(MockReplicationClient.exclusions, prior)
674627
=== added file 'ubuntuone/controlpanel/tests/test_replication_client.py'
--- ubuntuone/controlpanel/tests/test_replication_client.py 1970-01-01 00:00:00 +0000
+++ ubuntuone/controlpanel/tests/test_replication_client.py 2011-01-06 20:44:10 +0000
@@ -0,0 +1,160 @@
1# -*- coding: utf-8 -*-
2
3# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""Tests for the DBus service when accessing desktopcouch replications."""
20
21from twisted.internet.defer import inlineCallbacks
22
23from ubuntuone.controlpanel import replication_client
24from ubuntuone.controlpanel.tests import TestCase
25
26EXCLUSIONS = set()
27
28
29class FakedReplication(object):
30 """Faked a DC replication exclusion."""
31
32 def __init__(self):
33 self.all_exclusions = lambda: EXCLUSIONS
34 self.replicate = EXCLUSIONS.remove
35 self.exclude = EXCLUSIONS.add
36
37
38class FakedReplicationModule(object):
39 """Faked a DC replication module."""
40
41 ReplicationExclusion = FakedReplication
42
43
44class ReplicationsTestCase(TestCase):
45 """Test for the replications client methods."""
46
47 def setUp(self):
48 super(ReplicationsTestCase, self).setUp()
49
50 orig_get_proxy = replication_client.get_replication_proxy
51
52 def get_proxy():
53 """Fake the proxy getter."""
54 return orig_get_proxy(replication_module=FakedReplicationModule())
55
56 self.patch(replication_client, 'get_replication_proxy', get_proxy)
57
58 def tearDown(self):
59 EXCLUSIONS.clear()
60 super(ReplicationsTestCase, self).tearDown()
61
62 @inlineCallbacks
63 def test_no_pairing_record(self):
64 """Handle ValueError from replication layer."""
65
66 def no_pairing_record(*args, **kwargs):
67 """Fail with ValueError."""
68 raise ValueError('No pairing record.')
69
70 self.patch(FakedReplicationModule, 'ReplicationExclusion',
71 no_pairing_record)
72
73 yield self.assertFailure(replication_client.get_replications(),
74 replication_client.NoPairingRecord)
75
76 @inlineCallbacks
77 def test_get_replications(self):
78 """Replications are correctly retrieved."""
79 result = yield replication_client.get_replications()
80 self.assertEqual(result, replication_client.REPLICATIONS)
81
82 @inlineCallbacks
83 def test_get_exclusions(self):
84 """Exclusions are correctly retrieved."""
85 replications = yield replication_client.get_replications()
86 for rep in replications:
87 yield replication_client.exclude(rep)
88
89 result = yield replication_client.get_exclusions()
90 self.assertEqual(result, replications)
91
92 @inlineCallbacks
93 def test_replicate(self):
94 """Replicate a service is correct."""
95 replications = yield replication_client.get_replications()
96 rid = list(replications)[0]
97 yield replication_client.exclude(rid)
98
99 yield replication_client.replicate(rid)
100 exclusions = yield replication_client.get_exclusions()
101 self.assertNotIn(rid, exclusions)
102
103 @inlineCallbacks
104 def test_replicate_name_not_in_replications(self):
105 """Replicate a service fails if not in replications."""
106 replications = yield replication_client.get_replications()
107 rid = 'not in replications'
108 assert rid not in replications
109
110 yield self.assertFailure(replication_client.replicate(rid),
111 replication_client.InvalidIdError)
112
113 @inlineCallbacks
114 def test_replicate_name_not_in_exclusions(self):
115 """Replicate a service fails if not in exclusions."""
116 replications = yield replication_client.get_replications()
117 rid = list(replications)[0]
118 assert rid in replications
119
120 exclusions = yield replication_client.get_exclusions()
121 assert rid not in exclusions
122
123 yield self.assertFailure(replication_client.replicate(rid),
124 replication_client.NotExcludedError)
125
126 @inlineCallbacks
127 def test_exclude(self):
128 """Excluding a service is correct."""
129 replications = yield replication_client.get_replications()
130 rid = list(replications)[0]
131 yield replication_client.exclude(rid)
132 yield replication_client.replicate(rid)
133
134 yield replication_client.exclude(rid)
135 exclusions = yield replication_client.get_exclusions()
136 self.assertIn(rid, exclusions)
137
138 @inlineCallbacks
139 def test_exclude_name_not_in_replications(self):
140 """Excluding a service fails if not in replications."""
141 replications = yield replication_client.get_replications()
142 rid = 'not in replications'
143 assert rid not in replications
144
145 yield self.assertFailure(replication_client.exclude(rid),
146 replication_client.InvalidIdError)
147
148 @inlineCallbacks
149 def test_exclude_name_in_exclusions(self):
150 """Excluding a service fails if already on exclusions."""
151 replications = yield replication_client.get_replications()
152 rid = list(replications)[0]
153 assert rid in replications
154
155 yield replication_client.exclude(rid)
156 exclusions = yield replication_client.get_exclusions()
157 assert rid in exclusions
158
159 yield self.assertFailure(replication_client.exclude(rid),
160 replication_client.AlreadyExcludedError)

Subscribers

People subscribed via source and target branches