tests.py 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782
  1. # Copyright 2013 The Distro Tracker Developers
  2. # See the COPYRIGHT file at the top-level directory of this distribution and
  3. # at http://deb.li/DTAuthors
  4. #
  5. # This file is part of Distro Tracker. It is subject to the license terms
  6. # in the LICENSE file found in the top-level directory of this
  7. # distribution and at http://deb.li/DTLicense. No part of Distro Tracker,
  8. # including this file, may be copied, modified, propagated, or distributed
  9. # except according to the terms contained in the LICENSE file.
  10. """
  11. Functional tests for Distro Tracker.
  12. """
  13. from __future__ import unicode_literals
  14. from distro_tracker.test import LiveServerTestCase
  15. from django.core.urlresolvers import reverse
  16. from django.contrib.auth import get_user_model
  17. from django.core import mail
  18. from django_email_accounts.models import UserEmail
  19. from distro_tracker.core.models import SourcePackageName, BinaryPackageName
  20. from distro_tracker.accounts.models import UserRegistrationConfirmation
  21. from distro_tracker.accounts.models import ResetPasswordConfirmation
  22. from distro_tracker.accounts.models import AddEmailConfirmation
  23. from distro_tracker.accounts.models import MergeAccountConfirmation
  24. from distro_tracker.core.models import ContributorName
  25. from distro_tracker.core.models import Team
  26. from distro_tracker.core.models import SourcePackage
  27. from distro_tracker.core.models import PackageName
  28. from distro_tracker.core.models import Subscription
  29. from distro_tracker.core.models import TeamMembership
  30. from distro_tracker.core.panels import BasePanel
  31. from selenium import webdriver
  32. from selenium.common.exceptions import NoSuchElementException
  33. import selenium.webdriver.support.ui as ui
  34. from selenium.webdriver.common.keys import Keys
  35. from django.utils.six.moves import mock
  36. import os
  37. import time
  38. class SeleniumTestCase(LiveServerTestCase):
  39. """
  40. A class which includes some common functionality for all tests which use
  41. Selenium.
  42. """
  43. def setUp(self):
  44. os.environ['NO_PROXY'] = 'localhost,127.0.0.1,127.0.1.1'
  45. fp = webdriver.FirefoxProfile()
  46. fp.set_preference('network.proxy.type', 0)
  47. self.browser = webdriver.Firefox(firefox_profile=fp)
  48. self.browser.implicitly_wait(3)
  49. self.browser.set_page_load_timeout(3)
  50. self.browser.set_script_timeout(3)
  51. def tearDown(self):
  52. self.browser.close()
  53. def get_page(self, relative):
  54. """
  55. Helper method which points the browser to the absolute URL based on the
  56. given relative URL and the server's live_server_url.
  57. """
  58. self.browser.get(self.absolute_url(relative))
  59. def wait_response(self, seconds):
  60. time.sleep(seconds)
  61. def absolute_url(self, relative):
  62. """
  63. Helper method which builds an absolute URL where the live_server_url is
  64. the root.
  65. """
  66. return self.live_server_url + relative
  67. def input_to_element(self, id, text):
  68. """
  69. Helper method which sends the text to the element with the given ID.
  70. """
  71. element = self.browser.find_element_by_id(id)
  72. element.send_keys(text)
  73. def clear_element_text(self, id):
  74. """
  75. Helper method which removes any text already found in the element with
  76. the given ID.
  77. """
  78. element = self.browser.find_element_by_id(id)
  79. element.clear()
  80. def click_link(self, link_text):
  81. """
  82. Helper method which clicks on the link with the given text.
  83. """
  84. element = self.browser.find_element_by_link_text(link_text)
  85. element.click()
  86. def send_enter(self, id):
  87. """
  88. Helper method which sends the enter key to the element with the given
  89. ID.
  90. """
  91. element = self.browser.find_element_by_id(id)
  92. element.send_keys(Keys.ENTER)
  93. def assert_in_page_body(self, text):
  94. body = self.browser.find_element_by_tag_name('body')
  95. self.assertIn(text, body.text)
  96. def assert_not_in_page_body(self, text):
  97. body = self.browser.find_element_by_tag_name('body')
  98. self.assertNotIn(text, body.text)
  99. def assert_element_with_id_in_page(self, element_id, custom_message=None):
  100. """
  101. Helper method which asserts that the element with the given ID can be
  102. found in the current browser page.
  103. """
  104. if custom_message is None:
  105. custom_message = element_id + " not found in the page."
  106. try:
  107. self.browser.find_element_by_id(element_id)
  108. except NoSuchElementException:
  109. self.fail(custom_message)
  110. def assert_element_with_class_in_page(self, class_name,
  111. custom_message=None):
  112. if custom_message is None:
  113. custom_message = class_name + " not found in the page."
  114. try:
  115. self.browser.find_element_by_class_name(class_name)
  116. except NoSuchElementException:
  117. self.fail(custom_message)
  118. def get_element_by_id(self, element_id):
  119. try:
  120. return self.browser.find_element_by_id(element_id)
  121. except NoSuchElementException:
  122. return None
  123. def get_element_by_class(self, class_name):
  124. try:
  125. return self.browser.find_element_by_class_name(class_name)
  126. except NoSuchElementException:
  127. return None
  128. def assert_current_url_equal(self, url):
  129. """
  130. Helper method which asserts that the given URL equals the current
  131. browser URL.
  132. The given URL should not include the domain.
  133. """
  134. self.assertEqual(
  135. self.browser.current_url,
  136. self.absolute_url(url))
  137. def set_mock_http_response(self, mock_requests, text, status_code=200):
  138. mock_response = mock_requests.models.Response()
  139. mock_response.status_code = status_code
  140. mock_response.text = text
  141. mock_requests.get.return_value = mock_response
  142. mock_requests.head.return_value = mock_response
  143. def create_test_panel(panel_position):
  144. """
  145. Helper test decorator which creates a TestPanel before running the test and
  146. unregisters it when it completes, making sure all tests are ran in
  147. isolation.
  148. """
  149. def decorator(func):
  150. def wrap(self):
  151. class TestPanel(BasePanel):
  152. html_output = "Hello, world"
  153. position = panel_position
  154. try:
  155. ret = func(self)
  156. finally:
  157. TestPanel.unregister_plugin()
  158. return ret
  159. return wrap
  160. return decorator
  161. class PackagePageTest(SeleniumTestCase):
  162. def setUp(self):
  163. super(PackagePageTest, self).setUp()
  164. self.package = SourcePackageName.objects.create(name='dummy-package')
  165. SourcePackageName.objects.create(name='second-package')
  166. self.binary_package = BinaryPackageName.objects.create(
  167. name='binary-package')
  168. self.binary_package.sourcepackage_set.create(
  169. source_package_name=self.package,
  170. version='1.0.0')
  171. def get_package_url(self, package_name):
  172. """
  173. Helper method returning the URL of the package with the given name.
  174. """
  175. return reverse('dtracker-package-page', kwargs={
  176. 'package_name': package_name,
  177. })
  178. def send_text_to_package_search_form(self, text):
  179. """
  180. Helper function to send text input to the package search form.
  181. """
  182. search_form = self.browser.find_element_by_id('package-search-form')
  183. text_box = search_form.find_element_by_name('package_name')
  184. # Make sure any old text is removed.
  185. text_box.clear()
  186. text_box.send_keys(text)
  187. text_box.send_keys(Keys.ENTER)
  188. def test_access_source_package_page_by_url(self):
  189. """
  190. Tests that users can get to a package's page by going straight to its
  191. URL.
  192. """
  193. # The user tries to visit a package's page.
  194. self.get_page(self.get_package_url(self.package.name))
  195. # The browser shows the package's page, indicated in the page
  196. # title.
  197. self.assertIn(self.package.name, self.browser.title)
  198. # It is displayed in the content, as well.
  199. package_name_element = self.browser.find_element_by_tag_name('h1')
  200. self.assertEqual(package_name_element.text, self.package.name)
  201. # The user sees a footer with general information
  202. self.assert_element_with_id_in_page('footer')
  203. # There is a header with a form with a text box where the user can
  204. # type in the name of a package to get to its page.
  205. self.assert_element_with_id_in_page('package-search-form',
  206. "Form not found")
  207. # So, the uer types the name of another source package...
  208. self.send_text_to_package_search_form('second-package')
  209. # This causes the new pacakge's page to open.
  210. self.assertEqual(
  211. self.browser.current_url,
  212. self.absolute_url(self.get_package_url('second-package')))
  213. # The user would like to see the source package page for a binary
  214. # package. The user types the package name in the search form.
  215. self.send_text_to_package_search_form('binary-package')
  216. self.assertEqual(
  217. self.browser.current_url,
  218. self.absolute_url(self.get_package_url(self.package.name)))
  219. # However, when the user tries a package name which does not exist,
  220. # they expects the response page to state this.
  221. self.send_text_to_package_search_form('no-exist')
  222. self.assert_in_page_body('Package no-exist does not exist')
  223. def test_access_package_page_from_index(self):
  224. """
  225. Tests that the user can access a package page starting from the index
  226. and using the provided form.
  227. """
  228. # The user opens the start page
  229. self.get_page('/')
  230. # The page title should show the index page of the site.
  231. self.assertIn('Package Tracker', self.browser.title)
  232. # There is a form to use for access to packages.
  233. self.assert_element_with_id_in_page('package-search-form')
  234. # The user types in a name of a known source package...
  235. self.send_text_to_package_search_form(self.package.name)
  236. # ...and expects the response to show the package page.
  237. self.assertEqual(
  238. self.browser.current_url,
  239. self.absolute_url(self.package.get_absolute_url()))
  240. # The user goes back to the index...
  241. self.browser.back()
  242. # ...and tries using the form to access a package page, but the package
  243. # does not exist.
  244. self.send_text_to_package_search_form('no-exist')
  245. self.assert_in_page_body('Package no-exist does not exist')
  246. @create_test_panel('left')
  247. def test_include_panel_left(self):
  248. """
  249. Tests whether a package page includes a panel in the left side column.
  250. """
  251. self.get_page(self.get_package_url(self.package.name))
  252. self.assert_element_with_id_in_page('dtracker-package-left')
  253. column = self.browser.find_element_by_id('dtracker-package-left')
  254. self.assertIn("Hello, world", column.text)
  255. @create_test_panel('center')
  256. def test_include_panel_center(self):
  257. """
  258. Tests whether a package page includes a panel in the center column.
  259. """
  260. self.get_page(self.get_package_url(self.package.name))
  261. self.assert_element_with_id_in_page('dtracker-package-center')
  262. column = self.browser.find_element_by_id('dtracker-package-center')
  263. self.assertIn("Hello, world", column.text)
  264. @create_test_panel('right')
  265. def test_include_panel_right(self):
  266. """
  267. Tests whether a package page includes a panel in the right side column.
  268. """
  269. self.get_page(self.get_package_url(self.package.name))
  270. self.assert_element_with_id_in_page('dtracker-package-right')
  271. column = self.browser.find_element_by_id('dtracker-package-right')
  272. self.assertIn("Hello, world", column.text)
  273. User = get_user_model()
  274. class RepositoryAdminTest(SeleniumTestCase):
  275. def setUp(self):
  276. super(RepositoryAdminTest, self).setUp()
  277. # Create a superuser which will be used for the tests
  278. User.objects.create_superuser(
  279. main_email='admin',
  280. password='admin'
  281. )
  282. def login_to_admin(self, username='admin', password='admin'):
  283. """
  284. Helper method which logs the user with the given credentials to the
  285. admin console.
  286. """
  287. self.get_page('/admin/')
  288. self.input_to_element('id_username', username)
  289. self.input_to_element('id_password', password)
  290. self.send_enter('id_password')
  291. @mock.patch('distro_tracker.core.admin.requests')
  292. @mock.patch('distro_tracker.core.retrieve_data.requests')
  293. def test_repository_add(self, mock_requests, mock_requests2):
  294. """
  295. Tests that an admin user is able to add a new repository.
  296. """
  297. # The user first logs in to the admin panel with their credentials.
  298. self.login_to_admin()
  299. # They expect the log in to succeed, responsing with the
  300. # administration page.
  301. self.assertIn('Site administration', self.browser.title)
  302. # The user now wants to go to the repositories management
  303. # page. The necessary link can be found in the page.
  304. try:
  305. self.browser.find_element_by_link_text("Repositories")
  306. except NoSuchElementException:
  307. self.fail("Link for repositories management not found in the admin")
  308. # Clicking on it opens a new page to manage repositories.
  309. self.click_link("Repositories")
  310. self.assertIn(
  311. 'Repositories',
  312. self.browser.find_element_by_class_name('breadcrumbs').text
  313. )
  314. # The user now wants to create a new repository.
  315. self.click_link("Add repository")
  316. self.assert_in_page_body("Add repository")
  317. try:
  318. save_button = self.browser.find_element_by_css_selector(
  319. 'input.default')
  320. except NoSuchElementException:
  321. self.fail("Could not find the save button")
  322. # The user tries clicking the save button immediately
  323. save_button.click()
  324. # But this causes an error since there are some required fields.
  325. self.assert_in_page_body('Please correct the errors below')
  326. # The user enters a name and shorthand for the repository.
  327. self.input_to_element('id_name', 'stable')
  328. self.input_to_element('id_shorthand', 'stable')
  329. # They want to create the repository by using a sources.list entry.
  330. self.input_to_element(
  331. 'id_sources_list_entry',
  332. 'deb http://ftp.bad.debian.org/debian stable'
  333. )
  334. # === Make sure that no actual HTTP requests are sent out ===
  335. self.set_mock_http_response(
  336. mock_requests,
  337. 'Suite: stable\n'
  338. 'Codename: wheezy\n'
  339. 'Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64'
  340. ' kfreebsd-i386 mips mipsel powerpc s390 s390x sparc\n'
  341. 'Components: main contrib non-free\n'
  342. 'Version: 7.1\n'
  343. 'Description: Debian 7.1 Released 15 June 2013\n'
  344. )
  345. self.set_mock_http_response(mock_requests2, 'OK')
  346. # The user decides to save by hitting the enter key
  347. self.send_enter('id_sources_list_entry')
  348. # The response page shows confirmation the repository has been added.
  349. self.assert_in_page_body("added successfully")
  350. # The page also shows the information of the newly added repository.
  351. self.assert_in_page_body('Codename')
  352. self.assert_in_page_body('wheezy')
  353. self.assert_in_page_body('Components')
  354. self.assert_in_page_body('main contrib non-free')
  355. # The user now wants to add another repository
  356. self.click_link("Add repository")
  357. # This time, they want to enter all the necessary data manually.
  358. self.input_to_element('id_name', 'testing')
  359. self.input_to_element('id_shorthand', 'testing')
  360. self.input_to_element('id_uri', 'http://ftp.bad.debian.org/debian')
  361. self.input_to_element('id_suite', 'testing')
  362. self.input_to_element('id_codename', 'jessie')
  363. self.input_to_element('id_components', 'main non-free')
  364. architecture_option = self.browser.find_element_by_css_selector(
  365. '.field-architectures select option[value="1"]')
  366. architecture_option.click()
  367. # Finally the user clicks the save button
  368. self.browser.find_element_by_css_selector('input.default').click()
  369. # The response page confirms that the new repository has been created.
  370. self.assert_in_page_body("added successfully")
  371. # The page also shows the information of the newly added repository.
  372. self.assert_in_page_body('jessie')
  373. self.assert_in_page_body('main non-free')
  374. class UserAccountsTestMixin(object):
  375. """
  376. Defines some common methods for all user account tests.
  377. """
  378. def setUp(self):
  379. super(UserAccountsTestMixin, self).setUp()
  380. self.package = SourcePackageName.objects.create(name='dummy-package')
  381. self.password = 'asdf'
  382. self.user = User.objects.create_user(
  383. main_email='user@domain.com', password=self.password,
  384. first_name='', last_name='')
  385. def refresh_user_object(self):
  386. """
  387. The method retrieves the user instance from the database forcing any
  388. cached properties to reload. This can be used when the user's
  389. properties need to be tested for updated values.
  390. """
  391. self.user = User.objects.get(main_email=self.user.main_email)
  392. def get_login_url(self):
  393. return reverse('dtracker-accounts-login')
  394. def get_profile_url(self):
  395. return reverse('dtracker-accounts-profile')
  396. def get_package_url(self, package_name):
  397. return reverse('dtracker-package-page', kwargs={
  398. 'package_name': package_name,
  399. })
  400. def create_user(self, main_email, password, associated_emails=()):
  401. u = User.objects.create_user(main_email, password=password)
  402. for associated_email in associated_emails:
  403. u.emails.create(email=associated_email)
  404. return u
  405. def log_in(self, user=None, password=None):
  406. """
  407. Helper method which logs the user in, without taking any shortcuts (it
  408. goes through the steps to fill in the form and submit it).
  409. """
  410. if user is None:
  411. user = self.user
  412. if password is None:
  413. password = self.password
  414. self.get_page(self.get_login_url())
  415. self.input_to_element('id_username', user.main_email)
  416. self.input_to_element('id_password', password)
  417. self.send_enter('id_password')
  418. class UserRegistrationTest(UserAccountsTestMixin, SeleniumTestCase):
  419. """
  420. Tests for the user registration story.
  421. """
  422. def setUp(self):
  423. super(UserRegistrationTest, self).setUp()
  424. # User registration tests do not want any already registered users
  425. UserEmail.objects.all().delete()
  426. User.objects.all().delete()
  427. def get_confirmation_url(self, message):
  428. """
  429. Extracts the confirmation URL from the given email message.
  430. Returns ``None`` if the message did not contain a confirmation URL.
  431. """
  432. match = self.re_confirmation_url.search(message.body)
  433. if not match:
  434. return None
  435. return match.group(1)
  436. def get_registration_url(self):
  437. return reverse('dtracker-accounts-register')
  438. def test_user_register(self):
  439. profile_url = self.get_profile_url()
  440. password_form_id = 'form-reset-password'
  441. user_email = 'user@domain.com'
  442. # === Preconditions: ===
  443. # === No registered users or command confirmations ===
  444. self.assertEqual(0, User.objects.count())
  445. self.assertEqual(0, UserRegistrationConfirmation.objects.count())
  446. # === Start of the test. ===
  447. # The user opens the front page
  448. self.get_page('/')
  449. # The page shows a link to a registration page
  450. try:
  451. self.browser.find_element_by_link_text("Register")
  452. except NoSuchElementException:
  453. self.fail("Link for user registration not found on the front page")
  454. # Upon clicking the link, the user is taken to the registration page
  455. self.click_link("Register")
  456. self.assert_current_url_equal(self.get_registration_url())
  457. # The page shows a registration form
  458. self.assert_element_with_id_in_page('form-register')
  459. # The user inputs only the email address
  460. self.input_to_element("id_main_email", user_email)
  461. self.send_enter('id_main_email')
  462. # The user is notified of a successful registration
  463. self.assert_current_url_equal(
  464. reverse('dtracker-accounts-register-success'))
  465. # The user receives an email with the confirmation URL
  466. self.assertEqual(1, len(mail.outbox))
  467. # === Get confirmation key from the database ===
  468. self.assertEqual(1, UserRegistrationConfirmation.objects.count())
  469. confirmation = UserRegistrationConfirmation.objects.all()[0]
  470. self.assertIn(confirmation.confirmation_key, mail.outbox[0].body)
  471. # The user goes to the confirmation URL
  472. confirmation_url = reverse(
  473. 'dtracker-accounts-confirm-registration', kwargs={
  474. 'confirmation_key': confirmation.confirmation_key
  475. })
  476. self.get_page(confirmation_url)
  477. # The response page shows a password entry form.
  478. self.assert_element_with_id_in_page(password_form_id)
  479. # However, the user first goes back to the index...
  480. self.get_page('/')
  481. # ...and then goes back to the confirmation page which is still valid
  482. self.get_page(confirmation_url)
  483. password = 'asdf'
  484. self.input_to_element('id_password1', password)
  485. self.input_to_element('id_password2', password)
  486. self.send_enter('id_password2')
  487. # The user is now successfully logged in with their profile page open.
  488. self.assert_current_url_equal(profile_url)
  489. # A message confirms that the user is now registered.
  490. self.assert_in_page_body('You have successfully registered to the')
  491. # When the user tries opening the confirmation page for the same key
  492. # again, it is no longer valid
  493. self.get_page(confirmation_url)
  494. with self.assertRaises(NoSuchElementException):
  495. self.browser.find_element_by_id(password_form_id)
  496. # This is because the confirmation model instance has been removed...
  497. self.assertEqual(0, UserRegistrationConfirmation.objects.count())
  498. # The user goes back to the profile page and this time there is no
  499. # message confirming their registration.
  500. self.get_page(profile_url)
  501. self.assert_not_in_page_body('You have successfully registered to the')
  502. # The user now wishes to log out
  503. self.assert_in_page_body('Log out')
  504. self.click_link('Log out')
  505. # Because the session was on a private page, the user is now
  506. # redirected back to the index page.
  507. self.assert_current_url_equal('/')
  508. # From there, the user tries logging in with their new account.
  509. self.click_link('Log in')
  510. self.input_to_element('id_username', user_email)
  511. self.input_to_element('id_password', password)
  512. self.send_enter('id_password')
  513. # The response is the user's profile page.
  514. self.assert_current_url_equal(self.get_profile_url())
  515. def test_register_email_already_has_subscriptions(self):
  516. """
  517. Tests that a user can register using an email which already has
  518. subscriptions to some packages.
  519. """
  520. # === Set up such an email ===
  521. email = UserEmail.objects.create(email='user@domain.com')
  522. package_name = 'dummy-package'
  523. Subscription.objects.create_for(
  524. email=email,
  525. package_name=package_name)
  526. # The user opens the registration page and enters the email
  527. self.get_page(self.get_registration_url())
  528. self.input_to_element('id_main_email', email.email)
  529. self.send_enter('id_main_email')
  530. self.wait_response(1)
  531. # The user is successfully registered
  532. self.assertEqual(1, User.objects.count())
  533. user = User.objects.all()[0]
  534. self.assertEqual(email.email, user.main_email)
  535. self.assertEqual(
  536. [email.email],
  537. [e.email for e in user.emails.all()])
  538. # A message confirms that the user's registration is successful.
  539. self.assert_in_page_body(
  540. 'Congratulations, the registration is almost over.')
  541. # The existing subscriptions are not removed
  542. self.assertTrue(user.is_subscribed_to(package_name))
  543. def test_user_registered(self):
  544. """
  545. Tests that a user registration fails when there is already a registered
  546. user with the given email.
  547. """
  548. # === Set up a registered user ===
  549. user_email = 'user@domain.com'
  550. associated_email = 'email@domain.com'
  551. self.create_user(user_email, 'asdf', [associated_email])
  552. # The user goes to the registration page
  553. self.get_page(self.get_registration_url())
  554. # The user enters the already existing user's email
  555. self.input_to_element('id_main_email', user_email)
  556. self.send_enter('id_main_email')
  557. # The response is the same page...
  558. self.assert_current_url_equal(self.get_registration_url())
  559. # ... and shows an error message for the duplicate email address.
  560. self.assert_in_page_body('email address is already in use')
  561. # The user now tries using another email address associated
  562. # with the existing user account.
  563. self.clear_element_text('id_main_email')
  564. self.input_to_element('id_main_email', associated_email)
  565. self.send_enter('id_main_email')
  566. # The response is that same page...
  567. self.assert_current_url_equal(self.get_registration_url())
  568. # ... and shows an error message for the duplicate email address.
  569. self.assert_in_page_body('email address is already in use')
  570. def test_login(self):
  571. """
  572. Tests that a user can log in when they already have an account.
  573. """
  574. # === Set up an account ===
  575. user_email = 'user@domain.com'
  576. associated_emails = ['email@domain.com']
  577. password = 'asdf'
  578. self.create_user(user_email, password, associated_emails)
  579. # The user opens the front page and tries going to the log in page
  580. self.get_page('/')
  581. self.assert_in_page_body('Log in')
  582. self.click_link('Log in')
  583. # The user is now found in the log in page
  584. self.assert_current_url_equal(self.get_login_url())
  585. # The page shows a log in form.
  586. self.assert_element_with_id_in_page('form-login')
  587. # The user enters the correct email address, but incorrect password.
  588. self.input_to_element('id_username', user_email)
  589. self.input_to_element('id_password', 'fdsa')
  590. self.send_enter('id_password')
  591. # The response shows an error message for incorrect credentials.
  592. self.assert_in_page_body('Please enter a correct email and password')
  593. # Now the user correctly enters the password. The email
  594. # address should not need to be entered again.
  595. self.input_to_element('id_password', password)
  596. self.send_enter('id_password')
  597. # The user is redirected to their profile page.
  598. self.assert_current_url_equal(self.get_profile_url())
  599. def test_login_associated_email(self):
  600. """
  601. Tests that a user can log in with an associated email.
  602. """
  603. # === Set up an account ===
  604. user_email = 'user@domain.com'
  605. associated_emails = ['email@domain.com']
  606. password = 'asdf'
  607. self.create_user(user_email, password, associated_emails)
  608. # The user goes to the log in page
  609. self.get_page(self.get_login_url())
  610. # The page shows a log in form.
  611. self.assert_element_with_id_in_page('form-login')
  612. # The user enters the associated email address and password.
  613. self.input_to_element('id_username', associated_emails[0])
  614. self.input_to_element('id_password', password)
  615. self.send_enter('id_password')
  616. # The user is redirected to their profile page.
  617. self.assert_current_url_equal(self.get_profile_url())
  618. def test_logout_from_package_page(self):
  619. """
  620. If a user logs out when on the package page, the response
  621. should not redirect to the index.
  622. """
  623. # === Set up an account ===
  624. user_email = 'user@domain.com'
  625. associated_emails = ['email@domain.com']
  626. password = 'asdf'
  627. self.create_user(user_email, password, associated_emails)
  628. # === Set up an existing package ===
  629. package_name = 'dummy'
  630. SourcePackageName.objects.create(name=package_name)
  631. # The user logs in
  632. self.get_page(self.get_login_url())
  633. self.input_to_element('id_username', associated_emails[0])
  634. self.input_to_element('id_password', password)
  635. self.send_enter('id_password')
  636. # The user goes to the package page
  637. self.get_page('/' + package_name)
  638. # The page shows a link to log out.
  639. self.assert_in_page_body('Log out')
  640. # The user selects the link to log out.
  641. self.click_link('Log out')
  642. # The user is still at the package page, but no longer logged in
  643. self.assert_current_url_equal(self.get_package_url(package_name))
  644. self.assert_not_in_page_body('Log out')
  645. self.assert_in_page_body('Log in')
  646. # The user tries going to their profile page, but is
  647. # definitely logged out...
  648. self.get_page(self.get_profile_url())
  649. # ... which means the response redirects to the log in page.
  650. self.assert_current_url_equal(
  651. self.get_login_url() + '?next=' + self.get_profile_url())
  652. class SubscribeToPackageTest(UserAccountsTestMixin, SeleniumTestCase):
  653. """
  654. Tests for stories regarding subscribing to a package over the Web.
  655. """
  656. def get_subscriptions_url(self):
  657. return reverse('dtracker-accounts-subscriptions')
  658. def test_subscribe_from_package_page(self):
  659. """
  660. Tests that a user that has only one email address can subscribe to a
  661. package directly from the package page.
  662. """
  663. # The user first logs in
  664. self.log_in()
  665. # The user opens a package page
  666. self.get_page('/' + self.package.name)
  667. # The page shows a button allowing them to subscribe to the package.
  668. self.assert_element_with_id_in_page('subscribe-button')
  669. # So they click it.
  670. button = self.get_element_by_id('subscribe-button')
  671. button.click()
  672. # The subscribe button is no longer found in the page
  673. button = self.get_element_by_id('subscribe-button')
  674. # === Give the page a chance to refresh ===
  675. wait = ui.WebDriverWait(self.browser, 1)
  676. wait.until(lambda browser: not button.is_displayed())
  677. self.assertFalse(button.is_displayed())
  678. # It has been replaced by the unsubscribe button
  679. self.assert_element_with_id_in_page('unsubscribe-button')
  680. unsubscribe_button = self.get_element_by_id('unsubscribe-button')
  681. self.assertTrue(unsubscribe_button.is_displayed())
  682. # === The user has really been subscribed to the package? ===
  683. self.assertTrue(self.user.is_subscribed_to(self.package))
  684. def test_subscribe_not_logged_in(self):
  685. """
  686. Tests that when a user is not logged in, the response
  687. redirects to the log in page instead of subscribing to the
  688. package.
  689. """
  690. # The user opens the package page
  691. self.get_page('/' + self.package.name)
  692. # ...and tries subscribing to the package
  693. self.get_element_by_id('subscribe-not-logged-in-button').click()
  694. # ...only to find himself redirected to the log in page.
  695. self.assert_current_url_equal(self.get_login_url())
  696. def test_subscribe_multiple_associated_emails(self):
  697. """
  698. Tests that a user with multiple associated email addresses is
  699. offered a choice which address to use to subscribe to a
  700. package.
  701. """
  702. # === Set up such a user ===
  703. other_email = 'other-email@domain.com'
  704. self.user.emails.create(email=other_email)
  705. # The user logs in
  706. self.log_in()
  707. # The user opens a package page and clicks to subscribe button
  708. self.get_page('/' + self.package.name)
  709. self.get_element_by_id('subscribe-button').click()
  710. self.wait_response(1)
  711. # The user is presented with a choice of their email addresses.
  712. for email in self.user.emails.all():
  713. self.assert_in_page_body(email.email)
  714. # The user decides to cancel the subscription by dismissing the popup
  715. self.get_element_by_id('cancel-choose-email').click()
  716. self.wait_response(1)
  717. # === The user is not subscribed to anything yet ===
  718. self.assertEqual(0, Subscription.objects.count())
  719. # The user clicks the subscribe button again
  720. self.get_element_by_id('subscribe-button').click()
  721. self.wait_response(1)
  722. # ... and chooses to subscribe using their associated email address.
  723. self.assert_element_with_id_in_page('choose-email-1')
  724. self.get_element_by_id('choose-email-1').click()
  725. self.wait_response(1)
  726. # === User is now subscribed with only the clicked email ===
  727. self.assertEqual(1, Subscription.objects.count())
  728. sub = Subscription.objects.all()[0]
  729. self.assertEqual(other_email, sub.email_settings.user_email.email)
  730. # The UI that the user sees reflects this
  731. self.assert_element_with_id_in_page('unsubscribe-button')
  732. unsubscribe_button = self.get_element_by_id('unsubscribe-button')
  733. self.assertTrue(unsubscribe_button.is_displayed())
  734. def test_unsubscribe_all_emails(self):
  735. """
  736. Tests unsubscribing all user's emails from a package.
  737. """
  738. # === Set up a user with multiple emails subscribed to a package ===
  739. other_email = 'other-email@domain.com'
  740. self.user.emails.create(email=other_email)
  741. for email in self.user.emails.all():
  742. Subscription.objects.create_for(
  743. email=email.email,
  744. package_name=self.package.name)
  745. # The user logs in and opens the package page
  746. self.log_in()
  747. self.get_page('/' + self.package.name)
  748. # The page shows a button to unsubscribe from the package.
  749. self.assert_in_page_body('Unsubscribe')
  750. self.assert_element_with_id_in_page('unsubscribe-button')
  751. # The user decides to unsubscribe and clicks the button...
  752. self.get_element_by_id('unsubscribe-button').click()
  753. self.wait_response(1)
  754. # === The user is really unsubscribed from the package ===
  755. self.assertFalse(self.user.is_subscribed_to(self.package))
  756. # The user sees the subscribe button instead of the unsubscribe button
  757. sub_button = self.get_element_by_id('subscribe-button')
  758. self.assertTrue(sub_button.is_displayed())
  759. unsub_button = self.get_element_by_id('unsubscribe-button')
  760. self.assertFalse(unsub_button.is_displayed())
  761. def test_subscribe_package_from_subscription_tab(self):
  762. """
  763. This test validates that a user can correctly subscribe to a package
  764. from the subscription tab in its personnal space.
  765. """
  766. # Initially the user is not subscribed to the package
  767. self.assertFalse(self.user.is_subscribed_to(self.package))
  768. # The user logs in and goes to their subscriptions page
  769. self.log_in()
  770. self.get_page(self.get_subscriptions_url())
  771. # To ensure the subscription page is fully charged
  772. self.wait_response(1)
  773. self.assert_in_page_body('Subscribe')
  774. # Checking that at least one checkbox is checked
  775. available_mails = self.browser.find_elements_by_xpath(
  776. "//input[@type='checkbox'][@name='email']")
  777. is_there_checked_emails = False
  778. for checkbox in available_mails:
  779. if checkbox.is_selected():
  780. is_there_checked_emails = True
  781. break
  782. self.assertTrue(is_there_checked_emails)
  783. # Filling the package search field
  784. self.assert_element_with_id_in_page('package-search-input')
  785. self.input_to_element('package-search-input', self.package.name)
  786. # Subscribing to the package and ensuring it's completely done!
  787. self.send_enter('package-search-input')
  788. self.wait_response(1)
  789. self.assertTrue(self.user.is_subscribed_to(self.package))
  790. def test_package_subscription_no_email_from_subscription_tab_fails(self):
  791. """
  792. The UI should prevent the user from forgetting to check at least one
  793. email checkbox from the subscription tab in its personnal space.
  794. """
  795. # Initially the user is not subscribed to the package
  796. self.assertFalse(self.user.is_subscribed_to(self.package))
  797. # The user logs in and goes to their subscriptions page
  798. self.log_in()
  799. self.get_page(self.get_subscriptions_url())
  800. # To ensure the subscription page is fully charged
  801. self.wait_response(1)
  802. self.assert_in_page_body('Subscribe')
  803. # All email checkboxes should be unchecked
  804. available_mails = self.browser.find_elements_by_xpath(
  805. "//input[@type='checkbox'][@name='email']")
  806. for checkbox in available_mails:
  807. if checkbox.is_selected():
  808. checkbox.click()
  809. # The user specifies the package name...
  810. self.assert_element_with_id_in_page('package-search-input')
  811. self.input_to_element('package-search-input', self.package.name)
  812. # ... then submits the form to subscribe to the package.
  813. self.send_enter('package-search-input')
  814. self.wait_response(1)
  815. # The page shows a message stating that the field is required.
  816. self.assert_in_page_body('You need to select at least an email')
  817. # The user should still not be subscribed to the package.
  818. self.assertFalse(self.user.is_subscribed_to(self.package))
  819. def test_package_subscription_no_package_from_subscription_tab_fails(self):
  820. """
  821. The UI should prevent the user from forgetting to check at least one
  822. email checkbox from the subscription tab in its personnal space.
  823. """
  824. # Initially the user is not subscribed to the package
  825. self.assertFalse(self.user.is_subscribed_to(self.package))
  826. # The user logs in and goes to their subscriptions page.
  827. self.log_in()
  828. self.get_page(self.get_subscriptions_url())
  829. # To ensure the subscription page is fully charged
  830. self.wait_response(1)
  831. self.assert_in_page_body('Subscribe')
  832. # Checking that at least one checkbox is checked
  833. available_mails = self.browser.find_elements_by_xpath(
  834. "//input[@type='checkbox'][@name='email']")
  835. is_there_checked_emails = False
  836. for checkbox in available_mails:
  837. if checkbox.is_selected():
  838. is_there_checked_emails = True
  839. break
  840. self.assertTrue(is_there_checked_emails)
  841. # The user specifies an empty package name...
  842. self.assert_element_with_id_in_page('package-search-input')
  843. self.input_to_element('package-search-input', '')
  844. # ... then submits the form to subscribe to the package.
  845. self.send_enter('package-search-input')
  846. self.wait_response(1)
  847. # The page shows a message stating that the field is required.
  848. self.assert_in_page_body('This field is required')
  849. # The user should still not be subscribed to the package.
  850. self.assertFalse(self.user.is_subscribed_to(self.package))
  851. class ChangeProfileTest(UserAccountsTestMixin, SeleniumTestCase):
  852. def test_modify_personal_info(self):
  853. """
  854. Tests that the user is able to change their personal info upon
  855. logging in.
  856. """
  857. # The user logs in
  858. self.log_in()
  859. # The response page shows a link to modify personal information.
  860. self.assert_in_page_body('Personal Information')
  861. self.click_link('Personal Information')
  862. # The page shows a form to change the user's name.
  863. self.assert_element_with_id_in_page('form-change-profile')
  864. # The user decides to input a new name
  865. name = 'Name'
  866. old_last_name = self.user.last_name
  867. self.input_to_element('id_first_name', name)
  868. # ...and submits the form
  869. self.send_enter('id_first_name')
  870. # The user is met with a notification that the information has been
  871. # updated.
  872. self.assert_in_page_body('Successfully changed your information')
  873. # === The user's name has really changed ===
  874. self.refresh_user_object()
  875. self.assertEqual(name, self.user.first_name)
  876. # === But the last name has not ===
  877. self.assertEqual(old_last_name, self.user.last_name)
  878. # The user now wants to update both their first and last name.
  879. self.clear_element_text('id_first_name')
  880. new_first_name, new_last_name = 'Name', 'Last Name'
  881. # The user fills in the form
  882. self.input_to_element('id_first_name', new_first_name)
  883. self.input_to_element('id_last_name', new_last_name)
  884. # ...and submits it.
  885. self.send_enter('id_last_name')
  886. # The response shows a confirmation of success.
  887. self.assert_in_page_body('Successfully changed your information')
  888. # === The information has actually been updated ===
  889. self.refresh_user_object()
  890. self.assertEqual(new_first_name, self.user.first_name)
  891. self.assertEqual(new_last_name, self.user.last_name)
  892. # The user navigates away from the page now
  893. self.get_page(self.get_profile_url())
  894. # And then goes back
  895. self.click_link('Personal Information')
  896. # There are no notifications about modification in the page nw
  897. self.assert_not_in_page_body('Successfully changed your information')
  898. # And the user's first/last name is already filled in the form
  899. self.assertEqual(
  900. new_first_name,
  901. self.get_element_by_id('id_first_name').get_attribute('value'))
  902. self.assertEqual(
  903. new_last_name,
  904. self.get_element_by_id('id_last_name').get_attribute('value'))
  905. def test_change_password(self):
  906. """
  907. Tests that the user can change their password upon logging in.
  908. """
  909. # The user logs in
  910. self.log_in()
  911. # The response page shows a link to change their password...
  912. self.assert_in_page_body('Change Password')
  913. # ... and clicks it.
  914. self.click_link('Change Password')
  915. # The response page shows a form to enter the new password.
  916. self.assert_element_with_id_in_page('form-change-password')
  917. # The user first enters a wrong current password
  918. new_password = 'new-password'
  919. self.input_to_element('id_old_password', 'this-password-is-incorrect')
  920. self.input_to_element('id_new_password1', new_password)
  921. self.input_to_element('id_new_password2', new_password)
  922. self.send_enter('id_new_password2')
  923. # The response shows an error saying the current password was
  924. # incorrect.
  925. self.assert_in_page_body('Your old password was entered incorrectly')
  926. # The user enters their current password correctly, but
  927. # forgets the new password.
  928. self.input_to_element('id_old_password', self.password)
  929. self.send_enter('id_old_password')
  930. # The response shows a message that the field is required.
  931. self.assert_in_page_body('This field is required')
  932. # This time, the user enters both the old password and fills in the new
  933. # password fields, but they are mismatched
  934. self.input_to_element('id_old_password', self.password)
  935. self.input_to_element('id_new_password1', new_password)
  936. self.input_to_element('id_new_password2', new_password + '-miss-match')
  937. self.send_enter('id_new_password2')
  938. # The response shows a message that the password change failed
  939. # again.
  940. self.assert_in_page_body("The two password fields didn't match")
  941. # In the end, the user manages to fill in the form correctly!
  942. self.input_to_element('id_old_password', self.password)
  943. self.input_to_element('id_new_password1', new_password)
  944. self.input_to_element('id_new_password2', new_password)
  945. self.send_enter('id_new_password2')
  946. # The response shows a message confirming that the password
  947. # was changed.
  948. self.assert_in_page_body('Successfully updated your password')
  949. # The user logs out in order to try using their new password.
  950. self.click_link('Log out')
  951. # The user tries logging in using their old account password.
  952. self.log_in()
  953. # The response shows an error message for incorrect credentials.
  954. self.assert_in_page_body('Please enter a correct email and password')
  955. # Now they try with their new password.
  956. self.password = new_password
  957. self.log_in()
  958. # The user is finally logged in using their new account details.
  959. self.assert_current_url_equal(self.get_profile_url())
  960. def test_reset_password(self):
  961. """
  962. Tests that a user is able to reset their password if they forgot it.
  963. """
  964. # The user goes to the login page
  965. self.get_page(self.get_login_url())
  966. # The page shows a link to reset their password.
  967. self.assert_in_page_body('Forgot your password?')
  968. # The user has forgotten their password, so clicks the link.
  969. self.click_link('Forgot your password?')
  970. # The user sees a form to reset their password.
  971. self.assert_element_with_id_in_page('form-reset-password')
  972. # First they enter an email address not associated with any account.
  973. self.input_to_element('id_email', 'this-does-not-exist@domain.com')
  974. self.send_enter('id_email')
  975. # The response is the same page, with a warning that there is
  976. # no user with that email address.
  977. self.assert_in_page_body('No user with the given email is registered')
  978. # The user now correctly enters their own email address.
  979. self.clear_element_text('id_email')
  980. self.input_to_element('id_email', self.user.main_email)
  981. self.send_enter('id_email')
  982. # The response is another page, with a message that they must
  983. # check their email for a confirmation message.
  984. self.assert_in_page_body('Please check your email inbox for details')
  985. # === A confirmation email is actually sent? ===
  986. self.assertEqual(1, len(mail.outbox))
  987. confirmation = ResetPasswordConfirmation.objects.all()[0]
  988. # The user goes to the confirmation URL.
  989. self.get_page(reverse('dtracker-accounts-reset-password', kwargs={
  990. 'confirmation_key': confirmation.confirmation_key,
  991. }))
  992. # The response page asks them to enter a new password...
  993. self.assert_in_page_body('please enter a new password for your account')
  994. # ...so they do.
  995. new_password = self.password + '-new'
  996. self.input_to_element('id_password1', new_password)
  997. self.input_to_element('id_password2', new_password)
  998. self.send_enter('id_password2')
  999. # The response page redirects to the profile page, with a
  1000. # message that their password has been reset.
  1001. self.assert_current_url_equal(self.get_profile_url())
  1002. self.assert_in_page_body('You have successfully reset your password')
  1003. # The user logs out and tries logging back in with their new
  1004. # password.
  1005. self.click_link('Log out')
  1006. self.password = new_password
  1007. self.log_in()
  1008. # The user has successfully logged in.
  1009. self.assert_current_url_equal(self.get_profile_url())
  1010. def test_manage_account_emails(self):
  1011. """
  1012. Tests that the user can manage which email addresses are
  1013. associated with their account.
  1014. """
  1015. # The user logs in and goes to the account email management page
  1016. self.log_in()
  1017. self.click_link('Account Emails')
  1018. # The response page shows a form to add new email addresses to
  1019. # their account.
  1020. self.assert_element_with_id_in_page('form-add-account-email')
  1021. # The user adds a new email address.
  1022. new_email = 'completely-new-email@domain.com'
  1023. self.input_to_element('id_email', new_email)
  1024. self.send_enter('id_email')
  1025. # The user is notified that in order to activate the email association
  1026. # they must demonstrate ownership of the email address.
  1027. self.assert_in_page_body('you must follow the confirmation link')
  1028. self.assert_not_in_page_body(new_email)
  1029. # === The confirmation email sent? ===
  1030. self.assertEqual(1, len(mail.outbox))
  1031. self.assertIn(new_email, mail.outbox[0].to)
  1032. # === Confirmation created? ===
  1033. self.assertEqual(1, AddEmailConfirmation.objects.count())
  1034. confirmation = AddEmailConfirmation.objects.all()[0]
  1035. # The user now visits the confirmation URL...
  1036. self.get_page(reverse('dtracker-accounts-confirm-add-email', kwargs={
  1037. 'confirmation_key': confirmation.confirmation_key,
  1038. }))
  1039. # The response page shows a confirmation message that the
  1040. # address is associated with their account.
  1041. self.assert_in_page_body('now associated with your account')
  1042. # The user goes back to their profile page.
  1043. self.click_link('Profile')
  1044. self.click_link('Account Emails')
  1045. # The new email address is now in the list of email addresses
  1046. # for the account.
  1047. self.assert_in_page_body(new_email)
  1048. # The user tries adding an email already associated with their account.
  1049. self.input_to_element('id_email', new_email)
  1050. self.send_enter('id_email')
  1051. # The response shows a warning that the account is already
  1052. # associated with the given email address.
  1053. self.assert_in_page_body(
  1054. 'This email is already associated with your account')
  1055. def test_merge_accounts(self):
  1056. # === Set up an additional existing user account ===
  1057. password = 'other-password'
  1058. other_email = 'other@domain.com'
  1059. other_user = self.create_user(other_email, password)
  1060. # The user logs in, then goes to the form to add an email address.
  1061. self.log_in()
  1062. self.click_link('Account Emails')
  1063. # They input an address already associated with a different user.
  1064. self.input_to_element('id_email', other_email)
  1065. self.send_enter('id_email')
  1066. # The response page requests confirmation to merge the accounts.
  1067. self.assert_in_page_body('Are you sure you want to merge the accounts')
  1068. # The user clicks the button to confirm the merge.
  1069. self.assert_element_with_id_in_page('confirm-merge-button')
  1070. self.get_element_by_id('confirm-merge-button').click()
  1071. # The response notifies the user the merge is ineffective
  1072. # until the confirmation link is visited.
  1073. self.assert_in_page_body('you must follow a confirmation link')
  1074. # A confirmation mail is sent
  1075. self.assertEqual(1, len(mail.outbox))
  1076. # The mail was not sent to the logged in user, rather the one being
  1077. # merged to the account
  1078. self.assertIn(other_email, mail.outbox[0].to)
  1079. # === A confirmation instance is created? ===
  1080. self.assertEqual(1, MergeAccountConfirmation.objects.count())
  1081. confirmation = MergeAccountConfirmation.objects.all()[0]
  1082. # The user tries going to the confirmation URL without logging in to
  1083. # the other account
  1084. confirmation_url = reverse('dtracker-accounts-merge-finalize', kwargs={
  1085. 'confirmation_key': confirmation.confirmation_key,
  1086. })
  1087. self.get_page(confirmation_url)
  1088. # ...which is forbidden and has no effect
  1089. self.assertEqual(2, User.objects.count())
  1090. # The user now logs in with the other account
  1091. self.log_in(other_user, password)
  1092. # The user visits the URL from the confirmation email message.
  1093. self.get_page(confirmation_url)
  1094. # The response page asks for a final confirmation.
  1095. self.assert_in_page_body(
  1096. 'Are you sure you want to finalize the accounts merge')
  1097. self.assert_element_with_id_in_page('finalize-merge-button')
  1098. # The user decides to go on with the merge
  1099. self.get_element_by_id('finalize-merge-button').click()
  1100. # The user is notified that the merge was successful
  1101. self.assert_in_page_body(
  1102. 'The two accounts have been successfully merged')
  1103. # === User accounts are really changed? ===
  1104. self.assertEqual(1, User.objects.count())
  1105. # The user tries logging in with the merged account's password...
  1106. self.log_in(password=password)
  1107. # ...which fails.
  1108. self.assert_in_page_body('Please enter a correct email and password')
  1109. # They log in with the original account details...
  1110. self.log_in()
  1111. # ...which works.
  1112. self.assert_current_url_equal(self.get_profile_url())
  1113. # Both the email addresses are shown as associated with the
  1114. # user's account.
  1115. self.click_link('Account Emails')
  1116. self.assert_in_page_body(self.user.main_email)
  1117. self.assert_in_page_body(other_email)
  1118. class TeamTests(SeleniumTestCase):
  1119. def setUp(self):
  1120. super(TeamTests, self).setUp()
  1121. self.password = 'asdf'
  1122. self.user = User.objects.create_user(
  1123. main_email='user@domain.com', password=self.password,
  1124. first_name='', last_name='')
  1125. def get_login_url(self):
  1126. return reverse('dtracker-accounts-login')
  1127. def get_create_team_url(self):
  1128. return reverse('dtracker-teams-create')
  1129. def get_team_url(self, team_name):
  1130. team = Team.objects.get(name=team_name)
  1131. return team.get_absolute_url()
  1132. def get_delete_team_url(self, team_name):
  1133. team = Team.objects.get(name=team_name)
  1134. return reverse('dtracker-team-delete', kwargs={
  1135. 'slug': team.slug,
  1136. })
  1137. def get_team_deleted_url(self):
  1138. return reverse('dtracker-team-deleted')
  1139. def get_update_team_url(self, team_name):
  1140. team = Team.objects.get(name=team_name)
  1141. return reverse('dtracker-team-update', kwargs={
  1142. 'slug': team.slug,
  1143. })
  1144. def get_subscriptions_url(self):
  1145. return reverse('dtracker-accounts-subscriptions')
  1146. def assert_team_packages_equal(self, team, package_names):
  1147. team_package_names = [p.name for p in team.packages.all()]
  1148. self.assertEqual(len(package_names), len(team_package_names))
  1149. for package_name in package_names:
  1150. self.assertIn(package_name, team_package_names)
  1151. def log_in(self, user=None, password=None):
  1152. """
  1153. Helper method which logs the user in, without taking any shortcuts (it
  1154. goes through the steps to fill in the form and submit it).
  1155. """
  1156. if user is None:
  1157. user = self.user
  1158. if password is None:
  1159. password = self.password
  1160. self.get_page(self.get_login_url())
  1161. self.input_to_element('id_username', user.main_email)
  1162. self.input_to_element('id_password', password)
  1163. self.send_enter('id_password')
  1164. def log_out(self):
  1165. """
  1166. Logs the currently logged in user out.
  1167. """
  1168. self.browser.find_element_by_id("account-logout").click()
  1169. def test_create_team(self):
  1170. """
  1171. Tests that a logged in user can create a new team.
  1172. """
  1173. # The user tries going to the page to create a new team
  1174. self.get_page(self.get_create_team_url())
  1175. # However, the browser is not signed in so the response
  1176. # redirects to the login page.
  1177. self.assertIn(self.get_login_url(), self.browser.current_url)
  1178. self.wait_response(1)
  1179. # The user then logs in
  1180. self.log_in()
  1181. # ...and tries again
  1182. self.get_page(self.get_create_team_url())
  1183. # This time the response page has a form to create a new team.
  1184. self.assert_element_with_id_in_page('create-team-form')
  1185. # The user forgets to input the team name, initially.
  1186. self.send_enter('id_name')
  1187. # Since a name is required, an error is returned
  1188. self.assert_in_page_body('This field is required')
  1189. # The user inputs the team name, but not a maintainer email
  1190. team_name = 'New team'
  1191. self.input_to_element('id_name', team_name)
  1192. self.send_enter('id_name')
  1193. self.wait_response(1)
  1194. # The user is now redirected to the team's page
  1195. self.assert_current_url_equal(self.get_team_url(team_name))
  1196. # === The team actually exists ===
  1197. self.assertEqual(1, Team.objects.filter(name=team_name).count())
  1198. # === The user is its owner ===
  1199. team = Team.objects.get(name=team_name)
  1200. self.assertEqual(self.user, team.owner)
  1201. # The user goes back to the team creation page now
  1202. self.get_page(self.get_create_team_url())
  1203. # They try creating a new team with the same name
  1204. self.input_to_element('id_name', team_name)
  1205. self.send_enter('id_name')
  1206. # This time, the team creation process fails because the team name is
  1207. # not unique.
  1208. self.assert_in_page_body('Team with this Name already exists')
  1209. def test_create_team_maintainer_email(self):
  1210. """
  1211. Tests creating a team with a maintainer email set.
  1212. The team should become automatically associated with the maintainer's
  1213. packages.
  1214. """
  1215. # === Set up some packages maintained by the same maintainer ===
  1216. package_names = [
  1217. 'pkg1',
  1218. 'pkg2',
  1219. ]
  1220. maintainer_email = 'maintainer@domain.com'
  1221. maintainer = ContributorName.objects.create(
  1222. contributor_email=UserEmail.objects.create(
  1223. email=maintainer_email))
  1224. for package_name in package_names:
  1225. SourcePackage.objects.create(
  1226. source_package_name=SourcePackageName.objects.create(
  1227. name=package_name),
  1228. version='1.0.0',
  1229. maintainer=maintainer)
  1230. # === Create a package with no maintainer ===
  1231. SourcePackageName.objects.create(name='dummy-package')
  1232. # === -- ===
  1233. # The user logs in and accesses the create team page.
  1234. self.log_in()
  1235. self.get_page(self.get_create_team_url())
  1236. # They input both the team name and the maintaner name.
  1237. team_name = 'Team name'
  1238. self.input_to_element('id_name', team_name)
  1239. self.input_to_element('id_maintainer_email', maintainer_email)
  1240. self.send_enter('id_maintainer_email')
  1241. self.wait_response(1)
  1242. # The team is successfully created and the user can see the
  1243. # maintainer's packages already in the team's page
  1244. for package_name in package_names:
  1245. self.assert_in_page_body(package_name)
  1246. # === The team really is associated with the packages? ===
  1247. team = Team.objects.all()[0]
  1248. self.assert_team_packages_equal(team, package_names)
  1249. # The user now wants to associate the team with a different maintainer
  1250. # that maintains other packages
  1251. new_package_name = 'pkg3'
  1252. new_maintainer_email = 'new-maintainer@domain.com'
  1253. new_maintainer = ContributorName.objects.create(
  1254. contributor_email=UserEmail.objects.create(
  1255. email=new_maintainer_email))
  1256. SourcePackage.objects.create(
  1257. source_package_name=SourcePackageName.objects.create(
  1258. name=new_package_name),
  1259. version='1.0.0',
  1260. maintainer=new_maintainer)
  1261. self.get_element_by_id('update-team-button').click()
  1262. # The user modifies the maintainer field
  1263. self.clear_element_text('id_maintainer_email')
  1264. self.input_to_element('id_maintainer_email', new_maintainer_email)
  1265. self.send_enter('id_maintainer_email')
  1266. self.wait_response(1)
  1267. # The user is directed back to the team page which shows all the
  1268. # packages previously associated with the team, as well as the ones
  1269. # associated to the new maintainer.
  1270. self.assert_in_page_body(new_package_name)
  1271. for package_name in package_names:
  1272. self.assert_in_page_body(package_name)
  1273. # === The team is really associated with all these packages? ===
  1274. self.assert_team_packages_equal(
  1275. team, package_names + [new_package_name])
  1276. def test_delete_team(self):
  1277. """
  1278. Tests that the owner can delete a team.
  1279. """
  1280. # === Set up a team owned by the user ===
  1281. team_name = 'Team name'
  1282. Team.objects.create_with_slug(owner=self.user, name=team_name)
  1283. # Before logging in the user opens the team page
  1284. self.get_page(self.get_team_url(team_name))
  1285. # The page does not show the delete button.
  1286. self.assert_not_in_page_body("Delete")
  1287. # The user goes directly to the deletion URL.
  1288. self.get_page(self.get_delete_team_url(team_name))
  1289. # But permission is denied to the user
  1290. self.assert_not_in_page_body(team_name)
  1291. # The user now logs in.
  1292. self.log_in()
  1293. self.get_page(self.get_team_url(team_name))
  1294. # The delete button is now offered to the user
  1295. self.assert_element_with_id_in_page('delete-team-button')
  1296. # So the user decides to click it.
  1297. self.get_element_by_id('delete-team-button').click()
  1298. self.wait_response(1)
  1299. # The response shows a popup asking to confirm the team deletion.
  1300. cancel_button = self.get_element_by_id('team-delete-cancel-button')
  1301. confirm_button = self.get_element_by_id('confirm-team-delete-button')
  1302. self.assertTrue(confirm_button.is_displayed())
  1303. self.assertTrue(cancel_button.is_displayed())
  1304. # The user decides to cancel the deletion
  1305. cancel_button.click()
  1306. self.wait_response(1)
  1307. # The response is the team page and the team has not been deleted.
  1308. self.assert_current_url_equal(self.get_team_url(team_name))
  1309. # === The team is still here ===
  1310. self.assertEqual(1, Team.objects.count())
  1311. # The user now deletes the team.
  1312. self.get_element_by_id('delete-team-button').click()
  1313. self.wait_response(1)
  1314. self.get_element_by_id('confirm-team-delete-button').click()
  1315. self.wait_response(1)
  1316. # The response page confirms the team has been successfully
  1317. # deleted.
  1318. self.assert_current_url_equal(self.get_team_deleted_url())
  1319. # === The team is also really deleted? ===
  1320. self.assertEqual(0, Team.objects.count())
  1321. def test_update_team(self):
  1322. """
  1323. Tests that the team owner can update the team's basic info.
  1324. """
  1325. # === Set up a team owned by the user ===
  1326. team_name = 'Team name'
  1327. Team.objects.create_with_slug(owner=self.user, name=team_name)
  1328. # Before logging in the user opens the team page
  1329. self.get_page(self.get_team_url(team_name))
  1330. # The page does not show the update button.
  1331. self.assert_not_in_page_body("Update")
  1332. # The user goes directly to the update URL.
  1333. self.get_page(self.get_update_team_url(team_name))
  1334. # But permission is denied to the user
  1335. self.assert_not_in_page_body(team_name)
  1336. # The user now logs in
  1337. self.log_in()
  1338. self.get_page(self.get_team_url(team_name))
  1339. # The page now shows the update button
  1340. self.assert_element_with_id_in_page('update-team-button')
  1341. # The user clocks the link to update the team information.
  1342. self.get_element_by_id('update-team-button').click()
  1343. # The response page is the update page now...
  1344. self.assert_current_url_equal(self.get_update_team_url(team_name))
  1345. # ... with a form to update the team's info.
  1346. self.assert_element_with_id_in_page('update-team-form')
  1347. # The user modifies the team's description
  1348. new_description = "This is a new description"
  1349. self.input_to_element('id_description', new_description)
  1350. self.send_enter('id_name')
  1351. self.wait_response(1)
  1352. # The user is taken back to the team's page
  1353. self.assert_current_url_equal(self.get_team_url(team_name))
  1354. # The updated information is displayed in the page already
  1355. self.assert_in_page_body(new_description)
  1356. # === The team's info is actually updated? ===
  1357. team = Team.objects.all()[0]
  1358. self.assertEqual(new_description, team.description)
  1359. # The user now wants to update the team's name without affecting the
  1360. # team's URL.
  1361. old_url = self.get_team_url(team_name)
  1362. self.get_element_by_id('update-team-button').click()
  1363. self.clear_element_text('id_name')
  1364. new_name = team_name + ' new name'
  1365. self.input_to_element('id_name', new_name)
  1366. self.send_enter('id_name')
  1367. self.wait_response(1)
  1368. # The user is now found back at the team page which contains the
  1369. # updated name
  1370. self.assert_in_page_body(new_name)
  1371. # However, the package's URL is still the same
  1372. self.assert_current_url_equal(old_url)
  1373. # Now the user wants to modify the team's url without modifying its
  1374. # name.
  1375. self.get_element_by_id('update-team-button').click()
  1376. old_slug = team.slug
  1377. self.clear_element_text('id_slug')
  1378. new_slug = old_slug + '-new-slug'
  1379. self.input_to_element('id_slug', new_slug)
  1380. self.send_enter('id_slug')
  1381. self.wait_response(1)
  1382. # The user is once again back on the team page.
  1383. # The URL has been modified now to contain the new team slug.
  1384. self.assertIn(new_slug, self.browser.current_url)
  1385. # === The slug really is updated? ===
  1386. self.assertEqual(new_slug, Team.objects.all()[0].slug)
  1387. def test_package_management(self):
  1388. """
  1389. Tests that adding/removing packages from the team works as expected.
  1390. """
  1391. # === Set up a team owned by the user ===
  1392. team_name = 'Team name'
  1393. team = Team.objects.create_with_slug(owner=self.user, name=team_name)
  1394. # === Set up some packages which the user can add to the team ===
  1395. package_names = [
  1396. 'pkg1',
  1397. 'pkg2',
  1398. ]
  1399. for package_name in package_names:
  1400. PackageName.objects.create(name=package_name)
  1401. # === -- ===
  1402. # The user opens the team page without logging in
  1403. self.get_page(self.get_team_url(team_name))
  1404. # The page does not show the form to add a package.
  1405. form = self.get_element_by_id('add-team-package-form')
  1406. self.assertIsNone(form)
  1407. # The user logs in and opens the team page
  1408. self.log_in()
  1409. self.get_page(self.get_team_url(team_name))
  1410. # The response page shows the form to add packages now.
  1411. self.assert_element_with_id_in_page('add-team-package-form')
  1412. # The user enters the name of the package to add...
  1413. self.input_to_element('id_package_name', package_names[0])
  1414. # ...and submits the form.
  1415. self.send_enter('id_package_name')
  1416. self.wait_response(1)
  1417. # The user is still in the team page
  1418. self.assert_current_url_equal(self.get_team_url(team_name))
  1419. # The page now shows the package they added in the list of packages.
  1420. self.assert_in_page_body(package_names[0])
  1421. # The user tries adding a new package: one that does not exist.
  1422. self.input_to_element('id_package_name', 'this-does-not-exist')
  1423. self.send_enter('id_package_name')
  1424. self.wait_response(1)
  1425. # The user is still in the team page, but nothing is changed when it
  1426. # comes to the list of packages.
  1427. self.assert_not_in_page_body('this-does-not-exist')
  1428. # The user now wants to remove the package from the team.
  1429. # They click the button to remove the package from the team.
  1430. remove_button = self.browser.find_element_by_css_selector(
  1431. '.remove-package-from-team-button')
  1432. remove_button.click()
  1433. self.wait_response(1)
  1434. # A popup is displayed asking the user to confirm the removal
  1435. # The user decides to cancel the operation
  1436. self.get_element_by_id('remove-package-cancel-button').click()
  1437. self.wait_response(1)
  1438. # The response is still the team page, and the package is not removed.
  1439. self.assert_current_url_equal(self.get_team_url(team_name))
  1440. # === The package is not removed? ===
  1441. self.assertEqual(1, team.packages.count())
  1442. # The user decides to definitely remove the package now
  1443. remove_button.click()
  1444. self.wait_response(1)
  1445. self.get_element_by_id('confirm-remove-package-button').click()
  1446. self.wait_response(1)
  1447. # The user is still on the team page, but the package is not longer
  1448. # a part of the team.
  1449. self.assert_current_url_equal(self.get_team_url(team_name))
  1450. self.assert_not_in_page_body(package_names[0])
  1451. # === The package is really removed from the team ===
  1452. self.assertEqual(0, team.packages.count())
  1453. def test_team_access(self):
  1454. """
  1455. Tests joining and leaving a team.
  1456. """
  1457. # === Set up a team and a user who isn't the owner of the team ===
  1458. team_name = 'Team name'
  1459. team = Team.objects.create_with_slug(owner=self.user, name=team_name)
  1460. user = User.objects.create_user(
  1461. main_email='other@domain.com',
  1462. password=self.password)
  1463. UserEmail.objects.get_or_create(email=user.main_email)
  1464. # === end setup ===
  1465. # The user logs in and goes to the team page
  1466. self.log_in(user)
  1467. self.get_page(self.get_team_url(team_name))
  1468. # The page shows a button to join the team.
  1469. self.assert_element_with_id_in_page('join-team-button')
  1470. # ... so the user clicks it.
  1471. self.get_element_by_id('join-team-button').click()
  1472. self.wait_response(1)
  1473. # The response is still the team page, but now the user is a
  1474. # member of the team.
  1475. self.assert_element_with_id_in_page('add-team-package-form')
  1476. # === The user is really a member? ===
  1477. self.assertTrue(team.user_is_member(user))
  1478. # The page now has a button to leave the team.
  1479. self.assert_element_with_id_in_page('leave-team-button')
  1480. # So the user clicks that button.
  1481. self.get_element_by_id('leave-team-button').click()
  1482. self.wait_response(1)
  1483. # The user is now again not a member of the team
  1484. self.assert_element_with_id_in_page('join-team-button')
  1485. # === The user really isn't a member any more. ===
  1486. self.assertFalse(team.user_is_member(user))
  1487. # The user now logs out
  1488. self.log_out()
  1489. # And tries clicking the join team button
  1490. self.get_element_by_id('join-team-button').click()
  1491. self.wait_response(1)
  1492. # But the response redirects to the login page.
  1493. self.assert_element_with_id_in_page('form-login')
  1494. # === The privacy of the team is switched to private. ===
  1495. team.public = False
  1496. team.save()
  1497. # When the user opens the page again, the join button is replaced with
  1498. # a link to contact the owner.
  1499. self.get_page(self.get_team_url(team_name))
  1500. self.assert_in_page_body('Contact the owner')
  1501. def test_owner_members_management(self):
  1502. """
  1503. Tests that a team owner is able to add/remove members from a separate
  1504. panel.
  1505. """
  1506. team_name = 'Team name'
  1507. Team.objects.create_with_slug(owner=self.user, name=team_name)
  1508. self.log_in()
  1509. self.get_page(self.get_team_url(team_name))
  1510. # The user opens the member management page
  1511. self.get_element_by_id('manage-team-button').click()
  1512. # The user wants to add a new team member
  1513. new_team_member = 'member@domain.com'
  1514. self.input_to_element('id_email', new_team_member)
  1515. self.send_enter('id_email')
  1516. self.wait_response(1)
  1517. # The user is still in the same page, but can see the new member in the
  1518. # list of all members
  1519. self.assert_in_page_body(new_team_member)
  1520. # === The membership is marked muted, though ===
  1521. membership = TeamMembership.objects.all()[0]
  1522. self.assertTrue(membership.muted)
  1523. # === Email was sent to the new member asking for confirmation ===
  1524. self.assertEqual(1, len(mail.outbox))
  1525. self.assertIn(new_team_member, mail.outbox[0].to)
  1526. # The user now decides to remove the team member
  1527. button = \
  1528. self.browser.find_element_by_css_selector('.remove-user-button')
  1529. button.click()
  1530. self.wait_response(1)
  1531. # The user is no longer a part of the team
  1532. self.assert_not_in_page_body(new_team_member)
  1533. def test_toggle_team_mute(self):
  1534. """
  1535. Tests that a team member is able to mute and unmute a team membership
  1536. from the subscription details page.
  1537. """
  1538. # === -- ===
  1539. team_name = 'Team name'
  1540. team = Team.objects.create_with_slug(owner=self.user, name=team_name)
  1541. membership = team.add_members([self.user.emails.all()[0]])[0]
  1542. # === -- ===
  1543. # The user logs in and goes to their subscriptions page.
  1544. self.log_in()
  1545. self.get_page(self.get_subscriptions_url())
  1546. # The response page shows a button to mute the team membership.
  1547. self.assert_in_page_body('Mute')
  1548. # The user clicks the button.
  1549. btn = self.get_element_by_class('toggle-team-mute')
  1550. btn.click()
  1551. self.wait_response(1)
  1552. # The response page is still the team page, and shows a
  1553. # warning that the user's team membership is muted.
  1554. self.assert_element_with_class_in_page('mute-warning')
  1555. # === The membership is actually muted? ===
  1556. membership = TeamMembership.objects.get(pk=membership.pk)
  1557. self.assertTrue(membership.muted)
  1558. # The user now wants to revert this.
  1559. # The page shows the unmute button.
  1560. self.assert_in_page_body('Unmute')
  1561. # The user clicks the button.
  1562. btn = self.get_element_by_class('toggle-team-mute')
  1563. btn.click()
  1564. self.wait_response(1)
  1565. # Once again, the user is still in the subscriptions page, but the
  1566. # button has reverted back to the mute button
  1567. self.assert_in_page_body('Mute')
  1568. # And the warning is gone
  1569. self.assertIsNone(self.get_element_by_class('mute-warning'))