~mvo/ubuntu-sso-client/strawman-lp711413

« back to all changes in this revision

Viewing changes to ubuntu_sso/qt/tests/__init__.py

  • Committer: Natalia B. Bidart
  • Date: 2011-12-20 16:29:34 UTC
  • Revision ID: natalia.bidart@canonical.com-20111220162934-2s5xou06v3usxyr6
Tags: ubuntu-sso-client-2_99_0
- Release v2.99.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- coding: utf-8 -*-
 
2
 
 
3
# Authors: Natalia B. Bidart <natalia.bidart@canonical.com>
 
4
#          Diego Sarmentero <diego.sarmentero@canonical.com>
2
5
#
3
 
# Copyright 2011-2012 Canonical Ltd.
 
6
# Copyright 2011 Canonical Ltd.
4
7
#
5
8
# This program is free software: you can redistribute it and/or modify it
6
9
# under the terms of the GNU General Public License version 3, as published
13
16
#
14
17
# You should have received a copy of the GNU General Public License along
15
18
# with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 
#
17
 
# In addition, as a special exception, the copyright holders give
18
 
# permission to link the code of portions of this program with the
19
 
# OpenSSL library under certain conditions as described in each
20
 
# individual source file, and distribute linked combinations
21
 
# including the two.
22
 
# You must obey the GNU General Public License in all respects
23
 
# for all of the code used other than OpenSSL.  If you modify
24
 
# file(s) with this exception, you may extend this exception to your
25
 
# version of the file(s), but you are not obligated to do so.  If you
26
 
# do not wish to do so, delete this exception statement from your
27
 
# version.  If you delete this exception statement from all source
28
 
# files in the program, then also delete it here.
 
19
 
29
20
"""Test the Ui code."""
30
21
 
31
 
from PyQt4 import QtGui, QtCore
32
22
from twisted.internet import defer
33
23
from twisted.trial.unittest import TestCase
34
24
 
35
 
from ubuntu_sso import main, NO_OP
36
 
from ubuntu_sso.qt import ERROR_STYLE, maybe_elide_text, TITLE_STYLE
37
 
from ubuntu_sso.tests import (
38
 
    APP_NAME,
39
 
    HELP_TEXT,
40
 
    PING_URL,
41
 
    POLICY_URL,
42
 
    TC_URL,
43
 
    WINDOW_ID,
44
 
)
45
 
 
46
 
KWARGS = dict(app_name=APP_NAME, help_text=HELP_TEXT, ping_url=PING_URL,
47
 
              policy_url=POLICY_URL, tc_url=TC_URL, window_id=WINDOW_ID)
48
 
 
49
 
# is ok to access private method/attrs in tests
50
 
# pylint: disable=W0212
51
 
 
52
 
 
53
 
def build_string_for_pixels(label, width):
54
 
    """Return a random string that will be as big as 'width' in pixels."""
55
 
    char = 'm'
56
 
    fm = QtGui.QFontMetrics(label.font())
57
 
    pixel_width = fm.width(char)
58
 
    chars = int(width / pixel_width)
59
 
    result = char * chars
60
 
    assert pixel_width * chars <= width
61
 
    return result
 
25
from ubuntu_sso import main
62
26
 
63
27
 
64
28
class FakedObject(object):
85
49
        return inner
86
50
 
87
51
 
88
 
class FakedSSOLoginBackend(FakedObject):
89
 
    """A faked sso_login backend."""
90
 
 
91
 
    exposed_methods = [
92
 
        'connect_to_signal',
93
 
        'generate_captcha',
94
 
        'login',
95
 
        'login_and_ping',
96
 
        'register_user',
97
 
        'request_password_reset_token',
98
 
        'set_new_password',
99
 
        'validate_email',
100
 
        'validate_email_and_ping',
101
 
    ]
102
 
 
103
 
 
104
 
class FakedBackend(object):
 
52
class FakedBackend(FakedObject):
105
53
    """A faked backend."""
106
54
 
107
 
    sso_login = FakedSSOLoginBackend()
108
 
 
109
55
 
110
56
class FakePageUiStyle(object):
111
57
    """Fake the page."""
174
120
        return self.properties['visible']
175
121
 
176
122
 
177
 
class FakeOverlay(object):
178
 
 
179
 
    """A fake delay overlay."""
180
 
 
181
 
    def __init__(self, *args, **kwargs):
182
 
        """Initialize."""
183
 
        self.show_counter = 0
184
 
        self.hide_counter = 0
185
 
        self.args = (args, kwargs)
