Merge lp:~nataliabidart/ubuntu/maverick/ubuntu-sso-client/ubuntu-sso-client-0.99.4 into lp:ubuntu/maverick/ubuntu-sso-client
- Maverick (10.10)
- ubuntu-sso-client-0.99.4
- Merge into maverick
Proposed by
Natalia Bidart
Status: | Merged |
---|---|
Merged at revision: | 11 |
Proposed branch: | lp:~nataliabidart/ubuntu/maverick/ubuntu-sso-client/ubuntu-sso-client-0.99.4 |
Merge into: | lp:ubuntu/maverick/ubuntu-sso-client |
Diff against target: |
2354 lines (+1021/-331) 10 files modified
PKG-INFO (+1/-1) data/ui.glade (+38/-22) debian/changelog (+43/-0) setup.py (+4/-3) ubuntu_sso/gui.py (+183/-80) ubuntu_sso/keyring.py (+89/-38) ubuntu_sso/main.py (+22/-35) ubuntu_sso/tests/test_gui.py (+447/-50) ubuntu_sso/tests/test_keyring.py (+125/-29) ubuntu_sso/tests/test_main.py (+69/-73) |
To merge this branch: | bzr merge lp:~nataliabidart/ubuntu/maverick/ubuntu-sso-client/ubuntu-sso-client-0.99.4 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Development Team | Pending | ||
Review via email: mp+33969@code.launchpad.net |
Commit message
Description of the change
High priority bug fixes after UI freeze (v0.99.4).
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'PKG-INFO' |
2 | --- PKG-INFO 2010-08-26 01:07:41 +0000 |
3 | +++ PKG-INFO 2010-08-27 22:32:43 +0000 |
4 | @@ -1,6 +1,6 @@ |
5 | Metadata-Version: 1.1 |
6 | Name: ubuntu-sso-client |
7 | -Version: 0.99.3 |
8 | +Version: 0.99.4 |
9 | Summary: Ubuntu Single Sign-On client |
10 | Home-page: https://launchpad.net/ubuntu-sso-client |
11 | Author: Natalia Bidart |
12 | |
13 | === modified file 'data/ui.glade' |
14 | --- data/ui.glade 2010-08-26 01:07:41 +0000 |
15 | +++ data/ui.glade 2010-08-27 22:32:43 +0000 |
16 | @@ -60,6 +60,12 @@ |
17 | <property name="visible">True</property> |
18 | <property name="spacing">10</property> |
19 | <child> |
20 | + <placeholder/> |
21 | + </child> |
22 | + <child> |
23 | + <placeholder/> |
24 | + </child> |
25 | + <child> |
26 | <object class="GtkHBox" id="emails_hbox"> |
27 | <property name="visible">True</property> |
28 | <property name="spacing">5</property> |
29 | @@ -77,9 +83,6 @@ |
30 | </packing> |
31 | </child> |
32 | <child> |
33 | - <placeholder/> |
34 | - </child> |
35 | - <child> |
36 | <object class="GtkHBox" id="passwords_hbox"> |
37 | <property name="visible">True</property> |
38 | <property name="spacing">5</property> |
39 | @@ -107,9 +110,6 @@ |
40 | </packing> |
41 | </child> |
42 | <child> |
43 | - <placeholder/> |
44 | - </child> |
45 | - <child> |
46 | <object class="GtkHBox" id="hbox1"> |
47 | <property name="visible">True</property> |
48 | <child> |
49 | @@ -585,27 +585,43 @@ |
50 | <property name="visible">True</property> |
51 | <property name="spacing">10</property> |
52 | <child> |
53 | - <object class="GtkAlignment" id="alignment1"> |
54 | + <object class="GtkVBox" id="vbox2"> |
55 | <property name="visible">True</property> |
56 | - <property name="xscale">0</property> |
57 | - <property name="yscale">0</property> |
58 | - <child> |
59 | - <object class="GtkVBox" id="set_new_password_details_vbox"> |
60 | - <property name="visible">True</property> |
61 | - <property name="spacing">5</property> |
62 | - <child> |
63 | - <placeholder/> |
64 | - </child> |
65 | - <child> |
66 | - <placeholder/> |
67 | - </child> |
68 | - <child> |
69 | - <placeholder/> |
70 | - </child> |
71 | + <child> |
72 | + <object class="GtkAlignment" id="alignment1"> |
73 | + <property name="visible">True</property> |
74 | + <property name="xscale">0</property> |
75 | + <property name="yscale">0</property> |
76 | + <child> |
77 | + <object class="GtkVBox" id="set_new_password_details_vbox"> |
78 | + <property name="visible">True</property> |
79 | + <property name="spacing">5</property> |
80 | + <child> |
81 | + <placeholder/> |
82 | + </child> |
83 | + <child> |
84 | + <placeholder/> |
85 | + </child> |
86 | + <child> |
87 | + <placeholder/> |
88 | + </child> |
89 | + </object> |
90 | + </child> |
91 | + </object> |
92 | + <packing> |
93 | + <property name="position">0</property> |
94 | + </packing> |
95 | + </child> |
96 | + <child> |
97 | + <object class="GtkLabel" id="reset_password_help_label"> |
98 | + <property name="visible">True</property> |
99 | + <property name="label" translatable="yes">label</property> |
100 | + <property name="wrap">True</property> |
101 | </object> |
102 | </child> |
103 | </object> |
104 | <packing> |
105 | + <property name="padding">5</property> |
106 | <property name="position">0</property> |
107 | </packing> |
108 | </child> |
109 | |
110 | === modified file 'debian/changelog' |
111 | --- debian/changelog 2010-08-26 13:05:03 +0000 |
112 | +++ debian/changelog 2010-08-27 22:32:43 +0000 |
113 | @@ -1,3 +1,46 @@ |
114 | +ubuntu-sso-client (0.99.4-0ubuntu1) UNRELEASED; urgency=low |
115 | + |
116 | + * New upstream release: |
117 | + |
118 | + [ natalia.bidart@canonical.com ] |
119 | + * Validate form data for verify token page, request password token and set |
120 | + new password (LP: #625361). |
121 | + * Validate password strength on reset password page (LP: #616528). |
122 | + * Labels are not as wide as the parent windowm but a little bit less wide |
123 | + (LP: #625009). |
124 | + |
125 | + [ Alejandro J. Cura <alecu@canonical.com> ] |
126 | + * Store the credentials after the email validation step (LP: #625003) |
127 | + |
128 | + [ natalia.bidart@canonical.com |
129 | + * Every form can be submitted by activating the buttons and/or the entries |
130 | + (LP: #616421). |
131 | + |
132 | + [ David Planella <david.planella@ubuntu.com> ] |
133 | + * Make setup.py actually use python-distutils-extra, which will allow the |
134 | + .deb package to build the POT file required to import translations into |
135 | + Launchpad (LP: #624891). |
136 | + |
137 | + [ natalia.bidart@canonical.com ] |
138 | + * Errors from SSO servers are being shown now to users, matching |
139 | + error-specific to fields (LP: #616101). |
140 | + * Also, be robust when SSO server answer with a string where it's supposed |
141 | + to be a list (LP: #623447). |
142 | + |
143 | + [ Alejandro J. Cura <alecu@canonical.com> ] |
144 | + * Use the keyring unlocking gnomekeyring APIs (LP: #623622) |
145 | + * Search all keyrings for the credentials (LP: #624033) |
146 | + |
147 | + [ natalia.bidart@canonical.com ] |
148 | + * Customize "help_text" for the login only dialog (LP: #624097). |
149 | + * Label areas are as wide as the parent window (LP: #616551). |
150 | + |
151 | + [ Alejandro J. Cura <alecu@canonical.com> ] |
152 | + * The list of error strings as returned by the SSO webservice can't go thru |
153 | + DBus (LP: #624358). |
154 | + |
155 | + -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Fri, 27 Aug 2010 19:16:55 -0300 |
156 | + |
157 | ubuntu-sso-client (0.99.3-0ubuntu1) maverick; urgency=low |
158 | |
159 | * New upstream release: |
160 | |
161 | === modified file 'setup.py' |
162 | --- setup.py 2010-08-26 01:07:41 +0000 |
163 | +++ setup.py 2010-08-27 22:32:43 +0000 |
164 | @@ -21,6 +21,7 @@ |
165 | |
166 | try: |
167 | import DistUtilsExtra.auto |
168 | + from DistUtilsExtra.command import build_extra |
169 | except ImportError: |
170 | print >> sys.stderr, 'To build this program you need '\ |
171 | 'https://launchpad.net/python-distutils-extra' |
172 | @@ -36,7 +37,7 @@ |
173 | CLEANFILES = ['data/com.ubuntu.sso.service'] |
174 | |
175 | # XXX: This needs some serious cleanup |
176 | -class SSOBuild(build.build): |
177 | +class SSOBuild(build_extra.build_extra): |
178 | """Class to build the extra files.""" |
179 | |
180 | description = 'build extra files needed by ubuntu-sso-client' |
181 | @@ -61,7 +62,7 @@ |
182 | ) |
183 | |
184 | # Run the parent build command |
185 | - build.build.run(self) |
186 | + build_extra.build_extra.run(self) |
187 | |
188 | |
189 | class SSOClean(clean.clean): |
190 | @@ -81,7 +82,7 @@ |
191 | |
192 | DistUtilsExtra.auto.setup( |
193 | name='ubuntu-sso-client', |
194 | - version='0.99.3', |
195 | + version='0.99.4', |
196 | license='GPL v3', |
197 | author='Natalia Bidart', |
198 | author_email='natalia.bidart@canonical.com', |
199 | |
200 | === modified file 'ubuntu_sso/gui.py' |
201 | --- ubuntu_sso/gui.py 2010-08-26 01:07:41 +0000 |
202 | +++ ubuntu_sso/gui.py 2010-08-27 22:32:43 +0000 |
203 | @@ -168,6 +168,7 @@ |
204 | self.warning = warning_msg |
205 | self.set_property('secondary-icon-stock', gtk.STOCK_DIALOG_WARNING) |
206 | self.set_property('secondary-icon-sensitive', True) |
207 | + self.set_property('secondary-icon-activatable', False) |
208 | self.set_property('secondary-icon-tooltip-text', warning_msg) |
209 | |
210 | def clear_warning(self): |
211 | @@ -175,6 +176,7 @@ |
212 | self.warning = None |
213 | self.set_property('secondary-icon-stock', None) |
214 | self.set_property('secondary-icon-sensitive', False) |
215 | + self.set_property('secondary-icon-activatable', False) |
216 | self.set_property('secondary-icon-tooltip-text', None) |
217 | |
218 | |
219 | @@ -253,12 +255,10 @@ |
220 | builder.add_from_file(ui_filename) |
221 | builder.connect_signals(self) |
222 | |
223 | - # XXX: every button should have tests for 'activate' signal |
224 | - # see LP: #616421 |
225 | - |
226 | self.widgets = [] |
227 | self.warnings = [] |
228 | self.cancels = [] |
229 | + self.labels = [] |
230 | for obj in builder.get_objects(): |
231 | name = getattr(obj, 'name', None) |
232 | if name is None and isinstance(obj, gtk.Buildable): |
233 | @@ -275,6 +275,10 @@ |
234 | if 'cancel_button' in name: |
235 | obj.connect('clicked', self.on_close_clicked) |
236 | self.cancels.append(obj) |
237 | + if '_button' in name: |
238 | + obj.connect('activate', lambda w: w.clicked()) |
239 | + if 'label' in name: |
240 | + self.labels.append(obj) |
241 | |
242 | self.entries = ('name_entry', 'email1_entry', 'email2_entry', |
243 | 'password1_entry', 'password2_entry', |
244 | @@ -292,7 +296,6 @@ |
245 | assert getattr(self, name) is not None |
246 | |
247 | self.window.set_icon_name('ubuntu-logo') |
248 | - self.captcha_reload_button.set_tooltip_text(self.CAPTCHA_RELOAD_TOOLTIP) |
249 | |
250 | self.bus = dbus.SessionBus() |
251 | self.bus.add_signal_receiver = self._log(self.bus.add_signal_receiver) |
252 | @@ -316,17 +319,26 @@ |
253 | self._append_page(self._build_request_password_token_page()) |
254 | self._append_page(self._build_set_new_password_page()) |
255 | |
256 | + window_size = None |
257 | if not login_only: |
258 | + window_size = (550, 600) |
259 | self._append_page(self._build_enter_details_page()) |
260 | self._append_page(self._build_tc_page()) |
261 | self._append_page(self._build_verify_email_page()) |
262 | self.login_button.grab_focus() |
263 | self._set_current_page(self.enter_details_vbox) |
264 | else: |
265 | + window_size = (400, 350) |
266 | self.login_back_button.hide() |
267 | self.login_ok_button.grab_focus() |
268 | + self.login_vbox.help_text = help_text |
269 | self._set_current_page(self.login_vbox) |
270 | |
271 | + self.window.set_size_request(*window_size) |
272 | + size_req = (int(window_size[0] * 0.9), -1) |
273 | + for label in self.labels: |
274 | + label.set_size_request(*size_req) |
275 | + |
276 | self._setup_signals() |
277 | self._gtk_signal_log = [] |
278 | |
279 | @@ -487,16 +499,17 @@ |
280 | |
281 | self.enter_details_vbox.pack_start(self.name_entry, expand=False) |
282 | self.enter_details_vbox.reorder_child(self.name_entry, 0) |
283 | - self.captcha_solution_vbox.pack_start(self.captcha_solution_entry, |
284 | - expand=False) |
285 | - self.captcha_solution_vbox.reorder_child(self.captcha_solution_entry, |
286 | - 0) |
287 | - |
288 | - self.emails_hbox.pack_start(self.email1_entry) |
289 | - self.emails_hbox.pack_start(self.email2_entry) |
290 | - |
291 | - self.passwords_hbox.pack_start(self.password1_entry) |
292 | - self.passwords_hbox.pack_start(self.password2_entry) |
293 | + entry = self.captcha_solution_entry |
294 | + self.captcha_solution_vbox.pack_start(entry, expand=False) |
295 | + self.captcha_solution_vbox.reorder_child(entry, 0) |
296 | + msg = self.CAPTCHA_RELOAD_TOOLTIP |
297 | + self.captcha_reload_button.set_tooltip_text(msg) |
298 | + |
299 | + self.emails_hbox.pack_start(self.email1_entry, expand=False) |
300 | + self.emails_hbox.pack_start(self.email2_entry, expand=False) |
301 | + |
302 | + self.passwords_hbox.pack_start(self.password1_entry, expand=False) |
303 | + self.passwords_hbox.pack_start(self.password2_entry, expand=False) |
304 | help_msg = '<small>%s</small>' % self.PASSWORD_HELP |
305 | self.password_help_label.set_markup(help_msg) |
306 | |
307 | @@ -515,6 +528,11 @@ |
308 | self.tc_hbox.hide_all() |
309 | self.login_button.set_label(self.LOGIN_BUTTON_LABEL) |
310 | |
311 | + for entry in (self.name_entry, self.email1_entry, self.email2_entry, |
312 | + self.password1_entry, self.password2_entry, |
313 | + self.captcha_solution_entry): |
314 | + entry.connect('activate', lambda w: self.join_ok_button.clicked()) |
315 | + |
316 | return self.enter_details_vbox |
317 | |
318 | def _build_tc_page(self): |
319 | @@ -530,6 +548,8 @@ |
320 | |
321 | def _build_verify_email_page(self): |
322 | """Build the verify email page.""" |
323 | + cb = lambda w: self.verify_token_button.clicked() |
324 | + self.email_token_entry.connect('activate', cb) |
325 | self.verify_email_vbox.pack_start(self.email_token_entry, expand=False) |
326 | self.verify_email_vbox.reorder_child(self.email_token_entry, 0) |
327 | |
328 | @@ -537,7 +557,6 @@ |
329 | |
330 | def _build_success_page(self): |
331 | """Build the success page.""" |
332 | - #self.success_vbox.help_text = '' |
333 | self.success_label.set_markup('<span size="x-large">%s</span>' % |
334 | self.SUCCESS) |
335 | return self.success_vbox |
336 | @@ -557,6 +576,9 @@ |
337 | self.forgotten_password_button.set_label(msg) |
338 | self.login_ok_button.grab_focus() |
339 | |
340 | + for entry in (self.login_email_entry, self.login_password_entry): |
341 | + entry.connect('activate', lambda w: self.login_ok_button.clicked()) |
342 | + |
343 | return self.login_vbox |
344 | |
345 | def _build_request_password_token_page(self): |
346 | @@ -566,12 +588,16 @@ |
347 | self.request_password_token_vbox.help_text = text |
348 | |
349 | entry = self.reset_email_entry |
350 | - self.request_password_token_details_vbox.pack_start(entry) |
351 | + self.request_password_token_details_vbox.pack_start(entry, |
352 | + expand=False) |
353 | cb = self.on_reset_email_entry_changed |
354 | self.reset_email_entry.connect('changed', cb) |
355 | self.request_password_token_ok_button.set_label(self.NEXT) |
356 | self.request_password_token_ok_button.set_sensitive(False) |
357 | |
358 | + cb = lambda w: self.request_password_token_ok_button.clicked() |
359 | + self.reset_email_entry.connect('activate', cb) |
360 | + |
361 | return self.request_password_token_vbox |
362 | |
363 | def _build_set_new_password_page(self): |
364 | @@ -579,21 +605,52 @@ |
365 | self.set_new_password_vbox.header = self.RESET_PASSWORD |
366 | self.set_new_password_vbox.help_text = self.SET_NEW_PASSWORD_LABEL |
367 | |
368 | + cb = lambda w: self.set_new_password_ok_button.clicked() |
369 | for entry in (self.reset_code_entry, |
370 | self.reset_password1_entry, |
371 | self.reset_password2_entry): |
372 | - self.set_new_password_details_vbox.pack_start(entry) |
373 | + self.set_new_password_details_vbox.pack_start(entry, expand=False) |
374 | + entry.connect('activate', cb) |
375 | |
376 | cb = self.on_set_new_password_entries_changed |
377 | self.reset_code_entry.connect('changed', cb) |
378 | self.reset_password1_entry.connect('changed', cb) |
379 | self.reset_password2_entry.connect('changed', cb) |
380 | + help_msg = '<small>%s</small>' % self.PASSWORD_HELP |
381 | + self.reset_password_help_label.set_markup(help_msg) |
382 | |
383 | self.set_new_password_ok_button.set_label(self.RESET_PASSWORD) |
384 | self.set_new_password_ok_button.set_sensitive(False) |
385 | |
386 | return self.set_new_password_vbox |
387 | |
388 | + def _validate_email(self, email1, email2=None): |
389 | + """Validate 'email1', return error message if not valid. |
390 | + |
391 | + If 'email2' is given, must match 'email1'. |
392 | + """ |
393 | + if email2 is not None and email1 != email2: |
394 | + return self.EMAIL_MISMATCH |
395 | + |
396 | + if not email1: |
397 | + return self.FIELD_REQUIRED |
398 | + |
399 | + if '@' not in email1: |
400 | + return self.EMAIL_INVALID |
401 | + |
402 | + def _validate_password(self, password1, password2=None): |
403 | + """Validate 'password1', return error message if not valid. |
404 | + |
405 | + If 'password2' is given, must match 'email1'. |
406 | + """ |
407 | + if password2 is not None and password1 != password2: |
408 | + return self.PASSWORD_MISMATCH |
409 | + |
410 | + if (len(password1) < 8 or |
411 | + re.search('[A-Z]', password1) is None or |
412 | + re.search('\d+', password1) is None): |
413 | + return self.PASSWORD_TOO_WEAK |
414 | + |
415 | # GTK callbacks |
416 | |
417 | def run(self): |
418 | @@ -640,6 +697,9 @@ |
419 | |
420 | def on_join_ok_button_clicked(self, *args, **kwargs): |
421 | """Submit info for processing, present the processing vbox.""" |
422 | + if not self.join_ok_button.is_sensitive(): |
423 | + return |
424 | + |
425 | self._clear_warnings() |
426 | |
427 | error = False |
428 | @@ -647,56 +707,40 @@ |
429 | name = self.name_entry.get_text() |
430 | if not name: |
431 | self.name_entry.set_warning(self.FIELD_REQUIRED) |
432 | - error |= True |
433 | + error = True |
434 | |
435 | # check email |
436 | email1 = self.email1_entry.get_text() |
437 | email2 = self.email2_entry.get_text() |
438 | - if email1 != email2: |
439 | - self.email1_entry.set_warning(self.EMAIL_MISMATCH) |
440 | - self.email2_entry.set_warning(self.EMAIL_MISMATCH) |
441 | - error |= True |
442 | - |
443 | - if '@' not in email1: |
444 | - self.email1_entry.set_warning(self.EMAIL_INVALID) |
445 | - self.email2_entry.set_warning(self.EMAIL_INVALID) |
446 | - error |= True |
447 | - |
448 | - if not email1: |
449 | - self.email1_entry.set_warning(self.FIELD_REQUIRED) |
450 | - self.email2_entry.set_warning(self.FIELD_REQUIRED) |
451 | - error |= True |
452 | + msg = self._validate_email(email1, email2) |
453 | + if msg is not None: |
454 | + self.email1_entry.set_warning(msg) |
455 | + self.email2_entry.set_warning(msg) |
456 | + error = True |
457 | |
458 | # check password |
459 | password1 = self.password1_entry.get_text() |
460 | password2 = self.password2_entry.get_text() |
461 | - if password1 != password2: |
462 | - self.password1_entry.set_warning(self.PASSWORD_MISMATCH) |
463 | - self.password2_entry.set_warning(self.PASSWORD_MISMATCH) |
464 | - error |= True |
465 | - |
466 | - if (len(password1) < 8 or |
467 | - re.search('[A-Z]', password1) is None or |
468 | - re.search('\d+', password1) is None): |
469 | - self.password1_entry.set_warning(self.PASSWORD_TOO_WEAK) |
470 | - self.password2_entry.set_warning(self.PASSWORD_TOO_WEAK) |
471 | - error |= True |
472 | + msg = self._validate_password(password1, password2) |
473 | + if msg is not None: |
474 | + self.password1_entry.set_warning(msg) |
475 | + self.password2_entry.set_warning(msg) |
476 | + error = True |
477 | |
478 | # check T&C |
479 | if not self.yes_to_tc_checkbutton.get_active(): |
480 | self._set_warning_message(self.tc_warning_label, |
481 | self.TC_NOT_ACCEPTED) |
482 | - error |= True |
483 | + error = True |
484 | |
485 | captcha_solution = self.captcha_solution_entry.get_text() |
486 | if not captcha_solution: |
487 | self.captcha_solution_entry.set_warning(self.FIELD_REQUIRED) |
488 | - error |= True |
489 | + error = True |
490 | |
491 | if error: |
492 | return |
493 | |
494 | - self._clear_warnings() |
495 | self._set_current_page(self.processing_vbox) |
496 | |
497 | logger.info('Calling register_user with email %r, password <hidden>,' \ |
498 | @@ -716,14 +760,22 @@ |
499 | |
500 | def on_verify_token_button_clicked(self, *args, **kwargs): |
501 | """The user entered the email token, let's verify!""" |
502 | + if not self.verify_token_button.is_sensitive(): |
503 | + return |
504 | + |
505 | + self._clear_warnings() |
506 | + |
507 | + email_token = self.email_token_entry.get_text() |
508 | + if not email_token: |
509 | + self.email_token_entry.set_warning(self.FIELD_REQUIRED) |
510 | + return |
511 | + |
512 | email = self.email1_entry.get_text() |
513 | password = self.password1_entry.get_text() |
514 | - email_token = self.email_token_entry.get_text() |
515 | - |
516 | + f = self.backend.validate_email |
517 | logger.info('Calling validate_email with email %r, password <hidden>' \ |
518 | - ', app_name %r and email_token %r.', email, |
519 | - self.app_name, email_token) |
520 | - f = self.backend.validate_email |
521 | + ', app_name %r and email_token %r.', email, self.app_name, |
522 | + email_token) |
523 | f(self.app_name, email, password, email_token, |
524 | reply_handler=NO_OP, error_handler=NO_OP) |
525 | |
526 | @@ -731,30 +783,27 @@ |
527 | |
528 | def on_login_connect_button_clicked(self, *args, **kwargs): |
529 | """User wants to connect!""" |
530 | + if not self.login_ok_button.is_sensitive(): |
531 | + return |
532 | + |
533 | self._clear_warnings() |
534 | |
535 | error = False |
536 | + |
537 | email = self.login_email_entry.get_text() |
538 | - |
539 | - if '@' not in email: |
540 | - self.login_email_entry.set_warning(self.EMAIL_INVALID) |
541 | - error |= True |
542 | - |
543 | - if not email: |
544 | - self.login_email_entry.set_warning(self.FIELD_REQUIRED) |
545 | - error |= True |
546 | + msg = self._validate_email(email) |
547 | + if msg is not None: |
548 | + self.login_email_entry.set_warning(msg) |
549 | + error = True |
550 | |
551 | password = self.login_password_entry.get_text() |
552 | - |
553 | if not password: |
554 | self.login_password_entry.set_warning(self.FIELD_REQUIRED) |
555 | - error |= True |
556 | + error = True |
557 | |
558 | if error: |
559 | return |
560 | |
561 | - self._clear_warnings() |
562 | - |
563 | f = self.backend.login |
564 | f(self.app_name, email, password, |
565 | reply_handler=NO_OP, error_handler=NO_OP) |
566 | @@ -771,7 +820,16 @@ |
567 | |
568 | def on_request_password_token_ok_button_clicked(self, *args, **kwargs): |
569 | """User entered the email address to reset the password.""" |
570 | + if not self.request_password_token_ok_button.is_sensitive(): |
571 | + return |
572 | + |
573 | + self._clear_warnings() |
574 | + |
575 | email = self.reset_email_entry.get_text() |
576 | + msg = self._validate_email(email) |
577 | + if msg is not None: |
578 | + self.reset_email_entry.set_warning(msg) |
579 | + return |
580 | |
581 | logger.info('Calling request_password_reset_token with %r.', email) |
582 | f = self.backend.request_password_reset_token |
583 | @@ -799,13 +857,30 @@ |
584 | |
585 | def on_set_new_password_ok_button_clicked(self, *args, **kwargs): |
586 | """User entered reset code and new passwords.""" |
587 | - email = self.reset_email_entry.get_text() |
588 | + if not self.set_new_password_ok_button.is_sensitive(): |
589 | + return |
590 | + |
591 | + self._clear_warnings() |
592 | + |
593 | + error = False |
594 | + |
595 | token = self.reset_code_entry.get_text() |
596 | - # XXX: validate passwords! |
597 | - # see LP: #616528 |
598 | + if not token: |
599 | + self.reset_code_entry.set_warning(self.FIELD_REQUIRED) |
600 | + error = True |
601 | + |
602 | password1 = self.reset_password1_entry.get_text() |
603 | - #password2 = self.reset_password2_entry.get_text() |
604 | - |
605 | + password2 = self.reset_password2_entry.get_text() |
606 | + msg = self._validate_password(password1, password2) |
607 | + if msg is not None: |
608 | + self.reset_password1_entry.set_warning(msg) |
609 | + self.reset_password2_entry.set_warning(msg) |
610 | + error = True |
611 | + |
612 | + if error: |
613 | + return |
614 | + |
615 | + email = self.reset_email_entry.get_text() |
616 | logger.info('Calling set_new_password with email %r, token %r and ' \ |
617 | 'new password: <hidden>.', email, token) |
618 | f = self.backend.set_new_password |
619 | @@ -836,6 +911,17 @@ |
620 | |
621 | # backend callbacks |
622 | |
623 | + def _build_general_error_message(self, errordict): |
624 | + """Concatenate __all__ and message from the errordict.""" |
625 | + result = None |
626 | + msg1 = errordict.get('__all__') |
627 | + msg2 = errordict.get('message') |
628 | + if msg1 is not None and msg2 is not None: |
629 | + result = '\n'.join((msg1, msg2)) |
630 | + else: |
631 | + result = msg1 if msg1 is not None else msg2 |
632 | + return result |
633 | + |
634 | @log_call |
635 | def on_captcha_generated(self, app_name, captcha_id, *args, **kwargs): |
636 | """Captcha image has been generated and is available to be shown.""" |
637 | @@ -858,10 +944,21 @@ |
638 | @log_call |
639 | def on_user_registration_error(self, app_name, error, *args, **kwargs): |
640 | """Captcha image generation failed.""" |
641 | - self._set_current_page(self.enter_details_vbox, |
642 | - warning_text=self.UNKNOWN_ERROR) |
643 | + msg = error.get('email') |
644 | + if msg is not None: |
645 | + self.email1_entry.set_warning(msg) |
646 | + self.email2_entry.set_warning(msg) |
647 | + |
648 | + msg = error.get('password') |
649 | + if msg is not None: |
650 | + self.password1_entry.set_warning(msg) |
651 | + self.password2_entry.set_warning(msg) |
652 | + |
653 | + msg = self._build_general_error_message(error) |
654 | + self._set_current_page(self.enter_details_vbox, warning_text=msg) |
655 | + |
656 | self._gtk_signal_log.append((SIG_REGISTRATION_FAILED, self.app_name, |
657 | - error)) |
658 | + msg)) |
659 | |
660 | @log_call |
661 | def on_email_validated(self, app_name, email, *args, **kwargs): |
662 | @@ -873,10 +970,15 @@ |
663 | @log_call |
664 | def on_email_validation_error(self, app_name, error, *args, **kwargs): |
665 | """User email validation failed.""" |
666 | - self._set_current_page(self.verify_email_vbox, |
667 | - warning_text=self.UNKNOWN_ERROR) |
668 | + msg = error.get('email_token') |
669 | + if msg is not None: |
670 | + self.email_token_entry.set_warning(msg) |
671 | + |
672 | + msg = self._build_general_error_message(error) |
673 | + self._set_current_page(self.verify_email_vbox, warning_text=msg) |
674 | + |
675 | self._gtk_signal_log.append((SIG_REGISTRATION_FAILED, self.app_name, |
676 | - error)) |
677 | + msg)) |
678 | |
679 | @log_call |
680 | def on_logged_in(self, app_name, email, *args, **kwargs): |
681 | @@ -888,10 +990,10 @@ |
682 | @log_call |
683 | def on_login_error(self, app_name, error, *args, **kwargs): |
684 | """User was not successfully logged in.""" |
685 | - self._set_current_page(self.login_vbox, |
686 | - warning_text=self.UNKNOWN_ERROR) |
687 | + msg = self._build_general_error_message(error) |
688 | + self._set_current_page(self.login_vbox, warning_text=msg) |
689 | self._gtk_signal_log.append((SIG_LOGIN_FAILED, self.app_name, |
690 | - error)) |
691 | + msg)) |
692 | |
693 | @log_call |
694 | def on_password_reset_token_sent(self, app_name, email, *args, **kwargs): |
695 | @@ -903,8 +1005,8 @@ |
696 | @log_call |
697 | def on_password_reset_error(self, app_name, error, *args, **kwargs): |
698 | """Password reset failed.""" |
699 | - self._set_current_page(self.login_vbox, |
700 | - warning_text=self.UNKNOWN_ERROR) |
701 | + msg = self._build_general_error_message(error) |
702 | + self._set_current_page(self.login_vbox, warning_text=msg) |
703 | |
704 | @log_call |
705 | def on_password_changed(self, app_name, email, *args, **kwargs): |
706 | @@ -915,5 +1017,6 @@ |
707 | @log_call |
708 | def on_password_change_error(self, app_name, error, *args, **kwargs): |
709 | """Password reset failed.""" |
710 | + msg = self._build_general_error_message(error) |
711 | self._set_current_page(self.request_password_token_vbox, |
712 | - warning_text=self.UNKNOWN_ERROR) |
713 | + warning_text=msg) |
714 | |
715 | === modified file 'ubuntu_sso/keyring.py' |
716 | --- ubuntu_sso/keyring.py 2010-08-19 16:02:09 +0000 |
717 | +++ ubuntu_sso/keyring.py 2010-08-27 22:32:43 +0000 |
718 | @@ -19,77 +19,128 @@ |
719 | |
720 | """Handle keys in the gnome kerying.""" |
721 | |
722 | +import socket |
723 | +import urllib |
724 | +import urlparse |
725 | + |
726 | import gnomekeyring |
727 | |
728 | -from urllib import urlencode |
729 | -from urlparse import parse_qsl |
730 | +from ubuntu_sso.logger import setupLogging |
731 | +logger = setupLogging("ubuntu_sso.main") |
732 | + |
733 | +U1_APP_NAME = "Ubuntu One" |
734 | +U1_KEY_NAME = "UbuntuOne token for https://ubuntuone.com" |
735 | +U1_KEY_ATTR = { |
736 | + "oauth-consumer-key": "ubuntuone", |
737 | + "ubuntuone-realm": "https://ubuntuone.com", |
738 | +} |
739 | + |
740 | + |
741 | +def get_token_name(app_name): |
742 | + """Build the token name.""" |
743 | + quoted_app_name = urllib.quote(app_name) |
744 | + computer_name = socket.gethostname() |
745 | + quoted_computer_name = urllib.quote(computer_name) |
746 | + return "%s - %s" % (quoted_app_name, quoted_computer_name) |
747 | |
748 | |
749 | class Keyring(object): |
750 | - |
751 | + """A Keyring for a given application name.""" |
752 | KEYRING_NAME = "login" |
753 | |
754 | def __init__(self, app_name): |
755 | + """Initialize this instance given the app_name.""" |
756 | if not gnomekeyring.is_available(): |
757 | raise gnomekeyring.NoKeyringDaemonError |
758 | self.app_name = app_name |
759 | + self.token_name = get_token_name(self.app_name) |
760 | |
761 | def _create_keyring(self, name): |
762 | - """Creates a keyring, if it already exists, do nothing.""" |
763 | + """Creates a keyring, or if it already exists, it does nothing.""" |
764 | keyring_names = gnomekeyring.list_keyring_names_sync() |
765 | if not name in keyring_names: |
766 | gnomekeyring.create_sync(name) |
767 | |
768 | - def _get_item_id_from_name(self, sync, name): |
769 | - """Return the ID for a named item.""" |
770 | - for item_id in gnomekeyring.list_item_ids_sync(sync): |
771 | - item_info = gnomekeyring.item_get_info_sync(sync, item_id) |
772 | - display_name = item_info.get_display_name() |
773 | - if display_name == name: |
774 | - return item_id |
775 | - |
776 | - def _get_item_info_from_name(self, sync, name): |
777 | - """Return the ID for a named item.""" |
778 | - for item_id in gnomekeyring.list_item_ids_sync(sync): |
779 | - item_info = gnomekeyring.item_get_info_sync(sync, item_id) |
780 | - display_name = item_info.get_display_name() |
781 | - if display_name == name: |
782 | - return item_info |
783 | + def _find_keyring_item(self): |
784 | + """Return the keyring item or None if not found.""" |
785 | + try: |
786 | + items = gnomekeyring.find_items_sync( |
787 | + gnomekeyring.ITEM_GENERIC_SECRET, |
788 | + self._get_keyring_attr()) |
789 | + except gnomekeyring.NoMatchError: |
790 | + # if no items found, return None |
791 | + return None |
792 | + |
793 | + # we priorize the item in the "login" keyring |
794 | + for item in items: |
795 | + if item.keyring == "login": |
796 | + return item |
797 | + |
798 | + # if not on the "login" keyring, we return the first item |
799 | + return items[0] |
800 | + |
801 | + def _get_keyring_attr(self): |
802 | + """Build the keyring attributes for this credentials.""" |
803 | + attr = {"key-type": "Ubuntu SSO credentials", |
804 | + "token-name": self.token_name} |
805 | + return attr |
806 | |
807 | def set_ubuntusso_attr(self, cred): |
808 | """Set the credentials of the Ubuntu SSO item.""" |
809 | # Creates the secret from the credentials |
810 | - secret = urlencode(cred) |
811 | + secret = urllib.urlencode(cred) |
812 | |
813 | - # Create the keyring |
814 | + # Create the keyring if it does not exists |
815 | self._create_keyring(self.KEYRING_NAME) |
816 | |
817 | - # No need to delete the item if it already exists |
818 | - |
819 | - # A set of attributes for this credentials |
820 | - attr = {"key-type": "Ubuntu SSO credentials", |
821 | - "token-name": cred["name"].encode("utf8")} |
822 | - |
823 | # Add our SSO credentials to the keyring |
824 | gnomekeyring.item_create_sync(self.KEYRING_NAME, |
825 | - gnomekeyring.ITEM_GENERIC_SECRET, self.app_name, attr, |
826 | - secret, True) |
827 | + gnomekeyring.ITEM_GENERIC_SECRET, self.app_name, |
828 | + self._get_keyring_attr(), secret, True) |
829 | |
830 | def get_ubuntusso_attr(self): |
831 | """Return the secret of the SSO item in a dictionary.""" |
832 | # If we have no attributes, return None |
833 | - exist = self._get_item_info_from_name(self.KEYRING_NAME, self.app_name) |
834 | - if exist is not None: |
835 | - secret = self._get_item_info_from_name(self.KEYRING_NAME, |
836 | - self.app_name).get_secret() |
837 | - return dict(parse_qsl(secret)) |
838 | + item = self._find_keyring_item() |
839 | + if item is not None: |
840 | + return dict(urlparse.parse_qsl(item.secret)) |
841 | + else: |
842 | + # if no item found, try getting the old credentials |
843 | + if self.app_name == U1_APP_NAME: |
844 | + return try_old_credentials(self.app_name) |
845 | + # nothing was found |
846 | + return None |
847 | |
848 | def delete_ubuntusso_attr(self): |
849 | """Delete a set of credentials from the keyring.""" |
850 | - item_id = self._get_item_id_from_name(self.KEYRING_NAME, |
851 | - self.app_name) |
852 | - if item_id is not None: |
853 | - gnomekeyring.item_delete_sync(self.KEYRING_NAME, item_id) |
854 | + item = self._find_keyring_item() |
855 | + if item is not None: |
856 | + gnomekeyring.item_delete_sync(item.keyring, item.item_id) |
857 | + |
858 | + |
859 | +class UbuntuOneOAuthKeyring(Keyring): |
860 | + |
861 | + def _get_keyring_attr(self): |
862 | + """Build the keyring attributes for this credentials.""" |
863 | + return U1_KEY_ATTR |
864 | + |
865 | + |
866 | +def try_old_credentials(app_name): |
867 | + """Try to get old U1 credentials and format them as new.""" |
868 | + logger.debug('trying to get old credentials.') |
869 | + old_creds = UbuntuOneOAuthKeyring(U1_KEY_NAME).get_ubuntusso_attr() |
870 | + if old_creds is not None: |
871 | + # Old creds found, build a new credentials dict with them |
872 | + creds = { |
873 | + 'consumer_key': "ubuntuone", |
874 | + 'consumer_secret': "hammertime", |
875 | + 'name': U1_KEY_NAME, |
876 | + 'token': old_creds["oauth_token"], |
877 | + 'token_secret': old_creds["oauth_token_secret"], |
878 | + } |
879 | + logger.debug('found old credentials') |
880 | + return creds |
881 | + logger.debug('try_old_credentials: No old credentials for this app.') |
882 | |
883 | |
884 | if __name__ == "__main__": |
885 | |
886 | === modified file 'ubuntu_sso/main.py' |
887 | --- ubuntu_sso/main.py 2010-08-26 01:07:41 +0000 |
888 | +++ ubuntu_sso/main.py 2010-08-27 22:32:43 +0000 |
889 | @@ -28,11 +28,9 @@ |
890 | """ |
891 | |
892 | import re |
893 | -import socket |
894 | import time |
895 | import threading |
896 | import traceback |
897 | -import urllib |
898 | import urllib2 |
899 | import urlparse |
900 | |
901 | @@ -49,7 +47,7 @@ |
902 | from ubuntu_sso import (DBUS_IFACE_AUTH_NAME, DBUS_IFACE_USER_NAME, |
903 | DBUS_IFACE_CRED_NAME, DBUS_CRED_PATH, DBUS_BUS_NAME, gui) |
904 | from ubuntu_sso.config import get_config |
905 | -from ubuntu_sso.keyring import Keyring |
906 | +from ubuntu_sso.keyring import Keyring, get_token_name, U1_APP_NAME |
907 | from ubuntu_sso.logger import setupLogging |
908 | logger = setupLogging("ubuntu_sso.main") |
909 | |
910 | @@ -57,8 +55,6 @@ |
911 | # Disable the invalid name warning, as we have a lot of DBus style names |
912 | # pylint: disable-msg=C0103 |
913 | |
914 | -OLD_KEY_NAME = "UbuntuOne token for https://ubuntuone.com" |
915 | -U1_APP_NAME = "Ubuntu One" |
916 | PING_URL = "http://edge.one.ubuntu.com/oauth/sso-finished-so-get-tokens/" |
917 | |
918 | |
919 | @@ -113,32 +109,9 @@ |
920 | creds = Keyring(app_name).get_ubuntusso_attr() |
921 | logger.debug('keyring_get_credentials: Keyring returned credentials? %r', |
922 | creds is not None) |
923 | - if creds is None and app_name == U1_APP_NAME: |
924 | - logger.debug('keyring_get_credentials: trying for old service.') |
925 | - # No new creds, try to get old credentials |
926 | - old_creds = Keyring(OLD_KEY_NAME).get_ubuntusso_attr() |
927 | - if old_creds is not None: |
928 | - # Old creds found, build a new credentials dict with them |
929 | - creds = { |
930 | - 'consumer_key': "ubuntuone", |
931 | - 'consumer_secret': "hammertime", |
932 | - 'token_name': OLD_KEY_NAME, |
933 | - 'token': old_creds["oauth_token"], |
934 | - 'token_secret': old_creds["oauth_token_secret"], |
935 | - } |
936 | - else: |
937 | - logger.debug('keyring_get_credentials: Keyring returned credentials.') |
938 | return creds |
939 | |
940 | |
941 | -def get_token_name(app_name): |
942 | - """Build the token name.""" |
943 | - quoted_app_name = urllib.quote(app_name) |
944 | - computer_name = socket.gethostname() |
945 | - quoted_computer_name = urllib.quote(computer_name) |
946 | - return "%s - %s" % (quoted_app_name, quoted_computer_name) |
947 | - |
948 | - |
949 | class SSOLoginProcessor(object): |
950 | """Login and register users using the Ubuntu Single Sign On service.""" |
951 | |
952 | @@ -163,6 +136,17 @@ |
953 | re.search('\d+', password)) # one number |
954 | return res |
955 | |
956 | + def _format_webservice_errors(self, errdict): |
957 | + """Turn each list of strings in the errdict into a LF separated str.""" |
958 | + result = {} |
959 | + for k, v in errdict.iteritems(): |
960 | + # workaround until bug #624955 is solved |
961 | + if isinstance(v, basestring): |
962 | + result[k] = v |
963 | + else: |
964 | + result[k] = "\n".join(v) |
965 | + return result |
966 | + |
967 | def generate_captcha(self, filename): |
968 | """Generate a captcha using the SSO service.""" |
969 | logger.debug('generate_captcha: requesting captcha, filename: %r', |
970 | @@ -203,7 +187,8 @@ |
971 | logger.info('register_user: email: %r result: %r', email, result) |
972 | |
973 | if result['status'] == 'error': |
974 | - raise RegistrationError(result['errors']) |
975 | + errorsdict = self._format_webservice_errors(result['errors']) |
976 | + raise RegistrationError(errorsdict) |
977 | elif result['status'] != 'ok': |
978 | raise RegistrationError('Received unknown status: %s' % result) |
979 | else: |
980 | @@ -243,9 +228,10 @@ |
981 | result = sso_service.accounts.validate_email(email_token=email_token) |
982 | logger.info('validate_email: email: %r result: %r', email, result) |
983 | if 'errors' in result: |
984 | - raise EmailTokenError(result['errors']) |
985 | + errorsdict = self._format_webservice_errors(result['errors']) |
986 | + raise EmailTokenError(errorsdict) |
987 | elif 'email' in result: |
988 | - return result['email'] |
989 | + return token |
990 | else: |
991 | raise EmailTokenError('Received invalid reply: %s' % result) |
992 | |
993 | @@ -257,7 +243,7 @@ |
994 | result = service(email=email) |
995 | except HTTPError, e: |
996 | logger.exception('request_password_reset_token failed with:') |
997 | - raise ResetPasswordTokenError(e.content) |
998 | + raise ResetPasswordTokenError(e.content.split('\n')[0]) |
999 | |
1000 | if result['status'] == 'ok': |
1001 | return email |
1002 | @@ -279,7 +265,7 @@ |
1003 | new_password=new_password) |
1004 | except HTTPError, e: |
1005 | logger.exception('set_new_password failed with:') |
1006 | - raise NewPasswordError(e.content) |
1007 | + raise NewPasswordError(e.content.split('\n')[0]) |
1008 | |
1009 | if result['status'] == 'ok': |
1010 | return email |
1011 | @@ -291,7 +277,6 @@ |
1012 | """Turn an exception into a dictionary to return thru DBus.""" |
1013 | result = { |
1014 | "errtype": e.__class__.__name__, |
1015 | - "errargs": repr(e.args), |
1016 | } |
1017 | if len(e.args) == 0: |
1018 | result["message"] = e.__class__.__doc__ |
1019 | @@ -414,8 +399,10 @@ |
1020 | def f(): |
1021 | """Inner function that will be run in a thread.""" |
1022 | token_name = get_token_name(app_name) |
1023 | - return self.processor().validate_email(email, password, |
1024 | + credentials = self.processor().validate_email(email, password, |
1025 | email_token, token_name) |
1026 | + keyring_store_credentials(app_name, credentials) |
1027 | + return email |
1028 | blocking(f, app_name, self.EmailValidated, self.EmailValidationError) |
1029 | |
1030 | # request_password_reset_token signals |
1031 | |
1032 | === modified file 'ubuntu_sso/tests/test_gui.py' |
1033 | --- ubuntu_sso/tests/test_gui.py 2010-08-26 01:07:41 +0000 |
1034 | +++ ubuntu_sso/tests/test_gui.py 2010-08-27 22:32:43 +0000 |
1035 | @@ -44,7 +44,6 @@ |
1036 | CAPTCHA_SOLUTION = 'william Byrd' |
1037 | EMAIL = 'test@example.com' |
1038 | EMAIL_TOKEN = 'B2Pgtf' |
1039 | -ERROR = 'Something bad happened!' |
1040 | NAME = 'Juanito Pérez' |
1041 | PASSWORD = 'h3lloWorld' |
1042 | RESET_PASSWORD_TOKEN = '8G5Wtq' |
1043 | @@ -269,6 +268,8 @@ |
1044 | None) |
1045 | self.assertEqual(self.entry.get_property('secondary-icon-sensitive'), |
1046 | False) |
1047 | + self.assertEqual(self.entry.get_property('secondary-icon-activatable'), |
1048 | + False) |
1049 | prop = self.entry.get_property('secondary-icon-tooltip-text') |
1050 | self.assertEqual(prop, None) |
1051 | |
1052 | @@ -281,6 +282,8 @@ |
1053 | gtk.STOCK_DIALOG_WARNING) |
1054 | self.assertEqual(self.entry.get_property('secondary-icon-sensitive'), |
1055 | True) |
1056 | + self.assertEqual(self.entry.get_property('secondary-icon-activatable'), |
1057 | + False) |
1058 | prop = self.entry.get_property('secondary-icon-tooltip-text') |
1059 | self.assertEqual(prop, msg) |
1060 | |
1061 | @@ -292,6 +295,8 @@ |
1062 | None) |
1063 | self.assertEqual(self.entry.get_property('secondary-icon-sensitive'), |
1064 | False) |
1065 | + self.assertEqual(self.entry.get_property('secondary-icon-activatable'), |
1066 | + False) |
1067 | prop = self.entry.get_property('secondary-icon-tooltip-text') |
1068 | self.assertEqual(prop, None) |
1069 | |
1070 | @@ -338,6 +343,7 @@ |
1071 | 'tc_browser', 'login', 'request_password_token', |
1072 | 'set_new_password') |
1073 | self.ui = self.gui_class(**self.kwargs) |
1074 | + self.ERROR = {'message': self.ui.UNKNOWN_ERROR} |
1075 | |
1076 | def tearDown(self): |
1077 | """Clean up.""" |
1078 | @@ -358,11 +364,13 @@ |
1079 | def assert_warnings_visibility(self, visible=False): |
1080 | """Every warning label should be 'visible'.""" |
1081 | msg = '"%s" should be %svisible.' |
1082 | - warnings = filter(lambda name: 'warning' in name, self.ui.widgets) |
1083 | - for name in warnings: |
1084 | + for name in self.ui.widgets: |
1085 | widget = getattr(self.ui, name) |
1086 | - self.assertEqual(visible, widget.get_property('visible'), |
1087 | - msg % (name, '' if visible else 'not ')) |
1088 | + if 'warning' in name: |
1089 | + self.assertEqual(visible, widget.get_property('visible'), |
1090 | + msg % (name, '' if visible else 'not ')) |
1091 | + elif 'entry' in name: |
1092 | + self.assertEqual(widget.warning, '') |
1093 | |
1094 | def assert_correct_label_warning(self, label, message): |
1095 | """Check that a warning is shown displaying 'message'.""" |
1096 | @@ -533,6 +541,16 @@ |
1097 | # text content is correct |
1098 | self.assertEqual(expected, actual, msg % (name, expected, actual)) |
1099 | |
1100 | + def test_initial_size_for_labels(self): |
1101 | + """Labels have the correct width.""" |
1102 | + expected = (int(self.ui.window.get_size_request()[0] * 0.9), -1) |
1103 | + msg = 'Label %r must have size request %s (got %s instead).' |
1104 | + labels = [i for i in self.ui.widgets if 'label' in i] |
1105 | + for label in labels: |
1106 | + widget = getattr(self.ui, label) |
1107 | + actual = widget.get_size_request() |
1108 | + self.assertEqual(expected, actual, msg % (label, expected, actual)) |
1109 | + |
1110 | def test_password_fields_are_password(self): |
1111 | msg = '"%s" should be a password LabeledEntry instance.' |
1112 | passwords = filter(lambda name: 'password' in name, |
1113 | @@ -543,7 +561,7 @@ |
1114 | |
1115 | def test_warning_fields_are_hidden(self): |
1116 | """Every warning label should be not visible.""" |
1117 | - self.assert_warnings_visibility(visible=False) |
1118 | + self.assert_warnings_visibility() |
1119 | |
1120 | def test_cancel_buttons_close_window(self): |
1121 | """Every cancel button should close the window when clicked.""" |
1122 | @@ -558,6 +576,17 @@ |
1123 | self.assertEqual(self._called, ((widget,), {}), msg % name) |
1124 | self._called = False |
1125 | |
1126 | + def test_all_buttons_are_activatable(self): |
1127 | + """Every button should be activatable.""" |
1128 | + msg = '"%s" should be activatable.' |
1129 | + buttons = filter(lambda name: '_button' in name, self.ui.widgets) |
1130 | + for name in buttons: |
1131 | + widget = getattr(self.ui, name) |
1132 | + widget.connect('clicked', self._set_called) |
1133 | + widget.activate() |
1134 | + self.assertEqual(self._called, ((widget,), {}), msg % name) |
1135 | + self._called = False |
1136 | + |
1137 | def test_window_icon(self): |
1138 | """Main window has the proper icon.""" |
1139 | self.assertEqual('ubuntu-logo', self.ui.window.get_icon_name()) |
1140 | @@ -585,7 +614,7 @@ |
1141 | self.assertEqual(self._called, ((xid,), {})) |
1142 | |
1143 | |
1144 | -class RegistrationEnterDetailsTestCase(UbuntuSSOClientTestCase): |
1145 | +class EnterDetailsTestCase(UbuntuSSOClientTestCase): |
1146 | """Test suite for the user registration (enter details page).""" |
1147 | |
1148 | def test_initial_text_for_header_label(self): |
1149 | @@ -774,6 +803,50 @@ |
1150 | actual = self.ui.login_button.get_label() |
1151 | self.assertEqual(self.ui.LOGIN_BUTTON_LABEL, actual) |
1152 | |
1153 | + def test_activate_name_entry_clicks_connect(self): |
1154 | + """Activating any entry generates a connect attempt.""" |
1155 | + self.ui.join_ok_button.connect('clicked', self._set_called) |
1156 | + self.ui.name_entry.activate() |
1157 | + self.assertEqual(self._called, ((self.ui.join_ok_button,), {})) |
1158 | + |
1159 | + def test_activate_email_entry_clicks_connect(self): |
1160 | + """Activating any entry generates a connect attempt.""" |
1161 | + self.ui.join_ok_button.connect('clicked', self._set_called) |
1162 | + self.ui.email1_entry.activate() |
1163 | + self.assertEqual(self._called, ((self.ui.join_ok_button,), {})) |
1164 | + |
1165 | + self._called = False |
1166 | + self.ui.email2_entry.activate() |
1167 | + self.assertEqual(self._called, ((self.ui.join_ok_button,), {})) |
1168 | + |
1169 | + def test_activate_password_entry_clicks_connect(self): |
1170 | + """Activating any entry generates a connect attempt.""" |
1171 | + self.ui.join_ok_button.connect('clicked', self._set_called) |
1172 | + self.ui.password1_entry.activate() |
1173 | + self.assertEqual(self._called, ((self.ui.join_ok_button,), {})) |
1174 | + |
1175 | + self._called = False |
1176 | + self.ui.password2_entry.activate() |
1177 | + self.assertEqual(self._called, ((self.ui.join_ok_button,), {})) |
1178 | + |
1179 | + def test_activate_captcha_solution_entry_clicks_connect(self): |
1180 | + """Activating any entry generates a connect attempt.""" |
1181 | + self.ui.join_ok_button.connect('clicked', self._set_called) |
1182 | + self.ui.captcha_solution_entry.activate() |
1183 | + self.assertEqual(self._called, ((self.ui.join_ok_button,), {})) |
1184 | + |
1185 | + def test_join_ok_button_does_nothing_if_clicked_but_disabled(self): |
1186 | + """The join form can only be submitted if the button is sensitive.""" |
1187 | + self.patch(self.ui.name_entry, 'get_text', self._set_called) |
1188 | + |
1189 | + self.ui.join_ok_button.set_sensitive(False) |
1190 | + self.ui.join_ok_button.clicked() |
1191 | + self.assertFalse(self._called) |
1192 | + |
1193 | + self.ui.join_ok_button.set_sensitive(True) |
1194 | + self.ui.join_ok_button.clicked() |
1195 | + self.assertTrue(self._called) |
1196 | + |
1197 | |
1198 | class NoTermsAndConditionsTestCase(UbuntuSSOClientTestCase): |
1199 | """Test suite for the user registration (with no t&c link).""" |
1200 | @@ -843,24 +916,46 @@ |
1201 | test_tc_browser_opens_the_proper_uri.skip = 'The freaking test wont work.' |
1202 | |
1203 | |
1204 | -class UserRegistrationErrorTestCase(UbuntuSSOClientTestCase): |
1205 | +class RegistrationErrorTestCase(UbuntuSSOClientTestCase): |
1206 | """Test suite for the user registration error handling.""" |
1207 | |
1208 | def setUp(self): |
1209 | """Init.""" |
1210 | - super(UserRegistrationErrorTestCase, self).setUp() |
1211 | + super(RegistrationErrorTestCase, self).setUp() |
1212 | self.click_join_with_valid_data() |
1213 | - self.ui.on_user_registration_error(app_name=APP_NAME, error=ERROR) |
1214 | |
1215 | def test_previous_page_is_shown(self): |
1216 | """On UserRegistrationError the previous page is shown.""" |
1217 | + self.ui.on_user_registration_error(app_name=APP_NAME, error=self.ERROR) |
1218 | self.assert_pages_visibility(enter_details=True) |
1219 | |
1220 | def test_warning_label_is_shown(self): |
1221 | """On UserRegistrationError the warning label is shown.""" |
1222 | + self.ui.on_user_registration_error(app_name=APP_NAME, error=self.ERROR) |
1223 | self.assert_correct_label_warning(self.ui.warning_label, |
1224 | self.ui.UNKNOWN_ERROR) |
1225 | |
1226 | + def test_specific_errors_from_backend_are_shown(self): |
1227 | + """Specific errors from backend are used.""" |
1228 | + error = {'errtype': 'RegistrationError', |
1229 | + 'message': 'We\'re so doomed.', |
1230 | + 'email': 'Enter a valid e-mail address.', |
1231 | + 'password': 'I don\'t like your password.', |
1232 | + '__all__': 'Wrong captcha solution.'} |
1233 | + |
1234 | + self.ui.on_user_registration_error(app_name=APP_NAME, error=error) |
1235 | + |
1236 | + expected = '\n'.join((error['__all__'], error['message'])) |
1237 | + self.assert_correct_label_warning(self.ui.warning_label, expected) |
1238 | + self.assert_correct_entry_warning(self.ui.email1_entry, |
1239 | + error['email']) |
1240 | + self.assert_correct_entry_warning(self.ui.email2_entry, |
1241 | + error['email']) |
1242 | + self.assert_correct_entry_warning(self.ui.password1_entry, |
1243 | + error['password']) |
1244 | + self.assert_correct_entry_warning(self.ui.password2_entry, |
1245 | + error['password']) |
1246 | + |
1247 | |
1248 | class VerifyEmailTestCase(UbuntuSSOClientTestCase): |
1249 | """Test suite for the user registration (verify email page).""" |
1250 | @@ -899,30 +994,44 @@ |
1251 | self.click_verify_email_with_valid_data() |
1252 | self.assert_pages_visibility(processing=True) |
1253 | |
1254 | - def test_no_warning_messages_if_valid_data_on_verify_token(self): |
1255 | + def test_no_warning_messages_if_valid_data(self): |
1256 | """No warning messages are shown if the data is valid.""" |
1257 | # this will certainly NOT generate warnings |
1258 | self.click_verify_email_with_valid_data() |
1259 | - self.assert_warnings_visibility(visible=False) |
1260 | + self.assert_warnings_visibility() |
1261 | |
1262 | def test_on_email_validated_shows_success_page(self): |
1263 | """On email validated the success page is shown.""" |
1264 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
1265 | self.assert_pages_visibility(success=True) |
1266 | |
1267 | - def test_on_email_validated_clears_the_help_text(self): |
1268 | - """On email validated the help text is removed.""" |
1269 | + def test_on_email_validated_does_not_clear_the_help_text(self): |
1270 | + """On email validated the help text is not removed.""" |
1271 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
1272 | - self.assertEqual('', self.ui.help_label.get_text()) |
1273 | - test_on_email_validated_clears_the_help_text.skip = 'Maybe this is wrong.' |
1274 | + self.assertEqual(self.ui.verify_email_vbox.help_text, |
1275 | + self.ui.help_label.get_label()) |
1276 | |
1277 | def test_on_email_validation_error_verify_email_is_shown(self): |
1278 | """On email validation error, the verify_email page is shown.""" |
1279 | - self.ui.on_email_validation_error(app_name=APP_NAME, error=ERROR) |
1280 | + self.ui.on_email_validation_error(app_name=APP_NAME, error=self.ERROR) |
1281 | self.assert_pages_visibility(verify_email=True) |
1282 | self.assert_correct_label_warning(self.ui.warning_label, |
1283 | self.ui.UNKNOWN_ERROR) |
1284 | |
1285 | + def test_specific_errors_from_backend_are_shown(self): |
1286 | + """Specific errors from backend are used.""" |
1287 | + error = {'errtype': 'EmailValidationError', |
1288 | + 'message': 'We\'re so doomed.', |
1289 | + 'email_token': 'Enter a valid e-mail address.', |
1290 | + '__all__': 'We all are gonna die.'} |
1291 | + |
1292 | + self.ui.on_email_validation_error(app_name=APP_NAME, error=error) |
1293 | + |
1294 | + expected = '\n'.join((error['__all__'], error['message'])) |
1295 | + self.assert_correct_label_warning(self.ui.warning_label, expected) |
1296 | + self.assert_correct_entry_warning(self.ui.email_token_entry, |
1297 | + error['email_token']) |
1298 | + |
1299 | def test_success_label_is_correct(self): |
1300 | """The success message is correct.""" |
1301 | self.assertEqual(self.ui.SUCCESS, self.ui.success_label.get_text()) |
1302 | @@ -934,15 +1043,74 @@ |
1303 | self.ui.success_close_button.clicked() |
1304 | self.assertFalse(self.ui.window.get_property('visible')) |
1305 | |
1306 | + def test_activate_email_token_entry_clicks_verify_token(self): |
1307 | + """Activating any entry generates a connect attempt.""" |
1308 | + self.ui.verify_token_button.connect('clicked', self._set_called) |
1309 | + self.ui.email_token_entry.activate() |
1310 | + self.assertEqual(self._called, ((self.ui.verify_token_button,), {})) |
1311 | + |
1312 | + def test_verify_token_button_does_nothing_if_clicked_but_disabled(self): |
1313 | + """The email token can only be submitted if the button is sensitive.""" |
1314 | + self.patch(self.ui.email_token_entry, 'get_text', self._set_called) |
1315 | + |
1316 | + self.ui.verify_token_button.set_sensitive(False) |
1317 | + self.ui.verify_token_button.clicked() |
1318 | + self.assertFalse(self._called) |
1319 | + |
1320 | + self.ui.verify_token_button.set_sensitive(True) |
1321 | + self.ui.verify_token_button.clicked() |
1322 | + self.assertTrue(self._called) |
1323 | + |
1324 | + |
1325 | +class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase): |
1326 | + """Test suite for the user registration (verify email page).""" |
1327 | + |
1328 | + def setUp(self): |
1329 | + """Init.""" |
1330 | + super(VerifyEmailValidationTestCase, self).setUp() |
1331 | + self.ui.on_user_registered(app_name=APP_NAME, email=EMAIL) |
1332 | + |
1333 | + def test_warning_is_shown_if_empty_email_token(self): |
1334 | + """A warning message is shown if email token is empty.""" |
1335 | + self.ui.email_token_entry.set_text('') |
1336 | + |
1337 | + self.ui.verify_token_button.clicked() |
1338 | + |
1339 | + self.assert_correct_entry_warning(self.ui.email_token_entry, |
1340 | + self.ui.FIELD_REQUIRED) |
1341 | + self.assertNotIn('validate_email', self.ui.backend._called) |
1342 | + |
1343 | + def test_no_warning_messages_if_valid_data(self): |
1344 | + """No warning messages are shown if the data is valid.""" |
1345 | + # this will certainly NOT generate warnings |
1346 | + self.click_verify_email_with_valid_data() |
1347 | + |
1348 | + self.assert_warnings_visibility() |
1349 | + |
1350 | + def test_no_warning_messages_if_valid_data_after_invalid_data(self): |
1351 | + """No warnings if the data is valid (with prior invalid data).""" |
1352 | + # this will certainly generate warnings |
1353 | + self.ui.verify_token_button.clicked() |
1354 | + |
1355 | + # this will certainly NOT generate warnings |
1356 | + self.click_verify_email_with_valid_data() |
1357 | + |
1358 | + self.assert_warnings_visibility() |
1359 | + |
1360 | |
1361 | class RegistrationValidationTestCase(UbuntuSSOClientTestCase): |
1362 | """Test suite for the user registration validations.""" |
1363 | |
1364 | + def setUp(self): |
1365 | + """Init.""" |
1366 | + super(RegistrationValidationTestCase, self).setUp() |
1367 | + self.ui.join_ok_button.set_sensitive(True) |
1368 | + |
1369 | def test_warning_is_shown_if_name_empty(self): |
1370 | """A warning message is shown if name is empty.""" |
1371 | self.ui.name_entry.set_text('') |
1372 | |
1373 | - self.ui.join_ok_button.clicked() # submit form |
1374 | + self.ui.join_ok_button.clicked() |
1375 | |
1376 | self.assert_correct_entry_warning(self.ui.name_entry, |
1377 | self.ui.FIELD_REQUIRED) |
1378 | @@ -953,7 +1121,7 @@ |
1379 | self.ui.email1_entry.set_text('') |
1380 | self.ui.email2_entry.set_text('') |
1381 | |
1382 | - self.ui.join_ok_button.clicked() # submit form |
1383 | + self.ui.join_ok_button.clicked() |
1384 | |
1385 | self.assert_correct_entry_warning(self.ui.email1_entry, |
1386 | self.ui.FIELD_REQUIRED) |
1387 | @@ -966,7 +1134,7 @@ |
1388 | self.ui.email1_entry.set_text(EMAIL) |
1389 | self.ui.email2_entry.set_text(EMAIL * 2) |
1390 | |
1391 | - self.ui.join_ok_button.clicked() # submit form |
1392 | + self.ui.join_ok_button.clicked() |
1393 | |
1394 | self.assert_correct_entry_warning(self.ui.email1_entry, |
1395 | self.ui.EMAIL_MISMATCH) |
1396 | @@ -979,7 +1147,7 @@ |
1397 | self.ui.email1_entry.set_text('q') |
1398 | self.ui.email2_entry.set_text('q') |
1399 | |
1400 | - self.ui.join_ok_button.clicked() # submit form |
1401 | + self.ui.join_ok_button.clicked() |
1402 | |
1403 | self.assert_correct_entry_warning(self.ui.email1_entry, |
1404 | self.ui.EMAIL_INVALID) |
1405 | @@ -1000,7 +1168,7 @@ |
1406 | self.ui.password1_entry.set_text(PASSWORD) |
1407 | self.ui.password2_entry.set_text(PASSWORD * 2) |
1408 | |
1409 | - self.ui.join_ok_button.clicked() # submit form |
1410 | + self.ui.join_ok_button.clicked() |
1411 | |
1412 | self.assert_correct_entry_warning(self.ui.password1_entry, |
1413 | self.ui.PASSWORD_MISMATCH) |
1414 | @@ -1015,7 +1183,7 @@ |
1415 | self.ui.password1_entry.set_text(w) |
1416 | self.ui.password2_entry.set_text(w) |
1417 | |
1418 | - self.ui.join_ok_button.clicked() # submit form |
1419 | + self.ui.join_ok_button.clicked() |
1420 | |
1421 | self.assert_correct_entry_warning(self.ui.password1_entry, |
1422 | self.ui.PASSWORD_TOO_WEAK) |
1423 | @@ -1028,7 +1196,7 @@ |
1424 | # don't agree to TC |
1425 | self.ui.yes_to_tc_checkbutton.set_active(False) |
1426 | |
1427 | - self.ui.join_ok_button.clicked() # submit form |
1428 | + self.ui.join_ok_button.clicked() |
1429 | |
1430 | self.assert_correct_label_warning(self.ui.tc_warning_label, |
1431 | self.ui.TC_NOT_ACCEPTED) |
1432 | @@ -1039,18 +1207,18 @@ |
1433 | # captcha solution will be empty |
1434 | self.ui.captcha_solution_entry.set_text('') |
1435 | |
1436 | - self.ui.join_ok_button.clicked() # submit form |
1437 | + self.ui.join_ok_button.clicked() |
1438 | |
1439 | self.assert_correct_entry_warning(self.ui.captcha_solution_entry, |
1440 | self.ui.FIELD_REQUIRED) |
1441 | self.assertNotIn('register_user', self.ui.backend._called) |
1442 | |
1443 | - def test_no_warning_messages_if_valid_data_on_enter_details(self): |
1444 | + def test_no_warning_messages_if_valid_data(self): |
1445 | """No warning messages are shown if the data is valid.""" |
1446 | # this will certainly NOT generate warnings |
1447 | self.click_join_with_valid_data() |
1448 | |
1449 | - self.assert_warnings_visibility(visible=False) |
1450 | + self.assert_warnings_visibility() |
1451 | |
1452 | def test_no_warning_messages_if_valid_data_after_invalid_data(self): |
1453 | """No warnings if the data is valid (with prior invalid data).""" |
1454 | @@ -1060,7 +1228,7 @@ |
1455 | # this will certainly NOT generate warnings |
1456 | self.click_join_with_valid_data() |
1457 | |
1458 | - self.assert_warnings_visibility(visible=False) |
1459 | + self.assert_warnings_visibility() |
1460 | |
1461 | |
1462 | class LoginTestCase(UbuntuSSOClientTestCase): |
1463 | @@ -1136,23 +1304,58 @@ |
1464 | def test_on_login_error_morphs_to_login_page(self): |
1465 | """On user login error, the previous page is shown.""" |
1466 | self.click_connect_with_valid_data() |
1467 | - self.ui.on_login_error(app_name=APP_NAME, error=ERROR) |
1468 | + self.ui.on_login_error(app_name=APP_NAME, error=self.ERROR) |
1469 | self.assert_pages_visibility(login=True) |
1470 | |
1471 | def test_on_login_error_a_warning_is_shown(self): |
1472 | """On user login error, a warning is shown with proper wording.""" |
1473 | self.click_connect_with_valid_data() |
1474 | - self.ui.on_login_error(app_name=APP_NAME, error=ERROR) |
1475 | + self.ui.on_login_error(app_name=APP_NAME, error=self.ERROR) |
1476 | self.assert_correct_label_warning(self.ui.warning_label, |
1477 | self.ui.UNKNOWN_ERROR) |
1478 | |
1479 | + def test_specific_errors_from_backend_are_shown(self): |
1480 | + """Specific errors from backend are used.""" |
1481 | + error = {'errtype': 'AuthenticationError', |
1482 | + 'message': 'We\'re so doomed.', |
1483 | + '__all__': 'We all are gonna die.'} |
1484 | + |
1485 | + self.ui.on_login_error(app_name=APP_NAME, error=error) |
1486 | + |
1487 | + expected = '\n'.join((error['__all__'], error['message'])) |
1488 | + self.assert_correct_label_warning(self.ui.warning_label, expected) |
1489 | + |
1490 | def test_back_to_registration_hides_warning(self): |
1491 | """After user login error, warning is hidden when clicking 'Back'.""" |
1492 | self.click_connect_with_valid_data() |
1493 | - self.ui.on_login_error(app_name=APP_NAME, error=ERROR) |
1494 | + self.ui.on_login_error(app_name=APP_NAME, error=self.ERROR) |
1495 | self.ui.login_back_button.clicked() |
1496 | self.assertFalse(self.ui.warning_label.get_property('visible')) |
1497 | |
1498 | + def test_activate_email_entry_clicks_login(self): |
1499 | + """Activating any entry generates a connect attempt.""" |
1500 | + self.ui.login_ok_button.connect('clicked', self._set_called) |
1501 | + self.ui.login_email_entry.activate() |
1502 | + self.assertEqual(self._called, ((self.ui.login_ok_button,), {})) |
1503 | + |
1504 | + def test_activate_password_entry_clicks_login(self): |
1505 | + """Activating any entry generates a connect attempt.""" |
1506 | + self.ui.login_ok_button.connect('clicked', self._set_called) |
1507 | + self.ui.login_password_entry.activate() |
1508 | + self.assertEqual(self._called, ((self.ui.login_ok_button,), {})) |
1509 | + |
1510 | + def test_login_ok_button_does_nothing_if_clicked_but_disabled(self): |
1511 | + """The join form can only be submitted if the button is sensitive.""" |
1512 | + self.patch(self.ui.login_email_entry, 'get_text', self._set_called) |
1513 | + |
1514 | + self.ui.login_ok_button.set_sensitive(False) |
1515 | + self.ui.login_ok_button.clicked() |
1516 | + self.assertFalse(self._called) |
1517 | + |
1518 | + self.ui.login_ok_button.set_sensitive(True) |
1519 | + self.ui.login_ok_button.clicked() |
1520 | + self.assertTrue(self._called) |
1521 | + |
1522 | |
1523 | class LoginValidationTestCase(UbuntuSSOClientTestCase): |
1524 | """Test suite for the user login validation.""" |
1525 | @@ -1166,7 +1369,7 @@ |
1526 | """A warning message is shown if email is empty.""" |
1527 | self.ui.login_email_entry.set_text('') |
1528 | |
1529 | - self.ui.login_ok_button.clicked() # submit form |
1530 | + self.ui.login_ok_button.clicked() |
1531 | |
1532 | self.assert_correct_entry_warning(self.ui.login_email_entry, |
1533 | self.ui.FIELD_REQUIRED) |
1534 | @@ -1176,7 +1379,7 @@ |
1535 | """A warning message is shown if email is invalid.""" |
1536 | self.ui.login_email_entry.set_text('q') |
1537 | |
1538 | - self.ui.login_ok_button.clicked() # submit form |
1539 | + self.ui.login_ok_button.clicked() |
1540 | |
1541 | self.assert_correct_entry_warning(self.ui.login_email_entry, |
1542 | self.ui.EMAIL_INVALID) |
1543 | @@ -1186,7 +1389,7 @@ |
1544 | """A warning message is shown if password is empty.""" |
1545 | self.ui.login_password_entry.set_text('') |
1546 | |
1547 | - self.ui.login_ok_button.clicked() # submit form |
1548 | + self.ui.login_ok_button.clicked() |
1549 | |
1550 | self.assert_correct_entry_warning(self.ui.login_password_entry, |
1551 | self.ui.FIELD_REQUIRED) |
1552 | @@ -1197,7 +1400,7 @@ |
1553 | # this will certainly NOT generate warnings |
1554 | self.click_connect_with_valid_data() |
1555 | |
1556 | - self.assert_warnings_visibility(visible=False) |
1557 | + self.assert_warnings_visibility() |
1558 | |
1559 | def test_no_warning_messages_if_valid_data_after_invalid_data(self): |
1560 | """No warnings if the data is valid (with prior invalid data).""" |
1561 | @@ -1207,7 +1410,7 @@ |
1562 | # this will certainly NOT generate warnings |
1563 | self.click_connect_with_valid_data() |
1564 | |
1565 | - self.assert_warnings_visibility(visible=False) |
1566 | + self.assert_warnings_visibility() |
1567 | |
1568 | |
1569 | class ResetPasswordTestCase(UbuntuSSOClientTestCase): |
1570 | @@ -1301,11 +1504,86 @@ |
1571 | |
1572 | def test_on_password_reset_error_shows_login_page(self): |
1573 | """When reset token wasn't successfuly sent the login page is shown.""" |
1574 | - self.ui.on_password_reset_error(app_name=APP_NAME, error=ERROR) |
1575 | + self.ui.on_password_reset_error(app_name=APP_NAME, error=self.ERROR) |
1576 | self.assert_correct_label_warning(self.ui.warning_label, |
1577 | self.ui.UNKNOWN_ERROR) |
1578 | self.assert_pages_visibility(login=True) |
1579 | |
1580 | + def test_specific_errors_from_backend_are_shown(self): |
1581 | + """Specific errors from backend are used.""" |
1582 | + error = {'errtype': 'ResetPasswordTokenError', |
1583 | + 'message': 'We\'re so doomed.', |
1584 | + '__all__': 'We all are gonna die.'} |
1585 | + |
1586 | + self.ui.on_password_reset_error(app_name=APP_NAME, error=error) |
1587 | + |
1588 | + expected = '\n'.join((error['__all__'], error['message'])) |
1589 | + self.assert_correct_label_warning(self.ui.warning_label, expected) |
1590 | + |
1591 | + def test_activate_reset_email_entry_clicks_login(self): |
1592 | + """Activating any entry generates a connect attempt.""" |
1593 | + self.ui.request_password_token_ok_button.connect('clicked', |
1594 | + self._set_called) |
1595 | + self.ui.reset_email_entry.activate() |
1596 | + self.assertEqual(self._called, |
1597 | + ((self.ui.request_password_token_ok_button,), {})) |
1598 | + |
1599 | + def test_ok_button_does_nothing_if_clicked_but_disabled(self): |
1600 | + """The password token can be requested if the button is sensitive.""" |
1601 | + self.patch(self.ui.reset_email_entry, 'get_text', self._set_called) |
1602 | + |
1603 | + self.ui.request_password_token_ok_button.set_sensitive(False) |
1604 | + self.ui.request_password_token_ok_button.clicked() |
1605 | + self.assertFalse(self._called) |
1606 | + |
1607 | + self.ui.request_password_token_ok_button.set_sensitive(True) |
1608 | + self.ui.request_password_token_ok_button.clicked() |
1609 | + self.assertTrue(self._called) |
1610 | + |
1611 | + |
1612 | +class ResetPasswordValidationTestCase(UbuntuSSOClientTestCase): |
1613 | + """Test suite for the password reset validations.""" |
1614 | + |
1615 | + def test_warning_is_shown_if_empty_email(self): |
1616 | + """A warning message is shown if emails are empty.""" |
1617 | + self.ui.reset_email_entry.set_text(' ') |
1618 | + |
1619 | + self.ui.request_password_token_ok_button.set_sensitive(True) |
1620 | + self.ui.request_password_token_ok_button.clicked() |
1621 | + |
1622 | + self.assert_correct_entry_warning(self.ui.reset_email_entry, |
1623 | + self.ui.FIELD_REQUIRED) |
1624 | + self.assertNotIn('request_password_reset_token', |
1625 | + self.ui.backend._called) |
1626 | + |
1627 | + def test_warning_is_shown_if_invalid_email(self): |
1628 | + """A warning message is shown if email is invalid.""" |
1629 | + self.ui.reset_email_entry.set_text('q') |
1630 | + |
1631 | + self.ui.request_password_token_ok_button.clicked() |
1632 | + |
1633 | + self.assert_correct_entry_warning(self.ui.reset_email_entry, |
1634 | + self.ui.EMAIL_INVALID) |
1635 | + self.assertNotIn('request_password_reset_token', |
1636 | + self.ui.backend._called) |
1637 | + |
1638 | + def test_no_warning_messages_if_valid_data(self): |
1639 | + """No warning messages are shown if the data is valid.""" |
1640 | + # this will certainly NOT generate warnings |
1641 | + self.click_request_password_token_with_valid_data() |
1642 | + |
1643 | + self.assert_warnings_visibility() |
1644 | + |
1645 | + def test_no_warning_messages_if_valid_data_after_invalid_data(self): |
1646 | + """No warnings if the data is valid (with prior invalid data).""" |
1647 | + # this will certainly generate warnings |
1648 | + self.ui.request_password_token_ok_button.clicked() |
1649 | + |
1650 | + # this will certainly NOT generate warnings |
1651 | + self.click_request_password_token_with_valid_data() |
1652 | + |
1653 | + self.assert_warnings_visibility() |
1654 | + |
1655 | |
1656 | class SetNewPasswordTestCase(UbuntuSSOClientTestCase): |
1657 | """Test suite for setting a new password functionality.""" |
1658 | @@ -1358,11 +1636,123 @@ |
1659 | |
1660 | def test_on_password_change_error_shows_login_page(self): |
1661 | """When password wasn't changed the reset password page is shown.""" |
1662 | - self.ui.on_password_change_error(app_name=APP_NAME, error=ERROR) |
1663 | + self.ui.on_password_change_error(app_name=APP_NAME, error=self.ERROR) |
1664 | self.assert_correct_label_warning(self.ui.warning_label, |
1665 | self.ui.UNKNOWN_ERROR) |
1666 | self.assert_pages_visibility(request_password_token=True) |
1667 | |
1668 | + def test_specific_errors_from_backend_are_shown(self): |
1669 | + """Specific errors from backend are used.""" |
1670 | + error = {'errtype': 'NewPasswordError', |
1671 | + 'message': 'We\'re so doomed.', |
1672 | + '__all__': 'We all are gonna die.'} |
1673 | + |
1674 | + self.ui.on_password_change_error(app_name=APP_NAME, error=error) |
1675 | + |
1676 | + expected = '\n'.join((error['__all__'], error['message'])) |
1677 | + self.assert_correct_label_warning(self.ui.warning_label, expected) |
1678 | + |
1679 | + def test_activate_reset_code_entry_clicks_set_new_password(self): |
1680 | + """Activating any entry generates a connect attempt.""" |
1681 | + self.ui.set_new_password_ok_button.connect('clicked', self._set_called) |
1682 | + self.ui.reset_code_entry.activate() |
1683 | + self.assertEqual(self._called, |
1684 | + ((self.ui.set_new_password_ok_button,), {})) |
1685 | + |
1686 | + def test_activate_password_entry_clicks_set_new_password(self): |
1687 | + """Activating any entry generates a connect attempt.""" |
1688 | + self.ui.set_new_password_ok_button.connect('clicked', self._set_called) |
1689 | + self.ui.reset_password1_entry.activate() |
1690 | + self.assertEqual(self._called, |
1691 | + ((self.ui.set_new_password_ok_button,), {})) |
1692 | + |
1693 | + self._called = False |
1694 | + self.ui.reset_password2_entry.activate() |
1695 | + self.assertEqual(self._called, |
1696 | + ((self.ui.set_new_password_ok_button,), {})) |
1697 | + |
1698 | + def test_ok_button_does_nothing_if_clicked_but_disabled(self): |
1699 | + """The new passwrd can only be set if the button is sensitive.""" |
1700 | + self.patch(self.ui.reset_code_entry, 'get_text', self._set_called) |
1701 | + |
1702 | + self.ui.set_new_password_ok_button.set_sensitive(False) |
1703 | + self.ui.set_new_password_ok_button.clicked() |
1704 | + self.assertFalse(self._called) |
1705 | + |
1706 | + self.ui.set_new_password_ok_button.set_sensitive(True) |
1707 | + self.ui.set_new_password_ok_button.clicked() |
1708 | + self.assertTrue(self._called) |
1709 | + |
1710 | + |
1711 | +class SetNewPasswordValidationTestCase(UbuntuSSOClientTestCase): |
1712 | + """Test suite for validations for setting a new password.""" |
1713 | + |
1714 | + def test_warning_is_shown_if_reset_code_empty(self): |
1715 | + """A warning message is shown if reset_code is empty.""" |
1716 | + self.ui.reset_code_entry.set_text('') |
1717 | + |
1718 | + self.ui.set_new_password_ok_button.set_sensitive(True) |
1719 | + self.ui.set_new_password_ok_button.clicked() |
1720 | + |
1721 | + self.assert_correct_entry_warning(self.ui.reset_code_entry, |
1722 | + self.ui.FIELD_REQUIRED) |
1723 | + self.assertNotIn('set_new_password', self.ui.backend._called) |
1724 | + |
1725 | + def test_password_help_is_always_shown(self): |
1726 | + """Password help text is correctly displayed.""" |
1727 | + visible = self.ui.reset_password_help_label.get_property('visible') |
1728 | + self.assertTrue(visible, 'password help text is visible.') |
1729 | + self.assertEqual(self.ui.reset_password_help_label.get_text(), |
1730 | + self.ui.PASSWORD_HELP) |
1731 | + self.assertNotIn('set_new_password', self.ui.backend._called) |
1732 | + |
1733 | + def test_warning_is_shown_if_password_mismatch(self): |
1734 | + """A warning message is shown if password doesn't match.""" |
1735 | + self.ui.reset_password1_entry.set_text(PASSWORD) |
1736 | + self.ui.reset_password2_entry.set_text(PASSWORD * 2) |
1737 | + |
1738 | + self.ui.set_new_password_ok_button.set_sensitive(True) |
1739 | + self.ui.set_new_password_ok_button.clicked() |
1740 | + |
1741 | + self.assert_correct_entry_warning(self.ui.reset_password1_entry, |
1742 | + self.ui.PASSWORD_MISMATCH) |
1743 | + self.assert_correct_entry_warning(self.ui.reset_password2_entry, |
1744 | + self.ui.PASSWORD_MISMATCH) |
1745 | + self.assertNotIn('set_new_password', self.ui.backend._called) |
1746 | + |
1747 | + def test_warning_is_shown_if_password_too_weak(self): |
1748 | + """A warning message is shown if password is too weak.""" |
1749 | + # password will match but will be too weak |
1750 | + for w in ('', 'h3lloWo', PASSWORD.lower(), 'helloWorld'): |
1751 | + self.ui.reset_password1_entry.set_text(w) |
1752 | + self.ui.reset_password2_entry.set_text(w) |
1753 | + |
1754 | + self.ui.set_new_password_ok_button.set_sensitive(True) |
1755 | + self.ui.set_new_password_ok_button.clicked() |
1756 | + |
1757 | + self.assert_correct_entry_warning(self.ui.reset_password1_entry, |
1758 | + self.ui.PASSWORD_TOO_WEAK) |
1759 | + self.assert_correct_entry_warning(self.ui.reset_password2_entry, |
1760 | + self.ui.PASSWORD_TOO_WEAK) |
1761 | + self.assertNotIn('set_new_password', self.ui.backend._called) |
1762 | + |
1763 | + def test_no_warning_messages_if_valid_data(self): |
1764 | + """No warning messages are shown if the data is valid.""" |
1765 | + # this will certainly NOT generate warnings |
1766 | + self.click_set_new_password_with_valid_data() |
1767 | + |
1768 | + self.assert_warnings_visibility() |
1769 | + |
1770 | + def test_no_warning_messages_if_valid_data_after_invalid_data(self): |
1771 | + """No warnings if the data is valid (with prior invalid data).""" |
1772 | + # this will certainly generate warnings |
1773 | + self.ui.set_new_password_ok_button.clicked() |
1774 | + |
1775 | + # this will certainly NOT generate warnings |
1776 | + self.click_set_new_password_with_valid_data() |
1777 | + |
1778 | + self.assert_warnings_visibility() |
1779 | + |
1780 | |
1781 | class DbusTestCase(UbuntuSSOClientTestCase): |
1782 | """Test suite for the dbus calls.""" |
1783 | @@ -1408,6 +1798,10 @@ |
1784 | """The login_ok_button has the focus.""" |
1785 | self.assertTrue(self.ui.login_ok_button.is_focus()) |
1786 | |
1787 | + def test_help_text_is_used(self): |
1788 | + """The passed help_text is used.""" |
1789 | + self.assertEqual(self.ui.help_label.get_text(), HELP_TEXT) |
1790 | + |
1791 | |
1792 | class SignalsTestCase(UbuntuSSOClientTestCase): |
1793 | |
1794 | @@ -1438,9 +1832,10 @@ |
1795 | |
1796 | def test_on_user_registration_error_proper_signal_is_emitted(self): |
1797 | """On UserRegistrationError, 'registration-failed' signal is sent.""" |
1798 | - self.ui.on_user_registration_error(app_name=APP_NAME, error=ERROR) |
1799 | + self.ui.on_user_registration_error(app_name=APP_NAME, error=self.ERROR) |
1800 | self.ui.on_close_clicked() |
1801 | - self.assertEqual((self.ui.window, (APP_NAME, ERROR), {}), |
1802 | + expected = (self.ui.window, (APP_NAME, self.ui.UNKNOWN_ERROR), {}) |
1803 | + self.assertEqual(expected, |
1804 | self._called[gui.SIG_REGISTRATION_FAILED]) |
1805 | |
1806 | def test_on_email_validated_proper_signals_is_emitted(self): |
1807 | @@ -1452,10 +1847,11 @@ |
1808 | |
1809 | def test_on_email_validation_error_proper_signals_is_emitted(self): |
1810 | """On EmailValidationError, 'registration-failed' signal is sent.""" |
1811 | - self.ui.on_email_validation_error(app_name=APP_NAME, error=ERROR) |
1812 | + self.ui.on_email_validation_error(app_name=APP_NAME, error=self.ERROR) |
1813 | self.ui.on_close_clicked() |
1814 | - self.assertEqual((self.ui.window, (APP_NAME, ERROR), {}), |
1815 | - self._called[gui.SIG_REGISTRATION_FAILED]) |
1816 | + expected = (self.ui.window, (APP_NAME, self.ui.UNKNOWN_ERROR), {}) |
1817 | + self.assertEqual(expected, |
1818 | + self._called[gui.SIG_REGISTRATION_FAILED]) |
1819 | |
1820 | def test_on_logged_in_proper_signals_is_emitted(self): |
1821 | """On LoggedIn, 'login-succeeded' signal is sent.""" |
1822 | @@ -1467,15 +1863,16 @@ |
1823 | def test_on_login_error_proper_signals_is_emitted(self): |
1824 | """On LoginError, 'login-failed' signal is sent.""" |
1825 | self.click_connect_with_valid_data() |
1826 | - self.ui.on_login_error(app_name=APP_NAME, error=ERROR) |
1827 | + self.ui.on_login_error(app_name=APP_NAME, error=self.ERROR) |
1828 | self.ui.on_close_clicked() |
1829 | - self.assertEqual((self.ui.window, (APP_NAME, ERROR), {}), |
1830 | - self._called[gui.SIG_LOGIN_FAILED]) |
1831 | + expected = (self.ui.window, (APP_NAME, self.ui.UNKNOWN_ERROR), {}) |
1832 | + self.assertEqual(expected, |
1833 | + self._called[gui.SIG_LOGIN_FAILED]) |
1834 | |
1835 | def test_registration_successfull_even_if_prior_registration_error(self): |
1836 | """Only one signal is sent with the final outcome.""" |
1837 | self.click_join_with_valid_data() |
1838 | - self.ui.on_user_registration_error(app_name=APP_NAME, error=ERROR) |
1839 | + self.ui.on_user_registration_error(app_name=APP_NAME, error=self.ERROR) |
1840 | self.click_join_with_valid_data() |
1841 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
1842 | self.ui.on_close_clicked() |
1843 | @@ -1486,7 +1883,7 @@ |
1844 | def test_login_successfull_even_if_prior_login_error(self): |
1845 | """Only one signal is sent with the final outcome.""" |
1846 | self.click_connect_with_valid_data() |
1847 | - self.ui.on_login_error(app_name=APP_NAME, error=ERROR) |
1848 | + self.ui.on_login_error(app_name=APP_NAME, error=self.ERROR) |
1849 | self.click_connect_with_valid_data() |
1850 | self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
1851 | self.ui.on_close_clicked() |
1852 | @@ -1497,7 +1894,7 @@ |
1853 | def test_user_cancelation_even_if_prior_registration_error(self): |
1854 | """Only one signal is sent with the final outcome.""" |
1855 | self.click_join_with_valid_data() |
1856 | - self.ui.on_user_registration_error(app_name=APP_NAME, error=ERROR) |
1857 | + self.ui.on_user_registration_error(app_name=APP_NAME, error=self.ERROR) |
1858 | self.ui.join_cancel_button.clicked() |
1859 | |
1860 | self.assertEqual(len(self._called), 1) |
1861 | @@ -1506,7 +1903,7 @@ |
1862 | def test_user_cancelation_even_if_prior_login_error(self): |
1863 | """Only one signal is sent with the final outcome.""" |
1864 | self.click_connect_with_valid_data() |
1865 | - self.ui.on_login_error(app_name=APP_NAME, error=ERROR) |
1866 | + self.ui.on_login_error(app_name=APP_NAME, error=self.ERROR) |
1867 | self.ui.login_cancel_button.clicked() |
1868 | |
1869 | self.assertEqual(len(self._called), 1) |
1870 | |
1871 | === modified file 'ubuntu_sso/tests/test_keyring.py' |
1872 | --- ubuntu_sso/tests/test_keyring.py 2010-08-19 16:02:09 +0000 |
1873 | +++ ubuntu_sso/tests/test_keyring.py 2010-08-27 22:32:43 +0000 |
1874 | @@ -17,24 +17,39 @@ |
1875 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
1876 | """Tests for the keyring.py module.""" |
1877 | |
1878 | +import itertools |
1879 | +import socket |
1880 | +import urllib |
1881 | + |
1882 | import gnomekeyring |
1883 | from twisted.trial.unittest import TestCase |
1884 | |
1885 | from ubuntu_sso import keyring |
1886 | |
1887 | |
1888 | -class MockInfoSync(object): |
1889 | - """A mock object that represents a fake key.""" |
1890 | - |
1891 | - def __init__(self, (key, key_name, secret)): |
1892 | +def build_fake_gethostname(fake_hostname): |
1893 | + """Return a fake hostname getter.""" |
1894 | + return lambda *a: fake_hostname |
1895 | + |
1896 | + |
1897 | +class MockKeyringItem(object): |
1898 | + """A mock object that fakes an item found in a keyring search.""" |
1899 | + |
1900 | + def __init__(self, item_id, keyring, attributes, secret): |
1901 | """Initialize this instance.""" |
1902 | - self.key = key |
1903 | - self.key_name = key_name |
1904 | + self.item_id = item_id |
1905 | + self.keyring = keyring |
1906 | + self.attributes = attributes |
1907 | self.secret = secret |
1908 | |
1909 | - def get_display_name(self): |
1910 | - """Return info on the mocked object.""" |
1911 | - return self.key_name |
1912 | + def matches(self, search_attr): |
1913 | + """See if this item matches a given search.""" |
1914 | + for k, v in search_attr.items(): |
1915 | + if k not in self.attributes: |
1916 | + return False |
1917 | + if self.attributes[k] != v: |
1918 | + return False |
1919 | + return True |
1920 | |
1921 | |
1922 | class MockGnomeKeyring(object): |
1923 | @@ -43,28 +58,33 @@ |
1924 | def __init__(self): |
1925 | """Initialize this instance.""" |
1926 | self.keyrings = set() |
1927 | - self.store = [] |
1928 | + self.id_counter = itertools.count() |
1929 | + self.store = {} |
1930 | self.deleted = [] |
1931 | |
1932 | + def _get_next_id(self): |
1933 | + """Return the next keyring id.""" |
1934 | + return self.id_counter.next() |
1935 | + |
1936 | def item_create_sync(self, keyring_name, item_type, key_name, attr, |
1937 | secret, update_if_exists): |
1938 | - """Sets a value in the keyring.""" |
1939 | - # we'll use the attr as the dict key, so make it a tuple |
1940 | - key = tuple(attr.items()) |
1941 | - self.store.append((key, key_name, secret)) |
1942 | + """Add a key to a keyring.""" |
1943 | + new_id = self._get_next_id() |
1944 | + i = MockKeyringItem(new_id, keyring_name, attr, secret) |
1945 | + self.store[new_id] = i |
1946 | |
1947 | def item_delete_sync(self, keyring_name, item_id): |
1948 | - """Makes a list of deleted items.""" |
1949 | - key, key_name, secret = self.store.pop(item_id) |
1950 | - self.deleted.append(key_name) |
1951 | - |
1952 | - def list_item_ids_sync(self, keyring_name): |
1953 | - """Return a list of ids for all the items.""" |
1954 | - return [n for n, v in enumerate(self.store)] |
1955 | - |
1956 | - def item_get_info_sync(self, keyring_name, item_id): |
1957 | - """Return an info sync object for the item.""" |
1958 | - return MockInfoSync(self.store[item_id]) |
1959 | + """Delete a key from a keyring, and keep a list of deleted keys.""" |
1960 | + item = self.store.pop(item_id) |
1961 | + assert keyring_name == item.keyring |
1962 | + self.deleted.append(item) |
1963 | + |
1964 | + def find_items_sync(self, item_type, attr): |
1965 | + """Find all keys that match the given attributes.""" |
1966 | + items = [i for i in self.store.values() if i.matches(attr)] |
1967 | + if len(items) == 0: |
1968 | + raise gnomekeyring.NoMatchError() |
1969 | + return items |
1970 | |
1971 | def list_keyring_names_sync(self): |
1972 | """The keyring you are looking for may be here.""" |
1973 | @@ -79,6 +99,32 @@ |
1974 | return True |
1975 | |
1976 | |
1977 | +class TestTokenNameBuilder(TestCase): |
1978 | + """Test the method that builds the token name.""" |
1979 | + |
1980 | + def test_get_simple_token_name(self): |
1981 | + """A simple token name is built right.""" |
1982 | + sample_app_name = "UbuntuTwo" |
1983 | + sample_hostname = "Darkstar" |
1984 | + expected_result = "UbuntuTwo - Darkstar" |
1985 | + |
1986 | + fake_gethostname = build_fake_gethostname(sample_hostname) |
1987 | + self.patch(socket, "gethostname", fake_gethostname) |
1988 | + result = keyring.get_token_name(sample_app_name) |
1989 | + self.assertEqual(result, expected_result) |
1990 | + |
1991 | + def test_get_complex_token_name(self): |
1992 | + """A complex token name is built right too.""" |
1993 | + sample_app_name = "Ubuntu Eleven" |
1994 | + sample_hostname = "Mate+Cocido" |
1995 | + expected_result = "Ubuntu%20Eleven - Mate%2BCocido" |
1996 | + |
1997 | + fake_gethostname = build_fake_gethostname(sample_hostname) |
1998 | + self.patch(socket, "gethostname", fake_gethostname) |
1999 | + result = keyring.get_token_name(sample_app_name) |
2000 | + self.assertEqual(result, expected_result) |
2001 | + |
2002 | + |
2003 | class TestKeyring(TestCase): |
2004 | """Test the gnome keyring related functions.""" |
2005 | def setUp(self): |
2006 | @@ -89,11 +135,10 @@ |
2007 | self.patch(gnomekeyring, "create_sync", self.mgk.create_sync) |
2008 | self.patch(gnomekeyring, "list_keyring_names_sync", |
2009 | self.mgk.list_keyring_names_sync) |
2010 | - self.patch(gnomekeyring, "list_item_ids_sync", |
2011 | - self.mgk.list_item_ids_sync) |
2012 | - self.patch(gnomekeyring, "item_get_info_sync", |
2013 | - self.mgk.item_get_info_sync) |
2014 | + self.patch(gnomekeyring, "find_items_sync", self.mgk.find_items_sync) |
2015 | self.patch(gnomekeyring, "item_delete_sync", self.mgk.item_delete_sync) |
2016 | + fake_gethostname = build_fake_gethostname("darkstar") |
2017 | + self.patch(socket, "gethostname", fake_gethostname) |
2018 | |
2019 | def test_set_ubuntusso(self): |
2020 | """Test that the set method does not erase previous keys.""" |
2021 | @@ -113,3 +158,54 @@ |
2022 | |
2023 | self.assertEqual(len(self.mgk.store), 0) |
2024 | self.assertEqual(len(self.mgk.deleted), 1) |
2025 | + |
2026 | + def test_get_credentials(self): |
2027 | + """Test that credentials are properly retrieved.""" |
2028 | + sample_creds = {"name": "sample creds name"} |
2029 | + keyring.Keyring("appname").set_ubuntusso_attr(sample_creds) |
2030 | + |
2031 | + result = keyring.Keyring("appname").get_ubuntusso_attr() |
2032 | + self.assertEqual(result, sample_creds) |
2033 | + |
2034 | + def test_get_old_cred_found(self): |
2035 | + """The method returns a new set of creds if old creds are found.""" |
2036 | + sample_oauth_token = "sample oauth token" |
2037 | + sample_oauth_secret = "sample oauth secret" |
2038 | + old_creds = { |
2039 | + "oauth_token": sample_oauth_token, |
2040 | + "oauth_token_secret": sample_oauth_secret, |
2041 | + } |
2042 | + secret = urllib.urlencode(old_creds) |
2043 | + self.mgk.item_create_sync(keyring.U1_APP_NAME, None, |
2044 | + keyring.Keyring.KEYRING_NAME, |
2045 | + keyring.U1_KEY_ATTR, |
2046 | + secret, True) |
2047 | + |
2048 | + result = keyring.Keyring(keyring.U1_APP_NAME).get_ubuntusso_attr() |
2049 | + |
2050 | + self.assertIn("token", result) |
2051 | + self.assertEqual(result["token"], sample_oauth_token) |
2052 | + self.assertIn("token_secret", result) |
2053 | + self.assertEqual(result["token_secret"], sample_oauth_secret) |
2054 | + |
2055 | + def test_get_old_cred_found_but_not_asked_for(self): |
2056 | + """Returns None if old creds are present but the appname is not U1""" |
2057 | + sample_oauth_token = "sample oauth token" |
2058 | + sample_oauth_secret = "sample oauth secret" |
2059 | + old_creds = { |
2060 | + "oauth_token": sample_oauth_token, |
2061 | + "oauth_token_secret": sample_oauth_secret, |
2062 | + } |
2063 | + secret = urllib.urlencode(old_creds) |
2064 | + self.mgk.item_create_sync(keyring.U1_APP_NAME, None, |
2065 | + keyring.Keyring.KEYRING_NAME, |
2066 | + keyring.U1_KEY_ATTR, |
2067 | + secret, True) |
2068 | + |
2069 | + result = keyring.Keyring("Software Center").get_ubuntusso_attr() |
2070 | + self.assertEqual(result, None) |
2071 | + |
2072 | + def test_get_old_cred_not_found(self): |
2073 | + """The method returns None if no old nor new credentials found.""" |
2074 | + result = keyring.Keyring(keyring.U1_APP_NAME).get_ubuntusso_attr() |
2075 | + self.assertEqual(result, None) |
2076 | |
2077 | === modified file 'ubuntu_sso/tests/test_main.py' |
2078 | --- ubuntu_sso/tests/test_main.py 2010-08-26 01:07:41 +0000 |
2079 | +++ ubuntu_sso/tests/test_main.py 2010-08-27 22:32:43 +0000 |
2080 | @@ -31,19 +31,19 @@ |
2081 | from twisted.internet.defer import Deferred |
2082 | from twisted.trial.unittest import TestCase |
2083 | |
2084 | +import ubuntu_sso.keyring |
2085 | import ubuntu_sso.main |
2086 | |
2087 | from contrib.testing.testcase import MementoHandler |
2088 | from ubuntu_sso import config, gui |
2089 | +from ubuntu_sso.keyring import get_token_name, U1_APP_NAME |
2090 | from ubuntu_sso.main import ( |
2091 | AuthenticationError, BadRealmError, blocking, EmailTokenError, |
2092 | - except_to_errdict, get_token_name, |
2093 | - InvalidEmailError, InvalidPasswordError, |
2094 | + except_to_errdict, InvalidEmailError, InvalidPasswordError, |
2095 | keyring_get_credentials, keyring_store_credentials, logger, |
2096 | - LoginProcessor, NewPasswordError, OLD_KEY_NAME, PING_URL, |
2097 | + LoginProcessor, NewPasswordError, PING_URL, |
2098 | RegistrationError, ResetPasswordTokenError, |
2099 | - SSOCredentials, SSOLogin, SSOLoginProcessor, |
2100 | - U1_APP_NAME) |
2101 | + SSOCredentials, SSOLogin, SSOLoginProcessor) |
2102 | |
2103 | |
2104 | APP_NAME = 'The Coolest App Ever' |
2105 | @@ -55,6 +55,7 @@ |
2106 | "Can't reset password for this account" |
2107 | RESET_TOKEN_INVALID_CONTENT = "AuthToken matching query does not exist." |
2108 | EMAIL = 'test@example.com' |
2109 | +EMAIL_ALREADY_REGISTERED = 'a@example.com' |
2110 | EMAIL_TOKEN = 'B2Pgtf' |
2111 | HELP = 'help text' |
2112 | PASSWORD = 'be4tiFul' |
2113 | @@ -103,7 +104,10 @@ |
2114 | |
2115 | def register(self, email, password, captcha_id, captcha_solution): |
2116 | """Fake registration. Return a fix result.""" |
2117 | - if captcha_id is None and captcha_solution is None: |
2118 | + if email == EMAIL_ALREADY_REGISTERED: |
2119 | + return {'status': 'error', |
2120 | + 'errors': {'email': 'Email already registered'}} |
2121 | + elif captcha_id is None and captcha_solution is None: |
2122 | return STATUS_UNKNOWN |
2123 | elif captcha_id != CAPTCHA_ID or captcha_solution != CAPTCHA_SOLUTION: |
2124 | return STATUS_ERROR |
2125 | @@ -147,7 +151,10 @@ |
2126 | """Fake the email validation. Return a fix result.""" |
2127 | if email_token is None: |
2128 | return STATUS_EMAIL_UNKNOWN |
2129 | - if email_token != EMAIL_TOKEN: |
2130 | + elif email_token == EMAIL_ALREADY_REGISTERED: |
2131 | + return {'status': 'error', |
2132 | + 'errors': {'email': 'Email already registered'}} |
2133 | + elif email_token != EMAIL_TOKEN: |
2134 | return STATUS_EMAIL_ERROR |
2135 | else: |
2136 | return STATUS_EMAIL_OK |
2137 | @@ -231,7 +238,19 @@ |
2138 | failure = self.assertRaises(RegistrationError, |
2139 | self.processor.register_user, |
2140 | **self.register_kwargs) |
2141 | - self.assertIn(STATUS_ERROR['errors'], failure) |
2142 | + for k, v in failure.args[0].items(): |
2143 | + self.assertIn(k, STATUS_ERROR['errors']) |
2144 | + self.assertEqual(v, "\n".join(STATUS_ERROR['errors'][k])) |
2145 | + |
2146 | + def test_register_user_if_status_error_with_string_message(self): |
2147 | + """Proper error is raised if register fails.""" |
2148 | + self.register_kwargs['email'] = EMAIL_ALREADY_REGISTERED |
2149 | + failure = self.assertRaises(RegistrationError, |
2150 | + self.processor.register_user, |
2151 | + **self.register_kwargs) |
2152 | + for k, v in failure.args[0].items(): |
2153 | + self.assertIn(k, {'email': 'Email already registered'}) |
2154 | + self.assertEqual(v, 'Email already registered') |
2155 | |
2156 | def test_register_user_if_status_unknown(self): |
2157 | """Proper error is raised if register returns an unknown status.""" |
2158 | @@ -261,7 +280,7 @@ |
2159 | """A email is succesfuy validated in the SSO server.""" |
2160 | self.login_kwargs['email_token'] = EMAIL_TOKEN # valid email token |
2161 | result = self.processor.validate_email(**self.login_kwargs) |
2162 | - self.assertEqual(EMAIL, result, 'email validation was successful.') |
2163 | + self.assertEqual(TOKEN, result, 'email validation was successful.') |
2164 | |
2165 | def test_validate_email_if_status_error(self): |
2166 | """Proper error is raised if email validation fails.""" |
2167 | @@ -269,7 +288,19 @@ |
2168 | failure = self.assertRaises(EmailTokenError, |
2169 | self.processor.validate_email, |
2170 | **self.login_kwargs) |
2171 | - self.assertIn(STATUS_EMAIL_ERROR['errors'], failure) |
2172 | + for k, v in failure.args[0].items(): |
2173 | + self.assertIn(k, STATUS_EMAIL_ERROR['errors']) |
2174 | + self.assertEqual(v, "\n".join(STATUS_EMAIL_ERROR['errors'][k])) |
2175 | + |
2176 | + def test_validate_email_if_status_error_with_string_message(self): |
2177 | + """Proper error is raised if register fails.""" |
2178 | + self.login_kwargs['email_token'] = EMAIL_ALREADY_REGISTERED |
2179 | + failure = self.assertRaises(EmailTokenError, |
2180 | + self.processor.validate_email, |
2181 | + **self.login_kwargs) |
2182 | + for k, v in failure.args[0].items(): |
2183 | + self.assertIn(k, {'email': 'Email already registered'}) |
2184 | + self.assertEqual(v, 'Email already registered') |
2185 | |
2186 | def test_validate_email_if_status_unknown(self): |
2187 | """Proper error is raised if email validation returns unknown.""" |
2188 | @@ -413,8 +444,11 @@ |
2189 | """Assert over token and app_name.""" |
2190 | self.assertEqual(k, APP_NAME) |
2191 | self.assertEqual(v, TOKEN) |
2192 | + self.keyring_was_set = True |
2193 | + self.keyring_values = k, v |
2194 | |
2195 | self.patch(ubuntu_sso.main, "keyring_store_credentials", ksc) |
2196 | + self.keyring_was_set = False |
2197 | |
2198 | def tearDown(self): |
2199 | """Verify the mocking bus and shut it down.""" |
2200 | @@ -427,8 +461,12 @@ |
2201 | |
2202 | def fake_err_blocking(self, f, a, cb, eb): |
2203 | """A fake blocking function that fails.""" |
2204 | - f() |
2205 | - eb(a, except_to_errdict(BlockingSampleException())) |
2206 | + try: |
2207 | + f() |
2208 | + except Exception, e: |
2209 | + eb(a, except_to_errdict(e)) |
2210 | + else: |
2211 | + eb(a, except_to_errdict(BlockingSampleException())) |
2212 | |
2213 | def test_creation(self): |
2214 | """Test that the object creation is successful.""" |
2215 | @@ -544,6 +582,7 @@ |
2216 | def verify(app_name, result): |
2217 | self.assertEqual(result, EMAIL) |
2218 | self.assertEqual(app_name, APP_NAME) |
2219 | + self.assertTrue(self.keyring_was_set, "The keyring should be set") |
2220 | d.callback(result) |
2221 | |
2222 | client = SSOLogin(self.mockbusname, |
2223 | @@ -556,14 +595,20 @@ |
2224 | def test_login_error(self): |
2225 | """Test that the login method fails as expected.""" |
2226 | d = Deferred() |
2227 | - self.create_mock_processor().login(EMAIL, PASSWORD, TOKEN_NAME) |
2228 | - self.mocker.result(TOKEN) |
2229 | + self.mockprocessorclass = self.mocker.mock() |
2230 | self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking) |
2231 | + |
2232 | + def fake_gtn(*args): |
2233 | + """A fake get_token_name that fails.""" |
2234 | + raise BlockingSampleException() |
2235 | + |
2236 | + self.patch(ubuntu_sso.main, "get_token_name", fake_gtn) |
2237 | self.mocker.replay() |
2238 | |
2239 | def verify(app_name, errdict): |
2240 | self.assertEqual(app_name, APP_NAME) |
2241 | self.assertEqual(errdict["errtype"], "BlockingSampleException") |
2242 | + self.assertFalse(self.keyring_was_set, "Keyring should not be set") |
2243 | d.callback("Ok") |
2244 | |
2245 | client = SSOLogin(self.mockbusname, |
2246 | @@ -578,13 +623,14 @@ |
2247 | d = Deferred() |
2248 | self.create_mock_processor().validate_email(EMAIL, PASSWORD, |
2249 | EMAIL_TOKEN, TOKEN_NAME) |
2250 | - self.mocker.result(EMAIL) |
2251 | + self.mocker.result(TOKEN) |
2252 | self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking) |
2253 | self.mocker.replay() |
2254 | |
2255 | def verify(app_name, result): |
2256 | self.assertEqual(result, EMAIL) |
2257 | self.assertEqual(app_name, APP_NAME) |
2258 | + self.assertTrue(self.keyring_was_set, "The keyring should be set") |
2259 | d.callback(result) |
2260 | |
2261 | client = SSOLogin(self.mockbusname, |
2262 | @@ -597,17 +643,20 @@ |
2263 | def test_validate_email_error(self): |
2264 | """Test that the validate_email method fails as expected.""" |
2265 | d = Deferred() |
2266 | - expected_result = "expected result" |
2267 | - |
2268 | - self.create_mock_processor().validate_email(EMAIL, PASSWORD, |
2269 | - EMAIL_TOKEN, TOKEN_NAME) |
2270 | - self.mocker.result(expected_result) |
2271 | + self.mockprocessorclass = self.mocker.mock() |
2272 | self.patch(ubuntu_sso.main, "blocking", self.fake_err_blocking) |
2273 | + |
2274 | + def fake_gtn(*args): |
2275 | + """A fake get_token_name that fails.""" |
2276 | + raise BlockingSampleException() |
2277 | + |
2278 | + self.patch(ubuntu_sso.main, "get_token_name", fake_gtn) |
2279 | self.mocker.replay() |
2280 | |
2281 | def verify(app_name, errdict): |
2282 | self.assertEqual(app_name, APP_NAME) |
2283 | self.assertEqual(errdict["errtype"], "BlockingSampleException") |
2284 | + self.assertFalse(self.keyring_was_set, "Keyring should not be set") |
2285 | d.callback("Ok") |
2286 | |
2287 | client = SSOLogin(self.mockbusname, |
2288 | @@ -794,7 +843,6 @@ |
2289 | e = TestExceptToErrdictException(*sample_args) |
2290 | result = except_to_errdict(e) |
2291 | self.assertEqual(result["errtype"], e.__class__.__name__) |
2292 | - self.assertEqual(result["errargs"], repr(sample_args)) |
2293 | |
2294 | |
2295 | class KeyringCredentialsTestCase(MockerTestCase): |
2296 | @@ -838,58 +886,6 @@ |
2297 | token = keyring_get_credentials(APP_NAME) |
2298 | self.assertEqual(token, None) |
2299 | |
2300 | - def test_keyring_get_old_cred_found(self): |
2301 | - """The method returns a new set of creds if old creds are found.""" |
2302 | - sample_oauth_token = "sample oauth token" |
2303 | - sample_oauth_secret = "sample oauth secret" |
2304 | - old_creds = { |
2305 | - "oauth_token": sample_oauth_token, |
2306 | - "oauth_token_secret": sample_oauth_secret, |
2307 | - } |
2308 | - |
2309 | - mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring") |
2310 | - mockKeyringClass(U1_APP_NAME) |
2311 | - mockKeyring = self.mocker.mock() |
2312 | - self.mocker.result(mockKeyring) |
2313 | - mockKeyring.get_ubuntusso_attr() |
2314 | - self.mocker.result(None) |
2315 | - |
2316 | - mockKeyringClass(OLD_KEY_NAME) |
2317 | - mockKeyring2 = self.mocker.mock() |
2318 | - self.mocker.result(mockKeyring2) |
2319 | - mockKeyring2.get_ubuntusso_attr() |
2320 | - self.mocker.result(old_creds) |
2321 | - |
2322 | - self.mocker.replay() |
2323 | - |
2324 | - new_creds = keyring_get_credentials(U1_APP_NAME) |
2325 | - self.assertIn("token", new_creds) |
2326 | - self.assertEqual(new_creds["token"], sample_oauth_token) |
2327 | - self.assertIn("token_secret", new_creds) |
2328 | - self.assertEqual(new_creds["token_secret"], sample_oauth_secret) |
2329 | - self.assertIn("token_name", new_creds) |
2330 | - self.assertEqual(new_creds["token_name"], OLD_KEY_NAME) |
2331 | - |
2332 | - def test_keyring_get_old_cred_not_found(self): |
2333 | - """The method returns None if no old nor new credentials found.""" |
2334 | - mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring") |
2335 | - mockKeyringClass(U1_APP_NAME) |
2336 | - mockKeyring = self.mocker.mock() |
2337 | - self.mocker.result(mockKeyring) |
2338 | - mockKeyring.get_ubuntusso_attr() |
2339 | - self.mocker.result(None) |
2340 | - |
2341 | - mockKeyringClass(OLD_KEY_NAME) |
2342 | - mockKeyring2 = self.mocker.mock() |
2343 | - self.mocker.result(mockKeyring2) |
2344 | - mockKeyring2.get_ubuntusso_attr() |
2345 | - self.mocker.result(None) |
2346 | - |
2347 | - self.mocker.replay() |
2348 | - |
2349 | - token = keyring_get_credentials(U1_APP_NAME) |
2350 | - self.assertEqual(token, None) |
2351 | - |
2352 | |
2353 | class RegisterSampleException(Exception): |
2354 | """A mock exception thrown just when testing.""" |