Source code for functional_tests.tests

# Copyright 2013-2018 The Distro Tracker Developers
# See the COPYRIGHT file at the top-level directory of this distribution and
# at https://deb.li/DTAuthors
#
# This file is part of Distro Tracker. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution and at https://deb.li/DTLicense. No part of Distro Tracker,
# including this file, may be copied, modified, propagated, or distributed
# except according to the terms contained in the LICENSE file.

"""
Functional tests for Distro Tracker.
"""
import os
import time
from unittest import mock

from django.contrib.auth import get_user_model
from django.core import mail
from django.urls import reverse

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC  # noqa
from selenium.webdriver.support.ui import WebDriverWait

from distro_tracker.accounts.models import (
    AddEmailConfirmation,
    MergeAccountConfirmation,
    ResetPasswordConfirmation,
    UserRegistrationConfirmation,
)
from distro_tracker.core.models import (
    BinaryPackageName,
    ContributorName,
    Keyword,
    PackageName,
    SourcePackage,
    SourcePackageName,
    Subscription,
    Team,
    TeamMembership,
)
from distro_tracker.core.panels import BasePanel
from distro_tracker.test import LiveServerTestCase

from django_email_accounts.models import UserEmail


[docs]class SeleniumTestCase(LiveServerTestCase): """ A class which includes some common functionality for all tests which use Selenium. """
[docs] def setUp(self): os.environ['NO_PROXY'] = 'localhost,127.0.0.1,127.0.1.1' for path in ("/usr/bin", "/usr/lib/chromium-browser", "/usr/lib/chromium"): chromedriver = os.path.join(path, "chromedriver") if os.path.exists(chromedriver): break os.environ["webdriver.chrome.driver"] = chromedriver options = webdriver.ChromeOptions() options.add_argument('no-sandbox') self.browser = webdriver.Chrome(chromedriver, chrome_options=options) self.browser.implicitly_wait(3) self.browser.set_page_load_timeout(3) self.browser.set_script_timeout(3)
[docs] def tearDown(self): self.browser.quit()
[docs] def get_page(self, relative): """ Helper method which points the browser to the absolute URL based on the given relative URL and the server's live_server_url. """ self.browser.get(self.absolute_url(relative))
[docs] def wait_response(self, seconds): time.sleep(seconds)
[docs] def absolute_url(self, relative): """ Helper method which builds an absolute URL where the live_server_url is the root. """ return self.live_server_url + relative
[docs] def input_to_element(self, tag_id, text): """ Helper method which sends the text to the element with the given ID. """ element = self.browser.find_element_by_id(tag_id) element.send_keys(text)
[docs] def clear_element_text(self, tag_id): """ Helper method which removes any text already found in the element with the given ID. """ element = self.browser.find_element_by_id(tag_id) element.clear()
[docs] def send_enter(self, tag_id): """ Helper method which sends the enter key to the element with the given ID. """ element = self.browser.find_element_by_id(tag_id) element.send_keys(Keys.ENTER)
[docs] def assert_in_page_body(self, text): body = self.browser.find_element_by_tag_name('body') self.assertIn(text, body.text)
[docs] def assert_not_in_page_body(self, text): body = self.browser.find_element_by_tag_name('body') self.assertNotIn(text, body.text)
[docs] def assert_element_with_id_in_page(self, element_id, custom_message=None): """ Helper method which asserts that the element with the given ID can be found in the current browser page. """ if custom_message is None: custom_message = element_id + " not found in the page." try: self.browser.find_element_by_id(element_id) except NoSuchElementException: self.fail(custom_message)
[docs] def assert_element_with_class_in_page(self, class_name, custom_message=None): if custom_message is None: custom_message = class_name + " not found in the page." try: self.browser.find_element_by_class_name(class_name) except NoSuchElementException: self.fail(custom_message)
[docs] def get_element_by_id(self, element_id): try: return self.browser.find_element_by_id(element_id) except NoSuchElementException: return None
[docs] def get_element_by_class(self, class_name): try: return self.browser.find_element_by_class_name(class_name) except NoSuchElementException: return None
[docs] def assert_current_url_equal(self, url): """ Helper method which asserts that the given URL equals the current browser URL. The given URL should not include the domain. """ self.assertEqual( self.browser.current_url, self.absolute_url(url))
[docs] def set_mock_http_response(self, mock_requests, text, status_code=200): mock_response = mock_requests.models.Response() mock_response.status_code = status_code mock_response.text = text mock_requests.get.return_value = mock_response mock_requests.head.return_value = mock_response
[docs] def wait(self, timeout=2): return WebDriverWait(self.browser, timeout)
[docs] def wait_until_url_changes(self): """ Stop processing until the URL changes. Adding such a call makes it easy to use the browser interactively at the place of your choice and debug the content of the page currently displayed. """ self.wait(600).until(EC.url_changes(self.browser.current_url))
[docs]def create_test_panel(panel_position): """ Helper test decorator which creates a TestPanel before running the test and unregisters it when it completes, making sure all tests are ran in isolation. """ def decorator(func): def wrap(self): class TestPanel(BasePanel): html_output = "Hello, world" position = panel_position try: ret = func(self) finally: TestPanel.unregister_plugin() return ret return wrap return decorator
[docs]class PackagePageTest(SeleniumTestCase):
[docs] def setUp(self): super(PackagePageTest, self).setUp() self.package = SourcePackageName.objects.create(name='dummy-package') SourcePackageName.objects.create(name='second-package') self.binary_package = BinaryPackageName.objects.create( name='binary-package') self.binary_package.sourcepackage_set.create( source_package_name=self.package, version='1.0.0')
[docs] def get_package_url(self, package_name): """ Helper method returning the URL of the package with the given name. """ return reverse('dtracker-package-page', kwargs={ 'package_name': package_name, })
[docs] def send_text_to_package_search_form(self, text): """ Helper function to send text input to the package search form. """ search_form = self.browser.find_element_by_id('package-search-form') text_box = search_form.find_element_by_name('package_name') # Make sure any old text is removed. text_box.clear() text_box.send_keys(text) text_box.send_keys(Keys.ENTER)
[docs] def test_access_source_package_page_by_url(self): """ Tests that users can get to a package's page by going straight to its URL. """ # The user tries to visit a package's page. self.get_page(self.get_package_url(self.package.name)) # The browser shows the package's page, indicated in the page # title. self.assertIn(self.package.name, self.browser.title) # It is displayed in the content, as well. package_name_element = self.browser.find_element_by_tag_name('h1') self.assertEqual(package_name_element.text, self.package.name) # The user sees a footer with general information self.assert_element_with_id_in_page('footer') # There is a header with a form with a text box where the user can # type in the name of a package to get to its page. self.assert_element_with_id_in_page('package-search-form', "Form not found") # So, the uer types the name of another source package... self.send_text_to_package_search_form('second-package') # This causes the new pacakge's page to open. self.assertEqual( self.browser.current_url, self.absolute_url(self.get_package_url('second-package'))) # The user would like to see the source package page for a binary # package. The user types the package name in the search form. self.send_text_to_package_search_form('binary-package') self.assertEqual( self.browser.current_url, self.absolute_url(self.get_package_url(self.package.name))) # However, when the user tries a package name which does not exist, # they expects the response page to state this. self.send_text_to_package_search_form('no-exist') self.assert_in_page_body('Package no-exist does not exist')
[docs] def test_access_package_page_from_index(self): """ Tests that the user can access a package page starting from the index and using the provided form. """ # The user opens the start page self.get_page('/') # The page title should show the index page of the site. self.assertIn('Package Tracker', self.browser.title) # There is a form to use for access to packages. self.assert_element_with_id_in_page('package-search-form') # The user types in a name of a known source package... self.send_text_to_package_search_form(self.package.name) # ...and expects the response to show the package page. self.assertEqual( self.browser.current_url, self.absolute_url(self.package.get_absolute_url())) # The user goes back to the index... self.browser.back() # ...and tries using the form to access a package page, but the package # does not exist. self.send_text_to_package_search_form('no-exist') self.assert_in_page_body('Package no-exist does not exist')
[docs] @create_test_panel('left') def test_include_panel_left(self): """ Tests whether a package page includes a panel in the left side column. """ self.get_page(self.get_package_url(self.package.name)) self.assert_element_with_id_in_page('dtracker-package-left') column = self.browser.find_element_by_id('dtracker-package-left') self.assertIn("Hello, world", column.text)
[docs] @create_test_panel('center') def test_include_panel_center(self): """ Tests whether a package page includes a panel in the center column. """ self.get_page(self.get_package_url(self.package.name)) self.assert_element_with_id_in_page('dtracker-package-center') column = self.browser.find_element_by_id('dtracker-package-center') self.assertIn("Hello, world", column.text)
[docs] @create_test_panel('right') def test_include_panel_right(self): """ Tests whether a package page includes a panel in the right side column. """ self.get_page(self.get_package_url(self.package.name)) self.assert_element_with_id_in_page('dtracker-package-right') column = self.browser.find_element_by_id('dtracker-package-right') self.assertIn("Hello, world", column.text)
User = get_user_model()
[docs]class RepositoryAdminTest(SeleniumTestCase):
[docs] def setUp(self): super(RepositoryAdminTest, self).setUp() # Create a superuser which will be used for the tests User.objects.create_superuser( main_email='admin@example.net', password='admin' )
[docs] def login_to_admin(self, username='admin@example.net', password='admin'): """ Helper method which logs the user with the given credentials to the admin console. """ self.get_page('/admin/') self.input_to_element('id_username', username) self.input_to_element('id_password', password) self.send_enter('id_password')
[docs] @mock.patch('distro_tracker.core.admin.requests') @mock.patch('distro_tracker.core.retrieve_data.requests') def test_repository_add(self, mock_requests, mock_requests2): """ Tests that an admin user is able to add a new repository. """ # The user first logs in to the admin panel with their credentials. self.login_to_admin() self.wait_response(1) # They expect the log in to succeed, responding with the # administration page. self.assertIn('Site administration', self.browser.title) # The user now wants to go to the repositories management # page. The necessary link can be found in the page. try: self.browser.find_element_by_link_text("Repositories") except NoSuchElementException: self.fail("Link for repositories management not found in the admin") # Clicking on it opens a new page to manage repositories. self.click_link("Repositories") self.assertIn( 'Repositories', self.browser.find_element_by_class_name('breadcrumbs').text ) # The user now wants to create a new repository. self.browser.find_element_by_css_selector('a.addlink').click() self.assert_in_page_body("Add repository") try: save_button = self.browser.find_element_by_css_selector( 'input.default') except NoSuchElementException: self.fail("Could not find the save button") # The user tries clicking the save button immediately save_button.click() # But this causes an error since there are some required fields. self.assert_in_page_body('Please correct the errors below') # The user enters a name and shorthand for the repository. self.input_to_element('id_name', 'stable') self.input_to_element('id_shorthand', 'stable') # They want to create the repository by using a sources.list entry. self.input_to_element( 'id_sources_list_entry', 'deb http://ftp.bad.debian.org/debian stable' ) # === Make sure that no actual HTTP requests are sent out === self.set_mock_http_response( mock_requests, 'Suite: stable\n' 'Codename: wheezy\n' 'Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64' ' kfreebsd-i386 mips mipsel powerpc s390 s390x sparc\n' 'Components: main contrib non-free\n' 'Version: 7.1\n' 'Description: Debian 7.1 Released 15 June 2013\n' ) self.set_mock_http_response(mock_requests2, 'OK') # The user decides to save by hitting the enter key self.send_enter('id_sources_list_entry') # The response page shows confirmation the repository has been added. self.assert_in_page_body("added successfully") # The page also shows the information of the newly added repository. self.assert_in_page_body('CODENAME') self.assert_in_page_body('wheezy') self.assert_in_page_body('COMPONENTS') self.assert_in_page_body('main contrib non-free') # The user now wants to add another repository self.browser.find_element_by_css_selector('a.addlink').click() # This time, they want to enter all the necessary data manually. self.input_to_element('id_name', 'testing') self.input_to_element('id_shorthand', 'testing') self.input_to_element('id_uri', 'http://ftp.bad.debian.org/debian') self.input_to_element('id_suite', 'testing') self.input_to_element('id_codename', 'jessie') self.input_to_element('id_components', 'main non-free') # Finally the user clicks the save button self.browser.find_element_by_css_selector('input.default').click() # The response page confirms that the new repository has been created. self.assert_in_page_body("added successfully") # The page also shows the information of the newly added repository. self.assert_in_page_body('jessie') self.assert_in_page_body('main non-free')
[docs]class UserAccountsTestMixin(object): """ Defines some common methods for all user account tests. """
[docs] def setUp(self): super(UserAccountsTestMixin, self).setUp() self.package = SourcePackageName.objects.create(name='dummy-package') self.password = 'asdf' self.user = User.objects.create_user( main_email='user@domain.com', password=self.password, first_name='', last_name='')
[docs] def refresh_user_object(self): """ The method retrieves the user instance from the database forcing any cached properties to reload. This can be used when the user's properties need to be tested for updated values. """ self.user = User.objects.get(main_email=self.user.main_email)
[docs] def get_login_url(self): return reverse('dtracker-accounts-login')
[docs] def get_profile_url(self): return reverse('dtracker-accounts-profile')
[docs] def get_package_url(self, package_name): return reverse('dtracker-package-page', kwargs={ 'package_name': package_name, })
[docs] def create_user(self, main_email, password, associated_emails=()): u = User.objects.create_user(main_email, password=password) for associated_email in associated_emails: u.emails.create(email=associated_email) return u
[docs] def log_in(self, user=None, password=None): """ Helper method which logs the user in, without taking any shortcuts (it goes through the steps to fill in the form and submit it). """ if user is None: user = self.user if password is None: password = self.password self.get_page(self.get_login_url()) self.input_to_element('id_username', user.main_email) self.input_to_element('id_password', password) self.send_enter('id_password')
[docs] def log_out(self): """ Helper method which logs the user out. """ self.browser.find_element_by_id("account-logout").click()
[docs]class UserRegistrationTest(UserAccountsTestMixin, SeleniumTestCase): """ Tests for the user registration story. """
[docs] def setUp(self): super(UserRegistrationTest, self).setUp() # User registration tests do not want any already registered users UserEmail.objects.all().delete() User.objects.all().delete()
[docs] def get_confirmation_url(self, message): """ Extracts the confirmation URL from the given email message. Returns ``None`` if the message did not contain a confirmation URL. """ match = self.re_confirmation_url.search(message.body) if not match: return None return match.group(1)
[docs] def get_registration_url(self): return reverse('dtracker-accounts-register')
[docs] def test_user_register(self): profile_url = self.get_profile_url() password_form_id = 'form-reset-password' user_email = 'user@domain.com' # === Preconditions: === # === No registered users or command confirmations === self.assertEqual(0, User.objects.count()) self.assertEqual(0, UserRegistrationConfirmation.objects.count()) # === Start of the test. === # The user opens the front page self.get_page('/') # The page shows a link to a registration page try: self.browser.find_element_by_link_text("Register") except NoSuchElementException: self.fail("Link for user registration not found on the front page") # Upon clicking the link, the user is taken to the registration page self.click_link("Register") self.assert_current_url_equal(self.get_registration_url()) # The page shows a registration form self.assert_element_with_id_in_page('form-register') # The user inputs only the email address self.input_to_element("id_main_email", user_email) self.send_enter('id_main_email') # The user is notified of a successful registration self.assert_current_url_equal( reverse('dtracker-accounts-register-success')) # The user receives an email with the confirmation URL self.assertEqual(1, len(mail.outbox)) # === Get confirmation key from the database === self.assertEqual(1, UserRegistrationConfirmation.objects.count()) confirmation = UserRegistrationConfirmation.objects.all()[0] self.assertIn(confirmation.confirmation_key, mail.outbox[0].body) # The user goes to the confirmation URL confirmation_url = reverse( 'dtracker-accounts-confirm-registration', kwargs={ 'confirmation_key': confirmation.confirmation_key }) self.get_page(confirmation_url) # The response page shows a password entry form. self.assert_element_with_id_in_page(password_form_id) # However, the user first goes back to the index... self.get_page('/') # ...and then goes back to the confirmation page which is still valid self.get_page(confirmation_url) password = 'asdf' self.input_to_element('id_password1', password) self.input_to_element('id_password2', password) self.send_enter('id_password2') # The user is now successfully logged in with their profile page open. self.assert_current_url_equal(profile_url) # A message confirms that the user is now registered. self.assert_in_page_body('You have successfully registered to the') # When the user tries opening the confirmation page for the same key # again, it is no longer valid self.get_page(confirmation_url) with self.assertRaises(NoSuchElementException): self.browser.find_element_by_id(password_form_id) # This is because the confirmation model instance has been removed... self.assertEqual(0, UserRegistrationConfirmation.objects.count()) # The user goes back to the profile page and this time there is no # message confirming their registration. self.get_page(profile_url) self.assert_not_in_page_body('You have successfully registered to the') # The user now wishes to log out self.assert_in_page_body('Log out') self.click_link('Log out') # Because the session was on a private page, the user is now # redirected back to the index page. self.assert_current_url_equal('/') # From there, the user tries logging in with their new account. self.click_link('Log in') self.input_to_element('id_username', user_email) self.input_to_element('id_password', password) self.send_enter('id_password') # The response is the user's profile page. self.assert_current_url_equal(self.get_profile_url())
[docs] def test_register_email_already_has_subscriptions(self): """ Tests that a user can register using an email which already has subscriptions to some packages. """ # === Set up such an email === email = UserEmail.objects.create(email='user@domain.com') package_name = 'dummy-package' Subscription.objects.create_for( email=email, package_name=package_name) # The user opens the registration page and enters the email self.get_page(self.get_registration_url()) self.input_to_element('id_main_email', email.email) self.send_enter('id_main_email') self.wait_response(1) # The user is successfully registered self.assertEqual(1, User.objects.count()) user = User.objects.all()[0] self.assertEqual(email.email, user.main_email) self.assertEqual( [email.email], [e.email for e in user.emails.all()]) # A message confirms that the user's registration is successful. self.assert_in_page_body( 'Congratulations, the registration is almost over.') # The existing subscriptions are not removed self.assertTrue(user.is_subscribed_to(package_name))
[docs] def test_user_registered(self): """ Tests that a user registration fails when there is already a registered user with the given email. """ # === Set up a registered user === user_email = 'user@domain.com' associated_email = 'email@domain.com' self.create_user(user_email, 'asdf', [associated_email]) # The user goes to the registration page self.get_page(self.get_registration_url()) # The user enters the already existing user's email self.input_to_element('id_main_email', user_email) self.send_enter('id_main_email') # The response is the same page... self.assert_current_url_equal(self.get_registration_url()) # ... and shows an error message for the duplicate email address. self.assert_in_page_body('email address is already in use') # The user now tries using another email address associated # with the existing user account. self.clear_element_text('id_main_email') self.input_to_element('id_main_email', associated_email) self.send_enter('id_main_email') # The response is that same page... self.assert_current_url_equal(self.get_registration_url()) # ... and shows an error message for the duplicate email address. self.assert_in_page_body('email address is already in use')
[docs] def test_login(self): """ Tests that a user can log in when they already have an account. """ # === Set up an account === user_email = 'user@domain.com' associated_emails = ['email@domain.com'] password = 'asdf' self.create_user(user_email, password, associated_emails) # The user opens the front page and tries going to the log in page self.get_page('/') self.assert_in_page_body('Log in') self.click_link('Log in') # The user is now found in the log in page self.assert_current_url_equal(self.get_login_url()) # The page shows a log in form. self.assert_element_with_id_in_page('form-login') # The user enters the correct email address, but incorrect password. self.input_to_element('id_username', user_email) self.input_to_element('id_password', 'fdsa') self.send_enter('id_password') # The response shows an error message for incorrect credentials. self.assert_in_page_body('Please enter a correct email and password') # Now the user correctly enters the password. The email # address should not need to be entered again. self.input_to_element('id_password', password) self.send_enter('id_password') # The user is redirected to their profile page. self.assert_current_url_equal(self.get_profile_url())
[docs] def test_login_associated_email(self): """ Tests that a user can log in with an associated email. """ # === Set up an account === user_email = 'user@domain.com' associated_emails = ['email@domain.com'] password = 'asdf' self.create_user(user_email, password, associated_emails) # The user goes to the log in page self.get_page(self.get_login_url()) # The page shows a log in form. self.assert_element_with_id_in_page('form-login') # The user enters the associated email address and password. self.input_to_element('id_username', associated_emails[0]) self.input_to_element('id_password', password) self.send_enter('id_password') # The user is redirected to their profile page. self.assert_current_url_equal(self.get_profile_url())
[docs] def test_logout_from_package_page(self): """ If a user logs out when on the package page, the response should not redirect to the index. """ # === Set up an account === user_email = 'user@domain.com' associated_emails = ['email@domain.com'] password = 'asdf' self.create_user(user_email, password, associated_emails) # === Set up an existing package === package_name = 'dummy' SourcePackageName.objects.create(name=package_name) # The user logs in self.get_page(self.get_login_url()) self.input_to_element('id_username', associated_emails[0]) self.input_to_element('id_password', password) self.send_enter('id_password') # The user goes to the package page self.get_page('/' + package_name) # The page shows a link to log out. self.assert_in_page_body('Log out') # The user selects the link to log out. self.click_link('Log out') # The user is still at the package page, but no longer logged in self.assert_current_url_equal(self.get_package_url(package_name)) self.assert_not_in_page_body('Log out') self.assert_in_page_body('Log in') # The user tries going to their profile page, but is # definitely logged out... self.get_page(self.get_profile_url()) # ... which means the response redirects to the log in page. self.assert_current_url_equal( self.get_login_url() + '?next=' + self.get_profile_url())
[docs]class SubscribeToPackageTest(UserAccountsTestMixin, SeleniumTestCase): """ Tests for stories regarding subscribing to a package over the Web. """
[docs] def get_subscriptions_url(self): return reverse('dtracker-accounts-subscriptions')
[docs] def test_subscribe_from_package_page(self): """ Tests that a user that has only one email address can subscribe to a package directly from the package page. """ # The user first logs in self.log_in() # The user opens a package page self.get_page('/' + self.package.name) # The page shows a button allowing them to subscribe to the package. self.assert_element_with_id_in_page('subscribe-button') # So they click it. button = self.get_element_by_id('subscribe-button') button.click() # The subscribe button is no longer found in the page button = self.get_element_by_id('subscribe-button') # === Give the page a chance to refresh === self.wait().until(lambda browser: not button.is_displayed()) self.assertFalse(button.is_displayed()) # It has been replaced by the unsubscribe button self.assert_element_with_id_in_page('unsubscribe-button') unsubscribe_button = self.get_element_by_id('unsubscribe-button') self.assertTrue(unsubscribe_button.is_displayed()) # === The user has really been subscribed to the package? === self.assertTrue(self.user.is_subscribed_to(self.package))
[docs] def test_subscribe_not_logged_in(self): """ Tests that when a user is not logged in, the response redirects to the log in page instead of subscribing to the package. """ # The user opens the package page self.get_page('/' + self.package.name) # ...and tries subscribing to the package self.get_element_by_id('subscribe-not-logged-in-button').click() # ...only to find himself redirected to the log in page. url = self.get_login_url() + '?next=' + self.get_package_url( self.package) self.assert_current_url_equal(url)
[docs] def test_subscribe_multiple_associated_emails(self): """ Tests that a user with multiple associated email addresses is offered a choice which address to use to subscribe to a package. """ # === Set up such a user === other_email = 'other-email@domain.com' self.user.emails.create(email=other_email) # The user logs in self.log_in() # The user opens a package page and clicks to subscribe button self.get_page('/' + self.package.name) self.get_element_by_id('subscribe-button').click() self.wait_response(1) # The user is presented with a choice of their email addresses. for email in self.user.emails.all(): self.assert_in_page_body(email.email) # The user decides to cancel the subscription by dismissing the popup self.get_element_by_id('cancel-choose-email').click() self.wait_response(1) # === The user is not subscribed to anything yet === self.assertEqual(0, Subscription.objects.count()) # The user clicks the subscribe button again self.get_element_by_id('subscribe-button').click() self.wait_response(1) # ... and chooses to subscribe using their associated email address. self.assert_element_with_id_in_page('choose-email-1') self.get_element_by_id('choose-email-1').click() self.wait_response(1) # === User is now subscribed with only the clicked email === self.assertEqual(1, Subscription.objects.count()) sub = Subscription.objects.all()[0] self.assertEqual(other_email, sub.email_settings.user_email.email) # The UI that the user sees reflects this self.assert_element_with_id_in_page('unsubscribe-button') unsubscribe_button = self.get_element_by_id('unsubscribe-button') self.assertTrue(unsubscribe_button.is_displayed())
[docs] def test_unsubscribe_all_emails(self): """ Tests unsubscribing all user's emails from a package. """ # === Set up a user with multiple emails subscribed to a package === other_email = 'other-email@domain.com' self.user.emails.create(email=other_email) for email in self.user.emails.all(): Subscription.objects.create_for( email=email.email, package_name=self.package.name) # The user logs in and opens the package page self.log_in() self.get_page('/' + self.package.name) # The page shows a button to unsubscribe from the package. self.assert_in_page_body('Unsubscribe') self.assert_element_with_id_in_page('unsubscribe-button') # The user decides to unsubscribe and clicks the button... self.get_element_by_id('unsubscribe-button').click() self.wait_response(1) # === The user is really unsubscribed from the package === self.assertFalse(self.user.is_subscribed_to(self.package)) # The user sees the subscribe button instead of the unsubscribe button sub_button = self.get_element_by_id('subscribe-button') self.assertTrue(sub_button.is_displayed()) unsub_button = self.get_element_by_id('unsubscribe-button') self.assertFalse(unsub_button.is_displayed())
[docs] def test_subscribe_package_from_subscription_tab(self): """ This test validates that a user can correctly subscribe to a package from the subscription tab in its personnal space. """ # Initially the user is not subscribed to the package self.assertFalse(self.user.is_subscribed_to(self.package)) # The user logs in and goes to their subscriptions page self.log_in() self.get_page(self.get_subscriptions_url()) # To ensure the subscription page is fully charged self.wait_response(1) self.assert_in_page_body('Subscribe') # Checking that at least one checkbox is checked available_mails = self.browser.find_elements_by_xpath( "//input[@type='checkbox'][@name='email']") is_there_checked_emails = False for checkbox in available_mails: if checkbox.is_selected(): is_there_checked_emails = True break self.assertTrue(is_there_checked_emails) # Filling the package search field self.assert_element_with_id_in_page('package-search-input') self.input_to_element('package-search-input', self.package.name) # Subscribing to the package and ensuring it's completely done! self.send_enter('package-search-input') self.wait_response(1) self.assertTrue(self.user.is_subscribed_to(self.package))
[docs] def test_package_subscription_no_email_from_subscription_tab_fails(self): """ The UI should prevent the user from forgetting to check at least one email checkbox from the subscription tab in its personnal space. """ # Initially the user is not subscribed to the package self.assertFalse(self.user.is_subscribed_to(self.package)) # The user logs in and goes to their subscriptions page self.log_in() self.get_page(self.get_subscriptions_url()) # To ensure the subscription page is fully charged self.wait_response(1) self.assert_in_page_body('Subscribe') # All email checkboxes should be unchecked available_mails = self.browser.find_elements_by_xpath( "//input[@type='checkbox'][@name='email']") for checkbox in available_mails: if checkbox.is_selected(): checkbox.click() # The user specifies the package name... self.assert_element_with_id_in_page('package-search-input') self.input_to_element('package-search-input', self.package.name) # ... then submits the form to subscribe to the package. self.send_enter('package-search-input') self.wait_response(1) # The page shows a message stating that the field is required. self.assert_in_page_body('You need to select at least an email') # The user should still not be subscribed to the package. self.assertFalse(self.user.is_subscribed_to(self.package))
[docs] def test_package_subscription_no_package_from_subscription_tab_fails(self): """ The UI should prevent the user from forgetting to check at least one email checkbox from the subscription tab in its personnal space. """ # Initially the user is not subscribed to the package self.assertFalse(self.user.is_subscribed_to(self.package)) # The user logs in and goes to their subscriptions page. self.log_in() self.get_page(self.get_subscriptions_url()) # To ensure the subscription page is fully charged self.wait_response(1) self.assert_in_page_body('Subscribe') # Checking that at least one checkbox is checked available_mails = self.browser.find_elements_by_xpath( "//input[@type='checkbox'][@name='email']") is_there_checked_emails = False for checkbox in available_mails: if checkbox.is_selected(): is_there_checked_emails = True break self.assertTrue(is_there_checked_emails) # The user specifies an empty package name... self.assert_element_with_id_in_page('package-search-input') self.input_to_element('package-search-input', '') # ... then submits the form to subscribe to the package. self.send_enter('package-search-input') self.wait_response(1) # The page shows a message stating that the field is required. self.assert_in_page_body('This field is required') # The user should still not be subscribed to the package. self.assertFalse(self.user.is_subscribed_to(self.package))
[docs]class ChangeProfileTest(UserAccountsTestMixin, SeleniumTestCase):
[docs] def test_modify_personal_info(self): """ Tests that the user is able to change their personal info upon logging in. """ # The user logs in self.log_in() # The response page shows a link to modify personal information. self.assert_in_page_body('Personal Information') self.click_link('Personal Information') # The page shows a form to change the user's name. self.assert_element_with_id_in_page('form-change-profile') # The user decides to input a new name name = 'Name' old_last_name = self.user.last_name self.input_to_element('id_first_name', name) # ...and submits the form self.send_enter('id_first_name') # The user is met with a notification that the information has been # updated. self.assert_in_page_body('Successfully changed your information') # === The user's name has really changed === self.refresh_user_object() self.assertEqual(name, self.user.first_name) # === But the last name has not === self.assertTrue(self.user.last_name is None or self.user.last_name == old_last_name) # The user now wants to update both their first and last name. self.clear_element_text('id_first_name') new_first_name, new_last_name = 'Name', 'Last Name' # The user fills in the form self.input_to_element('id_first_name', new_first_name) self.input_to_element('id_last_name', new_last_name) # ...and submits it. self.send_enter('id_last_name') # The response shows a confirmation of success. self.assert_in_page_body('Successfully changed your information') # === The information has actually been updated === self.refresh_user_object() self.assertEqual(new_first_name, self.user.first_name) self.assertEqual(new_last_name, self.user.last_name) # The user navigates away from the page now self.get_page(self.get_profile_url()) # And then goes back self.click_link('Personal Information') # There are no notifications about modification in the page nw self.assert_not_in_page_body('Successfully changed your information') # And the user's first/last name is already filled in the form self.assertEqual( new_first_name, self.get_element_by_id('id_first_name').get_attribute('value')) self.assertEqual( new_last_name, self.get_element_by_id('id_last_name').get_attribute('value'))
[docs] def test_change_password(self): """ Tests that the user can change their password upon logging in. """ # The user logs in self.log_in() # The response page shows a link to change their password... self.assert_in_page_body('Change Password') # ... and clicks it. self.click_link('Change Password') # The response page shows a form to enter the new password. self.assert_element_with_id_in_page('form-change-password') # The user first enters a wrong current password new_password = 'new-password' self.input_to_element('id_old_password', 'this-password-is-incorrect') self.input_to_element('id_new_password1', new_password) self.input_to_element('id_new_password2', new_password) self.send_enter('id_new_password2') # The response shows an error saying the current password was # incorrect. self.assert_in_page_body('Your old password was entered incorrectly') # This time, the user enters both the old password and fills in the new # password fields, but they are mismatched self.input_to_element('id_old_password', self.password) self.input_to_element('id_new_password1', new_password) self.input_to_element('id_new_password2', new_password + '-miss-match') self.send_enter('id_new_password2') # The response shows a message that the password change failed # again. self.assert_in_page_body("The two password fields didn't match") # In the end, the user manages to fill in the form correctly! self.input_to_element('id_old_password', self.password) self.input_to_element('id_new_password1', new_password) self.input_to_element('id_new_password2', new_password) self.send_enter('id_new_password2') old_password = self.password self.password = new_password self.log_in() # The response shows a message confirming that the password # was changed. self.assert_in_page_body('Successfully updated your password') # The user logs out in order to try using their new password. self.click_link('Log out') # The user tries logging in using their old account password. self.password = old_password self.log_in() # The response shows an error message for incorrect credentials. self.assert_in_page_body('Please enter a correct email and password') # Now they try with their new password. self.password = new_password self.log_in() # The user is finally logged in using their new account details. self.assert_current_url_equal(self.get_profile_url())
[docs] def test_reset_password(self): """ Tests that a user is able to reset their password if they forgot it. """ # The user goes to the login page self.get_page(self.get_login_url()) # The page shows a link to reset their password. self.assert_in_page_body('Forgot your password?') # The user has forgotten their password, so clicks the link. self.click_link('Forgot your password?') # The user sees a form to reset their password. self.assert_element_with_id_in_page('form-reset-password') # First they enter an email address not associated with any account. self.input_to_element('id_email', 'this-does-not-exist@domain.com') self.send_enter('id_email') # The response is the same page, with a warning that there is # no user with that email address. self.assert_in_page_body('No user with the given email is registered') # The user now correctly enters their own email address. self.clear_element_text('id_email') self.input_to_element('id_email', self.user.main_email) self.send_enter('id_email') # The response is another page, with a message that they must # check their email for a confirmation message. self.assert_in_page_body('Please check your email inbox for details') # === A confirmation email is actually sent? === self.assertEqual(1, len(mail.outbox)) confirmation = ResetPasswordConfirmation.objects.all()[0] # The user goes to the confirmation URL. self.get_page(reverse('dtracker-accounts-reset-password', kwargs={ 'confirmation_key': confirmation.confirmation_key, })) # The response page asks them to enter a new password... self.assert_in_page_body('please enter a new password for your account') # ...so they do. new_password = self.password + '-new' self.input_to_element('id_password1', new_password) self.input_to_element('id_password2', new_password) self.send_enter('id_password2') # The response page redirects to the profile page, with a # message that their password has been reset. self.assert_current_url_equal(self.get_profile_url()) self.assert_in_page_body('You have successfully reset your password') # The user logs out and tries logging back in with their new # password. self.click_link('Log out') self.password = new_password self.log_in() # The user has successfully logged in. self.assert_current_url_equal(self.get_profile_url())
[docs] def test_manage_account_emails(self): """ Tests that the user can manage which email addresses are associated with their account. """ # The user logs in and goes to the account email management page self.log_in() self.click_link('Account Emails') # The response page shows a form to add new email addresses to # their account. self.assert_element_with_id_in_page('form-add-account-email') # The user adds a new email address. new_email = 'completely-new-email@domain.com' self.input_to_element('id_email', new_email) self.send_enter('id_email') # The user is notified that in order to activate the email association # they must demonstrate ownership of the email address. self.assert_in_page_body('you must follow the confirmation link') self.assert_not_in_page_body(new_email) # === The confirmation email sent? === self.assertEqual(1, len(mail.outbox)) self.assertIn(new_email, mail.outbox[0].to) # === Confirmation created? === self.assertEqual(1, AddEmailConfirmation.objects.count()) confirmation = AddEmailConfirmation.objects.all()[0] # The user now visits the confirmation URL... self.get_page(reverse('dtracker-accounts-confirm-add-email', kwargs={ 'confirmation_key': confirmation.confirmation_key, })) # The response page shows a confirmation message that the # address is associated with their account. self.assert_in_page_body('now associated with your account') # The user goes back to their profile page. self.click_link('Profile') self.click_link('Account Emails') # The new email address is now in the list of email addresses # for the account. self.assert_in_page_body(new_email) # The user tries adding an email already associated with their account. self.input_to_element('id_email', new_email) self.send_enter('id_email') # The response shows a warning that the account is already # associated with the given email address. self.assert_in_page_body( 'This email is already associated with your account')
[docs] def test_merge_accounts(self): # === Set up an additional existing user account === password = 'other-password' other_email = 'other@domain.com' other_user = self.create_user(other_email, password) # The user logs in, then goes to the form to add an email address. self.log_in() self.click_link('Account Emails') # They input an address already associated with a different user. self.input_to_element('id_email', other_email) self.send_enter('id_email') # The response page requests confirmation to merge the accounts. self.assert_in_page_body('Are you sure you want to merge the accounts') # The user clicks the button to confirm the merge. self.assert_element_with_id_in_page('confirm-merge-button') self.get_element_by_id('confirm-merge-button').click() # The response notifies the user the merge is ineffective # until the confirmation link is visited. self.assert_in_page_body('you must follow a confirmation link') # A confirmation mail is sent self.assertEqual(1, len(mail.outbox)) # The mail was not sent to the logged in user, rather the one being # merged to the account self.assertIn(other_email, mail.outbox[0].to) # === A confirmation instance is created? === self.assertEqual(1, MergeAccountConfirmation.objects.count()) confirmation = MergeAccountConfirmation.objects.all()[0] # The user tries going to the confirmation URL without logging in to # the other account confirmation_url = reverse('dtracker-accounts-merge-finalize', kwargs={ 'confirmation_key': confirmation.confirmation_key, }) self.get_page(confirmation_url) # ...which is forbidden and has no effect self.assertEqual(2, User.objects.count()) # The user now logs in with the other account self.log_out() self.log_in(other_user, password) # The user visits the URL from the confirmation email message. self.get_page(confirmation_url) # The response page asks for a final confirmation. self.assert_in_page_body( 'Are you sure you want to finalize the accounts merge') self.assert_element_with_id_in_page('finalize-merge-button') # The user decides to go on with the merge self.get_element_by_id('finalize-merge-button').click() # The user is notified that the merge was successful self.assert_in_page_body( 'The two accounts have been successfully merged') # === User accounts are really changed? === self.assertEqual(1, User.objects.count()) # The user tries logging in with the merged account's password... self.log_in(password=password) # ...which fails. self.assert_in_page_body('Please enter a correct email and password') # They log in with the original account details... self.log_in() # ...which works. self.assert_current_url_equal(self.get_profile_url()) # Both the email addresses are shown as associated with the # user's account. self.click_link('Account Emails') self.assert_in_page_body(self.user.main_email) self.assert_in_page_body(other_email)
[docs]class ManageSubscriptionTest(UserAccountsTestMixin, SeleniumTestCase):
[docs] def setUp(self): super().setUp() # Ensure we have the default keywords if Keyword.objects.count() == 0: Keyword.objects.bulk_create([ Keyword(name='default', default=True), Keyword(name='bts', default=True), Keyword(name='bts-control', default=True), Keyword(name='summary', default=True), Keyword(name='upload-source', default=True), Keyword(name='archive', default=True), Keyword(name='contact', default=True), Keyword(name='build', default=True), Keyword(name='vcs', default=False), Keyword(name='translation', default=False), Keyword(name='upload-binary', default=False), Keyword(name='derivatives', default=False), Keyword(name='derivatives-bugs', default=False), ]) self.package2 = SourcePackageName.objects.create(name='package2') self.user.emails.create(email='xyz@domain.com') self.email1 = self.user.emails.get(email='user@domain.com') self.email2 = self.user.emails.get(email='xyz@domain.com') self.team = Team.objects.create_with_slug(owner=self.user, name='Sample team') self.membership = self.team.add_members([self.email2])[0] self.team.packages.set([self.package, self.package2]) Subscription.objects.create_for('dummy-package', self.email1.email) Subscription.objects.create_for('package2', self.email1.email) # Go to the right page self.log_in() self.get_page(self.get_subscriptions_url())
[docs] def get_subscriptions_url(self): return reverse('dtracker-accounts-subscriptions')
def _select_keywords(self, keywords): checkboxes = self.browser.find_elements_by_css_selector( '#choose-keywords-modal input.keyword-choice') for checkbox in checkboxes: keyword = checkbox.get_attribute('value') if bool(checkbox.is_selected()) != (keyword in keywords): checkbox.click() def _get_selected_keywords(self): checkboxes = self.browser.find_elements_by_css_selector( '#choose-keywords-modal input.keyword-choice') keywords = [] for checkbox in checkboxes: keyword = checkbox.get_attribute('value') if checkbox.is_selected(): keywords.append(keyword) return keywords def _get_keywords_from_entry(self, entry): keyword_labels = entry.find_elements_by_css_selector( '.keyword-list .keyword') return [x.get_attribute('textContent') for x in keyword_labels] def _test_modify_keyword(self, entry, keywords, check_initial_keywords=None, toggle=True): # The user clicks on the button to show the details of the keywords if toggle: entry.find_element_by_class_name('toggle-details').click() # The initial keywords are shown initial_keywords = self._get_keywords_from_entry(entry) self.assertTrue(initial_keywords) if check_initial_keywords is not None: self.assertSetEqual(set(initial_keywords), set(check_initial_keywords)) # The user clicks on the modify keyword button button = entry.find_element_by_css_selector('.modify-keywords') button.click() # A modal window shows up and it contains the list of keywords # correctly checked (matching the initial keywords displayed) popup = self.browser.find_element_by_id('choose-keywords-modal') self.wait().until(EC.visibility_of(popup)) selected_keywords = self._get_selected_keywords() self.assertListEqual(initial_keywords, selected_keywords) # They click on various checkboxes to make their choices self._select_keywords(keywords) # And confirm this with the "Save keywords" button popup.find_element_by_id('save-keywords').click() self.wait().until_not(EC.visibility_of(popup)) # The list of keywords has been updated in the main table entry_keywords = self._get_keywords_from_entry(entry) self.assertSetEqual(set(keywords), set(entry_keywords))
[docs] def test_modify_keywords(self): # First package entry in first email, it's dummy_package in user@domain entry = self.browser.find_element_by_css_selector('#pkg-1-1') # We try to modify the keywords associated to the subscription keywords = ['build', 'vcs'] subscription = self.email1.emailsettings.subscription_set.get( package=self.package) default_keywords = subscription.keywords.values_list('name', flat=True) self._test_modify_keyword(entry, keywords, check_initial_keywords=default_keywords) subscription.refresh_from_db() self.assertSetEqual( set(keywords), set(subscription.keywords.values_list('name', flat=True)) ) # We do the same on the team subscription (second email, first team) entry = self.browser.find_element_by_css_selector('#team-2-1') # We try to modify the keywords associated to the subscription keywords = ['bts', 'bts-control'] default_keywords = self.membership.get_membership_keywords() default_keywords = default_keywords.values_list('name', flat=True) self._test_modify_keyword(entry, keywords, check_initial_keywords=default_keywords) self.assertSetEqual( set(keywords), set(self.membership.default_keywords.all().values_list( 'name', flat=True)) ) # Now let's try to modify the default keyword of email1 entry = self.browser.find_element_by_css_selector('#email-heading-1') keywords = ['archive', 'build', 'bts', 'vcs'] db_keywords = self.email1.emailsettings.default_keywords.all() default_keywords = db_keywords.values_list('name', flat=True) self._test_modify_keyword(entry, keywords, toggle=False, check_initial_keywords=default_keywords) self.assertSetEqual( set(keywords), set(db_keywords.values_list('name', flat=True)) )
[docs]class TeamTests(UserAccountsTestMixin, SeleniumTestCase):
[docs] def get_create_team_url(self): return reverse('dtracker-teams-create')
[docs] def get_team_url(self, team_name): team = Team.objects.get(name=team_name) return team.get_absolute_url()
[docs] def get_team_manage_url(self, team_name): team = Team.objects.get(name=team_name) return reverse('dtracker-team-manage', kwargs={ 'slug': team.slug, })
[docs] def get_delete_team_url(self, team_name): team = Team.objects.get(name=team_name) return reverse('dtracker-team-delete', kwargs={ 'slug': team.slug, })
[docs] def get_team_deleted_url(self): return reverse('dtracker-team-deleted')
[docs] def get_update_team_url(self, team_name): team = Team.objects.get(name=team_name) return reverse('dtracker-team-update', kwargs={ 'slug': team.slug, })
[docs] def get_subscriptions_url(self): return reverse('dtracker-accounts-subscriptions')
[docs] def assert_team_packages_equal(self, team, package_names): team_package_names = [p.name for p in team.packages.all()] self.assertEqual(len(package_names), len(team_package_names)) for package_name in package_names: self.assertIn(package_name, team_package_names)
[docs] def test_create_team(self): """ Tests that a logged in user can create a new team. """ # The user tries going to the page to create a new team self.get_page(self.get_create_team_url()) # However, the browser is not signed in so the response # redirects to the login page. self.assertIn(self.get_login_url(), self.browser.current_url) self.wait_response(1) # The user then logs in self.log_in() # ...and tries again self.get_page(self.get_create_team_url()) # This time the response page has a form to create a new team. self.assert_element_with_id_in_page('create-team-form') # The user inputs the team name, but not a maintainer email team_name = 'New team' self.input_to_element('id_name', team_name) self.send_enter('id_name') self.wait_response(1) # The user is now redirected to the team's page self.assert_current_url_equal(self.get_team_url(team_name)) # === The team actually exists === self.assertEqual(1, Team.objects.filter(name=team_name).count()) # === The user is its owner === team = Team.objects.get(name=team_name) self.assertEqual(self.user, team.owner) # The user goes back to the team creation page now self.get_page(self.get_create_team_url()) # They try creating a new team with the same name self.input_to_element('id_name', team_name) self.send_enter('id_name') # This time, the team creation process fails because the team name is # not unique. self.assert_in_page_body('Team with this Name already exists')
[docs] def test_create_team_maintainer_email(self): """ Tests creating a team with a maintainer email set. The team should become automatically associated with the maintainer's packages. """ # === Set up some packages maintained by the same maintainer === package_names = [ 'pkg1', 'pkg2', ] maintainer_email = 'maintainer@domain.com' maintainer = ContributorName.objects.create( contributor_email=UserEmail.objects.create( email=maintainer_email)) for package_name in package_names: SourcePackage.objects.create( source_package_name=SourcePackageName.objects.create( name=package_name), version='1.0.0', maintainer=maintainer) # The user logs in and accesses the create team page. self.log_in() self.get_page(self.get_create_team_url()) # They input both the team name and the maintaner name. team_name = 'Team name' self.input_to_element('id_name', team_name) self.input_to_element('id_maintainer_email', maintainer_email) self.send_enter('id_maintainer_email') self.wait_response(1) # The team is successfully created and the user can see the # maintainer's packages already in the team's page for package_name in package_names: self.assert_in_page_body(package_name) # === The team really is associated with the packages? === team = Team.objects.all()[0] self.assert_team_packages_equal(team, package_names) # The user now wants to associate the team with a different maintainer # that maintains other packages new_package_name = 'pkg3' new_maintainer_email = 'new-maintainer@domain.com' new_maintainer = ContributorName.objects.create( contributor_email=UserEmail.objects.create( email=new_maintainer_email)) SourcePackage.objects.create( source_package_name=SourcePackageName.objects.create( name=new_package_name), version='1.0.0', maintainer=new_maintainer) self.get_element_by_id('update-team-button').click() # The user modifies the maintainer field self.clear_element_text('id_maintainer_email') self.input_to_element('id_maintainer_email', new_maintainer_email) self.send_enter('id_maintainer_email') self.wait_response(1) # The user is directed back to the team page which shows all the # packages previously associated with the team, as well as the ones # associated to the new maintainer. self.assert_in_page_body(new_package_name) for package_name in package_names: self.assert_in_page_body(package_name) # === The team is really associated with all these packages? === self.assert_team_packages_equal( team, package_names + [new_package_name])
[docs] def test_delete_team(self): """ Tests that the owner can delete a team. """ # === Set up a team owned by the user === team_name = 'Team name' Team.objects.create_with_slug(owner=self.user, name=team_name) # Before logging in the user opens the team page self.get_page(self.get_team_url(team_name)) # The page does not show the delete button. self.assert_not_in_page_body("Delete") # The user goes directly to the deletion URL. self.get_page(self.get_delete_team_url(team_name)) # But permission is denied to the user self.assert_not_in_page_body(team_name) # The user now logs in. self.log_in() self.get_page(self.get_team_url(team_name)) # The delete button is now offered to the user self.assert_element_with_id_in_page('delete-team-button') # So the user decides to click it. self.get_element_by_id('delete-team-button').click() self.wait_response(1) # The response shows a popup asking to confirm the team deletion. cancel_button = self.get_element_by_id('team-delete-cancel-button') confirm_button = self.get_element_by_id('confirm-team-delete-button') self.assertTrue(confirm_button.is_displayed()) self.assertTrue(cancel_button.is_displayed()) # The user decides to cancel the deletion cancel_button.click() self.wait_response(1) # The response is the team page and the team has not been deleted. self.assert_current_url_equal(self.get_team_url(team_name)) # === The team is still here === self.assertEqual(1, Team.objects.count()) # The user now deletes the team. self.get_element_by_id('delete-team-button').click() self.wait_response(1) self.get_element_by_id('confirm-team-delete-button').click() self.wait_response(1) # The response page confirms the team has been successfully # deleted. self.assert_current_url_equal(self.get_team_deleted_url()) # === The team is also really deleted? === self.assertEqual(0, Team.objects.count())
[docs] def test_update_team(self): """ Tests that the team owner can update the team's basic info. """ # === Set up a team owned by the user === team_name = 'Team name' Team.objects.create_with_slug(owner=self.user, name=team_name) # Before logging in the user opens the team page self.get_page(self.get_team_url(team_name)) # The page does not show the update button. self.assert_not_in_page_body("Update") # The user goes directly to the update URL. self.get_page(self.get_update_team_url(team_name)) # But permission is denied to the user self.assert_not_in_page_body(team_name) # The user now logs in self.log_in() self.get_page(self.get_team_url(team_name)) # The page now shows the update button self.assert_element_with_id_in_page('update-team-button') # The user clocks the link to update the team information. self.get_element_by_id('update-team-button').click() # The response page is the update page now... self.assert_current_url_equal(self.get_update_team_url(team_name)) # ... with a form to update the team's info. self.assert_element_with_id_in_page('update-team-form') # The user modifies the team's description new_description = "This is a new description" self.input_to_element('id_description', new_description) self.send_enter('id_name') self.wait_response(1) # The user is taken back to the team's page self.assert_current_url_equal(self.get_team_url(team_name)) # The updated information is displayed in the page already self.assert_in_page_body(new_description) # === The team's info is actually updated? === team = Team.objects.all()[0] self.assertEqual(new_description, team.description) # The user now wants to update the team's name without affecting the # team's URL. old_url = self.get_team_url(team_name) self.get_element_by_id('update-team-button').click() self.clear_element_text('id_name') new_name = team_name + ' new name' self.input_to_element('id_name', new_name) self.send_enter('id_name') self.wait_response(1) # The user is now found back at the team page which contains the # updated name self.assert_in_page_body(new_name) # However, the package's URL is still the same self.assert_current_url_equal(old_url) # Now the user wants to modify the team's url without modifying its # name. self.get_element_by_id('update-team-button').click() old_slug = team.slug self.clear_element_text('id_slug') new_slug = old_slug + '-new-slug' self.input_to_element('id_slug', new_slug) self.send_enter('id_slug') self.wait_response(1) # The user is once again back on the team page. # The URL has been modified now to contain the new team slug. self.assertIn(new_slug, self.browser.current_url) # === The slug really is updated? === self.assertEqual(new_slug, Team.objects.all()[0].slug)
[docs] def test_package_management(self): """ Tests that adding/removing packages from the team works as expected. """ # === Set up a team owned by the user === team_name = 'Team name' team = Team.objects.create_with_slug(owner=self.user, name=team_name) # === Set up some packages which the user can add to the team === package_names = [ 'pkg1', 'pkg2', ] for package_name in package_names: PackageName.objects.create(name=package_name) # === -- === self.log_in() self.get_page(self.get_team_url(team_name)) # The user opens the member management page self.get_element_by_id('manage-team-button').click() # The response page shows the form to add packages now. self.assert_element_with_id_in_page('add-team-package-form') # The user enters the name of the package to add... self.input_to_element('id_package_name', package_names[0]) # ...and submits the form. self.send_enter('id_package_name') self.wait_response(1) # The user is still in the team page self.assert_current_url_equal(self.get_team_manage_url(team_name)) # The page now shows the package they added in the list of packages. self.assert_in_page_body(package_names[0]) # The user tries adding a new package: one that does not exist. self.input_to_element('id_package_name', 'this-does-not-exist') self.send_enter('id_package_name') self.wait_response(1) # The user is still in the team page, but nothing is changed when it # comes to the list of packages. self.assert_not_in_page_body('this-does-not-exist') # The user now wants to remove the package from the team. # They click the button to remove the package from the team. remove_button = self.browser.find_element_by_css_selector( '.remove-package-from-team-button') remove_button.click() self.wait_response(1) # A popup is displayed asking the user to confirm the removal # The user decides to cancel the operation self.get_element_by_id('remove-package-cancel-button').click() self.wait_response(1) # The response is still the team page, and the package is not removed. self.assert_current_url_equal(self.get_team_manage_url(team_name)) # === The package is not removed? === self.assertEqual(1, team.packages.count()) # The user decides to definitely remove the package now remove_button.click() self.wait_response(1) self.get_element_by_id('confirm-remove-package-button').click() self.wait_response(1) # The user is still on the team page, but the package is not longer # a part of the team. self.assert_current_url_equal(self.get_team_manage_url(team_name)) self.assert_not_in_page_body(package_names[0]) # === The package is really removed from the team === self.assertEqual(0, team.packages.count())
[docs] def test_team_access(self): """ Tests joining and leaving a team. """ # === Set up a team and a user who isn't the owner of the team === team_name = 'Team name' team = Team.objects.create_with_slug(owner=self.user, name=team_name) user = User.objects.create_user( main_email='other@domain.com', password=self.password) UserEmail.objects.get_or_create(email=user.main_email) # === end setup === # The user logs in and goes to the team page self.log_in(user) self.get_page(self.get_team_url(team_name)) # The page shows a button to join the team. self.assert_element_with_id_in_page('join-team-button') # ... so the user clicks it. self.get_element_by_id('join-team-button').click() self.wait_response(1) # === The user is really a member? === self.assertTrue(team.user_is_member(user)) # The page now has a button to leave the team. self.assert_element_with_id_in_page('leave-team-button') # So the user clicks that button. self.get_element_by_id('leave-team-button').click() self.wait_response(1) # The user is now again not a member of the team self.assert_element_with_id_in_page('join-team-button') # === The user really isn't a member any more. === self.assertFalse(team.user_is_member(user)) # The user now logs out self.log_out() # And tries clicking the join team button self.get_element_by_id('join-team-button').click() self.wait_response(1) # But the response redirects to the login page. self.assert_element_with_id_in_page('form-login') # === The privacy of the team is switched to private. === team.public = False team.save() # When the user opens the page again, the join button is replaced with # a link to contact the owner. self.get_page(self.get_team_url(team_name)) self.assert_in_page_body('Contact the owner')
[docs] def test_owner_team_management(self): """ Tests that a team owner is able to add/remove members from a separate panel. """ team_name = 'Team name' Team.objects.create_with_slug(owner=self.user, name=team_name) self.log_in() self.get_page(self.get_team_url(team_name)) # The user opens the member management page self.get_element_by_id('manage-team-button').click() # The user wants to add a new team member new_team_member = 'member@domain.com' self.input_to_element('id_email', new_team_member) self.send_enter('id_email') self.wait_response(1) # The user is still in the same page, but can see the new member in the # list of all members self.assert_in_page_body(new_team_member) # === The membership is marked muted, though === membership = TeamMembership.objects.all()[0] self.assertTrue(membership.muted) # === Email was sent to the new member asking for confirmation === self.assertEqual(1, len(mail.outbox)) self.assertIn(new_team_member, mail.outbox[0].to) # The user now decides to remove the team member button = \ self.browser.find_element_by_css_selector('.remove-user-button') button.click() self.wait_response(1) # The user is no longer a part of the team self.assert_not_in_page_body(new_team_member)
[docs] def test_toggle_team_mute(self): """ Tests that a team member is able to mute and unmute a team membership from the subscription details page. """ # === -- === team_name = 'Team name' team = Team.objects.create_with_slug(owner=self.user, name=team_name) membership = team.add_members([self.user.emails.all()[0]])[0] # === -- === # The user logs in and goes to their subscriptions page. self.log_in() self.get_page(self.get_subscriptions_url()) # The response page shows a button to mute the team membership. self.assert_in_page_body('Mute') # The user clicks the button. btn = self.get_element_by_class('toggle-team-mute') btn.click() self.wait_response(1) # The response page is still the team page, and shows a # warning that the user's team membership is muted. self.assert_element_with_class_in_page('mute-warning') # === The membership is actually muted? === membership = TeamMembership.objects.get(pk=membership.pk) self.assertTrue(membership.muted) # The user now wants to revert this. # The page shows the unmute button. self.assert_in_page_body('Unmute') # The user clicks the button. btn = self.get_element_by_class('toggle-team-mute') btn.click() self.wait_response(1) # Once again, the user is still in the subscriptions page, but the # button has reverted back to the mute button self.assert_in_page_body('Mute') # And the warning is gone self.assertIsNone(self.get_element_by_class('mute-warning'))