186
 
 
187
 
    def show(self):
188
 
        """Fake show."""
189
 
        self.show_counter += 1
190
 
 
191
 
    def hide(self):
192
 
        """Fake hide."""
193
 
        self.hide_counter += 1
194
 
 
195
 
    def resize(self, *args):
196
 
        """Fake resize."""
197
 
 
198
 
 
199
 
class FakeSignal(object):
200
 
 
201
 
    """A fake PyQt signal."""
202
 
 
203
 
    def __init__(self, *args, **kwargs):
204
 
        """Initialize."""
205
 
        self.target = None
206
 
 
207
 
    def connect(self, target):
208
 
        """Fake connect."""
209
 
        self.target = target
210
 
 
211
 
    def disconnect(self, *args):
212
 
        """Fake disconnect."""
213
 
        self.target = None
214
 
 
215
 
    def emit(self, *args):
216
 
        """Fake emit."""
217
 
        if self.target:
218
 
            self.target(*args)
219
 
 
220
 
 
221
 
class FakeOverlaySignal(FakeSignal):
222
 
 
223
 
    """Fake Signal for show and hide overlay."""
224
 
 
225
 
    def __init__(self, target):
226
 
        super(FakeOverlaySignal, self).__init__()
227
 
        self.target = target
228
 
 
229
 
    def connect(self, target):
230
 
        """We ignore the target and just call the function from the init."""
231
 
 
232
 
 
233
 
class FakeWizardPage(object):
234
 
 
235
 
    """A fake wizard page."""
236
 
 
237
 
    def __init__(self, *args, **kwargs):
238
 
        self.has_back_button = True
239
 
 
240
 
    # pylint: disable=C0103
241
 
    def initializePage(self):
242
 
        """Fake initializePage."""
243
 
 
244
 
 
245
 
class FakeMainWindow(object):
246
 
 
247
 
    """A fake MainWindow."""
248
 
 
249
 
    currentIdChanged = FakeSignal()
250
 
    loginSuccess = FakeSignal()
251
 
    registrationIncomplete = FakeSignal()
252
 
    registrationSuccess = FakeSignal()
253
 
    userCancellation = FakeSignal()
254
 
    shown = False
255
 
    SYNC_NOW_OR_LATER_PAGE = 4
256
 
    CONGRATULATIONS_PAGE = 5
257
 
    folders_page_id = 6
258
 
    local_folders_page_id = 7
259
 
 
260
 
    def __init__(self, close_callback=None):
261
 
        self.button_texts = []
262
 
        self.button_layout = None
263
 
        self.options = []
264
 
        self.overlay = FakeOverlay()
265
 
        self.local_folders_page = FakeWizardPage()
266
 
        self.folders_page = FakeWizardPage()
267
 
        self.app_name = APP_NAME
268
 
 
269
 
    def show(self):
270
 
        """Fake method."""
271
 
        self.shown = True
272
 
 
273
 
    # pylint: disable=C0103
274
 
    def setButtonText(self, button, text):
275
 
        """Fake setButtonText."""
276
 
        self.button_texts.append((button, text))
277
 
 
278
 
    def setButtonLayout(self, layout):
279
 
        """Fake setButtonText."""
280
 
        self.button_layout = layout
281
 
 
282
 
    def button(self, button):
283
 
        """Fake button method to obtain the wizard buttons."""
284
 
        return QtGui.QPushButton()
285
 
 
286
 
    def setOption(self, *args):
287
 
        """Fake setOption."""
288
 
        self.options.append(args)
289
 
 
290
 
    def next(self):
291
 
        """Fake next."""
292
 
 
293
 
    def reject(self):
294
 
        """Fake reject."""
295
 
 
296
 
 
297
 
class FakeController(object):
298
 
 
299
 
    """A fake controller for the tests."""
300
 
 
301
 
    is_fake = True
302
 
    set_next_validation = lambda *a, **kw: None
303
 
 
304
 
    def __init__(self, *args, **kwargs):
305
 
        self.args = (args, kwargs)
306
 
 
307
 
    # pylint: disable=C0103
308
 
    def setupUi(self, view):
309
 
        """Fake the setup."""
310
 
    # pylint: enable=C0103
311
 
 
312
 
 
313
 
class FakeWizard(object):
314
 
 
315
 
    """Replace wizard() function on wizard pages."""
316
 
 
317
 
    customButtonClicked = QtCore.QObject()
318
 
    sign_in_page_id = 1
319
 
    setup_account_id = 2
320
 
    current_user_id = 3
