PKrImfabfile/__init__.pyfrom fabfile.development import * from fabfile.environment import * # Make sure the virtualenv is activated. setup_virtualenv() PKrIWWWfabfile/development.py# Copyright 2012, 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import os import unittest from fabric import api def test(*test_regexps): """Run tests. :param test_regexps: A list of test regexps to include. """ # late import to ensure the virtual env is active from sst import ( filters, loaders, ) os.environ['DJANGO_SETTINGS_MODULE'] = \ 'u1testutils.selftests.django_project.settings' loader = loaders.TestLoader() suite = loader.suiteClass() suite.addTests(loader.discoverTestsFromTree('u1testutils')) suite.addTests(loader.discoverTestsFromTree('setup_vm')) suite = filters.include_regexps(test_regexps, suite) # List the tests as we run them runner = unittest.TextTestRunner(verbosity=2) res = runner.run(suite) print 'Totals: ran({0}), skipped({1}), errors({2}), failures({3})'.format( res.testsRun, len(res.skipped), len(res.errors), len(res.failures)) if not res.wasSuccessful(): api.abort('Tests failed.') PKrI]z  fabfile/environment.py# -*- coding: utf-8 -*- # Copyright 2012, 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import os import sys from fabric.api import env, local VIRTUALENV = '.env' def bootstrap(): local('rm -rf %s' % VIRTUALENV) local("find -name '*.pyc' -delete") setup_virtualenv() _install_dependencies() def setup_virtualenv(): created = False virtual_env = os.environ.get('VIRTUAL_ENV', None) if virtual_env is None: if not os.path.exists(VIRTUALENV): _create_virtualenv() created = True virtual_env = VIRTUALENV env.virtualenv = os.path.abspath(virtual_env) _activate_virtualenv() return created def _create_virtualenv(): if not os.path.exists(VIRTUALENV): virtualenv_bin_path = local('which virtualenv', capture=True) virtualenv_version = local('{0} {1} --version'.format( sys.executable, virtualenv_bin_path), capture=True) # We only care about major.minor revision numbers version_strings = virtualenv_version.split('.')[0:1] virtualenv_version = [int(x) for x in version_strings] args = '--distribute --clear' if virtualenv_version < [1, 7]: args += ' --no-site-packages' local('{0} {1} {2} {3}'.format(sys.executable, virtualenv_bin_path, args, VIRTUALENV), capture=False) def _activate_virtualenv(): activate_this = os.path.abspath( '{0}/bin/activate_this.py'.format(env.virtualenv)) execfile(activate_this, dict(__file__=activate_this)) # Work around fabric unconditionally removing first entry in sys.path by # adding a dummy one. Agreed with Leo on IRC that the workaround is ok as # the plan is to stop using fabric in the long run and the bug itself is a # blocker. -- vila 2013-09-25 sys.path.insert(0, '/I-dont-exist-blow-me-away-I-dont-care') def _install_dependencies(): # it's possible to get "ImportError: No module named setuptools" # when using pip<1.4 to upgrade a package that depends on setuptools. run_in_virtualenv_local('pip install -U setuptools', capture=False) run_in_virtualenv_local( 'pip install -U -r requirements.txt', capture=False) def run_in_virtualenv_local(command, capture=True): prefix = '' virtual_env = env.get('virtualenv', None) if virtual_env: prefix = '. {0}/bin/activate && '.format(virtual_env) command = prefix + command return local(command, capture=capture) PKrI-eWu1testutils/logging.pyfrom __future__ import absolute_import import logging from unittest import TestCase # Originally written by: # - Guillermo Gonzalez # - Facundo Batista # - Natalia Bidart class LogHandlerTestCase(TestCase): """A mixin that adds a memento loghandler for testing logging.""" class MementoHandler(logging.Handler): """A handler class which stores logging records in a list. From http://nessita.pastebin.com/mgc85uQT """ def __init__(self, *args, **kwargs): """Create the instance, and add a records attribute.""" logging.Handler.__init__(self, *args, **kwargs) self.records = [] def emit(self, record): """Just add the record to self.records.""" self.records.append(record) def check(self, level, msg, check_traceback=False): """Check that something is logged.""" result = False for rec in self.records: if rec.levelname == level: result = str(msg) in rec.getMessage() if not result and check_traceback: result = str(msg) in rec.exc_text if result: break return result def setUp(self): """Add the memento handler to the root logger.""" super(LogHandlerTestCase, self).setUp() self.memento_handler = self.MementoHandler() self.root_logger = logging.getLogger() self.root_logger.addHandler(self.memento_handler) def tearDown(self): """Remove the memento handler from the root logger.""" self.root_logger.removeHandler(self.memento_handler) super(LogHandlerTestCase, self).tearDown() def assertLogLevelContains(self, level, message, check_traceback=False): check = self.memento_handler.check( level, message, check_traceback=check_traceback) msg = ('Expected logging message/s could not be found:\n%s\n' 'Current logging records are:\n%s') expected = '\t%s: %s' % (level, message) records = ['\t%s: %s' % (r.levelname, r.getMessage()) for r in self.memento_handler.records] self.assertTrue(check, msg % (expected, '\n'.join(records))) PKrIcCCu1testutils/mail.py# -*- coding: utf-8 -*- # Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see (1, 3): csrf_middleware = 'django.middleware.csrf.CsrfViewMiddleware' else: csrf_middleware = 'django.contrib.csrf.middleware.CsrfMiddleware' # make sure csrf middleware is enabled self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES settings.MIDDLEWARE_CLASSES = [ 'django.middleware.common.CommonMiddleware', csrf_middleware, 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', ] def tearDown(self): settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES super(CsrfMiddlewareEnabledTestCase, self).tearDown() def get_csrf_token(self, response): # get csrf token csrf_token_re = re.compile(r"name='csrfmiddlewaretoken' value='(.*)'") match = re.search(csrf_token_re, response.content) token = match.group(1) return token class NeverCacheTestCase(TestCase): def is_cacheable(self, response): result = True if 'Expires'in response: expires = response['Expires'] now = http_date(time.time()) result &= expires > now if 'Cache-Control' in response: cache_control = response['Cache-Control'] values = map(string.strip, cache_control.split(',')) for value in values: if '=' in value: k, v = value.split('=') if k == 'max-age': result &= int(v) > 0 break return result PKrIѤu1testutils/sst/config.pyimport os from sst.actions import set_base_url DEFAULT_BASE_URL = 'http://localhost:8000' def set_base_url_from_env(default_url=DEFAULT_BASE_URL): """Set the base URL for SST tests from the env or default.""" set_base_url(get_base_url_from_env(default_url)) def get_base_url_from_env(default_url=DEFAULT_BASE_URL): base_url = os.environ.get('SST_BASE_URL', default_url) return base_url.rstrip('/') PKrIb8u1testutils/sst/__init__.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import logging import re import urlparse import sst.actions from functools import wraps from urllib2 import unquote logger = logging.getLogger('User test') def log_action(log_func): """Decorator to log the call of an action method.""" def middle(f): @wraps(f) def inner(instance, *args, **kwargs): class_name = str(instance.__class__.__name__) docstring = f.__doc__ if docstring: docstring = docstring.split('\n')[0].strip() else: docstring = f.__name__ log_line = '%r: %r. Arguments %r. Keyword arguments: %r.' log_func(log_line, class_name, docstring, args, kwargs) return f(instance, *args, **kwargs) return inner return middle class Page(object): """Base class for the page objects used in acceptance testing. Instance variables: title -- The title of the page. url_path -- The path of the page. is_url_path_regex -- If True, the url path will be considered as a regular expression. headings1 -- A list with the expected text of the h1 elements. If it's empty, the h1 elements will not be checked. headings2 -- A list with the expected text of the h2 elements. If it's empty, the h2 elements will not be checked. qa_anchor -- An string with the expected qa id """ title = None url_path = None is_url_path_regex = False headings1 = [] headings2 = [] qa_anchor = '' def __init__(self, open_page=False): super(Page, self).__init__() if open_page: self._open_page() self.assert_page_is_open() @log_action(logging.info) def _open_page(self): """Open the page.""" if self.is_url_path_regex: raise ValueError( "We can't open a page with a regular expression on the path.") else: assert self.url_path is not None sst.actions.go_to(self.url_path) return self def assert_page_is_open(self): """Assert that the page is open and that no oops are displayed.""" try: assert not self._is_oops_displayed(), \ 'An oops error is displayed: {0}'.format( self._get_oops_element().text) self.assert_url_path() # qa_anchor should take precedence # since checking for text should be # deprecated behaviour if self.qa_anchor: self.assert_qa_anchor() else: self.assert_title() if self.headings1: self.assert_headings1() if self.headings2: self.assert_headings2() except AssertionError: self._log_errors() raise def _is_oops_displayed(self): try: self._get_oops_element() return True except AssertionError: return False def _get_oops_element(self): # TODO this works for U1. Does it work the same for pay and SSO? oops_class = 'yui3-error-visible' return sst.actions.get_element(css_class=oops_class) def assert_title(self): """Assert the title of the page.""" sst.actions.assert_title(self.title) def assert_url_path(self): """Assert the path of the page URL.""" if not self.is_url_path_regex: sst.actions.assert_equal( self._get_current_url_path(), self.url_path) else: self._assert_url_path_match() def _assert_url_path_match(self): # Make sure that there are no more characters at the end of the path. url_path_regexp = self.url_path + '$' current_url_path = self._get_current_url_path() assert re.match(url_path_regexp, current_url_path), \ "The current URL path {0} doesn't match {1}".format( current_url_path, url_path_regexp) def _get_current_url_path(self): current_url = sst.actions.get_current_url() return unquote(urlparse.urlparse(current_url).path) def assert_headings1(self): """Assert the h1 elements of the page.""" self._assert_elements_text('h1', self.headings1) def _assert_elements_text(self, tag, expected_texts): elements_text = self._get_elements_text(tag) assert elements_text == expected_texts, \ 'Expected elements texts: {0}\n' \ 'Actual elements texts: {1}'.format( ', '.join(expected_texts), ', '.join(elements_text)) def _get_elements_text(self, tag=None, css_class=None): return map(lambda x: x.text, sst.actions.get_elements( tag=tag, css_class=css_class)) def assert_headings2(self): """Assert the h2 elements of the page.""" self._assert_elements_text('h2', self.headings2) def assert_qa_anchor(self): """Assert the qa anchor.""" sst.actions.assert_element( tag='html', **{'data-qa-id': self.qa_anchor}) def _log_errors(self): if sst.actions.exists_element(css_class='error'): logger.error( ', '.join(self._get_elements_text(css_class='error'))) class StringHTMLPage(Page): """Fake page for testing, with the source in a string attribute.""" def __init__(self, page_source, url_path, qa_anchor, open_page=False): # Do not call the super __init__ because it will assert that the page # is open. We will do that manually for the tests that require it. self.url_path = url_path self.page_source = page_source self.qa_anchor = qa_anchor self._make_temp_page() if open_page: self._open_page() def _make_temp_page(self): with open(self.url_path, 'w') as page_file: page_file.write(self.page_source) PKrI%u1testutils/sst/selftests/__init__.pyPKrI0u1testutils/sst/selftests/acceptance/__init__.pyPKrI'wTT8u1testutils/sst/selftests/acceptance/test_string_page.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import os import shutil import tempfile from sst import actions, cases import u1testutils.sst class StringPageTestCase(cases.SSTTestCase): xserver_headless = True base_url = 'file://' page_source = ( """ """ ) def setUp(self): super(StringPageTestCase, self).setUp() self.temp_directory = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.temp_directory) self.page_url_path = os.path.join(self.temp_directory, 'test') def test_assert_string_page_is_opened(self): page = u1testutils.sst.StringHTMLPage( self.page_source, self.page_url_path, qa_anchor='test-qa-anchor', open_page=True) page.assert_page_is_open() def test_string_page_not_opened(self): page = u1testutils.sst.StringHTMLPage( self.page_source, self.page_url_path, qa_anchor='test-qa-anchor') # The page is not opened. actions.fails(page.assert_page_is_open) # But its file is created. with open(self.page_url_path) as page_file: page_source = page_file.read() self.assertEqual(page_source, self.page_source) PKrI3VQ1u1testutils/sst/selftests/acceptance/test_page.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import os import tempfile import bs4 import mock import sst.runtests from testtools.matchers import Contains import u1testutils.logging import u1testutils.sst class StringHTMLPage(u1testutils.sst.Page): def __init__(self, page_source, title, headings1, headings2, url_path, is_url_path_regex, qa_anchor=None): self.page_source = page_source self.title = title self.headings1 = headings1 self.headings2 = headings2 page_file_name = None if url_path: self.url_path = url_path else: page_file_name = self._make_temp_page() self.url_path = page_file_name self.is_url_path_regex = is_url_path_regex if qa_anchor: self.qa_anchor = qa_anchor try: super(StringHTMLPage, self).__init__(open_page=True) finally: if page_file_name: os.remove(page_file_name) def _make_temp_page(self): page_file = tempfile.NamedTemporaryFile(delete=False) page_file.write(self.page_source) page_file.close() return page_file.name class AssertPageTestCase( sst.runtests.SSTTestCase, u1testutils.logging.LogHandlerTestCase): page_source = ( """ Test title

