Merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.8 into lp:ubuntu/natty/ubuntu-sso-client

Proposed by Natalia Bidart
Status: Merged
Merged at revision: 23
Proposed branch: lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.8
Merge into: lp:ubuntu/natty/ubuntu-sso-client
Diff against target: 2287 lines (+676/-719)
21 files modified
PKG-INFO (+1/-1)
bin/ubuntu-sso-login (+8/-4)
contrib/__init__.py (+0/-18)
contrib/dbus_util.py (+0/-77)
contrib/test (+0/-146)
contrib/testing/__init__.py (+0/-1)
contrib/testing/dbus-session.conf (+0/-63)
contrib/testing/testcase.py (+0/-123)
debian/changelog (+18/-0)
run-tests (+13/-4)
setup.py (+3/-4)
ubuntu_sso/credentials.py (+24/-32)
ubuntu_sso/gtk/gui.py (+27/-32)
ubuntu_sso/gtk/tests/test_gui.py (+156/-87)
ubuntu_sso/logger.py (+1/-0)
ubuntu_sso/main.py (+53/-4)
ubuntu_sso/tests/__init__.py (+14/-0)
ubuntu_sso/tests/bin/show_gui (+7/-6)
ubuntu_sso/tests/bin/show_nm_state (+3/-3)
ubuntu_sso/tests/test_credentials.py (+56/-100)
ubuntu_sso/tests/test_main.py (+292/-14)
To merge this branch: bzr merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.8
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+46049@code.launchpad.net