321
 
    email_verification_id = 4
322
 
    success_id = 5
323
 
    error_id = 6
324
 
    forgotten_id = 7
325
 
    reset_password_id = 8
326
 
 
327
 
    def __init__(self):
328
 
        self.overlay = FakeOverlay()
329
 
        self.called = []
330
 
        self.buttons = {}
331
 
        self.buttons_text = {}
332
 
        self._next_id = -1
333
 
        self.sign_in_page = object()
334
 
        self.setup_account = object()
335
 
        self.current_user = object()
336
 
        self.email_verification = object()
337
 
        self.success = object()
338
 
        self.error = object()
339
 
        self.forgotten = object()
340
 
        self.reset_password = object()
341
 
        self._pages = {
342
 
            self.sign_in_page: self.sign_in_page_id,
343
 
            self.setup_account: self.setup_account_id,
344
 
            self.current_user: self.current_user_id,
345
 
            self.email_verification: self.email_verification_id,
346
 
            self.success: self.success_id,
347
 
            self.error: self.error_id,
348
 
            self.forgotten: self.forgotten_id,
349
 
            self.reset_password: self.reset_password_id,
350
 
        }
351
 
 
352
 
    # Invalid name "setButtonLayout", "setOption"
353
 
    # pylint: disable=C0103
354
 
 
355
 
    def setButtonText(self, key, value):
356
 
        """Fake setButtonText."""
357
 
        self.buttons_text[key] = value
358
 
 
359
 
    def setButtonLayout(self, *args, **kwargs):
360
 
        """Fake the functionality of setButtonLayout on QWizard class."""
361
 
        self.called.append(('setButtonLayout', (args, kwargs)))
362
 
 
363
 
    def setOption(self, *args, **kwargs):
364
 
        """Fake the functionality of setOption on QWizard class."""
365
 
        self.called.append(('setOption', (args, kwargs)))
366
 
 
367
 
    # pylint: enable=C0103
368
 
 
369
 
    def button(self, button_id):
370
 
        """Fake the functionality of button on QWizard class."""
371
 
        return self.buttons.setdefault(button_id, QtGui.QPushButton())
372
 
 
373
 
    def next(self):
374
 
        """Fake next."""
375
 
        self.called.append('next')
376
 
 
377
 
    def page(self, p_id):
378
 
        """Fake page method."""
379
 
        return self
380
 
 
381
 
    def set_titles(self, title):
382
 
        """Fake set_titles."""
383
 
        self.called.append(('set_titles', title))
384
 
 
385
 
 
386
 
class FakeWizardButtonStyle(FakeWizard):
387
 
 
388
 
    """Fake Wizard with button style implementation."""
389
 
 
390
 
    SIGN_IN_PAGE_ID = 1
391
 
 
392
 
    # pylint: disable=C0103
393
 
    def __init__(self):
394
 
        super(FakeWizardButtonStyle, self).__init__()
395
 
        self.data = {}
396
 
        self.customButtonClicked = self
397
 
 
398
 
    def setDefault(self, value):
399
 
        """Fake setDefault for button."""
400
 
        self.data['default'] = value
401
 
 
402
 
    def isDefault(self):
403
 
        """Fake isDefault."""
404
 
        return self.data['default']
405
 
    # pylint: enable=C0103
406
 
 
407
 
    def connect(self, func):
408
 
        """Fake customButtonClicked connect."""
409
 
        self.data['connect'] = func
410
 
 
411
 
    def disconnect(self, func):
412
 
        """Fake customButtonClicked disconnect."""
413
 
        self.data['disconnect'] = func
414
 
 
415
 
    def button(self, button_id):
416
 
        """Fake the functionality of button on QWizard class."""
417
 
        return self
418
 
 
419
 
    def style(self):
420
 
        """Fake button style."""
421
 
        return self
422
 
 
423
 
    def next(self):
424
 
        """Fake next for wizard."""
425
 
        self.data['next'] = True
426
 
 
427
 
 
428
123
class BaseTestCase(TestCase):
429
124
    """The base test case."""
430
125
 
431
 
    kwargs = None
432
 
    ui_class = None
433
 
    ui_signals = ()
434
 
    ui_backend_signals = ()
435
 
    ui_wizard_class = FakeWizard
436
 
 
437
126
    @defer.inlineCallbacks
438
127
    def setUp(self):
439
128
        yield super(BaseTestCase, self).setUp()
440
129
        self._called = False
441
 
        backend = FakedBackend()
442
 
        self.sso_login_backend = backend.sso_login