Test h1 1

Test h2 1

Test paragraph 1

Test h1 2

Test h2 2

Test paragraph 2

""" ) base_url = 'file://' xserver_headless = True def setUp(self): super(AssertPageTestCase, self).setUp() self.page_kwargs = dict( page_source=self.page_source, title='Test title', headings1=['Test h1 1', 'Test h1 2'], headings2=['Test h2 1', 'Test h2 2'], # If the url path is not specifiec, it will be set to the name of # the temp file. url_path=None, is_url_path_regex=False ) def test_corect_page_is_open(self): StringHTMLPage(**self.page_kwargs) # No error means the page was opened and asserted. def test_wrong_title(self): self.page_kwargs['title'] = 'Wrong title' self.assertRaises(AssertionError, StringHTMLPage, **self.page_kwargs) def test_wrong_url_path(self): self.page_kwargs['url_path'] = 'Wrong path' self.assertRaises(AssertionError, StringHTMLPage, **self.page_kwargs) def test_wrong_headings1_text(self): self.page_kwargs['headings1'] = ['Test h1 1', 'Wrong h1'] error = self.assertRaises( AssertionError, StringHTMLPage, **self.page_kwargs) self.assertEqual( error.message, 'Expected elements texts: Test h1 1, Wrong h1\n' 'Actual elements texts: Test h1 1, Test h1 2') def test_wrong_headings2_text(self): self.page_kwargs['headings2'] = ['Test h2 1', 'Wrong h2'] error = self.assertRaises( AssertionError, StringHTMLPage, **self.page_kwargs) self.assertEqual( error.message, 'Expected elements texts: Test h2 1, Wrong h2\n' 'Actual elements texts: Test h2 1, Test h2 2') def test_assert_url_path_with_regexp(self): page = StringHTMLPage(**self.page_kwargs) page.url_path = '.+' page.is_url_path_regex = True page.assert_url_path() def test_wrong_url_path_with_a_match(self): page = StringHTMLPage(**self.page_kwargs) page.url_path = '/test_path' page.is_url_path_regex = True with mock.patch('sst.actions.get_current_url') as mock_action: mock_url = 'http://test_netloc/wrong/test_path/wrong' mock_action.return_value = mock_url self.assertRaises(AssertionError, page.assert_url_path) def test_wrong_url_path_with_a_suffix(self): page = StringHTMLPage(**self.page_kwargs) page.url_path = '/test_path' page.is_url_path_regex = True with mock.patch('sst.actions.get_current_url') as mock_action: mock_url = 'http://test_netloc/test_path/wrong' mock_action.return_value = mock_url self.assertRaises(AssertionError, page.assert_url_path) def test_assert_url_path_with_query(self): page = StringHTMLPage(**self.page_kwargs) page.url_path = '/test_path' with mock.patch('sst.actions.get_current_url') as mock_action: mock_action.return_value = 'http://test_netloc/test_path?query' page.assert_url_path() def test_assert_page_with_visible_oops(self): soup = bs4.BeautifulSoup(self.page_source) oops_element = soup.new_tag('div') oops_element['class'] = 'yui3-error-visible' oops_element.string = 'Test oops' soup.body.append(oops_element) self.page_kwargs['page_source'] = str(soup) error = self.assertRaises( AssertionError, StringHTMLPage, **self.page_kwargs) self.assertThat(error.message, Contains('Test oops')) def test_assert_wrong_page_with_error(self): soup = bs4.BeautifulSoup(self.page_source) error_element = soup.new_tag('span') error_element['class'] = 'error' error_element.string = 'Test error' soup.body.append(error_element) self.page_kwargs['page_source'] = str(soup) self.page_kwargs['title'] = 'Wrong title' self.assertRaises( AssertionError, StringHTMLPage, **self.page_kwargs) self.assertLogLevelContains('ERROR', 'Test error') def test_assert_correct_qa_anchor(self): # Add the qa anchor to the page source. soup = bs4.BeautifulSoup(self.page_source) soup.html['data-qa-id'] = 'test_anchor' self.page_kwargs['page_source'] = str(soup) self.page_kwargs['qa_anchor'] = 'test_anchor' # The title will not be asserted. self.page_kwargs['title'] = 'Wrong title' # The headings1 will not be asserted. self.page_kwargs['headings1'] = ['Wrong h1'] # The headings 2 will not be asserted. self.page_kwargs['headings2'] = ['Wrong h2'] # If the instantiation doesn't fail, it means we asserted it's the # correct page only checking the anchor. StringHTMLPage(**self.page_kwargs) def test_assert_wrong_qa_anchor(self): # Add the qa anchor to the page source. soup = bs4.BeautifulSoup(self.page_source) soup.html['data-qa-id'] = 'test_anchor' self.page_kwargs['page_source'] = str(soup) self.page_kwargs['qa_anchor'] = 'wrong_anchor' self.assertRaises(AssertionError, StringHTMLPage, **self.page_kwargs) def test_assert_qa_anchor_not_in_html_tag(self): # Add the qa anchor to the page source. soup = bs4.BeautifulSoup(self.page_source) soup.html['data-qa-id'] = 'test_html_anchor' div_element = soup.new_tag('div') div_element['data-qa-id'] = 'test_div_anchor' soup.body.append(div_element) self.page_kwargs['page_source'] = str(soup) self.page_kwargs['qa_anchor'] = 'test_div_anchor' self.assertRaises(AssertionError, StringHTMLPage, **self.page_kwargs) PKrI*u1testutils/sst/selftests/unit/__init__.pyPKrI-E[00,u1testutils/sst/selftests/unit/test_pages.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import contextlib import logging import mock import testtools import u1testutils.logging from u1testutils.sst import log_action, Page class PageWithoutBrowser(Page): """Page double that allows us to test instantiation without a browser.""" def __init__(self, *args, **kwargs): self.page_opened = False self.url_path = '/test/path/' super(PageWithoutBrowser, self).__init__(*args, **kwargs) def _open_page(self): self.page_opened = True with mock.patch('sst.actions.go_to'): super(PageWithoutBrowser, self)._open_page() def assert_page_is_open(self): assert self.page_opened # do not call super() class PageWithoutCheck(Page): def __init__(self): # We overwrite the parent constructor to do nothing, because on the # parent it will try to assert that the page is open. Here we don't # have a real page, but we can test the different page methods with # mocks if we skip the check on init. We also have acceptance tests # that use real pages for full coverage. pass class InitPageTestCase(testtools.TestCase): def test_open_page_on_instantiation(self): page = PageWithoutBrowser(open_page=True) self.assertTrue(page.page_opened) def test_assert_closed_page_on_instantiation(self): self.assertRaises(AssertionError, PageWithoutBrowser, open_page=False) def test_open_page_without_path(self): page = PageWithoutBrowser(open_page=True) page.url_path = None self.assertRaises(AssertionError, page._open_page) def test_open_page_with_path_regex(self): page = PageWithoutBrowser(open_page=True) page.url_path = '/test/.*' page.is_url_path_regex = True self.assertRaises(ValueError, page._open_page) def test_open_page_goes_to_url_path(self): page = PageWithoutCheck() page.url_path = '/test/path' with mock.patch('sst.actions.go_to') as mock_action: returned_page = page._open_page() mock_action.assert_called_with('/test/path') self.assertEquals(page, returned_page) def test_assert_page_is_open_without_qa_anchor(self): # All the mocked methods have acceptance tests. with contextlib.nested( mock.patch.object(Page, '_is_oops_displayed', return_value=False), mock.patch.object(Page, 'assert_title'), mock.patch.object(Page, 'assert_url_path'), ) as mock_checks: with mock.patch.object(Page, 'assert_qa_anchor') as mock_anchor: PageWithoutCheck().assert_page_is_open() for mock_check in mock_checks: mock_check.assert_called_once_with() self.assertFalse(mock_anchor.called) def test_assert_page_is_open_with_anchor(self): # All the mocked methods have acceptance tests. with contextlib.nested( mock.patch.object(Page, '_is_oops_displayed', return_value=False), mock.patch.object(Page, 'assert_qa_anchor'), mock.patch.object(Page, 'assert_url_path') ) as mock_checks: with mock.patch.object(Page, 'assert_title') as mock_title: page = PageWithoutCheck() page.qa_anchor = 'test_anchor' page.assert_page_is_open() for mock_check in mock_checks: mock_check.assert_called_once_with() self.assertFalse(mock_title.called) class PageWithOnlyHeadingsAssertions(PageWithoutCheck): def assert_title(self): pass def assert_url_path(self): pass def _is_oops_displayed(self): pass class PageHeadingsTestCase(testtools.TestCase): def test_assert_page_without_headings1_check(self): with mock.patch.object(Page, 'assert_headings1') as mock_assert: page = PageWithOnlyHeadingsAssertions() page.headings1 = [] page.assert_page_is_open() assert not mock_assert.called def test_assert_page_without_headings2_check(self): with mock.patch.object(Page, 'assert_headings2') as mock_assert: page = PageWithOnlyHeadingsAssertions() page.headings2 = [] page.assert_page_is_open() assert not mock_assert.called class PageWithLogDecorator(PageWithoutCheck): @log_action(logging.info) def do_something_without_docstring(self, *args, **kwargs): pass @log_action(logging.info) def do_something_with_docstring(self, *args, **kwargs): """Do something with docstring.""" pass @log_action(logging.info) def do_something_with_multiline_docstring(self, *args, **kwargs): """Do something with a multiline docstring. This should not be logged. """ pass class PageLoggingTestCase(u1testutils.logging.LogHandlerTestCase): def setUp(self): super(PageLoggingTestCase, self).setUp() self.root_logger.setLevel(logging.INFO) self.page = PageWithLogDecorator() def test_logged_action_without_docstring(self): self.page.do_something_without_docstring( 'arg1', 'arg2', arg3='arg3', arg4='arg4') self.assertLogLevelContains( 'INFO', "'PageWithLogDecorator': 'do_something_without_docstring'. " "Arguments ('arg1', 'arg2'). " "Keyword arguments: {'arg3': 'arg3', 'arg4': 'arg4'}.") def test_logged_action_with_docstring(self): self.page.do_something_with_docstring( 'arg1', 'arg2', arg3='arg3', arg4='arg4') self.assertLogLevelContains( 'INFO', "'PageWithLogDecorator': 'Do something with docstring.'. " "Arguments ('arg1', 'arg2'). " "Keyword arguments: {'arg3': 'arg3', 'arg4': 'arg4'}.") def test_logged_action_with_multiline_docstring(self): self.page.do_something_with_multiline_docstring( 'arg1', 'arg2', arg3='arg3', arg4='arg4') self.assertLogLevelContains( 'INFO', "'PageWithLogDecorator': " "'Do something with a multiline docstring.'. " "Arguments ('arg1', 'arg2'). " "Keyword arguments: {'arg3': 'arg3', 'arg4': 'arg4'}.") PKrIn8uT T u1testutils/pay/api.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import payclient from django.conf import settings import u1testutils.pay.environment from piston_mini_client import auth class APIClient(object): """API client helper for payment tests. It takes the credentials from the Django configuration file to make it easier for the tests to call the API methods. """ def __init__(self, pay_server_url=None): pay_server_url = self._get_pay_server_url(pay_server_url) user = settings.PAY_API_USERNAME password = settings.PAY_API_PASSWORD basic_auth = auth.BasicAuthorizer(user, password) pay_api_url = '{0}/api/2.0/'.format(pay_server_url) self.client = payclient.PaymentServiceAPI( auth=basic_auth, service_root=pay_api_url) def _get_pay_server_url(self, pay_server_url=None): if pay_server_url is None: pay_server_url = u1testutils.pay.environment.get_pay_base_url() else: pay_server_url = pay_server_url.rstrip('/') return pay_server_url def add_new_credit_card( self, shop_id, user, credit_card, billing_address, make_unattended_default=False): """Add a new credit card through the Pay API. Keyword arguments: shop_id -- The identifier of the shop that will add the credit card. user -- The user that will have the new credit card. It must have the openid attribute. credit_card -- The credit card information. It must have the attributes card_type, name_on_cad, card_number, ccv_number, expiration_month and expiration_year. billing_address -- The billing address for the card. It must have the attributes street_line1, state, country and postal_code. The country is the name of the country in english. make_unattended_default -- Boolean that indicates if the credit card must be the new default method for unattended payments. Default is False. """ credit_card_request = payclient.CreditCardRequest.from_data_objects( shop_id, user.openid, credit_card, billing_address, make_unattended_default) return self.client.new_credit_card(data=credit_card_request) PKrI@u1testutils/pay/__init__.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import data import environment from api import APIClient __all__ = [ 'APIClient', 'data', 'environment', ] PKrI[;;u1testutils/pay/environment.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . from django.conf import settings def get_pay_base_url(default='http://localhost:8000'): base_url = getattr(settings, 'PAY_SERVER_URL', default).rstrip('/') return base_url PKrId0¤ u1testutils/pay/data.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see (1, 3): included, excluded = excluded, included self.testcase.setUp() self.assertIn(included, settings.MIDDLEWARE_CLASSES) self.assertNotIn(excluded, settings.MIDDLEWARE_CLASSES) PKrIyH^*u1testutils/selftests/unit/test_logging.pyfrom mock import Mock from u1testutils.logging import LogHandlerTestCase class LogHandlerTestCaseTestCase(LogHandlerTestCase): def test_assertLogLevelContains_check_traceback(self): mock_record = Mock() mock_record.levelname = 'ERROR' mock_record.getMessage.return_value = 'some message' mock_record.exc_text = 'the exception' self.memento_handler.emit(mock_record) self.assertLogLevelContains('ERROR', 'some message', check_traceback=True) def test_MementoHandler_check_check_traceback(self): mock_record = Mock() mock_record.levelname = 'ERROR' mock_record.getMessage.return_value = 'some message' mock_record.exc_text = 'the exception' self.memento_handler.emit(mock_record) result = self.memento_handler.check('ERROR', 'some message', check_traceback=True) self.assertTrue(result) PKrI'u1testutils/selftests/unit/test_mail.py# -*- coding: utf-8 -*- # Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import u1testutils from u1testutils.static import ( test_pep8_conformance, test_pyflakes_analysis, ) class Pep8ConformanceTestCase( test_pep8_conformance.Pep8ConformanceTestCase): packages = [u1testutils] class PyflakesAnalysisTestCase( test_pyflakes_analysis.PyflakesAnalysisTestCase): packages = [u1testutils] PKrI0u1testutils/selftests/django_project/__init__.pyPKrIX90u1testutils/selftests/django_project/settings.py# -*- coding: utf-8 -*- # Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import quopri import re import u1testutils.mail # The regex accepts 6-character tokens handed out by SSO between # 2010 and 2015, and 20-character tokens with reduced charset handed # out starting in November, 2015. TOKEN_REGEX = re.compile( r'/([A-Za-z0-9]{6}|' '[bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ2346789]{20})/', re.S) VALIDATION_LINK_PATTERNS = [ '/confirm-account/', '/+newaccount/', '/+newemail', '/+resetpassword/', ] INVALIDATION_LINK_PATTERNS = [ '/invalidate-email/', ] def _get_verification_data_for_address(email_address): """A private helper for public helpers below. Note: We have two different public helpers here for verification code and link so that functional tests don't need to deal with idioms like: vcode, ignored = get_verification_for_address(email_address). """ email_msg = u1testutils.mail.get_latest_email_sent_to(email_address) vcode = validation_link = invalidation_link = None if email_msg: # The body is encoded as quoted-printable. This affects any # line longer than a certain length. Decode now to not have # to worry about it in the regexen. body = quopri.decodestring(email_msg.get_payload()) links = map(str.strip, re.findall('^(http.*)$', body, re.MULTILINE)) if len(links) > 0: for link in links: if any(pat in link for pat in VALIDATION_LINK_PATTERNS): validation_link = link match = TOKEN_REGEX.search(link) if match: vcode = match.group(1).strip() elif any(pat in link for pat in INVALIDATION_LINK_PATTERNS): invalidation_link = link else: match = re.search( 'Here is your confirmation code:(.*)(Enter|If you made)', body, re.S) if match: vcode = match.group(1).strip() if not vcode: msg = "No verification code found in email. Email is:\n%r." raise AssertionError(msg % body) return vcode, validation_link, invalidation_link def get_verification_code_for_address(email_address): print("Retrieving verification code for %s." % email_address) vcode, _, _ = _get_verification_data_for_address(email_address) print("Verification code retrieved: %s." % vcode) return vcode def get_verification_link_for_address(email_address): print("Retrieving verification link for %s." % email_address) _, link, _ = _get_verification_data_for_address(email_address) print("Verification link retrieved: %s." % link) return link def get_invalidation_link_for_address(email_address): print("Retrieving invalidation link for %s." % email_address) _, _, link = _get_verification_data_for_address(email_address) print("Invalidation link retrieved: %s." % link) return link PKrIMgu1testutils/sso/api.py# Copyright 2012, 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . from ssoclient.v2 import V2ApiClient from u1testutils.sso import environment class APIClient(object): """API client helper for identity tests.""" def __init__(self, sso_server_url=None): sso_server_url = self._get_sso_server_url(sso_server_url) self.client = V2ApiClient(sso_server_url + '/api/v2') def _get_sso_server_url(self, sso_server_url=None): if sso_server_url is None: sso_server_url = environment.get_sso_base_url() else: sso_server_url = sso_server_url.rstrip('/') return sso_server_url def get_account_openid(self, email, password, token_name): response = self.client.login( email=email, password=password, token_name=token_name) data = response.json() openid = data.get('consumer_key') return openid def create_new_account(self, user, captcha_id=None, captcha_solution=None): data = dict( email=user.email, password=user.password, displayname=user.full_name ) if captcha_id is not None: data['captcha_id'] = captcha_id if captcha_solution is not None: data['captcha_solution'] = captcha_solution response = self.client.register(data) return response.status_code == 201 PKrIu1testutils/sso/__init__.pyPKrIyppu1testutils/sso/environment.py# Copyright 2012, 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . from __future__ import absolute_import from django.conf import settings def get_sso_base_url(default='http://localhost:8000'): base_url = getattr(settings, 'OPENID_SSO_SERVER_URL', default).rstrip('/') return base_url PKrIZٌp^ ^ u1testutils/sso/data.py# Copyright 2012, 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import uuid from django.conf import settings from u1testutils.sso import api class User(object): def __init__(self, full_name, email, password, openid=None): self.full_name = full_name self.email = email self.password = password self._openid = openid def __repr__(self): return '%s(%r)' % (self.__class__, self.__dict__) @property def openid(self): if self._openid is None: token_name = "token-%s" % self.email api_client = api.APIClient() self._openid = api_client.get_account_openid( email=self.email, password=self.password, token_name=token_name) return self._openid @classmethod def make_unique(cls, unique_id=None): """Return a unique user. unique_id -- If a unique_id is passed, it will be used to uniquely identify the data of the new user. If None is passed as unique_id, then a uuid will be used. Default is None. """ if unique_id is None: unique_id = str(uuid.uuid1()) full_name = 'Test user ' + unique_id email = 'test+{0}@example.com'.format(unique_id) password = 'Hola123*' return cls(full_name, email, password) @classmethod def make_from_configuration(cls, new_user=False, unique_id=None): """Return a user taking its credentials from the configuration files. Keyword arguments: new_user -- If True, the full name and email of the user will have a UUID to make them practically unique. In this case, the email will be formed with the value of the EMAIL_ADDRESS_PATTERN configuration variable. If False, the full name and email will be the values of the SSO_TEST_ACCOUNT_FULL_NAME and SSO_TEST_ACCOUNT_EMAIL configuration variables, respectively. In both cases, the password will be the value of the SSO_TEST_ACCOUNT_PASSWORD configuration variable. Default is False. unique_id -- If new_user is True and a unique_id is passed, then it will be used to uniquely identify the data of the new user. If None is passed as unique_id, then a uuid will be used. Default is None. """ if new_user: if unique_id is None: unique_id = str(uuid.uuid1()) full_name = 'Test user ' + unique_id email = settings.EMAIL_ADDRESS_PATTERN % unique_id else: full_name = settings.SSO_TEST_ACCOUNT_FULL_NAME email = settings.SSO_TEST_ACCOUNT_EMAIL password = settings.SSO_TEST_ACCOUNT_PASSWORD return cls(full_name, email, password) PKrI44u1testutils/sso/sst/pages.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import logging import sst.actions import u1testutils.sst from u1testutils.sst import log_action logger = logging.getLogger('User test') class LogIn(u1testutils.sst.Page): """Log in page of the Ubuntu Single Sign On website. This is a subclass of the PageWithAnonymousSubheader object to add methods for the actions available in this page. """ url_path = '(/|/\+login)' is_url_path_regex = True qa_anchor = 'login' @log_action(logging.info) def _open_page(self): """Open the page. This overwrites the method from the base Page class in order to choose one of the possible URL paths to open the page. """ sst.actions.go_to('/+login') return self @log_action(logging.info) def log_in_to_site_recognized(self, user=None): """Fill the log in form and continue to the site that requested it. Keyword arguments: user -- The user credentials. It must have the attributes email and password. If None is passed as the user, it means that the user has already started session on the identity provider and it's not necessary to enter the credentials again. """ self._log_in(user) @log_action(logging.info) def log_in_to_site_not_recognized(self, user=None): """Fill the log in form and continue to the next step. As the site is not recognized, the next step is the page where the user can select the information that will be send to the site. Keyword arguments: user -- The user credentials. It must have the attributes email and password. If None is passed as the user, it means that the user has already started session on the identity provider and it's not necessary to enter the credentials again. """ self._log_in(user) return SiteNotRecognized() def _log_in(self, user=None): if user is not None: sst.actions.wait_for(sst.actions.assert_title, 'Log in') self._fill_log_in_form(user.email, user.password) self._click_continue_button() else: # If None is passed as the user, it means that the user has # already started session on the identity provider and it's not # necessary to enter the credentials again. pass def _fill_log_in_form(self, email, password): email_field = sst.actions.get_element_by_css( '*[data-qa-id="login_form"] #id_email') sst.actions.write_textfield(email_field, email) password_field = sst.actions.get_element_by_css( '*[data-qa-id="login_form"] #id_password') sst.actions.write_textfield(password_field, password) def _click_continue_button(self): continue_button = sst.actions.get_element_by_css( '*[data-qa-id="login_button"]') sst.actions.click_button(continue_button) @log_action(logging.info) def go_to_create_new_account(self): """Go to the Create new account page.""" self._click_create_new_account() return self._new_create_account_page() def _click_create_new_account(self): link = sst.actions.get_element_by_css( '*[data-qa-id="create_account_link"]') sst.actions.click_link(link) def _new_create_account_page(self): # override in children return CreateAccount() class LogInFromRedirect(LogIn): url_path = '/.*/\+decide' is_url_path_regex = True @log_action(logging.info) def go_to_create_new_account(self): """Go to the Create new account page.""" self._click_create_new_account() return CreateAccountFromRedirect() class PageWithAnonymousSubheader(u1testutils.sst.Page): def __init__(self, open_page=False): super(PageWithAnonymousSubheader, self).__init__(open_page) self.subheader = _AnonymousSubheader() class _AnonymousSubheader(object): @log_action(logging.info) def go_to_log_in_or_create_account(self): link = sst.actions.get_element_by_css('*[data-qa-id="login_link"]') sst.actions.click_link(link) return LogIn() class CreateAccount(PageWithAnonymousSubheader): """Create account page of the Ubuntu Single Sign On website. This is a subclass of the PageWithAnonymousSubheader object to add methods for the actions available in this page. """ url_path = '/+new_account' qa_anchor = 'new_account' @log_action(logging.info) def create_ubuntu_sso_account(self, user): """Fill the new account form and continue to the next step. Keyword arguments: user -- The user credentials. It must have the attributes email and password. """ self._fill_new_account_form(user) self._click_continue() def _fill_new_account_form(self, user, password_confirmation=None): if password_confirmation is None: password_confirmation = user.password sst.actions.write_textfield('id_displayname', user.full_name) email_field = sst.actions.get_element_by_css( '*[data-qa-id="create_account_form"] #id_email') sst.actions.write_textfield(email_field, user.email) password_field = sst.actions.get_element_by_css( '*[data-qa-id="create_account_form"] #id_password') sst.actions.write_textfield(password_field, user.password) sst.actions.write_textfield( 'id_passwordconfirm', password_confirmation) captcha_field = 'recaptcha_response_field' if sst.actions.exists_element(id=captcha_field): sst.actions.write_textfield(captcha_field, 'ignored') tos_field = 'id_accept_tos' if sst.actions.exists_element(id=tos_field): sst.actions.set_checkbox_value(tos_field, True) def _click_continue(self): continue_button = sst.actions.get_element_by_css( '*[data-qa-id="register_button"]') sst.actions.click_button(continue_button) class CreateAccountFromRedirect(CreateAccount): url_path = '/.*/\+new_account' is_url_path_regex = True class UnifiedLogInCreateAccount(LogIn, CreateAccount): """Log in or create account, unified experience.""" url_path = LogIn.url_path is_url_path_regex = LogIn.is_url_path_regex qa_anchor = 'login' def _click_create_new_account(self): new_user_intention = sst.actions.get_element_by_css( '*[data-qa-id="user_intention_create"]') sst.actions.click_element(new_user_intention) def _new_create_account_page(self): return self class UnifiedLogInCreateAccountFromRedirect(UnifiedLogInCreateAccount): url_path = '/.*/\+decide' is_url_path_regex = True class AccountCreationMailSent(PageWithAnonymousSubheader): """AccountCreation mail sent page of the Ubuntu Single Sign On website. This is a subclass of the PageWithAnonymousSubheader object to add methods for the actions available in this page. """ url_path = '/+new_account' qa_anchor = 'new_account' @log_action(logging.info) def confirm_email_to_site_recognized(self, confirmation_code): """Confirm email and continue to the site that requested the log in. Keyword arguments: confirmation_code -- The confirmation code sent to the user email address. """ self._confirm_email(confirmation_code) @log_action(logging.info) def confirm_email_to_site_not_recognized(self, confirmation_code): """Enter the confirmation code and continue to the next step. As the site is not recognized, the next step is the page where the user can select the information that will be send to the site. Keyword arguments: confirmation_code -- The confirmation code sent to the user email address. """ self._confirm_email(confirmation_code) return SiteNotRecognized() def _confirm_email(self, confirmation_code): self._enter_confirmation_code(confirmation_code) self._click_continue_button() def _enter_confirmation_code(self, confirmation_code): confirmation_code_text_field = sst.actions.get_element( name='confirmation_code') sst.actions.write_textfield(confirmation_code_text_field, confirmation_code) def _click_continue_button(self): continue_button = sst.actions.get_element_by_css( '*[data-qa-id="complete_account_creation"]') sst.actions.click_button(continue_button) class PageWithUserSubheader(u1testutils.sst.Page): def __init__(self, open_page=False): # Some pages will need to take the user name from the subheader in # order to assert that they are open. We need the subheader before # that check. self.subheader = UserSubheader() super(PageWithUserSubheader, self).__init__(open_page) class UserSubheader(object): @log_action(logging.info) def log_out(self): """Log out from the web site.""" link = sst.actions.get_element_by_css('*[data-qa-id="logout_link"]') sst.actions.click_link(link) return YouHaveBeenLoggedOut() def get_user_name(self): return sst.actions.get_element_by_css('*[data-qa-id="user_name"]').text class CompleteEmailValidation(PageWithUserSubheader): """Complete email address validation page. This is a subclass of the Page object. It adds methods for the actions available in this page. """ url_path = '/token/.+/\+newemail/.+@.+' is_url_path_regex = True qa_anchor = 'confirm_email' def __init__(self, open_page=False): super(CompleteEmailValidation, self).__init__(open_page) def _click_continue_button(self): continue_button = sst.actions.get_element_by_css( '*[data-qa-id="confirm_email_validation"]') sst.actions.click_button(continue_button) def _click_cancel(self): cancel_link = sst.actions.get_element_by_css( '*[data-qa-id="cancel_email_validation"]') sst.actions.click_link(cancel_link) @log_action(logging.info) def confirm(self): self._click_continue_button() return YourAccount() @log_action(logging.info) def cancel(self): return YourAccount() class SiteNotRecognized(u1testutils.sst.Page): """Site not Recognized page of the Ubuntu Single Sign On website. This is a subclass of the Page object. It overrides the assert_title method to check only the first part of the title, and adds methods for the actions available in this page. """ url_path = '/.+/\+decide' is_url_path_regex = True qa_anchor = 'decide' def assert_title(self): """Assert that the page is open. We use a regular expression because the title has the URL of the site that requested the log in plus some tokens, and we don't need to check that. """ sst.actions.assert_title_contains(self.title, regex=True) @log_action(logging.info) def make_all_information_available_to_website(self): """Select all the user available information. This information will be send to the site that requested the log in. """ information_checkboxes = self._get_information_checkboxes() for checkbox in information_checkboxes: if checkbox.is_displayed(): sst.actions.set_checkbox_value(checkbox, True) return self def _get_information_checkboxes(self): return sst.actions.wait_for( sst.actions.get_elements_by_css, '*[data-qa-id="info-items"] input[type="checkbox"]') @log_action(logging.info) def yes_sign_me_in(self): """Accept to sign in to the site not recognized and go back to it.""" sign_me_in_button = sst.actions.get_element_by_css( '*[data-qa-id="rp_confirm_login"]') sst.actions.click_button(sign_me_in_button) class YourAccount(PageWithUserSubheader): """Your account page of the Ubuntu Single Sign On website. This is a subclass of the Page object. It extends the constructor to to receive the user name, and adds methods for the actions available in this page. Instance variables: title -- The title of the page. It's build when the page is instantiated using the user name. """ url_path = '/' qa_anchor = 'edit_account' def __init__(self, open_page=False): super(YourAccount, self).__init__(open_page) class YouHaveBeenLoggedOut(PageWithAnonymousSubheader): """Your account page of the Ubuntu Single Sign On website.""" url_path = '/+logout' qa_anchor = 'logout' PKrIWu1testutils/sso/sst/__init__.py# Copyright 2012, 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import logging import sst.actions from u1testutils.sso import mail from u1testutils.sso.sst import pages logger = logging.getLogger('User test') def create_new_account(user, is_site_recognized=True): """Create a new account on Ubuntu Single Sign On. The browser must be on the Log in page. If the account creation succeeds, the browser will be back on the site that requested the log in. Keyword arguments: user -- The user object with the information for the new account. It must have the full_name, email and password attributes. is_site_recognized -- Boolean indicating if the site that requested the log in is recognized. If it is not recognized, all the user available information will be send to it. Default is True. """ log_in = pages.UnifiedLogInCreateAccountFromRedirect() create_account = log_in.go_to_create_new_account() create_account.create_ubuntu_sso_account(user) if not is_site_recognized: site_not_recognized = pages.SiteNotRecognized() site_not_recognized.make_all_information_available_to_website() site_not_recognized.yes_sign_me_in() else: _assert_site_not_recognized_page_not_opened() def _assert_site_not_recognized_page_not_opened(): try: sst.actions.fails(pages.SiteNotRecognized) except AssertionError: suggestion = ( 'Please check that you are accessing SSO from a server that is ' 'trusted. Otherwise, the unexpected Site Not Recognized page will ' 'be opened.') logger.error(suggestion) raise def validate_user_email(user): """Validate user's email address. The user must be logged in and received the verification email (for example, right after registration). If the this succeeds, the browser will be redirected to user's account page. Keyword arguments: user -- The user object with the information for the log in. It must have the email and full_name attributes. """ vlink = mail.get_verification_link_for_address(user.email) sst.actions.go_to(vlink) validate_email = pages.CompleteEmailAddressValidation() validate_email.confirm() def sign_in(user=None, is_site_recognized=True): """Log in with an Ubuntu Single Sign On account. The browser must be on the Log in page. If the log in succeeds, the browser will be back on the site that requested the log in. Keyword arguments: user -- The user object with the information for the log in. It must have the email and password attributes. If its value is None, it means that the user has already started a session on Ubuntu Single Sign On and it's not necessary to enter the credentials again. Default is None. is_site_recognized -- Boolean indicating if the site that requested the log in is recognized. If it is not recognized, all the user available information will be send to it. Default is True. """ if is_site_recognized: if user is not None: log_in = pages.LogInFromRedirect() log_in.log_in_to_site_recognized(user) else: # User is already signed in so SSO will just redirect back to the # main site. pass _assert_site_not_recognized_page_not_opened() else: if user is not None: log_in = pages.LogInFromRedirect() site_not_recognized = log_in.log_in_to_site_not_recognized(user) else: # User is already signed in so SSO will just show the site not # recognized page. site_not_recognized = pages.SiteNotRecognized() site_not_recognized.make_all_information_available_to_website() site_not_recognized.yes_sign_me_in() def log_out(): """Log out from the Ubuntu Single Sign On site. The browser must be on the Single Sign On site, and the user must have the session started there. If the log out succeeds, the browser will be on the Ubuntu Single Sign On logout page. """ your_account = pages.YourAccount() return your_account.subheader.log_out() PKrI%u1testutils/sso/selftests/__init__.pyPKrI0u1testutils/sso/selftests/acceptance/__init__.pyPKrI\++8u1testutils/sso/selftests/acceptance/test_sst_helpers.py# Copyright 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License version 3, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import os import shutil import tempfile import uuid import testscenarios from sst import cases import u1testutils.sst from u1testutils import logging from u1testutils.sso import ( data, sst as sso_sst, ) _WRONG_PAGE_SOURCE = ( """ """ ) _SITE_NOT_RECOGNIZED_PAGE_SOURCE = ( """
create account """ ) _UNIFIED_CREATE_ACCOUNT_FROM_REDIRECT_PAGE_SOURCE = ( """ Log in