Merge lp:~nataliabidart/ubuntu-sso-client/no-more-gobject into lp:ubuntu-sso-client
- no-more-gobject
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Natalia Bidart |
Approved revision: | 666 |
Merged at revision: | 666 |
Proposed branch: | lp:~nataliabidart/ubuntu-sso-client/no-more-gobject |
Merge into: | lp:ubuntu-sso-client |
Prerequisite: | lp:~nataliabidart/ubuntu-sso-client/fix-693531 |
Diff against target: |
956 lines (+263/-234) 5 files modified
ubuntu_sso/credentials.py (+21/-28) ubuntu_sso/gtk/gui.py (+27/-32) ubuntu_sso/gtk/tests/test_gui.py (+155/-86) ubuntu_sso/tests/test_credentials.py (+49/-84) ubuntu_sso/tests/test_main.py (+11/-4) |
To merge this branch: | bzr merge lp:~nataliabidart/ubuntu-sso-client/no-more-gobject |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Roberto Alsina (community) | Approve | ||
Manuel de la Peña (community) | Approve | ||
Review via email: mp+44988@code.launchpad.net |
Commit message
No more gobject dependency on non-GUI classes! (LP: #695798).
Description of the change
To run the test suite, you can do:
./run-tests
To test IRL, you can run within this branch:
killall ubuntu-sso-login; DEBUG=True PYTHONPATH=. ./bin/ubuntu-
And delete your Ubuntu One token using d-feet (com.ubuntu.sso service, /com/ubuntu/
'Ubuntu One', {}
Then, you can try re-adding your machine to Ubuntu One.
Roberto Alsina (ralsina) wrote : | # |
+1 from me too.
Alejandro J. Cura (alecu) wrote : | # |
59 + # This code is blocking, we should change this.
60 + # I've tried with deferToThread an twisted.
61 + # but the returned deferred will never be fired (nataliabidart).
Neither deferToThread nor getPage will work because the twisted reactor is not used anymore in ubuntu-sso-client. Only twisted Deferreds are used in this code, but only as a control structure in order to make the sequence of callbacks cleaner, and to ease testing with trial.
I'm thinking of a few ways to have safe async http calls here: using libsoup or putting back the twisted reactor.
Using libsoup (like the ubuntuone-
Using twisted will make the code more portable, but the backend should be moved to its own process (also just like u1cp) to avoid the problems we had when we mixed gtkwebkit and the twisted reactor.
Yet another option would be to move the ping url code to the gui layer, where each toolkit would choose the best async way to do it. (a Gnome ui would use libsoup, etc).
Natalia Bidart (nataliabidart) wrote : | # |
Alecu, thanks a lot for your input, it makes perfect sense.
Currently, with my not-very-
Preview Diff
1 | === modified file 'ubuntu_sso/credentials.py' |
2 | --- ubuntu_sso/credentials.py 2010-11-24 12:48:28 +0000 |
3 | +++ ubuntu_sso/credentials.py 2011-01-02 03:31:12 +0000 |
4 | @@ -41,8 +41,6 @@ |
5 | |
6 | from functools import wraps |
7 | |
8 | -import gobject |
9 | - |
10 | from oauth import oauth |
11 | from twisted.internet.defer import inlineCallbacks, returnValue |
12 | |
13 | @@ -187,13 +185,15 @@ |
14 | |
15 | @handle_failures(msg='Problem while retrieving credentials') |
16 | @inlineCallbacks |
17 | - def _login_success_cb(self, dialog, app_name, email): |
18 | + def _login_success_cb(self, app_name, email): |
19 | """Store credentials when the login/registration succeeded. |
20 | |
21 | Also, open self.ping_url/email to notify about this new token. If any |
22 | error occur, self.error_cb is called. Otherwise, self.success_cb is |
23 | called. |
24 | |
25 | + Return 0 on success, and a non-zero value (or None) on error. |
26 | + |
27 | """ |
28 | logger.info('Login/registration was successful for app %r, email %r', |
29 | app_name, email) |
30 | @@ -202,19 +202,21 @@ |
31 | assert len(creds) > 0, 'Creds are empty! This should not happen' |
32 | # ping a server with the credentials if we were requested to |
33 | if self.ping_url is not None: |
34 | - status = self._ping_url(app_name, email, creds) |
35 | + status = yield self._ping_url(app_name, email, creds) |
36 | if status is None: |
37 | yield self.clear_credentials() |
38 | return |
39 | |
40 | self.success_cb(creds) |
41 | + returnValue(0) |
42 | |
43 | - def _auth_denial_cb(self, dialog, app_name): |
44 | + def _auth_denial_cb(self, app_name): |
45 | """The user decided not to allow the registration or login.""" |
46 | logger.warning('Login/registration was denied to app %r', app_name) |
47 | self.denial_cb(app_name) |
48 | |
49 | - @handle_exceptions(msg='Problem opening the ping_url') |
50 | + @handle_failures(msg='Problem opening the ping_url') |
51 | + @inlineCallbacks |
52 | def _ping_url(self, app_name, email, credentials): |
53 | """Ping the self.ping_url with the email attached. |
54 | |
55 | @@ -237,9 +239,12 @@ |
56 | request = urllib2.Request(url, headers=oauth_req.to_header()) |
57 | logger.debug('Opening the url "%s" with urllib2.urlopen.', |
58 | request.get_full_url()) |
59 | + # This code is blocking, we should change this. |
60 | + # I've tried with deferToThread an twisted.web.client.getPage |
61 | + # but the returned deferred will never be fired (nataliabidart). |
62 | response = urllib2.urlopen(request) |
63 | logger.debug('Url opened. Response: %s.', response.code) |
64 | - return response.code |
65 | + returnValue(response.code) |
66 | |
67 | @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface') |
68 | def _show_ui(self, login_only): |
69 | @@ -252,10 +257,9 @@ |
70 | tc_url=self.tc_url, help_text=self.help_text, |
71 | window_id=self.window_id, login_only=login_only) |
72 | |
73 | - self.gui.connect(gui.SIG_LOGIN_SUCCEEDED, self._login_success_cb) |
74 | - self.gui.connect(gui.SIG_REGISTRATION_SUCCEEDED, |
75 | - self._login_success_cb) |
76 | - self.gui.connect(gui.SIG_USER_CANCELATION, self._auth_denial_cb) |
77 | + self.gui.login_success_callback = self._login_success_cb |
78 | + self.gui.registration_success_callback = self._login_success_cb |
79 | + self.gui.user_cancellation_callback = self._auth_denial_cb |
80 | |
81 | @handle_failures(msg='Problem while retrieving credentials') |
82 | @inlineCallbacks |
83 | @@ -265,33 +269,22 @@ |
84 | if token is not None and len(token) > 0: |
85 | self.success_cb(token) |
86 | elif token == {}: |
87 | - gobject.idle_add(self._show_ui, login_only) |
88 | + self._show_ui(login_only) |
89 | else: |
90 | # something went wrong with find_credentials, already handled. |
91 | logger.info('_login_or_register: call to "find_credentials" went ' |
92 | 'wrong, and was already handled.') |
93 | |
94 | def error_cb(self, error_dict): |
95 | - """Handle error. |
96 | - |
97 | - Notify the caller and finish the GUI with error. |
98 | - |
99 | - """ |
100 | + """Handle error and notify the caller.""" |
101 | + logger.error('Calling error callback at %r (error is %r).', |
102 | + self._error_cb, error_dict) |
103 | self._error_cb(self.app_name, error_dict) |
104 | - if self.gui is not None: |
105 | - self.gui.finish_error(error=error_dict) |
106 | - self.gui = None |
107 | |
108 | def success_cb(self, creds): |
109 | - """Handle success. |
110 | - |
111 | - Notify the caller and finish the GUI with success. |
112 | - |
113 | - """ |
114 | + """Handle success and notify the caller.""" |
115 | + logger.debug('Calling success callback at %r.', self._success_cb) |
116 | self._success_cb(self.app_name, creds) |
117 | - if self.gui is not None: |
118 | - self.gui.finish_success() |
119 | - self.gui = None |
120 | |
121 | @handle_failures(msg='Problem while retrieving credentials') |
122 | @inlineCallbacks |
123 | |
124 | === modified file 'ubuntu_sso/gtk/gui.py' |
125 | --- ubuntu_sso/gtk/gui.py 2010-11-29 16:04:26 +0000 |
126 | +++ ubuntu_sso/gtk/gui.py 2011-01-02 03:31:12 +0000 |
127 | @@ -30,11 +30,11 @@ |
128 | |
129 | import dbus |
130 | import gettext |
131 | -import gobject |
132 | import gtk |
133 | import xdg |
134 | |
135 | from dbus.mainloop.glib import DBusGMainLoop |
136 | +from twisted.internet.defer import inlineCallbacks |
137 | |
138 | from ubuntu_sso import (DBUS_ACCOUNT_PATH, DBUS_BUS_NAME, DBUS_IFACE_USER_NAME, |
139 | NO_OP) |
140 | @@ -72,20 +72,6 @@ |
141 | HELP_TEXT_COLOR = gtk.gdk.Color("#bfbfbf") |
142 | WARNING_TEXT_COLOR = gtk.gdk.Color("red") |
143 | |
144 | -SIG_LOGIN_SUCCEEDED = 'login-succeeded' |
145 | -SIG_REGISTRATION_SUCCEEDED = 'registration-succeeded' |
146 | -SIG_USER_CANCELATION = 'user-cancelation' |
147 | - |
148 | -SIGNAL_ARGUMENTS = [ |
149 | - (SIG_LOGIN_SUCCEEDED, (gobject.TYPE_STRING, gobject.TYPE_STRING,)), |
150 | - (SIG_REGISTRATION_SUCCEEDED, (gobject.TYPE_STRING, gobject.TYPE_STRING,)), |
151 | - (SIG_USER_CANCELATION, (gobject.TYPE_STRING,)), |
152 | -] |
153 | - |
154 | -for sig, sig_args in SIGNAL_ARGUMENTS: |
155 | - gobject.signal_new(sig, gtk.Window, gobject.SIGNAL_RUN_FIRST, |
156 | - gobject.TYPE_NONE, sig_args) |
157 | - |
158 | |
159 | def get_data_dir(): |
160 | """Build absolute path to the 'data' directory.""" |
161 | @@ -198,7 +184,7 @@ |
162 | |
163 | |
164 | class UbuntuSSOClientGUI(object): |
165 | - """Ubuntu single sign on GUI.""" |
166 | + """Ubuntu single sign-on GUI.""" |
167 | |
168 | CAPTCHA_SOLUTION_ENTRY = _('Type the characters above') |
169 | CAPTCHA_LOAD_ERROR = _('There was a problem getting the captcha, ' |
170 | @@ -256,7 +242,7 @@ |
171 | CAPTCHA_RELOAD_TOOLTIP = _('Reload') |
172 | |
173 | def __init__(self, app_name, tc_url='', help_text='', |
174 | - window_id=0, login_only=False, close_callback=None): |
175 | + window_id=0, login_only=False): |
176 | """Create the GUI and initialize widgets.""" |
177 | gtk.link_button_set_uri_hook(NO_OP) |
178 | |
179 | @@ -270,7 +256,12 @@ |
180 | self.tc_url = tc_url |
181 | self.help_text = help_text |
182 | self.login_only = login_only |
183 | - self.close_callback = close_callback |
184 | + |
185 | + self.close_callback = NO_OP |
186 | + self.login_success_callback = NO_OP |
187 | + self.registration_success_callback = NO_OP |
188 | + self.user_cancellation_callback = NO_OP |
189 | + |
190 | self.user_email = None |
191 | self.user_password = None |
192 | |
193 | @@ -730,17 +721,12 @@ |
194 | signal_name, handler, args, kwargs) |
195 | self.window.connect(signal_name, handler, *args, **kwargs) |
196 | |
197 | - def emit(self, *args, **kwargs): |
198 | - """Emit a signal proxing the main window.""" |
199 | - logger.debug('emit: args %r, kwargs, %r', args, kwargs) |
200 | - self.window.emit(*args, **kwargs) |
201 | - |
202 | def finish_success(self): |
203 | """The whole process was completed succesfully. Show success page.""" |
204 | self._done = True |
205 | self._set_current_page(self.success_vbox) |
206 | |
207 | - def finish_error(self, error): |
208 | + def finish_error(self): |
209 | """The whole process was not completed succesfully. Show error page.""" |
210 | self._done = True |
211 | self._set_current_page(self.error_vbox) |
212 | @@ -766,18 +752,17 @@ |
213 | if self.window is not None: |
214 | self.window.hide() |
215 | |
216 | - # process any pending events before emitting signals |
217 | + # process any pending events before callbacking with result |
218 | while gtk.events_pending(): |
219 | gtk.main_iteration() |
220 | |
221 | if not self._done: |
222 | - self.emit(SIG_USER_CANCELATION, self.app_name) |
223 | + self.user_cancellation_callback(self.app_name) |
224 | |
225 | # call user defined callback |
226 | - if self.close_callback is not None: |
227 | - logger.info('Calling custom close_callback %r with params %r, %r', |
228 | - self.close_callback, args, kwargs) |
229 | - self.close_callback(*args, **kwargs) |
230 | + logger.info('Calling custom close_callback %r with params %r, %r', |
231 | + self.close_callback, args, kwargs) |
232 | + self.close_callback(*args, **kwargs) |
233 | |
234 | def on_sign_in_button_clicked(self, *args, **kwargs): |
235 | """User wants to sign in, present the Login page.""" |
236 | @@ -1096,10 +1081,15 @@ |
237 | self._set_current_page(self.enter_details_vbox, warning_text=msg) |
238 | |
239 | @log_call |
240 | + @inlineCallbacks |
241 | def on_email_validated(self, app_name, email, *args, **kwargs): |
242 | """User email was successfully verified.""" |
243 | self._done = True |
244 | - self.emit(SIG_REGISTRATION_SUCCEEDED, self.app_name, email) |
245 | + result = yield self.registration_success_callback(self.app_name, email) |
246 | + if result == 0: |
247 | + self.finish_success() |
248 | + else: |
249 | + self.finish_error() |
250 | |
251 | @log_call |
252 | def on_email_validation_error(self, app_name, error, *args, **kwargs): |
253 | @@ -1112,10 +1102,15 @@ |
254 | self._set_current_page(self.verify_email_vbox, warning_text=msg) |
255 | |
256 | @log_call |
257 | + @inlineCallbacks |
258 | def on_logged_in(self, app_name, email, *args, **kwargs): |
259 | """User was successfully logged in.""" |
260 | self._done = True |
261 | - self.emit(SIG_LOGIN_SUCCEEDED, self.app_name, email) |
262 | + result = yield self.login_success_callback(self.app_name, email) |
263 | + if result == 0: |
264 | + self.finish_success() |
265 | + else: |
266 | + self.finish_error() |
267 | |
268 | @log_call |
269 | def on_login_error(self, app_name, error, *args, **kwargs): |
270 | |
271 | === modified file 'ubuntu_sso/gtk/tests/test_gui.py' |
272 | --- ubuntu_sso/gtk/tests/test_gui.py 2010-12-09 21:04:59 +0000 |
273 | +++ ubuntu_sso/gtk/tests/test_gui.py 2011-01-02 03:31:12 +0000 |
274 | @@ -517,6 +517,11 @@ |
275 | self.assertTrue(self._called, |
276 | 'close_callback was called when window was closed.') |
277 | |
278 | + def test_close_callback_if_not_set(self): |
279 | + """The close_callback is a no op if not set.""" |
280 | + self.ui.on_close_clicked() |
281 | + # no crash when close_callback is not set |
282 | + |
283 | def test_app_name_is_stored(self): |
284 | """The app_name is stored for further use.""" |
285 | self.assertIn(APP_NAME, self.ui.app_name) |
286 | @@ -555,18 +560,6 @@ |
287 | |
288 | self.assertEqual(self.ui.bus.callbacks, {}) |
289 | |
290 | - def test_close_callback(self): |
291 | - """A close_callback parameter is called when closing the window.""" |
292 | - ui = self.gui_class(close_callback=self._set_called, **self.kwargs) |
293 | - ui.on_close_clicked() |
294 | - self.assertTrue(self._called, 'close_callback was called on close.') |
295 | - |
296 | - def test_close_callback_if_none(self): |
297 | - """A close_callback parameter is not called if is None.""" |
298 | - ui = self.gui_class(close_callback=None, **self.kwargs) |
299 | - ui.on_close_clicked() |
300 | - # no crash when close_callback is None |
301 | - |
302 | def test_pages_are_packed_into_container(self): |
303 | """All the pages are packed in the main container.""" |
304 | children = self.ui.content.get_children() |
305 | @@ -620,8 +613,7 @@ |
306 | buttons = filter(lambda name: 'cancel_button' in name or |
307 | 'close_button' in name, self.ui.widgets) |
308 | for name in buttons: |
309 | - self.ui = self.gui_class(close_callback=self._set_called, |
310 | - **self.kwargs) |
311 | + self.ui.close_callback = self._set_called |
312 | widget = getattr(self.ui, name) |
313 | widget.clicked() |
314 | self.assertEqual(self._called, ((widget,), {}), msg % name) |
315 | @@ -661,7 +653,7 @@ |
316 | |
317 | def test_finish_error_shows_error_page(self): |
318 | """When calling 'finish_error' the error page is shown.""" |
319 | - self.ui.finish_error(error=self.error) |
320 | + self.ui.finish_error() |
321 | self.assert_pages_visibility(finish=True) |
322 | self.assertEqual(self.ui.ERROR, self.ui.finish_vbox.label.get_text()) |
323 | |
324 | @@ -1164,10 +1156,10 @@ |
325 | self.click_verify_email_with_valid_data() |
326 | self.assert_warnings_visibility() |
327 | |
328 | - def test_on_email_validated_shows_processing_page(self): |
329 | - """On email validated the procesing page is still shown.""" |
330 | + def test_on_email_validated_shows_finish_page(self): |
331 | + """On email validated the finish page is shown.""" |
332 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
333 | - self.assert_pages_visibility(processing=True) |
334 | + self.assert_pages_visibility(finish=True) |
335 | |
336 | def test_on_email_validated_does_not_clear_the_help_text(self): |
337 | """On email validated the help text is not removed.""" |
338 | @@ -1227,6 +1219,32 @@ |
339 | self.ui.verify_token_button.clicked() |
340 | self.assertTrue(self._called) |
341 | |
342 | + def test_after_registration_success_finish_success(self): |
343 | + """After REGISTRATION_SUCCESS is called, finish_success is called. |
344 | + |
345 | + Only when REGISTRATION_SUCCESS returns 0. |
346 | + |
347 | + """ |
348 | + self.patch(self.ui, 'finish_success', self._set_called) |
349 | + self.ui.registration_success_callback = lambda app, email: 0 |
350 | + |
351 | + self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
352 | + |
353 | + self.assertEqual(self._called, ((), {})) |
354 | + |
355 | + def test_after_registration_error_finish_error(self): |
356 | + """After REGISTRATION_SUCCESS is called, finish_error is called. |
357 | + |
358 | + Only when REGISTRATION_SUCCESS returns a non-zero value. |
359 | + |
360 | + """ |
361 | + self.patch(self.ui, 'finish_error', self._set_called) |
362 | + self.ui.registration_success_callback = lambda app, email: -1 |
363 | + |
364 | + self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
365 | + |
366 | + self.assertEqual(self._called, ((), {})) |
367 | + |
368 | |
369 | class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase): |
370 | """Test suite for the user registration validation (verify email page).""" |
371 | @@ -1480,11 +1498,11 @@ |
372 | self.click_connect_with_valid_data() |
373 | self.assert_pages_visibility(processing=True) |
374 | |
375 | - def test_on_logged_in_morphs_to_processing_page(self): |
376 | - """When user logged in the processing page is still shown.""" |
377 | + def test_on_logged_in_morphs_to_finish_page(self): |
378 | + """When user logged in the finish page is shown.""" |
379 | self.click_connect_with_valid_data() |
380 | self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
381 | - self.assert_pages_visibility(processing=True) |
382 | + self.assert_pages_visibility(finish=True) |
383 | |
384 | def test_on_login_error_morphs_to_login_page(self): |
385 | """On user login error, the previous page is shown.""" |
386 | @@ -1541,6 +1559,32 @@ |
387 | self.assertEqual(self.ui.user_email, EMAIL) |
388 | self.assertEqual(self.ui.user_password, PASSWORD) |
389 | |
390 | + def test_after_login_success_finish_success(self): |
391 | + """After LOGIN_SUCCESSFULL is called, finish_success is called. |
392 | + |
393 | + Only when LOGIN_SUCCESSFULL returns 0. |
394 | + |
395 | + """ |
396 | + self.patch(self.ui, 'finish_success', self._set_called) |
397 | + self.ui.login_success_callback = lambda app, email: 0 |
398 | + |
399 | + self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
400 | + |
401 | + self.assertEqual(self._called, ((), {})) |
402 | + |
403 | + def test_after_login_error_finish_error(self): |
404 | + """After LOGIN_SUCCESSFULL is called, finish_error is called. |
405 | + |
406 | + Only when LOGIN_SUCCESSFULL returns a non-zero value. |
407 | + |
408 | + """ |
409 | + self.patch(self.ui, 'finish_error', self._set_called) |
410 | + self.ui.login_success_callback = lambda app, email: -1 |
411 | + |
412 | + self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
413 | + |
414 | + self.assertEqual(self._called, ((), {})) |
415 | + |
416 | |
417 | class LoginValidationTestCase(UbuntuSSOClientTestCase): |
418 | """Test suite for the user login validation.""" |
419 | @@ -2060,119 +2104,144 @@ |
420 | self.assertEqual(self.ui.help_label.get_text(), HELP_TEXT) |
421 | |
422 | |
423 | -class SignalsTestCase(UbuntuSSOClientTestCase): |
424 | - """Test the GTK signal emission.""" |
425 | +class CallbacksTestCase(UbuntuSSOClientTestCase): |
426 | + """Test the GTK callback calls.""" |
427 | + |
428 | + LOGIN_SUCCESSFULL = 'login_success_callback' |
429 | + REGISTRATION_SUCCESS = 'registration_success_callback' |
430 | + USER_CANCELLATION = 'user_cancellation_callback' |
431 | |
432 | def setUp(self): |
433 | """Init.""" |
434 | - super(SignalsTestCase, self).setUp() |
435 | + super(CallbacksTestCase, self).setUp() |
436 | self._called = {} |
437 | - for sig_name, _ in gui.SIGNAL_ARGUMENTS: |
438 | - self.ui.connect(sig_name, self._set_called, sig_name) |
439 | - |
440 | - def _set_called(self, widget, *args, **kwargs): |
441 | - """Keep trace of signals emition.""" |
442 | + for name in (self.LOGIN_SUCCESSFULL, |
443 | + self.REGISTRATION_SUCCESS, |
444 | + self.USER_CANCELLATION): |
445 | + setattr(self.ui, name, self._set_called(name)) |
446 | + |
447 | + def _set_called(self, callback_name): |
448 | + """Keep trace of callbacks calls.""" |
449 | + |
450 | # pylint: disable=W0221 |
451 | - self._called[args[-1]] = (widget, args[:-1], kwargs) |
452 | - |
453 | - def test_closing_main_window_sends_outcome_as_signal(self): |
454 | - """A signal is sent when closing the main window.""" |
455 | + |
456 | + def inner(*args, **kwargs): |
457 | + """Store arguments used in this call.""" |
458 | + self._called[callback_name] = args |
459 | + |
460 | + return inner |
461 | + |
462 | + def test_closing_main_window(self): |
463 | + """When closing the main window, USER_CANCELLATION is called.""" |
464 | self.ui.window.emit('delete-event', gtk.gdk.Event(gtk.gdk.DELETE)) |
465 | - expected = (self.ui.window, (APP_NAME,), {}) |
466 | - self.assertEqual(self._called[gui.SIG_USER_CANCELATION], expected) |
467 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
468 | |
469 | - def test_every_cancel_emits_proper_signal(self): |
470 | - """Clicking on any cancel button, 'user-cancelation' signal is sent.""" |
471 | - sig = gui.SIG_USER_CANCELATION |
472 | - msg = 'user-cancelation is emitted when "%s" is clicked.' |
473 | + def test_every_cancel_calls_proper_callback(self): |
474 | + """When any cancel button is clicked, USER_CANCELLATION is called.""" |
475 | + msg = 'user_cancellation_callback is called when "%s" is clicked.' |
476 | buttons = filter(lambda name: 'cancel_button' in name, self.ui.widgets) |
477 | for name in buttons: |
478 | - self.ui = self.gui_class(**self.kwargs) |
479 | - self.ui.connect(sig, self._set_called, sig) |
480 | widget = getattr(self.ui, name) |
481 | widget.clicked() |
482 | - expected_args = (self.ui.window, (APP_NAME,), {}) |
483 | - self.assertEqual(self._called[sig], expected_args, msg % name) |
484 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,), |
485 | + msg % name) |
486 | self._called = {} |
487 | |
488 | - def test_on_user_registration_error_proper_signal_is_emitted(self): |
489 | - """On UserRegistrationError, SIG_USER_CANCELATION signal is sent.""" |
490 | + def test_on_user_registration_error_proper_callback_is_called(self): |
491 | + """On UserRegistrationError, USER_CANCELLATION is called.""" |
492 | self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error) |
493 | self.ui.on_close_clicked() |
494 | - expected = (self.ui.window, (APP_NAME,), {}) |
495 | - self.assertEqual(expected, |
496 | - self._called[gui.SIG_USER_CANCELATION]) |
497 | - |
498 | - def test_on_email_validated_proper_signals_is_emitted(self): |
499 | - """On EmailValidated, 'registration-succeeded' signal is sent.""" |
500 | + |
501 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
502 | + |
503 | + def test_on_email_validated_proper_callback_is_called(self): |
504 | + """On EmailValidated, REGISTRATION_SUCCESS is called.""" |
505 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
506 | self.ui.on_close_clicked() |
507 | - self.assertEqual((self.ui.window, (APP_NAME, EMAIL), {}), |
508 | - self._called[gui.SIG_REGISTRATION_SUCCEEDED]) |
509 | - |
510 | - def test_on_email_validation_error_proper_signals_is_emitted(self): |
511 | - """On EmailValidationError, SIG_USER_CANCELATION signal is sent.""" |
512 | + |
513 | + self.assertEqual(self._called[self.REGISTRATION_SUCCESS], |
514 | + (APP_NAME, EMAIL)) |
515 | + |
516 | + def test_on_email_validation_error_proper_callback_is_called(self): |
517 | + """On EmailValidationError, USER_CANCELLATION is called.""" |
518 | self.ui.on_email_validation_error(app_name=APP_NAME, error=self.error) |
519 | self.ui.on_close_clicked() |
520 | - expected = (self.ui.window, (APP_NAME,), {}) |
521 | - self.assertEqual(expected, |
522 | - self._called[gui.SIG_USER_CANCELATION]) |
523 | - |
524 | - def test_on_logged_in_proper_signals_is_emitted(self): |
525 | - """On LoggedIn, 'login-succeeded' signal is sent.""" |
526 | + |
527 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
528 | + |
529 | + def test_on_logged_in_proper_callback_is_called(self): |
530 | + """On LoggedIn, LOGIN_SUCCESSFULL is called.""" |
531 | self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
532 | self.ui.on_close_clicked() |
533 | - self.assertEqual((self.ui.window, (APP_NAME, EMAIL), {}), |
534 | - self._called[gui.SIG_LOGIN_SUCCEEDED]) |
535 | - |
536 | - def test_on_login_error_proper_signals_is_emitted(self): |
537 | - """On LoginError, SIG_USER_CANCELATION signal is sent.""" |
538 | + |
539 | + self.assertEqual(self._called[self.LOGIN_SUCCESSFULL], |
540 | + (APP_NAME, EMAIL)) |
541 | + |
542 | + def test_on_login_error_proper_callback_is_called(self): |
543 | + """On LoginError, USER_CANCELLATION is called.""" |
544 | self.click_connect_with_valid_data() |
545 | self.ui.on_login_error(app_name=APP_NAME, error=self.error) |
546 | self.ui.on_close_clicked() |
547 | - expected = (self.ui.window, (APP_NAME,), {}) |
548 | - self.assertEqual(expected, |
549 | - self._called[gui.SIG_USER_CANCELATION]) |
550 | - |
551 | - def test_registration_successfull_even_if_prior_registration_error(self): |
552 | - """Only one signal is sent with the final outcome.""" |
553 | + |
554 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
555 | + |
556 | + def test_registration_success_even_if_prior_registration_error(self): |
557 | + """Only one callback is called with the final outcome. |
558 | + |
559 | + When the user successfully registers, REGISTRATION_SUCCESS is |
560 | + called even if there were errors before. |
561 | + |
562 | + """ |
563 | self.click_join_with_valid_data() |
564 | self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error) |
565 | self.click_join_with_valid_data() |
566 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
567 | self.ui.on_close_clicked() |
568 | |
569 | - self.assertEqual(len(self._called), 1) |
570 | - self.assertTrue(gui.SIG_REGISTRATION_SUCCEEDED in self._called) |
571 | - |
572 | - def test_login_successfull_even_if_prior_login_error(self): |
573 | - """Only one signal is sent with the final outcome.""" |
574 | + self.assertEqual(self._called[self.REGISTRATION_SUCCESS], |
575 | + (APP_NAME, EMAIL)) |
576 | + |
577 | + def test_login_success_even_if_prior_login_error(self): |
578 | + """Only one callback is called with the final outcome. |
579 | + |
580 | + When the user successfully logins, LOGIN_SUCCESSFULL is called even if |
581 | + there were errors before. |
582 | + |
583 | + """ |
584 | self.click_connect_with_valid_data() |
585 | self.ui.on_login_error(app_name=APP_NAME, error=self.error) |
586 | self.click_connect_with_valid_data() |
587 | self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
588 | self.ui.on_close_clicked() |
589 | |
590 | - self.assertEqual(len(self._called), 1) |
591 | - self.assertTrue(gui.SIG_LOGIN_SUCCEEDED in self._called) |
592 | + self.assertEqual(self._called[self.LOGIN_SUCCESSFULL], |
593 | + (APP_NAME, EMAIL)) |
594 | |
595 | def test_user_cancelation_even_if_prior_registration_error(self): |
596 | - """Only one signal is sent with the final outcome.""" |
597 | + """Only one callback is called with the final outcome. |
598 | + |
599 | + When the user closes the window, USER_CANCELLATION is called even if |
600 | + there were registration errors before. |
601 | + |
602 | + """ |
603 | self.click_join_with_valid_data() |
604 | self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error) |
605 | self.ui.join_cancel_button.clicked() |
606 | |
607 | - self.assertEqual(len(self._called), 1) |
608 | - self.assertTrue(gui.SIG_USER_CANCELATION in self._called) |
609 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
610 | |
611 | def test_user_cancelation_even_if_prior_login_error(self): |
612 | - """Only one signal is sent with the final outcome.""" |
613 | + """Only one callback is called with the final outcome. |
614 | + |
615 | + When the user closes the window, USER_CANCELLATION is called even if |
616 | + there were login errors before. |
617 | + |
618 | + """ |
619 | self.click_connect_with_valid_data() |
620 | self.ui.on_login_error(app_name=APP_NAME, error=self.error) |
621 | self.ui.login_cancel_button.clicked() |
622 | |
623 | - self.assertEqual(len(self._called), 1) |
624 | - self.assertTrue(gui.SIG_USER_CANCELATION in self._called) |
625 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
626 | |
627 | |
628 | class DefaultButtonsTestCase(UbuntuSSOClientTestCase): |
629 | |
630 | === modified file 'ubuntu_sso/tests/test_credentials.py' |
631 | --- ubuntu_sso/tests/test_credentials.py 2010-12-20 14:29:18 +0000 |
632 | +++ ubuntu_sso/tests/test_credentials.py 2011-01-02 03:31:12 +0000 |
633 | @@ -21,8 +21,6 @@ |
634 | import logging |
635 | import urllib |
636 | |
637 | -import gobject |
638 | - |
639 | from twisted.internet import defer |
640 | from twisted.internet.defer import inlineCallbacks |
641 | from twisted.trial.unittest import TestCase, FailTest |
642 | @@ -60,10 +58,6 @@ |
643 | WINDOW_ID_KEY: WINDOW_ID, |
644 | } |
645 | |
646 | -SIG_LOGIN_SUCCEEDED = '1' |
647 | -SIG_REGISTRATION_SUCCEEDED = '2' |
648 | -SIG_USER_CANCELATION = '3' |
649 | - |
650 | |
651 | class SampleMiscException(Exception): |
652 | """An error to be used while testing.""" |
653 | @@ -82,17 +76,9 @@ |
654 | def __init__(self, *args, **kwargs): |
655 | self.args = args |
656 | self.kwargs = kwargs |
657 | - self.signals = [] |
658 | - self.methods = [] |
659 | - self.connect = lambda *a: self.signals.append(a) |
660 | - |
661 | - def finish_success(self): |
662 | - """Fake the success finish.""" |
663 | - self.methods.append('finish_success') |
664 | - |
665 | - def finish_error(self, error): |
666 | - """Fake the error finish.""" |
667 | - self.methods.append(('finish_error', error)) |
668 | + self.login_success_callback = None |
669 | + self.registration_success_callback = None |
670 | + self.user_cancellation_callback = None |
671 | |
672 | |
673 | class BasicTestCase(TestCase): |
674 | @@ -100,7 +86,6 @@ |
675 | |
676 | def setUp(self): |
677 | """Init.""" |
678 | - self.patch(gobject, 'idle_add', lambda f, *a, **kw: f(*a, **kw)) |
679 | self._called = False # helper |
680 | |
681 | self.memento = MementoHandler() |
682 | @@ -226,45 +211,21 @@ |
683 | |
684 | self.assertEqual(getattr(self.obj, UI_MODULE_KEY), GTK_GUI_MODULE) |
685 | |
686 | - def test_success_cb_gui_none(self): |
687 | - """Success callback calls the caller but not the GUI.""" |
688 | - self.obj.gui = None |
689 | - self.obj.success_cb(creds=TOKEN) |
690 | - |
691 | - self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}), |
692 | - 'caller _success_cb was called.') |
693 | - |
694 | - def test_success_cb_gui_not_none(self): |
695 | - """Success callback calls the caller and finish the GUI.""" |
696 | - self.obj.gui = ui = FakedClientGUI(APP_NAME) |
697 | - self.obj.success_cb(creds=TOKEN) |
698 | - |
699 | - self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}), |
700 | - 'caller _success_cb was called.') |
701 | - self.assertEqual(ui.methods, ['finish_success'], |
702 | - 'GUI finish_success was called.') |
703 | - self.assertTrue(self.obj.gui is None, 'gui is reset to None.') |
704 | - |
705 | - def test_error_cb_gui_none(self): |
706 | - """Error callback calls the caller but not the GUI.""" |
707 | - self.obj.gui = None |
708 | - error_dict = {'foo': 'bar'} |
709 | - self.obj.error_cb(error_dict=error_dict) |
710 | - |
711 | - self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}), |
712 | - 'caller _error_cb was called.') |
713 | - |
714 | - def test_error_cb_gui_not_none(self): |
715 | - """Error callback calls the caller and finish the GUI.""" |
716 | - self.obj.gui = ui = FakedClientGUI(APP_NAME) |
717 | - error_dict = {'foo': 'bar'} |
718 | - self.obj.error_cb(error_dict=error_dict) |
719 | - |
720 | - self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}), |
721 | - 'caller _error_cb was called.') |
722 | - self.assertEqual(ui.methods, [('finish_error', error_dict)], |
723 | - 'GUI finish_error was called.') |
724 | - self.assertTrue(self.obj.gui is None, 'gui is reset to None.') |
725 | + def test_success_cb(self): |
726 | + """Success callback calls the caller.""" |
727 | + self.obj.gui = None |
728 | + self.obj.success_cb(creds=TOKEN) |
729 | + |
730 | + self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}), |
731 | + 'caller _success_cb was called.') |
732 | + |
733 | + def test_error_cb(self): |
734 | + """Error callback calls the caller.""" |
735 | + error_dict = {'foo': 'bar'} |
736 | + self.obj.error_cb(error_dict=error_dict) |
737 | + |
738 | + self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}), |
739 | + 'caller _error_cb was called.') |
740 | |
741 | |
742 | class CredentialsLoginSuccessTestCase(CredentialsTestCase): |
743 | @@ -276,12 +237,12 @@ |
744 | self.patch(credentials.Keyring, 'get_credentials', |
745 | lambda kr, app: defer.succeed(None)) |
746 | |
747 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
748 | - email=EMAIL) |
749 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
750 | |
751 | msg = 'Creds are empty! This should not happen' |
752 | self.assert_error_cb_called(msg='Problem while retrieving credentials', |
753 | detailed_error=AssertionError(msg)) |
754 | + self.assertEqual(result, None) |
755 | |
756 | @inlineCallbacks |
757 | def test_cred_error(self): |
758 | @@ -290,11 +251,11 @@ |
759 | self.patch(credentials.Keyring, 'get_credentials', |
760 | lambda kr, app: defer.fail(expected_error)) |
761 | |
762 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
763 | - email=EMAIL) |
764 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
765 | |
766 | msg = 'Problem while retrieving credentials' |
767 | self.assert_error_cb_called(msg=msg, detailed_error=expected_error) |
768 | + self.assertEqual(result, None) |
769 | self.assertTrue(self.memento.check_exception(SampleMiscException)) |
770 | |
771 | @inlineCallbacks |
772 | @@ -304,10 +265,10 @@ |
773 | lambda kr, app: defer.succeed(TOKEN)) |
774 | self.patch(self.obj, '_ping_url', lambda *a, **kw: 200) |
775 | |
776 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
777 | - email=EMAIL) |
778 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
779 | |
780 | self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {})) |
781 | + self.assertEqual(result, 0) |
782 | |
783 | @inlineCallbacks |
784 | def test_ping_error(self): |
785 | @@ -324,12 +285,12 @@ |
786 | self.patch(self.obj, 'clear_credentials', |
787 | lambda: setattr(self, '_cred_cleared', True)) |
788 | |
789 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
790 | - email=EMAIL) |
791 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
792 | |
793 | # error cb called correctly |
794 | msg = 'Problem opening the ping_url' |
795 | self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error)) |
796 | + self.assertEqual(result, None) |
797 | |
798 | # credentials cleared |
799 | self.assertTrue(self._cred_cleared) |
800 | @@ -349,8 +310,7 @@ |
801 | self.patch(self.obj, '_ping_url', |
802 | lambda *a, **kw: setattr(self, '_url_pinged', (a, kw))) |
803 | |
804 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
805 | - email=EMAIL) |
806 | + yield self.obj._login_success_cb(APP_NAME, EMAIL) |
807 | |
808 | self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {})) |
809 | |
810 | @@ -366,10 +326,10 @@ |
811 | self.patch(self.obj, 'clear_credentials', self._set_called) |
812 | self.obj.ping_url = None |
813 | |
814 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
815 | - email=EMAIL) |
816 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
817 | |
818 | self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {})) |
819 | + self.assertEqual(result, 0) |
820 | |
821 | |
822 | class CredentialsAuthDeniedTestCase(CredentialsTestCase): |
823 | @@ -377,10 +337,9 @@ |
824 | |
825 | def test_auth_denial_cb(self): |
826 | """On auth denied, self.denial_cb is called.""" |
827 | - self.obj.gui = ui = FakedClientGUI(APP_NAME) |
828 | - self.obj._auth_denial_cb(dialog=None, app_name=APP_NAME) |
829 | + self.obj._auth_denial_cb(app_name=APP_NAME) |
830 | + |
831 | self.assertEqual(self._called, (('denial', APP_NAME), {})) |
832 | - self.assertEqual(ui.methods, [], 'no GUI method was called.') |
833 | |
834 | |
835 | class PingUrlTestCase(CredentialsTestCase): |
836 | @@ -401,24 +360,29 @@ |
837 | |
838 | self.patch(credentials.urllib2, 'urlopen', faked_urlopen) |
839 | |
840 | + @inlineCallbacks |
841 | def test_ping_url_if_url_is_none(self): |
842 | """self.ping_url is opened.""" |
843 | self.patch(credentials.urllib2, 'urlopen', self.fail) |
844 | self.obj.ping_url = None |
845 | - self.obj._ping_url(app_name=APP_NAME, email=EMAIL, credentials=TOKEN) |
846 | + yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL, |
847 | + credentials=TOKEN) |
848 | # no failure |
849 | |
850 | + @inlineCallbacks |
851 | def test_ping_url(self): |
852 | """On auth success, self.ping_url is opened.""" |
853 | - self.obj._ping_url(app_name=APP_NAME, email=EMAIL, credentials=TOKEN) |
854 | + yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL, |
855 | + credentials=TOKEN) |
856 | |
857 | self.assertIsInstance(self._request, credentials.urllib2.Request) |
858 | self.assertEqual(self._request.get_full_url(), |
859 | self.obj.ping_url + EMAIL) |
860 | |
861 | + @inlineCallbacks |
862 | def test_request_is_signed_with_credentials(self): |
863 | """The http request to self.ping_url is signed with the credentials.""" |
864 | - result = self.obj._ping_url(APP_NAME, EMAIL, TOKEN) |
865 | + result = yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN) |
866 | headers = self._request.headers |
867 | |
868 | self.assertIn('Authorization', headers) |
869 | @@ -431,12 +395,13 @@ |
870 | self.assertIn(expected, oauth_stuff) |
871 | self.assertEqual(result, 200) |
872 | |
873 | + @inlineCallbacks |
874 | def test_ping_url_error(self): |
875 | """Exception is handled if ping fails.""" |
876 | error = 'Blu' |
877 | self.patch(credentials.urllib2, 'urlopen', lambda r: self.fail(error)) |
878 | |
879 | - self.obj._ping_url(APP_NAME, EMAIL, TOKEN) |
880 | + yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN) |
881 | |
882 | msg = 'Problem opening the ping_url' |
883 | self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error)) |
884 | @@ -592,17 +557,17 @@ |
885 | |
886 | @inlineCallbacks |
887 | def test_connects_gui_signals(self): |
888 | - """Outcome signals from GUI are connected to callbacks.""" |
889 | + """GUI callbacks are properly connected.""" |
890 | self.patch(credentials.Keyring, 'get_credentials', |
891 | lambda kr, app: defer.succeed(None)) |
892 | - |
893 | - expected = [ |
894 | - (SIG_LOGIN_SUCCEEDED, self.obj._login_success_cb), |
895 | - (SIG_REGISTRATION_SUCCEEDED, self.obj._login_success_cb), |
896 | - (SIG_USER_CANCELATION, self.obj._auth_denial_cb), |
897 | - ] |
898 | yield getattr(self.obj, self.operation)() |
899 | - self.assertEqual(self.obj.gui.signals, expected) |
900 | + |
901 | + self.assertEqual(self.obj.gui.login_success_callback, |
902 | + self.obj._login_success_cb) |
903 | + self.assertEqual(self.obj.gui.registration_success_callback, |
904 | + self.obj._login_success_cb) |
905 | + self.assertEqual(self.obj.gui.user_cancellation_callback, |
906 | + self.obj._auth_denial_cb) |
907 | |
908 | @inlineCallbacks |
909 | def test_gui_is_created(self): |
910 | |
911 | === modified file 'ubuntu_sso/tests/test_main.py' |
912 | --- ubuntu_sso/tests/test_main.py 2011-01-02 03:31:12 +0000 |
913 | +++ ubuntu_sso/tests/test_main.py 2011-01-02 03:31:12 +0000 |
914 | @@ -702,6 +702,7 @@ |
915 | class CredentialsManagementTestCase(TestCase): |
916 | """Tests for the CredentialsManagement DBus interface.""" |
917 | |
918 | + timeout = 2 |
919 | base_args = {HELP_TEXT_KEY: HELP_TEXT, PING_URL_KEY: PING_URL, |
920 | TC_URL_KEY: TC_URL, WINDOW_ID_KEY: WINDOW_ID, |
921 | UI_CLASS_KEY: 'SuperUI', UI_MODULE_KEY: 'foo.bar.baz', |
922 | @@ -739,10 +740,8 @@ |
923 | self.assertIsInstance(self.client, ubuntu_sso.main.dbus.service.Object) |
924 | |
925 | |
926 | -class CredentialsManagementFindClearTestCase(CredentialsManagementTestCase): |
927 | - """Tests for the CredentialsManagement find/clear/store methods.""" |
928 | - |
929 | - timeout = 5 |
930 | +class CredentialsManagementFindTestCase(CredentialsManagementTestCase): |
931 | + """Tests for the CredentialsManagement find method.""" |
932 | |
933 | def test_find_credentials(self): |
934 | """The credentials are asked and returned in signals.""" |
935 | @@ -808,6 +807,10 @@ |
936 | self.client.find_credentials(APP_NAME, self.args) |
937 | return d |
938 | |
939 | + |
940 | +class CredentialsManagementClearTestCase(CredentialsManagementTestCase): |
941 | + """Tests for the CredentialsManagement clear method.""" |
942 | + |
943 | def test_clear_credentials(self): |
944 | """The credentials are removed.""" |
945 | self.create_mock_backend().clear_credentials() |
946 | @@ -840,6 +843,10 @@ |
947 | self.client.clear_credentials(APP_NAME, self.args) |
948 | return d |
949 | |
950 | + |
951 | +class CredentialsManagementStoreTestCase(CredentialsManagementTestCase): |
952 | + """Tests for the CredentialsManagement store method.""" |
953 | + |
954 | def test_store_credentials(self): |
955 | """The credentials are stored and the outcome is a signal.""" |
956 | self.create_mock_backend().store_credentials(TOKEN) |
I cannot see any problem with this code. +1