443
 
        self.patch(main, 'get_sso_client',
444
 
                   lambda *a: defer.succeed(backend))
445
 
 
446
 
        self.app_name = APP_NAME
447
 
        self.ping_url = PING_URL
448
 
        self.signal_results = []
449
 
 
450
 
        # self.ui_class is not callable
451
 
        # pylint: disable=E1102, C0103, W0212
452
 
        self.ui = None
453
 
        self.wizard = None
454
 
        if self.ui_class is not None:
455
 
            kwargs = dict(KWARGS) if self.kwargs is None else self.kwargs
456
 
            self.ui = self.ui_class(**kwargs)
457
 
 
458
 
            for signal in self.ui_signals:
459
 
                self.patch(self.ui_class, signal, FakeSignal())
460
 
 
461
 
            if self.ui_wizard_class is not None:
462
 
                self.wizard = self.ui_wizard_class()
463
 
                self.patch(self.ui, 'wizard', lambda: self.wizard)
464
 
 
465
 
            self.ui.show()
466
 
            self.addCleanup(self.ui.hide)
 
130
        self.sso_login_backend = FakedBackend()
 
131
        self.patch(main, 'get_sso_login_backend',
 
132
                   lambda *a: defer.succeed(self.sso_login_backend))
467
133
 
468
134
    def _set_called(self, *args, **kwargs):
469
135
        """Store 'args' and 'kwargs' for test assertions."""
470
136
        self._called = (args, kwargs)
471
 
 
472
 
    def assert_backend_called(self, method, *args, **kwargs):
473
 
        """Check that 'method(*args, **kwargs)' was called in the backend."""
474
 
        self.assertIn(method, self.ui.backend._called)
475
 
 
476
 
        call = self.ui.backend._called[method]
477
 
        self.assertEqual(call[0], args)
478
 
 
479
 
        reply_handler = call[1].pop('reply_handler')
480
 
        self.assertEqual(reply_handler, NO_OP)
481
 
 
482
 
        error_handler = call[1].pop('error_handler')
483
 
        self.assertEqual(error_handler.func, self.ui._handle_error)
484
 
 
485
 
        self.assertEqual(call[1], kwargs)
486
 
 
487
 
    def assert_signal_emitted(self, signal, signal_args,
488
 
                              trigger, *args, **kwargs):
489
 
        """Check that 'trigger(*signal_args)' emits 'signal(*signal_args)'."""
490
 
        signal.connect(lambda *a: self.signal_results.append(a))
491
 
 
492
 
        trigger(*args, **kwargs)
493
 
 
494
 
        self.assertEqual(self.signal_results, [signal_args])
495
 
 
496
 
    def assert_title_correct(self, title_label, expected, max_width):
497
 
        """Check that the label's text is equal to 'expected'."""
498
 
        label = QtGui.QLabel()
499
 
        maybe_elide_text(label, expected, max_width)
500
 
 
501
 
        self.assertEqual(TITLE_STYLE % unicode(label.text()),
502
 
                         unicode(title_label.text()))
503
 
        self.assertEqual(unicode(label.toolTip()),
504
 
                         unicode(title_label.toolTip()))
505
 
        self.assertTrue(title_label.isVisible())
506
 
 
507
 
    def assert_subtitle_correct(self, subtitle_label, expected, max_width):
508
 
        """Check that the subtitle is equal to 'expected'."""
509
 
        label = QtGui.QLabel()
510
 
        maybe_elide_text(label, expected, max_width)
511
 
 
512
 
        self.assertEqual(unicode(label.text()), unicode(subtitle_label.text()))
513
 
        self.assertEqual(unicode(label.toolTip()),
514
 
                         unicode(subtitle_label.toolTip()))
515
 
        self.assertTrue(subtitle_label.isVisible())
516
 
 
517
 
    def assert_error_correct(self, error_label, expected, max_width):
518
 
        """Check that the error 'error_label' displays 'expected' as text."""
519
 
        label = QtGui.QLabel()
520
 
        maybe_elide_text(label, expected, max_width)
521
 
 
522
 
        self.assertEqual(ERROR_STYLE % unicode(label.text()),
523
 
                         unicode(error_label.text()))
524
 
        self.assertEqual(unicode(label.toolTip()),
525
 
                         unicode(error_label.toolTip()))
526
 
        self.assertTrue(error_label.isVisible())
527
 
 
528
 
    def get_pixmap_data(self, pixmap):
529
 
        """Get the raw data of a QPixmap."""
530
 
        byte_array = QtCore.QByteArray()
