Merge lp:~nataliabidart/ubuntu/natty/ubuntu-sso-client/ubuntu-sso-client-1.1.8 into lp:ubuntu/natty/ubuntu-sso-client
- Natty (11.04)
- ubuntu-sso-client-1.1.8
- Merge into natty
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu branches | Pending | ||
Review via email: mp+46049@code.launchpad.net |
Commit message
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,
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
1 | === modified file 'PKG-INFO' |
2 | --- PKG-INFO 2010-12-16 16:31:34 +0000 |
3 | +++ PKG-INFO 2011-01-13 00:33:10 +0000 |
4 | @@ -1,6 +1,6 @@ |
5 | Metadata-Version: 1.1 |
6 | Name: ubuntu-sso-client |
7 | -Version: 1.1.7 |
8 | +Version: 1.1.8 |
9 | Summary: Ubuntu Single Sign-On client |
10 | Home-page: https://launchpad.net/ubuntu-sso-client |
11 | Author: Natalia Bidart |
12 | |
13 | === modified file 'bin/ubuntu-sso-login' |
14 | --- bin/ubuntu-sso-login 2010-10-11 13:22:16 +0000 |
15 | +++ bin/ubuntu-sso-login 2011-01-13 00:33:10 +0000 |
16 | @@ -21,7 +21,9 @@ |
17 | |
18 | """Run the dbus service for UserManagement and ApplicationCredentials.""" |
19 | |
20 | -# import decimal even if we don't need it. |
21 | +# Invalid name "ubuntu-sso-login", pylint: disable=C0103 |
22 | + |
23 | +# import decimal even if we don't need it, pylint: disable=W0611 |
24 | import decimal |
25 | # This is a workaround for LP: #467397. Some module in our depency chain sets |
26 | # the locale and imports decimal, and that generates the following trace: |
27 | @@ -70,8 +72,8 @@ |
28 | # See the link below for info: |
29 | # www.listware.net/201004/gtk-devel-list/115067-unix-signals-in-glib.html |
30 | # |
31 | - # gtk.main_quit and the logger methods are safe to be called from any thread. |
32 | - # Just don't call other random stuff here. |
33 | + # gtk.main_quit and the logger methods are safe to be called from any |
34 | + # thread. Just don't call other random stuff here. |
35 | logger.info("Stoping Ubuntu SSO login manager since SIGHUP was received.") |
36 | gtk.main_quit() |
37 | |
38 | @@ -92,6 +94,8 @@ |
39 | bus_name = dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus()) |
40 | SSOLogin(bus_name, object_path=DBUS_ACCOUNT_PATH) |
41 | SSOCredentials(bus_name, object_path=DBUS_CRED_PATH) |
42 | - CredentialsManagement(bus_name, object_path=DBUS_CREDENTIALS_PATH) |
43 | + CredentialsManagement(timeout_func=gtk.timeout_add, |
44 | + shutdown_func=gtk.main_quit, |
45 | + bus_name=bus_name, object_path=DBUS_CREDENTIALS_PATH) |
46 | |
47 | gtk.main() |
48 | |
49 | === removed directory 'contrib' |
50 | === removed file 'contrib/__init__.py' |
51 | --- contrib/__init__.py 2010-06-22 14:18:04 +0000 |
52 | +++ contrib/__init__.py 1970-01-01 00:00:00 +0000 |
53 | @@ -1,18 +0,0 @@ |
54 | -# contrib - Extra required code to build/install the client |
55 | -# |
56 | -# Author: Rodney Dawes <rodney.dawes@canonical.com> |
57 | -# |
58 | -# Copyright 2009-2010 Canonical Ltd. |
59 | -# |
60 | -# This program is free software: you can redistribute it and/or modify it |
61 | -# under the terms of the GNU General Public License version 3, as published |
62 | -# by the Free Software Foundation. |
63 | -# |
64 | -# This program is distributed in the hope that it will be useful, but |
65 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
66 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
67 | -# PURPOSE. See the GNU General Public License for more details. |
68 | -# |
69 | -# You should have received a copy of the GNU General Public License along |
70 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
71 | -"""Extra things we need to build, test, or install the client.""" |
72 | |
73 | === removed file 'contrib/dbus_util.py' |
74 | --- contrib/dbus_util.py 2010-09-08 19:25:02 +0000 |
75 | +++ contrib/dbus_util.py 1970-01-01 00:00:00 +0000 |
76 | @@ -1,77 +0,0 @@ |
77 | -# |
78 | -# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com> |
79 | -# |
80 | -# |
81 | -# This program is free software: you can redistribute it and/or modify it |
82 | -# under the terms of the GNU General Public License version 3, as published |
83 | -# by the Free Software Foundation. |
84 | -# |
85 | -# This program is distributed in the hope that it will be useful, but |
86 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
87 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
88 | -# PURPOSE. See the GNU General Public License for more details. |
89 | -# |
90 | -# You should have received a copy of the GNU General Public License along |
91 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
92 | - |
93 | -"""Utilies to run a separated DBus session.""" |
94 | - |
95 | -import os |
96 | -import signal |
97 | -import subprocess |
98 | - |
99 | -from distutils.spawn import find_executable |
100 | - |
101 | -SRCDIR = os.environ.get('SRCDIR', os.getcwd()) |
102 | - |
103 | - |
104 | -class DBusLaunchError(Exception): |
105 | - """Error while launching dbus-daemon.""" |
106 | - |
107 | - |
108 | -class NotFoundError(Exception): |
109 | - """Not found error.""" |
110 | - |
111 | - |
112 | -class DBusRunner(object): |
113 | - """A DBus runner.""" |
114 | - |
115 | - # pylint: disable=C0103 |
116 | - |
117 | - def __init__(self): |
118 | - self.dbus_address = None |
119 | - self.dbus_pid = None |
120 | - self.running = False |
121 | - |
122 | - def startDBus(self): |
123 | - """Start our own session bus daemon for testing.""" |
124 | - dbus = find_executable("dbus-daemon") |
125 | - if not dbus: |
126 | - raise NotFoundError("dbus-daemon was not found.") |
127 | - |
128 | - config_file = os.path.join(os.path.abspath(SRCDIR), |
129 | - "contrib", "testing", |
130 | - "dbus-session.conf") |
131 | - dbus_args = ["--fork", |
132 | - "--config-file=" + config_file, |
133 | - "--print-address=1", |
134 | - "--print-pid=2"] |
135 | - p = subprocess.Popen([dbus] + dbus_args, |
136 | - bufsize=4096, stdout=subprocess.PIPE, |
137 | - stderr=subprocess.PIPE) |
138 | - |
139 | - self.dbus_address = "".join(p.stdout.readlines()).strip() |
140 | - self.dbus_pid = int("".join(p.stderr.readlines()).strip()) |
141 | - |
142 | - if self.dbus_address != "": |
143 | - os.environ["DBUS_SESSION_BUS_ADDRESS"] = self.dbus_address |
144 | - else: |
145 | - os.kill(self.dbus_pid, signal.SIGKILL) |
146 | - raise DBusLaunchError("There was a problem launching dbus-daemon.") |
147 | - self.running = True |
148 | - |
149 | - def stopDBus(self): |
150 | - """Stop our DBus session bus daemon.""" |
151 | - del os.environ["DBUS_SESSION_BUS_ADDRESS"] |
152 | - os.kill(self.dbus_pid, signal.SIGKILL) |
153 | - self.running = False |
154 | |
155 | === removed file 'contrib/test' |
156 | --- contrib/test 2010-09-08 19:25:02 +0000 |
157 | +++ contrib/test 1970-01-01 00:00:00 +0000 |
158 | @@ -1,146 +0,0 @@ |
159 | -#!/usr/bin/env python |
160 | -# |
161 | -# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com> |
162 | -# |
163 | -# Copyright 2009-2010 Canonical Ltd. |
164 | -# |
165 | -# This program is free software: you can redistribute it and/or modify it |
166 | -# under the terms of the GNU General Public License version 3, as published |
167 | -# by the Free Software Foundation. |
168 | -# |
169 | -# This program is distributed in the hope that it will be useful, but |
170 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
171 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
172 | -# PURPOSE. See the GNU General Public License for more details. |
173 | -# |
174 | -# You should have received a copy of the GNU General Public License along |
175 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
176 | - |
177 | -import os |
178 | -import re |
179 | -import signal |
180 | -import sys |
181 | -import string |
182 | -import subprocess |
183 | -import unittest |
184 | - |
185 | -sys.path.insert(0, os.path.abspath(".")) |
186 | - |
187 | -from distutils.spawn import find_executable |
188 | - |
189 | - |
190 | -class TestRunner(object): |
191 | - |
192 | - def _load_unittest(self, relpath): |
193 | - """Load unittests from a Python module with the given relative path.""" |
194 | - assert relpath.endswith(".py"), ( |
195 | - "%s does not appear to be a Python module" % relpath) |
196 | - modpath = relpath.replace(os.path.sep, ".")[:-3] |
197 | - module = __import__(modpath, None, None, [""]) |
198 | - |
199 | - # If the module has a 'suite' or 'test_suite' function, use that |
200 | - # to load the tests. |
201 | - if hasattr(module, "suite"): |
202 | - return module.suite() |
203 | - elif hasattr(module, "test_suite"): |
204 | - return module.test_suite() |
205 | - else: |
206 | - return unittest.defaultTestLoader.loadTestsFromModule(module) |
207 | - |
208 | - def _collect_tests(self, testpath, test_pattern): |
209 | - """Return the set of unittests.""" |
210 | - suite = unittest.TestSuite() |
211 | - if test_pattern: |
212 | - pattern = re.compile('.*%s.*' % test_pattern) |
213 | - else: |
214 | - pattern = None |
215 | - |
216 | - if testpath: |
217 | - module_suite = self._load_unittest(testpath) |
218 | - if pattern: |
219 | - for inner_suite in module_suite._tests: |
220 | - for test in inner_suite._tests: |
221 | - if pattern.match(test.id()): |
222 | - suite.addTest(test) |
223 | - else: |
224 | - suite.addTests(module_suite) |
225 | - return suite |
226 | - |
227 | - # We don't use the dirs variable, so ignore the warning |
228 | - # pylint: disable=W0612 |
229 | - for root, dirs, files in os.walk("ubuntu_sso"): |
230 | - for file in files: |
231 | - path = os.path.join(root, file) |
232 | - if file.endswith(".py") and file.startswith("test_"): |
233 | - module_suite = self._load_unittest(path) |
234 | - if pattern: |
235 | - for inner_suite in module_suite._tests: |
236 | - for test in inner_suite._tests: |
237 | - if pattern.match(test.id()): |
238 | - suite.addTest(test) |
239 | - else: |
240 | - suite.addTests(module_suite) |
241 | - return suite |
242 | - |
243 | - def run(self, testpath, test_pattern=None, loops=None): |
244 | - """run the tests. """ |
245 | - # install the glib2reactor before any import of the reactor to avoid |
246 | - # using the default SelectReactor and be able to run the dbus tests |
247 | - from twisted.internet import glib2reactor |
248 | - glib2reactor.install() |
249 | - from twisted.internet import reactor |
250 | - from twisted.trial.reporter import TreeReporter |
251 | - from twisted.trial.runner import TrialRunner |
252 | - |
253 | - from contrib.dbus_util import DBusRunner |
254 | - dbus_runner = DBusRunner() |
255 | - dbus_runner.startDBus() |
256 | - |
257 | - workingDirectory = os.path.join(os.getcwd(), "_trial_temp", "tmp") |
258 | - runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True, |
259 | - workingDirectory=workingDirectory) |
260 | - |
261 | - # setup a custom XDG_CACHE_HOME and create the logs directory |
262 | - xdg_cache = os.path.join(os.getcwd(), "_trial_temp", "xdg_cache") |
263 | - os.environ["XDG_CACHE_HOME"] = xdg_cache |
264 | - # setup the ROOTDIR env var |
265 | - os.environ['ROOTDIR'] = os.getcwd() |
266 | - if not os.path.exists(xdg_cache): |
267 | - os.makedirs(xdg_cache) |
268 | - success = 0 |
269 | - try: |
270 | - suite = self._collect_tests(testpath, test_pattern) |
271 | - if loops: |
272 | - old_suite = suite |
273 | - suite = unittest.TestSuite() |
274 | - for x in xrange(loops): |
275 | - suite.addTest(old_suite) |
276 | - result = runner.run(suite) |
277 | - success = result.wasSuccessful() |
278 | - finally: |
279 | - dbus_runner.stopDBus() |
280 | - if not success: |
281 | - sys.exit(1) |
282 | - else: |
283 | - sys.exit(0) |
284 | - |
285 | - |
286 | -if __name__ == '__main__': |
287 | - from optparse import OptionParser |
288 | - usage = '%prog [options] path' |
289 | - parser = OptionParser(usage=usage) |
290 | - parser.add_option("-t", "--test", dest="test", |
291 | - help="run specific tests, e.g: className.methodName") |
292 | - parser.add_option("-l", "--loop", dest="loops", type="int", default=1, |
293 | - help="loop selected tests LOOPS number of times", |
294 | - metavar="LOOPS") |
295 | - |
296 | - (options, args) = parser.parse_args() |
297 | - if args: |
298 | - testpath = args[0] |
299 | - if not os.path.exists(testpath): |
300 | - print "the path to test does not exists!" |
301 | - sys.exit() |
302 | - else: |
303 | - testpath = None |
304 | - TestRunner().run(testpath, options.test, options.loops) |
305 | |
306 | === removed directory 'contrib/testing' |
307 | === removed file 'contrib/testing/__init__.py' |
308 | --- contrib/testing/__init__.py 2010-09-08 19:25:02 +0000 |
309 | +++ contrib/testing/__init__.py 1970-01-01 00:00:00 +0000 |
310 | @@ -1,1 +0,0 @@ |
311 | -"""Testing utilities for Ubuntu SSO code.""" |
312 | |
313 | === removed file 'contrib/testing/dbus-session.conf' |
314 | --- contrib/testing/dbus-session.conf 2010-06-18 20:51:58 +0000 |
315 | +++ contrib/testing/dbus-session.conf 1970-01-01 00:00:00 +0000 |
316 | @@ -1,63 +0,0 @@ |
317 | -<!-- This configuration file controls our test-only session bus --> |
318 | - |
319 | -<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN" |
320 | - "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> |
321 | -<busconfig> |
322 | - <!-- We only use a session bus --> |
323 | - <type>session</type> |
324 | - |
325 | - <listen>unix:tmpdir=/tmp</listen> |
326 | - |
327 | - <!-- Load our own services. |
328 | - To make other dbus service in this session bus, just add another servicedir entry. --> |
329 | - <servicedir>dbus-session</servicedir> |
330 | - <!-- Load the standard session services --> |
331 | - <!--standard_session_servicedirs /--> |
332 | - |
333 | - <policy context="default"> |
334 | - <!-- Allow everything to be sent --> |
335 | - <allow send_destination="*" eavesdrop="true"/> |
336 | - <!-- Allow everything to be received --> |
337 | - <allow eavesdrop="true"/> |
338 | - <!-- Allow anyone to own anything --> |
339 | - <allow own="*"/> |
340 | - </policy> |
341 | - |
342 | - <!-- Config files are placed here that among other things, |
343 | - further restrict the above policy for specific services. --> |
344 | - <includedir>/etc/dbus-1/session.d</includedir> |
345 | - |
346 | - <!-- raise the service start timeout to 40 seconds as it can timeout |
347 | - on the live cd on slow machines --> |
348 | - <limit name="service_start_timeout">60000</limit> |
349 | - |
350 | - <!-- This is included last so local configuration can override what's |
351 | - in this standard file --> |
352 | - <include ignore_missing="yes">session-local.conf</include> |
353 | - |
354 | - <include ignore_missing="yes" if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include> |
355 | - |
356 | - <!-- For the session bus, override the default relatively-low limits |
357 | - with essentially infinite limits, since the bus is just running |
358 | - as the user anyway, using up bus resources is not something we need |
359 | - to worry about. In some cases, we do set the limits lower than |
360 | - "all available memory" if exceeding the limit is almost certainly a bug, |
361 | - having the bus enforce a limit is nicer than a huge memory leak. But the |
362 | - intent is that these limits should never be hit. --> |
363 | - |
364 | - <!-- the memory limits are 1G instead of say 4G because they can't exceed 32-bit signed int max --> |
365 | - <limit name="max_incoming_bytes">1000000000</limit> |
366 | - <limit name="max_outgoing_bytes">1000000000</limit> |
367 | - <limit name="max_message_size">1000000000</limit> |
368 | - <limit name="service_start_timeout">120000</limit> |
369 | - <limit name="auth_timeout">240000</limit> |
370 | - <limit name="max_completed_connections">100000</limit> |
371 | - <limit name="max_incomplete_connections">10000</limit> |
372 | - <limit name="max_connections_per_user">100000</limit> |
373 | - <limit name="max_pending_service_starts">10000</limit> |
374 | - <limit name="max_names_per_connection">50000</limit> |
375 | - <limit name="max_match_rules_per_connection">50000</limit> |
376 | - <limit name="max_replies_per_connection">50000</limit> |
377 | - <limit name="reply_timeout">300000</limit> |
378 | - |
379 | -</busconfig> |
380 | |
381 | === removed file 'contrib/testing/testcase.py' |
382 | --- contrib/testing/testcase.py 2010-10-11 13:22:16 +0000 |
383 | +++ contrib/testing/testcase.py 1970-01-01 00:00:00 +0000 |
384 | @@ -1,123 +0,0 @@ |
385 | -# |
386 | -# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com> |
387 | -# Author: Natalia B. Bidart <natalia.bidart@canonical.com> |
388 | -# |
389 | -# Copyright 2009-2010 Canonical Ltd. |
390 | -# |
391 | -# This program is free software: you can redistribute it and/or modify it |
392 | -# under the terms of the GNU General Public License version 3, as published |
393 | -# by the Free Software Foundation. |
394 | -# |
395 | -# This program is distributed in the hope that it will be useful, but |
396 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
397 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
398 | -# PURPOSE. See the GNU General Public License for more details. |
399 | -# |
400 | -# You should have received a copy of the GNU General Public License along |
401 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
402 | -"""Base tests cases and test utilities.""" |
403 | - |
404 | -import logging |
405 | - |
406 | -import dbus |
407 | - |
408 | -from dbus.mainloop.glib import DBusGMainLoop |
409 | -from twisted.internet import defer |
410 | -from twisted.python import failure |
411 | -from twisted.trial.unittest import TestCase |
412 | - |
413 | - |
414 | -class DBusTestCase(TestCase): |
415 | - """Test the DBus event handling.""" |
416 | - |
417 | - def setUp(self): |
418 | - """Setup the infrastructure fo the test (dbus service).""" |
419 | - self.loop = DBusGMainLoop(set_as_default=True) |
420 | - self.bus = dbus.bus.BusConnection(mainloop=self.loop) |
421 | - # monkeypatch busName.__del__ to avoid errors on gc |
422 | - # we take care of releasing the name in shutdown |
423 | - dbus.service.BusName.__del__ = lambda _: None |
424 | - self.bus.set_exit_on_disconnect(False) |
425 | - self.signal_receivers = set() |
426 | - |
427 | - def tearDown(self): |
428 | - """Cleanup the test.""" |
429 | - d = self.cleanup_signal_receivers(self.signal_receivers) |
430 | - d.addBoth(self._tear_down) |
431 | - return d |
432 | - |
433 | - def _tear_down(self, _): |
434 | - """Shutdown.""" |
435 | - self.bus.flush() |
436 | - self.bus.close() |
437 | - |
438 | - def error_handler(self, error): |
439 | - """Default error handler for DBus calls.""" |
440 | - if isinstance(error, failure.Failure): |
441 | - self.fail(error.getErrorMessage()) |
442 | - |
443 | - def cleanup_signal_receivers(self, signal_receivers): |
444 | - """Cleanup self.signal_receivers and returns a deferred.""" |
445 | - deferreds = [] |
446 | - for match in signal_receivers: |
447 | - d = defer.Deferred() |
448 | - |
449 | - def callback(*args): |
450 | - """Callback that accepts *args.""" |
451 | - if not d.called: |
452 | - d.callback(args) |
453 | - |
454 | - self.bus.call_async(dbus.bus.BUS_DAEMON_NAME, |
455 | - dbus.bus.BUS_DAEMON_PATH, |
456 | - dbus.bus.BUS_DAEMON_IFACE, 'RemoveMatch', 's', |
457 | - (str(match),), callback, self.error_handler) |
458 | - deferreds.append(d) |
459 | - if deferreds: |
460 | - return defer.DeferredList(deferreds) |
461 | - else: |
462 | - return defer.succeed(True) |
463 | - |
464 | - |
465 | -class MementoHandler(logging.Handler): |
466 | - """A handler class which store logging records in a list.""" |
467 | - |
468 | - def __init__(self, *args, **kwargs): |
469 | - """Create the instance, and add a records attribute.""" |
470 | - logging.Handler.__init__(self, *args, **kwargs) |
471 | - self.records = [] |
472 | - |
473 | - def emit(self, record): |
474 | - """Just add the record to self.records.""" |
475 | - self.records.append(record) |
476 | - |
477 | - def check(self, level, *msgs): |
478 | - """Verifies that the msgs are logged in the specified level.""" |
479 | - for rec in self.records: |
480 | - if rec.levelno == level and all(m in rec.message for m in msgs): |
481 | - return True |
482 | - return False |
483 | - |
484 | - def check_debug(self, *msgs): |
485 | - """Shortcut for checking in DEBUG.""" |
486 | - return self.check(logging.DEBUG, *msgs) |
487 | - |
488 | - def check_info(self, *msgs): |
489 | - """Shortcut for checking in INFO.""" |
490 | - return self.check(logging.INFO, *msgs) |
491 | - |
492 | - def check_warning(self, *msgs): |
493 | - """Shortcut for checking in WARNING.""" |
494 | - return self.check(logging.WARNING, *msgs) |
495 | - |
496 | - def check_error(self, *msgs): |
497 | - """Shortcut for checking in ERROR.""" |
498 | - return self.check(logging.ERROR, *msgs) |
499 | - |
500 | - def check_exception(self, exception_class, *msgs): |
501 | - """Shortcut for checking exceptions.""" |
502 | - for rec in self.records: |
503 | - if rec.levelno == logging.ERROR and \ |
504 | - all(m in rec.exc_text for m in msgs) and \ |
505 | - exception_class == rec.exc_info[0]: |
506 | - return True |
507 | - return False |
508 | |
509 | === modified file 'debian/changelog' |
510 | --- debian/changelog 2010-12-17 20:25:51 +0000 |
511 | +++ debian/changelog 2011-01-13 00:33:10 +0000 |
512 | @@ -1,3 +1,21 @@ |
513 | +ubuntu-sso-client (1.1.8-0ubuntu1) UNRELEASED; urgency=low |
514 | + |
515 | + * New upstream release: |
516 | + |
517 | + [ Natalia B. Bidart <natalia.bidart@canonical.com>] |
518 | + - The service should shutdown when unused |
519 | + (LP: #701606), |
520 | + - On error, {find,clear,store}_credentials send proper CredentialsError |
521 | + signal (LP: #696676). |
522 | + - No more gobject dependency on non-GUI classes! |
523 | + (LP: #695798). |
524 | + - After login, if the storing of credentials fails, send LoginError |
525 | + (LP: #693531). |
526 | + - Use ubuntuone-dev-tools to run the tests and the lint checker |
527 | + (LP: #686606). |
528 | + |
529 | + -- Natalia Bidart (nessita) <nataliabidart@gmail.com> Wed, 12 Jan 2011 15:59:23 -0300 |
530 | + |
531 | ubuntu-sso-client (1.1.7-0ubuntu1) natty; urgency=low |
532 | |
533 | * New upstream release. |
534 | |
535 | === modified file 'run-tests' |
536 | --- run-tests 2010-10-11 13:22:16 +0000 |
537 | +++ run-tests 2011-01-13 00:33:10 +0000 |
538 | @@ -15,14 +15,23 @@ |
539 | # You should have received a copy of the GNU General Public License along |
540 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
541 | |
542 | +if [ $# -ne 0 ]; then |
543 | + # an extra argument was given |
544 | + MODULE="$@" |
545 | +else |
546 | + # run all tests, useful for tarmac and reviews |
547 | + MODULE="ubuntu_sso" |
548 | +fi |
549 | + |
550 | style_check() { |
551 | - pylint contrib ubuntu_sso |
552 | + u1lint |
553 | if [ -x `which pep8` ]; then |
554 | - pep8 --repeat bin/ contrib/ ubuntu_sso/ |
555 | + pep8 --repeat bin/ $MODULE |
556 | else |
557 | echo "Please install the 'pep8' package." |
558 | fi |
559 | } |
560 | |
561 | -`which xvfb-run` ./contrib/test "$@" && style_check |
562 | -rm -rf _trial_temp/ |
563 | +echo "Running test suite for ""$MODULE" |
564 | +`which xvfb-run` u1trial "$MODULE" && style_check |
565 | +rm -rf _trial_temp |
566 | |
567 | === modified file 'setup.py' |
568 | --- setup.py 2010-12-16 16:31:34 +0000 |
569 | +++ setup.py 2011-01-13 00:33:10 +0000 |
570 | @@ -31,16 +31,15 @@ |
571 | assert DistUtilsExtra.auto.__version__ >= '2.18', \ |
572 | 'needs DistUtilsExtra.auto >= 2.18' |
573 | |
574 | -from distutils.core import setup |
575 | from distutils.spawn import find_executable |
576 | -from distutils.command import clean, build |
577 | +from distutils.command import clean |
578 | |
579 | # Defining variables for various rules here, similar to a Makefile.am |
580 | CLEANFILES = ['data/com.ubuntu.sso.service', 'po/ubuntu-sso-client.pot', |
581 | 'MANIFEST'] |
582 | |
583 | |
584 | -# XXX: This needs some serious cleanup |
585 | +# This needs some serious cleanup |
586 | class SSOBuild(build_extra.build_extra): |
587 | """Class to build the extra files.""" |
588 | |
589 | @@ -87,7 +86,7 @@ |
590 | |
591 | DistUtilsExtra.auto.setup( |
592 | name='ubuntu-sso-client', |
593 | - version='1.1.7', |
594 | + version='1.1.8', |
595 | license='GPL v3', |
596 | author='Natalia Bidart', |
597 | author_email='natalia.bidart@canonical.com', |
598 | |
599 | === modified file 'ubuntu_sso/credentials.py' |
600 | --- ubuntu_sso/credentials.py 2010-11-30 13:21:17 +0000 |
601 | +++ ubuntu_sso/credentials.py 2011-01-13 00:33:10 +0000 |
602 | @@ -22,10 +22,12 @@ |
603 | |
604 | * find_credentials |
605 | * clear_credentials |
606 | + * store_credentials |
607 | * register |
608 | * login |
609 | |
610 | -The first two returns immediately (for now). |
611 | +The first three return a Deferred that will be fired when the operation was |
612 | +completed. |
613 | |
614 | The second two use the 'success_cb', 'error_cb' and 'denial_cb' to signal the |
615 | caller when the credentials were retrieved successfully, when there was an |
616 | @@ -41,8 +43,6 @@ |
617 | |
618 | from functools import wraps |
619 | |
620 | -import gobject |
621 | - |
622 | from oauth import oauth |
623 | from twisted.internet.defer import inlineCallbacks, returnValue |
624 | |
625 | @@ -187,13 +187,15 @@ |
626 | |
627 | @handle_failures(msg='Problem while retrieving credentials') |
628 | @inlineCallbacks |
629 | - def _login_success_cb(self, dialog, app_name, email): |
630 | + def _login_success_cb(self, app_name, email): |
631 | """Store credentials when the login/registration succeeded. |
632 | |
633 | Also, open self.ping_url/email to notify about this new token. If any |
634 | error occur, self.error_cb is called. Otherwise, self.success_cb is |
635 | called. |
636 | |
637 | + Return 0 on success, and a non-zero value (or None) on error. |
638 | + |
639 | """ |
640 | logger.info('Login/registration was successful for app %r, email %r', |
641 | app_name, email) |
642 | @@ -202,19 +204,21 @@ |
643 | assert len(creds) > 0, 'Creds are empty! This should not happen' |
644 | # ping a server with the credentials if we were requested to |
645 | if self.ping_url is not None: |
646 | - status = self._ping_url(app_name, email, creds) |
647 | + status = yield self._ping_url(app_name, email, creds) |
648 | if status is None: |
649 | yield self.clear_credentials() |
650 | return |
651 | |
652 | self.success_cb(creds) |
653 | + returnValue(0) |
654 | |
655 | - def _auth_denial_cb(self, dialog, app_name): |
656 | + def _auth_denial_cb(self, app_name): |
657 | """The user decided not to allow the registration or login.""" |
658 | logger.warning('Login/registration was denied to app %r', app_name) |
659 | self.denial_cb(app_name) |
660 | |
661 | - @handle_exceptions(msg='Problem opening the ping_url') |
662 | + @handle_failures(msg='Problem opening the ping_url') |
663 | + @inlineCallbacks |
664 | def _ping_url(self, app_name, email, credentials): |
665 | """Ping the self.ping_url with the email attached. |
666 | |
667 | @@ -237,9 +241,12 @@ |
668 | request = urllib2.Request(url, headers=oauth_req.to_header()) |
669 | logger.debug('Opening the url "%s" with urllib2.urlopen.', |
670 | request.get_full_url()) |
671 | + # This code is blocking, we should change this. |
672 | + # I've tried with deferToThread an twisted.web.client.getPage |
673 | + # but the returned deferred will never be fired (nataliabidart). |
674 | response = urllib2.urlopen(request) |
675 | logger.debug('Url opened. Response: %s.', response.code) |
676 | - return response.code |
677 | + returnValue(response.code) |
678 | |
679 | @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface') |
680 | def _show_ui(self, login_only): |
681 | @@ -252,10 +259,9 @@ |
682 | tc_url=self.tc_url, help_text=self.help_text, |
683 | window_id=self.window_id, login_only=login_only) |
684 | |
685 | - self.gui.connect(gui.SIG_LOGIN_SUCCEEDED, self._login_success_cb) |
686 | - self.gui.connect(gui.SIG_REGISTRATION_SUCCEEDED, |
687 | - self._login_success_cb) |
688 | - self.gui.connect(gui.SIG_USER_CANCELATION, self._auth_denial_cb) |
689 | + self.gui.login_success_callback = self._login_success_cb |
690 | + self.gui.registration_success_callback = self._login_success_cb |
691 | + self.gui.user_cancellation_callback = self._auth_denial_cb |
692 | |
693 | @handle_failures(msg='Problem while retrieving credentials') |
694 | @inlineCallbacks |
695 | @@ -265,35 +271,23 @@ |
696 | if token is not None and len(token) > 0: |
697 | self.success_cb(token) |
698 | elif token == {}: |
699 | - gobject.idle_add(self._show_ui, login_only) |
700 | + self._show_ui(login_only) |
701 | else: |
702 | # something went wrong with find_credentials, already handled. |
703 | logger.info('_login_or_register: call to "find_credentials" went ' |
704 | 'wrong, and was already handled.') |
705 | |
706 | def error_cb(self, error_dict): |
707 | - """Handle error. |
708 | - |
709 | - Notify the caller and finish the GUI with error. |
710 | - |
711 | - """ |
712 | + """Handle error and notify the caller.""" |
713 | + logger.error('Calling error callback at %r (error is %r).', |
714 | + self._error_cb, error_dict) |
715 | self._error_cb(self.app_name, error_dict) |
716 | - if self.gui is not None: |
717 | - self.gui.finish_error(error=error_dict) |
718 | - self.gui = None |
719 | |
720 | def success_cb(self, creds): |
721 | - """Handle success. |
722 | - |
723 | - Notify the caller and finish the GUI with success. |
724 | - |
725 | - """ |
726 | + """Handle success and notify the caller.""" |
727 | + logger.debug('Calling success callback at %r.', self._success_cb) |
728 | self._success_cb(self.app_name, creds) |
729 | - if self.gui is not None: |
730 | - self.gui.finish_success() |
731 | - self.gui = None |
732 | |
733 | - @handle_failures(msg='Problem while retrieving credentials') |
734 | @inlineCallbacks |
735 | def find_credentials(self): |
736 | """Get the credentials for 'self.app_name'. Return {} if not there.""" |
737 | @@ -302,13 +296,11 @@ |
738 | 'result is {}? %s', self.app_name, creds is None) |
739 | returnValue(creds if creds is not None else {}) |
740 | |
741 | - @handle_failures(msg='Problem while deleting credentials') |
742 | @inlineCallbacks |
743 | def clear_credentials(self): |
744 | """Clear the credentials for 'self.app_name'.""" |
745 | yield Keyring().delete_credentials(self.app_name) |
746 | |
747 | - @handle_failures(msg='Problem while storing credentials') |
748 | @inlineCallbacks |
749 | def store_credentials(self, token): |
750 | """Store the credentials for 'self.app_name'.""" |
751 | |
752 | === modified file 'ubuntu_sso/gtk/gui.py' |
753 | --- ubuntu_sso/gtk/gui.py 2010-11-30 13:21:17 +0000 |
754 | +++ ubuntu_sso/gtk/gui.py 2011-01-13 00:33:10 +0000 |
755 | @@ -30,11 +30,11 @@ |
756 | |
757 | import dbus |
758 | import gettext |
759 | -import gobject |
760 | import gtk |
761 | import xdg |
762 | |
763 | from dbus.mainloop.glib import DBusGMainLoop |
764 | +from twisted.internet.defer import inlineCallbacks |
765 | |
766 | from ubuntu_sso import (DBUS_ACCOUNT_PATH, DBUS_BUS_NAME, DBUS_IFACE_USER_NAME, |
767 | NO_OP) |
768 | @@ -72,20 +72,6 @@ |
769 | HELP_TEXT_COLOR = gtk.gdk.Color("#bfbfbf") |
770 | WARNING_TEXT_COLOR = gtk.gdk.Color("red") |
771 | |
772 | -SIG_LOGIN_SUCCEEDED = 'login-succeeded' |
773 | -SIG_REGISTRATION_SUCCEEDED = 'registration-succeeded' |
774 | -SIG_USER_CANCELATION = 'user-cancelation' |
775 | - |
776 | -SIGNAL_ARGUMENTS = [ |
777 | - (SIG_LOGIN_SUCCEEDED, (gobject.TYPE_STRING, gobject.TYPE_STRING,)), |
778 | - (SIG_REGISTRATION_SUCCEEDED, (gobject.TYPE_STRING, gobject.TYPE_STRING,)), |
779 | - (SIG_USER_CANCELATION, (gobject.TYPE_STRING,)), |
780 | -] |
781 | - |
782 | -for sig, sig_args in SIGNAL_ARGUMENTS: |
783 | - gobject.signal_new(sig, gtk.Window, gobject.SIGNAL_RUN_FIRST, |
784 | - gobject.TYPE_NONE, sig_args) |
785 | - |
786 | |
787 | def get_data_dir(): |
788 | """Build absolute path to the 'data' directory.""" |
789 | @@ -198,7 +184,7 @@ |
790 | |
791 | |
792 | class UbuntuSSOClientGUI(object): |
793 | - """Ubuntu single sign on GUI.""" |
794 | + """Ubuntu single sign-on GUI.""" |
795 | |
796 | CAPTCHA_SOLUTION_ENTRY = _('Type the characters above') |
797 | CAPTCHA_LOAD_ERROR = _('There was a problem getting the captcha, ' |
798 | @@ -256,7 +242,7 @@ |
799 | CAPTCHA_RELOAD_TOOLTIP = _('Reload') |
800 | |
801 | def __init__(self, app_name, tc_url='', help_text='', |
802 | - window_id=0, login_only=False, close_callback=None): |
803 | + window_id=0, login_only=False): |
804 | """Create the GUI and initialize widgets.""" |
805 | gtk.link_button_set_uri_hook(NO_OP) |
806 | |
807 | @@ -270,7 +256,12 @@ |
808 | self.tc_url = tc_url |
809 | self.help_text = help_text |
810 | self.login_only = login_only |
811 | - self.close_callback = close_callback |
812 | + |
813 | + self.close_callback = NO_OP |
814 | + self.login_success_callback = NO_OP |
815 | + self.registration_success_callback = NO_OP |
816 | + self.user_cancellation_callback = NO_OP |
817 | + |
818 | self.user_email = None |
819 | self.user_password = None |
820 | |
821 | @@ -730,17 +721,12 @@ |
822 | signal_name, handler, args, kwargs) |
823 | self.window.connect(signal_name, handler, *args, **kwargs) |
824 | |
825 | - def emit(self, *args, **kwargs): |
826 | - """Emit a signal proxing the main window.""" |
827 | - logger.debug('emit: args %r, kwargs, %r', args, kwargs) |
828 | - self.window.emit(*args, **kwargs) |
829 | - |
830 | def finish_success(self): |
831 | """The whole process was completed succesfully. Show success page.""" |
832 | self._done = True |
833 | self._set_current_page(self.success_vbox) |
834 | |
835 | - def finish_error(self, error): |
836 | + def finish_error(self): |
837 | """The whole process was not completed succesfully. Show error page.""" |
838 | self._done = True |
839 | self._set_current_page(self.error_vbox) |
840 | @@ -766,18 +752,17 @@ |
841 | if self.window is not None: |
842 | self.window.hide() |
843 | |
844 | - # process any pending events before emitting signals |
845 | + # process any pending events before callbacking with result |
846 | while gtk.events_pending(): |
847 | gtk.main_iteration() |
848 | |
849 | if not self._done: |
850 | - self.emit(SIG_USER_CANCELATION, self.app_name) |
851 | + self.user_cancellation_callback(self.app_name) |
852 | |
853 | # call user defined callback |
854 | - if self.close_callback is not None: |
855 | - logger.info('Calling custom close_callback %r with params %r, %r', |
856 | - self.close_callback, args, kwargs) |
857 | - self.close_callback(*args, **kwargs) |
858 | + logger.info('Calling custom close_callback %r with params %r, %r', |
859 | + self.close_callback, args, kwargs) |
860 | + self.close_callback(*args, **kwargs) |
861 | |
862 | def on_sign_in_button_clicked(self, *args, **kwargs): |
863 | """User wants to sign in, present the Login page.""" |
864 | @@ -1096,10 +1081,15 @@ |
865 | self._set_current_page(self.enter_details_vbox, warning_text=msg) |
866 | |
867 | @log_call |
868 | + @inlineCallbacks |
869 | def on_email_validated(self, app_name, email, *args, **kwargs): |
870 | """User email was successfully verified.""" |
871 | self._done = True |
872 | - self.emit(SIG_REGISTRATION_SUCCEEDED, self.app_name, email) |
873 | + result = yield self.registration_success_callback(self.app_name, email) |
874 | + if result == 0: |
875 | + self.finish_success() |
876 | + else: |
877 | + self.finish_error() |
878 | |
879 | @log_call |
880 | def on_email_validation_error(self, app_name, error, *args, **kwargs): |
881 | @@ -1112,10 +1102,15 @@ |
882 | self._set_current_page(self.verify_email_vbox, warning_text=msg) |
883 | |
884 | @log_call |
885 | + @inlineCallbacks |
886 | def on_logged_in(self, app_name, email, *args, **kwargs): |
887 | """User was successfully logged in.""" |
888 | self._done = True |
889 | - self.emit(SIG_LOGIN_SUCCEEDED, self.app_name, email) |
890 | + result = yield self.login_success_callback(self.app_name, email) |
891 | + if result == 0: |
892 | + self.finish_success() |
893 | + else: |
894 | + self.finish_error() |
895 | |
896 | @log_call |
897 | def on_login_error(self, app_name, error, *args, **kwargs): |
898 | |
899 | === modified file 'ubuntu_sso/gtk/tests/test_gui.py' |
900 | --- ubuntu_sso/gtk/tests/test_gui.py 2010-11-30 13:21:17 +0000 |
901 | +++ ubuntu_sso/gtk/tests/test_gui.py 2011-01-13 00:33:10 +0000 |
902 | @@ -30,8 +30,8 @@ |
903 | import webkit |
904 | |
905 | from twisted.trial.unittest import TestCase |
906 | +from ubuntuone.devtools.handlers import MementoHandler |
907 | |
908 | -from contrib.testing.testcase import MementoHandler |
909 | from ubuntu_sso.gtk import gui |
910 | from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID, |
911 | CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, NAME, PASSWORD, RESET_PASSWORD_TOKEN) |
912 | @@ -517,6 +517,11 @@ |
913 | self.assertTrue(self._called, |
914 | 'close_callback was called when window was closed.') |
915 | |
916 | + def test_close_callback_if_not_set(self): |
917 | + """The close_callback is a no op if not set.""" |
918 | + self.ui.on_close_clicked() |
919 | + # no crash when close_callback is not set |
920 | + |
921 | def test_app_name_is_stored(self): |
922 | """The app_name is stored for further use.""" |
923 | self.assertIn(APP_NAME, self.ui.app_name) |
924 | @@ -555,18 +560,6 @@ |
925 | |
926 | self.assertEqual(self.ui.bus.callbacks, {}) |
927 | |
928 | - def test_close_callback(self): |
929 | - """A close_callback parameter is called when closing the window.""" |
930 | - ui = self.gui_class(close_callback=self._set_called, **self.kwargs) |
931 | - ui.on_close_clicked() |
932 | - self.assertTrue(self._called, 'close_callback was called on close.') |
933 | - |
934 | - def test_close_callback_if_none(self): |
935 | - """A close_callback parameter is not called if is None.""" |
936 | - ui = self.gui_class(close_callback=None, **self.kwargs) |
937 | - ui.on_close_clicked() |
938 | - # no crash when close_callback is None |
939 | - |
940 | def test_pages_are_packed_into_container(self): |
941 | """All the pages are packed in the main container.""" |
942 | children = self.ui.content.get_children() |
943 | @@ -620,8 +613,7 @@ |
944 | buttons = filter(lambda name: 'cancel_button' in name or |
945 | 'close_button' in name, self.ui.widgets) |
946 | for name in buttons: |
947 | - self.ui = self.gui_class(close_callback=self._set_called, |
948 | - **self.kwargs) |
949 | + self.ui.close_callback = self._set_called |
950 | widget = getattr(self.ui, name) |
951 | widget.clicked() |
952 | self.assertEqual(self._called, ((widget,), {}), msg % name) |
953 | @@ -661,7 +653,7 @@ |
954 | |
955 | def test_finish_error_shows_error_page(self): |
956 | """When calling 'finish_error' the error page is shown.""" |
957 | - self.ui.finish_error(error=self.error) |
958 | + self.ui.finish_error() |
959 | self.assert_pages_visibility(finish=True) |
960 | self.assertEqual(self.ui.ERROR, self.ui.finish_vbox.label.get_text()) |
961 | |
962 | @@ -1164,10 +1156,10 @@ |
963 | self.click_verify_email_with_valid_data() |
964 | self.assert_warnings_visibility() |
965 | |
966 | - def test_on_email_validated_shows_processing_page(self): |
967 | - """On email validated the procesing page is still shown.""" |
968 | + def test_on_email_validated_shows_finish_page(self): |
969 | + """On email validated the finish page is shown.""" |
970 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
971 | - self.assert_pages_visibility(processing=True) |
972 | + self.assert_pages_visibility(finish=True) |
973 | |
974 | def test_on_email_validated_does_not_clear_the_help_text(self): |
975 | """On email validated the help text is not removed.""" |
976 | @@ -1227,6 +1219,32 @@ |
977 | self.ui.verify_token_button.clicked() |
978 | self.assertTrue(self._called) |
979 | |
980 | + def test_after_registration_success_finish_success(self): |
981 | + """After REGISTRATION_SUCCESS is called, finish_success is called. |
982 | + |
983 | + Only when REGISTRATION_SUCCESS returns 0. |
984 | + |
985 | + """ |
986 | + self.patch(self.ui, 'finish_success', self._set_called) |
987 | + self.ui.registration_success_callback = lambda app, email: 0 |
988 | + |
989 | + self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
990 | + |
991 | + self.assertEqual(self._called, ((), {})) |
992 | + |
993 | + def test_after_registration_error_finish_error(self): |
994 | + """After REGISTRATION_SUCCESS is called, finish_error is called. |
995 | + |
996 | + Only when REGISTRATION_SUCCESS returns a non-zero value. |
997 | + |
998 | + """ |
999 | + self.patch(self.ui, 'finish_error', self._set_called) |
1000 | + self.ui.registration_success_callback = lambda app, email: -1 |
1001 | + |
1002 | + self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
1003 | + |
1004 | + self.assertEqual(self._called, ((), {})) |
1005 | + |
1006 | |
1007 | class VerifyEmailValidationTestCase(UbuntuSSOClientTestCase): |
1008 | """Test suite for the user registration validation (verify email page).""" |
1009 | @@ -1480,11 +1498,11 @@ |
1010 | self.click_connect_with_valid_data() |
1011 | self.assert_pages_visibility(processing=True) |
1012 | |
1013 | - def test_on_logged_in_morphs_to_processing_page(self): |
1014 | - """When user logged in the processing page is still shown.""" |
1015 | + def test_on_logged_in_morphs_to_finish_page(self): |
1016 | + """When user logged in the finish page is shown.""" |
1017 | self.click_connect_with_valid_data() |
1018 | self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
1019 | - self.assert_pages_visibility(processing=True) |
1020 | + self.assert_pages_visibility(finish=True) |
1021 | |
1022 | def test_on_login_error_morphs_to_login_page(self): |
1023 | """On user login error, the previous page is shown.""" |
1024 | @@ -1541,6 +1559,32 @@ |
1025 | self.assertEqual(self.ui.user_email, EMAIL) |
1026 | self.assertEqual(self.ui.user_password, PASSWORD) |
1027 | |
1028 | + def test_after_login_success_finish_success(self): |
1029 | + """After LOGIN_SUCCESSFULL is called, finish_success is called. |
1030 | + |
1031 | + Only when LOGIN_SUCCESSFULL returns 0. |
1032 | + |
1033 | + """ |
1034 | + self.patch(self.ui, 'finish_success', self._set_called) |
1035 | + self.ui.login_success_callback = lambda app, email: 0 |
1036 | + |
1037 | + self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
1038 | + |
1039 | + self.assertEqual(self._called, ((), {})) |
1040 | + |
1041 | + def test_after_login_error_finish_error(self): |
1042 | + """After LOGIN_SUCCESSFULL is called, finish_error is called. |
1043 | + |
1044 | + Only when LOGIN_SUCCESSFULL returns a non-zero value. |
1045 | + |
1046 | + """ |
1047 | + self.patch(self.ui, 'finish_error', self._set_called) |
1048 | + self.ui.login_success_callback = lambda app, email: -1 |
1049 | + |
1050 | + self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
1051 | + |
1052 | + self.assertEqual(self._called, ((), {})) |
1053 | + |
1054 | |
1055 | class LoginValidationTestCase(UbuntuSSOClientTestCase): |
1056 | """Test suite for the user login validation.""" |
1057 | @@ -2060,119 +2104,144 @@ |
1058 | self.assertEqual(self.ui.help_label.get_text(), HELP_TEXT) |
1059 | |
1060 | |
1061 | -class SignalsTestCase(UbuntuSSOClientTestCase): |
1062 | - """Test the GTK signal emission.""" |
1063 | +class CallbacksTestCase(UbuntuSSOClientTestCase): |
1064 | + """Test the GTK callback calls.""" |
1065 | + |
1066 | + LOGIN_SUCCESSFULL = 'login_success_callback' |
1067 | + REGISTRATION_SUCCESS = 'registration_success_callback' |
1068 | + USER_CANCELLATION = 'user_cancellation_callback' |
1069 | |
1070 | def setUp(self): |
1071 | """Init.""" |
1072 | - super(SignalsTestCase, self).setUp() |
1073 | + super(CallbacksTestCase, self).setUp() |
1074 | self._called = {} |
1075 | - for sig_name, _ in gui.SIGNAL_ARGUMENTS: |
1076 | - self.ui.connect(sig_name, self._set_called, sig_name) |
1077 | - |
1078 | - def _set_called(self, widget, *args, **kwargs): |
1079 | - """Keep trace of signals emition.""" |
1080 | + for name in (self.LOGIN_SUCCESSFULL, |
1081 | + self.REGISTRATION_SUCCESS, |
1082 | + self.USER_CANCELLATION): |
1083 | + setattr(self.ui, name, self._set_called(name)) |
1084 | + |
1085 | + def _set_called(self, callback_name): |
1086 | + """Keep trace of callbacks calls.""" |
1087 | + |
1088 | # pylint: disable=W0221 |
1089 | - self._called[args[-1]] = (widget, args[:-1], kwargs) |
1090 | - |
1091 | - def test_closing_main_window_sends_outcome_as_signal(self): |
1092 | - """A signal is sent when closing the main window.""" |
1093 | + |
1094 | + def inner(*args, **kwargs): |
1095 | + """Store arguments used in this call.""" |
1096 | + self._called[callback_name] = args |
1097 | + |
1098 | + return inner |
1099 | + |
1100 | + def test_closing_main_window(self): |
1101 | + """When closing the main window, USER_CANCELLATION is called.""" |
1102 | self.ui.window.emit('delete-event', gtk.gdk.Event(gtk.gdk.DELETE)) |
1103 | - expected = (self.ui.window, (APP_NAME,), {}) |
1104 | - self.assertEqual(self._called[gui.SIG_USER_CANCELATION], expected) |
1105 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
1106 | |
1107 | - def test_every_cancel_emits_proper_signal(self): |
1108 | - """Clicking on any cancel button, 'user-cancelation' signal is sent.""" |
1109 | - sig = gui.SIG_USER_CANCELATION |
1110 | - msg = 'user-cancelation is emitted when "%s" is clicked.' |
1111 | + def test_every_cancel_calls_proper_callback(self): |
1112 | + """When any cancel button is clicked, USER_CANCELLATION is called.""" |
1113 | + msg = 'user_cancellation_callback is called when "%s" is clicked.' |
1114 | buttons = filter(lambda name: 'cancel_button' in name, self.ui.widgets) |
1115 | for name in buttons: |
1116 | - self.ui = self.gui_class(**self.kwargs) |
1117 | - self.ui.connect(sig, self._set_called, sig) |
1118 | widget = getattr(self.ui, name) |
1119 | widget.clicked() |
1120 | - expected_args = (self.ui.window, (APP_NAME,), {}) |
1121 | - self.assertEqual(self._called[sig], expected_args, msg % name) |
1122 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,), |
1123 | + msg % name) |
1124 | self._called = {} |
1125 | |
1126 | - def test_on_user_registration_error_proper_signal_is_emitted(self): |
1127 | - """On UserRegistrationError, SIG_USER_CANCELATION signal is sent.""" |
1128 | + def test_on_user_registration_error_proper_callback_is_called(self): |
1129 | + """On UserRegistrationError, USER_CANCELLATION is called.""" |
1130 | self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error) |
1131 | self.ui.on_close_clicked() |
1132 | - expected = (self.ui.window, (APP_NAME,), {}) |
1133 | - self.assertEqual(expected, |
1134 | - self._called[gui.SIG_USER_CANCELATION]) |
1135 | - |
1136 | - def test_on_email_validated_proper_signals_is_emitted(self): |
1137 | - """On EmailValidated, 'registration-succeeded' signal is sent.""" |
1138 | + |
1139 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
1140 | + |
1141 | + def test_on_email_validated_proper_callback_is_called(self): |
1142 | + """On EmailValidated, REGISTRATION_SUCCESS is called.""" |
1143 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
1144 | self.ui.on_close_clicked() |
1145 | - self.assertEqual((self.ui.window, (APP_NAME, EMAIL), {}), |
1146 | - self._called[gui.SIG_REGISTRATION_SUCCEEDED]) |
1147 | - |
1148 | - def test_on_email_validation_error_proper_signals_is_emitted(self): |
1149 | - """On EmailValidationError, SIG_USER_CANCELATION signal is sent.""" |
1150 | + |
1151 | + self.assertEqual(self._called[self.REGISTRATION_SUCCESS], |
1152 | + (APP_NAME, EMAIL)) |
1153 | + |
1154 | + def test_on_email_validation_error_proper_callback_is_called(self): |
1155 | + """On EmailValidationError, USER_CANCELLATION is called.""" |
1156 | self.ui.on_email_validation_error(app_name=APP_NAME, error=self.error) |
1157 | self.ui.on_close_clicked() |
1158 | - expected = (self.ui.window, (APP_NAME,), {}) |
1159 | - self.assertEqual(expected, |
1160 | - self._called[gui.SIG_USER_CANCELATION]) |
1161 | - |
1162 | - def test_on_logged_in_proper_signals_is_emitted(self): |
1163 | - """On LoggedIn, 'login-succeeded' signal is sent.""" |
1164 | + |
1165 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
1166 | + |
1167 | + def test_on_logged_in_proper_callback_is_called(self): |
1168 | + """On LoggedIn, LOGIN_SUCCESSFULL is called.""" |
1169 | self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
1170 | self.ui.on_close_clicked() |
1171 | - self.assertEqual((self.ui.window, (APP_NAME, EMAIL), {}), |
1172 | - self._called[gui.SIG_LOGIN_SUCCEEDED]) |
1173 | - |
1174 | - def test_on_login_error_proper_signals_is_emitted(self): |
1175 | - """On LoginError, SIG_USER_CANCELATION signal is sent.""" |
1176 | + |
1177 | + self.assertEqual(self._called[self.LOGIN_SUCCESSFULL], |
1178 | + (APP_NAME, EMAIL)) |
1179 | + |
1180 | + def test_on_login_error_proper_callback_is_called(self): |
1181 | + """On LoginError, USER_CANCELLATION is called.""" |
1182 | self.click_connect_with_valid_data() |
1183 | self.ui.on_login_error(app_name=APP_NAME, error=self.error) |
1184 | self.ui.on_close_clicked() |
1185 | - expected = (self.ui.window, (APP_NAME,), {}) |
1186 | - self.assertEqual(expected, |
1187 | - self._called[gui.SIG_USER_CANCELATION]) |
1188 | - |
1189 | - def test_registration_successfull_even_if_prior_registration_error(self): |
1190 | - """Only one signal is sent with the final outcome.""" |
1191 | + |
1192 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
1193 | + |
1194 | + def test_registration_success_even_if_prior_registration_error(self): |
1195 | + """Only one callback is called with the final outcome. |
1196 | + |
1197 | + When the user successfully registers, REGISTRATION_SUCCESS is |
1198 | + called even if there were errors before. |
1199 | + |
1200 | + """ |
1201 | self.click_join_with_valid_data() |
1202 | self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error) |
1203 | self.click_join_with_valid_data() |
1204 | self.ui.on_email_validated(app_name=APP_NAME, email=EMAIL) |
1205 | self.ui.on_close_clicked() |
1206 | |
1207 | - self.assertEqual(len(self._called), 1) |
1208 | - self.assertTrue(gui.SIG_REGISTRATION_SUCCEEDED in self._called) |
1209 | - |
1210 | - def test_login_successfull_even_if_prior_login_error(self): |
1211 | - """Only one signal is sent with the final outcome.""" |
1212 | + self.assertEqual(self._called[self.REGISTRATION_SUCCESS], |
1213 | + (APP_NAME, EMAIL)) |
1214 | + |
1215 | + def test_login_success_even_if_prior_login_error(self): |
1216 | + """Only one callback is called with the final outcome. |
1217 | + |
1218 | + When the user successfully logins, LOGIN_SUCCESSFULL is called even if |
1219 | + there were errors before. |
1220 | + |
1221 | + """ |
1222 | self.click_connect_with_valid_data() |
1223 | self.ui.on_login_error(app_name=APP_NAME, error=self.error) |
1224 | self.click_connect_with_valid_data() |
1225 | self.ui.on_logged_in(app_name=APP_NAME, email=EMAIL) |
1226 | self.ui.on_close_clicked() |
1227 | |
1228 | - self.assertEqual(len(self._called), 1) |
1229 | - self.assertTrue(gui.SIG_LOGIN_SUCCEEDED in self._called) |
1230 | + self.assertEqual(self._called[self.LOGIN_SUCCESSFULL], |
1231 | + (APP_NAME, EMAIL)) |
1232 | |
1233 | def test_user_cancelation_even_if_prior_registration_error(self): |
1234 | - """Only one signal is sent with the final outcome.""" |
1235 | + """Only one callback is called with the final outcome. |
1236 | + |
1237 | + When the user closes the window, USER_CANCELLATION is called even if |
1238 | + there were registration errors before. |
1239 | + |
1240 | + """ |
1241 | self.click_join_with_valid_data() |
1242 | self.ui.on_user_registration_error(app_name=APP_NAME, error=self.error) |
1243 | self.ui.join_cancel_button.clicked() |
1244 | |
1245 | - self.assertEqual(len(self._called), 1) |
1246 | - self.assertTrue(gui.SIG_USER_CANCELATION in self._called) |
1247 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
1248 | |
1249 | def test_user_cancelation_even_if_prior_login_error(self): |
1250 | - """Only one signal is sent with the final outcome.""" |
1251 | + """Only one callback is called with the final outcome. |
1252 | + |
1253 | + When the user closes the window, USER_CANCELLATION is called even if |
1254 | + there were login errors before. |
1255 | + |
1256 | + """ |
1257 | self.click_connect_with_valid_data() |
1258 | self.ui.on_login_error(app_name=APP_NAME, error=self.error) |
1259 | self.ui.login_cancel_button.clicked() |
1260 | |
1261 | - self.assertEqual(len(self._called), 1) |
1262 | - self.assertTrue(gui.SIG_USER_CANCELATION in self._called) |
1263 | + self.assertEqual(self._called[self.USER_CANCELLATION], (APP_NAME,)) |
1264 | |
1265 | |
1266 | class DefaultButtonsTestCase(UbuntuSSOClientTestCase): |
1267 | |
1268 | === modified file 'ubuntu_sso/logger.py' |
1269 | --- ubuntu_sso/logger.py 2010-10-11 13:22:16 +0000 |
1270 | +++ ubuntu_sso/logger.py 2011-01-13 00:33:10 +0000 |
1271 | @@ -57,6 +57,7 @@ |
1272 | logger.addHandler(MAIN_HANDLER) |
1273 | if os.environ.get('DEBUG'): |
1274 | debug_handler = logging.StreamHandler(sys.stderr) |
1275 | + debug_handler.setFormatter(logging.Formatter(fmt=FMT)) |
1276 | logger.addHandler(debug_handler) |
1277 | |
1278 | return logger |
1279 | |
1280 | === modified file 'ubuntu_sso/main.py' |
1281 | --- ubuntu_sso/main.py 2010-12-16 16:31:34 +0000 |
1282 | +++ ubuntu_sso/main.py 2011-01-13 00:33:10 +0000 |
1283 | @@ -50,6 +50,7 @@ |
1284 | |
1285 | logger = setup_logging("ubuntu_sso.main") |
1286 | U1_PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/" |
1287 | +TIMEOUT_INTERVAL = 500 |
1288 | |
1289 | |
1290 | class SSOLoginProcessor(Account): |
1291 | @@ -198,7 +199,9 @@ |
1292 | # pylint: disable=E1101 |
1293 | d = Keyring().set_credentials(app_name, credentials) |
1294 | d.addCallback(lambda _: self.LoggedIn(app_name, email)) |
1295 | - d.addErrback(lambda _: self.UserNotValidated(app_name, email)) |
1296 | + d.addErrback(lambda failure: \ |
1297 | + self.LoginError(app_name, |
1298 | + except_to_errdict(failure.value))) |
1299 | else: |
1300 | self.UserNotValidated(app_name, email) |
1301 | blocking(f, app_name, success_cb, self.LoginError) |
1302 | @@ -419,6 +422,12 @@ |
1303 | |
1304 | """ |
1305 | |
1306 | + def __init__(self, timeout_func, shutdown_func, *args, **kwargs): |
1307 | + super(CredentialsManagement, self).__init__(*args, **kwargs) |
1308 | + self._ref_count = 0 |
1309 | + self.timeout_func = timeout_func |
1310 | + self.shutdown_func = shutdown_func |
1311 | + |
1312 | # Operator not preceded by a space (fails with dbus decorators) |
1313 | # pylint: disable=C0322 |
1314 | |
1315 | @@ -434,39 +443,70 @@ |
1316 | result[DENIAL_CB_KEY] = self.AuthorizationDenied |
1317 | return result |
1318 | |
1319 | + def _process_failure(self, failure, app_name): |
1320 | + """Process the 'failure' and emit CredentialsError.""" |
1321 | + self.CredentialsError(app_name, except_to_errdict(failure.value)) |
1322 | + |
1323 | + def _get_ref_count(self): |
1324 | + """Get value of ref_count.""" |
1325 | + logger.debug('ref_count is %r.', self._ref_count) |
1326 | + return self._ref_count |
1327 | + |
1328 | + def _set_ref_count(self, new_value): |
1329 | + """Set a new value to ref_count.""" |
1330 | + logger.debug('ref_count is %r, changing value to %r.', |
1331 | + self._ref_count, new_value) |
1332 | + if new_value < 0: |
1333 | + self._ref_count = 0 |
1334 | + msg = 'Attempting to decrease ref_count to a negative value (%r).' |
1335 | + logger.warning(msg, new_value) |
1336 | + else: |
1337 | + self._ref_count = new_value |
1338 | + |
1339 | + if self._ref_count == 0: |
1340 | + self.timeout_func(TIMEOUT_INTERVAL, self.shutdown_func) |
1341 | + |
1342 | + ref_count = property(fget=_get_ref_count, fset=_set_ref_count) |
1343 | + |
1344 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s') |
1345 | def AuthorizationDenied(self, app_name): |
1346 | """Signal thrown when the user denies the authorization.""" |
1347 | + self.ref_count -= 1 |
1348 | logger.info('%s: emitting AuthorizationDenied with app_name "%s".', |
1349 | self.__class__.__name__, app_name) |
1350 | |
1351 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}') |
1352 | def CredentialsFound(self, app_name, credentials): |
1353 | """Signal thrown when the credentials are found.""" |
1354 | + self.ref_count -= 1 |
1355 | logger.info('%s: emitting CredentialsFound with app_name "%s".', |
1356 | self.__class__.__name__, app_name) |
1357 | |
1358 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s') |
1359 | def CredentialsNotFound(self, app_name): |
1360 | """Signal thrown when the credentials are not found.""" |
1361 | + self.ref_count -= 1 |
1362 | logger.info('%s: emitting CredentialsNotFound with app_name "%s".', |
1363 | self.__class__.__name__, app_name) |
1364 | |
1365 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s') |
1366 | def CredentialsCleared(self, app_name): |
1367 | """Signal thrown when the credentials were cleared.""" |
1368 | + self.ref_count -= 1 |
1369 | logger.info('%s: emitting CredentialsCleared with app_name "%s".', |
1370 | self.__class__.__name__, app_name) |
1371 | |
1372 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='s') |
1373 | def CredentialsStored(self, app_name): |
1374 | """Signal thrown when the credentials were cleared.""" |
1375 | + self.ref_count -= 1 |
1376 | logger.info('%s: emitting CredentialsStored with app_name "%s".', |
1377 | self.__class__.__name__, app_name) |
1378 | |
1379 | @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='sa{ss}') |
1380 | def CredentialsError(self, app_name, error_dict): |
1381 | """Signal thrown when there is a problem getting the credentials.""" |
1382 | + self.ref_count -= 1 |
1383 | logger.error('%s: emitting CredentialsError with app_name "%s" and ' |
1384 | 'error_dict %r.', self.__class__.__name__, app_name, |
1385 | error_dict) |
1386 | @@ -482,6 +522,7 @@ |
1387 | - 'args' is a dictionary, currently not used. |
1388 | |
1389 | """ |
1390 | + self.ref_count += 1 |
1391 | |
1392 | def success_cb(credentials): |
1393 | """Find credentials and notify using signals.""" |
1394 | @@ -494,7 +535,7 @@ |
1395 | d = obj.find_credentials() |
1396 | # pylint: disable=E1101 |
1397 | d.addCallback(success_cb) |
1398 | - d.addErrback(self.CredentialsError) |
1399 | + d.addErrback(self._process_failure, app_name) |
1400 | |
1401 | @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE, |
1402 | in_signature='sa{ss}', out_signature='') |
1403 | @@ -507,11 +548,13 @@ |
1404 | - 'args' is a dictionary, currently not used. |
1405 | |
1406 | """ |
1407 | + self.ref_count += 1 |
1408 | + |
1409 | obj = Credentials(app_name) |
1410 | d = obj.clear_credentials() |
1411 | # pylint: disable=E1101 |
1412 | d.addCallback(lambda _: self.CredentialsCleared(app_name)) |
1413 | - d.addErrback(self.CredentialsError) |
1414 | + d.addErrback(self._process_failure, app_name) |
1415 | |
1416 | @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE, |
1417 | in_signature='sa{ss}', out_signature='') |
1418 | @@ -526,16 +569,20 @@ |
1419 | 'consumer_secret'. |
1420 | |
1421 | """ |
1422 | + self.ref_count += 1 |
1423 | + |
1424 | obj = Credentials(app_name) |
1425 | d = obj.store_credentials(args) |
1426 | # pylint: disable=E1101 |
1427 | d.addCallback(lambda _: self.CredentialsStored(app_name)) |
1428 | - d.addErrback(self.CredentialsError) |
1429 | + d.addErrback(self._process_failure, app_name) |
1430 | |
1431 | @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE, |
1432 | in_signature='sa{ss}', out_signature='') |
1433 | def register(self, app_name, args): |
1434 | """Get credentials if found else prompt GUI to register.""" |
1435 | + self.ref_count += 1 |
1436 | + |
1437 | obj = Credentials(app_name, **self._parse_args(args)) |
1438 | obj.register() |
1439 | |
1440 | @@ -543,5 +590,7 @@ |
1441 | in_signature='sa{ss}', out_signature='') |
1442 | def login(self, app_name, args): |
1443 | """Get credentials if found else prompt GUI to login.""" |
1444 | + self.ref_count += 1 |
1445 | + |
1446 | obj = Credentials(app_name, **self._parse_args(args)) |
1447 | obj.login() |
1448 | |
1449 | === modified file 'ubuntu_sso/tests/__init__.py' |
1450 | --- ubuntu_sso/tests/__init__.py 2010-11-30 13:21:17 +0000 |
1451 | +++ ubuntu_sso/tests/__init__.py 2011-01-13 00:33:10 +0000 |
1452 | @@ -18,6 +18,8 @@ |
1453 | |
1454 | import os |
1455 | |
1456 | +from twisted.trial import unittest |
1457 | + |
1458 | from ubuntu_sso.keyring import get_token_name |
1459 | |
1460 | APP_NAME = 'The Super App!' |
1461 | @@ -45,3 +47,15 @@ |
1462 | TOKEN_NAME = get_token_name(APP_NAME) |
1463 | TC_URL = 'http://localhost/' |
1464 | WINDOW_ID = 5 |
1465 | + |
1466 | + |
1467 | +class TestCase(unittest.TestCase): |
1468 | + """Customized test case that keeps tracks of method calls.""" |
1469 | + |
1470 | + def setUp(self): |
1471 | + super(TestCase, self).setUp() |
1472 | + self._called = False |
1473 | + |
1474 | + def _set_called(self, *args, **kwargs): |
1475 | + """Keep track of a method call.""" |
1476 | + self._called = (args, kwargs) |
1477 | |
1478 | === modified file 'ubuntu_sso/tests/bin/show_gui' |
1479 | --- ubuntu_sso/tests/bin/show_gui 2010-11-30 13:21:17 +0000 |
1480 | +++ ubuntu_sso/tests/bin/show_gui 2011-01-13 00:33:10 +0000 |
1481 | @@ -31,11 +31,12 @@ |
1482 | 'Nam sed lorem nibh. Suspendisse gravida nulla non nunc suscipit' \ |
1483 | ' pulvinar tempus ut augue.' |
1484 | |
1485 | -main_quit = lambda *args, **kwargs: gtk.main_quit() |
1486 | - |
1487 | |
1488 | if __name__ == '__main__': |
1489 | - tc_url=TC_URL |
1490 | + |
1491 | + # pylint: disable=C0103, E1101 |
1492 | + |
1493 | + tc_url = TC_URL |
1494 | login_only = False |
1495 | xid = 0 |
1496 | |
1497 | @@ -51,7 +52,7 @@ |
1498 | win.show() |
1499 | xid = win.window.xid |
1500 | |
1501 | - UbuntuSSOClientGUI(close_callback=main_quit, app_name=APP_NAME, |
1502 | - tc_url=tc_url, help_text=HELP_TEXT, window_id=xid, |
1503 | - login_only=login_only) |
1504 | + UbuntuSSOClientGUI(close_callback=lambda *args, **kwargs: gtk.main_quit(), |
1505 | + app_name=APP_NAME, tc_url=tc_url, help_text=HELP_TEXT, |
1506 | + window_id=xid, login_only=login_only) |
1507 | gtk.main() |
1508 | |
1509 | === modified file 'ubuntu_sso/tests/bin/show_nm_state' |
1510 | --- ubuntu_sso/tests/bin/show_nm_state 2010-09-08 19:25:02 +0000 |
1511 | +++ ubuntu_sso/tests/bin/show_nm_state 2011-01-13 00:33:10 +0000 |
1512 | @@ -26,7 +26,6 @@ |
1513 | from ubuntu_sso.networkstate import NetworkManagerState, NM_STATE_NAMES |
1514 | |
1515 | DBusGMainLoop(set_as_default=True) |
1516 | -loop = gobject.MainLoop() |
1517 | |
1518 | |
1519 | def got_state(state): |
1520 | @@ -34,8 +33,9 @@ |
1521 | try: |
1522 | print NM_STATE_NAMES[state] |
1523 | finally: |
1524 | - loop.quit() |
1525 | + gobject.MainLoop().quit() |
1526 | |
1527 | +# pylint: disable=C0103 |
1528 | nms = NetworkManagerState(got_state) |
1529 | nms.find_online_state() |
1530 | -loop.run() |
1531 | +gobject.MainLoop().run() |
1532 | |
1533 | === modified file 'ubuntu_sso/tests/test_credentials.py' |
1534 | --- ubuntu_sso/tests/test_credentials.py 2010-11-30 13:21:17 +0000 |
1535 | +++ ubuntu_sso/tests/test_credentials.py 2011-01-13 00:33:10 +0000 |
1536 | @@ -21,13 +21,11 @@ |
1537 | import logging |
1538 | import urllib |
1539 | |
1540 | -import gobject |
1541 | - |
1542 | from twisted.internet import defer |
1543 | from twisted.internet.defer import inlineCallbacks |
1544 | from twisted.trial.unittest import TestCase, FailTest |
1545 | +from ubuntuone.devtools.handlers import MementoHandler |
1546 | |
1547 | -from contrib.testing.testcase import MementoHandler |
1548 | from ubuntu_sso import credentials |
1549 | from ubuntu_sso.credentials import (APP_NAME_KEY, HELP_TEXT_KEY, NO_OP, |
1550 | PING_URL_KEY, TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY, |
1551 | @@ -60,10 +58,6 @@ |
1552 | WINDOW_ID_KEY: WINDOW_ID, |
1553 | } |
1554 | |
1555 | -SIG_LOGIN_SUCCEEDED = '1' |
1556 | -SIG_REGISTRATION_SUCCEEDED = '2' |
1557 | -SIG_USER_CANCELATION = '3' |
1558 | - |
1559 | |
1560 | class SampleMiscException(Exception): |
1561 | """An error to be used while testing.""" |
1562 | @@ -82,17 +76,9 @@ |
1563 | def __init__(self, *args, **kwargs): |
1564 | self.args = args |
1565 | self.kwargs = kwargs |
1566 | - self.signals = [] |
1567 | - self.methods = [] |
1568 | - self.connect = lambda *a: self.signals.append(a) |
1569 | - |
1570 | - def finish_success(self): |
1571 | - """Fake the success finish.""" |
1572 | - self.methods.append('finish_success') |
1573 | - |
1574 | - def finish_error(self, error): |
1575 | - """Fake the error finish.""" |
1576 | - self.methods.append(('finish_error', error)) |
1577 | + self.login_success_callback = None |
1578 | + self.registration_success_callback = None |
1579 | + self.user_cancellation_callback = None |
1580 | |
1581 | |
1582 | class BasicTestCase(TestCase): |
1583 | @@ -100,7 +86,6 @@ |
1584 | |
1585 | def setUp(self): |
1586 | """Init.""" |
1587 | - self.patch(gobject, 'idle_add', lambda f, *a, **kw: f(*a, **kw)) |
1588 | self._called = False # helper |
1589 | |
1590 | self.memento = MementoHandler() |
1591 | @@ -226,45 +211,21 @@ |
1592 | |
1593 | self.assertEqual(getattr(self.obj, UI_MODULE_KEY), GTK_GUI_MODULE) |
1594 | |
1595 | - def test_success_cb_gui_none(self): |
1596 | - """Success callback calls the caller but not the GUI.""" |
1597 | - self.obj.gui = None |
1598 | - self.obj.success_cb(creds=TOKEN) |
1599 | - |
1600 | - self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}), |
1601 | - 'caller _success_cb was called.') |
1602 | - |
1603 | - def test_success_cb_gui_not_none(self): |
1604 | - """Success callback calls the caller and finish the GUI.""" |
1605 | - self.obj.gui = ui = FakedClientGUI(APP_NAME) |
1606 | - self.obj.success_cb(creds=TOKEN) |
1607 | - |
1608 | - self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}), |
1609 | - 'caller _success_cb was called.') |
1610 | - self.assertEqual(ui.methods, ['finish_success'], |
1611 | - 'GUI finish_success was called.') |
1612 | - self.assertTrue(self.obj.gui is None, 'gui is reset to None.') |
1613 | - |
1614 | - def test_error_cb_gui_none(self): |
1615 | - """Error callback calls the caller but not the GUI.""" |
1616 | - self.obj.gui = None |
1617 | - error_dict = {'foo': 'bar'} |
1618 | - self.obj.error_cb(error_dict=error_dict) |
1619 | - |
1620 | - self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}), |
1621 | - 'caller _error_cb was called.') |
1622 | - |
1623 | - def test_error_cb_gui_not_none(self): |
1624 | - """Error callback calls the caller and finish the GUI.""" |
1625 | - self.obj.gui = ui = FakedClientGUI(APP_NAME) |
1626 | - error_dict = {'foo': 'bar'} |
1627 | - self.obj.error_cb(error_dict=error_dict) |
1628 | - |
1629 | - self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}), |
1630 | - 'caller _error_cb was called.') |
1631 | - self.assertEqual(ui.methods, [('finish_error', error_dict)], |
1632 | - 'GUI finish_error was called.') |
1633 | - self.assertTrue(self.obj.gui is None, 'gui is reset to None.') |
1634 | + def test_success_cb(self): |
1635 | + """Success callback calls the caller.""" |
1636 | + self.obj.gui = None |
1637 | + self.obj.success_cb(creds=TOKEN) |
1638 | + |
1639 | + self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {}), |
1640 | + 'caller _success_cb was called.') |
1641 | + |
1642 | + def test_error_cb(self): |
1643 | + """Error callback calls the caller.""" |
1644 | + error_dict = {'foo': 'bar'} |
1645 | + self.obj.error_cb(error_dict=error_dict) |
1646 | + |
1647 | + self.assertEqual(self._called, (('error', APP_NAME, error_dict), {}), |
1648 | + 'caller _error_cb was called.') |
1649 | |
1650 | |
1651 | class CredentialsLoginSuccessTestCase(CredentialsTestCase): |
1652 | @@ -276,12 +237,12 @@ |
1653 | self.patch(credentials.Keyring, 'get_credentials', |
1654 | lambda kr, app: defer.succeed(None)) |
1655 | |
1656 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
1657 | - email=EMAIL) |
1658 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
1659 | |
1660 | msg = 'Creds are empty! This should not happen' |
1661 | self.assert_error_cb_called(msg='Problem while retrieving credentials', |
1662 | detailed_error=AssertionError(msg)) |
1663 | + self.assertEqual(result, None) |
1664 | |
1665 | @inlineCallbacks |
1666 | def test_cred_error(self): |
1667 | @@ -290,11 +251,11 @@ |
1668 | self.patch(credentials.Keyring, 'get_credentials', |
1669 | lambda kr, app: defer.fail(expected_error)) |
1670 | |
1671 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
1672 | - email=EMAIL) |
1673 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
1674 | |
1675 | msg = 'Problem while retrieving credentials' |
1676 | self.assert_error_cb_called(msg=msg, detailed_error=expected_error) |
1677 | + self.assertEqual(result, None) |
1678 | self.assertTrue(self.memento.check_exception(SampleMiscException)) |
1679 | |
1680 | @inlineCallbacks |
1681 | @@ -304,10 +265,10 @@ |
1682 | lambda kr, app: defer.succeed(TOKEN)) |
1683 | self.patch(self.obj, '_ping_url', lambda *a, **kw: 200) |
1684 | |
1685 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
1686 | - email=EMAIL) |
1687 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
1688 | |
1689 | self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {})) |
1690 | + self.assertEqual(result, 0) |
1691 | |
1692 | @inlineCallbacks |
1693 | def test_ping_error(self): |
1694 | @@ -324,12 +285,12 @@ |
1695 | self.patch(self.obj, 'clear_credentials', |
1696 | lambda: setattr(self, '_cred_cleared', True)) |
1697 | |
1698 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
1699 | - email=EMAIL) |
1700 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
1701 | |
1702 | # error cb called correctly |
1703 | msg = 'Problem opening the ping_url' |
1704 | self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error)) |
1705 | + self.assertEqual(result, None) |
1706 | |
1707 | # credentials cleared |
1708 | self.assertTrue(self._cred_cleared) |
1709 | @@ -349,8 +310,7 @@ |
1710 | self.patch(self.obj, '_ping_url', |
1711 | lambda *a, **kw: setattr(self, '_url_pinged', (a, kw))) |
1712 | |
1713 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
1714 | - email=EMAIL) |
1715 | + yield self.obj._login_success_cb(APP_NAME, EMAIL) |
1716 | |
1717 | self.assertEqual(self._url_pinged, ((APP_NAME, EMAIL, TOKEN), {})) |
1718 | |
1719 | @@ -366,10 +326,10 @@ |
1720 | self.patch(self.obj, 'clear_credentials', self._set_called) |
1721 | self.obj.ping_url = None |
1722 | |
1723 | - yield self.obj._login_success_cb(dialog=None, app_name=APP_NAME, |
1724 | - email=EMAIL) |
1725 | + result = yield self.obj._login_success_cb(APP_NAME, EMAIL) |
1726 | |
1727 | self.assertEqual(self._called, (('success', APP_NAME, TOKEN), {})) |
1728 | + self.assertEqual(result, 0) |
1729 | |
1730 | |
1731 | class CredentialsAuthDeniedTestCase(CredentialsTestCase): |
1732 | @@ -377,10 +337,9 @@ |
1733 | |
1734 | def test_auth_denial_cb(self): |
1735 | """On auth denied, self.denial_cb is called.""" |
1736 | - self.obj.gui = ui = FakedClientGUI(APP_NAME) |
1737 | - self.obj._auth_denial_cb(dialog=None, app_name=APP_NAME) |
1738 | + self.obj._auth_denial_cb(app_name=APP_NAME) |
1739 | + |
1740 | self.assertEqual(self._called, (('denial', APP_NAME), {})) |
1741 | - self.assertEqual(ui.methods, [], 'no GUI method was called.') |
1742 | |
1743 | |
1744 | class PingUrlTestCase(CredentialsTestCase): |
1745 | @@ -401,24 +360,29 @@ |
1746 | |
1747 | self.patch(credentials.urllib2, 'urlopen', faked_urlopen) |
1748 | |
1749 | + @inlineCallbacks |
1750 | def test_ping_url_if_url_is_none(self): |
1751 | """self.ping_url is opened.""" |
1752 | self.patch(credentials.urllib2, 'urlopen', self.fail) |
1753 | self.obj.ping_url = None |
1754 | - self.obj._ping_url(app_name=APP_NAME, email=EMAIL, credentials=TOKEN) |
1755 | + yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL, |
1756 | + credentials=TOKEN) |
1757 | # no failure |
1758 | |
1759 | + @inlineCallbacks |
1760 | def test_ping_url(self): |
1761 | """On auth success, self.ping_url is opened.""" |
1762 | - self.obj._ping_url(app_name=APP_NAME, email=EMAIL, credentials=TOKEN) |
1763 | + yield self.obj._ping_url(app_name=APP_NAME, email=EMAIL, |
1764 | + credentials=TOKEN) |
1765 | |
1766 | self.assertIsInstance(self._request, credentials.urllib2.Request) |
1767 | self.assertEqual(self._request.get_full_url(), |
1768 | self.obj.ping_url + EMAIL) |
1769 | |
1770 | + @inlineCallbacks |
1771 | def test_request_is_signed_with_credentials(self): |
1772 | """The http request to self.ping_url is signed with the credentials.""" |
1773 | - result = self.obj._ping_url(APP_NAME, EMAIL, TOKEN) |
1774 | + result = yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN) |
1775 | headers = self._request.headers |
1776 | |
1777 | self.assertIn('Authorization', headers) |
1778 | @@ -431,12 +395,13 @@ |
1779 | self.assertIn(expected, oauth_stuff) |
1780 | self.assertEqual(result, 200) |
1781 | |
1782 | + @inlineCallbacks |
1783 | def test_ping_url_error(self): |
1784 | """Exception is handled if ping fails.""" |
1785 | error = 'Blu' |
1786 | self.patch(credentials.urllib2, 'urlopen', lambda r: self.fail(error)) |
1787 | |
1788 | - self.obj._ping_url(APP_NAME, EMAIL, TOKEN) |
1789 | + yield self.obj._ping_url(APP_NAME, EMAIL, TOKEN) |
1790 | |
1791 | msg = 'Problem opening the ping_url' |
1792 | self.assert_error_cb_called(msg=msg, detailed_error=FailTest(error)) |
1793 | @@ -472,11 +437,8 @@ |
1794 | self.patch(credentials.Keyring, 'get_credentials', |
1795 | lambda kr, app: defer.fail(expected_error)) |
1796 | |
1797 | - yield self.obj.find_credentials() |
1798 | - |
1799 | - msg = 'Problem while retrieving credentials' |
1800 | - self.assert_error_cb_called(msg=msg, detailed_error=expected_error) |
1801 | - self.assertTrue(self.memento.check_exception(SampleMiscException)) |
1802 | + yield self.assertFailure(self.obj.find_credentials(), |
1803 | + SampleMiscException) |
1804 | |
1805 | |
1806 | class ClearCredentialsTestCase(CredentialsTestCase): |
1807 | @@ -498,11 +460,8 @@ |
1808 | self.patch(credentials.Keyring, 'delete_credentials', |
1809 | lambda kr, app: defer.fail(expected_error)) |
1810 | |
1811 | - yield self.obj.clear_credentials() |
1812 | - |
1813 | - msg = 'Problem while deleting credentials' |
1814 | - self.assert_error_cb_called(msg=msg, detailed_error=expected_error) |
1815 | - self.assertTrue(self.memento.check_exception(SampleMiscException)) |
1816 | + yield self.assertFailure(self.obj.clear_credentials(), |
1817 | + SampleMiscException) |
1818 | |
1819 | |
1820 | class StoreCredentialsTestCase(CredentialsTestCase): |
1821 | @@ -524,11 +483,8 @@ |
1822 | self.patch(credentials.Keyring, 'set_credentials', |
1823 | lambda kr, app, token: defer.fail(expected_error)) |
1824 | |
1825 | - yield self.obj.store_credentials(TOKEN) |
1826 | - |
1827 | - msg = 'Problem while storing credentials' |
1828 | - self.assert_error_cb_called(msg=msg, detailed_error=expected_error) |
1829 | - self.assertTrue(self.memento.check_exception(SampleMiscException)) |
1830 | + yield self.assertFailure(self.obj.store_credentials(TOKEN), |
1831 | + SampleMiscException) |
1832 | |
1833 | |
1834 | class RegisterTestCase(CredentialsTestCase): |
1835 | @@ -592,17 +548,17 @@ |
1836 | |
1837 | @inlineCallbacks |
1838 | def test_connects_gui_signals(self): |
1839 | - """Outcome signals from GUI are connected to callbacks.""" |
1840 | + """GUI callbacks are properly connected.""" |
1841 | self.patch(credentials.Keyring, 'get_credentials', |
1842 | lambda kr, app: defer.succeed(None)) |
1843 | - |
1844 | - expected = [ |
1845 | - (SIG_LOGIN_SUCCEEDED, self.obj._login_success_cb), |
1846 | - (SIG_REGISTRATION_SUCCEEDED, self.obj._login_success_cb), |
1847 | - (SIG_USER_CANCELATION, self.obj._auth_denial_cb), |
1848 | - ] |
1849 | yield getattr(self.obj, self.operation)() |
1850 | - self.assertEqual(self.obj.gui.signals, expected) |
1851 | + |
1852 | + self.assertEqual(self.obj.gui.login_success_callback, |
1853 | + self.obj._login_success_cb) |
1854 | + self.assertEqual(self.obj.gui.registration_success_callback, |
1855 | + self.obj._login_success_cb) |
1856 | + self.assertEqual(self.obj.gui.user_cancellation_callback, |
1857 | + self.obj._auth_denial_cb) |
1858 | |
1859 | @inlineCallbacks |
1860 | def test_gui_is_created(self): |
1861 | |
1862 | === modified file 'ubuntu_sso/tests/test_main.py' |
1863 | --- ubuntu_sso/tests/test_main.py 2010-12-16 16:31:34 +0000 |
1864 | +++ ubuntu_sso/tests/test_main.py 2011-01-13 00:33:10 +0000 |
1865 | @@ -27,21 +27,22 @@ |
1866 | from twisted.internet import defer |
1867 | from twisted.internet.defer import Deferred, inlineCallbacks |
1868 | from twisted.trial.unittest import TestCase |
1869 | +from ubuntuone.devtools.handlers import MementoHandler |
1870 | |
1871 | import ubuntu_sso.keyring |
1872 | import ubuntu_sso.main |
1873 | |
1874 | -from contrib.testing.testcase import MementoHandler |
1875 | from ubuntu_sso import DBUS_CREDENTIALS_IFACE |
1876 | from ubuntu_sso.keyring import U1_APP_NAME |
1877 | -from ubuntu_sso.main import (U1_PING_URL, blocking, except_to_errdict, |
1878 | +from ubuntu_sso.main import (U1_PING_URL, TIMEOUT_INTERVAL, |
1879 | + blocking, except_to_errdict, |
1880 | CredentialsManagement, SSOCredentials, SSOLogin) |
1881 | from ubuntu_sso.main import (HELP_TEXT_KEY, PING_URL_KEY, |
1882 | TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY, |
1883 | SUCCESS_CB_KEY, ERROR_CB_KEY, DENIAL_CB_KEY) |
1884 | from ubuntu_sso.tests import (APP_NAME, TC_URL, HELP_TEXT, CAPTCHA_ID, |
1885 | CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, PASSWORD, PING_URL, TOKEN, |
1886 | - TOKEN_NAME, WINDOW_ID) |
1887 | + TOKEN_NAME, WINDOW_ID, TestCase) |
1888 | |
1889 | |
1890 | # Access to a protected member 'yyy' of a client class |
1891 | @@ -70,6 +71,8 @@ |
1892 | class SsoDbusTestCase(TestCase): |
1893 | """Test the SSOLogin DBus interface.""" |
1894 | |
1895 | + timeout = 2 |
1896 | + |
1897 | def setUp(self): |
1898 | """Create the mocking bus.""" |
1899 | self.mocker = Mocker() |
1900 | @@ -256,8 +259,8 @@ |
1901 | client.login(APP_NAME, EMAIL, PASSWORD) |
1902 | return d |
1903 | |
1904 | - def test_login_error(self): |
1905 | - """Test that the login method fails as expected.""" |
1906 | + def test_login_error_get_token_name(self): |
1907 | + """The login method fails as expected when get_token_name fails.""" |
1908 | d = Deferred() |
1909 | self.create_mock_processor() |
1910 | self.patch(ubuntu_sso.main, "blocking", fake_err_blocking) |
1911 | @@ -284,6 +287,40 @@ |
1912 | client.login(APP_NAME, EMAIL, PASSWORD) |
1913 | return d |
1914 | |
1915 | + def test_login_error_set_credentials(self): |
1916 | + """The login method fails as expected when set_credentials fails.""" |
1917 | + d = Deferred() |
1918 | + processor = self.create_mock_processor() |
1919 | + processor.login(EMAIL, PASSWORD, TOKEN_NAME) |
1920 | + self.mocker.result(TOKEN) |
1921 | + processor.is_validated(TOKEN) |
1922 | + self.mocker.result(True) |
1923 | + |
1924 | + self.patch(ubuntu_sso.main, "blocking", fake_ok_blocking) |
1925 | + |
1926 | + def fake_set_creds(*args): |
1927 | + """A fake Keyring.set_credentials that fails.""" |
1928 | + return defer.fail(BlockingSampleException()) |
1929 | + |
1930 | + self.patch(ubuntu_sso.main.Keyring, "set_credentials", fake_set_creds) |
1931 | + self.mocker.replay() |
1932 | + |
1933 | + def verify(app_name, errdict): |
1934 | + """The actual test.""" |
1935 | + self.assertEqual(app_name, APP_NAME) |
1936 | + self.assertEqual(errdict["errtype"], "BlockingSampleException") |
1937 | + self.assertFalse(self.keyring_was_set, "Keyring should not be set") |
1938 | + d.callback("Ok") |
1939 | + |
1940 | + client = SSOLogin(self.mockbusname, |
1941 | + sso_login_processor_class=self.mockprocessorclass) |
1942 | + fail = lambda app, res: d.errback((app, res)) |
1943 | + self.patch(client, "LoggedIn", fail) |
1944 | + self.patch(client, "LoginError", verify) |
1945 | + self.patch(client, "UserNotValidated", fail) |
1946 | + client.login(APP_NAME, EMAIL, PASSWORD) |
1947 | + return d |
1948 | + |
1949 | def test_validate_email(self): |
1950 | """Test that the validate_email method works ok.""" |
1951 | d = Deferred() |
1952 | @@ -666,21 +703,30 @@ |
1953 | class CredentialsManagementTestCase(TestCase): |
1954 | """Tests for the CredentialsManagement DBus interface.""" |
1955 | |
1956 | + timeout = 2 |
1957 | base_args = {HELP_TEXT_KEY: HELP_TEXT, PING_URL_KEY: PING_URL, |
1958 | TC_URL_KEY: TC_URL, WINDOW_ID_KEY: WINDOW_ID, |
1959 | UI_CLASS_KEY: 'SuperUI', UI_MODULE_KEY: 'foo.bar.baz', |
1960 | } |
1961 | |
1962 | def setUp(self): |
1963 | + super(CredentialsManagementTestCase, self).setUp() |
1964 | + |
1965 | self.mocker = Mocker() |
1966 | - self.client = CredentialsManagement() |
1967 | + self.client = CredentialsManagement(timeout_func=lambda *a: None, |
1968 | + shutdown_func=lambda *a: None) |
1969 | self.args = {} |
1970 | self.cred_args = {} |
1971 | |
1972 | + self.memento = MementoHandler() |
1973 | + self.memento.setLevel(logging.DEBUG) |
1974 | + ubuntu_sso.main.logger.addHandler(self.memento) |
1975 | + |
1976 | def tearDown(self): |
1977 | """Verify the mocking stuff and shut it down.""" |
1978 | self.mocker.verify() |
1979 | self.mocker.restore() |
1980 | + super(CredentialsManagementTestCase, self).tearDown() |
1981 | |
1982 | def assert_dbus_method_correct(self, method): |
1983 | """Check that 'method' is a dbus method with proper signatures.""" |
1984 | @@ -703,10 +749,168 @@ |
1985 | self.assertIsInstance(self.client, ubuntu_sso.main.dbus.service.Object) |
1986 | |
1987 | |
1988 | -class CredentialsManagementFindClearTestCase(CredentialsManagementTestCase): |
1989 | - """Tests for the CredentialsManagement find/clear/store methods.""" |
1990 | - |
1991 | - timeout = 5 |
1992 | +class CredentialsManagementRefCountingTestCase(CredentialsManagementTestCase): |
1993 | + """Tests for the CredentialsManagement ref counting.""" |
1994 | + |
1995 | + def test_ref_counting(self): |
1996 | + """Ref counting is in place.""" |
1997 | + self.assertEqual(self.client.ref_count, 0) |
1998 | + |
1999 | + def test_find_credentials(self): |
2000 | + """Keep proper track of on going requests.""" |
2001 | + self.client.find_credentials(APP_NAME, self.args) |
2002 | + |
2003 | + self.assertEqual(self.client.ref_count, 1) |
2004 | + |
2005 | + def test_clear_credentials(self): |
2006 | + """Keep proper track of on going requests.""" |
2007 | + self.client.clear_credentials(APP_NAME, self.args) |
2008 | + |
2009 | + self.assertEqual(self.client.ref_count, 1) |
2010 | + |
2011 | + def test_store_credentials(self): |
2012 | + """Keep proper track of on going requests.""" |
2013 | + self.client.store_credentials(APP_NAME, self.args) |
2014 | + |
2015 | + self.assertEqual(self.client.ref_count, 1) |
2016 | + |
2017 | + def test_register(self): |
2018 | + """Keep proper track of on going requests.""" |
2019 | + self.client.register(APP_NAME, self.args) |
2020 | + |
2021 | + self.assertEqual(self.client.ref_count, 1) |
2022 | + |
2023 | + def test_login(self): |
2024 | + """Keep proper track of on going requests.""" |
2025 | + self.client.login(APP_NAME, self.args) |
2026 | + |
2027 | + self.assertEqual(self.client.ref_count, 1) |
2028 | + |
2029 | + def test_several_requests(self): |
2030 | + """Requests can be nested.""" |
2031 | + self.client.login(APP_NAME, self.args) |
2032 | + self.client.clear_credentials(APP_NAME, self.args) |
2033 | + self.client.find_credentials(APP_NAME, self.args) |
2034 | + self.client.register(APP_NAME, self.args) |
2035 | + self.client.store_credentials(APP_NAME, self.args) |
2036 | + |
2037 | + self.assertEqual(self.client.ref_count, 5) |
2038 | + |
2039 | + def test_credentials_found(self): |
2040 | + """Ref counter is decreased when a signal is sent.""" |
2041 | + self.client.ref_count = 3 |
2042 | + self.client.CredentialsFound(APP_NAME, TOKEN) |
2043 | + |
2044 | + self.assertEqual(self.client.ref_count, 2) |
2045 | + |
2046 | + def test_credentials_not_found(self): |
2047 | + """Ref counter is decreased when a signal is sent.""" |
2048 | + self.client.ref_count = 3 |
2049 | + self.client.CredentialsNotFound(APP_NAME) |
2050 | + |
2051 | + self.assertEqual(self.client.ref_count, 2) |
2052 | + |
2053 | + def test_credentials_cleared(self): |
2054 | + """Ref counter is decreased when a signal is sent.""" |
2055 | + self.client.ref_count = 3 |
2056 | + self.client.CredentialsCleared(APP_NAME) |
2057 | + |
2058 | + self.assertEqual(self.client.ref_count, 2) |
2059 | + |
2060 | + def test_credentials_stored(self): |
2061 | + """Ref counter is decreased when a signal is sent.""" |
2062 | + self.client.ref_count = 3 |
2063 | + self.client.CredentialsStored(APP_NAME) |
2064 | + |
2065 | + self.assertEqual(self.client.ref_count, 2) |
2066 | + |
2067 | + def test_credentials_error(self): |
2068 | + """Ref counter is decreased when a signal is sent.""" |
2069 | + self.client.ref_count = 3 |
2070 | + self.client.CredentialsError(APP_NAME, {'error_type': 'test'}) |
2071 | + |
2072 | + self.assertEqual(self.client.ref_count, 2) |
2073 | + |
2074 | + def test_authorization_denied(self): |
2075 | + """Ref counter is decreased when a signal is sent.""" |
2076 | + self.client.ref_count = 3 |
2077 | + self.client.AuthorizationDenied(APP_NAME) |
2078 | + |
2079 | + self.assertEqual(self.client.ref_count, 2) |
2080 | + |
2081 | + def test_credentials_found_when_ref_count_is_not_positive(self): |
2082 | + """Ref counter is decreased when a signal is sent.""" |
2083 | + self.client._ref_count = -3 |
2084 | + self.client.CredentialsFound(APP_NAME, TOKEN) |
2085 | + |
2086 | + self.assertEqual(self.client.ref_count, 0) |
2087 | + msg = 'Attempting to decrease ref_count to a negative value (-4).' |
2088 | + self.assertTrue(self.memento.check_warning(msg)) |
2089 | + |
2090 | + def test_credentials_not_found_when_ref_count_is_not_positive(self): |
2091 | + """Ref counter is decreased when a signal is sent.""" |
2092 | + self.client._ref_count = -3 |
2093 | + self.client.CredentialsNotFound(APP_NAME) |
2094 | + |
2095 | + self.assertEqual(self.client.ref_count, 0) |
2096 | + msg = 'Attempting to decrease ref_count to a negative value (-4).' |
2097 | + self.assertTrue(self.memento.check_warning(msg)) |
2098 | + |
2099 | + def test_credentials_cleared_when_ref_count_is_not_positive(self): |
2100 | + """Ref counter is decreased when a signal is sent.""" |
2101 | + self.client._ref_count = -3 |
2102 | + self.client.CredentialsCleared(APP_NAME) |
2103 | + |
2104 | + self.assertEqual(self.client.ref_count, 0) |
2105 | + msg = 'Attempting to decrease ref_count to a negative value (-4).' |
2106 | + self.assertTrue(self.memento.check_warning(msg)) |
2107 | + |
2108 | + def test_credentials_stored_when_ref_count_is_not_positive(self): |
2109 | + """Ref counter is decreased when a signal is sent.""" |
2110 | + self.client._ref_count = -3 |
2111 | + self.client.CredentialsStored(APP_NAME) |
2112 | + |
2113 | + self.assertEqual(self.client.ref_count, 0) |
2114 | + msg = 'Attempting to decrease ref_count to a negative value (-4).' |
2115 | + self.assertTrue(self.memento.check_warning(msg)) |
2116 | + |
2117 | + def test_credentials_error_when_ref_count_is_not_positive(self): |
2118 | + """Ref counter is decreased when a signal is sent.""" |
2119 | + self.client._ref_count = -3 |
2120 | + self.client.CredentialsError(APP_NAME, {'error_type': 'test'}) |
2121 | + |
2122 | + self.assertEqual(self.client.ref_count, 0) |
2123 | + msg = 'Attempting to decrease ref_count to a negative value (-4).' |
2124 | + self.assertTrue(self.memento.check_warning(msg)) |
2125 | + |
2126 | + def test_autorization_denied_when_ref_count_is_not_positive(self): |
2127 | + """Ref counter is decreased when a signal is sent.""" |
2128 | + self.client._ref_count = -3 |
2129 | + self.client.AuthorizationDenied(APP_NAME) |
2130 | + |
2131 | + self.assertEqual(self.client.ref_count, 0) |
2132 | + msg = 'Attempting to decrease ref_count to a negative value (-4).' |
2133 | + self.assertTrue(self.memento.check_warning(msg)) |
2134 | + |
2135 | + def test_on_zero_ref_count_shutdown(self): |
2136 | + """When ref count reaches 0, queue shutdown op.""" |
2137 | + self.client.timeout_func = self._set_called |
2138 | + self.client.find_credentials(APP_NAME, self.args) |
2139 | + self.client.CredentialsFound(APP_NAME, TOKEN) |
2140 | + |
2141 | + self.assertEqual(self._called, |
2142 | + ((TIMEOUT_INTERVAL, self.client.shutdown_func), {})) |
2143 | + |
2144 | + def test_on_non_zero_ref_count_do_not_shutdown(self): |
2145 | + """If ref count is not 0, do not queue shutdown op.""" |
2146 | + self.client.timeout_func = self._set_called |
2147 | + self.client.find_credentials(APP_NAME, self.args) |
2148 | + |
2149 | + self.assertEqual(self._called, False) |
2150 | + |
2151 | + |
2152 | +class CredentialsManagementFindTestCase(CredentialsManagementTestCase): |
2153 | + """Tests for the CredentialsManagement find method.""" |
2154 | |
2155 | def test_find_credentials(self): |
2156 | """The credentials are asked and returned in signals.""" |
2157 | @@ -762,7 +966,8 @@ |
2158 | else: |
2159 | d.callback(app_name) |
2160 | |
2161 | - self.patch(self.client, 'CredentialsFound', d.errback) |
2162 | + self.patch(self.client, 'CredentialsFound', |
2163 | + lambda app, creds: d.errback(app)) |
2164 | self.patch(self.client, 'CredentialsNotFound', verify) |
2165 | |
2166 | self.create_mock_backend().find_credentials() |
2167 | @@ -772,6 +977,32 @@ |
2168 | self.client.find_credentials(APP_NAME, self.args) |
2169 | return d |
2170 | |
2171 | + def test_find_credentials_error(self): |
2172 | + """If find_credentials fails, CredentialsError is sent.""" |
2173 | + d = Deferred() |
2174 | + |
2175 | + def verify(app_name, errdict): |
2176 | + """The actual test.""" |
2177 | + self.assertEqual(errdict["errtype"], "BlockingSampleException") |
2178 | + self.assertEqual(app_name, APP_NAME) |
2179 | + d.callback("Ok") |
2180 | + |
2181 | + self.patch(self.client, 'CredentialsFound', |
2182 | + lambda app, creds: d.errback(app)) |
2183 | + self.patch(self.client, 'CredentialsNotFound', d.errback) |
2184 | + self.patch(self.client, 'CredentialsError', verify) |
2185 | + |
2186 | + self.create_mock_backend().find_credentials() |
2187 | + self.mocker.result(defer.fail(BlockingSampleException())) |
2188 | + self.mocker.replay() |
2189 | + |
2190 | + self.client.find_credentials(APP_NAME, self.args) |
2191 | + return d |
2192 | + |
2193 | + |
2194 | +class CredentialsManagementClearTestCase(CredentialsManagementTestCase): |
2195 | + """Tests for the CredentialsManagement clear method.""" |
2196 | + |
2197 | def test_clear_credentials(self): |
2198 | """The credentials are removed.""" |
2199 | self.create_mock_backend().clear_credentials() |
2200 | @@ -795,7 +1026,8 @@ |
2201 | d.callback(app_name) |
2202 | |
2203 | self.patch(self.client, 'CredentialsCleared', verify) |
2204 | - self.patch(self.client, 'CredentialsError', d.errback) |
2205 | + self.patch(self.client, 'CredentialsError', |
2206 | + lambda app, err: d.errback(app)) |
2207 | |
2208 | self.create_mock_backend().clear_credentials() |
2209 | self.mocker.result(defer.succeed(APP_NAME)) |
2210 | @@ -804,6 +1036,30 @@ |
2211 | self.client.clear_credentials(APP_NAME, self.args) |
2212 | return d |
2213 | |
2214 | + def test_clear_credentials_error(self): |
2215 | + """If clear_credentials fails, CredentialsError is sent.""" |
2216 | + d = Deferred() |
2217 | + |
2218 | + def verify(app_name, errdict): |
2219 | + """The actual test.""" |
2220 | + self.assertEqual(errdict["errtype"], "BlockingSampleException") |
2221 | + self.assertEqual(app_name, APP_NAME) |
2222 | + d.callback("Ok") |
2223 | + |
2224 | + self.patch(self.client, 'CredentialsCleared', d.errback) |
2225 | + self.patch(self.client, 'CredentialsError', verify) |
2226 | + |
2227 | + self.create_mock_backend().clear_credentials() |
2228 | + self.mocker.result(defer.fail(BlockingSampleException())) |
2229 | + self.mocker.replay() |
2230 | + |
2231 | + self.client.clear_credentials(APP_NAME, self.args) |
2232 | + return d |
2233 | + |
2234 | + |
2235 | +class CredentialsManagementStoreTestCase(CredentialsManagementTestCase): |
2236 | + """Tests for the CredentialsManagement store method.""" |
2237 | + |
2238 | def test_store_credentials(self): |
2239 | """The credentials are stored and the outcome is a signal.""" |
2240 | self.create_mock_backend().store_credentials(TOKEN) |
2241 | @@ -831,7 +1087,8 @@ |
2242 | d.callback(app_name) |
2243 | |
2244 | self.patch(self.client, 'CredentialsStored', verify) |
2245 | - self.patch(self.client, 'CredentialsError', d.errback) |
2246 | + self.patch(self.client, 'CredentialsError', |
2247 | + lambda app, err: d.errback(app)) |
2248 | |
2249 | self.create_mock_backend().store_credentials(TOKEN) |
2250 | self.mocker.result(defer.succeed(APP_NAME)) |
2251 | @@ -840,6 +1097,26 @@ |
2252 | self.client.store_credentials(APP_NAME, TOKEN) |
2253 | return d |
2254 | |
2255 | + def test_store_credentials_error(self): |
2256 | + """If store_credentials fails, CredentialsError is sent.""" |
2257 | + d = Deferred() |
2258 | + |
2259 | + def verify(app_name, errdict): |
2260 | + """The actual test.""" |
2261 | + self.assertEqual(errdict["errtype"], "BlockingSampleException") |
2262 | + self.assertEqual(app_name, APP_NAME) |
2263 | + d.callback("Ok") |
2264 | + |
2265 | + self.patch(self.client, 'CredentialsStored', d.errback) |
2266 | + self.patch(self.client, 'CredentialsError', verify) |
2267 | + |
2268 | + self.create_mock_backend().store_credentials(TOKEN) |
2269 | + self.mocker.result(defer.fail(BlockingSampleException())) |
2270 | + self.mocker.replay() |
2271 | + |
2272 | + self.client.store_credentials(APP_NAME, TOKEN) |
2273 | + return d |
2274 | + |
2275 | |
2276 | class CredentialsManagementOpsTestCase(CredentialsManagementTestCase): |
2277 | """Tests for the CredentialsManagement login/register methods.""" |
2278 | @@ -881,7 +1158,8 @@ |
2279 | """Tests for the CredentialsManagement DBus signals.""" |
2280 | |
2281 | def setUp(self): |
2282 | - self.client = CredentialsManagement() |
2283 | + self.client = CredentialsManagement(timeout_func=lambda *a: None, |
2284 | + shutdown_func=lambda *a: None) |
2285 | |
2286 | self.memento = MementoHandler() |
2287 | self.memento.setLevel(logging.DEBUG) |