1
# -*- coding: utf-8 -*-
3
# Copyright 2010-2012 Canonical Ltd.
5
# This program is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License version 3, as published
7
# by the Free Software Foundation.
9
# This program is distributed in the hope that it will be useful, but
10
# WITHOUT ANY WARRANTY; without even the implied warranties of
11
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12
# PURPOSE. See the GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License along
15
# with this program. If not, see <http://www.gnu.org/licenses/>.
17
"""Tests for the GUI for registration/login."""
23
from collections import defaultdict
24
from functools import partial
25
from unittest import skip, TestCase
27
# pylint: disable=E0611
28
from gi.repository import Gdk, Gtk, WebKit
29
# pylint: enable=E0611
30
from mock import patch
32
from softwarecenter.sso import gui
33
from softwarecenter.sso.tests import (
48
# Access to a protected member 'yyy' of a client class
49
# pylint: disable=W0212
51
# Instance of 'UbuntuSSOClientGUI' has no 'yyy' member
52
# pylint: disable=E1101,E1103
54
# Use of super on an old style class
55
# pylint: disable=E1002
58
class FakedSSOBackend(object):
59
"""Fake a SSO Backend."""
61
def __init__(self, *args, **kwargs):
65
self.callbacks = defaultdict(list)
67
for i in ('generate_captcha', 'login', 'login_and_ping',
68
'register_user', 'validate_email',
69
'validate_email_and_ping',
70
'request_password_reset_token',
72
setattr(self, i, self._record_call(i))
74
def connect_to_signal(self, signal_name, callback):
75
"""Connect a callback to a given signal."""
76
self.callbacks[signal_name].append(callback)
79
def disconnect_from_signal(self, signal_name, match):
80
"""Disconnect from a given signal."""
81
self.callbacks[signal_name].remove(match)
82
if len(self.callbacks[signal_name]) == 0:
83
self.callbacks.pop(signal_name)
85
def _record_call(self, func_name):
86
"""Store values when calling 'func_name'."""
88
def inner(*args, **kwargs):
89
"""Fake 'func_name'."""
90
self._called[func_name] = (args, kwargs)
95
class FakedLogger(object):
98
self.records = defaultdict(list)
99
self.debug = partial(self.log, logging.DEBUG)
100
self.info = partial(self.log, logging.INFO)
101
self.warning = partial(self.log, logging.WARNING)
102
self.error = self.exception = partial(self.log, logging.ERROR)
104
def log(self, level, msg, *args):
105
"""Fake a logging operation."""
106
self.records[level].append(msg % args)
108
def check(self, level, *msgs):
109
"""Return whether there is a log entry for 'level' with 'msgs'."""
110
result = all(any(msg in r for r in self.records[level])
115
"""Reset internal state."""
119
class Settings(dict):
120
"""Faked embedded browser settings."""
122
def get_property(self, prop_name):
123
"""Alias for __getitem__."""
124
return self[prop_name]
126
def set_property(self, prop_name, newval):
127
"""Alias for __setitem__."""
128
self[prop_name] = newval
131
class FakedEmbeddedBrowser(Gtk.TextView):
132
"""Faked an embedded browser."""
135
super(FakedEmbeddedBrowser, self).__init__()
137
self._signals = defaultdict(list)
138
self._settings = Settings()
140
def connect(self, signal_name, callback):
141
"""Connect 'signal_name' with 'callback'."""
142
self._signals[signal_name].append(callback)
144
def load_uri(self, uri):
145
"""Navigate to the given 'uri'."""
146
self._props['uri'] = uri
148
def set_property(self, prop_name, newval):
149
"""Set 'prop_name' to 'newval'."""
150
self._props[prop_name] = newval
152
def get_property(self, prop_name):
153
"""Return the current value for 'prop_name'."""
154
return self._props[prop_name]
156
def get_settings(self,):
157
"""Return the current settings."""
158
return self._settings
160
def get_load_status(self):
161
"""Return the current load status."""
162
return WebKit.LoadStatus.FINISHED
165
"""Show this instance."""
166
self.set_property('visible', True)
169
class BasicTestCase(TestCase):
170
"""Test case with a helper tracker."""
174
super(BasicTestCase, self).setUp()
175
self._called = False # helper
177
self.memento = FakedLogger()
178
self.patch(gui, 'logger', self.memento)
179
self.patch(gui.sys, 'exit', lambda *a: None)
181
def _set_called(self, *args, **kwargs):
182
"""Set _called to True."""
183
self._called = (args, kwargs)
185
def patch(self, obj, attr, new_value):
186
patcher = patch.object(obj, attr, new_value)
188
self.addCleanup(patcher.stop)
190
def assert_color_equal(self, rgba_color, gdk_color):
191
"""Check that 'rgba_color' is the same as 'gdk_color'."""
193
assert tmp.parse(gdk_color.to_string())
195
msg = 'Text color must be %r (got %r instead).'
196
self.assertEqual(rgba_color, tmp, msg % (rgba_color, tmp))
198
def assert_backend_called(self, method, *args, **kwargs):
199
"""Check that 'method(*args, **kwargs)' was called in the backend."""
200
self.assertIn(method, self.ui.backend._called)
202
call = self.ui.backend._called[method]
203
self.assertEqual(call[0], args)
205
reply_handler = call[1].pop('reply_handler')
206
self.assertEqual(reply_handler, gui.NO_OP)
208
error_handler = call[1].pop('error_handler')
209
self.assertEqual(error_handler.func, self.ui._handle_error)
211
self.assertEqual(call[1], kwargs)
214
class LabeledEntryTestCase(BasicTestCase):
215
"""Test suite for the labeled entry."""
219
super(LabeledEntryTestCase, self).setUp()
220
self.label = 'Test me please'
221
self.entry = gui.LabeledEntry(label=self.label)
223
# we need a window to be able to realize ourselves
224
window = Gtk.Window()
225
window.add(self.entry)
227
self.addCleanup(window.hide)
228
self.addCleanup(window.destroy)
230
def grab_focus(self, focus_in=True):
231
"""Grab focus on widget, if None use self.entry."""
232
direction = 'in' if focus_in else 'out'
233
self.entry.emit('focus-%s-event' % direction, None)
235
# Bad first argument 'LabeledEntry' given to super class
236
# pylint: disable=E1003
237
def assert_correct_label(self):
238
"""Check that the entry has the correct label."""
239
# text content is correct
240
msg = 'Text content must be %r (got %r instead).'
241
expected = self.label
242
actual = super(gui.LabeledEntry, self.entry).get_text()
243
self.assertEqual(expected, actual, msg % (expected, actual))
245
# text color is correct
246
expected = gui.HELP_TEXT_COLOR
247
actual = self.entry.get_style().text[Gtk.StateFlags.NORMAL]
248
self.assert_color_equal(expected, actual)
250
def test_initial_text(self):
251
"""Entry have the correct text at startup."""
252
self.assert_correct_label()
254
def test_width_chars(self):
255
"""Entry have the correct width."""
256
self.assertEqual(self.entry.get_width_chars(), gui.DEFAULT_WIDTH)
258
def test_tooltip(self):
259
"""Entry have the correct tooltip."""
260
msg = 'Tooltip must be %r (got %r instead).'
261
expected = self.label
262
actual = self.entry.get_tooltip_text()
264
self.assertEqual(expected, actual, msg % (expected, actual))
266
def test_clear_entry_on_focus_in(self):
267
"""Entry are cleared when focused."""
270
msg = 'Entry must be cleared on focus in.'
271
self.assertEqual('', self.entry.get_text(), msg)
273
def test_text_defaults_to_theme_color_when_focus_in(self):
274
"""Entry restore its text color when focused in."""
275
self.patch(self.entry, 'override_color', self._set_called)
279
self.assertEqual(((Gtk.StateFlags.NORMAL, None), {}), self._called,
280
'Entry text color must be restore on focus in.')
282
def test_refill_entry_on_focus_out_if_no_input(self):
283
"""Entry is re-filled with label when focused out if no user input."""
285
self.grab_focus() # grab focus
286
self.grab_focus(focus_in=False) # loose focus
288
# Entry must be re-filled on focus out
289
self.assert_correct_label()
291
def test_refill_entry_on_focus_out_if_empty_input(self):
292
"""Entry is re-filled with label when focused out if empty input."""
294
self.grab_focus() # grab focus
296
self.entry.set_text(' ') # add empty text to the entry
298
self.grab_focus(focus_in=False) # loose focus
300
# Entry must be re-filled on focus out
301
self.assert_correct_label()
303
def test_preserve_input_on_focus_out_if_user_input(self):
304
"""Entry is unmodified when focused out if user input."""
305
msg = 'Entry must be left unmodified on focus out when user input.'
306
expected = 'test me please'
308
self.grab_focus() # grab focus
310
self.entry.set_text(expected) # add empty text to the entry
312
self.grab_focus(focus_in=False) # loose focus
314
self.assertEqual(expected, self.entry.get_text(), msg)
316
def test_preserve_input_on_focus_out_and_in_again(self):
317
"""Entry is unmodified when focused out and then in again."""
318
msg = 'Entry must be left unmodified on focus out and then in again.'
319
expected = 'test me I mean it'
321
self.grab_focus() # grab focus
323
self.entry.set_text(expected) # add text to the entry
325
self.grab_focus(focus_in=False) # loose focus
326
self.grab_focus() # grab focus again!
328
self.assertEqual(expected, self.entry.get_text(), msg)
330
def test_get_text_ignores_label(self):
331
"""Entry's text is only user input (label is ignored)."""
332
self.assertEqual(self.entry.get_text(), '')
334
def test_get_text_ignores_empty_input(self):
335
"""Entry's text is only user input (empty text is ignored)."""
336
self.entry.set_text(' ')
337
self.assertEqual(self.entry.get_text(), '')
339
def test_get_text_doesnt_ignore_user_input(self):
340
"""Entry's text is user input."""
341
self.entry.set_text('a')
342
self.assertEqual(self.entry.get_text(), 'a')
344
def test_no_warning_by_default(self):
345
"""No secondary icon by default."""
346
self.assertEqual(self.entry.warning, None)
347
self.assertEqual(self.entry.get_property('secondary-icon-stock'),
349
self.assertEqual(self.entry.get_property('secondary-icon-sensitive'),
351
self.assertEqual(self.entry.get_property('secondary-icon-activatable'),
353
prop = self.entry.get_property('secondary-icon-tooltip-text')
354
self.assertEqual(prop, None)
356
def test_set_warning(self):
357
"""Setting a warning show the proper secondary icon."""
359
self.entry.set_warning(msg)
360
self.assertEqual(self.entry.warning, msg)
361
self.assertEqual(self.entry.get_property('secondary-icon-stock'),
362
Gtk.STOCK_DIALOG_WARNING)
363
self.assertEqual(self.entry.get_property('secondary-icon-sensitive'),
365
self.assertEqual(self.entry.get_property('secondary-icon-activatable'),
367
prop = self.entry.get_property('secondary-icon-tooltip-text')
368
self.assertEqual(prop, msg)
370
def test_clear_warning(self):
371
"""Clearing a warning no longer show the secondary icon."""
372
self.entry.clear_warning()
373
self.assertEqual(self.entry.warning, None)
374
self.assertEqual(self.entry.get_property('secondary-icon-stock'),
376
self.assertEqual(self.entry.get_property('secondary-icon-sensitive'),
378
self.assertEqual(self.entry.get_property('secondary-icon-activatable'),
380
prop = self.entry.get_property('secondary-icon-tooltip-text')
381
self.assertEqual(prop, None)
384
class PasswordLabeledEntryTestCase(LabeledEntryTestCase):
385
"""Test suite for the labeled entry when is_password is True."""
389
super(PasswordLabeledEntryTestCase, self).setUp()
390
self.entry.is_password = True
392
def test_password_fields_are_visible_at_startup(self):
393
"""Password entries show the helping text at startup."""
394
self.assertTrue(self.entry.get_visibility(),
395
'Password entry should be visible at start up.')
397
def test_password_field_is_visible_if_no_input_and_focus_out(self):
398
"""Password entry show the label when focus out."""
399
self.grab_focus() # user clicked or TAB'd to the entry
400
self.grab_focus(focus_in=False) # loose focus
401
self.assertTrue(self.entry.get_visibility(),
402
'Entry should be visible when focus out and no input.')
404
def test_password_fields_are_not_visible_when_editing(self):
405
"""Password entries show the hidden chars instead of the password."""
406
self.grab_focus() # user clicked or TAB'd to the entry
407
self.assertFalse(self.entry.get_visibility(),
408
'Entry should not be visible when editing.')
411
class UbuntuSSOClientTestCase(BasicTestCase):
412
"""Basic setup and helper functions."""
414
gui_class = gui.UbuntuSSOClientGUI
415
kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT)
419
super(UbuntuSSOClientTestCase, self).setUp()
420
self.patch(gui, 'get_sso_client', lambda: FakedSSOBackend())
421
self.pages = ('enter_details', 'processing', 'verify_email', 'finish',
422
'tc_browser', 'login', 'request_password_token',
424
self.ui = self.gui_class(**self.kwargs)
425
self.addCleanup(self.ui.destroy)
426
self.error = {'message': UNKNOWN_ERROR}
428
def assert_entries_are_packed_to_ui(self, container_name, entries):
429
"""Every entry is properly packed in the ui 'container_name'."""
430
msg = 'Entry %r must be packed in %r but is not.'
431
container = getattr(self.ui, container_name)
433
name = '%s_entry' % kind
434
entry = getattr(self.ui, name)
435
self.assertIsInstance(entry, gui.LabeledEntry)
436
self.assertIn(entry, container, msg % (name, container_name))
438
def assert_warnings_visibility(self, visible=False):
439
"""Every warning label should be 'visible'."""
440
msg = '%r should have %sempty content.'
441
for name in self.ui.widgets:
442
widget = getattr(self.ui, name)
443
if 'warning' in name:
444
self.assertEqual('', widget.get_text(),
445
msg % (name, '' if visible else 'non-'))
446
elif 'entry' in name:
447
self.assertEqual(widget.warning, '')
449
def assert_correct_label_warning(self, label, message):
450
"""Check that a warning is shown displaying 'message'."""
451
# warning label is visible
452
self.assertTrue(label.get_property('visible'))
454
# warning content is correct
455
actual = label.get_text().decode('utf-8')
456
self.assertEqual(actual, message)
458
# content color is correct
459
# FIXME - New GTK+ 3.5 breaks this check - see bug #1014772
460
# expected = gui.WARNING_TEXT_COLOR
461
# actual = label.get_style().fg[Gtk.StateFlags.NORMAL]
462
# self.assert_color_equal(expected, actual)
464
def assert_correct_entry_warning(self, entry, message):
465
"""Check that a warning is shown displaying 'message'."""
466
self.assertEqual(entry.warning, message)
468
def assert_pages_visibility(self, **kwargs):
469
"""The page 'name' is the current page for the content notebook."""
470
msg = 'page %r must be self.ui.content\'s current page.'
471
(name, _), = kwargs.items()
472
page = getattr(self.ui, '%s_vbox' % name)
473
self.assertEqual(self.ui.content.get_current_page(),
474
self.ui.content.page_num(page), msg % name)
476
def click_join_with_valid_data(self):
477
"""Move to the next page after entering details."""
478
self.ui.on_captcha_generated(app_name=APP_NAME, captcha_id=CAPTCHA_ID)
480
self.ui.name_entry.set_text(NAME)
482
self.ui.email1_entry.set_text(EMAIL)
483
self.ui.email2_entry.set_text(EMAIL)
485
self.ui.password1_entry.set_text(PASSWORD)
486
self.ui.password2_entry.set_text(PASSWORD)
488
# agree to TC, only if the tc_url is defined, so we catch errors
489
self.ui.yes_to_tc_checkbutton.set_active(True)
490
# resolve captcha properly
491
self.ui.captcha_solution_entry.set_text(CAPTCHA_SOLUTION)
493
self.ui.join_ok_button.clicked()
495
def click_verify_email_with_valid_data(self):
496
"""Move to the next page after entering email token."""
497
self.click_join_with_valid_data()
499
# resolve email token properly
500
self.ui.email_token_entry.set_text(EMAIL_TOKEN)
502
self.ui.verify_token_button.clicked()
504
def click_connect_with_valid_data(self):
505
"""Move to the next page after entering login info."""
507
self.ui.login_email_entry.set_text(EMAIL)
509
self.ui.login_password_entry.set_text(PASSWORD)
511
self.ui.login_ok_button.clicked()
513
def click_request_password_token_with_valid_data(self):
514
"""Move to the next page after requesting for password reset token."""
516
self.ui.reset_email_entry.set_text(EMAIL)
518
self.ui.request_password_token_ok_button.clicked()
520
def click_set_new_password_with_valid_data(self):
521
"""Move to the next page after resetting password."""
523
self.ui.reset_code_entry.set_text(RESET_PASSWORD_TOKEN)
525
self.ui.reset_password1_entry.set_text(PASSWORD)
526
self.ui.reset_password2_entry.set_text(PASSWORD)
528
self.ui.set_new_password_ok_button.clicked()
531
class BasicUbuntuSSOClientTestCase(UbuntuSSOClientTestCase):
532
"""Test suite for basic functionality."""
534
def test_main_window_is_visible_at_startup(self):
535
"""The main window is shown at startup."""
536
self.assertTrue(self.ui.window.get_property('visible'))
538
def test_main_window_is_resizable(self):
539
"""The main window can be resized."""
540
self.assertTrue(self.ui.window.get_property('resizable'))
542
def test_closing_main_window_calls_close_callback(self):
543
"""The close_callback is called when closing the main window."""
544
self.ui.close_callback = self._set_called
545
self.ui.on_close_clicked()
546
self.assertTrue(self._called,
547
'close_callback was called when window was closed.')
549
def test_close_callback_if_not_set(self):
550
"""The close_callback is a no op if not set."""
551
self.ui.on_close_clicked()
552
# no crash when close_callback is not set
554
def test_app_name_is_stored(self):
555
"""The app_name is stored for further use."""
556
self.assertIn(APP_NAME, self.ui.app_name)
558
def test_signals_are_removed(self):
559
"""The hooked signals are removed at shutdown time."""
560
assert len(self.ui.backend.callbacks) > 0 # at least one callback
562
self.ui.on_close_clicked()
564
self.assertEqual(self.ui.backend.callbacks, {})
566
def test_pages_are_packed_into_container(self):
567
"""All the pages are packed in the main container."""
568
children = self.ui.content.get_children()
569
for page_name in self.pages:
570
page = getattr(self.ui, '%s_vbox' % page_name)
571
self.assertIn(page, children)
573
def test_initial_text_for_entries(self):
574
"""Entries have the correct text at startup."""
575
msg = 'Text for %r must be %r (got %r instead).'
576
for name in self.ui.entries:
577
entry = getattr(self.ui, name)
578
expected = getattr(gui.ui_strings, name.upper())
580
# text content is correct
581
self.assertEqual(expected, actual, msg % (name, expected, actual))
583
def test_entries_activates_default(self):
584
"""Entries have the activates default prop set."""
585
msg = '%r must have activates_default set to True.'
586
for name in self.ui.entries:
587
entry = getattr(self.ui, name)
588
self.assertTrue(entry.get_activates_default(), msg % (name,))
590
def test_password_fields_are_password(self):
591
"""Password fields have the is_password flag set."""
592
msg = '%r should be a password LabeledEntry instance.'
593
passwords = filter(lambda name: 'password' in name,
595
for name in passwords:
596
widget = getattr(self.ui, name)
597
self.assertTrue(widget.is_password, msg % name)
599
def test_warning_fields_are_cleared(self):
600
"""Every warning label should be cleared."""
601
self.assert_warnings_visibility()
603
def test_cancel_buttons_close_window(self):
604
"""Every cancel button should close the window when clicked."""
605
self.patch(self.ui.backend, 'disconnect_from_signal', lambda *a: None)
606
msg = '%r should close the window when clicked.'
607
buttons = filter(lambda name: 'cancel_button' in name or
608
'close_button' in name, self.ui.widgets)
610
self.ui.close_callback = self._set_called
611
widget = getattr(self.ui, name)
613
self.assertEqual(self._called, ((widget,), {}), msg % name)
616
def test_window_icon(self):
617
"""Main window has the proper icon."""
618
self.assertEqual('ubuntu-logo', self.ui.window.get_icon_name())
620
def test_finish_success_shows_success_page(self):
621
"""When calling 'finish_success' the success page is shown."""
622
self.ui.finish_success()
623
self.assert_pages_visibility(finish=True)
624
self.assertEqual(gui.SUCCESS % {'app_name': APP_NAME},
625
self.ui.finish_vbox.label.get_text().decode('utf8'))
626
result = self.ui.finish_vbox.label.get_text().decode('utf8')
627
self.assertTrue(self.ui.app_name in result)
629
def test_finish_error_shows_error_page(self):
630
"""When calling 'finish_error' the error page is shown."""
631
self.ui.finish_error()
632
self.assert_pages_visibility(finish=True)
633
self.assertEqual(gui.ERROR,
634
self.ui.finish_vbox.label.get_text().decode('utf8'))
637
@skip("Apparently, so far we can't use XLib dynamic bindings "
638
"to complete the call to X11Window.foreign_new_for_display.")
639
class SetTransientForTestCase(UbuntuSSOClientTestCase):
640
"""Test suite for setting the window as transient for another one."""
642
def test_transient_window_is_none_if_window_id_is_zero(self):
643
"""The transient window is correct."""
644
self.patch(gui.X11Window, 'foreign_new_for_display', self._set_called)
645
ui = self.gui_class(window_id=0, **self.kwargs)
646
self.addCleanup(ui.destroy)
648
self.assertFalse(self._called, 'set_transient_for must not be called.')
650
def test_transient_window_is_correct(self):
651
"""The transient window is correct."""
653
self.patch(gui.X11Window, 'foreign_new_for_display', self._set_called)
654
ui = self.gui_class(window_id=xid, **self.kwargs)
655
self.addCleanup(ui.destroy)
657
self.assertTrue(self.memento.check(logging.ERROR, 'set_transient_for'))
658
self.assertTrue(self.memento.check(logging.ERROR, str(xid)))
659
self.assertEqual(self._called, ((xid,), {}))
661
def test_transient_window_accepts_negative_id(self):
662
"""The transient window accepts a negative window id."""
664
self.patch(gui.X11Window, 'foreign_new_for_display', self._set_called)
665
ui = self.gui_class(window_id=xid, **self.kwargs)
666
self.addCleanup(ui.destroy)
668
self.assertEqual(self._called, ((xid,), {}))
671
class EnterDetailsTestCase(UbuntuSSOClientTestCase):
672
"""Test suite for the user registration (enter details page)."""
674
def test_initial_text_for_header_label(self):
675
"""The header must have the correct text at startup."""
676
msg = 'Text for the header must be %r (got %r instead).'
677
expected = gui.JOIN_HEADER_LABEL % {'app_name': APP_NAME}
678
actual = self.ui.header_label.get_text().decode('utf8')
679
# text content is correct
680
self.assertEqual(expected, actual, msg % (expected, actual))
682
def test_entries_are_packed_to_ui(self):
683
"""Every entry is properly packed in the ui."""
684
for kind in ('email', 'password'):
685
container_name = '%ss_hbox' % kind
686
entries = ('%s%s' % (kind, i) for i in xrange(1, 3))
687
self.assert_entries_are_packed_to_ui(container_name, entries)
689
self.assert_entries_are_packed_to_ui('enter_details_vbox', ('name',))
690
self.assert_entries_are_packed_to_ui('captcha_solution_vbox',
691
('captcha_solution',))
692
self.assert_entries_are_packed_to_ui('verify_email_details_vbox',
695
def test_initial_texts_for_checkbuttons(self):
696
"""Check buttons have the correct text at startup."""
697
msg = 'Text for %r must be %r (got %r instead).'
698
expected = gui.YES_TO_UPDATES % {'app_name': APP_NAME}
699
actual = self.ui.yes_to_updates_checkbutton.get_label().decode('utf8')
700
self.assertEqual(expected, actual, msg % ('yes_to_updates_checkbutton',
702
expected = gui.YES_TO_TC % {'app_name': APP_NAME}
703
actual = self.ui.yes_to_tc_checkbutton.get_label().decode('utf8')
704
self.assertEqual(expected, actual,
705
msg % ('yes_to_tc_checkbutton', expected, actual))
707
def test_updates_checkbutton_is_checked_at_startup(self):
708
"""The 'yes to updates' checkbutton is checked by default."""
709
msg = '%r is checked by default.'
710
name = 'yes_to_updates_checkbutton'
711
widget = getattr(self.ui, name)
712
self.assertTrue(widget.get_active(), msg % name)
714
def test_tc_checkbutton_is_not_checked_at_startup(self):
715
"""The 'yes to T&C' checkbutton is not checked by default."""
716
msg = '%r is checked by default.'
717
name = 'yes_to_tc_checkbutton'
718
widget = getattr(self.ui, name)
719
self.assertFalse(widget.get_active(), msg % name)
721
def test_vboxes_visible_properties(self):
722
"""Only 'enter_details' vbox is visible at start up."""
723
self.assert_pages_visibility(enter_details=True)
725
def test_join_ok_button_clicked(self):
726
"""Clicking 'join_ok_button' sends info to backend using 'register'."""
727
self.click_join_with_valid_data()
729
# assert register_user was called
730
self.assert_backend_called('register_user',
731
APP_NAME, EMAIL, PASSWORD, NAME, CAPTCHA_ID, CAPTCHA_SOLUTION)
733
def test_join_ok_button_clicked_morphs_to_processing_page(self):
734
"""Clicking 'join_ok_button' presents the processing vbox."""
735
self.click_join_with_valid_data()
736
self.assert_pages_visibility(processing=True)
738
def test_processing_vbox_displays_an_active_spinner(self):
739
"""When processing the registration, an active spinner is shown."""
740
self.click_join_with_valid_data()
742
self.assertTrue(self.ui.processing_vbox.get_property('visible'),
743
'the processing box should be visible.')
745
box = self.ui.processing_vbox.get_children()[0].get_children()[0]
746
self.assertEqual(2, len(box.get_children()),
747
'processing_vbox must have two children.')
749
spinner, label = box.get_children()
750
self.assertIsInstance(spinner, Gtk.Spinner)
751
self.assertIsInstance(label, Gtk.Label)
753
self.assertTrue(spinner.get_property('visible'),
754
'the processing spinner should be visible.')
755
self.assertTrue(spinner.get_property('active'),
756
'the processing spinner should be active.')
757
self.assertTrue(label.get_property('visible'),
758
'the processing label should be visible.')
759
self.assertEqual(label.get_text().decode('utf8'),
760
gui.ONE_MOMENT_PLEASE,
761
'the processing label text must be correct.')
763
def test_captcha_image_is_not_visible_at_startup(self):
764
"""Captcha image is not shown at startup."""
765
self.assertFalse(self.ui.captcha_image.get_property('visible'),
766
'the captcha_image should not be visible.')
768
def test_captcha_filename_is_different_each_time(self):
769
"""The captcha image is different each time."""
770
ui = self.gui_class(**self.kwargs)
771
self.addCleanup(ui.destroy)
773
self.assertNotEqual(self.ui._captcha_filename, ui._captcha_filename)
775
def test_captcha_image_is_removed_when_exiting(self):
776
"""The captcha image is removed at shutdown time."""
777
open(self.ui._captcha_filename, 'w').close()
778
assert os.path.exists(self.ui._captcha_filename)
779
self.ui.on_close_clicked()
781
self.assertFalse(os.path.exists(self.ui._captcha_filename),
782
'captcha image must be removed when exiting.')
784
def test_captcha_image_is_a_spinner_at_first(self):
785
"""Captcha image shows a spinner until the image is downloaded."""
786
self.assertTrue(self.ui.captcha_loading.get_property('visible'),
787
'the captcha_loading box should be visible.')
789
box = self.ui.captcha_loading.get_children()[0].get_children()[0]
790
self.assertEqual(2, len(box.get_children()),
791
'captcha_loading must have two children.')
793
spinner, label = box.get_children()
794
self.assertIsInstance(spinner, Gtk.Spinner)
795
self.assertIsInstance(label, Gtk.Label)
797
self.assertTrue(spinner.get_property('visible'),
798
'the captcha_loading spinner should be visible.')
799
self.assertTrue(spinner.get_property('active'),
800
'the captcha_loading spinner should be active.')
801
self.assertTrue(label.get_property('visible'),
802
'the captcha_loading label should be visible.')
803
self.assertEqual(label.get_text().decode('utf8'), gui.LOADING,
804
'the captcha_loading label text must be correct.')
806
def test_join_ok_button_is_disabled_until_captcha_is_available(self):
807
"""The join_ok_button is not sensitive until captcha is available."""
808
self.assertFalse(self.ui.join_ok_button.is_sensitive())
810
def test_join_ok_button_is_enabled_when_captcha_is_available(self):
811
"""The join_ok_button is sensitive when captcha is available."""
812
self.ui.on_captcha_generated(app_name=APP_NAME, captcha_id=CAPTCHA_ID)
813
self.assertTrue(self.ui.join_ok_button.is_sensitive())
815
def test_captcha_loading_is_hid_when_captcha_is_available(self):
816
"""The captcha_loading is hid when captcha is available."""
817
self.ui.on_captcha_generated(app_name=APP_NAME, captcha_id=CAPTCHA_ID)
818
self.assertFalse(self.ui.captcha_loading.get_property('visible'),
819
'captcha_loading is not visible.')
821
def test_captcha_id_is_stored_when_captcha_is_available(self):
822
"""The captcha_id is stored when captcha is available."""
823
self.ui.on_captcha_generated(app_name=APP_NAME, captcha_id=CAPTCHA_ID)
824
self.assertEqual(CAPTCHA_ID, self.ui._captcha_id)
826
def test_captcha_image_is_requested_as_startup(self):
827
"""The captcha image is requested at startup."""
828
# assert generate_captcha was called
829
self.assert_backend_called('generate_captcha',
830
APP_NAME, self.ui._captcha_filename)
832
def test_captcha_is_shown_when_available(self):
833
"""The captcha image is shown when available."""
834
self.patch(self.ui.captcha_image, 'set_from_file', self._set_called)
835
self.ui.on_captcha_generated(app_name=APP_NAME, captcha_id=CAPTCHA_ID)
836
self.assertTrue(self.ui.captcha_image.get_property('visible'))
837
self.assertEqual(self._called, ((self.ui._captcha_filename,), {}))
839
def test_on_captcha_generated_logs_captcha_id_when_none(self):
840
"""If the captcha id is None, a warning is logged."""
841
self.ui.on_captcha_generated(app_name=APP_NAME, captcha_id=None)
842
self.assertTrue(self.memento.check(logging.WARNING, repr(APP_NAME)))
843
self.assertTrue(self.memento.check(logging.WARNING,
844
'captcha_id is None'))
846
def test_captcha_reload_button_visible(self):
847
"""The captcha reload button is initially visible."""
848
self.assertTrue(self.ui.captcha_reload_button.get_visible(),
849
"The captcha button is not visible")
851
def test_captcha_reload_button_reloads_captcha(self):
852
"""The captcha reload button loads a new captcha."""
853
self.ui.on_captcha_generated(app_name=APP_NAME, captcha_id=CAPTCHA_ID)
854
self.patch(self.ui, '_generate_captcha', self._set_called)
855
self.ui.captcha_reload_button.clicked()
856
self.assertEqual(self._called, ((), {}))
858
def test_captcha_reload_button_has_tooltip(self):
859
"""The captcha reload button has a tooltip."""
860
self.assertEqual(self.ui.captcha_reload_button.get_tooltip_text(),
861
gui.CAPTCHA_RELOAD_TOOLTIP)
863
def test_login_button_has_correct_wording(self):
864
"""The sign in button has the proper wording."""
865
actual = self.ui.login_button.get_label().decode('utf8')
866
self.assertEqual(gui.LOGIN_BUTTON_LABEL, actual)
868
def test_join_ok_button_does_nothing_if_clicked_but_disabled(self):
869
"""The join form can only be submitted if the button is sensitive."""
870
self.patch(self.ui.email1_entry, 'get_text', self._set_called)
872
self.ui.join_ok_button.set_sensitive(False)
873
self.ui.join_ok_button.clicked()
874
self.assertFalse(self._called)
876
self.ui.join_ok_button.set_sensitive(True)
877
self.ui.join_ok_button.clicked()
878
self.assertTrue(self._called)
880
def test_user_and_pass_are_cached(self):
881
"""Username and password are temporarily cached for further use."""
882
self.click_join_with_valid_data()
883
self.assertEqual(self.ui.user_email, EMAIL)
884
self.assertEqual(self.ui.user_password, PASSWORD)
886
def test_on_captcha_generation_error(self):
887
"""on_captcha_generation_error shows an error and reloads captcha."""
888
self.patch(self.ui, '_generate_captcha', self._set_called)
889
self.ui.on_captcha_generation_error(APP_NAME, error=self.error)
890
self.assert_correct_label_warning(self.ui.warning_label,
891
gui.CAPTCHA_LOAD_ERROR)
892
self.assertEqual(self._called, ((), {}))
894
def test_captcha_success_after_error(self):
895
"""When captcha was retrieved after error, the warning is removed."""
896
self.ui.on_captcha_generation_error(APP_NAME, error=self.error)
897
self.ui.on_captcha_generated(app_name=APP_NAME, captcha_id=CAPTCHA_ID)
898
self.assertEqual(self.ui.warning_label.get_text().decode('utf8'), '')
900
def test_has_tc_link(self):
901
"""The T&C button and checkbox are shown if the link is provided"""
902
self.assertEqual(self.ui.tc_button.get_visible(), True)
903
self.assertEqual(self.ui.yes_to_tc_checkbutton.get_visible(), True)
906
class NoTermsAndConditionsTestCase(EnterDetailsTestCase):
907
"""Test suite for the user registration (with no t&c link)."""
909
kwargs = dict(app_name=APP_NAME, tc_url='', help_text=HELP_TEXT)
911
def test_has_tc_link(self):
912
"""The T&C button and checkbox are not shown if no link is provided"""
913
self.assertEqual(self.ui.tc_vbox.get_visible(), False)
916
class TermsAndConditionsBrowserTestCase(UbuntuSSOClientTestCase):
917
"""Test suite for the terms & conditions browser."""
920
super(TermsAndConditionsBrowserTestCase, self).setUp()
921
self.patch(WebKit, 'WebView', FakedEmbeddedBrowser)
922
self.patch(self.ui, '_webkit_init_ssl', self._set_called)
924
self.ui.tc_button.clicked()
925
self.addCleanup(self.ui.tc_browser_vbox.hide)
927
children = self.ui.tc_browser_window.get_children()
928
assert len(children) == 1
929
self.browser = children[0]
931
def test_ssl_validation(self):
932
"""The browser is set to validate SSL."""
933
self.assertEqual(self._called, ((), {}),
934
'_webkit_init_ssl should be called when creating a '
937
def test_tc_browser_is_created_when_tc_page_is_shown(self):
938
"""The browser is created when the TC button is clicked."""
939
self.ui.on_tc_browser_notify_load_status(self.browser)
941
children = self.ui.tc_browser_window.get_children()
942
self.assertEqual(1, len(children))
944
def test_is_visible(self):
945
"""The browser is visible."""
946
self.assertIsInstance(self.browser, FakedEmbeddedBrowser)
947
self.assertTrue(self.browser.get_property('visible'))
949
def test_settings(self):
950
"""The browser settings are correct."""
951
settings = self.browser.get_settings()
952
self.assertFalse(settings.get_property('enable-plugins'))
953
self.assertFalse(settings.get_property('enable-default-context-menu'))
955
def test_tc_browser_is_destroyed_when_tc_page_is_hid(self):
956
"""The browser is destroyed when the TC page is hid."""
957
self.ui.on_tc_browser_notify_load_status(self.browser)
958
self.patch(self.browser, 'destroy', self._set_called)
959
self.ui.tc_browser_vbox.hide()
960
self.assertEqual(self._called, ((), {}))
962
def test_tc_browser_is_removed_when_tc_page_is_hid(self):
963
"""The browser is removed when the TC page is hid."""
964
self.ui.on_tc_browser_notify_load_status(self.browser)
966
self.ui.tc_browser_vbox.hide()
968
children = self.ui.tc_browser_window.get_children()
969
self.assertEqual(0, len(children))
971
def test_tc_button_clicked_morphs_into_processing_page(self):
972
"""Clicking the T&C button morphs into processing page."""
973
self.assert_pages_visibility(processing=True)
975
def test_tc_back_clicked_returns_to_previous_page(self):
976
"""Terms & Conditions back button return to previous page."""
977
self.ui.on_tc_browser_notify_load_status(self.browser)
978
self.ui.tc_back_button.clicked()
979
self.assert_pages_visibility(enter_details=True)
981
def test_tc_button_has_the_proper_wording(self):
982
"""Terms & Conditions has the proper wording."""
983
self.assertEqual(self.ui.tc_button.get_label().decode('utf8'),
986
def test_tc_has_no_help_text(self):
987
"""The help text is removed."""
988
self.ui.on_tc_browser_notify_load_status(self.browser)
989
self.assertEqual('', self.ui.help_label.get_text().decode('utf8'))
991
def test_tc_browser_opens_the_proper_url(self):
992
"""Terms & Conditions browser shows the proper uri."""
993
self.assertEqual(self.browser.get_property('uri'), TC_URL)
995
@skip('Connecting to notify::load-status makes U1 terms navigation fail.')
996
def test_notify_load_status_connected(self):
997
"""The 'notify::load-status' signal is connected."""
998
expected = [self.ui.on_tc_browser_notify_load_status]
999
self.assertEqual(self.browser._signals['notify::load-status'],
1002
def test_notify_load_finished_connected(self):
1003
"""The 'load-finished' signal is connected."""
1004
expected = [self.ui.on_tc_browser_notify_load_status]
1005
self.assertEqual(self.browser._signals['notify::load-status'],
1008
def test_tc_loaded_morphs_into_tc_browser_vbox(self):
1009
"""When the Terms & Conditions is loaded, show the browser window."""
1010
self.ui.on_tc_browser_notify_load_status(self.browser)
1011
self.assert_pages_visibility(tc_browser=True)
1013
def test_navigation_requested_connected(self):
1014
"""The 'navigation-policy-decision-requested' signal is connected."""
1015
actual = self.browser._signals['navigation-policy-decision-requested']
1016
expected = [self.ui.on_tc_browser_navigation_requested]
1017
self.assertEqual(actual, expected)
1019
def test_navigation_requested_succeeds_for_no_clicking(self):
1020
"""The navigation request succeeds when user hasn't clicked a link."""
1021
action = WebKit.WebNavigationAction()
1022
action.set_reason(WebKit.WebNavigationReason.OTHER)
1024
decision = WebKit.WebPolicyDecision()
1025
decision.use = self._set_called
1027
kwargs = dict(browser=self.browser, frame=None, request=None,
1028
action=action, decision=decision)
1029
self.ui.on_tc_browser_navigation_requested(**kwargs)
1030
self.assertEqual(self._called, ((), {}))
1032
def test_navigation_requested_ignores_clicked_links(self):
1033
"""The navigation request is ignored if a link was clicked."""
1034
action = WebKit.WebNavigationAction()
1035
action.set_reason(WebKit.WebNavigationReason.LINK_CLICKED)
1037
decision = WebKit.WebPolicyDecision()
1038
decision.ignore = self._set_called
1040
self.patch(gui.webbrowser, 'open', lambda *args, **kwargs: None)
1042
kwargs = dict(browser=self.browser, frame=None, request=None,
1043
action=action, decision=decision)
1044
self.ui.on_tc_browser_navigation_requested(**kwargs)
1045
self.assertEqual(self._called, ((), {}))
1047
def test_navigation_requested_ignores_for_none(self):
1048
"""The navigation request is ignored the request if params are None."""
1049
kwargs = dict(browser=None, frame=None, request=None,
1050
action=None, decision=None)
1051
self.ui.on_tc_browser_navigation_requested(**kwargs)
1053
def test_navigation_requested_opens_links_when_clicked(self):
1054
"""The navigation request is opened on user's default browser
1056
(If the user opened a link by clicking into it).
1059
url = 'http://something.com/yadda'
1060
action = WebKit.WebNavigationAction()
1061
action.set_reason(WebKit.WebNavigationReason.LINK_CLICKED)
1062
action.set_original_uri(url)
1064
decision = WebKit.WebPolicyDecision()
1065
decision.ignore = gui.NO_OP
1067
self.patch(gui.webbrowser, 'open', self._set_called)
1069
kwargs = dict(browser=self.browser, frame=None, request=None,
1070
action=action, decision=decision)
1071
self.ui.on_tc_browser_navigation_requested(**kwargs)
1072
self.assertEqual(self._called, ((url,), {}))
1074
def test_on_tc_button_clicked_no_child(self):
1075
"""Test the tc loading with no child."""
1078
def fake_add_browser():
1079
"""Fake add browser."""
1080
called.append('fake_add_browser')
1082
self.patch(self.ui, '_add_webkit_browser', fake_add_browser)
1083
self.patch(self.ui.tc_browser_window, 'get_child', lambda: None)
1085
self.ui.on_tc_button_clicked()
1086
self.assertIn('fake_add_browser', called)
1088
def test_on_tc_button_clicked_child(self):
1089
"""Test the tc loading with child."""
1092
def fake_add_browser(i_self):
1093
"""Fake add browser."""
1094
called.append('fake_add_browser')
1096
self.patch(self.ui, '_add_webkit_browser', fake_add_browser)
1098
browser = WebKit.WebView()
1099
self.ui.tc_browser_window.add(browser)
1100
self.ui.on_tc_button_clicked()
1101
self.assertNotIn('fake_add_browser', called)
1104
class RegistrationErrorTestCase(UbuntuSSOClientTestCase):
1105
"""Test suite for the user registration error handling."""
1109
super(RegistrationErrorTestCase, self).setUp()
1110
self.click_join_with_valid_data()
1112
def test_previous_page_is_shown(self):
1113
"""On UserRegistrationError the previous page is shown."""
1114
self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1115
self.assert_pages_visibility(enter_details=True)
1117
def test_captcha_is_reloaded(self):
1118
"""On UserRegistrationError the captcha is reloaded."""
1119
self.patch(self.ui, '_generate_captcha', self._set_called)
1120
self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1121
self.assertEqual(self._called, ((), {}))
1123
def test_warning_label_is_shown(self):
1124
"""On UserRegistrationError the warning label is shown."""
1125
self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
1126
self.assert_correct_label_warning(self.ui.warning_label,
1129
def test_specific_errors_from_backend_are_shown(self):
1130
"""Specific errors from backend are used."""
1131
error = {'errtype': 'RegistrationError',
1132
'message': 'We\'re so doomed.',
1133
'email': 'Enter a valid e-mail address.',
1134
'password': 'I don\'t like your password.',
1135
'__all__': 'Wrong captcha solution.'}
1137
self.ui.on_user_registration_error(app_name=APP_NAME, error=error)
1139
expected = '\n'.join((error['__all__'], error['message']))
1140
self.assert_correct_label_warning(self.ui.warning_label, expected)
1141
self.assert_correct_entry_warning(self.ui.email1_entry,
1143
self.assert_correct_entry_warning(self.ui.email2_entry,
1145
self.assert_correct_entry_warning(self.ui.password1_entry,
1147
self.assert_correct_entry_warning(self.ui.password2_entry,
1151
class VerifyEmailTestCase(UbuntuSSOClientTestCase):
1152
"""Test suite for the user registration (verify email page)."""
1154
method = 'validate_email'
1155
method_args = (APP_NAME, EMAIL, PASSWORD, EMAIL_TOKEN)
1159
super(VerifyEmailTestCase, self).setUp()
1160
self.ui.on_user_registered(app_name=APP_NAME, email=EMAIL)
1162
def test_registration_successful_shows_verify_email_vbox(self):
1163
"""Receiving 'registration_successful' shows the verify email vbox."""
1164
self.ui.on_user_registered(app_name=APP_NAME, email=EMAIL)
1165
self.assert_pages_visibility(verify_email=True)
1167
def test_help_label_display_correct_wording(self):
1168
"""The help_label display VERIFY_EMAIL_LABEL."""
1169
msg = 'help_label must read %r (got %r instead).'
1170
actual = self.ui.help_label.get_label().decode('utf8')
1171
expected = gui.VERIFY_EMAIL_LABEL % {'app_name': APP_NAME,
1173
self.assertEqual(expected, actual, msg % (expected, actual))
1175
def test_on_verify_token_button_clicked_calls_backend(self):
1176
"""Verify token button triggers call to backend."""
1177
self.click_verify_email_with_valid_data()
1178
self.assert_backend_called(self.method, *self.method_args)
1180
def test_on_verify_token_button_clicked(self):
1181
"""Verify token uses cached user_email and user_password."""
1182
self.ui.user_email = 'test@me.com'
1183
self.ui.user_password = 'yadda-yedda'
1184
method_args = list(self.method_args)
1185
method_args[1] = self.ui.user_email
1186
method_args[2] = self.ui.user_password
1188
# resolve email token properly
1189
self.ui.email_token_entry.set_text(EMAIL_TOKEN)
1191
self.ui.on_verify_token_button_clicked()
1192
self.assert_backend_called(self.method, *tuple(method_args))
1194
def test_on_verify_token_button_shows_processing_page(self):
1195
"""Verify token button triggers call to backend."""
1196
self.click_verify_email_with_valid_data()
1197
self.assert_pages_visibility(processing=True)
1199
def test_no_warning_messages_if_valid_data(self):
1200
"""No warning messages are shown if the data is valid."""
1201
# this will certainly NOT generate warnings
1202
self.click_verify_email_with_valid_data()
1203
self.assert_warnings_visibility()
1205
def test_on_email_validated_shows_finish_page(self):
1206
"""On email validated the finish page is shown."""
1207
self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1208
self.assert_pages_visibility(finish=True)
1210
def test_on_email_validated_does_not_clear_the_help_text(self):
1211
"""On email validated the help text is not removed."""
1212
self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1213
self.assertEqual(self.ui.verify_email_vbox.help_text,
1214
self.ui.help_label.get_label().decode('utf8'))
1216
def test_on_email_validation_error_verify_email_is_shown(self):
1217
"""On email validation error, the verify_email page is shown."""
1218
self.ui.on_email_validation_error(app_name=APP_NAME, error=self.error)
1219
self.assert_pages_visibility(verify_email=True)
1220
self.assert_correct_label_warning(self.ui.warning_label,
1223
def test_specific_errors_from_backend_are_shown(self):
1224
"""Specific errors from backend are used."""
1225
error = {'errtype': 'EmailValidationError',
1226
'message': 'We\'re so doomed.',
1227
'email_token': 'Enter a valid e-mail address.',
1228
'__all__': 'We all are gonna die.'}
1230
self.ui.on_email_validation_error(app_name=APP_NAME, error=error)
1232
expected = '\n'.join((error['__all__'], error['message']))
1233
self.assert_correct_label_warning(self.ui.warning_label, expected)
1234
self.assert_correct_entry_warning(self.ui.email_token_entry,
1235
error['email_token'])
1237
def test_success_label_is_correct(self):
1238
"""The success message is correct."""
1239
self.assertEqual(gui.SUCCESS % {'app_name': APP_NAME},
1240
self.ui.success_vbox.label.get_text().decode('utf8'))
1241
markup = self.ui.success_vbox.label.get_label().decode('utf8')
1242
self.assertTrue('<span size="x-large">' in markup)
1243
self.assertTrue(self.ui.app_name in markup)
1245
def test_error_label_is_correct(self):
1246
"""The error message is correct."""
1247
self.assertEqual(gui.ERROR,
1248
self.ui.error_vbox.label.get_text().decode('utf8'))
1249
markup = self.ui.error_vbox.label.get_label().decode('utf8')
1250
self.assertTrue('<span size="x-large">' in markup)
1252
def test_on_finish_close_button_clicked_closes_window(self):
1253
"""When done the window is closed."""
1254
self.ui.finish_close_button.clicked()
1255
self.assertFalse(self.ui.window.get_property('visible'))
1257
def test_verify_token_button_does_nothing_if_clicked_but_disabled(self):
1258
"""The email token can only be submitted if the button is sensitive."""
1259
self.patch(self.ui.email_token_entry, 'get_text', self._set_called)
1261
self.ui.verify_token_button.set_sensitive(False)
1262
self.ui.verify_token_button.clicked()
1263
self.assertFalse(self._called)
1265
self.ui.verify_token_button.set_sensitive(True)
1266
self.ui.verify_token_button.clicked()
1267
self.assertTrue(self._called)
1269
def test_after_email_validated_finish_success(self):
1270
"""After email_validated is called, finish_success is called."""
1271
self.patch(self.ui, 'finish_success', self._set_called)
1273
self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1275
self.assertEqual(self._called, ((), {}))
1278
class VerifyEmailWithPingTestCase(VerifyEmailTestCase):
1279
"""Test suite for the user registration (verify email page)."""
1281
kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
1283
method = 'validate_email_and_ping'
1284
method_args = (APP_NAME, EMAIL, PASSWORD, EMAIL_TOKEN, PING_URL)
1287
class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase):
1288
"""Test suite for the user registration validation (verify email page)."""
1292
super(VerifyEmailValidationTestCase, self).setUp()
1293
self.ui.on_user_registered(app_name=APP_NAME, email=EMAIL)
1295
def test_warning_is_shown_if_empty_email_token(self):
1296
"""A warning message is shown if email token is empty."""
1297
self.ui.email_token_entry.set_text('')
1299
self.ui.verify_token_button.clicked()
1301
self.assert_correct_entry_warning(self.ui.email_token_entry,
1303
self.assertNotIn('validate_email', self.ui.backend._called)
1305
def test_no_warning_messages_if_valid_data(self):
1306
"""No warning messages are shown if the data is valid."""
1307
# this will certainly NOT generate warnings
1308
self.click_verify_email_with_valid_data()
1310
self.assert_warnings_visibility()
1312
def test_no_warning_messages_if_valid_data_after_invalid_data(self):
1313
"""No warnings if the data is valid (with prior invalid data)."""
1314
# this will certainly generate warnings
1315
self.ui.verify_token_button.clicked()
1317
# this will certainly NOT generate warnings
1318
self.click_verify_email_with_valid_data()
1320
self.assert_warnings_visibility()
1323
class VerifyEmailLoginOnlyTestCase(VerifyEmailTestCase):
1324
"""Test suite for the user login (verify email page)."""
1326
kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
1330
class VerifyEmailValidationLoginOnlyTestCase(VerifyEmailValidationTestCase):
1331
"""Test suite for the user login validation (verify email page)."""
1333
kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
1337
class RegistrationValidationTestCase(UbuntuSSOClientTestCase):
1338
"""Test suite for the user registration validations."""
1342
super(RegistrationValidationTestCase, self).setUp()
1343
self.ui.join_ok_button.set_sensitive(True)
1345
def test_warning_is_shown_if_name_empty(self):
1346
"""A warning message is shown if name is empty."""
1347
self.ui.name_entry.set_text('')
1349
self.ui.join_ok_button.clicked()
1351
self.assert_correct_entry_warning(self.ui.name_entry,
1353
self.assertNotIn('register_user', self.ui.backend._called)
1355
def test_warning_is_shown_if_empty_email(self):
1356
"""A warning message is shown if emails are empty."""
1357
self.ui.email1_entry.set_text('')
1358
self.ui.email2_entry.set_text('')
1360
self.ui.join_ok_button.clicked()
1362
self.assert_correct_entry_warning(self.ui.email1_entry,
1364
self.assert_correct_entry_warning(self.ui.email2_entry,
1366
self.assertNotIn('register_user', self.ui.backend._called)
1368
def test_warning_is_shown_if_email_mismatch(self):
1369
"""A warning message is shown if emails doesn't match."""
1370
self.ui.email1_entry.set_text(EMAIL)
1371
self.ui.email2_entry.set_text(EMAIL * 2)
1373
self.ui.join_ok_button.clicked()
1375
self.assert_correct_entry_warning(self.ui.email1_entry,
1377
self.assert_correct_entry_warning(self.ui.email2_entry,
1379
self.assertNotIn('register_user', self.ui.backend._called)
1381
def test_warning_is_shown_if_invalid_email(self):
1382
"""A warning message is shown if email is invalid."""
1383
self.ui.email1_entry.set_text('q')
1384
self.ui.email2_entry.set_text('q')
1386
self.ui.join_ok_button.clicked()
1388
self.assert_correct_entry_warning(self.ui.email1_entry,
1390
self.assert_correct_entry_warning(self.ui.email2_entry,
1392
self.assertNotIn('register_user', self.ui.backend._called)
1394
def test_password_help_is_always_shown(self):
1395
"""Password help text is correctly displayed."""
1396
self.assertTrue(self.ui.password_help_label.get_property('visible'),
1397
'password help text is visible.')
1398
self.assertEqual(self.ui.password_help_label.get_text().decode('utf8'),
1400
self.assertNotIn('register_user', self.ui.backend._called)
1402
def test_warning_is_shown_if_password_mismatch(self):
1403
"""A warning message is shown if password doesn't match."""
1404
self.ui.password1_entry.set_text(PASSWORD)
1405
self.ui.password2_entry.set_text(PASSWORD * 2)
1407
self.ui.join_ok_button.clicked()
1409
self.assert_correct_entry_warning(self.ui.password1_entry,
1410
gui.PASSWORD_MISMATCH)
1411
self.assert_correct_entry_warning(self.ui.password2_entry,
1412
gui.PASSWORD_MISMATCH)
1413
self.assertNotIn('register_user', self.ui.backend._called)
1415
def test_warning_is_shown_if_password_too_weak(self):
1416
"""A warning message is shown if password is too weak."""
1417
# password will match but will be too weak
1418
for pwd in ('', 'h3lloWo', PASSWORD.lower(), 'helloWorld'):
1419
self.ui.password1_entry.set_text(pwd)
1420
self.ui.password2_entry.set_text(pwd)
1422
self.ui.join_ok_button.clicked()
1424
self.assert_correct_entry_warning(self.ui.password1_entry,
1425
gui.PASSWORD_TOO_WEAK)
1426
self.assert_correct_entry_warning(self.ui.password2_entry,
1427
gui.PASSWORD_TOO_WEAK)
1428
self.assertNotIn('register_user', self.ui.backend._called)
1430
def test_warning_is_shown_if_tc_not_accepted(self):
1431
"""A warning message is shown if TC are not accepted."""
1433
self.ui.yes_to_tc_checkbutton.set_active(False)
1435
self.ui.join_ok_button.clicked()
1437
self.assert_correct_label_warning(self.ui.tc_warning_label,
1438
gui.TC_NOT_ACCEPTED % {'app_name': APP_NAME})
1439
self.assertNotIn('register_user', self.ui.backend._called)
1441
def test_warning_is_shown_if_not_captcha_solution(self):
1442
"""A warning message is shown if TC are not accepted."""
1443
# captcha solution will be empty
1444
self.ui.captcha_solution_entry.set_text('')
1446
self.ui.join_ok_button.clicked()
1448
self.assert_correct_entry_warning(self.ui.captcha_solution_entry,
1450
self.assertNotIn('register_user', self.ui.backend._called)
1452
def test_no_warning_messages_if_valid_data(self):
1453
"""No warning messages are shown if the data is valid."""
1454
# this will certainly NOT generate warnings
1455
self.click_join_with_valid_data()
1457
self.assert_warnings_visibility()
1459
def test_no_warning_messages_if_valid_data_after_invalid_data(self):
1460
"""No warnings if the data is valid (with prior invalid data)."""
1461
# this will certainly generate warnings
1462
self.ui.join_ok_button.clicked()
1464
# this will certainly NOT generate warnings
1465
self.click_join_with_valid_data()
1467
self.assert_warnings_visibility()
1470
class LoginTestCase(UbuntuSSOClientTestCase):
1471
"""Test suite for the user login pages."""
1474
method_args = (APP_NAME, EMAIL, PASSWORD)
1478
super(LoginTestCase, self).setUp()
1479
self.ui.login_button.clicked()
1481
def test_login_button_clicked_morphs_to_login_page(self):
1482
"""Clicking sig_in_button morphs window into login page."""
1483
self.assert_pages_visibility(login=True)
1485
def test_initial_text_for_header_label(self):
1486
"""The header must have the correct text when logging in."""
1487
msg = 'Text for the header must be %r (got %r instead).'
1488
expected = gui.LOGIN_HEADER_LABEL % {'app_name': APP_NAME}
1489
actual = self.ui.header_label.get_text().decode('utf8')
1490
self.assertEqual(expected, actual, msg % (expected, actual))
1492
def test_initial_text_for_help_label(self):
1493
"""The help must have the correct text at startup."""
1494
msg = 'Text for the help must be %r (got %r instead).'
1495
expected = gui.CONNECT_HELP_LABEL % {'app_name': APP_NAME}
1496
actual = self.ui.help_label.get_text().decode('utf8')
1497
self.assertEqual(expected, actual, msg % (expected, actual))
1499
def test_entries_are_packed_to_ui_for_login(self):
1500
"""Every entry is properly packed in the ui for the login page."""
1501
entries = ('login_email', 'login_password')
1502
self.assert_entries_are_packed_to_ui('login_details_vbox', entries)
1504
def test_entries_are_packed_to_ui_for_set_new_password(self):
1505
"""Every entry is packed in the ui for the reset password page."""
1506
entries = ('reset_code', 'reset_password1', 'reset_password2')
1507
self.assert_entries_are_packed_to_ui('set_new_password_details_vbox',
1510
def test_entries_are_packed_to_ui_for_request_password_token(self):
1511
"""Every entry is packed in the ui for the reset email page."""
1512
container_name = 'request_password_token_details_vbox'
1513
entries = ('reset_email',)
1514
self.assert_entries_are_packed_to_ui(container_name, entries)
1516
def test_on_login_back_button_clicked(self):
1517
"""Clicking login_back_button show registration page."""
1518
self.ui.login_back_button.clicked()
1519
self.assert_pages_visibility(enter_details=True)
1521
def test_on_login_connect_button_clicked(self):
1522
"""Clicking login_ok_button calls backend.login."""
1523
self.click_connect_with_valid_data()
1524
self.assert_backend_called(self.method, *self.method_args)
1526
def test_on_login_connect_button_clicked_morphs_to_processing_page(self):
1527
"""Clicking login_ok_button morphs to the processing page."""
1528
self.click_connect_with_valid_data()
1529
self.assert_pages_visibility(processing=True)
1531
def test_on_logged_in_morphs_to_finish_page(self):
1532
"""When user logged in the finish page is shown."""
1533
self.click_connect_with_valid_data()
1534
self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1535
self.assert_pages_visibility(finish=True)
1537
def test_on_login_error_morphs_to_login_page(self):
1538
"""On user login error, the previous page is shown."""
1539
self.click_connect_with_valid_data()
1540
self.ui.on_login_error(app_name=APP_NAME, error=self.error)
1541
self.assert_pages_visibility(login=True)
1543
def test_on_user_not_validated_morphs_to_verify_page(self):
1544
"""On user not validated, the verify page is shown."""
1545
self.click_connect_with_valid_data()
1546
self.ui.on_user_not_validated(app_name=APP_NAME, email=EMAIL)
1547
self.assert_pages_visibility(verify_email=True)
1549
def test_on_login_error_a_warning_is_shown(self):
1550
"""On user login error, a warning is shown with proper wording."""
1551
self.click_connect_with_valid_data()
1552
self.ui.on_login_error(app_name=APP_NAME, error=self.error)
1553
self.assert_correct_label_warning(self.ui.warning_label,
1556
def test_specific_errors_from_backend_are_shown(self):
1557
"""Specific errors from backend are used."""
1558
error = {'errtype': 'AuthenticationError',
1559
'message': 'We\'re so doomed.',
1560
'__all__': 'We all are gonna die.'}
1562
self.ui.on_login_error(app_name=APP_NAME, error=error)
1564
expected = '\n'.join((error['__all__'], error['message']))
1565
self.assert_correct_label_warning(self.ui.warning_label, expected)
1567
def test_back_to_registration_hides_warning(self):
1568
"""After user login error, warning is hidden when clicking 'Back'."""
1569
self.click_connect_with_valid_data()
1570
self.ui.on_login_error(app_name=APP_NAME, error=self.error)
1571
self.ui.login_back_button.clicked()
1572
self.assert_warnings_visibility()
1574
def test_login_ok_button_does_nothing_if_clicked_but_disabled(self):
1575
"""The join form can only be submitted if the button is sensitive."""
1576
self.patch(self.ui.login_email_entry, 'get_text', self._set_called)
1578
self.ui.login_ok_button.set_sensitive(False)
1579
self.ui.login_ok_button.clicked()
1580
self.assertFalse(self._called)
1582
self.ui.login_ok_button.set_sensitive(True)
1583
self.ui.login_ok_button.clicked()
1584
self.assertTrue(self._called)
1586
def test_user_and_pass_are_cached(self):
1587
"""Username and password are temporarily cached for further use."""
1588
self.click_connect_with_valid_data()
1589
self.assertEqual(self.ui.user_email, EMAIL)
1590
self.assertEqual(self.ui.user_password, PASSWORD)
1592
def test_after_login_success_finish_success(self):
1593
"""After logged_in is called, finish_success is called."""
1594
self.patch(self.ui, 'finish_success', self._set_called)
1596
self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1598
self.assertEqual(self._called, ((), {}))
1601
class LoginWithPingTestCase(LoginTestCase):
1602
"""Test suite for the login when the ping_url is set."""
1604
kwargs = dict(app_name=APP_NAME, tc_url=TC_URL, help_text=HELP_TEXT,
1606
method = 'login_and_ping'
1607
method_args = (APP_NAME, EMAIL, PASSWORD, PING_URL)
1610
class LoginValidationTestCase(UbuntuSSOClientTestCase):
1611
"""Test suite for the user login validation."""
1615
super(LoginValidationTestCase, self).setUp()
1616
self.ui.login_button.clicked()
1618
def test_warning_is_shown_if_empty_email(self):
1619
"""A warning message is shown if email is empty."""
1620
self.ui.login_email_entry.set_text('')
1622
self.ui.login_ok_button.clicked()
1624
self.assert_correct_entry_warning(self.ui.login_email_entry,
1626
self.assertNotIn('login', self.ui.backend._called)
1628
def test_warning_is_shown_if_invalid_email(self):
1629
"""A warning message is shown if email is invalid."""
1630
self.ui.login_email_entry.set_text('q')
1632
self.ui.login_ok_button.clicked()
1634
self.assert_correct_entry_warning(self.ui.login_email_entry,
1636
self.assertNotIn('login', self.ui.backend._called)
1638
def test_warning_is_shown_if_empty_password(self):
1639
"""A warning message is shown if password is empty."""
1640
self.ui.login_password_entry.set_text('')
1642
self.ui.login_ok_button.clicked()
1644
self.assert_correct_entry_warning(self.ui.login_password_entry,
1646
self.assertNotIn('login', self.ui.backend._called)
1648
def test_no_warning_messages_if_valid_data(self):
1649
"""No warning messages are shown if the data is valid."""
1650
# this will certainly NOT generate warnings
1651
self.click_connect_with_valid_data()
1653
self.assert_warnings_visibility()
1655
def test_no_warning_messages_if_valid_data_after_invalid_data(self):
1656
"""No warnings if the data is valid (with prior invalid data)."""
1657
# this will certainly generate warnings
1658
self.ui.login_ok_button.clicked()
1660
# this will certainly NOT generate warnings
1661
self.click_connect_with_valid_data()
1663
self.assert_warnings_visibility()
1666
class ResetPasswordTestCase(UbuntuSSOClientTestCase):
1667
"""Test suite for the reset password functionality."""
1671
super(ResetPasswordTestCase, self).setUp()
1672
self.ui.login_button.clicked()
1673
self.ui.forgotten_password_button.clicked()
1675
def test_forgotten_password_button_has_the_proper_wording(self):
1676
"""The forgotten_password_button has the proper wording."""
1677
actual = self.ui.forgotten_password_button.get_label()
1678
self.assertEqual(actual.decode('utf8'), gui.FORGOTTEN_PASSWORD_BUTTON)
1680
def test_on_forgotten_password_button_clicked_help_text(self):
1681
"""Clicking forgotten_password_button the help is properly changed."""
1682
wanted = gui.REQUEST_PASSWORD_TOKEN_LABEL % {'app_name': APP_NAME}
1683
self.assertEqual(self.ui.help_label.get_text().decode('utf8'), wanted)
1685
def test_on_forgotten_password_button_clicked_header_label(self):
1686
"""Clicking forgotten_password_button the title is properly changed."""
1687
self.assertEqual(self.ui.header_label.get_text().decode('utf8'),
1690
def test_on_forgotten_password_button_clicked_ok_button(self):
1691
"""Clicking forgotten_password_button the ok button reads 'Next'."""
1692
actual = self.ui.request_password_token_ok_button.get_label()
1693
self.assertEqual(actual.decode('utf8'), gui.NEXT)
1695
def test_on_forgotten_password_button_clicked_morphs_window(self):
1696
"""Clicking forgotten_password_button the proper page is shown."""
1697
self.assert_pages_visibility(request_password_token=True)
1699
def test_on_request_password_token_back_button_clicked(self):
1700
"""Clicking request_password_token_back_button show login screen."""
1701
self.ui.request_password_token_back_button.clicked()
1702
self.assert_pages_visibility(login=True)
1704
def test_request_password_token_ok_button_disabled_until_email_added(self):
1705
"""The button is disabled until email added."""
1706
is_sensitive = self.ui.request_password_token_ok_button.get_sensitive
1707
self.assertFalse(is_sensitive())
1709
self.ui.reset_email_entry.set_text('a')
1710
self.assertTrue(is_sensitive())
1712
self.ui.reset_email_entry.set_text('')
1713
self.assertFalse(is_sensitive())
1715
self.ui.reset_email_entry.set_text(' ')
1716
self.assertFalse(is_sensitive())
1718
def test_on_request_password_token_ok_button_clicked_morphs_window(self):
1719
"""Clicking request_password_token_ok_button morphs processing page."""
1720
self.click_request_password_token_with_valid_data()
1721
self.assert_pages_visibility(processing=True)
1723
def test_on_request_password_token_ok_button_clicked_calls_backend(self):
1724
"""Clicking request_password_token_ok_button the backend is called."""
1725
self.click_request_password_token_with_valid_data()
1726
self.assert_backend_called('request_password_reset_token',
1729
def test_on_password_reset_token_sent_morphs_window(self):
1730
"""When the reset token was sent, the reset password page is shown."""
1731
self.click_request_password_token_with_valid_data()
1732
self.ui.on_password_reset_token_sent(app_name=APP_NAME, email=EMAIL)
1733
self.assert_pages_visibility(set_new_password=True)
1735
def test_on_password_reset_token_sent_help_text(self):
1736
"""Clicking request_password_token_ok_button changes the help text."""
1737
self.click_request_password_token_with_valid_data()
1738
self.ui.on_password_reset_token_sent(app_name=APP_NAME, email=EMAIL)
1740
self.assertEqual(self.ui.help_label.get_text().decode('utf8'),
1741
gui.SET_NEW_PASSWORD_LABEL % {'email': EMAIL})
1743
def test_on_password_reset_token_sent_ok_button(self):
1744
"""After request_password_token_ok_button the ok button is updated."""
1745
self.click_request_password_token_with_valid_data()
1746
self.ui.on_password_reset_token_sent(app_name=APP_NAME, email=EMAIL)
1748
actual = self.ui.set_new_password_ok_button.get_label()
1749
self.assertEqual(actual.decode('utf8'), gui.RESET_PASSWORD)
1751
def test_on_password_reset_error_shows_login_page(self):
1752
"""When reset token wasn't successfully sent, login page is shown."""
1753
self.ui.on_password_reset_error(app_name=APP_NAME, error=self.error)
1754
self.assert_correct_label_warning(self.ui.warning_label,
1756
self.assert_pages_visibility(login=True)
1758
def test_specific_errors_from_backend_are_shown(self):
1759
"""Specific errors from backend are used."""
1760
error = {'errtype': 'ResetPasswordTokenError',
1761
'message': 'We\'re so doomed.',
1762
'__all__': 'We all are gonna die.'}
1764
self.ui.on_password_reset_error(app_name=APP_NAME, error=error)
1766
expected = '\n'.join((error['__all__'], error['message']))
1767
self.assert_correct_label_warning(self.ui.warning_label, expected)
1769
def test_ok_button_does_nothing_if_clicked_but_disabled(self):
1770
"""The password token can be requested if the button is sensitive."""
1771
self.patch(self.ui.reset_email_entry, 'get_text', self._set_called)
1773
self.ui.request_password_token_ok_button.set_sensitive(False)
1774
self.ui.request_password_token_ok_button.clicked()
1775
self.assertFalse(self._called)
1777
self.ui.request_password_token_ok_button.set_sensitive(True)
1778
self.ui.request_password_token_ok_button.clicked()
1779
self.assertTrue(self._called)
1782
class ResetPasswordValidationTestCase(UbuntuSSOClientTestCase):
1783
"""Test suite for the password reset validations."""
1785
def test_warning_is_shown_if_empty_email(self):
1786
"""A warning message is shown if emails are empty."""
1787
self.ui.reset_email_entry.set_text(' ')
1789
self.ui.request_password_token_ok_button.set_sensitive(True)
1790
self.ui.request_password_token_ok_button.clicked()
1792
self.assert_correct_entry_warning(self.ui.reset_email_entry,
1794
self.assertNotIn('request_password_reset_token',
1795
self.ui.backend._called)
1797
def test_warning_is_shown_if_invalid_email(self):
1798
"""A warning message is shown if email is invalid."""
1799
self.ui.reset_email_entry.set_text('q')
1801
self.ui.request_password_token_ok_button.clicked()
1803
self.assert_correct_entry_warning(self.ui.reset_email_entry,
1805
self.assertNotIn('request_password_reset_token',
1806
self.ui.backend._called)
1808
def test_no_warning_messages_if_valid_data(self):
1809
"""No warning messages are shown if the data is valid."""
1810
# this will certainly NOT generate warnings
1811
self.click_request_password_token_with_valid_data()
1813
self.assert_warnings_visibility()
1815
def test_no_warning_messages_if_valid_data_after_invalid_data(self):
1816
"""No warnings if the data is valid (with prior invalid data)."""
1817
# this will certainly generate warnings
1818
self.ui.request_password_token_ok_button.clicked()
1820
# this will certainly NOT generate warnings
1821
self.click_request_password_token_with_valid_data()
1823
self.assert_warnings_visibility()
1826
class SetNewPasswordTestCase(UbuntuSSOClientTestCase):
1827
"""Test suite for setting a new password functionality."""
1831
super(SetNewPasswordTestCase, self).setUp()
1832
self.click_request_password_token_with_valid_data()
1833
self.ui.on_password_reset_token_sent(app_name=APP_NAME, email=EMAIL)
1835
def test_on_set_new_password_ok_button_disabled(self):
1836
"""The set_new_password_ok_button is disabled until values added."""
1837
self.click_request_password_token_with_valid_data()
1838
self.assertFalse(self.ui.set_new_password_ok_button.get_sensitive())
1840
msg = 'set_new_password_ok_button must be sensitive (%s) for %r.'
1841
entries = (self.ui.reset_code_entry,
1842
self.ui.reset_password1_entry,
1843
self.ui.reset_password2_entry)
1844
for values in itertools.product(('', ' ', 'a'), repeat=3):
1846
for entry, val in zip(entries, values):
1848
expected &= bool(val and not val.isspace())
1850
actual = self.ui.set_new_password_ok_button.get_sensitive()
1851
self.assertEqual(expected, actual, msg % (expected, values))
1853
def test_on_set_new_password_ok_button_clicked_morphs_window(self):
1854
"""Clicking set_new_password_ok_button the processing page is shown."""
1855
self.click_set_new_password_with_valid_data()
1856
self.assert_pages_visibility(processing=True)
1858
def test_on_set_new_password_ok_button_clicked_calls_backend(self):
1859
"""Clicking set_new_password_ok_button the backend is called."""
1860
self.click_set_new_password_with_valid_data()
1861
self.assert_backend_called('set_new_password',
1862
APP_NAME, EMAIL, RESET_PASSWORD_TOKEN, PASSWORD)
1864
def test_on_password_changed_shows_login_page(self):
1865
"""When password was successfully changed the login page is shown."""
1866
self.ui.on_password_changed(app_name=APP_NAME, email=EMAIL)
1867
self.assert_correct_label_warning(self.ui.warning_label,
1868
gui.PASSWORD_CHANGED)
1869
self.assert_pages_visibility(login=True)
1871
def test_on_password_change_error_shows_login_page(self):
1872
"""When password wasn't changed the reset password page is shown."""
1873
self.ui.on_password_change_error(app_name=APP_NAME, error=self.error)
1874
self.assert_correct_label_warning(self.ui.warning_label,
1876
self.assert_pages_visibility(request_password_token=True)
1878
def test_specific_errors_from_backend_are_shown(self):
1879
"""Specific errors from backend are used."""
1880
error = {'errtype': 'NewPasswordError',
1881
'message': 'We\'re so doomed.',
1882
'__all__': 'We all are gonna die.'}
1884
self.ui.on_password_change_error(app_name=APP_NAME, error=error)
1886
expected = '\n'.join((error['__all__'], error['message']))
1887
self.assert_correct_label_warning(self.ui.warning_label, expected)
1889
def test_ok_button_does_nothing_if_clicked_but_disabled(self):
1890
"""The new password can only be set if the button is sensitive."""
1891
self.patch(self.ui.reset_code_entry, 'get_text', self._set_called)
1893
self.ui.set_new_password_ok_button.set_sensitive(False)
1894
self.ui.set_new_password_ok_button.clicked()
1895
self.assertFalse(self._called)
1897
self.ui.set_new_password_ok_button.set_sensitive(True)
1898
self.ui.set_new_password_ok_button.clicked()
1899
self.assertTrue(self._called)
1902
class SetNewPasswordValidationTestCase(UbuntuSSOClientTestCase):
1903
"""Test suite for validations for setting a new password."""
1905
def test_warning_is_shown_if_reset_code_empty(self):
1906
"""A warning message is shown if reset_code is empty."""
1907
self.ui.reset_code_entry.set_text('')
1909
self.ui.set_new_password_ok_button.set_sensitive(True)
1910
self.ui.set_new_password_ok_button.clicked()
1912
self.assert_correct_entry_warning(self.ui.reset_code_entry,
1914
self.assertNotIn('set_new_password', self.ui.backend._called)
1916
def test_password_help_is_always_shown(self):
1917
"""Password help text is correctly displayed."""
1918
visible = self.ui.reset_password_help_label.get_property('visible')
1919
self.assertTrue(visible, 'password help text is visible.')
1920
actual = self.ui.reset_password_help_label.get_text()
1921
self.assertEqual(actual.decode('utf8'), gui.PASSWORD_HELP)
1922
self.assertNotIn('set_new_password', self.ui.backend._called)
1924
def test_warning_is_shown_if_password_mismatch(self):
1925
"""A warning message is shown if password doesn't match."""
1926
self.ui.reset_password1_entry.set_text(PASSWORD)
1927
self.ui.reset_password2_entry.set_text(PASSWORD * 2)
1929
self.ui.set_new_password_ok_button.set_sensitive(True)
1930
self.ui.set_new_password_ok_button.clicked()
1932
self.assert_correct_entry_warning(self.ui.reset_password1_entry,
1933
gui.PASSWORD_MISMATCH)
1934
self.assert_correct_entry_warning(self.ui.reset_password2_entry,
1935
gui.PASSWORD_MISMATCH)
1936
self.assertNotIn('set_new_password', self.ui.backend._called)
1938
def test_warning_is_shown_if_password_too_weak(self):
1939
"""A warning message is shown if password is too weak."""
1940
# password will match but will be too weak
1941
for pwd in ('', 'h3lloWo', PASSWORD.lower(), 'helloWorld'):
1942
self.ui.reset_password1_entry.set_text(pwd)
1943
self.ui.reset_password2_entry.set_text(pwd)
1945
self.ui.set_new_password_ok_button.set_sensitive(True)
1946
self.ui.set_new_password_ok_button.clicked()
1948
self.assert_correct_entry_warning(self.ui.reset_password1_entry,
1949
gui.PASSWORD_TOO_WEAK)
1950
self.assert_correct_entry_warning(self.ui.reset_password2_entry,
1951
gui.PASSWORD_TOO_WEAK)
1952
self.assertNotIn('set_new_password', self.ui.backend._called)
1954
def test_no_warning_messages_if_valid_data(self):
1955
"""No warning messages are shown if the data is valid."""
1956
# this will certainly NOT generate warnings
1957
self.click_set_new_password_with_valid_data()
1959
self.assert_warnings_visibility()
1961
def test_no_warning_messages_if_valid_data_after_invalid_data(self):
1962
"""No warnings if the data is valid (with prior invalid data)."""
1963
# this will certainly generate warnings
1964
self.ui.set_new_password_ok_button.clicked()
1966
# this will certainly NOT generate warnings
1967
self.click_set_new_password_with_valid_data()
1969
self.assert_warnings_visibility()
1972
class SignalsTestCase(UbuntuSSOClientTestCase):
1973
"""Test suite for the backend signals."""
1975
def test_all_the_signals_are_listed(self):
1976
"""All the backend signals are listed to be bound."""
1977
for sig in ('CaptchaGenerated', 'CaptchaGenerationError',
1978
'UserRegistered', 'UserRegistrationError',
1979
'LoggedIn', 'LoginError', 'UserNotValidated',
1980
'EmailValidated', 'EmailValidationError',
1981
'PasswordResetTokenSent', 'PasswordResetError',
1982
'PasswordChanged', 'PasswordChangeError'):
1983
self.assertIn(sig, self.ui._signals)
1985
def test_signal_receivers_are_connected(self):
1986
"""Callbacks are connected to signals of interest."""
1987
msg1 = 'callback %r for signal %r must be added to the backend.'
1988
msg2 = 'callback %r for signal %r must be added to the ui log.'
1989
for signal, method in self.ui._signals.items():
1990
actual = self.ui.backend.callbacks.get(signal)
1991
self.assertEqual([method], actual, msg1 % (method, signal))
1992
actual = self.ui._signals_receivers.get(signal)
1993
self.assertEqual(method, actual, msg2 % (method, signal))
1995
def test_callbacks_only_log_when_app_name_doesnt_match(self):
1996
"""Callbacks do nothing but logging when app_name doesn't match."""
1997
mismatch_app_name = self.ui.app_name * 2
1998
for method in self.ui._signals.values():
1999
msgs = ('ignoring', method.__name__, repr(mismatch_app_name))
2000
method(mismatch_app_name, 'dummy')
2001
self.assertTrue(self.memento.check(logging.INFO, *msgs))
2002
self.memento.reset()
2004
def test_on_captcha_generated_is_not_called(self):
2005
"""on_captcha_generated is not called if incorrect app_name."""
2006
self.patch(self.ui, 'on_captcha_generated', self._set_called)
2007
mismatch_app_name = self.ui.app_name * 2
2008
self.ui._signals['CaptchaGenerated'](mismatch_app_name, 'dummy')
2009
self.assertFalse(self._called)
2011
def test_on_captcha_generation_error_is_not_called(self):
2012
"""on_captcha_generation_error is not called if incorrect app_name."""
2013
self.patch(self.ui, 'on_captcha_generation_error', self._set_called)
2014
mismatch_app_name = self.ui.app_name * 2
2015
self.ui._signals['CaptchaGenerationError'](mismatch_app_name, 'dummy')
2016
self.assertFalse(self._called)
2018
def test_on_user_registered_is_not_called(self):
2019
"""on_user_registered is not called if incorrect app_name."""
2020
self.patch(self.ui, 'on_user_registered', self._set_called)
2021
mismatch_app_name = self.ui.app_name * 2
2022
self.ui._signals['UserRegistered'](mismatch_app_name, 'dummy')
2023
self.assertFalse(self._called)
2025
def test_on_user_registration_error_is_not_called(self):
2026
"""on_user_registration_error is not called if incorrect app_name."""
2027
self.patch(self.ui, 'on_user_registration_error', self._set_called)
2028
mismatch_app_name = self.ui.app_name * 2
2029
self.ui._signals['UserRegistrationError'](mismatch_app_name, 'dummy')
2030
self.assertFalse(self._called)
2032
def test_on_email_validated_is_not_called(self):
2033
"""on_email_validated is not called if incorrect app_name."""
2034
self.patch(self.ui, 'on_email_validated', self._set_called)
2035
mismatch_app_name = self.ui.app_name * 2
2036
self.ui._signals['EmailValidated'](mismatch_app_name, 'dummy')
2037
self.assertFalse(self._called)
2039
def test_on_email_validation_error_is_not_called(self):
2040
"""on_email_validation_error is not called if incorrect app_name."""
2041
self.patch(self.ui, 'on_email_validation_error', self._set_called)
2042
mismatch_app_name = self.ui.app_name * 2
2043
self.ui._signals['EmailValidationError'](mismatch_app_name, 'dummy')
2044
self.assertFalse(self._called)
2046
def test_on_logged_in_is_not_called(self):
2047
"""on_logged_in is not called if incorrect app_name."""
2048
self.patch(self.ui, 'on_logged_in', self._set_called)
2049
mismatch_app_name = self.ui.app_name * 2
2050
self.ui._signals['LoggedIn'](mismatch_app_name, 'dummy')
2051
self.assertFalse(self._called)
2053
def test_on_login_error_is_not_called(self):
2054
"""on_login_error is not called if incorrect app_name."""
2055
self.patch(self.ui, 'on_login_error', self._set_called)
2056
mismatch_app_name = self.ui.app_name * 2
2057
self.ui._signals['LoginError'](mismatch_app_name, 'dummy')
2058
self.assertFalse(self._called)
2060
def test_on_user_not_validated_is_not_called(self):
2061
"""on_user_not_validated is not called if incorrect app_name."""
2062
self.patch(self.ui, 'on_user_not_validated', self._set_called)
2063
mismatch_app_name = self.ui.app_name * 2
2064
self.ui._signals['UserNotValidated'](mismatch_app_name, 'dummy')
2065
self.assertFalse(self._called)
2067
def test_on_password_reset_token_sent_is_not_called(self):
2068
"""on_password_reset_token_sent is not called if incorrect app_name."""
2069
self.patch(self.ui, 'on_password_reset_token_sent', self._set_called)
2070
mismatch_app_name = self.ui.app_name * 2
2071
self.ui._signals['PasswordResetTokenSent'](mismatch_app_name, 'dummy')
2072
self.assertFalse(self._called)
2074
def test_on_password_reset_error_is_not_called(self):
2075
"""on_password_reset_error is not called if incorrect app_name."""
2076
self.patch(self.ui, 'on_password_reset_error', self._set_called)
2077
mismatch_app_name = self.ui.app_name * 2
2078
self.ui._signals['PasswordResetError'](mismatch_app_name, 'dummy')
2079
self.assertFalse(self._called)
2081
def test_on_password_changed_is_not_called(self):
2082
"""on_password_changed is not called if incorrect app_name."""
2083
self.patch(self.ui, 'on_password_changed', self._set_called)
2084
mismatch_app_name = self.ui.app_name * 2
2085
self.ui._signals['PasswordChanged'](mismatch_app_name, 'dummy')
2086
self.assertFalse(self._called)
2088
def test_on_password_change_error_is_not_called(self):
2089
"""on_password_change_error is not called if incorrect app_name."""
2090
self.patch(self.ui, 'on_password_change_error', self._set_called)
2091
mismatch_app_name = self.ui.app_name * 2
2092
self.ui._signals['PasswordChangeError'](mismatch_app_name, 'dummy')
2093
self.assertFalse(self._called)
2096
class LoginOnlyTestCase(UbuntuSSOClientTestCase):
2097
"""Test suite for the login only GUI."""
2099
kwargs = dict(app_name=APP_NAME, tc_url=None, help_text=HELP_TEXT,
2102
def test_login_is_first_page(self):
2103
"""When starting, the login page is the first one."""
2104
self.assert_pages_visibility(login=True)
2106
def test_no_back_button(self):
2107
"""There is no back button in the login screen."""
2108
self.assertFalse(self.ui.login_back_button.get_property('visible'))
2110
def test_login_ok_button_has_the_focus(self):
2111
"""The login_ok_button has the focus."""
2112
self.assertTrue(self.ui.login_ok_button.is_focus())
2114
def test_help_text_is_used(self):
2115
"""The passed help_text is used."""
2116
self.assertEqual(self.ui.help_label.get_text().decode('utf8'),
2120
class ReturnCodeTestCase(UbuntuSSOClientTestCase):
2121
"""Test the return codes."""
2124
super(ReturnCodeTestCase, self).setUp()
2125
self.patch(gui.sys, 'exit', self._set_called)
2127
def test_closing_main_window(self):
2128
"""When closing the main window, USER_CANCELLATION is called."""
2129
self.ui.window.emit('delete-event', Gdk.Event())
2130
self.assertEqual(self._called, ((gui.USER_CANCELLATION,), {}))
2132
def test_every_cancel_calls_proper_callback(self):
2133
"""When any cancel button is clicked, USER_CANCELLATION is called."""
2134
self.patch(self.ui.backend, 'disconnect_from_signal', lambda *a: None)
2135
msg = 'USER_CANCELLATION should be returned when %r is clicked.'
2136
buttons = filter(lambda name: 'cancel_button' in name, self.ui.widgets)
2137
for name in buttons:
2138
widget = getattr(self.ui, name)
2140
self.assertEqual(self._called, ((gui.USER_CANCELLATION,), {}),
2142
self._called = False
2144
def test_on_user_registration_error_proper_callback_is_called(self):
2145
"""On UserRegistrationError, USER_CANCELLATION is called."""
2146
self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
2147
self.ui.on_close_clicked()
2149
self.assertEqual(self._called, ((gui.USER_CANCELLATION,), {}))
2151
def test_on_email_validated_proper_callback_is_called(self):
2152
"""On EmailValidated, REGISTRATION_SUCCESS is called."""
2153
self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
2154
self.ui.on_close_clicked()
2156
self.assertEqual(self._called, ((gui.USER_SUCCESS,), {}))
2158
def test_on_email_validation_error_proper_callback_is_called(self):
2159
"""On EmailValidationError, USER_CANCELLATION is called."""
2160
self.ui.on_email_validation_error(app_name=APP_NAME, error=self.error)
2161
self.ui.on_close_clicked()
2163
self.assertEqual(self._called, ((gui.USER_CANCELLATION,), {}))
2165
def test_on_logged_in_proper_callback_is_called(self):
2166
"""On LoggedIn, LOGIN_SUCCESS is called."""
2167
self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
2168
self.ui.on_close_clicked()
2170
self.assertEqual(self._called, ((gui.USER_SUCCESS,), {}))
2172
def test_on_login_error_proper_callback_is_called(self):
2173
"""On LoginError, USER_CANCELLATION is called."""
2174
self.click_connect_with_valid_data()
2175
self.ui.on_login_error(app_name=APP_NAME, error=self.error)
2176
self.ui.on_close_clicked()
2178
self.assertEqual(self._called, ((gui.USER_CANCELLATION,), {}))
2180
def test_registration_success_even_if_prior_registration_error(self):
2181
"""Only one callback is called with the final outcome.
2183
When the user successfully registers, REGISTRATION_SUCCESS is
2184
called even if there were errors before.
2187
self.click_join_with_valid_data()
2188
self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
2189
self.click_join_with_valid_data()
2190
self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
2191
self.ui.on_close_clicked()
2193
self.assertEqual(self._called, ((gui.USER_SUCCESS,), {}))
2195
def test_login_success_even_if_prior_login_error(self):
2196
"""Only one callback is called with the final outcome.
2198
When the user successfully logs in, LOGIN_SUCCESS is called even if
2199
there were errors before.
2202
self.click_connect_with_valid_data()
2203
self.ui.on_login_error(app_name=APP_NAME, error=self.error)
2204
self.click_connect_with_valid_data()
2205
self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
2206
self.ui.on_close_clicked()
2208
self.assertEqual(self._called, ((gui.USER_SUCCESS,), {}))
2210
def test_user_cancelation_even_if_prior_registration_error(self):
2211
"""Only one callback is called with the final outcome.
2213
When the user closes the window, USER_CANCELLATION is called even if
2214
there were registration errors before.
2217
self.click_join_with_valid_data()
2218
self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
2219
self.ui.join_cancel_button.clicked()
2221
self.assertEqual(self._called, ((gui.USER_CANCELLATION,), {}))
2223
def test_user_cancelation_even_if_prior_login_error(self):
2224
"""Only one callback is called with the final outcome.
2226
When the user closes the window, USER_CANCELLATION is called even if
2227
there were login errors before.
2230
self.click_connect_with_valid_data()
2231
self.ui.on_login_error(app_name=APP_NAME, error=self.error)
2232
self.ui.login_cancel_button.clicked()
2234
self.assertEqual(self._called, ((gui.USER_CANCELLATION,), {}))
2237
class DefaultButtonsTestCase(UbuntuSSOClientTestCase):
2238
"""Each UI page has a default button when visible."""
2242
super(DefaultButtonsTestCase, self).setUp()
2244
('enter_details_vbox', 'join_ok_button'),
2245
('tc_browser_vbox', 'tc_back_button'),
2246
('verify_email_vbox', 'verify_token_button'),
2247
('login_vbox', 'login_ok_button'),
2248
('request_password_token_vbox',
2249
'request_password_token_ok_button'),
2250
('set_new_password_vbox', 'set_new_password_ok_button'),
2251
('success_vbox', 'finish_close_button'),
2252
('error_vbox', 'finish_close_button'),
2253
('processing_vbox', None))
2255
def test_pages_have_default_widget_set(self):
2256
"""Each page has a proper button as default widget."""
2257
msg = 'Page %r must have %r as default_widget (got %r instead).'
2258
for pname, bname in self.mapping:
2259
page = getattr(self.ui, pname)
2260
button = bname and getattr(self.ui, bname)
2261
self.assertTrue(page.default_widget is button,
2262
msg % (pname, bname, page.default_widget))
2264
def test_default_widget_can_default(self):
2265
"""Each default button can default."""
2266
msg = 'Button %r must have can-default enabled.'
2267
for _, bname in self.mapping:
2268
if bname is not None:
2269
button = getattr(self.ui, bname)
2270
self.assertTrue(button.get_property('can-default'),
2273
def test_set_current_page_grabs_focus_for_default_button(self):
2274
"""Setting the current page marks the default widget as default."""
2275
msg = '%r "has_default" must be True when %r if the current page.'
2276
for pname, bname in self.mapping:
2277
if bname is not None:
2278
page = getattr(self.ui, pname)
2279
self.patch(page.default_widget, 'grab_default',
2281
self.ui._set_current_page(page)
2282
self.assertEqual(self._called, ((), {}), msg % (bname, pname))
2283
self._called = False
2286
class RunTestCase(BasicTestCase):
2289
"""Calling run.gui() a UI instance is created."""
2291
self.patch(gui, 'UbuntuSSOClientGUI',
2292
lambda **kw: called.append(('GUI', kw)))
2293
self.patch(gui.Gtk, 'main',
2294
lambda: called.append('main'))
2296
kwargs = dict(foo='foo', bar='bar', baz='yadda', yadda=0)
2299
kwargs['close_callback'] = gui.Gtk.main_quit
2300
self.assertEqual(called, [('GUI', kwargs), 'main'])