Description of the change

  * New upstream release:

    [ Natalia B. Bidart <email address hidden>]
     - The service should shutdown when unused
       (LP: #701606),
     - On error, {find,clear,store}_credentials send proper CredentialsError
       signal (LP: #696676).
     - No more gobject dependency on non-GUI classes!
       (LP: #695798).
     - After login, if the storing of credentials fails, send LoginError
       (LP: #693531).
     - Use ubuntuone-dev-tools to run the tests and the lint checker
       (LP: #686606).

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'PKG-INFO'
--- PKG-INFO 2010-12-16 16:31:34 +0000
+++ PKG-INFO 2011-01-13 00:33:10 +0000
@@ -1,6 +1,6 @@
1Metadata-Version: 1.11Metadata-Version: 1.1
2Name: ubuntu-sso-client2Name: ubuntu-sso-client
3Version: 1.1.73Version: 1.1.8
4Summary: Ubuntu Single Sign-On client4Summary: Ubuntu Single Sign-On client
5Home-page: https://launchpad.net/ubuntu-sso-client5Home-page: https://launchpad.net/ubuntu-sso-client
6Author: Natalia Bidart6Author: Natalia Bidart
77
=== modified file 'bin/ubuntu-sso-login'
--- bin/ubuntu-sso-login 2010-10-11 13:22:16 +0000
+++ bin/ubuntu-sso-login 2011-01-13 00:33:10 +0000
@@ -21,7 +21,9 @@
2121
22"""Run the dbus service for UserManagement and ApplicationCredentials."""22"""Run the dbus service for UserManagement and ApplicationCredentials."""
2323
24# import decimal even if we don't need it.24# Invalid name "ubuntu-sso-login", pylint: disable=C0103
25
26# import decimal even if we don't need it, pylint: disable=W0611
25import decimal27import decimal
26# This is a workaround for LP: #467397. Some module in our depency chain sets28# This is a workaround for LP: #467397. Some module in our depency chain sets
27# the locale and imports decimal, and that generates the following trace:29# the locale and imports decimal, and that generates the following trace:
@@ -70,8 +72,8 @@
70 # See the link below for info:72 # See the link below for info:
71 # www.listware.net/201004/gtk-devel-list/115067-unix-signals-in-glib.html73 # www.listware.net/201004/gtk-devel-list/115067-unix-signals-in-glib.html
72 #74 #
73 # gtk.main_quit and the logger methods are safe to be called from any thread.75 # gtk.main_quit and the logger methods are safe to be called from any
74 # Just don't call other random stuff here.76 # thread. Just don't call other random stuff here.
75 logger.info("Stoping Ubuntu SSO login manager since SIGHUP was received.")77 logger.info("Stoping Ubuntu SSO login manager since SIGHUP was received.")
76 gtk.main_quit()78 gtk.main_quit()
7779
@@ -92,6 +94,8 @@
92 bus_name = dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus())94 bus_name = dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus())
93 SSOLogin(bus_name, object_path=DBUS_ACCOUNT_PATH)95 SSOLogin(bus_name, object_path=DBUS_ACCOUNT_PATH)
94 SSOCredentials(bus_name, object_path=DBUS_CRED_PATH)96 SSOCredentials(bus_name, object_path=DBUS_CRED_PATH)
95 CredentialsManagement(bus_name, object_path=DBUS_CREDENTIALS_PATH)97 CredentialsManagement(timeout_func=gtk.timeout_add,
98 shutdown_func=gtk.main_quit,
99 bus_name=bus_name, object_path=DBUS_CREDENTIALS_PATH)
96100
97 gtk.main()101 gtk.main()
98102
=== removed directory 'contrib'
=== removed file 'contrib/__init__.py'
--- contrib/__init__.py 2010-06-22 14:18:04 +0000
+++ contrib/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,18 +0,0 @@
1# contrib - Extra required code to build/install the client
2#
3# Author: Rodney Dawes <rodney.dawes@canonical.com>
4#
5# Copyright 2009-2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Extra things we need to build, test, or install the client."""
190
=== removed file 'contrib/dbus_util.py'
--- contrib/dbus_util.py 2010-09-08 19:25:02 +0000
+++ contrib/dbus_util.py 1970-01-01 00:00:00 +0000
@@ -1,77 +0,0 @@
1#
2# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
3#
4#
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.
8#
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.
13#
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/>.
16
17"""Utilies to run a separated DBus session."""
18
19import os
20import signal
21import subprocess
22
23from distutils.spawn import find_executable
24
25SRCDIR = os.environ.get('SRCDIR', os.getcwd())
26
27
28class DBusLaunchError(Exception):
29 """Error while launching dbus-daemon."""
30
31
32class NotFoundError(Exception):
33 """Not found error."""
34
35
36class DBusRunner(object):
37 """A DBus runner."""
38
39 # pylint: disable=C0103
40
41 def __init__(self):
42 self.dbus_address = None
43 self.dbus_pid = None
44 self.running = False
45
46 def startDBus(self):
47 """Start our own session bus daemon for testing."""
48 dbus = find_executable("dbus-daemon")
49 if not dbus:
50 raise NotFoundError("dbus-daemon was not found.")
51
52 config_file = os.path.join(os.path.abspath(SRCDIR),
53 "contrib", "testing",
54 "dbus-session.conf")
55 dbus_args = ["--fork",
56 "--config-file=" + config_file,
57 "--print-address=1",
58 "--print-pid=2"]
59 p = subprocess.Popen([dbus] + dbus_args,
60 bufsize=4096, stdout=subprocess.PIPE,
61 stderr=subprocess.PIPE)
62
63 self.dbus_address = "".join(p.stdout.readlines()).strip()
64 self.dbus_pid = int("".join(p.stderr.readlines()).strip())
65
66 if self.dbus_address != "":
67 os.environ["DBUS_SESSION_BUS_ADDRESS"] = self.dbus_address
68 else:
69 os.kill(self.dbus_pid, signal.SIGKILL)
70 raise DBusLaunchError("There was a problem launching dbus-daemon.")
71 self.running = True
72
73 def stopDBus(self):
74 """Stop our DBus session bus daemon."""
75 del os.environ["DBUS_SESSION_BUS_ADDRESS"]
76 os.kill(self.dbus_pid, signal.SIGKILL)
77 self.running = False
780
=== removed file 'contrib/test'
--- contrib/test 2010-09-08 19:25:02 +0000
+++ contrib/test 1970-01-01 00:00:00 +0000
@@ -1,146 +0,0 @@
1#!/usr/bin/env python
2#
3# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
4#
5# Copyright 2009-2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19import os
20import re
21import signal
22import sys
23import string
24import subprocess
25import unittest
26
27sys.path.insert(0, os.path.abspath("."))
28
29from distutils.spawn import find_executable
30
31
32class TestRunner(object):
33
34 def _load_unittest(self, relpath):
35 """Load unittests from a Python module with the given relative path."""
36 assert relpath.endswith(".py"), (
37 "%s does not appear to be a Python module" % relpath)
38 modpath = relpath.replace(os.path.sep, ".")[:-3]
39 module = __import__(modpath, None, None, [""])
40
41 # If the module has a 'suite' or 'test_suite' function, use that
42 # to load the tests.
43 if hasattr(module, "suite"):
44 return module.suite()
45 elif hasattr(module, "test_suite"):
46 return module.test_suite()
47 else:
48 return unittest.defaultTestLoader.loadTestsFromModule(module)
49
50 def _collect_tests(self, testpath, test_pattern):
51 """Return the set of unittests."""
52 suite = unittest.TestSuite()
53 if test_pattern:
54 pattern = re.compile('.*%s.*' % test_pattern)
55 else:
56 pattern = None
57
58 if testpath:
59 module_suite = self._load_unittest(testpath)
60 if pattern:
61 for inner_suite in module_suite._tests:
62 for test in inner_suite._tests:
63 if pattern.match(test.id()):
64 suite.addTest(test)
65 else:
66 suite.addTests(module_suite)
67 return suite
68
69 # We don't use the dirs variable, so ignore the warning
70 # pylint: disable=W0612
71 for root, dirs, files in os.walk("ubuntu_sso"):
72 for file in files:
73 path = os.path.join(root, file)
74 if file.endswith(".py") and file.startswith("test_"):
75 module_suite = self._load_unittest(path)
76 if pattern:
77 for inner_suite in module_suite._tests:
78 for test in inner_suite._tests:
79 if pattern.match(test.id()):
80 suite.addTest(test)
81 else:
82 suite.addTests(module_suite)
83 return suite
84
85 def run(self, testpath, test_pattern=None, loops=None):
86 """run the tests. """
87 # install the glib2reactor before any import of the reactor to avoid
88 # using the default SelectReactor and be able to run the dbus tests
89 from twisted.internet import glib2reactor
90 glib2reactor.install()
91 from twisted.internet import reactor
92 from twisted.trial.reporter import TreeReporter
93 from twisted.trial.runner import TrialRunner
94
95 from contrib.dbus_util import DBusRunner
96 dbus_runner = DBusRunner()
97 dbus_runner.startDBus()
98
99 workingDirectory = os.path.join(os.getcwd(), "_trial_temp", "tmp")
100 runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True,
101 workingDirectory=workingDirectory)
102
103 # setup a custom XDG_CACHE_HOME and create the logs directory
104 xdg_cache = os.path.join(os.getcwd(), "_trial_temp", "xdg_cache")
105 os.environ["XDG_CACHE_HOME"] = xdg_cache
106 # setup the ROOTDIR env var
107 os.environ['ROOTDIR'] = os.getcwd()
108 if not os.path.exists(xdg_cache):
109 os.makedirs(xdg_cache)
110 success = 0
111 try:
112 suite = self._collect_tests(testpath, test_pattern)
113 if loops:
114 old_suite = suite
115 suite = unittest.TestSuite()
116 for x in xrange(loops):
117 suite.addTest(old_suite)
118 result = runner.run(suite)
119 success = result.wasSuccessful()
120 finally:
121 dbus_runner.stopDBus()
122 if not success:
123 sys.exit(1)
124 else:
125 sys.exit(0)
126
127
128if __name__ == '__main__':
129 from optparse import OptionParser
130 usage = '%prog [options] path'
131 parser = OptionParser(usage=usage)
132 parser.add_option("-t", "--test", dest="test",
133 help="run specific tests, e.g: className.methodName")
134 parser.add_option("-l", "--loop", dest="loops", type="int", default=1,
135 help="loop selected tests LOOPS number of times",
136 metavar="LOOPS")
137
138 (options, args) = parser.parse_args()
139 if args:
140 testpath = args[0]
141 if not os.path.exists(testpath):
142 print "the path to test does not exists!"
143 sys.exit()
144 else:
145 testpath = None
146 TestRunner().run(testpath, options.test, options.loops)
1470
=== removed directory 'contrib/testing'
=== removed file 'contrib/testing/__init__.py'
--- contrib/testing/__init__.py 2010-09-08 19:25:02 +0000
+++ contrib/testing/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1"""Testing utilities for Ubuntu SSO code."""
20
=== removed file 'contrib/testing/dbus-session.conf'
--- contrib/testing/dbus-session.conf 2010-06-18 20:51:58 +0000
+++ contrib/testing/dbus-session.conf 1970-01-01 00:00:00 +0000
@@ -1,63 +0,0 @@
1<!-- This configuration file controls our test-only session bus -->
2
3<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
4 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
5<busconfig>
6 <!-- We only use a session bus -->
7 <type>session</type>
8
9 <listen>unix:tmpdir=/tmp</listen>
10
11 <!-- Load our own services.
12 To make other dbus service in this session bus, just add another servicedir entry. -->
13 <servicedir>dbus-session</servicedir>
14 <!-- Load the standard session services -->
15 <!--standard_session_servicedirs /-->
16
17 <policy context="default">
18 <!-- Allow everything to be sent -->
19 <allow send_destination="*" eavesdrop="true"/>
20 <!-- Allow everything to be received -->
21 <allow eavesdrop="true"/>
22 <!-- Allow anyone to own anything -->
23 <allow own="*"/>
24 </policy>
25
26 <!-- Config files are placed here that among other things,
27 further restrict the above policy for specific services. -->
28 <includedir>/etc/dbus-1/session.d</includedir>
29
30 <!-- raise the service start timeout to 40 seconds as it can timeout
31 on the live cd on slow machines -->
32 <limit name="service_start_timeout">60000</limit>
33
34 <!-- This is included last so local configuration can override what's
35 in this standard file -->
36 <include ignore_missing="yes">session-local.conf</include>
37
38 <include ignore_missing="yes" if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
39
40 <!-- For the session bus, override the default relatively-low limits
41 with essentially infinite limits, since the bus is just running
42 as the user anyway, using up bus resources is not something we need
43 to worry about. In some cases, we do set the limits lower than
44 "all available memory" if exceeding the limit is almost certainly a bug,
45 having the bus enforce a limit is nicer than a huge memory leak. But the
46 intent is that these limits should never be hit. -->
47
48 <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max -->
49 <limit name="max_incoming_bytes">1000000000</limit>
50 <limit name="max_outgoing_bytes">1000000000</limit>
51 <limit name="max_message_size">1000000000</limit>
52 <limit name="service_start_timeout">120000</limit>
53 <limit name="auth_timeout">240000</limit>
54 <limit name="max_completed_connections">100000</limit>
55 <limit name="max_incomplete_connections">10000</limit>
56 <limit name="max_connections_per_user">100000</limit>
57 <limit name="max_pending_service_starts">10000</limit>
58 <limit name="max_names_per_connection">50000</limit>
59 <limit name="max_match_rules_per_connection">50000</limit>
60 <limit name="max_replies_per_connection">50000</limit>
61 <limit name="reply_timeout">300000</limit>
62
63</busconfig>
640
=== removed file 'contrib/testing/testcase.py'
--- contrib/testing/testcase.py 2010-10-11 13:22:16 +0000
+++ contrib/testing/testcase.py 1970-01-01 00:00:00 +0000
@@ -1,123 +0,0 @@
1#
2# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
3# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2009-2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Base tests cases and test utilities."""
19
20import logging
21
22import dbus
23
24from dbus.mainloop.glib import DBusGMainLoop
25from twisted.internet import defer
26from twisted.python import failure
27from twisted.trial.unittest import TestCase
28
29
30class DBusTestCase(TestCase):
31 """Test the DBus event handling."""
32
33 def setUp(self):
34 """Setup the infrastructure fo the test (dbus service)."""
35 self.loop = DBusGMainLoop(set_as_default=True)
36 self.bus = dbus.bus.BusConnection(mainloop=self.loop)
37 # monkeypatch busName.__del__ to avoid errors on gc
38 # we take care of releasing the name in shutdown
39 dbus.service.BusName.__del__ = lambda _: None
40 self.bus.set_exit_on_disconnect(False)
41 self.signal_receivers = set()
42
43 def tearDown(self):
44 """Cleanup the test."""
45 d = self.cleanup_signal_receivers(self.signal_receivers)
46 d.addBoth(self._tear_down)
47 return d
48
49 def _tear_down(self, _):
50 """Shutdown."""
51 self.bus.flush()
52 self.bus.close()
53
54 def error_handler(self, error):
55 """Default error handler for DBus calls."""
56 if isinstance(error, failure.Failure):
57 self.fail(error.getErrorMessage())
58
59 def cleanup_signal_receivers(self, signal_receivers):
60 """Cleanup self.signal_receivers and returns a deferred."""
61 deferreds = []
62 for match in signal_receivers:
63 d = defer.Deferred()
64
65 def callback(*args):
66 """Callback that accepts *args."""
67 if not d.called:
68 d.callback(args)
69
70 self.bus.call_async(dbus.bus.BUS_DAEMON_NAME,
71 dbus.bus.BUS_DAEMON_PATH,
72 dbus.bus.BUS_DAEMON_IFACE, 'RemoveMatch', 's',
73 (str(match),), callback, self.error_handler)
74 deferreds.append(d)
75 if deferreds:
76 return defer.DeferredList(deferreds)
77 else:
78 return defer.succeed(True)
79
80
81class MementoHandler(logging.Handler):
82 """A handler class which store logging records in a list."""
83
84 def __init__(self, *args, **kwargs):
85 """Create the instance, and add a records attribute."""
86 logging.Handler.__init__(self, *args, **kwargs)
87 self.records = []
88
89 def emit(self, record):
90 """Just add the record to self.records."""
91 self.records.append(record)
92
93 def check(self, level, *msgs):
94 """Verifies that the msgs are logged in the specified level."""
95 for rec in self.records:
96 if rec.levelno == level and all(m in rec.message for m in msgs):
97 return True
98 return False
99
100 def check_debug(self, *msgs):
101 """Shortcut for checking in DEBUG."""
102 return self.check(logging.DEBUG, *msgs)
103
104 def check_info(self, *msgs):
105 """Shortcut for checking in INFO."""
106 return self.check(logging.INFO, *msgs)
107
108 def check_warning(self, *msgs):
109 """Shortcut for checking in WARNING."""
110 return self.check(logging.WARNING, *msgs)
111
112 def check_error(self, *msgs):
113 """Shortcut for checking in ERROR."""
114 return self.check(logging.ERROR, *msgs)
115
116 def check_exception(self, exception_class, *msgs):
117 """Shortcut for checking exceptions."""
118 for rec in self.records:
119 if rec.levelno == logging.ERROR and \
120 all(m in rec.exc_text for m in msgs) and \
121 exception_class == rec.exc_info[0]:
122 return True
123 return False
1240
=== modified file 'debian/changelog'
--- debian/changelog 2010-12-17 20:25:51 +0000
+++ debian/changelog 2011-01-13 00:33:10 +0000
@@ -1,3 +1,21 @@
1ubuntu-sso-client (1.1.8-0ubuntu1) UNRELEASED; urgency=low
2
3 * New upstream release:
4
5 [ Natalia B. Bidart <natalia.bidart@canonical.com>]
6 - The service should shutdown when unused
7 (LP: #701606),
8 - On error, {find,clear,store}_credentials send proper CredentialsError
9 signal (LP: #696676).
10 - No more gobject dependency on non-GUI classes!
11 (LP: #695798).
12 - After login, if the storing of credentials fails, send LoginError
13 (LP: #693531).
14 - Use ubuntuone-dev-tools to run the tests and the lint checker
15 (LP: #686606).
16
17 -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Wed, 12 Jan 2011 15:59:23 -0300
18
1ubuntu-sso-client (1.1.7-0ubuntu1) natty; urgency=low19ubuntu-sso-client (1.1.7-0ubuntu1) natty; urgency=low
220
3 * New upstream release.21 * New upstream release.
422
=== modified file 'run-tests'
--- run-tests 2010-10-11 13:22:16 +0000
+++ run-tests 2011-01-13 00:33:10 +0000
@@ -15,14 +15,23 @@
15# You should have received a copy of the GNU General Public License along15# You should have received a copy of the GNU General Public License along
16# with this program. If not, see <http://www.gnu.org/licenses/>.16# with this program. If not, see <http://www.gnu.org/licenses/>.
1717
18if [ $# -ne 0 ]; then
19 # an extra argument was given
20 MODULE="$@"
21else
22 # run all tests, useful for tarmac and reviews
23 MODULE="ubuntu_sso"
24fi
25
18style_check() {26style_check() {
19 pylint contrib ubuntu_sso27 u1lint
20 if [ -x `which pep8` ]; then28 if [ -x `which pep8` ]; then
21 pep8 --repeat bin/ contrib/ ubuntu_sso/29 pep8 --repeat bin/ $MODULE
22 else30 else
23 echo "Please install the 'pep8' package."31 echo "Please install the 'pep8' package."
24 fi32 fi
25}33}
2634
27`which xvfb-run` ./contrib/test "$@" && style_check35echo "Running test suite for ""$MODULE"
28rm -rf _trial_temp/36`which xvfb-run` u1trial "$MODULE" && style_check
37rm -rf _trial_temp
2938
=== modified file 'setup.py'
--- setup.py 2010-12-16 16:31:34 +0000
+++ setup.py 2011-01-13 00:33:10 +0000
@@ -31,16 +31,15 @@
31assert DistUtilsExtra.auto.__version__ >= '2.18', \31assert DistUtilsExtra.auto.__version__ >= '2.18', \
32 'needs DistUtilsExtra.auto >= 2.18'32 'needs DistUtilsExtra.auto >= 2.18'
3333
34from distutils.core import setup
35from distutils.spawn import find_executable34from distutils.spawn import find_executable
36from distutils.command import clean, build35from distutils.command import clean
3736
38# Defining variables for various rules here, similar to a Makefile.am37# Defining variables for various rules here, similar to a Makefile.am
39CLEANFILES = ['data/com.ubuntu.sso.service', 'po/ubuntu-sso-client.pot',38CLEANFILES = ['data/com.ubuntu.sso.service', 'po/ubuntu-sso-client.pot',
40 'MANIFEST']39 'MANIFEST']
4140
4241
43# XXX: This needs some serious cleanup42# This needs some serious cleanup
44class SSOBuild(build_extra.build_extra):43class SSOBuild(build_extra.build_extra):
45 """Class to build the extra files."""44 """Class to build the extra files."""
4645
@@ -87,7 +86,7 @@
8786
88DistUtilsExtra.auto.setup(87DistUtilsExtra.auto.setup(
89 name='ubuntu-sso-client',88 name='ubuntu-sso-client',
90 version='1.1.7',89 version='1.1.8',
91 license='GPL v3',90 license='GPL v3',
92 author='Natalia Bidart',91 author='Natalia Bidart',
93 author_email='natalia.bidart@canonical.com',92 author_email='natalia.bidart@canonical.com',
9493
=== modified file 'ubuntu_sso/credentials.py'
--- ubuntu_sso/credentials.py 2010-11-30 13:21:17 +0000
+++ ubuntu_sso/credentials.py 2011-01-13 00:33:10 +0000
@@ -22,10 +22,12 @@
2222
23 * find_credentials23 * find_credentials
24 * clear_credentials24 * clear_credentials
25 * store_credentials
25 * register26 * register
26 * login27 * login
2728
28The first two returns immediately (for now).29The first three return a Deferred that will be fired when the operation was
30completed.
2931
30The second two use the 'success_cb', 'error_cb' and 'denial_cb' to signal the32The second two use the 'success_cb', 'error_cb' and 'denial_cb' to signal the
31caller when the credentials were retrieved successfully, when there was an33caller when the credentials were retrieved successfully, when there was an
@@ -41,8 +43,6 @@
4143
42from functools import wraps44from functools import wraps
4345
44import gobject
45
46from oauth import oauth46from oauth import oauth
47from twisted.internet.defer import inlineCallbacks, returnValue47from twisted.internet.defer import inlineCallbacks, returnValue
4848
@@ -187,13 +187,15 @@
187187
188 @handle_failures(msg='Problem while retrieving credentials')188 @handle_failures(msg='Problem while retrieving credentials')
189 @inlineCallbacks189 @inlineCallbacks
190 def _login_success_cb(self, dialog, app_name, email):190 def _login_success_cb(self, app_name, email):
191 """Store credentials when the login/registration succeeded.191 """Store credentials when the login/registration succeeded.
192192
193 Also, open self.ping_url/email to notify about this new token. If any193 Also, open self.ping_url/email to notify about this new token. If any
194 error occur, self.error_cb is called. Otherwise, self.success_cb is194 error occur, self.error_cb is called. Otherwise, self.success_cb is
195 called.195 called.
196196
197 Return 0 on success, and a non-zero value (or None) on error.
198
197 """199 """
198 logger.info('Login/registration was successful for app %r, email %r',200 logger.info('Login/registration was successful for app %r, email %r',
199 app_name, email)201 app_name, email)
@@ -202,19 +204,21 @@
202 assert len(creds) > 0, 'Creds are empty! This should not happen'204 assert len(creds) > 0, 'Creds are empty! This should not happen'
203 # ping a server with the credentials if we were requested to205 # ping a server with the credentials if we were requested to
204 if self.ping_url is not None:206 if self.ping_url is not None:
205 status = self._ping_url(app_name, email, creds)207 status = yield self._ping_url(app_name, email, creds)
206 if status is None:208 if status is None:
207 yield self.clear_credentials()209 yield self.clear_credentials()
208 return210 return
209211
210 self.success_cb(creds)212 self.success_cb(creds)
213 returnValue(0)
211214
212 def _auth_denial_cb(self, dialog, app_name):215 def _auth_denial_cb(self, app_name):
213 """The user decided not to allow the registration or login."""216 """The user decided not to allow the registration or login."""
214 logger.warning('Login/registration was denied to app %r', app_name)217 logger.warning('Login/registration was denied to app %r', app_name)
215 self.denial_cb(app_name)218 self.denial_cb(app_name)
216219
217 @handle_exceptions(msg='Problem opening the ping_url')220 @handle_failures(msg='Problem opening the ping_url')
221 @inlineCallbacks
218 def _ping_url(self, app_name, email, credentials):222 def _ping_url(self, app_name, email, credentials):
219 """Ping the self.ping_url with the email attached.223 """Ping the self.ping_url with the email attached.
220224
@@ -237,9 +241,12 @@
237 request = urllib2.Request(url, headers=oauth_req.to_header())241 request = urllib2.Request(url, headers=oauth_req.to_header())
238 logger.debug('Opening the url "%s" with urllib2.urlopen.',242 logger.debug('Opening the url "%s" with urllib2.urlopen.',
239 request.get_full_url())243 request.get_full_url())
244 # This code is blocking, we should change this.
245 # I've tried with deferToThread an twisted.web.client.getPage
246 # but the returned deferred will never be fired (nataliabidart).
240 response = urllib2.urlopen(request)247 response = urllib2.urlopen(request)
241 logger.debug('Url opened. Response: %s.', response.code)248 logger.debug('Url opened. Response: %s.', response.code)
242 return response.code249 returnValue(response.code)
243250
244 @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface')251 @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface')
245 def _show_ui(self, login_only):252 def _show_ui(self, login_only):
@@ -252,10 +259,9 @@
252 tc_url=self.tc_url, help_text=self.help_text,259 tc_url=self.tc_url, help_text=self.help_text,
253 window_id=self.window_id, login_only=login_only)260 window_id=self.window_id, login_only=login_only)
254261
255 self.gui.connect(gui.SIG_LOGIN_SUCCEEDED, self._login_success_cb)262 self.gui.login_success_callback = self._login_success_cb
256 self.gui.connect(gui.SIG_REGISTRATION_SUCCEEDED,263 self.gui.registration_success_callback = self._login_success_cb
257 self._login_success_cb)264 self.gui.user_cancellation_callback = self._auth_denial_cb
258 self.gui.connect(gui.SIG_USER_CANCELATION, self._auth_denial_cb)
259265
260 @handle_failures(msg='Problem while retrieving credentials')266 @handle_failures(msg='Problem while retrieving credentials')
261 @inlineCallbacks267 @inlineCallbacks
@@ -265,35 +271,23 @@
265 if token is not None and len(token) > 0:271 if token is not None and len(token) > 0:
266 self.success_cb(token)272 self.success_cb(token)
267 elif token == {}:273 elif token == {}:
268 gobject.idle_add(self._show_ui, login_only)274 self._show_ui(login_only)
269 else:275 else:
270 # something went wrong with find_credentials, already handled.276 # something went wrong with find_credentials, already handled.
271 logger.info('_login_or_register: call to "find_credentials" went '277 logger.info('_login_or_register: call to "find_credentials" went '
272 'wrong, and was already handled.')278 'wrong, and was already handled.')
273279
274 def error_cb(self, error_dict):280 def error_cb(self, error_dict):
275 """Handle error.281 """Handle error and notify the caller."""
276282 logger.error('Calling error callback at %r (error is %r).',
277 Notify the caller and finish the GUI with error.283 self._error_cb, error_dict)
278
279 """
280 self._error_cb(self.app_name, error_dict)284 self._error_cb(self.app_name, error_dict)
281 if self.gui is not None:
282 self.gui.finish_error(error=error_dict)
283 self.gui = None
284285
285 def success_cb(self, creds):286 def success_cb(self, creds):
286 """Handle success.287 """Handle success and notify the caller."""
287288 logger.debug('Calling success callback at %r.', self._success_cb)
288 Notify the caller and finish the GUI with success.
289
290 """
291 self._success_cb(self.app_name, creds)289 self._success_cb(self.app_name, creds)
292 if self.gui is not None:
293 self.gui.finish_success()
294 self.gui = None
295290
296 @handle_failures(msg='Problem while retrieving credentials')
297 @inlineCallbacks291 @inlineCallbacks
298 def find_credentials(self):292 def find_credentials(self):
299 """Get the credentials for 'self.app_name'. Return {} if not there."""293 """Get the credentials for 'self.app_name'. Return {} if not there."""
@@ -302,13 +296,11 @@
302 'result is {}? %s', self.app_name, creds is None)296 'result is {}? %s', self.app_name, creds is None)
303 returnValue(creds if creds is not None else {})297 returnValue(creds if creds is not None else {})
304298
305 @handle_failures(msg='Problem while deleting credentials')
306 @inlineCallbacks299 @inlineCallbacks
307 def clear_credentials(self):300 def clear_credentials(self):
308 """Clear the credentials for 'self.app_name'."""301 """Clear the credentials for 'self.app_name'."""
309 yield Keyring().delete_credentials(self.app_name)302 yield Keyring().delete_credentials(self.app_name)
310303
311 @handle_failures(msg='Problem while storing credentials')
312 @inlineCallbacks304 @inlineCallbacks
313 def store_credentials(self, token):305 def store_credentials(self, token):
314 """Store the credentials for 'self.app_name'."""306 """Store the credentials for 'self.app_name'."""
315307
=== modified file 'ubuntu_sso/gtk/gui.py'
--- ubuntu_sso/gtk/gui.py 2010-11-30 13:21:17 +0000
+++ ubuntu_sso/gtk/gui.py 2011-01-13 00:33:10 +0000
@@ -30,11 +30,11 @@
3030
31import dbus31import dbus
32import gettext32import gettext
33import gobject
34import gtk33import gtk
35import xdg34import xdg
3635
37from dbus.mainloop.glib import DBusGMainLoop36from dbus.mainloop.glib import DBusGMainLoop
37from twisted.internet.defer import inlineCallbacks
3838
39from ubuntu_sso import (DBUS_ACCOUNT_PATH, DBUS_BUS_NAME, DBUS_IFACE_USER_NAME,39from ubuntu_sso import (DBUS_ACCOUNT_PATH, DBUS_BUS_NAME, DBUS_IFACE_USER_NAME,
40 NO_OP)40 NO_OP)
@@ -72,20 +72,6 @@
72HELP_TEXT_COLOR = gtk.gdk.Color("#bfbfbf")72HELP_TEXT_COLOR = gtk.gdk.Color("#bfbfbf")
73WARNING_TEXT_COLOR = gtk.gdk.Color("red")73WARNING_TEXT_COLOR = gtk.gdk.Color("red")
7474
75SIG_LOGIN_SUCCEEDED = 'login-succeeded'
76SIG_REGISTRATION_SUCCEEDED = 'registration-succeeded'
77SIG_USER_CANCELATION = 'user-cancelation'
78
79SIGNAL_ARGUMENTS = [
80 (SIG_LOGIN_SUCCEEDED, (gobject.TYPE_STRING, gobject.TYPE_STRING,)),
81 (SIG_REGISTRATION_SUCCEEDED, (gobject.TYPE_STRING, gobject.TYPE_STRING,)),
82 (SIG_USER_CANCELATION, (gobject.TYPE_STRING,)),
83]
84
85for sig, sig_args in SIGNAL_ARGUMENTS:
86 gobject.signal_new(sig, gtk.Window, gobject.SIGNAL_RUN_FIRST,
87 gobject.TYPE_NONE, sig_args)
88
8975
90def get_data_dir():76def get_data_dir():
91 """Build absolute path to the 'data' directory."""77 """Build absolute path to the 'data' directory."""
@@ -198,7 +184,7 @@
198184
199185
200class UbuntuSSOClientGUI(object):186class UbuntuSSOClientGUI(object):
201 """Ubuntu single sign on GUI."""187 """Ubuntu single sign-on GUI."""
202188
203 CAPTCHA_SOLUTION_ENTRY = _('Type the characters above')189 CAPTCHA_SOLUTION_ENTRY = _('Type the characters above')
204 CAPTCHA_LOAD_ERROR = _('There was a problem getting the captcha, '190 CAPTCHA_LOAD_ERROR = _('There was a problem getting the captcha, '
@@ -256,7 +242,7 @@
256 CAPTCHA_RELOAD_TOOLTIP = _('Reload')242 CAPTCHA_RELOAD_TOOLTIP = _('Reload')
257243
258 def __init__(self, app_name, tc_url='', help_text='',244 def __init__(self, app_name, tc_url='', help_text='',
259 window_id=0, login_only=False, close_callback=None):245 window_id=0, login_only=False):
260 """Create the GUI and initialize widgets."""246 """Create the GUI and initialize widgets."""
261 gtk.link_button_set_uri_hook(NO_OP)247 gtk.link_button_set_uri_hook(NO_OP)
262248
@@ -270,7 +256,12 @@
270 self.tc_url = tc_url256 self.tc_url = tc_url
271 self.help_text = help_text257 self.help_text = help_text
272 self.login_only = login_only258 self.login_only = login_only
273 self.close_callback = close_callback259
260 self.close_callback = NO_OP
261 self.login_success_callback = NO_OP
262 self.registration_success_callback = NO_OP
263 self.user_cancellation_callback = NO_OP
264
274 self.user_email = None265 self.user_email = None
275 self.user_password = None266 self.user_password = None
276267
@@ -730,17 +721,12 @@
730 signal_name, handler, args, kwargs)721 signal_name, handler, args, kwargs)
731 self.window.connect(signal_name, handler, *args, **kwargs)722 self.window.connect(signal_name, handler, *args, **kwargs)
732723
733 def emit(self, *args, **kwargs):
734 """Emit a signal proxing the main window."""
735 logger.debug('emit: args %r, kwargs, %r', args, kwargs)
736 self.window.emit(*args, **kwargs)
737
738 def finish_success(self):724 def finish_success(self):
739 """The whole process was completed succesfully. Show success page."""725 """The whole process was completed succesfully. Show success page."""
740 self._done = True726 self._done = True
741 self._set_current_page(self.success_vbox)727 self._set_current_page(self.success_vbox)
742728
743 def finish_error(self, error):729 def finish_error(self):
744 """The whole process was not completed succesfully. Show error page."""730 """The whole process was not completed succesfully. Show error page."""
745 self._done = True731 self._done = True
746 self._set_current_page(self.error_vbox)732 self._set_current_page(self.error_vbox)
@@ -766,18 +752,17 @@
766 if self.window is not None:752 if self.window is not None:
767 self.window.hide()753 self.window.hide()
768754
769 # process any pending events before emitting signals755 # process any pending events before callbacking with result
770 while gtk.events_pending():756 while gtk.events_pending():
771 gtk.main_iteration()757 gtk.main_iteration()
772758
773 if not self._done:759 if not self._done:
774 self.emit(SIG_USER_CANCELATION, self.app_name)760 self.user_cancellation_callback(self.app_name)
775761
776 # call user defined callback762 # call user defined callback
777 if self.close_callback is not None:763 logger.info('Calling custom close_callback %r with params %r, %r',
778 logger.info('Calling custom close_callback %r with params %r, %r',764 self.close_callback, args, kwargs)
779 self.close_callback, args, kwargs)765 self.close_callback(*args, **kwargs)
780 self.close_callback(*args, **kwargs)
781766
782 def on_sign_in_button_clicked(self, *args, **kwargs):767 def on_sign_in_button_clicked(self, *args, **kwargs):
783 """User wants to sign in, present the Login page."""768 """User wants to sign in, present the Login page."""
@@ -1096,10 +1081,15 @@
1096 self._set_current_page(self.enter_details_vbox, warning_text=msg)1081 self._set_current_page(self.enter_details_vbox, warning_text=msg)
10971082
1098 @log_call1083 @log_call
1084 @inlineCallbacks
1099 def on_email_validated(self, app_name, email, *args, **kwargs):1085 def on_email_validated(self, app_name, email, *args, **kwargs):
1100 """User email was successfully verified."""1086 """User email was successfully verified."""
1101 self._done = True1087 self._done = True
1102 self.emit(SIG_REGISTRATION_SUCCEEDED, self.app_name, email)1088 result = yield self.registration_success_callback(self.app_name, email)
1089 if result == 0:
1090 self.finish_success()
1091 else:
1092 self.finish_error()
11031093
1104 @log_call1094 @log_call
1105 def on_email_validation_error(self, app_name, error, *args, **kwargs):1095 def on_email_validation_error(self, app_name, error, *args, **kwargs):
@@ -1112,10 +1102,15 @@
1112 self._set_current_page(self.verify_email_vbox, warning_text=msg)1102 self._set_current_page(self.verify_email_vbox, warning_text=msg)
11131103
1114 @log_call1104 @log_call
1105 @inlineCallbacks
1115 def on_logged_in(self, app_name, email, *args, **kwargs):1106 def on_logged_in(self, app_name, email, *args, **kwargs):
1116 """User was successfully logged in."""1107 """User was successfully logged in."""
1117 self._done = True1108 self._done = True
1118 self.emit(SIG_LOGIN_SUCCEEDED, self.app_name, email)1109 result = yield self.login_success_callback(self.app_name, email)
1110 if result == 0:
1111 self.finish_success()
1112 else:
1113 self.finish_error()
11191114
1120 @log_call1115 @log_call
1121 def on_login_error(self, app_name, error, *args, **kwargs):1116 def on_login_error(self, app_name, error, *args, **kwargs):
11221117
=== modified file 'ubuntu_sso/gtk/tests/test_gui.py'
--- ubuntu_sso/gtk/tests/test_gui.py 2010-11-30 13:21:17 +0000
+++ ubuntu_sso/gtk/tests/test_gui.py 2011-01-13 00:33:10 +0000
@@ -30,8 +30,8 @@
30import webkit30import webkit
3131
32from twisted.trial.unittest import TestCase32from twisted.trial.unittest import TestCase
33from ubuntuone.devtools.handlers import MementoHandler
3334
34from contrib.testing.testcase import MementoHandler
35from ubuntu_sso.gtk import gui35from ubuntu_sso.gtk import gui
36from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID,36from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID,
37 CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, NAME, PASSWORD, RESET_PASSWORD_TOKEN)37 CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, NAME, PASSWORD, RESET_PASSWORD_TOKEN)
@@ -517,6 +517,11 @@
517 self.assertTrue(self._called,517 self.assertTrue(self._called,
518 'close_callback was called when window was closed.')518 'close_callback was called when window was closed.')
519519
520 def test_close_callback_if_not_set(self):
521 """The close_callback is a no op if not set."""
522 self.ui.on_close_clicked()
523 # no crash when close_callback is not set
524
520 def test_app_name_is_stored(self):525 def test_app_name_is_stored(self):
521 """The app_name is stored for further use."""526 """The app_name is stored for further use."""
522 self.assertIn(APP_NAME, self.ui.app_name)527 self.assertIn(APP_NAME, self.ui.app_name)
@@ -555,18 +560,6 @@
555560
556 self.assertEqual(self.ui.bus.callbacks, {})561 self.assertEqual(self.ui.bus.callbacks, {})
557562
558 def test_close_callback(self):
559 """A close_callback parameter is called when closing the window."""
560 ui = self.gui_class(close_callback=self._set_called, **self.kwargs)
561 ui.on_close_clicked()
562 self.assertTrue(self._called, 'close_callback was called on close.')
563
564 def test_close_callback_if_none(self):
565 """A close_callback parameter is not called if is None."""
566 ui = self.gui_class(close_callback=None, **self.kwargs)
567 ui.on_close_clicked()
568 # no crash when close_callback is None
569
570 def test_pages_are_packed_into_container(self):563 def test_pages_are_packed_into_container(self):
571 """All the pages are packed in the main container."""564 """All the pages are packed in the main container."""
572 children = self.ui.content.get_children()565 children = self.ui.content.get_children()
@@ -620,8 +613,7 @@
620 buttons = filter(lambda name: 'cancel_button' in name or613 buttons = filter(lambda name: 'cancel_button' in name or
621 'close_button' in name, self.ui.widgets)614 'close_button' in name, self.ui.widgets)
622 for name in buttons:615 for name in buttons:
623 self.ui = self.gui_class(close_callback=self._set_called,616 self.ui.close_callback = self._set_called
624 **self.kwargs)
625 widget = getattr(self.ui, name)617 widget = getattr(self.ui, name)
626 widget.clicked()618 widget.clicked()
627 self.assertEqual(self._called, ((widget,), {}), msg % name)619 self.assertEqual(self._called, ((widget,), {}), msg % name)
@@ -661,7 +653,7 @@
661653
662 def test_finish_error_shows_error_page(self):654 def test_finish_error_shows_error_page(self):
663 """When calling 'finish_error' the error page is shown."""655 """When calling 'finish_error' the error page is shown."""
664 self.ui.finish_error(error=self.error)656 self.ui.finish_error()
665 self.assert_pages_visibility(finish=True)657 self.assert_pages_visibility(finish=True)
666 self.assertEqual(self.ui.ERROR, self.ui.finish_vbox.label.get_text())658 self.assertEqual(self.ui.ERROR, self.ui.finish_vbox.label.get_text())
667659
@@ -1164,10 +1156,10 @@
1164 self.click_verify_email_with_valid_data()1156 self.click_verify_email_with_valid_data()
1165 self.assert_warnings_visibility()1157 self.assert_warnings_visibility()
11661158
1167 def test_on_email_validated_shows_processing_page(self):1159 def test_on_email_validated_shows_finish_page(self):
1168 """On email validated the procesing page is still shown."""1160 """On email validated the finish page is shown."""
1169 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)1161 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1170 self.assert_pages_visibility(processing=True)1162 self.assert_pages_visibility(finish=True)
11711163
1172 def test_on_email_validated_does_not_clear_the_help_text(self):1164 def test_on_email_validated_does_not_clear_the_help_text(self):
1173 """On email validated the help text is not removed."""1165 """On email validated the help text is not removed."""
@@ -1227,6 +1219,32 @@
1227 self.ui.verify_token_button.clicked()1219 self.ui.verify_token_button.clicked()
1228 self.assertTrue(self._called)1220 self.assertTrue(self._called)
12291221
1222 def test_after_registration_success_finish_success(self):
1223 """After REGISTRATION_SUCCESS is called, finish_success is called.
1224
1225 Only when REGISTRATION_SUCCESS returns 0.
1226
1227 """
1228 self.patch(self.ui, 'finish_success', self._set_called)
1229 self.ui.registration_success_callback = lambda app, email: 0
1230
1231 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1232
1233 self.assertEqual(self._called, ((), {}))
1234
1235 def test_after_registration_error_finish_error(self):
1236 """After REGISTRATION_SUCCESS is called, finish_error is called.
1237
1238 Only when REGISTRATION_SUCCESS returns a non-zero value.
1239
1240 """
1241 self.patch(self.ui, 'finish_error', self._set_called)
1242 self.ui.registration_success_callback = lambda app, email: -1
1243
1244 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
1245
1246 self.assertEqual(self._called, ((), {}))
1247
12301248
1231class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase):1249class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase):
1232 """Test suite for the user registration validation (verify email page)."""1250 """Test suite for the user registration validation (verify email page)."""
@@ -1480,11 +1498,11 @@
1480 self.click_connect_with_valid_data()1498 self.click_connect_with_valid_data()
1481 self.assert_pages_visibility(processing=True)1499 self.assert_pages_visibility(processing=True)
14821500
1483 def test_on_logged_in_morphs_to_processing_page(self):1501 def test_on_logged_in_morphs_to_finish_page(self):
1484 """When user logged in the processing page is still shown."""1502 """When user logged in the finish page is shown."""
1485 self.click_connect_with_valid_data()1503 self.click_connect_with_valid_data()
1486 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)1504 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1487 self.assert_pages_visibility(processing=True)1505 self.assert_pages_visibility(finish=True)
14881506
1489 def test_on_login_error_morphs_to_login_page(self):1507 def test_on_login_error_morphs_to_login_page(self):
1490 """On user login error, the previous page is shown."""1508 """On user login error, the previous page is shown."""
@@ -1541,6 +1559,32 @@
1541 self.assertEqual(self.ui.user_email, EMAIL)1559 self.assertEqual(self.ui.user_email, EMAIL)
1542 self.assertEqual(self.ui.user_password, PASSWORD)1560 self.assertEqual(self.ui.user_password, PASSWORD)
15431561
1562 def test_after_login_success_finish_success(self):
1563 """After LOGIN_SUCCESSFULL is called, finish_success is called.
1564
1565 Only when LOGIN_SUCCESSFULL returns 0.
1566
1567 """
1568 self.patch(self.ui, 'finish_success', self._set_called)
1569 self.ui.login_success_callback = lambda app, email: 0
1570
1571 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1572
1573 self.assertEqual(self._called, ((), {}))
1574
1575 def test_after_login_error_finish_error(self):
1576 """After LOGIN_SUCCESSFULL is called, finish_error is called.
1577
1578 Only when LOGIN_SUCCESSFULL returns a non-zero value.
1579
1580 """
1581 self.patch(self.ui, 'finish_error', self._set_called)
1582 self.ui.login_success_callback = lambda app, email: -1
1583
1584 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
1585
1586 self.assertEqual(self._called, ((), {}))
1587
15441588
1545class LoginValidationTestCase(UbuntuSSOClientTestCase):1589class LoginValidationTestCase(UbuntuSSOClientTestCase):
1546 """Test suite for the user login validation."""1590 """Test suite for the user login validation."""
@@ -2060,119 +2104,144 @@
2060 self.assertEqual(self.ui.help_label.get_text(), HELP_TEXT)2104 self.assertEqual(self.ui.help_label.get_text(), HELP_TEXT)
20612105
20622106
2063class SignalsTestCase(UbuntuSSOClientTestCase):2107class CallbacksTestCase(UbuntuSSOClientTestCase):
2064 """Test the GTK signal emission."""2108 """Test the GTK callback calls."""
2109
2110 LOGIN_SUCCESSFULL = 'login_success_callback'
2111 REGISTRATION_SUCCESS = 'registration_success_callback'
2112 USER_CANCELLATION = 'user_cancellation_callback'
20652113
2066 def setUp(self):2114 def setUp(self):
2067 """Init."""2115 """Init."""
2068 super(SignalsTestCase, self).setUp()2116 super(CallbacksTestCase, self).setUp()
2069 self._called = {}2117 self._called = {}
2070 for sig_name, _ in gui.SIGNAL_ARGUMENTS:2118 for name in (self.LOGIN_SUCCESSFULL,
2071 self.ui.connect(sig_name, self._set_called, sig_name)2119 self.REGISTRATION_SUCCESS,
20722120 self.USER_CANCELLATION):
2073 def _set_called(self, widget, *args, **kwargs):2121 setattr(self.ui, name, self._set_called(name))
2074 """Keep trace of signals emition."""2122
2123 def _set_called(self, callback_name):
2124 """Keep trace of callbacks calls."""
2125
2075 # pylint: disable=W02212126 # pylint: disable=W0221
2076 self._called[args[-1]] = (widget, args[:-1], kwargs)2127
20772128 def inner(*args, **kwargs):
2078 def test_closing_main_window_sends_outcome_as_signal(self):2129 """Store arguments used in this call."""
2079 """A signal is sent when closing the main window."""2130 self._called[callback_name] = args
2131
2132 return inner
2133
2134 def test_closing_main_window(self):
2135 """When closing the main window, USER_CANCELLATION is called."""
2080 self.ui.window.emit('delete-event', gtk.gdk.Event(gtk.gdk.DELETE))2136 self.ui.window.emit('delete-event', gtk.gdk.Event(gtk.gdk.DELETE))
2081 expected = (self.ui.window, (APP_NAME,), {})2137 self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
2082 self.assertEqual(self._called[gui.SIG_USER_CANCELATION], expected)
20832138
2084 def test_every_cancel_emits_proper_signal(self):2139 def test_every_cancel_calls_proper_callback(self):
2085 """Clicking on any cancel button, 'user-cancelation' signal is sent."""2140 """When any cancel button is clicked, USER_CANCELLATION is called."""
2086 sig = gui.SIG_USER_CANCELATION2141 msg = 'user_cancellation_callback is called when "%s" is clicked.'
2087 msg = 'user-cancelation is emitted when "%s" is clicked.'
2088 buttons = filter(lambda name: 'cancel_button' in name, self.ui.widgets)2142 buttons = filter(lambda name: 'cancel_button' in name, self.ui.widgets)
2089 for name in buttons:2143 for name in buttons:
2090 self.ui = self.gui_class(**self.kwargs)
2091 self.ui.connect(sig, self._set_called, sig)
2092 widget = getattr(self.ui, name)2144 widget = getattr(self.ui, name)
2093 widget.clicked()2145 widget.clicked()
2094 expected_args = (self.ui.window, (APP_NAME,), {})2146 self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,),
2095 self.assertEqual(self._called[sig], expected_args, msg % name)2147 msg % name)
2096 self._called = {}2148 self._called = {}
20972149
2098 def test_on_user_registration_error_proper_signal_is_emitted(self):2150 def test_on_user_registration_error_proper_callback_is_called(self):
2099 """On UserRegistrationError, SIG_USER_CANCELATION signal is sent."""2151 """On UserRegistrationError, USER_CANCELLATION is called."""
2100 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)2152 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
2101 self.ui.on_close_clicked()2153 self.ui.on_close_clicked()
2102 expected = (self.ui.window, (APP_NAME,), {})2154
2103 self.assertEqual(expected,2155 self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
2104 self._called[gui.SIG_USER_CANCELATION])2156
21052157 def test_on_email_validated_proper_callback_is_called(self):
2106 def test_on_email_validated_proper_signals_is_emitted(self):2158 """On EmailValidated, REGISTRATION_SUCCESS is called."""
2107 """On EmailValidated, 'registration-succeeded' signal is sent."""
2108 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)2159 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
2109 self.ui.on_close_clicked()2160 self.ui.on_close_clicked()
2110 self.assertEqual((self.ui.window, (APP_NAME, EMAIL), {}),2161
2111 self._called[gui.SIG_REGISTRATION_SUCCEEDED])2162 self.assertEqual(self._called[self.REGISTRATION_SUCCESS],
21122163 (APP_NAME, EMAIL))
2113 def test_on_email_validation_error_proper_signals_is_emitted(self):2164
2114 """On EmailValidationError, SIG_USER_CANCELATION signal is sent."""2165 def test_on_email_validation_error_proper_callback_is_called(self):
2166 """On EmailValidationError, USER_CANCELLATION is called."""
2115 self.ui.on_email_validation_error(app_name=APP_NAME, error=self.error)2167 self.ui.on_email_validation_error(app_name=APP_NAME, error=self.error)
2116 self.ui.on_close_clicked()2168 self.ui.on_close_clicked()
2117 expected = (self.ui.window, (APP_NAME,), {})2169
2118 self.assertEqual(expected,2170 self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
2119 self._called[gui.SIG_USER_CANCELATION])2171
21202172 def test_on_logged_in_proper_callback_is_called(self):
2121 def test_on_logged_in_proper_signals_is_emitted(self):2173 """On LoggedIn, LOGIN_SUCCESSFULL is called."""
2122 """On LoggedIn, 'login-succeeded' signal is sent."""
2123 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)2174 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
2124 self.ui.on_close_clicked()2175 self.ui.on_close_clicked()
2125 self.assertEqual((self.ui.window, (APP_NAME, EMAIL), {}),2176
2126 self._called[gui.SIG_LOGIN_SUCCEEDED])2177 self.assertEqual(self._called[self.LOGIN_SUCCESSFULL],
21272178 (APP_NAME, EMAIL))
2128 def test_on_login_error_proper_signals_is_emitted(self):2179
2129 """On LoginError, SIG_USER_CANCELATION signal is sent."""2180 def test_on_login_error_proper_callback_is_called(self):
2181 """On LoginError, USER_CANCELLATION is called."""
2130 self.click_connect_with_valid_data()2182 self.click_connect_with_valid_data()
2131 self.ui.on_login_error(app_name=APP_NAME, error=self.error)2183 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
2132 self.ui.on_close_clicked()2184 self.ui.on_close_clicked()
2133 expected = (self.ui.window, (APP_NAME,), {})2185
2134 self.assertEqual(expected,2186 self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
2135 self._called[gui.SIG_USER_CANCELATION])2187
21362188 def test_registration_success_even_if_prior_registration_error(self):
2137 def test_registration_successfull_even_if_prior_registration_error(self):2189 """Only one callback is called with the final outcome.
2138 """Only one signal is sent with the final outcome."""2190
2191 When the user successfully registers, REGISTRATION_SUCCESS is
2192 called even if there were errors before.
2193
2194 """
2139 self.click_join_with_valid_data()2195 self.click_join_with_valid_data()
2140 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)2196 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
2141 self.click_join_with_valid_data()2197 self.click_join_with_valid_data()
2142 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)2198 self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL)
2143 self.ui.on_close_clicked()2199 self.ui.on_close_clicked()
21442200
2145 self.assertEqual(len(self._called), 1)2201 self.assertEqual(self._called[self.REGISTRATION_SUCCESS],
2146 self.assertTrue(gui.SIG_REGISTRATION_SUCCEEDED in self._called)2202 (APP_NAME, EMAIL))
21472203
2148 def test_login_successfull_even_if_prior_login_error(self):2204 def test_login_success_even_if_prior_login_error(self):
2149 """Only one signal is sent with the final outcome."""2205 """Only one callback is called with the final outcome.
2206
2207 When the user successfully logins, LOGIN_SUCCESSFULL is called even if
2208 there were errors before.
2209
2210 """
2150 self.click_connect_with_valid_data()2211 self.click_connect_with_valid_data()
2151 self.ui.on_login_error(app_name=APP_NAME, error=self.error)2212 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
2152 self.click_connect_with_valid_data()2213 self.click_connect_with_valid_data()
2153 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)2214 self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL)
2154 self.ui.on_close_clicked()2215 self.ui.on_close_clicked()
21552216
2156 self.assertEqual(len(self._called), 1)2217 self.assertEqual(self._called[self.LOGIN_SUCCESSFULL],
2157 self.assertTrue(gui.SIG_LOGIN_SUCCEEDED in self._called)2218 (APP_NAME, EMAIL))
21582219
2159 def test_user_cancelation_even_if_prior_registration_error(self):2220 def test_user_cancelation_even_if_prior_registration_error(self):
2160 """Only one signal is sent with the final outcome."""2221 """Only one callback is called with the final outcome.
2222
2223 When the user closes the window, USER_CANCELLATION is called even if
2224 there were registration errors before.
2225
2226 """
2161 self.click_join_with_valid_data()2227 self.click_join_with_valid_data()
2162 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)2228 self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error)
2163 self.ui.join_cancel_button.clicked()2229 self.ui.join_cancel_button.clicked()
21642230
2165 self.assertEqual(len(self._called), 1)2231 self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
2166 self.assertTrue(gui.SIG_USER_CANCELATION in self._called)
21672232
2168 def test_user_cancelation_even_if_prior_login_error(self):2233 def test_user_cancelation_even_if_prior_login_error(self):
2169 """Only one signal is sent with the final outcome."""2234 """Only one callback is called with the final outcome.
2235
2236 When the user closes the window, USER_CANCELLATION is called even if
2237 there were login errors before.
2238
2239 """
2170 self.click_connect_with_valid_data()2240 self.click_connect_with_valid_data()
2171 self.ui.on_login_error(app_name=APP_NAME, error=self.error)2241 self.ui.on_login_error(app_name=APP_NAME, error=self.error)
2172 self.ui.login_cancel_button.clicked()2242 self.ui.login_cancel_button.clicked()
21732243
2174 self.assertEqual(len(self._called), 1)2244 self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,))
2175 self.assertTrue(gui.SIG_USER_CANCELATION in self._called)
21762245
21772246
2178class DefaultButtonsTestCase(UbuntuSSOClientTestCase):2247class DefaultButtonsTestCase(UbuntuSSOClientTestCase):
21792248
=== modified file 'ubuntu_sso/logger.py'
--- ubuntu_sso/logger.py 2010-10-11 13:22:16 +0000
+++ ubuntu_sso/logger.py 2011-01-13 00:33:10 +0000
@@ -57,6 +57,7 @@
57 logger.addHandler(MAIN_HANDLER)57 logger.addHandler(MAIN_HANDLER)
58 if os.environ.get('DEBUG'):58 if os.environ.get('DEBUG'):
59 debug_handler = logging.StreamHandler(sys.stderr)59 debug_handler = logging.StreamHandler(sys.stderr)
60 debug_handler.setFormatter(logging.Formatter(fmt=FMT))
60 logger.addHandler(debug_handler)61 logger.addHandler(debug_handler)
6162
62 return logger63 return logger
6364
=== modified file 'ubuntu_sso/main.py'
--- ubuntu_sso/main.py 2010-12-16 16:31:34 +0000
+++ ubuntu_sso/main.py 2011-01-13 00:33:10 +0000
@@ -50,6 +50,7 @@
5050
51logger = setup_logging("ubuntu_sso.main")51logger = setup_logging("ubuntu_sso.main")
52U1_PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"52U1_PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
53TIMEOUT_INTERVAL = 500
5354
5455
55class SSOLoginProcessor(Account):56class SSOLoginProcessor(Account):
@@ -198,7 +199,9 @@
198 # pylint: disable=E1101199 # pylint: disable=E1101
199 d = Keyring().set_credentials(app_name, credentials)200 d = Keyring().set_credentials(app_name, credentials)
200 d.addCallback(lambda _: self.LoggedIn(app_name, email))201 d.addCallback(lambda _: self.LoggedIn(app_name, email))
201 d.addErrback(lambda _: self.UserNotValidated(app_name, email))202 d.addErrback(lambda failure: \
203 self.LoginError(app_name,
204 except_to_errdict(failure.value)))
202 else:205 else:
203 self.UserNotValidated(app_name, email)206 self.UserNotValidated(app_name, email)
204 blocking(f, app_name, success_cb, self.LoginError)207 blocking(f, app_name, success_cb, self.LoginError)
@@ -419,6 +422,12 @@
419422
420 """423 """
421424
425 def __init__(self, timeout_func, shutdown_func, *args, **kwargs):
426 super(CredentialsManagement, self).__init__(*args, **kwargs)
427 self._ref_count = 0
428 self.timeout_func = timeout_func
429 self.shutdown_func = shutdown_func
430
422 # Operator not preceded by a space (fails with dbus decorators)431 # Operator not preceded by a space (fails with dbus decorators)
423 # pylint: disable=C0322432 # pylint: disable=C0322
424433
@@ -434,39 +443,70 @@
434 result[DENIAL_CB_KEY] = self.AuthorizationDenied443 result[DENIAL_CB_KEY] = self.AuthorizationDenied
435 return result444 return result
436445
446 def _process_failure(self, failure, app_name):
447 """Process the 'failure' and emit CredentialsError."""
448 self.CredentialsError(app_name, except_to_errdict(failure.value))
449
450 def _get_ref_count(self):
451 """Get value of ref_count."""
452 logger.debug('ref_count is %r.', self._ref_count)
453 return self._ref_count
454
455 def _set_ref_count(self, new_value):
456 """Set a new value to ref_count."""
457 logger.debug('ref_count is %r, changing value to %r.',
458 self._ref_count, new_value)
459 if new_value < 0:
460 self._ref_count = 0
461 msg = 'Attempting to decrease ref_count to a negative value (%r).'
462 logger.warning(msg, new_value)
463 else:
464 self._ref_count = new_value
465
466 if self._ref_count == 0:
467 self.timeout_func(TIMEOUT_INTERVAL, self.shutdown_func)
468
469 ref_count = property(fget=_get_ref_count, fset=_set_ref_count)
470
437 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')471 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
438 def AuthorizationDenied(self, app_name):472 def AuthorizationDenied(self, app_name):
439 """Signal thrown when the user denies the authorization."""473 """Signal thrown when the user denies the authorization."""
474 self.ref_count -= 1
440 logger.info('%s: emitting AuthorizationDenied with app_name "%s".',475 logger.info('%s: emitting AuthorizationDenied with app_name "%s".',
441 self.__class__.__name__, app_name)476 self.__class__.__name__, app_name)
442477
443 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')478 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
444 def CredentialsFound(self, app_name, credentials):479 def CredentialsFound(self, app_name, credentials):
445 """Signal thrown when the credentials are found."""480 """Signal thrown when the credentials are found."""
481 self.ref_count -= 1
446 logger.info('%s: emitting CredentialsFound with app_name "%s".',482 logger.info('%s: emitting CredentialsFound with app_name "%s".',
447 self.__class__.__name__, app_name)483 self.__class__.__name__, app_name)
448484
449 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')485 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
450 def CredentialsNotFound(self, app_name):486 def CredentialsNotFound(self, app_name):
451 """Signal thrown when the credentials are not found."""487 """Signal thrown when the credentials are not found."""
488 self.ref_count -= 1
452 logger.info('%s: emitting CredentialsNotFound with app_name "%s".',489 logger.info('%s: emitting CredentialsNotFound with app_name "%s".',
453 self.__class__.__name__, app_name)490 self.__class__.__name__, app_name)
454491
455 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')492 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
456 def CredentialsCleared(self, app_name):493 def CredentialsCleared(self, app_name):
457 """Signal thrown when the credentials were cleared."""494 """Signal thrown when the credentials were cleared."""
495 self.ref_count -= 1
458 logger.info('%s: emitting CredentialsCleared with app_name "%s".',496 logger.info('%s: emitting CredentialsCleared with app_name "%s".',
459 self.__class__.__name__, app_name)497 self.__class__.__name__, app_name)
460498
461 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')499 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s')
462 def CredentialsStored(self, app_name):500 def CredentialsStored(self, app_name):
463 """Signal thrown when the credentials were cleared."""501 """Signal thrown when the credentials were cleared."""
502 self.ref_count -= 1
464 logger.info('%s: emitting CredentialsStored with app_name "%s".',503 logger.info('%s: emitting CredentialsStored with app_name "%s".',
465 self.__class__.__name__, app_name)504 self.__class__.__name__, app_name)
466505
467 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')506 @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
468 def CredentialsError(self, app_name, error_dict):507 def CredentialsError(self, app_name, error_dict):
469 """Signal thrown when there is a problem getting the credentials."""508 """Signal thrown when there is a problem getting the credentials."""
509 self.ref_count -= 1
470 logger.error('%s: emitting CredentialsError with app_name "%s" and '510 logger.error('%s: emitting CredentialsError with app_name "%s" and '
471 'error_dict %r.', self.__class__.__name__, app_name,511 'error_dict %r.', self.__class__.__name__, app_name,
472 error_dict)512 error_dict)
@@ -482,6 +522,7 @@
482 - 'args' is a dictionary, currently not used.522 - 'args' is a dictionary, currently not used.
483523
484 """524 """
525 self.ref_count += 1
485526
486 def success_cb(credentials):527 def success_cb(credentials):
487 """Find credentials and notify using signals."""528 """Find credentials and notify using signals."""
@@ -494,7 +535,7 @@
494 d = obj.find_credentials()535 d = obj.find_credentials()
495 # pylint: disable=E1101536 # pylint: disable=E1101
496 d.addCallback(success_cb)537 d.addCallback(success_cb)
497 d.addErrback(self.CredentialsError)538 d.addErrback(self._process_failure, app_name)
498539
499 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,540 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
500 in_signature='sa{ss}', out_signature='')541 in_signature='sa{ss}', out_signature='')
@@ -507,11 +548,13 @@
507 - 'args' is a dictionary, currently not used.548 - 'args' is a dictionary, currently not used.
508549
509 """550 """
551 self.ref_count += 1
552
510 obj = Credentials(app_name)553 obj = Credentials(app_name)
511 d = obj.clear_credentials()554 d = obj.clear_credentials()
512 # pylint: disable=E1101555 # pylint: disable=E1101
513 d.addCallback(lambda _: self.CredentialsCleared(app_name))556 d.addCallback(lambda _: self.CredentialsCleared(app_name))
514 d.addErrback(self.CredentialsError)557 d.addErrback(self._process_failure, app_name)
515558
516 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,559 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
517 in_signature='sa{ss}', out_signature='')560 in_signature='sa{ss}', out_signature='')
@@ -526,16 +569,20 @@
526 'consumer_secret'.569 'consumer_secret'.
527570
528 """571 """
572 self.ref_count += 1
573
529 obj = Credentials(app_name)574 obj = Credentials(app_name)
530 d = obj.store_credentials(args)575 d = obj.store_credentials(args)
531 # pylint: disable=E1101576 # pylint: disable=E1101
532 d.addCallback(lambda _: self.CredentialsStored(app_name))577 d.addCallback(lambda _: self.CredentialsStored(app_name))
533 d.addErrback(self.CredentialsError)578 d.addErrback(self._process_failure, app_name)
534579
535 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,580 @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
536 in_signature='sa{ss}', out_signature='')581 in_signature='sa{ss}', out_signature='')
537 def register(self, app_name, args):582 def register(self, app_name, args):
538 """Get credentials if found else prompt GUI to register."""583 """Get credentials if found else prompt GUI to register."""
584 self.ref_count += 1
585
539 obj = Credentials(app_name, **self._parse_args(args))586 obj = Credentials(app_name, **self._parse_args(args))
540 obj.register()587 obj.register()
541588
@@ -543,5 +590,7 @@
543 in_signature='sa{ss}', out_signature='')590 in_signature='sa{ss}', out_signature='')
544 def login(self, app_name, args):591 def login(self, app_name, args):
545 """Get credentials if found else prompt GUI to login."""592 """Get credentials if found else prompt GUI to login."""
593 self.ref_count += 1
594
546 obj = Credentials(app_name, **self._parse_args(args))595 obj = Credentials(app_name, **self._parse_args(args))
547 obj.login()596 obj.login()
548597
=== modified file 'ubuntu_sso/tests/__init__.py'
--- ubuntu_sso/tests/__init__.py 2010-11-30 13:21:17 +0000
+++ ubuntu_sso/tests/__init__.py 2011-01-13 00:33:10 +0000
@@ -18,6 +18,8 @@
1818
19import os19import os
2020
21from twisted.trial import unittest
22
21from ubuntu_sso.keyring import get_token_name23from ubuntu_sso.keyring import get_token_name
2224
23APP_NAME = 'The Super App!'25APP_NAME = 'The Super App!'
@@ -45,3 +47,15 @@
45TOKEN_NAME = get_token_name(APP_NAME)47TOKEN_NAME = get_token_name(APP_NAME)
46TC_URL = 'http://localhost/'48TC_URL = 'http://localhost/'
47WINDOW_ID = 549WINDOW_ID = 5
50
51
52class TestCase(unittest.TestCase):
53 """Customized test case that keeps tracks of method calls."""
54
55 def setUp(self):
56 super(TestCase, self).setUp()
57 self._called = False
58
59 def _set_called(self, *args, **kwargs):
60 """Keep track of a method call."""
61 self._called = (args, kwargs)
4862
=== modified file 'ubuntu_sso/tests/bin/show_gui'
--- ubuntu_sso/tests/bin/show_gui 2010-11-30 13:21:17 +0000
+++ ubuntu_sso/tests/bin/show_gui 2011-01-13 00:33:10 +0000
@@ -31,11 +31,12 @@
31 'Nam sed lorem nibh. Suspendisse gravida nulla non nunc suscipit' \31 'Nam sed lorem nibh. Suspendisse gravida nulla non nunc suscipit' \
32 ' pulvinar tempus ut augue.'32 ' pulvinar tempus ut augue.'
3333
34main_quit = lambda *args, **kwargs: gtk.main_quit()
35
3634
37if __name__ == '__main__':35if __name__ == '__main__':
38 tc_url=TC_URL36
37 # pylint: disable=C0103, E1101
38
39 tc_url = TC_URL
39 login_only = False40 login_only = False
40 xid = 041 xid = 0
4142
@@ -51,7 +52,7 @@
51 win.show()52 win.show()
52 xid = win.window.xid53 xid = win.window.xid
5354
54 UbuntuSSOClientGUI(close_callback=main_quit, app_name=APP_NAME,55 UbuntuSSOClientGUI(close_callback=lambda *args, **kwargs: gtk.main_quit(),
55 tc_url=tc_url, help_text=HELP_TEXT, window_id=xid,56 app_name=APP_NAME, tc_url=tc_url, help_text=HELP_TEXT,
56 login_only=login_only)57 window_id=xid, login_only=login_only)
57 gtk.main()58 gtk.main()
5859
=== modified file 'ubuntu_sso/tests/bin/show_nm_state'
--- ubuntu_sso/tests/bin/show_nm_state 2010-09-08 19:25:02 +0000
+++ ubuntu_sso/tests/bin/show_nm_state 2011-01-13 00:33:10 +0000
@@ -26,7 +26,6 @@
26from ubuntu_sso.networkstate import NetworkManagerState, NM_STATE_NAMES26from ubuntu_sso.networkstate import NetworkManagerState, NM_STATE_NAMES
2727
28DBusGMainLoop(set_as_default=True)28DBusGMainLoop(set_as_default=True)
29loop = gobject.MainLoop()
3029
3130
32def got_state(state):31def got_state(state):
@@ -34,8 +33,9 @@
34 try:33 try:
35 print NM_STATE_NAMES[state]34 print NM_STATE_NAMES[state]
36 finally:35 finally:
37 loop.quit()36 gobject.MainLoop().quit()
3837
38# pylint: disable=C0103
39nms = NetworkManagerState(got_state)39nms = NetworkManagerState(got_state)
40nms.find_online_state()40nms.find_online_state()
41loop.run()41gobject.MainLoop().run()
4242
=== modified file 'ubuntu_sso/tests/test_credentials.py'
--- ubuntu_sso/tests/test_credentials.py 2010-11-30 13:21:17 +0000
+++ ubuntu_sso/tests/test_credentials.py 2011-01-13 00:33:10 +0000
@@ -21,13 +21,11 @@
21import logging21import logging
22import urllib22import urllib
2323
24import gobject
25
26from twisted.internet import defer24from twisted.internet import defer
27from twisted.internet.defer import inlineCallbacks25from twisted.internet.defer import inlineCallbacks
28from twisted.trial.unittest import TestCase, FailTest26from twisted.trial.unittest import TestCase, FailTest
27from ubuntuone.devtools.handlers import MementoHandler
2928
30from contrib.testing.testcase import MementoHandler
31from ubuntu_sso import credentials29from ubuntu_sso import credentials
32from ubuntu_sso.credentials import (APP_NAME_KEY, HELP_TEXT_KEY, NO_OP,30from ubuntu_sso.credentials import (APP_NAME_KEY, HELP_TEXT_KEY, NO_OP,
33 PING_URL_KEY, TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,31 PING_URL_KEY, TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
@@ -60,10 +58,6 @@
60 WINDOW_ID_KEY: WINDOW_ID,58 WINDOW_ID_KEY: WINDOW_ID,
61}59}
6260
63SIG_LOGIN_SUCCEEDED = '1'
64SIG_REGISTRATION_SUCCEEDED = '2'
65SIG_USER_CANCELATION = '3'
66
6761
68class SampleMiscException(Exception):62class SampleMiscException(Exception):
69 """An error to be used while testing."""63 """An error to be used while testing."""
@@ -82,17 +76,9 @@
82 def __init__(self, *args, **kwargs):76 def __init__(self, *args, **kwargs):
83 self.args = args77 self.args = args
84 self.kwargs = kwargs78 self.kwargs = kwargs
85 self.signals = []79 self.login_success_callback = None
86 self.methods = []80 self.registration_success_callback = None
87 self.connect = lambda *a: self.signals.append(a)81 self.user_cancellation_callback = None
88
89 def finish_success(self):
90 """Fake the success finish."""
91 self.methods.append('finish_success')
92
93 def finish_error(self, error):
94 """Fake the error finish."""
95 self.methods.append(('finish_error', error))
9682
9783
98class BasicTestCase(TestCase):84class BasicTestCase(TestCase):
@@ -100,7 +86,6 @@
10086
101 def setUp(self):87 def setUp(self):
102 """Init."""88 """Init."""
103 self.patch(gobject, 'idle_add', lambda f, *a, **kw: f(*a, **kw))
104 self._called = False # helper89 self._called = False # helper
10590
106 self.memento = MementoHandler()91 self.memento = MementoHandler()
@@ -226,45 +211,21 @@
226211
227 self.assertEqual(getattr(self.obj, UI_MODULE_KEY), GTK_GUI_MODULE)212 self.assertEqual(getattr(self.obj, UI_MODULE_KEY), GTK_GUI_MODULE)
228213
229 def test_success_cb_gui_none(self):214 def test_success_cb(self):
230 """Success callback calls the caller but not the GUI."""215 """Success callback calls the caller."""
231 self.obj.gui = None216 self.obj.gui = None
232 self.obj.success_cb(creds=TOKEN)217 self.obj.success_cb(creds=TOKEN)
233218
234 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}),219 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}),
235 'caller _success_cb was called.')220 'caller _success_cb was called.')
236221
237 def test_success_cb_gui_not_none(self):222 def test_error_cb(self):
238 """Success callback calls the caller and finish the GUI."""223 """Error callback calls the caller."""
239 self.obj.gui = ui = FakedClientGUI(APP_NAME)224 error_dict = {'foo': 'bar'}
240 self.obj.success_cb(creds=TOKEN)225 self.obj.error_cb(error_dict=error_dict)
241226
242 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}),227 self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}),
243 'caller _success_cb was called.')228 'caller _error_cb was called.')
244 self.assertEqual(ui.methods, ['finish_success'],
245 'GUI finish_success was called.')
246 self.assertTrue(self.obj.gui is None, 'gui is reset to None.')
247
248 def test_error_cb_gui_none(self):
249 """Error callback calls the caller but not the GUI."""
250 self.obj.gui = None
251 error_dict = {'foo': 'bar'}
252 self.obj.error_cb(error_dict=error_dict)
253
254 self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}),
255 'caller _error_cb was called.')
256
257 def test_error_cb_gui_not_none(self):
258 """Error callback calls the caller and finish the GUI."""
259 self.obj.gui = ui = FakedClientGUI(APP_NAME)
260 error_dict = {'foo': 'bar'}
261 self.obj.error_cb(error_dict=error_dict)
262
263 self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}),
264 'caller _error_cb was called.')
265 self.assertEqual(ui.methods, [('finish_error', error_dict)],
266 'GUI finish_error was called.')
267 self.assertTrue(self.obj.gui is None, 'gui is reset to None.')
268229
269230
270class CredentialsLoginSuccessTestCase(CredentialsTestCase):231class CredentialsLoginSuccessTestCase(CredentialsTestCase):
@@ -276,12 +237,12 @@
276 self.patch(credentials.Keyring, 'get_credentials',237 self.patch(credentials.Keyring, 'get_credentials',
277 lambda kr, app: defer.succeed(None))238 lambda kr, app: defer.succeed(None))
278239
279 yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,240 result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
280 email=EMAIL)
281241
282 msg = 'Creds are empty! This should not happen'242 msg = 'Creds are empty! This should not happen'
283 self.assert_error_cb_called(msg='Problem while retrieving credentials',243 self.assert_error_cb_called(msg='Problem while retrieving credentials',
284 detailed_error=AssertionError(msg))244 detailed_error=AssertionError(msg))
245 self.assertEqual(result, None)
285246
286 @inlineCallbacks247 @inlineCallbacks
287 def test_cred_error(self):248 def test_cred_error(self):
@@ -290,11 +251,11 @@
290 self.patch(credentials.Keyring, 'get_credentials',251 self.patch(credentials.Keyring, 'get_credentials',
291 lambda kr, app: defer.fail(expected_error))252 lambda kr, app: defer.fail(expected_error))
292253
293 yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,254 result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
294 email=EMAIL)
295255
296 msg = 'Problem while retrieving credentials'256 msg = 'Problem while retrieving credentials'
297 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)257 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
258 self.assertEqual(result, None)
298 self.assertTrue(self.memento.check_exception(SampleMiscException))259 self.assertTrue(self.memento.check_exception(SampleMiscException))
299260
300 @inlineCallbacks261 @inlineCallbacks
@@ -304,10 +265,10 @@
304 lambda kr, app: defer.succeed(TOKEN))265 lambda kr, app: defer.succeed(TOKEN))
305 self.patch(self.obj, '_ping_url', lambda *a, **kw: 200)266 self.patch(self.obj, '_ping_url', lambda *a, **kw: 200)
306267
307 yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,268 result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
308 email=EMAIL)
309269
310 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))270 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
271 self.assertEqual(result, 0)
311272
312 @inlineCallbacks273 @inlineCallbacks
313 def test_ping_error(self):274 def test_ping_error(self):
@@ -324,12 +285,12 @@
324 self.patch(self.obj, 'clear_credentials',285 self.patch(self.obj, 'clear_credentials',
325 lambda: setattr(self, '_cred_cleared', True))286 lambda: setattr(self, '_cred_cleared', True))
326287
327 yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,288 result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
328 email=EMAIL)
329289
330 # error cb called correctly290 # error cb called correctly
331 msg = 'Problem opening the ping_url'291 msg = 'Problem opening the ping_url'
332 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))292 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))
293 self.assertEqual(result, None)
333294
334 # credentials cleared295 # credentials cleared
335 self.assertTrue(self._cred_cleared)296 self.assertTrue(self._cred_cleared)
@@ -349,8 +310,7 @@
349 self.patch(self.obj, '_ping_url',310 self.patch(self.obj, '_ping_url',
350 lambda *a, **kw: setattr(self, '_url_pinged', (a, kw)))311 lambda *a, **kw: setattr(self, '_url_pinged', (a, kw)))
351312
352 yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,313 yield self.obj._login_success_cb(APP_NAME, EMAIL)
353 email=EMAIL)
354314
355 self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {}))315 self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {}))
356316
@@ -366,10 +326,10 @@
366 self.patch(self.obj, 'clear_credentials', self._set_called)326 self.patch(self.obj, 'clear_credentials', self._set_called)
367 self.obj.ping_url = None327 self.obj.ping_url = None
368328
369 yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME,329 result = yield self.obj._login_success_cb(APP_NAME, EMAIL)
370 email=EMAIL)
371330
372 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))331 self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}))
332 self.assertEqual(result, 0)
373333
374334
375class CredentialsAuthDeniedTestCase(CredentialsTestCase):335class CredentialsAuthDeniedTestCase(CredentialsTestCase):
@@ -377,10 +337,9 @@
377337
378 def test_auth_denial_cb(self):338 def test_auth_denial_cb(self):
379 """On auth denied, self.denial_cb is called."""339 """On auth denied, self.denial_cb is called."""
380 self.obj.gui = ui = FakedClientGUI(APP_NAME)340 self.obj._auth_denial_cb(app_name=APP_NAME)
381 self.obj._auth_denial_cb(dialog=None, app_name=APP_NAME)341
382 self.assertEqual(self._called, (('denial', APP_NAME), {}))342 self.assertEqual(self._called, (('denial', APP_NAME), {}))
383 self.assertEqual(ui.methods, [], 'no GUI method was called.')
384343
385344
386class PingUrlTestCase(CredentialsTestCase):345class PingUrlTestCase(CredentialsTestCase):
@@ -401,24 +360,29 @@
401360
402 self.patch(credentials.urllib2, 'urlopen', faked_urlopen)361 self.patch(credentials.urllib2, 'urlopen', faked_urlopen)
403362
363 @inlineCallbacks
404 def test_ping_url_if_url_is_none(self):364 def test_ping_url_if_url_is_none(self):
405 """self.ping_url is opened."""365 """self.ping_url is opened."""
406 self.patch(credentials.urllib2, 'urlopen', self.fail)366 self.patch(credentials.urllib2, 'urlopen', self.fail)
407 self.obj.ping_url = None367 self.obj.ping_url = None
408 self.obj._ping_url(app_name=APP_NAME, email=EMAIL, credentials=TOKEN)368 yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
369 credentials=TOKEN)
409 # no failure370 # no failure
410371
372 @inlineCallbacks
411 def test_ping_url(self):373 def test_ping_url(self):
412 """On auth success, self.ping_url is opened."""374 """On auth success, self.ping_url is opened."""
413 self.obj._ping_url(app_name=APP_NAME, email=EMAIL, credentials=TOKEN)375 yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL,
376 credentials=TOKEN)
414377
415 self.assertIsInstance(self._request, credentials.urllib2.Request)378 self.assertIsInstance(self._request, credentials.urllib2.Request)
416 self.assertEqual(self._request.get_full_url(),379 self.assertEqual(self._request.get_full_url(),
417 self.obj.ping_url + EMAIL)380 self.obj.ping_url + EMAIL)
418381
382 @inlineCallbacks
419 def test_request_is_signed_with_credentials(self):383 def test_request_is_signed_with_credentials(self):
420 """The http request to self.ping_url is signed with the credentials."""384 """The http request to self.ping_url is signed with the credentials."""
421 result = self.obj._ping_url(APP_NAME, EMAIL, TOKEN)385 result = yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN)
422 headers = self._request.headers386 headers = self._request.headers
423387
424 self.assertIn('Authorization', headers)388 self.assertIn('Authorization', headers)
@@ -431,12 +395,13 @@
431 self.assertIn(expected, oauth_stuff)395 self.assertIn(expected, oauth_stuff)
432 self.assertEqual(result, 200)396 self.assertEqual(result, 200)
433397
398 @inlineCallbacks
434 def test_ping_url_error(self):399 def test_ping_url_error(self):
435 """Exception is handled if ping fails."""400 """Exception is handled if ping fails."""
436 error = 'Blu'401 error = 'Blu'
437 self.patch(credentials.urllib2, 'urlopen', lambda r: self.fail(error))402 self.patch(credentials.urllib2, 'urlopen', lambda r: self.fail(error))
438403
439 self.obj._ping_url(APP_NAME, EMAIL, TOKEN)404 yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN)
440405
441 msg = 'Problem opening the ping_url'406 msg = 'Problem opening the ping_url'
442 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))407 self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error))
@@ -472,11 +437,8 @@
472 self.patch(credentials.Keyring, 'get_credentials',437 self.patch(credentials.Keyring, 'get_credentials',
473 lambda kr, app: defer.fail(expected_error))438 lambda kr, app: defer.fail(expected_error))
474439
475 yield self.obj.find_credentials()440 yield self.assertFailure(self.obj.find_credentials(),
476441 SampleMiscException)
477 msg = 'Problem while retrieving credentials'
478 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
479 self.assertTrue(self.memento.check_exception(SampleMiscException))
480442
481443
482class ClearCredentialsTestCase(CredentialsTestCase):444class ClearCredentialsTestCase(CredentialsTestCase):
@@ -498,11 +460,8 @@
498 self.patch(credentials.Keyring, 'delete_credentials',460 self.patch(credentials.Keyring, 'delete_credentials',
499 lambda kr, app: defer.fail(expected_error))461 lambda kr, app: defer.fail(expected_error))
500462
501 yield self.obj.clear_credentials()463 yield self.assertFailure(self.obj.clear_credentials(),
502464 SampleMiscException)
503 msg = 'Problem while deleting credentials'
504 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
505 self.assertTrue(self.memento.check_exception(SampleMiscException))
506465
507466
508class StoreCredentialsTestCase(CredentialsTestCase):467class StoreCredentialsTestCase(CredentialsTestCase):
@@ -524,11 +483,8 @@
524 self.patch(credentials.Keyring, 'set_credentials',483 self.patch(credentials.Keyring, 'set_credentials',
525 lambda kr, app, token: defer.fail(expected_error))484 lambda kr, app, token: defer.fail(expected_error))
526485
527 yield self.obj.store_credentials(TOKEN)486 yield self.assertFailure(self.obj.store_credentials(TOKEN),
528487 SampleMiscException)
529 msg = 'Problem while storing credentials'
530 self.assert_error_cb_called(msg=msg, detailed_error=expected_error)
531 self.assertTrue(self.memento.check_exception(SampleMiscException))
532488
533489
534class RegisterTestCase(CredentialsTestCase):490class RegisterTestCase(CredentialsTestCase):
@@ -592,17 +548,17 @@
592548
593 @inlineCallbacks549 @inlineCallbacks
594 def test_connects_gui_signals(self):550 def test_connects_gui_signals(self):
595 """Outcome signals from GUI are connected to callbacks."""551 """GUI callbacks are properly connected."""
596 self.patch(credentials.Keyring, 'get_credentials',552 self.patch(credentials.Keyring, 'get_credentials',
597 lambda kr, app: defer.succeed(None))553 lambda kr, app: defer.succeed(None))
598
599 expected = [
600 (SIG_LOGIN_SUCCEEDED, self.obj._login_success_cb),
601 (SIG_REGISTRATION_SUCCEEDED, self.obj._login_success_cb),
602 (SIG_USER_CANCELATION, self.obj._auth_denial_cb),
603 ]
604 yield getattr(self.obj, self.operation)()554 yield getattr(self.obj, self.operation)()
605 self.assertEqual(self.obj.gui.signals, expected)555
556 self.assertEqual(self.obj.gui.login_success_callback,
557 self.obj._login_success_cb)
558 self.assertEqual(self.obj.gui.registration_success_callback,
559 self.obj._login_success_cb)
560 self.assertEqual(self.obj.gui.user_cancellation_callback,
561 self.obj._auth_denial_cb)
606562
607 @inlineCallbacks563 @inlineCallbacks
608 def test_gui_is_created(self):564 def test_gui_is_created(self):
609565
=== modified file 'ubuntu_sso/tests/test_main.py'
--- ubuntu_sso/tests/test_main.py 2010-12-16 16:31:34 +0000
+++ ubuntu_sso/tests/test_main.py 2011-01-13 00:33:10 +0000
@@ -27,21 +27,22 @@
27from twisted.internet import defer27from twisted.internet import defer
28from twisted.internet.defer import Deferred, inlineCallbacks28from twisted.internet.defer import Deferred, inlineCallbacks
29from twisted.trial.unittest import TestCase29from twisted.trial.unittest import TestCase
30from ubuntuone.devtools.handlers import MementoHandler
3031
31import ubuntu_sso.keyring32import ubuntu_sso.keyring
32import ubuntu_sso.main33import ubuntu_sso.main
3334
34from contrib.testing.testcase import MementoHandler
35from ubuntu_sso import DBUS_CREDENTIALS_IFACE35from ubuntu_sso import DBUS_CREDENTIALS_IFACE
36from ubuntu_sso.keyring import U1_APP_NAME36from ubuntu_sso.keyring import U1_APP_NAME
37from ubuntu_sso.main import (U1_PING_URL, blocking, except_to_errdict,37from ubuntu_sso.main import (U1_PING_URL, TIMEOUT_INTERVAL,
38 blocking, except_to_errdict,
38 CredentialsManagement, SSOCredentials, SSOLogin)39 CredentialsManagement, SSOCredentials, SSOLogin)
39from ubuntu_sso.main import (HELP_TEXT_KEY, PING_URL_KEY,40from ubuntu_sso.main import (HELP_TEXT_KEY, PING_URL_KEY,
40 TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,41 TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
41 SUCCESS_CB_KEY, ERROR_CB_KEY, DENIAL_CB_KEY)42 SUCCESS_CB_KEY, ERROR_CB_KEY, DENIAL_CB_KEY)
42from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID,43from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID,
43 CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, PASSWORD, PING_URL, TOKEN,44 CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, PASSWORD, PING_URL, TOKEN,
44 TOKEN_NAME, WINDOW_ID)45 TOKEN_NAME, WINDOW_ID, TestCase)
4546
4647
47# Access to a protected member 'yyy' of a client class48# Access to a protected member 'yyy' of a client class
@@ -70,6 +71,8 @@
70class SsoDbusTestCase(TestCase):71class SsoDbusTestCase(TestCase):
71 """Test the SSOLogin DBus interface."""72 """Test the SSOLogin DBus interface."""
7273
74 timeout = 2
75
73 def setUp(self):76 def setUp(self):
74 """Create the mocking bus."""77 """Create the mocking bus."""
75 self.mocker = Mocker()78 self.mocker = Mocker()
@@ -256,8 +259,8 @@
256 client.login(APP_NAME, EMAIL, PASSWORD)259 client.login(APP_NAME, EMAIL, PASSWORD)
257 return d260 return d
258261
259 def test_login_error(self):262 def test_login_error_get_token_name(self):
260 """Test that the login method fails as expected."""263 """The login method fails as expected when get_token_name fails."""
261 d = Deferred()264 d = Deferred()
262 self.create_mock_processor()265 self.create_mock_processor()
263 self.patch(ubuntu_sso.main, "blocking", fake_err_blocking)266 self.patch(ubuntu_sso.main, "blocking", fake_err_blocking)
@@ -284,6 +287,40 @@
284 client.login(APP_NAME, EMAIL, PASSWORD)287 client.login(APP_NAME, EMAIL, PASSWORD)
285 return d288 return d
286289
290 def test_login_error_set_credentials(self):
291 """The login method fails as expected when set_credentials fails."""
292 d = Deferred()
293 processor = self.create_mock_processor()
294 processor.login(EMAIL, PASSWORD, TOKEN_NAME)
295 self.mocker.result(TOKEN)
296 processor.is_validated(TOKEN)
297 self.mocker.result(True)
298
299 self.patch(ubuntu_sso.main, "blocking", fake_ok_blocking)
300
301 def fake_set_creds(*args):
302 """A fake Keyring.set_credentials that fails."""
303 return defer.fail(BlockingSampleException())
304
305 self.patch(ubuntu_sso.main.Keyring, "set_credentials", fake_set_creds)
306 self.mocker.replay()
307
308 def verify(app_name, errdict):
309 """The actual test."""
310 self.assertEqual(app_name, APP_NAME)
311 self.assertEqual(errdict["errtype"], "BlockingSampleException")
312 self.assertFalse(self.keyring_was_set, "Keyring should not be set")
313 d.callback("Ok")
314
315 client = SSOLogin(self.mockbusname,
316 sso_login_processor_class=self.mockprocessorclass)
317 fail = lambda app, res: d.errback((app, res))
318 self.patch(client, "LoggedIn", fail)
319 self.patch(client, "LoginError", verify)
320 self.patch(client, "UserNotValidated", fail)
321 client.login(APP_NAME, EMAIL, PASSWORD)
322 return d
323
287 def test_validate_email(self):324 def test_validate_email(self):
288 """Test that the validate_email method works ok."""325 """Test that the validate_email method works ok."""
289 d = Deferred()326 d = Deferred()
@@ -666,21 +703,30 @@
666class CredentialsManagementTestCase(TestCase):703class CredentialsManagementTestCase(TestCase):
667 """Tests for the CredentialsManagement DBus interface."""704 """Tests for the CredentialsManagement DBus interface."""
668705
706 timeout = 2
669 base_args = {HELP_TEXT_KEY: HELP_TEXT, PING_URL_KEY: PING_URL,707 base_args = {HELP_TEXT_KEY: HELP_TEXT, PING_URL_KEY: PING_URL,
670 TC_URL_KEY: TC_URL, WINDOW_ID_KEY: WINDOW_ID,708 TC_URL_KEY: TC_URL, WINDOW_ID_KEY: WINDOW_ID,
671 UI_CLASS_KEY: 'SuperUI', UI_MODULE_KEY: 'foo.bar.baz',709 UI_CLASS_KEY: 'SuperUI', UI_MODULE_KEY: 'foo.bar.baz',
672 }710 }
673711
674 def setUp(self):712 def setUp(self):
713 super(CredentialsManagementTestCase, self).setUp()
714
675 self.mocker = Mocker()715 self.mocker = Mocker()
676 self.client = CredentialsManagement()716 self.client = CredentialsManagement(timeout_func=lambda *a: None,
717 shutdown_func=lambda *a: None)
677 self.args = {}718 self.args = {}
678 self.cred_args = {}719 self.cred_args = {}
679720
721 self.memento = MementoHandler()
722 self.memento.setLevel(logging.DEBUG)
723 ubuntu_sso.main.logger.addHandler(self.memento)
724
680 def tearDown(self):725 def tearDown(self):
681 """Verify the mocking stuff and shut it down."""726 """Verify the mocking stuff and shut it down."""
682 self.mocker.verify()727 self.mocker.verify()
683 self.mocker.restore()728 self.mocker.restore()
729 super(CredentialsManagementTestCase, self).tearDown()
684730
685 def assert_dbus_method_correct(self, method):731 def assert_dbus_method_correct(self, method):
686 """Check that 'method' is a dbus method with proper signatures."""732 """Check that 'method' is a dbus method with proper signatures."""
@@ -703,10 +749,168 @@
703 self.assertIsInstance(self.client, ubuntu_sso.main.dbus.service.Object)749 self.assertIsInstance(self.client, ubuntu_sso.main.dbus.service.Object)
704750
705751
706class CredentialsManagementFindClearTestCase(CredentialsManagementTestCase):752class CredentialsManagementRefCountingTestCase(CredentialsManagementTestCase):
707 """Tests for the CredentialsManagement find/clear/store methods."""753 """Tests for the CredentialsManagement ref counting."""
708754
709 timeout = 5755 def test_ref_counting(self):
756 """Ref counting is in place."""
757 self.assertEqual(self.client.ref_count, 0)
758
759 def test_find_credentials(self):
760 """Keep proper track of on going requests."""
761 self.client.find_credentials(APP_NAME, self.args)
762
763 self.assertEqual(self.client.ref_count, 1)
764
765 def test_clear_credentials(self):
766 """Keep proper track of on going requests."""
767 self.client.clear_credentials(APP_NAME, self.args)
768
769 self.assertEqual(self.client.ref_count, 1)
770
771 def test_store_credentials(self):
772 """Keep proper track of on going requests."""
773 self.client.store_credentials(APP_NAME, self.args)
774
775 self.assertEqual(self.client.ref_count, 1)
776
777 def test_register(self):
778 """Keep proper track of on going requests."""
779 self.client.register(APP_NAME, self.args)
780
781 self.assertEqual(self.client.ref_count, 1)
782
783 def test_login(self):
784 """Keep proper track of on going requests."""
785 self.client.login(APP_NAME, self.args)
786
787 self.assertEqual(self.client.ref_count, 1)
788
789 def test_several_requests(self):
790 """Requests can be nested."""
791 self.client.login(APP_NAME, self.args)
792 self.client.clear_credentials(APP_NAME, self.args)
793 self.client.find_credentials(APP_NAME, self.args)
794 self.client.register(APP_NAME, self.args)
795 self.client.store_credentials(APP_NAME, self.args)
796
797 self.assertEqual(self.client.ref_count, 5)
798
799 def test_credentials_found(self):
800 """Ref counter is decreased when a signal is sent."""
801 self.client.ref_count = 3
802 self.client.CredentialsFound(APP_NAME, TOKEN)
803
804 self.assertEqual(self.client.ref_count, 2)
805
806 def test_credentials_not_found(self):
807 """Ref counter is decreased when a signal is sent."""
808 self.client.ref_count = 3
809 self.client.CredentialsNotFound(APP_NAME)
810
811 self.assertEqual(self.client.ref_count, 2)
812
813 def test_credentials_cleared(self):
814 """Ref counter is decreased when a signal is sent."""
815 self.client.ref_count = 3
816 self.client.CredentialsCleared(APP_NAME)
817
818 self.assertEqual(self.client.ref_count, 2)
819
820 def test_credentials_stored(self):
821 """Ref counter is decreased when a signal is sent."""
822 self.client.ref_count = 3
823 self.client.CredentialsStored(APP_NAME)
824
825 self.assertEqual(self.client.ref_count, 2)
826
827 def test_credentials_error(self):
828 """Ref counter is decreased when a signal is sent."""
829 self.client.ref_count = 3
830 self.client.CredentialsError(APP_NAME, {'error_type': 'test'})
831
832 self.assertEqual(self.client.ref_count, 2)
833
834 def test_authorization_denied(self):
835 """Ref counter is decreased when a signal is sent."""
836 self.client.ref_count = 3
837 self.client.AuthorizationDenied(APP_NAME)
838
839 self.assertEqual(self.client.ref_count, 2)
840
841 def test_credentials_found_when_ref_count_is_not_positive(self):
842 """Ref counter is decreased when a signal is sent."""
843 self.client._ref_count = -3
844 self.client.CredentialsFound(APP_NAME, TOKEN)
845
846 self.assertEqual(self.client.ref_count, 0)
847 msg = 'Attempting to decrease ref_count to a negative value (-4).'
848 self.assertTrue(self.memento.check_warning(msg))
849
850 def test_credentials_not_found_when_ref_count_is_not_positive(self):
851 """Ref counter is decreased when a signal is sent."""
852 self.client._ref_count = -3
853 self.client.CredentialsNotFound(APP_NAME)
854
855 self.assertEqual(self.client.ref_count, 0)
856 msg = 'Attempting to decrease ref_count to a negative value (-4).'
857 self.assertTrue(self.memento.check_warning(msg))
858
859 def test_credentials_cleared_when_ref_count_is_not_positive(self):
860 """Ref counter is decreased when a signal is sent."""
861 self.client._ref_count = -3
862 self.client.CredentialsCleared(APP_NAME)
863
864 self.assertEqual(self.client.ref_count, 0)
865 msg = 'Attempting to decrease ref_count to a negative value (-4).'
866 self.assertTrue(self.memento.check_warning(msg))
867
868 def test_credentials_stored_when_ref_count_is_not_positive(self):
869 """Ref counter is decreased when a signal is sent."""
870 self.client._ref_count = -3
871 self.client.CredentialsStored(APP_NAME)
872
873 self.assertEqual(self.client.ref_count, 0)
874 msg = 'Attempting to decrease ref_count to a negative value (-4).'
875 self.assertTrue(self.memento.check_warning(msg))
876
877 def test_credentials_error_when_ref_count_is_not_positive(self):
878 """Ref counter is decreased when a signal is sent."""
879 self.client._ref_count = -3
880 self.client.CredentialsError(APP_NAME, {'error_type': 'test'})
881
882 self.assertEqual(self.client.ref_count, 0)
883 msg = 'Attempting to decrease ref_count to a negative value (-4).'
884 self.assertTrue(self.memento.check_warning(msg))
885
886 def test_autorization_denied_when_ref_count_is_not_positive(self):
887 """Ref counter is decreased when a signal is sent."""
888 self.client._ref_count = -3
889 self.client.AuthorizationDenied(APP_NAME)
890
891 self.assertEqual(self.client.ref_count, 0)
892 msg = 'Attempting to decrease ref_count to a negative value (-4).'
893 self.assertTrue(self.memento.check_warning(msg))
894
895 def test_on_zero_ref_count_shutdown(self):
896 """When ref count reaches 0, queue shutdown op."""
897 self.client.timeout_func = self._set_called
898 self.client.find_credentials(APP_NAME, self.args)
899 self.client.CredentialsFound(APP_NAME, TOKEN)
900
901 self.assertEqual(self._called,
902 ((TIMEOUT_INTERVAL, self.client.shutdown_func), {}))
903
904 def test_on_non_zero_ref_count_do_not_shutdown(self):
905 """If ref count is not 0, do not queue shutdown op."""
906 self.client.timeout_func = self._set_called
907 self.client.find_credentials(APP_NAME, self.args)
908
909 self.assertEqual(self._called, False)
910
911
912class CredentialsManagementFindTestCase(CredentialsManagementTestCase):
913 """Tests for the CredentialsManagement find method."""
710914
711 def test_find_credentials(self):915 def test_find_credentials(self):
712 """The credentials are asked and returned in signals."""916 """The credentials are asked and returned in signals."""
@@ -762,7 +966,8 @@
762 else:966 else:
763 d.callback(app_name)967 d.callback(app_name)
764968
765 self.patch(self.client, 'CredentialsFound', d.errback)969 self.patch(self.client, 'CredentialsFound',
970 lambda app, creds: d.errback(app))
766 self.patch(self.client, 'CredentialsNotFound', verify)971 self.patch(self.client, 'CredentialsNotFound', verify)
767972
768 self.create_mock_backend().find_credentials()973 self.create_mock_backend().find_credentials()
@@ -772,6 +977,32 @@
772 self.client.find_credentials(APP_NAME, self.args)977 self.client.find_credentials(APP_NAME, self.args)
773 return d978 return d
774979
980 def test_find_credentials_error(self):
981 """If find_credentials fails, CredentialsError is sent."""
982 d = Deferred()
983
984 def verify(app_name, errdict):
985 """The actual test."""
986 self.assertEqual(errdict["errtype"], "BlockingSampleException")
987 self.assertEqual(app_name, APP_NAME)
988 d.callback("Ok")
989
990 self.patch(self.client, 'CredentialsFound',
991 lambda app, creds: d.errback(app))
992 self.patch(self.client, 'CredentialsNotFound', d.errback)
993 self.patch(self.client, 'CredentialsError', verify)
994
995 self.create_mock_backend().find_credentials()
996 self.mocker.result(defer.fail(BlockingSampleException()))
997 self.mocker.replay()
998
999 self.client.find_credentials(APP_NAME, self.args)
1000 return d
1001
1002
1003class CredentialsManagementClearTestCase(CredentialsManagementTestCase):
1004 """Tests for the CredentialsManagement clear method."""
1005
775 def test_clear_credentials(self):1006 def test_clear_credentials(self):
776 """The credentials are removed."""1007 """The credentials are removed."""
777 self.create_mock_backend().clear_credentials()1008 self.create_mock_backend().clear_credentials()
@@ -795,7 +1026,8 @@
795 d.callback(app_name)1026 d.callback(app_name)
7961027
797 self.patch(self.client, 'CredentialsCleared', verify)1028 self.patch(self.client, 'CredentialsCleared', verify)
798 self.patch(self.client, 'CredentialsError', d.errback)1029 self.patch(self.client, 'CredentialsError',
1030 lambda app, err: d.errback(app))
7991031
800 self.create_mock_backend().clear_credentials()1032 self.create_mock_backend().clear_credentials()
801 self.mocker.result(defer.succeed(APP_NAME))1033 self.mocker.result(defer.succeed(APP_NAME))
@@ -804,6 +1036,30 @@
804 self.client.clear_credentials(APP_NAME, self.args)1036 self.client.clear_credentials(APP_NAME, self.args)
805 return d1037 return d
8061038
1039 def test_clear_credentials_error(self):
1040 """If clear_credentials fails, CredentialsError is sent."""
1041 d = Deferred()
1042
1043 def verify(app_name, errdict):
1044 """The actual test."""
1045 self.assertEqual(errdict["errtype"], "BlockingSampleException")
1046 self.assertEqual(app_name, APP_NAME)
1047 d.callback("Ok")
1048
1049 self.patch(self.client, 'CredentialsCleared', d.errback)
1050 self.patch(self.client, 'CredentialsError', verify)
1051
1052 self.create_mock_backend().clear_credentials()
1053 self.mocker.result(defer.fail(BlockingSampleException()))
1054 self.mocker.replay()
1055
1056 self.client.clear_credentials(APP_NAME, self.args)
1057 return d
1058
1059
1060class CredentialsManagementStoreTestCase(CredentialsManagementTestCase):
1061 """Tests for the CredentialsManagement store method."""
1062
807 def test_store_credentials(self):1063 def test_store_credentials(self):
808 """The credentials are stored and the outcome is a signal."""1064 """The credentials are stored and the outcome is a signal."""
809 self.create_mock_backend().store_credentials(TOKEN)1065 self.create_mock_backend().store_credentials(TOKEN)
@@ -831,7 +1087,8 @@
831 d.callback(app_name)1087 d.callback(app_name)
8321088
833 self.patch(self.client, 'CredentialsStored', verify)1089 self.patch(self.client, 'CredentialsStored', verify)
834 self.patch(self.client, 'CredentialsError', d.errback)1090 self.patch(self.client, 'CredentialsError',
1091 lambda app, err: d.errback(app))
8351092
836 self.create_mock_backend().store_credentials(TOKEN)1093 self.create_mock_backend().store_credentials(TOKEN)
837 self.mocker.result(defer.succeed(APP_NAME))1094 self.mocker.result(defer.succeed(APP_NAME))
@@ -840,6 +1097,26 @@
840 self.client.store_credentials(APP_NAME, TOKEN)1097 self.client.store_credentials(APP_NAME, TOKEN)
841 return d1098 return d
8421099
1100 def test_store_credentials_error(self):
1101 """If store_credentials fails, CredentialsError is sent."""
1102 d = Deferred()
1103
1104 def verify(app_name, errdict):
1105 """The actual test."""
1106 self.assertEqual(errdict["errtype"], "BlockingSampleException")
1107 self.assertEqual(app_name, APP_NAME)
1108 d.callback("Ok")
1109
1110 self.patch(self.client, 'CredentialsStored', d.errback)
1111 self.patch(self.client, 'CredentialsError', verify)
1112
1113 self.create_mock_backend().store_credentials(TOKEN)
1114 self.mocker.result(defer.fail(BlockingSampleException()))
1115 self.mocker.replay()
1116
1117 self.client.store_credentials(APP_NAME, TOKEN)
1118 return d
1119
8431120
844class CredentialsManagementOpsTestCase(CredentialsManagementTestCase):1121class CredentialsManagementOpsTestCase(CredentialsManagementTestCase):
845 """Tests for the CredentialsManagement login/register methods."""1122 """Tests for the CredentialsManagement login/register methods."""
@@ -881,7 +1158,8 @@
881 """Tests for the CredentialsManagement DBus signals."""1158 """Tests for the CredentialsManagement DBus signals."""
8821159
883 def setUp(self):1160 def setUp(self):
884 self.client = CredentialsManagement()1161 self.client = CredentialsManagement(timeout_func=lambda *a: None,
1162 shutdown_func=lambda *a: None)
8851163
886 self.memento = MementoHandler()1164 self.memento = MementoHandler()
887 self.memento.setLevel(logging.DEBUG)1165 self.memento.setLevel(logging.DEBUG)

Subscribers

People subscribed via source and target branches