531
 
        array_buffer = QtCore.QBuffer(byte_array)
532
 
        pixmap.save(array_buffer, "PNG")
533
 
        return byte_array
534
 
 
535
 
    # Invalid name "assertEqualPixmap"
536
 
    # pylint: disable=C0103
537
 
 
538
 
    def assertEqualPixmaps(self, pixmap1, pixmap2):
539
 
        """Compare two Qt pixmaps."""
540
 
        d1 = self.get_pixmap_data(pixmap1)
541
 
        d2 = self.get_pixmap_data(pixmap2)
542
 
        self.assertEqual(d1, d2)
543
 
 
544
 
    # pylint: enable=C0103
545
 
 
546
 
    @defer.inlineCallbacks
547
 
    def test_setup_page(self):
548
 
        """Test the backend signal connection."""
549
 
        if self.ui is None or getattr(self.ui, 'setup_page', None) is None:
550
 
            return
551
 
 
552
 
        called = []
553
 
        self.patch(self.ui, '_setup_signals',
554
 
                   lambda *a: called.append('_setup_signals'))
555
 
        self.patch(self.ui, '_connect_ui',
556
 
                   lambda *a: called.append('_connect_ui'))
557
 
        self.patch(self.ui, '_set_translated_strings',
558
 
                   lambda *a: called.append('_set_translated_strings'))
559
 
 
560
 
        yield self.ui.setup_page()
561
 
 
562
 
        self.assertEqual(self.ui.backend, self.sso_login_backend)
563
 
 
564
 
        for signal in self.ui_backend_signals:
565
 
            self.assertIn(signal, self.ui._signals)
566
 
            self.assertTrue(callable(self.ui._signals[signal]))
567
 
 
568
 
        expected = ['_set_translated_strings', '_connect_ui', '_setup_signals']
569
 
        self.assertEqual(expected, called)
570
 
 
571
 
 
572
 
class PageBaseTestCase(BaseTestCase):
573
 
 
574
 
    """BaseTestCase with some specialization for the Wizard Pages."""
575
 
 
576
 
    @defer.inlineCallbacks
577
 
    def setUp(self):
578
 
        yield super(PageBaseTestCase, self).setUp()
579
 
        self._overlay_show_counter = 0
580
 
        self._overlay_hide_counter = 0
581
 
 
582
 
        if self.ui is not None:
583
 
            fake_show_overlay_signal = FakeOverlaySignal(
584
 
                self._show_overlay_slot)
585
 
            fake_hide_overlay_signal = FakeOverlaySignal(
586
 
                self._hide_overlay_slot)
587
 
            self.patch(self.ui, "processingStarted", fake_show_overlay_signal)
588
 
            self.patch(self.ui, "processingFinished", fake_hide_overlay_signal)
589
 
 
590
 
    def _show_overlay_slot(self):
591
 
        """Fake show overlay slot."""
592
 
        self._overlay_show_counter += 1
593
 
 
594
 
    def _hide_overlay_slot(self):
595
 
        """Fake hide overlay slot."""
596
 
        self._overlay_hide_counter += 1
597
 
 
598
 
    def assert_signal_emitted(self, signal, signal_args,
599
 
                              trigger, *args, **kwargs):
600
 
        """Check that 'trigger(*args, **kwargs)' emits 'signal(*signal_args)'.
601
 
 
602
 
        Also check that the _overlay_hide_counter was increased by one, and
603
 
        that the ui is enabled.
604
 
 
605
 
        """
606
 
        super(PageBaseTestCase, self).assert_signal_emitted(signal,
607
 
            signal_args, trigger, *args, **kwargs)
608
 
        self.assertEqual(self._overlay_hide_counter, 1)
609
 
        self.assertTrue(self.ui.isEnabled())
610
 
 
611
 
    # pylint: disable=W0221
612
 
 
613
 
    def assert_title_correct(self, expected):
614
 
        """Check that the title is equal to 'expected'."""
615
 
        check = super(PageBaseTestCase, self).assert_title_correct
616
 
        check(self.ui.header.title_label, expected,
617
 
              self.ui.header.max_title_width)
618
 
 
619
 
    def assert_subtitle_correct(self, expected):
620
 
        """Check that the subtitle is equal to 'expected'."""
621
 
        check = super(PageBaseTestCase, self).assert_subtitle_correct
622
 
        check(self.ui.header.subtitle_label, expected,
623
 
              self.ui.header.max_subtitle_width)
624
 
 
625
 
    # pylint: enable=W0221