Merge lp:~nataliabidart/ubuntuone-control-panel/start-dc-on-backend into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
Status: Superseded
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/start-dc-on-backend
Merge into: lp:ubuntuone-control-panel
Diff against target: 1989 lines (+1003/-414)
14 files modified
data/install.ui (+7/-2)
po/POTFILES.in (+2/-1)
pylintrc (+1/-1)
ubuntuone/controlpanel/backend.py (+41/-1)
ubuntuone/controlpanel/dbus_service.py (+41/-0)
ubuntuone/controlpanel/gtk/gui.py (+114/-81)
ubuntuone/controlpanel/gtk/tests/__init__.py (+14/-13)
ubuntuone/controlpanel/gtk/tests/test_gui.py (+187/-140)
ubuntuone/controlpanel/integrationtests/test_dbus_service.py (+55/-7)
ubuntuone/controlpanel/logger.py (+1/-4)
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/start-dc-on-backend
Reviewer Review Type Date Requested Status
Ubuntu One hackers Pending
Review via email: mp+45440@code.launchpad.net

Commit message

Desktopcouch replication service is accessed through the backend using its DBus service (LP: #696782).

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

Avoiding conflicts.

56. By Natalia Bidart

Merged dependency branch in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/install.ui'
2--- data/install.ui 2010-12-24 14:53:03 +0000
3+++ data/install.ui 2011-01-06 20:36:09 +0000
4@@ -23,11 +23,12 @@
5 <property name="visible">True</property>
6 <child>
7 <object class="GtkButton" id="install_button">
8- <property name="label">gtk-ok</property>
9+ <property name="label" translatable="yes">_Install now</property>
10 <property name="visible">True</property>
11 <property name="can_focus">True</property>
12 <property name="receives_default">True</property>
13- <property name="use_stock">True</property>
14+ <property name="image">image1</property>
15+ <property name="use_underline">True</property>
16 <signal name="clicked" handler="on_install_button_clicked"/>
17 </object>
18 <packing>
19@@ -43,4 +44,8 @@
20 </packing>
21 </child>
22 </object>
23+ <object class="GtkImage" id="image1">
24+ <property name="visible">True</property>
25+ <property name="stock">gtk-ok</property>
26+ </object>
27 </interface>
28
29=== modified file 'po/POTFILES.in'
30--- po/POTFILES.in 2011-01-04 16:12:56 +0000
31+++ po/POTFILES.in 2011-01-06 20:36:09 +0000
32@@ -1,8 +1,9 @@
33 ubuntuone-control-panel-gtk.desktop.in
34 ubuntuone/controlpanel/gtk/gui.py
35 [type: gettext/glade] data/dashboard.ui
36-[type: gettext/glade] data/services.ui
37 [type: gettext/glade] data/device.ui
38 [type: gettext/glade] data/devices.ui
39+[type: gettext/glade] data/install.ui
40 [type: gettext/glade] data/management.ui
41 [type: gettext/glade] data/overview.ui
42+[type: gettext/glade] data/services.ui
43
44=== modified file 'pylintrc'
45--- pylintrc 2010-10-13 18:55:23 +0000
46+++ pylintrc 2011-01-06 20:36:09 +0000
47@@ -272,7 +272,7 @@
48 max-line-length=79
49
50 # Maximum number of lines in a module
51-max-module-lines=2000
52+max-module-lines=2500
53
54 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
55 # tab).
56
57=== modified file 'ubuntuone/controlpanel/backend.py'
58--- ubuntuone/controlpanel/backend.py 2010-12-23 18:20:56 +0000
59+++ ubuntuone/controlpanel/backend.py 2011-01-06 20:36:09 +0000
60@@ -22,6 +22,7 @@
61 from twisted.internet.defer import inlineCallbacks, returnValue
62
63 from ubuntuone.controlpanel import dbus_client
64+from ubuntuone.controlpanel import replication_client
65 from ubuntuone.controlpanel.logger import setup_logging, log_call
66 from ubuntuone.controlpanel.webclient import WebClient
67
68@@ -48,6 +49,9 @@
69 MSG_KEY = 'message'
70 STATUS_KEY = 'status'
71
72+BOOKMARKS_PKG = 'xul-ext-bindwood'
73+CONTACTS_PKG = 'evolution-couchdb'
74+
75
76 def bool_str(value):
77 """Return the string representation of a bool (dbus-compatible)."""
78@@ -300,7 +304,7 @@
79
80 """
81 if 'subscribed' in settings:
82- subscribed = settings['subscribed']
83+ subscribed = bool(settings['subscribed'])
84 if subscribed:
85 yield self.subscribe_volume(volume_id)
86 else:
87@@ -321,6 +325,42 @@
88 yield dbus_client.unsubscribe_folder(volume_id)
89
90 @log_call(logger.debug)
91+ @inlineCallbacks
92+ def replications_info(self):
93+ """Get the user replications info."""
94+ replications = yield replication_client.get_replications()
95+ exclusions = yield replication_client.get_exclusions()
96+
97+ result = []
98+ for rep in replications:
99+ dependency = ''
100+ if rep == replication_client.BOOKMARKS:
101+ dependency = BOOKMARKS_PKG
102+ elif rep == replication_client.CONTACTS:
103+ dependency = CONTACTS_PKG
104+
105+ repd = {
106+ "replication_id": rep,
107+ "name": rep, # this may change to be more user friendly
108+ "enabled": bool_str(rep not in exclusions),
109+ "dependency": dependency,
110+ }
111+ result.append(repd)
112+
113+ returnValue(result)
114+
115+ @log_call(logger.info)
116+ @inlineCallbacks
117+ def change_replication_settings(self, replication_id, settings):
118+ """Change the settings for the given replication."""
119+ if 'enabled' in settings:
120+ if bool(settings['enabled']):
121+ yield replication_client.replicate(replication_id)
122+ else:
123+ yield replication_client.exclude(replication_id)
124+ returnValue(replication_id)
125+
126+ @log_call(logger.debug)
127 def query_bookmark_extension(self):
128 """True if the bookmark extension has been installed."""
129 # still pending (LP: #673672)
130
131=== modified file 'ubuntuone/controlpanel/dbus_service.py'
132--- ubuntuone/controlpanel/dbus_service.py 2010-12-23 18:20:56 +0000
133+++ ubuntuone/controlpanel/dbus_service.py 2011-01-06 20:36:09 +0000
134@@ -339,6 +339,47 @@
135
136 @log_call(logger.debug)
137 @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
138+ def replications_info(self):
139+ """Return the replications info."""
140+ d = self.backend.replications_info()
141+ d.addCallback(self.ReplicationsInfoReady)
142+ d.addErrback(transform_failure(self.ReplicationsInfoError))
143+
144+ @log_call(logger.debug)
145+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
146+ def ReplicationsInfoReady(self, info):
147+ """The replications info is ready."""
148+
149+ @log_call(logger.error)
150+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
151+ def ReplicationsInfoError(self, error):
152+ """Problem getting the replications info."""
153+
154+ #---
155+
156+ @log_call(logger.info)
157+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
158+ def change_replication_settings(self, replication_id, settings):
159+ """Configure a given replication."""
160+ d = self.backend.change_replication_settings(replication_id, settings)
161+ d.addCallback(self.ReplicationSettingsChanged)
162+ d.addErrback(transform_failure(self.ReplicationSettingsChangeError),
163+ replication_id)
164+
165+ @log_call(logger.info)
166+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
167+ def ReplicationSettingsChanged(self, replication_id):
168+ """The settings for the replication were changed."""
169+
170+ @log_call(logger.error)
171+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
172+ def ReplicationSettingsChangeError(self, replication_id, error):
173+ """Problem changing settings for the replication."""
174+
175+ #---
176+
177+ @log_call(logger.debug)
178+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
179 def query_bookmark_extension(self):
180 """Check if the extension to sync bookmarks is installed."""
181 d = self.backend.query_bookmark_extension()
182
183=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
184--- ubuntuone/controlpanel/gtk/gui.py 2010-12-26 15:02:52 +0000
185+++ ubuntuone/controlpanel/gtk/gui.py 2011-01-06 20:36:09 +0000
186@@ -78,6 +78,12 @@
187 VALUE_ERROR = _('Value could not be retrieved.')
188 WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE
189 KILOBYTES = 1024
190+NO_OP = lambda *a, **kw: None
191+
192+
193+def error_handler(*args, **kwargs):
194+ """Log errors when calling D-Bus methods in a async way."""
195+ logger.error('Error handler received: %r, %r', args, kwargs)
196
197
198 def filter_by_app_name(f):
199@@ -352,7 +358,8 @@
200 settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
201 WINDOW_ID_KEY: str(self._window_id),
202 PING_URL_KEY: U1_PING_URL}
203- self.sso_backend.register(U1_APP_NAME, settings)
204+ self.sso_backend.register(U1_APP_NAME, settings,
205+ reply_handler=NO_OP, error_handler=error_handler)
206 self.set_property('greyed', True)
207 self.warning_label.set_text('')
208
209@@ -361,7 +368,8 @@
210 settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
211 WINDOW_ID_KEY: str(self._window_id),
212 PING_URL_KEY: U1_PING_URL}
213- self.sso_backend.login(U1_APP_NAME, settings)
214+ self.sso_backend.login(U1_APP_NAME, settings,
215+ reply_handler=NO_OP, error_handler=error_handler)
216 self.set_property('greyed', True)
217 self.warning_label.set_text('')
218
219@@ -408,7 +416,8 @@
220 else:
221 self.set_sensitive(True)
222 self.warning_label.set_text('')
223- self.sso_backend.find_credentials(U1_APP_NAME, {})
224+ self.sso_backend.find_credentials(U1_APP_NAME, {},
225+ reply_handler=NO_OP, error_handler=error_handler)
226
227
228 class DashboardPanel(UbuntuOneBin, ControlPanelMixin):
229@@ -516,11 +525,13 @@
230 volume_id = checkbutton.get_label()
231 subscribed = bool_str(checkbutton.get_active())
232 self.backend.change_volume_settings(volume_id,
233- {'subscribed': subscribed})
234+ {'subscribed': subscribed},
235+ reply_handler=NO_OP, error_handler=error_handler)
236
237 def load(self):
238 """Load the volume list."""
239- self.backend.volumes_info()
240+ self.backend.volumes_info(reply_handler=NO_OP,
241+ error_handler=error_handler)
242 self.message.start()
243
244
245@@ -565,7 +576,8 @@
246 # Not disabling the GUI to avoid annyong twitchings
247 #self.set_sensitive(False)
248 self.warning_label.set_text('')
249- self.backend.change_device_settings(self.id, self.__dict__)
250+ self.backend.change_device_settings(self.id, self.__dict__,
251+ reply_handler=NO_OP, error_handler=error_handler)
252
253 def _block_signals(f):
254 """Execute 'f' while having the _updating flag set."""
255@@ -591,7 +603,8 @@
256
257 def on_remove_clicked(self, widget):
258 """Remove button was clicked or activated."""
259- self.backend.remove_device(self.id)
260+ self.backend.remove_device(self.id,
261+ reply_handler=NO_OP, error_handler=error_handler)
262 self.set_sensitive(False)
263
264 @_block_signals
265@@ -747,7 +760,8 @@
266
267 def load(self):
268 """Load the device list."""
269- self.backend.devices_info()
270+ self.backend.devices_info(reply_handler=NO_OP,
271+ error_handler=error_handler)
272 self.message.start()
273
274
275@@ -818,12 +832,18 @@
276 class Service(gtk.VBox, ControlPanelMixin):
277 """A service."""
278
279- def __init__(self, name, localized_name, *args, **kwargs):
280+ CHANGE_ERROR = _('The settings could not be changed,\n'
281+ 'previous values were restored.')
282+
283+ def __init__(self, service_id, name, *args, **kwargs):
284 gtk.VBox.__init__(self)
285 ControlPanelMixin.__init__(self)
286- self.service_name = name
287-
288- self.button = gtk.CheckButton(label=localized_name)
289+ self.id = service_id
290+
291+ self.warning_label = gtk.Label()
292+ self.pack_start(self.warning_label, expand=False)
293+
294+ self.button = gtk.CheckButton(label=name)
295 self.pack_start(self.button, expand=False)
296
297 self.show_all()
298@@ -835,15 +855,18 @@
299 FILES_SERVICE_NAME = _('Files')
300
301 def __init__(self):
302- Service.__init__(self, name='files',
303- localized_name=self.FILES_SERVICE_NAME)
304+ Service.__init__(self, service_id='files',
305+ name=self.FILES_SERVICE_NAME)
306
307 self.set_sensitive(False)
308+
309 self.backend.connect_to_signal('FileSyncStatusChanged',
310 self.on_file_sync_status_changed)
311 self.backend.connect_to_signal('FilesEnabled', self.on_files_enabled)
312 self.backend.connect_to_signal('FilesDisabled', self.on_files_disabled)
313- self.backend.file_sync_status()
314+
315+ self.backend.file_sync_status(reply_handler=NO_OP,
316+ error_handler=error_handler)
317
318 @log_call(logger.debug)
319 def on_file_sync_status_changed(self, status):
320@@ -869,19 +892,24 @@
321 """Button was toggled, exclude/replicate the service properly."""
322 logger.info('File sync enabled? %r', self.button.get_active())
323 if self.button.get_active():
324- self.backend.enable_files()
325+ self.backend.enable_files(reply_handler=NO_OP,
326+ error_handler=error_handler)
327 else:
328- self.backend.disable_files()
329+ self.backend.disable_files(reply_handler=NO_OP,
330+ error_handler=error_handler)
331
332
333 class DesktopcouchService(Service):
334 """A desktopcouch service."""
335
336- def __init__(self, name, localized_name,
337- replication_service, dependency=None):
338- Service.__init__(self, name, localized_name)
339- self.replication_service = replication_service
340- enabled = name not in self.replication_service.all_exclusions()
341+ def __init__(self, service_id, name, enabled, dependency=None):
342+ Service.__init__(self, service_id, name)
343+
344+ self.backend.connect_to_signal('ReplicationSettingsChanged',
345+ self.on_replication_settings_changed)
346+ self.backend.connect_to_signal('ReplicationSettingsChangeError',
347+ self.on_replication_settings_change_error)
348+
349 self.button.set_active(enabled)
350
351 self.dependency = None
352@@ -903,11 +931,27 @@
353 def on_button_toggled(self, button):
354 """Button was toggled, exclude/replicate the service properly."""
355 logger.info('Starting replication for %r? %r',
356- self.service_name, self.button.get_active())
357- if self.button.get_active():
358- self.replication_service.replicate(self.service_name)
359- else:
360- self.replication_service.exclude(self.service_name)
361+ self.id, self.button.get_active())
362+
363+ args = {'enabled': bool_str(self.button.get_active())}
364+ self.backend.change_replication_settings(self.id, args,
365+ reply_handler=NO_OP, error_handler=error_handler)
366+
367+ @log_call(logger.info)
368+ def on_replication_settings_changed(self, replication_id):
369+ """The change of settings for this replication succeded."""
370+ if replication_id != self.id:
371+ return
372+ self.warning_label.set_text('')
373+
374+ @log_call(logger.error)
375+ def on_replication_settings_change_error(self, replication_id,
376+ error_dict=None):
377+ """The change of settings for this replication failed."""
378+ if replication_id != self.id:
379+ return
380+ self.button.set_active(not self.button.get_active())
381+ self._set_warning(self.CHANGE_ERROR, self.warning_label)
382
383
384 class ServicesPanel(UbuntuOneBin, ControlPanelMixin):
385@@ -916,32 +960,33 @@
386 TITLE = _('Ubuntu One services including data sync are enabled for the '
387 'data types and services listed below.')
388 CHOOSE_SERVICES = _('Choose services to synchronize with this computer:')
389- DESKTOPCOUCH_PKG = 'desktopcouch'
390- BINDWOOD_PKG = 'xul-ext-bindwood'
391- EVOCOUCH_PKG = 'evolution-couchdb'
392+ DESKTOPCOUCH_PKG = 'desktopcouch-ubuntuone'
393 BOOKMARKS = _('Bookmarks (Firefox)')
394 CONTACTS = _('Contacts (Evolution)')
395 NO_PAIRING_RECORD = _('There is no Ubuntu One pairing record.')
396
397- def __init__(self, replication_exclusion_class=None):
398+ def __init__(self):
399 UbuntuOneBin.__init__(self)
400 ControlPanelMixin.__init__(self, filename='services.ui')
401 self.add(self.itself)
402
403- self.replication_exclusion_class = replication_exclusion_class
404- self.replication_service = None
405- self.has_desktopcouch = False
406- self.has_bindwood = False
407- self.has_evocouch = False
408 self.package_manager = package_manager.PackageManager()
409 self.install_box = None
410- self.bookmarks = None
411- self.contacts = None
412+
413+ self.backend.connect_to_signal('ReplicationsInfoReady',
414+ self.on_replications_info_ready)
415+ self.backend.connect_to_signal('ReplicationsInfoError',
416+ self.on_replications_info_error)
417
418 self.files.pack_start(FilesService(), expand=False)
419
420 self.show()
421
422+ @property
423+ def has_desktopcouch(self):
424+ """Is desktopcouch installed?"""
425+ return self.package_manager.is_installed(self.DESKTOPCOUCH_PKG)
426+
427 @log_call(logger.debug)
428 def load(self):
429 """Load info."""
430@@ -949,16 +994,7 @@
431 self.itself.remove(self.install_box)
432 self.install_box = None
433
434- self.has_desktopcouch = \
435- self.package_manager.is_installed(self.DESKTOPCOUCH_PKG)
436- self.has_bindwood = \
437- self.package_manager.is_installed(self.BINDWOOD_PKG)
438- self.has_evocouch = \
439- self.package_manager.is_installed(self.EVOCOUCH_PKG)
440-
441- logger.info('load: has_desktopcouch? %r has_bindwood? %s '
442- 'has_evocouch? %s', self.has_desktopcouch,
443- self.has_bindwood, self.has_evocouch)
444+ logger.info('load: has_desktopcouch? %r', self.has_desktopcouch)
445 if not self.has_desktopcouch:
446 self.message.set_text('')
447 self.replications.hide()
448@@ -975,46 +1011,41 @@
449 @log_call(logger.debug)
450 def load_replications(self, *args):
451 """Load replications info."""
452+ # ask replications to the backend
453+ self.message.start()
454+ self.backend.replications_info(reply_handler=NO_OP,
455+ error_handler=error_handler)
456+
457+ @log_call(logger.debug)
458+ def on_replications_info_ready(self, info):
459+ """The replication info is ready."""
460+ self.on_success(self.CHOOSE_SERVICES)
461+
462 self.replications.show()
463
464 if self.install_box is not None:
465 self.itself.remove(self.install_box)
466 self.install_box = None
467
468- self.message.set_text(self.CHOOSE_SERVICES)
469 for child in self.replications.get_children():
470 self.replications.remove(child)
471
472- # Unable to import 'desktopcouch.application.replication_services'
473- # pylint: disable=F0401
474- if self.replication_exclusion_class is None:
475- from desktopcouch.application.replication_services import \
476- ubuntuone as u1rep
477- self.replication_exclusion_class = u1rep.ReplicationExclusion
478-
479- if self.replication_service is None:
480- try:
481- self.replication_service = self.replication_exclusion_class()
482- except ValueError:
483- logger.exception('Can not load replications:')
484- self._set_warning(self.NO_PAIRING_RECORD, self.message)
485- return
486-
487- pkg = None
488- if not self.has_bindwood:
489- pkg = self.BINDWOOD_PKG
490- self.bookmarks = DesktopcouchService('bookmarks', self.BOOKMARKS,
491- self.replication_service,
492- dependency=pkg)
493- self.replications.pack_start(self.bookmarks, expand=False)
494-
495- pkg = None
496- if not self.has_evocouch:
497- pkg = self.EVOCOUCH_PKG
498- self.contacts = DesktopcouchService('contacts', self.CONTACTS,
499- self.replication_service,
500- dependency=pkg)
501- self.replications.pack_start(self.contacts, expand=False)
502+ for item in info:
503+ pkg = item['dependency']
504+ child = DesktopcouchService(service_id=item['replication_id'],
505+ name=item['name'], # self.BOOKMARKS,
506+ enabled=bool(item['enabled']),
507+ dependency=pkg if pkg else None)
508+ self.replications.pack_start(child, expand=False)
509+
510+ @log_call(logger.error)
511+ def on_replications_info_error(self, error_dict=None):
512+ """The replication info can not be retrieved."""
513+ if error_dict is not None and \
514+ error_dict.get('error_type', None) == 'NoPairingRecord':
515+ self.on_error(self.NO_PAIRING_RECORD)
516+ else:
517+ self.on_error()
518
519
520 class ManagementPanel(gtk.VBox, ControlPanelMixin):
521@@ -1107,8 +1138,10 @@
522
523 def load(self):
524 """Load the account info and file sync status list."""
525- self.backend.account_info()
526- self.backend.file_sync_status()
527+ self.backend.account_info(reply_handler=NO_OP,
528+ error_handler=error_handler)
529+ self.backend.file_sync_status(reply_handler=NO_OP,
530+ error_handler=error_handler)
531 self.dashboard_button.clicked()
532
533 @log_call(logger.debug)
534
535=== modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py'
536--- ubuntuone/controlpanel/gtk/tests/__init__.py 2010-12-23 19:17:53 +0000
537+++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-06 20:36:09 +0000
538@@ -53,6 +53,15 @@
539 'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local
540 ]
541
542+FAKE_REPLICATIONS_INFO = [
543+ {'replication_id': 'foo', 'name': 'Bar',
544+ 'enabled': 'True', 'dependency': ''},
545+ {'replication_id': 'yadda', 'name': 'Foo',
546+ 'enabled': '', 'dependency': 'a very weird one'},
547+ {'replication_id': 'yoda', 'name': 'Figthers',
548+ 'enabled': 'True', 'dependency': 'other dep'},
549+]
550+
551
552 class FakedObject(object):
553 """Fake an object, record every call."""
554@@ -117,9 +126,11 @@
555 object_path = gui.DBUS_PREFERENCES_PATH
556 iface = gui.DBUS_PREFERENCES_IFACE
557 exposed_methods = [
558- 'account_info', 'devices_info', 'change_device_settings',
559- 'volumes_info', 'change_volume_settings', 'file_sync_status',
560- 'remove_device', 'enable_files', 'disable_files',
561+ 'account_info', # account
562+ 'devices_info', 'change_device_settings', 'remove_device', # devices
563+ 'volumes_info', 'change_volume_settings', # volumes
564+ 'replications_info', 'change_replication_settings', # replications
565+ 'file_sync_status', 'enable_files', 'disable_files', # files
566 ]
567
568
569@@ -157,13 +168,3 @@
570 yield
571 self._installed[package_name] = True
572 gui.package_manager.return_value(FakedTransaction([package_name]))
573-
574-
575-class FakedReplication(object):
576- """Faked a DC replication exclusion."""
577-
578- def __init__(self):
579- self._exclusions = set()
580- self.all_exclusions = lambda: self._exclusions
581- self.replicate = self._exclusions.remove
582- self.exclude = self._exclusions.add
583
584=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
585--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-26 15:02:52 +0000
586+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-06 20:36:09 +0000
587@@ -26,9 +26,10 @@
588
589 from ubuntuone.controlpanel.gtk import gui
590 from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,
591- FAKE_VOLUMES_INFO, FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
592+ FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
593+ FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO,
594 FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,
595- FakedPackageManager, FakedReplication,
596+ FakedPackageManager,
597 )
598 from ubuntuone.controlpanel.tests import TOKEN, TestCase
599 from ubuntuone.controlpanel.gtk.tests.test_package_manager import (
600@@ -37,6 +38,8 @@
601
602 # Attribute 'yyy' defined outside __init__, access to a protected member
603 # pylint: disable=W0201, W0212
604+# Too many lines in module
605+# pylint: disable=C0302
606
607
608 class BaseTestCase(TestCase):
609@@ -96,7 +99,9 @@
610 if backend is None:
611 backend = self.ui.backend
612 self.assertIn(method_name, backend._called)
613- self.assertEqual(backend._called[method_name], (args, {}))
614+ kwargs = {'reply_handler': gui.NO_OP,
615+ 'error_handler': gui.error_handler}
616+ self.assertEqual(backend._called[method_name], (args, kwargs))
617
618 def assert_warning_correct(self, warning, text):
619 """Check that 'warning' is visible, showing 'text'."""
620@@ -1361,9 +1366,9 @@
621 """The test suite for a service."""
622
623 klass = gui.Service
624- name = 'dc_test'
625- localized_name = u'Qué lindo test!'
626- kwargs = {'name': 'dc_test', 'localized_name': u'Qué lindo test!'}
627+ service_id = 'dc_test'
628+ name = u'Qué lindo test!'
629+ kwargs = {'service_id': service_id, 'name': name}
630
631 def test_is_an_box(self):
632 """Inherits from gtk.VBox."""
633@@ -1373,30 +1378,35 @@
634 """Is visible."""
635 self.assertTrue(self.ui.get_visible())
636
637+ def test_warning_label_is_cleared(self):
638+ """The warning label is cleared."""
639+ self.assertEqual(self.ui.warning_label.get_text(), '')
640+
641+ def test_warning_label_packed(self):
642+ """The warning label is packed as child."""
643+ self.assertIn(self.ui.warning_label, self.ui.get_children())
644+
645 def test_check_button_packed(self):
646- """A check button is packed as only child."""
647+ """A check button is packed as child."""
648 self.assertIn(self.ui.button, self.ui.get_children())
649
650 def test_label(self):
651 """The label is set."""
652- self.assertEqual(self.localized_name, self.ui.button.get_label())
653+ self.assertEqual(self.name, self.ui.button.get_label())
654
655- def test_service_name(self):
656- """The service_name is set."""
657- self.assertEqual(self.name, self.ui.service_name)
658+ def test_service_id(self):
659+ """The service id is set."""
660+ self.assertEqual(self.service_id, self.ui.id)
661
662
663 class FilesServiceTestCase(ServiceTestCase):
664 """The test suite for the file sync service."""
665
666 klass = gui.FilesService
667+ service_id = 'files'
668+ name = gui.FilesService.FILES_SERVICE_NAME
669 kwargs = {}
670
671- def setUp(self):
672- self.name = 'files'
673- self.localized_name = gui.FilesService.FILES_SERVICE_NAME
674- super(FilesServiceTestCase, self).setUp()
675-
676 def test_backend_account_signals(self):
677 """The proper signals are connected to the backend."""
678 self.assertEqual(self.ui.backend._signals['FileSyncStatusChanged'],
679@@ -1461,35 +1471,36 @@
680 """The test suite for a desktopcouch service."""
681
682 klass = gui.DesktopcouchService
683+ enabled = True
684
685 def setUp(self):
686- self.replication = FakedReplication()
687- self.name = self.kwargs['name']
688- self.kwargs['replication_service'] = self.replication
689+ self.kwargs['enabled'] = self.enabled
690 super(DesktopcouchServiceTestCase, self).setUp()
691
692+ def modify_settings(self):
693+ """Modify settings so values actually change."""
694+ self.ui.button.set_active(not self.ui.button.get_active())
695+
696+ def test_backend_account_signals(self):
697+ """The proper signals are connected to the backend."""
698+ self.assertEqual(
699+ self.ui.backend._signals['ReplicationSettingsChanged'],
700+ [self.ui.on_replication_settings_changed])
701+ self.assertEqual(
702+ self.ui.backend._signals['ReplicationSettingsChangeError'],
703+ [self.ui.on_replication_settings_change_error])
704+
705 def test_active(self):
706- """Is active since replication has an empty database."""
707- self.assertTrue(self.ui.button.get_active())
708-
709- def test_not_active(self):
710- """Is not active since 'name' is excluded on replication database."""
711- self.replication.exclude(self.name)
712- self.ui = self.klass(**self.kwargs)
713- self.assertFalse(self.ui.button.get_active())
714+ """Is active if enabled."""
715+ self.assertEqual(self.enabled, self.ui.button.get_active())
716
717 def test_on_button_toggled(self):
718 """When toggling the button, the DC exclude list is updated."""
719- assert self.ui.button.get_active()
720 self.ui.button.set_active(not self.ui.button.get_active())
721- self.assertEqual(set([self.name]), self.replication.all_exclusions())
722
723- def test_on_button_toggled_twice(self):
724- """When toggling the button twice, the DC exclude list is updated."""
725- assert self.ui.button.get_active()
726- self.ui.button.set_active(not self.ui.button.get_active())
727- self.ui.button.set_active(not self.ui.button.get_active())
728- self.assertEqual(set(), self.replication.all_exclusions())
729+ args = (self.service_id,
730+ {'enabled': gui.bool_str(self.ui.button.get_active())})
731+ self.assert_backend_called('change_replication_settings', args)
732
733 def test_dependency(self):
734 """The dependency box is None."""
735@@ -1499,6 +1510,72 @@
736 """The check button is sensitive."""
737 self.assertTrue(self.ui.button.get_sensitive())
738
739+ def test_on_replication_settings_changed(self):
740+ """When settings were changed for this replication, enable it."""
741+ new_val = not self.ui.button.get_active()
742+ self.ui.button.set_active(new_val)
743+
744+ self.ui.on_replication_settings_changed(replication_id=self.ui.id)
745+
746+ self.assertEqual(self.ui.warning_label.get_text(), '')
747+ self.assertEqual(new_val, self.ui.button.get_active())
748+
749+ def test_on_replication_settings_changed_after_error(self):
750+ """Change success after error."""
751+ self.ui.button.set_active(not self.ui.button.get_active())
752+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
753+
754+ self.test_on_replication_settings_changed()
755+
756+ def test_on_replication_settings_changed_different_id(self):
757+ """When settings were changed for other rep, nothing changes."""
758+ self.ui.button.set_active(not self.ui.button.get_active())
759+ self.ui.on_replication_settings_changed(replication_id='yadda')
760+
761+ self.assertEqual(self.ui.warning_label.get_text(), '')
762+
763+ def test_on_replication_settings_changed_different_id_after_error(self):
764+ """When settings were changed for other + error, nothing changes."""
765+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
766+ self.ui.on_replication_settings_changed(replication_id='yadda')
767+
768+ self.assert_warning_correct(self.ui.warning_label,
769+ self.ui.CHANGE_ERROR)
770+
771+ def test_on_replication_settings_change_error(self):
772+ """When settings were not changed, notify the user.
773+
774+ Also, confirm that old value was restored.
775+
776+ """
777+ old_val = self.ui.button.get_active()
778+ self.ui.button.set_active(not old_val)
779+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
780+
781+ self.assert_warning_correct(self.ui.warning_label,
782+ self.ui.CHANGE_ERROR)
783+ self.assertEqual(old_val, self.ui.button.get_active())
784+
785+ def test_on_replication_settings_change_error_after_success(self):
786+ """Change error after success."""
787+ self.ui.button.set_active(not self.ui.button.get_active())
788+ self.ui.on_replication_settings_changed(replication_id=self.ui.id)
789+
790+ self.test_on_replication_settings_change_error()
791+
792+ def test_on_replication_settings_change_error_different_id(self):
793+ """When settings were not changed for other replication, do nothing."""
794+ self.ui.button.set_active(not self.ui.button.get_active())
795+ self.ui.on_replication_settings_change_error(replication_id='yudo')
796+
797+ self.assertEqual(self.ui.warning_label.get_text(), '')
798+
799+
800+class DesktopcouchServiceDisabledAtStartupTestCase(ServiceTestCase):
801+ """The test suite for a desktopcouch service when enabled=False."""
802+
803+ enabled = False
804+
805
806 class DesktopcouchServiceWithDependencyTestCase(DesktopcouchServiceTestCase):
807 """The test suite for a desktopcouch service when it needs a dependency."""
808@@ -1532,7 +1609,8 @@
809 self.ui.dependency.emit('finished')
810
811 self.assertTrue(self.ui.dependency is None)
812- self.assertEqual(self.ui.get_children(), [self.ui.button])
813+ self.assertEqual(sorted(self.ui.get_children()),
814+ sorted([self.ui.button, self.ui.warning_label]))
815
816
817 class ServicesTestCase(ControlPanelMixinTestCase):
818@@ -1562,6 +1640,13 @@
819 """The install box is None."""
820 self.assertTrue(self.ui.install_box is None)
821
822+ def test_backend_signals(self):
823+ """The proper signals are connected to the backend."""
824+ self.assertEqual(self.ui.backend._signals['ReplicationsInfoReady'],
825+ [self.ui.on_replications_info_ready])
826+ self.assertEqual(self.ui.backend._signals['ReplicationsInfoError'],
827+ [self.ui.on_replications_info_error])
828+
829
830 class ServicesFilesTestCase(ServicesTestCase):
831 """The test suite for the services panel (files section)."""
832@@ -1589,22 +1674,10 @@
833 self.assertFalse(self.ui.message.active)
834 self.assertEqual(self.ui.message.get_text(), '')
835
836- def test_replication_service(self):
837- """Has a replication service."""
838- self.assertEqual(self.ui.replication_service, None)
839-
840 def test_has_desktopcouch(self):
841 """Has desktopcouch installed?"""
842 self.assertFalse(self.ui.has_desktopcouch)
843
844- def test_has_bindwood(self):
845- """Has bindwood installed?"""
846- self.assertFalse(self.ui.has_bindwood)
847-
848- def test_has_evocouch(self):
849- """Has evocouch installed?"""
850- self.assertFalse(self.ui.has_evocouch)
851-
852 def test_install_box_is_hidden(self):
853 """The install box is not hidden."""
854 self.assertTrue(self.ui.install_box.get_visible())
855@@ -1629,41 +1702,27 @@
856
857 self.assertEqual(self._called, ((self.ui.install_box,), {}))
858
859+ def test_load_replications(self):
860+ """The load_replications starts the spinner and calls the backend."""
861+ self.ui.load_replications()
862+
863+ self.assertTrue(self.ui.message.active)
864+ self.assert_backend_called('replications_info', ())
865+
866
867 class ServicesWithDesktopcouchTestCase(ServicesTestCase):
868 """The test suite for the services panel."""
869
870- kwargs = {'replication_exclusion_class': FakedReplication}
871-
872 def setUp(self):
873 super(ServicesWithDesktopcouchTestCase, self).setUp()
874 self.ui.package_manager._installed[self.ui.DESKTOPCOUCH_PKG] = True
875- self.ui.load()
876+ self.ui.on_replications_info_ready(info=FAKE_REPLICATIONS_INFO)
877
878 def test_message(self):
879 """Global load message is stopped and proper test is shown."""
880 self.assertFalse(self.ui.message.active)
881 self.assertEqual(self.ui.message.get_text(), self.ui.CHOOSE_SERVICES)
882
883- def test_replication_service(self):
884- """Has a replication service."""
885- self.assertIsInstance(self.ui.replication_service, FakedReplication)
886-
887- def test_no_pairing_record(self):
888- """The pairing record is not in place."""
889-
890- def no_pairing_record(*a):
891- """Fake a ReplicationExclusion with no pairing record."""
892- raise ValueError("No pairing record for ubuntuone.")
893-
894- self.ui.replication_exclusion_class = no_pairing_record
895- self.ui.replication_service = None
896- self.ui.load()
897-
898- self.assertEqual(self.ui.replications.get_children(), [])
899- self.assertFalse(self.ui.message.active)
900- self.assert_warning_correct(self.ui.message, self.ui.NO_PAIRING_RECORD)
901-
902 def test_has_desktopcouch(self):
903 """Has desktopcouch installed?"""
904 self.assertTrue(self.ui.has_desktopcouch)
905@@ -1673,79 +1732,67 @@
906 self.assertTrue(self.ui.replications.get_visible())
907
908 children = self.ui.replications.get_children()
909- self.assertEqual(len(children), 2)
910- for child in children:
911+ self.assertEqual(len(children), len(FAKE_REPLICATIONS_INFO))
912+ for expected, child in zip(FAKE_REPLICATIONS_INFO, children):
913 self.assertIsInstance(child, gui.DesktopcouchService)
914-
915- self.assertTrue(self.ui.bookmarks is children[0])
916- self.assertTrue(self.ui.contacts is children[1])
917-
918- def test_replications_after_loading_twice(self):
919- """Has proper child after loading twice."""
920- self.ui.load()
921+ self.assertEqual(expected['replication_id'], child.id)
922+ self.assertEqual(expected['name'], child.button.get_label())
923+ self.assertEqual(bool(expected['enabled']),
924+ child.button.get_active())
925+
926+ if expected['dependency']:
927+ self.assertTrue(child.dependency is not None)
928+ self.assertEqual(expected['dependency'],
929+ child.dependency.package_name)
930+ else:
931+ self.assertTrue(child.dependency is None)
932+
933+ def test_replications_after_getting_info_twice(self):
934+ """Has proper child after getting backend info twice."""
935+ self.ui.on_replications_info_ready(info=FAKE_REPLICATIONS_INFO)
936 self.test_replications()
937
938- def test_bookmarks(self):
939- """The bookmarks is correct."""
940- self.assertEqual(self.ui.bookmarks.service_name, 'bookmarks')
941- self.assertEqual(self.ui.bookmarks.button.get_label(),
942- self.ui.BOOKMARKS)
943- self.assertTrue(self.ui.bookmarks.replication_service is
944- self.ui.replication_service)
945-
946- def test_bookmarks_dependency(self):
947- """The bookmarks dependency is correct."""
948- self.assertTrue(self.ui.bookmarks.dependency is not None)
949- self.assertEqual(self.ui.bookmarks.dependency.package_name,
950- self.ui.BINDWOOD_PKG)
951-
952- def test_contacts(self):
953- """The contacts is correct."""
954- self.assertEqual(self.ui.contacts.service_name, 'contacts')
955- self.assertEqual(self.ui.contacts.button.get_label(),
956- self.ui.CONTACTS)
957- self.assertTrue(self.ui.contacts.replication_service is
958- self.ui.replication_service)
959-
960- def test_contacts_dependency(self):
961- """The contacts dependency is correct."""
962- self.assertTrue(self.ui.contacts.dependency is not None)
963- self.assertEqual(self.ui.contacts.dependency.package_name,
964- self.ui.EVOCOUCH_PKG)
965-
966-
967-class ServicesWithDCAndBindwoodTestCase(ServicesWithDesktopcouchTestCase):
968- """The test suite for the services panel."""
969-
970- def setUp(self):
971- super(ServicesWithDCAndBindwoodTestCase, self).setUp()
972- self.ui.package_manager._installed[self.ui.BINDWOOD_PKG] = True
973- self.ui.load()
974-
975- def test_has_bindwood(self):
976- """Has bindwood installed?"""
977- self.assertTrue(self.ui.has_bindwood)
978-
979- def test_bookmarks_dependency(self):
980- """The bookmarks dependency is correct."""
981- self.assertTrue(self.ui.bookmarks.dependency is None)
982-
983-
984-class ServicesWithDCAndEvocouchTestCase(ServicesWithDesktopcouchTestCase):
985- """The test suite for the services panel."""
986-
987- def setUp(self):
988- super(ServicesWithDCAndEvocouchTestCase, self).setUp()
989- self.ui.package_manager._installed[self.ui.EVOCOUCH_PKG] = True
990- self.ui.load()
991-
992- def test_has_evocouch(self):
993- """Has evocoucg installed?"""
994- self.assertTrue(self.ui.has_evocouch)
995-
996- def test_contacts_dependency(self):
997- """The bookmarks dependency is correct."""
998- self.assertTrue(self.ui.contacts.dependency is None)
999+
1000+class ServicesWithDesktopcouchErrorTestCase(ServicesTestCase):
1001+ """The test suite for the services panel."""
1002+
1003+ def setUp(self):
1004+ super(ServicesWithDesktopcouchErrorTestCase, self).setUp()
1005+ self.ui.package_manager._installed[self.ui.DESKTOPCOUCH_PKG] = True
1006+
1007+ def test_no_pairing_record(self):
1008+ """The pairing record is not in place."""
1009+ error_dict = {'error_type': 'NoPairingRecord'}
1010+ self.ui.on_replications_info_error(error_dict)
1011+
1012+ self.assertEqual(self.ui.replications.get_children(), [])
1013+ self.assertFalse(self.ui.message.active)
1014+ self.assert_warning_correct(self.ui.message, self.ui.NO_PAIRING_RECORD)
1015+
1016+ def test_other_error(self):
1017+ """There was an error other than no pairing record."""
1018+ error_dict = {'error_type': 'OtherError'}
1019+ self.ui.on_replications_info_error(error_dict)
1020+
1021+ self.assertEqual(self.ui.replications.get_children(), [])
1022+ self.assertFalse(self.ui.message.active)
1023+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
1024+
1025+ def test_empty_dict(self):
1026+ """Handle empty dicts errors."""
1027+ self.ui.on_replications_info_error(error_dict={})
1028+
1029+ self.assertEqual(self.ui.replications.get_children(), [])
1030+ self.assertFalse(self.ui.message.active)
1031+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
1032+
1033+ def test_error_dict_none(self):
1034+ """HGandle empty dicts errors."""
1035+ self.ui.on_replications_info_error(error_dict=None)
1036+
1037+ self.assertEqual(self.ui.replications.get_children(), [])
1038+ self.assertFalse(self.ui.message.active)
1039+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
1040
1041
1042 class ManagementPanelTestCase(ControlPanelMixinTestCase):
1043
1044=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py'
1045--- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-23 18:20:56 +0000
1046+++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-01-06 20:36:09 +0000
1047@@ -84,6 +84,11 @@
1048 },
1049 ]
1050
1051+SAMPLE_REPLICATIONS_INFO = [
1052+ {'replication_id': 'yadda', 'wait for it': 'awesome'},
1053+ {'replication_id': 'yoda', 'something else': 'awesome'},
1054+]
1055+
1056
1057 class DBusServiceMainTestCase(mocker.MockerTestCase):
1058 """Tests for the main function."""
1059@@ -166,6 +171,19 @@
1060 """Configure a given volume."""
1061 return self._process(volume_id)
1062
1063+ def replications_info(self):
1064+ """Start the replication exclusion service if needed.
1065+
1066+ Return the replication info, which is a dictionary of (replication
1067+ name, enabled).
1068+
1069+ """
1070+ return self._process(SAMPLE_REPLICATIONS_INFO)
1071+
1072+ def change_replication_settings(self, replication_id, settings):
1073+ """Configure a given replication."""
1074+ return self._process(replication_id)
1075+
1076 def query_bookmark_extension(self):
1077 """True if the bookmark extension has been installed."""
1078 return self._process(False)
1079@@ -258,13 +276,13 @@
1080 self.assertEqual(expected, result)
1081
1082
1083-class OperationsTestCase(TestCase):
1084- """Test for the DBus service operations."""
1085+class BaseTestCase(TestCase):
1086+ """Base test case for the DBus service."""
1087
1088 timeout = 3
1089
1090 def setUp(self):
1091- super(OperationsTestCase, self).setUp()
1092+ super(BaseTestCase, self).setUp()
1093 dbus_service.init_mainloop()
1094 be = dbus_service.publish_backend(MockBackend())
1095 self.addCleanup(be.remove_from_connection)
1096@@ -279,7 +297,7 @@
1097 def tearDown(self):
1098 self.backend = None
1099 self.deferred = None
1100- super(OperationsTestCase, self).tearDown()
1101+ super(BaseTestCase, self).tearDown()
1102
1103 def got_error(self, *a):
1104 """Some error happened in the DBus call."""
1105@@ -322,6 +340,10 @@
1106
1107 return self.deferred
1108
1109+
1110+class OperationsTestCase(BaseTestCase):
1111+ """Test for the DBus service operations."""
1112+
1113 def test_account_info_returned(self):
1114 """The account info is successfully returned."""
1115
1116@@ -416,9 +438,9 @@
1117 def test_volumes_info(self):
1118 """The volumes info is reported."""
1119
1120- def got_signal(volumes_dict):
1121+ def got_signal(volumes):
1122 """The correct info was received."""
1123- self.assertEqual(volumes_dict, SAMPLE_VOLUMES_INFO)
1124+ self.assertEqual(volumes, SAMPLE_VOLUMES_INFO)
1125 self.deferred.callback("success")
1126
1127 args = ("VolumesInfoReady", "VolumesInfoError", got_signal,
1128@@ -439,6 +461,32 @@
1129 expected_volume_id, {'subscribed': ''})
1130 return self.assert_correct_method_call(*args)
1131
1132+ def test_replications_info(self):
1133+ """The replications info is reported."""
1134+
1135+ def got_signal(replications):
1136+ """The correct info was received."""
1137+ self.assertEqual(replications, SAMPLE_REPLICATIONS_INFO)
1138+ self.deferred.callback("success")
1139+
1140+ args = ("ReplicationsInfoReady", "ReplicationsInfoError", got_signal,
1141+ self.backend.replications_info)
1142+ return self.assert_correct_method_call(*args)
1143+
1144+ def test_change_replication_settings(self):
1145+ """The replication settings are successfully changed."""
1146+ expected_replication_id = SAMPLE_REPLICATIONS_INFO[0]['replication_id']
1147+
1148+ def got_signal(replication_id):
1149+ """The correct replication was changed."""
1150+ self.assertEqual(replication_id, expected_replication_id)
1151+ self.deferred.callback("success")
1152+
1153+ args = ("ReplicationSettingsChanged", "ReplicationSettingsChangeError",
1154+ got_signal, self.backend.change_replication_settings,
1155+ expected_replication_id, {'enabled': ''})
1156+ return self.assert_correct_method_call(*args)
1157+
1158 def test_query_bookmarks_extension(self):
1159 """The bookmarks extension is queried."""
1160
1161@@ -495,7 +543,7 @@
1162 error_sig, success_sig, got_error_signal, method, *args)
1163
1164
1165-class FileSyncTestCase(OperationsTestCase):
1166+class FileSyncTestCase(BaseTestCase):
1167 """Test for the DBus service when requesting file sync status."""
1168
1169 def assert_correct_status_signal(self, status, sync_signal,
1170
1171=== modified file 'ubuntuone/controlpanel/logger.py'
1172--- ubuntuone/controlpanel/logger.py 2010-12-23 18:20:56 +0000
1173+++ ubuntuone/controlpanel/logger.py 2011-01-06 20:36:09 +0000
1174@@ -53,10 +53,7 @@
1175 logger.addHandler(MAIN_HANDLER)
1176 if os.environ.get('DEBUG'):
1177 debug_handler = logging.StreamHandler(sys.stderr)
1178- if prefix is not None:
1179- fmt = prefix + "%(name)s - %(levelname)s\n%(message)s\n"
1180- formatter = logging.Formatter(fmt)
1181- debug_handler.setFormatter(formatter)
1182+ debug_handler.setFormatter(basic_formatter)
1183 logger.addHandler(debug_handler)
1184
1185 return logger
1186
1187=== added file 'ubuntuone/controlpanel/replication_client.py'
1188--- ubuntuone/controlpanel/replication_client.py 1970-01-01 00:00:00 +0000
1189+++ ubuntuone/controlpanel/replication_client.py 2011-01-06 20:36:09 +0000
1190@@ -0,0 +1,115 @@
1191+# -*- coding: utf-8 -*-
1192+
1193+# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
1194+#
1195+# Copyright 2010 Canonical Ltd.
1196+#
1197+# This program is free software: you can redistribute it and/or modify it
1198+# under the terms of the GNU General Public License version 3, as published
1199+# by the Free Software Foundation.
1200+#
1201+# This program is distributed in the hope that it will be useful, but
1202+# WITHOUT ANY WARRANTY; without even the implied warranties of
1203+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1204+# PURPOSE. See the GNU General Public License for more details.
1205+#
1206+# You should have received a copy of the GNU General Public License along
1207+# with this program. If not, see <http://www.gnu.org/licenses/>.
1208+
1209+"""Client to use replication services."""
1210+
1211+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
1212+
1213+from ubuntuone.controlpanel.logger import setup_logging
1214+
1215+
1216+logger = setup_logging('replication_client')
1217+
1218+BOOKMARKS = 'bookmarks'
1219+CONTACTS = 'contacts'
1220+# we should get this list from somewhere else
1221+REPLICATIONS = set([BOOKMARKS, CONTACTS])
1222+
1223+
1224+class ReplicationError(Exception):
1225+ """A replication error."""
1226+
1227+
1228+class NoPairingRecord(ReplicationError):
1229+ """There is no pairing record."""
1230+
1231+
1232+class InvalidIdError(ReplicationError):
1233+ """The replication id is not valid."""
1234+
1235+
1236+class NotExcludedError(ReplicationError):
1237+ """The replication can not be replicated since is not excluded."""
1238+
1239+
1240+class AlreadyExcludedError(ReplicationError):
1241+ """The replication can not be excluded since is already excluded."""
1242+
1243+
1244+def get_replication_proxy(replication_module=None):
1245+ """Return a proxy to the replication client."""
1246+ d = Deferred()
1247+ if replication_module is None:
1248+ # delay import in case DC is not installed at module import time
1249+ # Unable to import 'desktopcouch.application.replication_services'
1250+ # pylint: disable=F0401
1251+ from desktopcouch.application.replication_services \
1252+ import ubuntuone as replication_module
1253+ try:
1254+ result = replication_module.ReplicationExclusion()
1255+ except ValueError:
1256+ d.errback(NoPairingRecord())
1257+ else:
1258+ d.callback(result)
1259+
1260+ return d
1261+
1262+
1263+@inlineCallbacks
1264+def get_replications():
1265+ """Retrieve the list of replications."""
1266+ yield get_replication_proxy()
1267+ returnValue(REPLICATIONS)
1268+
1269+
1270+@inlineCallbacks
1271+def get_exclusions():
1272+ """Retrieve the list of exclusions."""
1273+ proxy = yield get_replication_proxy()
1274+ result = proxy.all_exclusions()
1275+ returnValue(result)
1276+
1277+
1278+@inlineCallbacks
1279+def replicate(replication_id):
1280+ """Remove replication_id from the exclusions list."""
1281+ replications = yield get_replications()
1282+ if replication_id not in replications:
1283+ raise InvalidIdError(replication_id)
1284+
1285+ exclusions = yield get_exclusions()
1286+ if replication_id not in exclusions:
1287+ raise NotExcludedError(replication_id)
1288+
1289+ proxy = yield get_replication_proxy()
1290+ yield proxy.replicate(replication_id)
1291+
1292+
1293+@inlineCallbacks
1294+def exclude(replication_id):
1295+ """Add replication_id to the exclusions list."""
1296+ replications = yield get_replications()
1297+ if replication_id not in replications:
1298+ raise InvalidIdError(replication_id)
1299+
1300+ exclusions = yield get_exclusions()
1301+ if replication_id in exclusions:
1302+ raise AlreadyExcludedError(replication_id)
1303+
1304+ proxy = yield get_replication_proxy()
1305+ yield proxy.exclude(replication_id)
1306
1307=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
1308--- ubuntuone/controlpanel/tests/__init__.py 2010-12-20 16:11:13 +0000
1309+++ ubuntuone/controlpanel/tests/__init__.py 2011-01-06 20:36:09 +0000
1310@@ -24,9 +24,157 @@
1311 TOKEN = {u'consumer_key': u'xQ7xDAz',
1312 u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
1313 u'token_name': u'test',
1314- u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
1315+ u'token': u'ABCDEF01234-localtoken',
1316 u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
1317
1318+SAMPLE_ACCOUNT_JSON = """
1319+{
1320+ "username": "andrewpz",
1321+ "openid": "https://login.launchpad.net/+id/abcdefg",
1322+ "first_name": "Andrew P.",
1323+ "last_name": "Zoilo",
1324+ "couchdb": {
1325+ "host": "https://couchdb.one.ubuntu.com",
1326+ "root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
1327+ "dbpath": "u/abc/def/12345"
1328+ },
1329+ "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
1330+ "email": "andrewpz@protocultura.net",%s
1331+ "nickname": "Andrew P. Zoilo",
1332+ "id": 12345,
1333+ "subscription": {
1334+ "upgrade_available": false,
1335+ "description": "Paid Plan, 50 GB of storage",
1336+ "trial": false,
1337+ "started": "2010-03-24T18:38:38Z",
1338+ "is_paid": true,
1339+ "expires": null,
1340+ "qty": 1,
1341+ "price": 0.0,
1342+ "currency": null,
1343+ "id": 654321,
1344+ "name": "50 GB"
1345+ }
1346+}
1347+"""
1348+
1349+CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
1350+SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
1351+
1352+SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
1353+SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
1354+
1355+
1356+SAMPLE_QUOTA_JSON = """
1357+{
1358+ "total": 53687091200,
1359+ "used": 2350345156
1360+}
1361+"""
1362+
1363+EXPECTED_ACCOUNT_INFO = {
1364+ "quota_used": "2350345156",
1365+ "quota_total": "53687091200",
1366+ "type": "Paid Plan, 50 GB of storage",
1367+ "name": "Andrew P. Zoilo",
1368+ "email": "andrewpz@protocultura.net",
1369+}
1370+
1371+EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
1372+ "quota_used": "2350345156",
1373+ "quota_total": "53687091200",
1374+ "type": CURRENT_PLAN,
1375+ "name": "Andrew P. Zoilo",
1376+ "email": "andrewpz@protocultura.net",
1377+}
1378+
1379+SAMPLE_DEVICES_JSON = """
1380+[
1381+ {
1382+ "token": "ABCDEF01234token",
1383+ "description": "Ubuntu One @ darkstar",
1384+ "kind": "Computer"
1385+ },
1386+ {
1387+ "token": "ABCDEF01234-localtoken",
1388+ "description": "Ubuntu One @ localhost",
1389+ "kind": "Computer"
1390+ },
1391+ {
1392+ "kind": "Phone",
1393+ "description": "Nokia E65",
1394+ "id": 1000
1395+ }
1396+]
1397+"""
1398+
1399+EXPECTED_DEVICES_INFO = [
1400+ {
1401+ "device_id": "ComputerABCDEF01234token",
1402+ "name": "Ubuntu One @ darkstar",
1403+ "type": "Computer",
1404+ "is_local": '',
1405+ "configurable": '',
1406+ },
1407+ {
1408+ 'is_local': 'True',
1409+ 'configurable': 'True',
1410+ 'device_id': 'ComputerABCDEF01234-localtoken',
1411+ 'limit_bandwidth': '',
1412+ 'max_download_speed': '-1',
1413+ 'max_upload_speed': '-1',
1414+ 'name': 'Ubuntu One @ localhost',
1415+ 'type': 'Computer'
1416+ },
1417+ {
1418+ "device_id": "Phone1000",
1419+ "name": "Nokia E65",
1420+ "type": "Phone",
1421+ "configurable": '',
1422+ "is_local": '',
1423+ },
1424+]
1425+
1426+SAMPLE_FOLDERS = [
1427+ {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
1428+ u'path': u'/home/tester/Public', u'subscribed': u'True',
1429+ u'suggested_path': u'~/Public',
1430+ u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
1431+ {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
1432+ u'path': u'/home/tester/Documents', u'subscribed': u'',
1433+ u'suggested_path': u'~/Documents',
1434+ u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
1435+ {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
1436+ u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
1437+ u'suggested_path': u'~/Pictures/Photos',
1438+ u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
1439+]
1440+
1441+SAMPLE_SHARES = [
1442+ {u'accepted': u'True', u'access_level': u'View',
1443+ u'free_bytes': u'39892622746', u'generation': u'2704',
1444+ u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
1445+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
1446+ u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
1447+ u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
1448+ {u'accepted': u'True', u'access_level': u'Modify',
1449+ u'free_bytes': u'39892622746', u'generation': u'2704',
1450+ u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
1451+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
1452+ u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
1453+ u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
1454+]
1455+
1456+SAMPLE_SHARED = [
1457+ {u'accepted': u'True', u'access_level': u'View',
1458+ u'free_bytes': u'', u'generation': u'',
1459+ u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
1460+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
1461+ u'path': u'/home/tester/Ubuntu One/bar',
1462+ u'type': u'Shared',
1463+ u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
1464+]
1465+
1466
1467 class TestCase(BaseTestCase):
1468 """Basics for testing."""
1469
1470=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
1471--- ubuntuone/controlpanel/tests/test_backend.py 2010-12-23 18:20:56 +0000
1472+++ ubuntuone/controlpanel/tests/test_backend.py 2011-01-06 20:36:09 +0000
1473@@ -25,9 +25,9 @@
1474 from twisted.internet.defer import inlineCallbacks
1475 from ubuntuone.devtools.handlers import MementoHandler
1476
1477-from ubuntuone.controlpanel import backend
1478-from ubuntuone.controlpanel.backend import (ACCOUNT_API,
1479- DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
1480+from ubuntuone.controlpanel import backend, replication_client
1481+from ubuntuone.controlpanel.backend import (bool_str,
1482+ ACCOUNT_API, DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
1483 FILE_SYNC_DISABLED,
1484 FILE_SYNC_DISCONNECTED,
1485 FILE_SYNC_ERROR,
1486@@ -37,160 +37,21 @@
1487 FILE_SYNC_UNKNOWN,
1488 MSG_KEY, STATUS_KEY,
1489 )
1490-
1491-from ubuntuone.controlpanel.tests import TestCase
1492+from ubuntuone.controlpanel.tests import (TestCase,
1493+ EXPECTED_ACCOUNT_INFO,
1494+ EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,
1495+ EXPECTED_DEVICES_INFO,
1496+ SAMPLE_ACCOUNT_NO_CURRENT_PLAN,
1497+ SAMPLE_ACCOUNT_WITH_CURRENT_PLAN,
1498+ SAMPLE_DEVICES_JSON,
1499+ SAMPLE_FOLDERS,
1500+ SAMPLE_QUOTA_JSON,
1501+ SAMPLE_SHARED,
1502+ SAMPLE_SHARES,
1503+ TOKEN,
1504+)
1505 from ubuntuone.controlpanel.webclient import WebClientError
1506
1507-SAMPLE_CREDENTIALS = {"token": "ABC1234DEF"}
1508-
1509-SAMPLE_ACCOUNT_JSON = """
1510-{
1511- "username": "andrewpz",
1512- "openid": "https://login.launchpad.net/+id/abcdefg",
1513- "first_name": "Andrew P.",
1514- "last_name": "Zoilo",
1515- "couchdb": {
1516- "host": "https://couchdb.one.ubuntu.com",
1517- "root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
1518- "dbpath": "u/abc/def/12345"
1519- },
1520- "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
1521- "email": "andrewpz@protocultura.net",%s
1522- "nickname": "Andrew P. Zoilo",
1523- "id": 12345,
1524- "subscription": {
1525- "upgrade_available": false,
1526- "description": "Paid Plan, 50 GB of storage",
1527- "trial": false,
1528- "started": "2010-03-24T18:38:38Z",
1529- "is_paid": true,
1530- "expires": null,
1531- "qty": 1,
1532- "price": 0.0,
1533- "currency": null,
1534- "id": 654321,
1535- "name": "50 GB"
1536- }
1537-}
1538-"""
1539-
1540-CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
1541-SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
1542-
1543-SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
1544-SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
1545-
1546-
1547-SAMPLE_QUOTA_JSON = """
1548-{
1549- "total": 53687091200,
1550- "used": 2350345156
1551-}
1552-"""
1553-
1554-EXPECTED_ACCOUNT_INFO = {
1555- "quota_used": "2350345156",
1556- "quota_total": "53687091200",
1557- "type": "Paid Plan, 50 GB of storage",
1558- "name": "Andrew P. Zoilo",
1559- "email": "andrewpz@protocultura.net",
1560-}
1561-
1562-EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
1563- "quota_used": "2350345156",
1564- "quota_total": "53687091200",
1565- "type": CURRENT_PLAN,
1566- "name": "Andrew P. Zoilo",
1567- "email": "andrewpz@protocultura.net",
1568-}
1569-
1570-SAMPLE_DEVICES_JSON = """
1571-[
1572- {
1573- "token": "ABCDEF01234token",
1574- "description": "Ubuntu One @ darkstar",
1575- "kind": "Computer"
1576- },
1577- {
1578- "token": "ABC1234DEF",
1579- "description": "Ubuntu One @ localhost",
1580- "kind": "Computer"
1581- },
1582- {
1583- "kind": "Phone",
1584- "description": "Nokia E65",
1585- "id": 1000
1586- }
1587-]
1588-"""
1589-
1590-EXPECTED_DEVICES_INFO = [
1591- {
1592- "device_id": "ComputerABCDEF01234token",
1593- "name": "Ubuntu One @ darkstar",
1594- "type": "Computer",
1595- "is_local": '',
1596- "configurable": '',
1597- },
1598- {
1599- 'is_local': 'True',
1600- 'configurable': 'True',
1601- 'device_id': 'ComputerABC1234DEF',
1602- 'limit_bandwidth': '',
1603- 'max_download_speed': '-1',
1604- 'max_upload_speed': '-1',
1605- 'name': 'Ubuntu One @ localhost',
1606- 'type': 'Computer'
1607- },
1608- {
1609- "device_id": "Phone1000",
1610- "name": "Nokia E65",
1611- "type": "Phone",
1612- "configurable": '',
1613- "is_local": '',
1614- },
1615-]
1616-
1617-SAMPLE_FOLDERS = [
1618- {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
1619- u'path': u'/home/tester/Public', u'subscribed': u'True',
1620- u'suggested_path': u'~/Public',
1621- u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
1622- {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
1623- u'path': u'/home/tester/Documents', u'subscribed': u'',
1624- u'suggested_path': u'~/Documents',
1625- u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
1626- {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
1627- u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
1628- u'suggested_path': u'~/Pictures/Photos',
1629- u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
1630-]
1631-
1632-SAMPLE_SHARES = [
1633- {u'accepted': u'True', u'access_level': u'View',
1634- u'free_bytes': u'39892622746', u'generation': u'2704',
1635- u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
1636- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
1637- u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
1638- u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
1639- {u'accepted': u'True', u'access_level': u'Modify',
1640- u'free_bytes': u'39892622746', u'generation': u'2704',
1641- u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
1642- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
1643- u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
1644- u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
1645-]
1646-
1647-SAMPLE_SHARED = [
1648- {u'accepted': u'True', u'access_level': u'View',
1649- u'free_bytes': u'', u'generation': u'',
1650- u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
1651- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
1652- u'path': u'/home/tester/Ubuntu One/bar',
1653- u'type': u'Shared',
1654- u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
1655-]
1656-
1657
1658 class MockWebClient(object):
1659 """A mock webclient."""
1660@@ -213,7 +74,7 @@
1661 class MockDBusClient(object):
1662 """A mock dbus_client module."""
1663
1664- creds = SAMPLE_CREDENTIALS
1665+ creds = TOKEN
1666 throttling = False
1667 limits = {"download": -1, "upload": -1}
1668 file_sync = True
1669@@ -292,6 +153,36 @@
1670 return SAMPLE_SHARED
1671
1672
1673+class MockReplicationClient(object):
1674+ """A mock replication_client module."""
1675+
1676+ BOOKMARKS = 'awesome'
1677+ CONTACTS = 'legendary'
1678+
1679+ replications = set([BOOKMARKS, CONTACTS, 'other'])
1680+ exclusions = set([CONTACTS])
1681+
1682+ def get_replications(self):
1683+ """Grab the list of replications in this machine."""
1684+ return MockReplicationClient.replications
1685+
1686+ def get_exclusions(self):
1687+ """Grab the list of exclusions in this machine."""
1688+ return MockReplicationClient.exclusions
1689+
1690+ def replicate(self, replication_id):
1691+ """Remove replication_id from the exclusions list."""
1692+ if replication_id not in MockReplicationClient.replications:
1693+ raise replication_client.ReplicationError(replication_id)
1694+ MockReplicationClient.exclusions.remove(replication_id)
1695+
1696+ def exclude(self, replication_id):
1697+ """Add replication_id to the exclusions list."""
1698+ if replication_id not in MockReplicationClient.replications:
1699+ raise replication_client.ReplicationError(replication_id)
1700+ MockReplicationClient.exclusions.add(replication_id)
1701+
1702+
1703 class BackendBasicTestCase(TestCase):
1704 """Simple tests for the backend."""
1705
1706@@ -301,13 +192,14 @@
1707 super(BackendBasicTestCase, self).setUp()
1708 self.patch(backend, "WebClient", MockWebClient)
1709 self.patch(backend, "dbus_client", MockDBusClient())
1710- self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"]
1711+ self.patch(backend, "replication_client", MockReplicationClient())
1712+ self.local_token = "Computer" + TOKEN["token"]
1713 self.be = backend.ControlBackend()
1714
1715 self.memento = MementoHandler()
1716 backend.logger.addHandler(self.memento)
1717
1718- MockDBusClient.creds = SAMPLE_CREDENTIALS
1719+ MockDBusClient.creds = TOKEN
1720
1721 def test_backend_creation(self):
1722 """The backend instance is successfully created."""
1723@@ -317,7 +209,7 @@
1724 def test_get_token(self):
1725 """The get_token method returns the right token."""
1726 token = yield self.be.get_token()
1727- self.assertEqual(token, SAMPLE_CREDENTIALS["token"])
1728+ self.assertEqual(token, TOKEN["token"])
1729
1730 @inlineCallbacks
1731 def test_device_is_local(self):
1732@@ -388,12 +280,12 @@
1733 result = yield self.be.remove_device(device_id)
1734 self.assertEqual(result, device_id)
1735 # credentials were not cleared
1736- self.assertEqual(MockDBusClient.creds, SAMPLE_CREDENTIALS)
1737+ self.assertEqual(MockDBusClient.creds, TOKEN)
1738
1739 @inlineCallbacks
1740 def test_remove_device_clear_credentials_if_local_device(self):
1741 """The remove_device method clears the credentials if is local."""
1742- apiurl = DEVICE_REMOVE_API % ('computer', SAMPLE_CREDENTIALS['token'])
1743+ apiurl = DEVICE_REMOVE_API % ('computer', TOKEN['token'])
1744 # pylint: disable=E1101
1745 self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
1746 yield self.be.remove_device(self.local_token)
1747@@ -487,10 +379,10 @@
1748 """The volume settings can be changed."""
1749 fid = '0123-4567'
1750
1751- yield self.be.change_volume_settings(fid, {'subscribed': True})
1752+ yield self.be.change_volume_settings(fid, {'subscribed': 'True'})
1753 self.assertEqual(MockDBusClient.subscribed_folders, [fid])
1754
1755- yield self.be.change_volume_settings(fid, {'subscribed': False})
1756+ yield self.be.change_volume_settings(fid, {'subscribed': ''})
1757 self.assertEqual(MockDBusClient.subscribed_folders, [])
1758
1759 @inlineCallbacks
1760@@ -671,3 +563,64 @@
1761
1762 self.be.disable_files()
1763 self.assertFalse(MockDBusClient.file_sync)
1764+
1765+
1766+class BackendReplicationsTestCase(BackendBasicTestCase):
1767+ """Replications tests for the backend."""
1768+
1769+ @inlineCallbacks
1770+ def test_replications_info(self):
1771+ """The replications_info method exercises its callback."""
1772+ result = yield self.be.replications_info()
1773+
1774+ # replications_info will use exclusions information
1775+ expected = []
1776+ for name in MockReplicationClient.replications:
1777+ enabled = bool_str(name not in MockReplicationClient.exclusions)
1778+ dependency = ''
1779+ if name == MockReplicationClient.BOOKMARKS:
1780+ dependency = backend.BOOKMARKS_PKG
1781+ elif name == MockReplicationClient.CONTACTS:
1782+ dependency = backend.CONTACTS_PKG
1783+
1784+ item = {'replication_id': name, 'name': name,
1785+ 'enabled': enabled, 'dependency': dependency}
1786+ expected.append(item)
1787+ self.assertEqual(sorted(expected), sorted(result))
1788+
1789+ @inlineCallbacks
1790+ def test_change_replication_settings(self):
1791+ """The replication settings can be changed."""
1792+ rid = '0123-4567'
1793+ MockReplicationClient.replications.add(rid)
1794+ self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
1795+
1796+ yield self.be.change_replication_settings(rid, {'enabled': ''})
1797+ self.assertIn(rid, MockReplicationClient.exclusions)
1798+
1799+ yield self.be.change_replication_settings(rid, {'enabled': 'True'})
1800+ self.assertNotIn(rid, MockReplicationClient.exclusions)
1801+
1802+ @inlineCallbacks
1803+ def test_change_replication_settings_not_in_replications(self):
1804+ """The settings can not be changed for an item not in replications."""
1805+ rid = '0123-4567'
1806+ assert rid not in MockReplicationClient.replications
1807+
1808+ d = self.be.change_replication_settings(rid, {'enabled': 'True'})
1809+ yield self.assertFailure(d, replication_client.ReplicationError)
1810+
1811+ d = self.be.change_replication_settings(rid, {'enabled': ''})
1812+ yield self.assertFailure(d, replication_client.ReplicationError)
1813+
1814+ @inlineCallbacks
1815+ def test_change_replication_settings_no_setting(self):
1816+ """The change replication settings does not fail on empty settings."""
1817+ rid = '0123-4567'
1818+ MockReplicationClient.replications.add(rid)
1819+ self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
1820+
1821+ prior = MockReplicationClient.exclusions.copy()
1822+ yield self.be.change_replication_settings(rid, {})
1823+
1824+ self.assertEqual(MockReplicationClient.exclusions, prior)
1825
1826=== added file 'ubuntuone/controlpanel/tests/test_replication_client.py'
1827--- ubuntuone/controlpanel/tests/test_replication_client.py 1970-01-01 00:00:00 +0000
1828+++ ubuntuone/controlpanel/tests/test_replication_client.py 2011-01-06 20:36:09 +0000
1829@@ -0,0 +1,160 @@
1830+# -*- coding: utf-8 -*-
1831+
1832+# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
1833+#
1834+# Copyright 2010 Canonical Ltd.
1835+#
1836+# This program is free software: you can redistribute it and/or modify it
1837+# under the terms of the GNU General Public License version 3, as published
1838+# by the Free Software Foundation.
1839+#
1840+# This program is distributed in the hope that it will be useful, but
1841+# WITHOUT ANY WARRANTY; without even the implied warranties of
1842+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1843+# PURPOSE. See the GNU General Public License for more details.
1844+#
1845+# You should have received a copy of the GNU General Public License along
1846+# with this program. If not, see <http://www.gnu.org/licenses/>.
1847+
1848+"""Tests for the DBus service when accessing desktopcouch replications."""
1849+
1850+from twisted.internet.defer import inlineCallbacks
1851+
1852+from ubuntuone.controlpanel import replication_client
1853+from ubuntuone.controlpanel.tests import TestCase
1854+
1855+EXCLUSIONS = set()
1856+
1857+
1858+class FakedReplication(object):
1859+ """Faked a DC replication exclusion."""
1860+
1861+ def __init__(self):
1862+ self.all_exclusions = lambda: EXCLUSIONS
1863+ self.replicate = EXCLUSIONS.remove
1864+ self.exclude = EXCLUSIONS.add
1865+
1866+
1867+class FakedReplicationModule(object):
1868+ """Faked a DC replication module."""
1869+
1870+ ReplicationExclusion = FakedReplication
1871+
1872+
1873+class ReplicationsTestCase(TestCase):
1874+ """Test for the replications client methods."""
1875+
1876+ def setUp(self):
1877+ super(ReplicationsTestCase, self).setUp()
1878+
1879+ orig_get_proxy = replication_client.get_replication_proxy
1880+
1881+ def get_proxy():
1882+ """Fake the proxy getter."""
1883+ return orig_get_proxy(replication_module=FakedReplicationModule())
1884+
1885+ self.patch(replication_client, 'get_replication_proxy', get_proxy)
1886+
1887+ def tearDown(self):
1888+ EXCLUSIONS.clear()
1889+ super(ReplicationsTestCase, self).tearDown()
1890+
1891+ @inlineCallbacks
1892+ def test_no_pairing_record(self):
1893+ """Handle ValueError from replication layer."""
1894+
1895+ def no_pairing_record(*args, **kwargs):
1896+ """Fail with ValueError."""
1897+ raise ValueError('No pairing record.')
1898+
1899+ self.patch(FakedReplicationModule, 'ReplicationExclusion',
1900+ no_pairing_record)
1901+
1902+ yield self.assertFailure(replication_client.get_replications(),
1903+ replication_client.NoPairingRecord)
1904+
1905+ @inlineCallbacks
1906+ def test_get_replications(self):
1907+ """Replications are correctly retrieved."""
1908+ result = yield replication_client.get_replications()
1909+ self.assertEqual(result, replication_client.REPLICATIONS)
1910+
1911+ @inlineCallbacks
1912+ def test_get_exclusions(self):
1913+ """Exclusions are correctly retrieved."""
1914+ replications = yield replication_client.get_replications()
1915+ for rep in replications:
1916+ yield replication_client.exclude(rep)
1917+
1918+ result = yield replication_client.get_exclusions()
1919+ self.assertEqual(result, replications)
1920+
1921+ @inlineCallbacks
1922+ def test_replicate(self):
1923+ """Replicate a service is correct."""
1924+ replications = yield replication_client.get_replications()
1925+ rid = list(replications)[0]
1926+ yield replication_client.exclude(rid)
1927+
1928+ yield replication_client.replicate(rid)
1929+ exclusions = yield replication_client.get_exclusions()
1930+ self.assertNotIn(rid, exclusions)
1931+
1932+ @inlineCallbacks
1933+ def test_replicate_name_not_in_replications(self):
1934+ """Replicate a service fails if not in replications."""
1935+ replications = yield replication_client.get_replications()
1936+ rid = 'not in replications'
1937+ assert rid not in replications
1938+
1939+ yield self.assertFailure(replication_client.replicate(rid),
1940+ replication_client.InvalidIdError)
1941+
1942+ @inlineCallbacks
1943+ def test_replicate_name_not_in_exclusions(self):
1944+ """Replicate a service fails if not in exclusions."""
1945+ replications = yield replication_client.get_replications()
1946+ rid = list(replications)[0]
1947+ assert rid in replications
1948+
1949+ exclusions = yield replication_client.get_exclusions()
1950+ assert rid not in exclusions
1951+
1952+ yield self.assertFailure(replication_client.replicate(rid),
1953+ replication_client.NotExcludedError)
1954+
1955+ @inlineCallbacks
1956+ def test_exclude(self):
1957+ """Excluding a service is correct."""
1958+ replications = yield replication_client.get_replications()
1959+ rid = list(replications)[0]
1960+ yield replication_client.exclude(rid)
1961+ yield replication_client.replicate(rid)
1962+
1963+ yield replication_client.exclude(rid)
1964+ exclusions = yield replication_client.get_exclusions()
1965+ self.assertIn(rid, exclusions)
1966+
1967+ @inlineCallbacks
1968+ def test_exclude_name_not_in_replications(self):
1969+ """Excluding a service fails if not in replications."""
1970+ replications = yield replication_client.get_replications()
1971+ rid = 'not in replications'
1972+ assert rid not in replications
1973+
1974+ yield self.assertFailure(replication_client.exclude(rid),
1975+ replication_client.InvalidIdError)
1976+
1977+ @inlineCallbacks
1978+ def test_exclude_name_in_exclusions(self):
1979+ """Excluding a service fails if already on exclusions."""
1980+ replications = yield replication_client.get_replications()
1981+ rid = list(replications)[0]
1982+ assert rid in replications
1983+
1984+ yield replication_client.exclude(rid)
1985+ exclusions = yield replication_client.get_exclusions()
1986+ assert rid in exclusions
1987+
1988+ yield self.assertFailure(replication_client.exclude(rid),
1989+ replication_client.AlreadyExcludedError)

Subscribers

People subscribed via source and target branches