tests.py 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769
  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. # He has reached the package's page which is indicated in the tab's
  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 that he types 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. # he expects to see a page informing him of 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. # He sees it is the index page
  231. self.assertIn('Package Tracker', self.browser.title)
  232. # There is a form which he can use for access to pacakges.
  233. self.assert_element_with_id_in_page('package-search-form')
  234. # He types in a name of a known source package...
  235. self.send_text_to_package_search_form(self.package.name)
  236. # ...and expects to see the package page open.
  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.
  298. self.login_to_admin()
  299. # He expects to be able to successfully access it with his credentials.
  300. self.assertIn('Site administration', self.browser.title)
  301. # He now wants to go to the repositories management page.
  302. # The necessary link can be found in the page.
  303. try:
  304. self.browser.find_element_by_link_text("Repositories")
  305. except NoSuchElementException:
  306. self.fail("Link for repositories management not found in the admin")
  307. # Clicking on it opens a new page to manage repositories.
  308. self.click_link("Repositories")
  309. self.assertIn(
  310. 'Repositories',
  311. self.browser.find_element_by_class_name('breadcrumbs').text
  312. )
  313. # He now wants to create a new repository...
  314. self.click_link("Add repository")
  315. self.assert_in_page_body("Add repository")
  316. try:
  317. save_button = self.browser.find_element_by_css_selector(
  318. 'input.default')
  319. except NoSuchElementException:
  320. self.fail("Could not find the save button")
  321. # The user tries clicking the save button immediately
  322. save_button.click()
  323. # But this causes an error since there are some required fields...
  324. self.assert_in_page_body('Please correct the errors below')
  325. # He enters a name and shorthand for the repository
  326. self.input_to_element('id_name', 'stable')
  327. self.input_to_element('id_shorthand', 'stable')
  328. # He wants to create the repository by using a sources.list entry
  329. self.input_to_element(
  330. 'id_sources_list_entry',
  331. 'deb http://ftp.bad.debian.org/debian stable'
  332. )
  333. # === Make sure that no actual HTTP requests are sent out ===
  334. self.set_mock_http_response(
  335. mock_requests,
  336. 'Suite: stable\n'
  337. 'Codename: wheezy\n'
  338. 'Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64'
  339. ' kfreebsd-i386 mips mipsel powerpc s390 s390x sparc\n'
  340. 'Components: main contrib non-free\n'
  341. 'Version: 7.1\n'
  342. 'Description: Debian 7.1 Released 15 June 2013\n'
  343. )
  344. self.set_mock_http_response(mock_requests2, 'OK')
  345. # The user decides to save by hitting the enter key
  346. self.send_enter('id_sources_list_entry')
  347. # The user sees a message telling him the repository has been added.
  348. self.assert_in_page_body("added successfully")
  349. # He also sees the information of the newly added repository
  350. self.assert_in_page_body('Codename')
  351. self.assert_in_page_body('wheezy')
  352. self.assert_in_page_body('Components')
  353. self.assert_in_page_body('main contrib non-free')
  354. # The user now wants to add another repository
  355. self.click_link("Add repository")
  356. # This time, he wants to enter all the necessary data manually.
  357. self.input_to_element('id_name', 'testing')
  358. self.input_to_element('id_shorthand', 'testing')
  359. self.input_to_element('id_uri', 'http://ftp.bad.debian.org/debian')
  360. self.input_to_element('id_suite', 'testing')
  361. self.input_to_element('id_codename', 'jessie')
  362. self.input_to_element('id_components', 'main non-free')
  363. architecture_option = self.browser.find_element_by_css_selector(
  364. '.field-architectures select option[value="1"]')
  365. architecture_option.click()
  366. # Finally the user clicks the save button
  367. self.browser.find_element_by_css_selector('input.default').click()
  368. # He sees that the new repository has also been created.
  369. self.assert_in_page_body("added successfully")
  370. # He also sees the information of the newly added repository
  371. self.assert_in_page_body('jessie')
  372. self.assert_in_page_body('main non-free')
  373. class UserAccountsTestMixin(object):
  374. """
  375. Defines some common methods for all user account tests.
  376. """
  377. def setUp(self):
  378. super(UserAccountsTestMixin, self).setUp()
  379. self.package = SourcePackageName.objects.create(name='dummy-package')
  380. self.password = 'asdf'
  381. self.user = User.objects.create_user(
  382. main_email='user@domain.com', password=self.password,
  383. first_name='', last_name='')
  384. def refresh_user_object(self):
  385. """
  386. The method retrieves the user instance from the database forcing any
  387. cached properties to reload. This can be used when the user's
  388. properties need to be tested for updated values.
  389. """
  390. self.user = User.objects.get(main_email=self.user.main_email)
  391. def get_login_url(self):
  392. return reverse('dtracker-accounts-login')
  393. def get_profile_url(self):
  394. return reverse('dtracker-accounts-profile')
  395. def get_package_url(self, package_name):
  396. return reverse('dtracker-package-page', kwargs={
  397. 'package_name': package_name,
  398. })
  399. def create_user(self, main_email, password, associated_emails=()):
  400. u = User.objects.create_user(main_email, password=password)
  401. for associated_email in associated_emails:
  402. u.emails.create(email=associated_email)
  403. return u
  404. def log_in(self, user=None, password=None):
  405. """
  406. Helper method which logs the user in, without taking any shortcuts (it
  407. goes through the steps to fill in the form and submit it).
  408. """
  409. if user is None:
  410. user = self.user
  411. if password is None:
  412. password = self.password
  413. self.get_page(self.get_login_url())
  414. self.input_to_element('id_username', user.main_email)
  415. self.input_to_element('id_password', password)
  416. self.send_enter('id_password')
  417. class UserRegistrationTest(UserAccountsTestMixin, SeleniumTestCase):
  418. """
  419. Tests for the user registration story.
  420. """
  421. def setUp(self):
  422. super(UserRegistrationTest, self).setUp()
  423. # User registration tests do not want any already registered users
  424. UserEmail.objects.all().delete()
  425. User.objects.all().delete()
  426. def get_confirmation_url(self, message):
  427. """
  428. Extracts the confirmation URL from the given email message.
  429. Returns ``None`` if the message did not contain a confirmation URL.
  430. """
  431. match = self.re_confirmation_url.search(message.body)
  432. if not match:
  433. return None
  434. return match.group(1)
  435. def get_registration_url(self):
  436. return reverse('dtracker-accounts-register')
  437. def test_user_register(self):
  438. profile_url = self.get_profile_url()
  439. password_form_id = 'form-reset-password'
  440. user_email = 'user@domain.com'
  441. # === Preconditions: ===
  442. # === No registered users or command confirmations ===
  443. self.assertEqual(0, User.objects.count())
  444. self.assertEqual(0, UserRegistrationConfirmation.objects.count())
  445. # === Start of the test. ===
  446. # The user opens the front page
  447. self.get_page('/')
  448. # He can see a link to a registration page
  449. try:
  450. self.browser.find_element_by_link_text("Register")
  451. except NoSuchElementException:
  452. self.fail("Link for user registration not found on the front page")
  453. # Upon clicking the link, the user is taken to the registration page
  454. self.click_link("Register")
  455. self.assert_current_url_equal(self.get_registration_url())
  456. # He can see a registration form
  457. self.assert_element_with_id_in_page('form-register')
  458. # The user inputs only the email address
  459. self.input_to_element("id_main_email", user_email)
  460. self.send_enter('id_main_email')
  461. # The user is notified of a successful registration
  462. self.assert_current_url_equal(
  463. reverse('dtracker-accounts-register-success'))
  464. # The user receives an email with the confirmation URL
  465. self.assertEqual(1, len(mail.outbox))
  466. # === Get confirmation key from the database ===
  467. self.assertEqual(1, UserRegistrationConfirmation.objects.count())
  468. confirmation = UserRegistrationConfirmation.objects.all()[0]
  469. self.assertIn(confirmation.confirmation_key, mail.outbox[0].body)
  470. # The user goes to the confirmation URL
  471. confirmation_url = reverse(
  472. 'dtracker-accounts-confirm-registration', kwargs={
  473. 'confirmation_key': confirmation.confirmation_key
  474. })
  475. self.get_page(confirmation_url)
  476. # The user is asked to enter his password
  477. self.assert_element_with_id_in_page(password_form_id)
  478. # However, the user first goes back to the index...
  479. self.get_page('/')
  480. # ...and then goes back to the confirmation page which is still valid
  481. self.get_page(confirmation_url)
  482. password = 'asdf'
  483. self.input_to_element('id_password1', password)
  484. self.input_to_element('id_password2', password)
  485. self.send_enter('id_password2')
  486. # The user is now successfully logged in with his profile page open
  487. self.assert_current_url_equal(profile_url)
  488. # A message is provided telling the user that he has been registered
  489. self.assert_in_page_body('You have successfully registered to the')
  490. # When the user tries opening the confirmation page for the same key
  491. # again, it is no longer valid
  492. self.get_page(confirmation_url)
  493. with self.assertRaises(NoSuchElementException):
  494. self.browser.find_element_by_id(password_form_id)
  495. # This is because the confirmation model instance has been removed...
  496. self.assertEqual(0, UserRegistrationConfirmation.objects.count())
  497. # The user goes back to the profile page and this time there is no
  498. # message saying he has been registered.
  499. self.get_page(profile_url)
  500. self.assert_not_in_page_body('You have successfully registered to the')
  501. # The user now wishes to log out
  502. self.assert_in_page_body('Log out')
  503. self.click_link('Log out')
  504. # The user is redirected back to the index page since he was found on
  505. # a private page prior to logging out.
  506. self.assert_current_url_equal('/')
  507. # From there, he tries logging in with his new account
  508. self.click_link('Log in')
  509. self.input_to_element('id_username', user_email)
  510. self.input_to_element('id_password', password)
  511. self.send_enter('id_password')
  512. # He is now back at the profile page
  513. self.assert_current_url_equal(self.get_profile_url())
  514. def test_register_email_already_has_subscriptions(self):
  515. """
  516. Tests that a user can register using an email which already has
  517. subscriptions to some packages.
  518. """
  519. # === Set up such an email ===
  520. email = UserEmail.objects.create(email='user@domain.com')
  521. package_name = 'dummy-package'
  522. Subscription.objects.create_for(
  523. email=email,
  524. package_name=package_name)
  525. # The user opens the registration page and enters the email
  526. self.get_page(self.get_registration_url())
  527. self.input_to_element('id_main_email', email.email)
  528. self.send_enter('id_main_email')
  529. self.wait_response(1)
  530. # The user is successfully registered
  531. self.assertEqual(1, User.objects.count())
  532. user = User.objects.all()[0]
  533. self.assertEqual(email.email, user.main_email)
  534. self.assertEqual(
  535. [email.email],
  536. [e.email for e in user.emails.all()])
  537. # ...a notification is displayed informing him of that
  538. self.assert_in_page_body(
  539. 'Congratulations, the registration is almost over.')
  540. # The existing subscriptions are not removed
  541. self.assertTrue(user.is_subscribed_to(package_name))
  542. def test_user_registered(self):
  543. """
  544. Tests that a user registration fails when there is already a registered
  545. user with the given email.
  546. """
  547. # === Set up a registered user ===
  548. user_email = 'user@domain.com'
  549. associated_email = 'email@domain.com'
  550. self.create_user(user_email, 'asdf', [associated_email])
  551. # The user goes to the registration page
  552. self.get_page(self.get_registration_url())
  553. # The user enters the already existing user's email
  554. self.input_to_element('id_main_email', user_email)
  555. self.send_enter('id_main_email')
  556. # He stays on the same page and receives an error message
  557. self.assert_current_url_equal(self.get_registration_url())
  558. self.assert_in_page_body('email address is already in use')
  559. # The user now tries using the other email associated with the already
  560. # existing user account.
  561. self.clear_element_text('id_main_email')
  562. self.input_to_element('id_main_email', associated_email)
  563. self.send_enter('id_main_email')
  564. # He stays on the same page and receives an error message
  565. self.assert_current_url_equal(self.get_registration_url())
  566. self.assert_in_page_body('email address is already in use')
  567. def test_login(self):
  568. """
  569. Tests that a user can log in when he already has an existing account.
  570. """
  571. # === Set up an account ===
  572. user_email = 'user@domain.com'
  573. associated_emails = ['email@domain.com']
  574. password = 'asdf'
  575. self.create_user(user_email, password, associated_emails)
  576. # The user opens the front page and tries going to the log in page
  577. self.get_page('/')
  578. self.assert_in_page_body('Log in')
  579. self.click_link('Log in')
  580. # The user is now found in the log in page
  581. self.assert_current_url_equal(self.get_login_url())
  582. # There he can see a log in form
  583. self.assert_element_with_id_in_page('form-login')
  584. # He enters the correct email, but an incorrect password
  585. self.input_to_element('id_username', user_email)
  586. self.input_to_element('id_password', 'fdsa')
  587. self.send_enter('id_password')
  588. # He is met with an error message
  589. self.assert_in_page_body('Please enter a correct email and password')
  590. # Now the user correctly enters the password. The email should not
  591. # need to be entered again.
  592. self.input_to_element('id_password', password)
  593. self.send_enter('id_password')
  594. # The user is redirected to his profile page
  595. self.assert_current_url_equal(self.get_profile_url())
  596. def test_login_associated_email(self):
  597. """
  598. Tests that a user can log in with an associated email.
  599. """
  600. # === Set up an account ===
  601. user_email = 'user@domain.com'
  602. associated_emails = ['email@domain.com']
  603. password = 'asdf'
  604. self.create_user(user_email, password, associated_emails)
  605. # The user goes to the log in page
  606. self.get_page(self.get_login_url())
  607. # There he can see a log in form
  608. self.assert_element_with_id_in_page('form-login')
  609. # He enters the associated email and account password
  610. self.input_to_element('id_username', associated_emails[0])
  611. self.input_to_element('id_password', password)
  612. self.send_enter('id_password')
  613. # The user is redirected to his profile page
  614. self.assert_current_url_equal(self.get_profile_url())
  615. def test_logout_from_package_page(self):
  616. """
  617. If a user logs out when on the package page, he should not be
  618. redirected to the index.
  619. """
  620. # === Set up an account ===
  621. user_email = 'user@domain.com'
  622. associated_emails = ['email@domain.com']
  623. password = 'asdf'
  624. self.create_user(user_email, password, associated_emails)
  625. # === Set up an existing package ===
  626. package_name = 'dummy'
  627. SourcePackageName.objects.create(name=package_name)
  628. # The user logs in
  629. self.get_page(self.get_login_url())
  630. self.input_to_element('id_username', associated_emails[0])
  631. self.input_to_element('id_password', password)
  632. self.send_enter('id_password')
  633. # The user goes to the package page
  634. self.get_page('/' + package_name)
  635. # From there he can log out...
  636. self.assert_in_page_body('Log out')
  637. self.click_link('Log out')
  638. # The user is still at the package page, but no longer logged in
  639. self.assert_current_url_equal(self.get_package_url(package_name))
  640. self.assert_not_in_page_body('Log out')
  641. self.assert_in_page_body('Log in')
  642. # The user tries going to his profile page, but he is definitely
  643. # logged out...
  644. self.get_page(self.get_profile_url())
  645. # ...which means he is redirected to the log in page
  646. self.assert_current_url_equal(
  647. self.get_login_url() + '?next=' + self.get_profile_url())
  648. class SubscribeToPackageTest(UserAccountsTestMixin, SeleniumTestCase):
  649. """
  650. Tests for stories regarding subscribing to a package over the Web.
  651. """
  652. def get_subscriptions_url(self):
  653. return reverse('dtracker-accounts-subscriptions')
  654. def test_subscribe_from_package_page(self):
  655. """
  656. Tests that a user that has only one email address can subscribe to a
  657. package directly from the package page.
  658. """
  659. # The user first logs in
  660. self.log_in()
  661. # The user opens a package page
  662. self.get_page('/' + self.package.name)
  663. # There he sees a button allowing him to subscribe to the package
  664. self.assert_element_with_id_in_page('subscribe-button')
  665. # So he clicks it.
  666. button = self.get_element_by_id('subscribe-button')
  667. button.click()
  668. # The subscribe button is no longer found in the page
  669. button = self.get_element_by_id('subscribe-button')
  670. # === Give the page a chance to refresh ===
  671. wait = ui.WebDriverWait(self.browser, 1)
  672. wait.until(lambda browser: not button.is_displayed())
  673. self.assertFalse(button.is_displayed())
  674. # It has been replaced by the unsubscribe button
  675. self.assert_element_with_id_in_page('unsubscribe-button')
  676. unsubscribe_button = self.get_element_by_id('unsubscribe-button')
  677. self.assertTrue(unsubscribe_button.is_displayed())
  678. # === The user has really been subscribed to the package? ===
  679. self.assertTrue(self.user.is_subscribed_to(self.package))
  680. def test_subscribe_not_logged_in(self):
  681. """
  682. Tests that when a user is not logged in, he is redirected to the log in
  683. page instead of being subscribed to the package.
  684. """
  685. # The user opens the package page
  686. self.get_page('/' + self.package.name)
  687. # ...and tries subscribing to the package
  688. self.get_element_by_id('subscribe-not-logged-in-button').click()
  689. # ...only to find himself redirected to the log in page.
  690. self.assert_current_url_equal(self.get_login_url())
  691. def test_subscribe_multiple_associated_emails(self):
  692. """
  693. Tests that a user is offered a choice which email to use to subscribe
  694. to a package when he has multiple associated emails.
  695. """
  696. # === Set up such a user ===
  697. other_email = 'other-email@domain.com'
  698. self.user.emails.create(email=other_email)
  699. # The user logs in
  700. self.log_in()
  701. # The user opens a package page and clicks to subscribe button
  702. self.get_page('/' + self.package.name)
  703. self.get_element_by_id('subscribe-button').click()
  704. self.wait_response(1)
  705. # The user is presented with a choice of his emails
  706. for email in self.user.emails.all():
  707. self.assert_in_page_body(email.email)
  708. # The user decides to cancel the subscription by dismissing the popup
  709. self.get_element_by_id('cancel-choose-email').click()
  710. self.wait_response(1)
  711. # === The user is not subscribed to anything yet ===
  712. self.assertEqual(0, Subscription.objects.count())
  713. # The user clicks the subscribe button again
  714. self.get_element_by_id('subscribe-button').click()
  715. self.wait_response(1)
  716. # ...and chooses to subscribe using his associated email
  717. self.assert_element_with_id_in_page('choose-email-1')
  718. self.get_element_by_id('choose-email-1').click()
  719. self.wait_response(1)
  720. # === User is now subscribed with only the clicked email ===
  721. self.assertEqual(1, Subscription.objects.count())
  722. sub = Subscription.objects.all()[0]
  723. self.assertEqual(other_email, sub.email_settings.user_email.email)
  724. # The UI that the user sees reflects this
  725. self.assert_element_with_id_in_page('unsubscribe-button')
  726. unsubscribe_button = self.get_element_by_id('unsubscribe-button')
  727. self.assertTrue(unsubscribe_button.is_displayed())
  728. def test_unsubscribe_all_emails(self):
  729. """
  730. Tests unsubscribing all user's emails from a package.
  731. """
  732. # === Set up a user with multiple emails subscribed to a package ===
  733. other_email = 'other-email@domain.com'
  734. self.user.emails.create(email=other_email)
  735. for email in self.user.emails.all():
  736. Subscription.objects.create_for(
  737. email=email.email,
  738. package_name=self.package.name)
  739. # The user logs in and opens the package page
  740. self.log_in()
  741. self.get_page('/' + self.package.name)
  742. # There he sees a button allowing him to unsubscribe from the package
  743. self.assert_in_page_body('Unsubscribe')
  744. self.assert_element_with_id_in_page('unsubscribe-button')
  745. # The user decides to unsubscribe and clicks the button...
  746. self.get_element_by_id('unsubscribe-button').click()
  747. self.wait_response(1)
  748. # === The user is really unsubscribed from the package ===
  749. self.assertFalse(self.user.is_subscribed_to(self.package))
  750. # The user sees the subscribe button instead of the unsubscribe button
  751. sub_button = self.get_element_by_id('subscribe-button')
  752. self.assertTrue(sub_button.is_displayed())
  753. unsub_button = self.get_element_by_id('unsubscribe-button')
  754. self.assertFalse(unsub_button.is_displayed())
  755. def test_subscribe_package_from_subscription_tab(self):
  756. """
  757. This test validates that a user can correctly subscribe to a package
  758. from the subscription tab in its personnal space.
  759. """
  760. # Initially the user is not subscribed to the package
  761. self.assertFalse(self.user.is_subscribed_to(self.package))
  762. # The user logs in and goes to his subscriptions page
  763. self.log_in()
  764. self.get_page(self.get_subscriptions_url())
  765. # To ensure the subscription page is fully charged
  766. self.wait_response(1)
  767. self.assert_in_page_body('Subscribe')
  768. # Checking that at least one checkbox is checked
  769. available_mails = self.browser.find_elements_by_xpath(
  770. "//input[@type='checkbox'][@name='email']")
  771. is_there_checked_emails = False
  772. for checkbox in available_mails:
  773. if checkbox.is_selected():
  774. is_there_checked_emails = True
  775. break
  776. self.assertTrue(is_there_checked_emails)
  777. # Filling the package search field
  778. self.assert_element_with_id_in_page('package-search-input')
  779. self.input_to_element('package-search-input', self.package.name)
  780. # Subscribing to the package and ensuring it's completely done!
  781. self.send_enter('package-search-input')
  782. self.wait_response(1)
  783. self.assertTrue(self.user.is_subscribed_to(self.package))
  784. def test_package_subscription_no_email_from_subscription_tab_fails(self):
  785. """
  786. The UI should prevent the user from forgetting to check at least one
  787. email checkbox from the subscription tab in its personnal space.
  788. """
  789. # Initially the user is not subscribed to the package
  790. self.assertFalse(self.user.is_subscribed_to(self.package))
  791. # The user logs in and goes to his subscriptions page
  792. self.log_in()
  793. self.get_page(self.get_subscriptions_url())
  794. # To ensure the subscription page is fully charged
  795. self.wait_response(1)
  796. self.assert_in_page_body('Subscribe')
  797. # All email checkboxes should be unchecked
  798. available_mails = self.browser.find_elements_by_xpath(
  799. "//input[@type='checkbox'][@name='email']")
  800. for checkbox in available_mails:
  801. if checkbox.is_selected():
  802. checkbox.click()
  803. # Filling the package search field
  804. self.assert_element_with_id_in_page('package-search-input')
  805. self.input_to_element('package-search-input', self.package.name)
  806. # Subscribing to the package
  807. self.send_enter('package-search-input')
  808. self.wait_response(1)
  809. # He gets a message informing him that the field is required
  810. self.assert_in_page_body('You need to select at least an email')
  811. # The scubscription should not have been done!
  812. self.assertFalse(self.user.is_subscribed_to(self.package))
  813. def test_package_subscription_no_package_from_subscription_tab_fails(self):
  814. """
  815. The UI should prevent the user from forgetting to check at least one
  816. email checkbox from the subscription tab in its personnal space.
  817. """
  818. # Initially the user is not subscribed to the package
  819. self.assertFalse(self.user.is_subscribed_to(self.package))
  820. # The user logs in and goes to his subscriptions page
  821. self.log_in()
  822. self.get_page(self.get_subscriptions_url())
  823. # To ensure the subscription page is fully charged
  824. self.wait_response(1)
  825. self.assert_in_page_body('Subscribe')
  826. # Checking that at least one checkbox is checked
  827. available_mails = self.browser.find_elements_by_xpath(
  828. "//input[@type='checkbox'][@name='email']")
  829. is_there_checked_emails = False
  830. for checkbox in available_mails:
  831. if checkbox.is_selected():
  832. is_there_checked_emails = True
  833. break
  834. self.assertTrue(is_there_checked_emails)
  835. # Filling the package search field
  836. self.assert_element_with_id_in_page('package-search-input')
  837. self.input_to_element('package-search-input', '')
  838. # Subscribing to the package
  839. self.send_enter('package-search-input')
  840. self.wait_response(1)
  841. # He gets a message informing him that the field is required
  842. self.assert_in_page_body('This field is required')
  843. # The scubscription should not have been done!
  844. self.assertFalse(self.user.is_subscribed_to(self.package))
  845. class ChangeProfileTest(UserAccountsTestMixin, SeleniumTestCase):
  846. def test_modify_personal_info(self):
  847. """
  848. Tests that the user is able to change his personal info upon logging
  849. in.
  850. """
  851. # The user logs in
  852. self.log_in()
  853. # He can see a link for a page letting him modify his personal info
  854. self.assert_in_page_body('Personal Information')
  855. self.click_link('Personal Information')
  856. # In the page there is a form allowing him to change his first/last
  857. # name.
  858. self.assert_element_with_id_in_page('form-change-profile')
  859. # The user decides to input a new name
  860. name = 'Name'
  861. old_last_name = self.user.last_name
  862. self.input_to_element('id_first_name', name)
  863. # ...and submits the form
  864. self.send_enter('id_first_name')
  865. # The user is met with a notification that the information has been
  866. # updated.
  867. self.assert_in_page_body('Successfully changed your information')
  868. # === The user's name has really changed ===
  869. self.refresh_user_object()
  870. self.assertEqual(name, self.user.first_name)
  871. # === But the last name has not ===
  872. self.assertEqual(old_last_name, self.user.last_name)
  873. # The user now wants to update both his first and last name
  874. self.clear_element_text('id_first_name')
  875. new_first_name, new_last_name = 'Name', 'Last Name'
  876. # The user fills in the form
  877. self.input_to_element('id_first_name', new_first_name)
  878. self.input_to_element('id_last_name', new_last_name)
  879. # ...and submits it.
  880. self.send_enter('id_last_name')
  881. # He is faced with another notification of success
  882. self.assert_in_page_body('Successfully changed your information')
  883. # === The information has actually been updated ===
  884. self.refresh_user_object()
  885. self.assertEqual(new_first_name, self.user.first_name)
  886. self.assertEqual(new_last_name, self.user.last_name)
  887. # The user navigates away from the page now
  888. self.get_page(self.get_profile_url())
  889. # And then goes back
  890. self.click_link('Personal Information')
  891. # There are no notifications about modification in the page nw
  892. self.assert_not_in_page_body('Successfully changed your information')
  893. # And the user's first/last name is already filled in the form
  894. self.assertEqual(
  895. new_first_name,
  896. self.get_element_by_id('id_first_name').get_attribute('value'))
  897. self.assertEqual(
  898. new_last_name,
  899. self.get_element_by_id('id_last_name').get_attribute('value'))
  900. def test_change_password(self):
  901. """
  902. Tests that the user can change his password upon logging in.
  903. """
  904. # The user logs in
  905. self.log_in()
  906. # He can see a link for a page letting him change his password
  907. self.assert_in_page_body('Change Password')
  908. self.click_link('Change Password')
  909. # He can see the form which is used to enter the new password
  910. self.assert_element_with_id_in_page('form-change-password')
  911. # The user first enters a wrong current password
  912. new_password = 'new-password'
  913. self.input_to_element('id_old_password', 'this-password-is-incorrect')
  914. self.input_to_element('id_new_password1', new_password)
  915. self.input_to_element('id_new_password2', new_password)
  916. self.send_enter('id_new_password2')
  917. # The user is met with an error saying his current password was
  918. # incorrect.
  919. self.assert_in_page_body('Your old password was entered incorrectly')
  920. # The user enters his current password correctly, but forgets the new
  921. # password.
  922. self.input_to_element('id_old_password', self.password)
  923. self.send_enter('id_old_password')
  924. # He gets a message informing him that the field is required
  925. self.assert_in_page_body('This field is required')
  926. # This time, the user enters both the old password and fills in the new
  927. # password fields, but they are mismatched
  928. self.input_to_element('id_old_password', self.password)
  929. self.input_to_element('id_new_password1', new_password)
  930. self.input_to_element('id_new_password2', new_password + '-miss-match')
  931. self.send_enter('id_new_password2')
  932. # He gets a message informing him that the password change failed once
  933. # again.
  934. self.assert_in_page_body("The two password fields didn't match")
  935. # In the end, the user manages to fill in the form correctly!
  936. self.input_to_element('id_old_password', self.password)
  937. self.input_to_element('id_new_password1', new_password)
  938. self.input_to_element('id_new_password2', new_password)
  939. self.send_enter('id_new_password2')
  940. # He gets a message informing him of a successful change
  941. self.assert_in_page_body('Successfully updated your password')
  942. # The user logs out in order to try using his new password
  943. self.click_link('Log out')
  944. # The user tries logging in using his old account password
  945. self.log_in()
  946. # He is met with an error
  947. self.assert_in_page_body('Please enter a correct email and password')
  948. # Now he tries with his new password
  949. self.password = new_password
  950. self.log_in()
  951. # The user is finally logged in using his new account details
  952. self.assert_current_url_equal(self.get_profile_url())
  953. def test_reset_password(self):
  954. """
  955. Tests that a user is able to reset his password if he forgot it.
  956. """
  957. # The user goes to the login page
  958. self.get_page(self.get_login_url())
  959. # There he sees a convenient link letting him get to a page where he
  960. # can reset his password.
  961. self.assert_in_page_body('Forgot your password?')
  962. self.click_link('Forgot your password?')
  963. # The user sees a form which lets him reset his password
  964. self.assert_element_with_id_in_page('form-reset-password')
  965. # First he enters an invalid email: one not associated with any account
  966. self.input_to_element('id_email', 'this-does-not-exist@domain.com')
  967. self.send_enter('id_email')
  968. # The user still stays in the same page with a warning that no user
  969. # with the email exists.
  970. self.assert_in_page_body('No user with the given email is registered')
  971. # The user now correctly enters his own email
  972. self.clear_element_text('id_email')
  973. self.input_to_element('id_email', self.user.main_email)
  974. self.send_enter('id_email')
  975. # The user is taken to another page where he is informed that he must
  976. # check his email for a confirmation email
  977. self.assert_in_page_body('Please check your email inbox for details')
  978. # === A confirmation email is actually sent? ===
  979. self.assertEqual(1, len(mail.outbox))
  980. confirmation = ResetPasswordConfirmation.objects.all()[0]
  981. # The user goes to the confirmation URL!
  982. self.get_page(reverse('dtracker-accounts-reset-password', kwargs={
  983. 'confirmation_key': confirmation.confirmation_key,
  984. }))
  985. # There, he is asked to enter a new password...
  986. self.assert_in_page_body('please enter a new password for your account')
  987. # ...so he does
  988. new_password = self.password + '-new'
  989. self.input_to_element('id_password1', new_password)
  990. self.input_to_element('id_password2', new_password)
  991. self.send_enter('id_password2')
  992. # He is redirected back to the profile page with a message that his
  993. # password has been reset.
  994. self.assert_current_url_equal(self.get_profile_url())
  995. self.assert_in_page_body('You have successfully reset your password')
  996. # The user decides to log out and try logging back in with his new
  997. # password
  998. self.click_link('Log out')
  999. self.password = new_password
  1000. self.log_in()
  1001. # The user has successfully logged in using his new password
  1002. self.assert_current_url_equal(self.get_profile_url())
  1003. def test_manage_account_emails(self):
  1004. """
  1005. Tests that users are able to manage which emails are associated with
  1006. their accounts.
  1007. """
  1008. # The user logs in and goes to the account email management page
  1009. self.log_in()
  1010. self.click_link('Account Emails')
  1011. # There he sees a form letting him add new emails to his account
  1012. self.assert_element_with_id_in_page('form-add-account-email')
  1013. # He decides to add a new email.
  1014. new_email = 'completely-new-email@domain.com'
  1015. self.input_to_element('id_email', new_email)
  1016. self.send_enter('id_email')
  1017. # The user is notified that in order to activate the email association
  1018. # he must confirm the ownership of the email address.
  1019. self.assert_in_page_body('you must follow the confirmation link')
  1020. self.assert_not_in_page_body(new_email)
  1021. # === The confirmation email sent? ===
  1022. self.assertEqual(1, len(mail.outbox))
  1023. self.assertIn(new_email, mail.outbox[0].to)
  1024. # === Confirmation created? ===
  1025. self.assertEqual(1, AddEmailConfirmation.objects.count())
  1026. confirmation = AddEmailConfirmation.objects.all()[0]
  1027. # The user now visits he confirmation URL
  1028. self.get_page(reverse('dtracker-accounts-confirm-add-email', kwargs={
  1029. 'confirmation_key': confirmation.confirmation_key,
  1030. }))
  1031. # And is notified that the address is associated with an account
  1032. self.assert_in_page_body('now associated with your account')
  1033. # The user goes back to his profile page to check if the email can be
  1034. # found there
  1035. self.click_link('Profile')
  1036. self.click_link('Account Emails')
  1037. # The new email is now in the list of all emails
  1038. self.assert_in_page_body(new_email)
  1039. # The user tries adding an email he is already associated with again
  1040. self.input_to_element('id_email', new_email)
  1041. self.send_enter('id_email')
  1042. # He gets a warning telling him his account is already associated with
  1043. # the given email.
  1044. self.assert_in_page_body(
  1045. 'This email is already associated with your account')
  1046. def test_merge_accounts(self):
  1047. # === Set up an additional existing user account ===
  1048. password = 'other-password'
  1049. other_email = 'other@domain.com'
  1050. other_user = self.create_user(other_email, password)
  1051. # A user logs in and goes to the form to add an additional account
  1052. self.log_in()
  1053. self.click_link('Account Emails')
  1054. # He inputs the email of a user that is already registered
  1055. self.input_to_element('id_email', other_email)
  1056. self.send_enter('id_email')
  1057. # The user is taken to a confirmation page
  1058. self.assert_in_page_body('Are you sure you want to merge the accounts')
  1059. # There is a button letting him confirm the merge
  1060. self.assert_element_with_id_in_page('confirm-merge-button')
  1061. self.get_element_by_id('confirm-merge-button').click()
  1062. # The user is notified that the merge is ineffective until confirmed
  1063. self.assert_in_page_body('you must follow a confirmation link')
  1064. # A confirmation mail is sent
  1065. self.assertEqual(1, len(mail.outbox))
  1066. # The mail was not sent to the logged in user, rather the one being
  1067. # merged to the account
  1068. self.assertIn(other_email, mail.outbox[0].to)
  1069. # === A confirmation instance is created? ===
  1070. self.assertEqual(1, MergeAccountConfirmation.objects.count())
  1071. confirmation = MergeAccountConfirmation.objects.all()[0]
  1072. # The user tries going to the confirmation URL without logging in to
  1073. # the other account
  1074. confirmation_url = reverse('dtracker-accounts-merge-finalize', kwargs={
  1075. 'confirmation_key': confirmation.confirmation_key,
  1076. })
  1077. self.get_page(confirmation_url)
  1078. # ...which is forbidden and has no effect
  1079. self.assertEqual(2, User.objects.count())
  1080. # The user now logs in with the other account
  1081. self.log_in(other_user, password)
  1082. self.get_page(confirmation_url)
  1083. # He accesses the page which asks for a final confirmation
  1084. self.assert_in_page_body(
  1085. 'Are you sure you want to finalize the accounts merge')
  1086. self.assert_element_with_id_in_page('finalize-merge-button')
  1087. # The user decides to go on with the merge
  1088. self.get_element_by_id('finalize-merge-button').click()
  1089. # The user is notified that the merge was successful
  1090. self.assert_in_page_body(
  1091. 'The two accounts have been successfully merged')
  1092. # === User accounts are really changed? ===
  1093. self.assertEqual(1, User.objects.count())
  1094. # He tries logging in with the merged account's password
  1095. self.log_in(password=password)
  1096. # ...which fails
  1097. self.assert_in_page_body('Please enter a correct email and password')
  1098. # He logs in with the original account details
  1099. self.log_in()
  1100. # ...which works
  1101. self.assert_current_url_equal(self.get_profile_url())
  1102. # He goes to check that his associated emails contain all the emails
  1103. self.click_link('Account Emails')
  1104. self.assert_in_page_body(self.user.main_email)
  1105. self.assert_in_page_body(other_email)
  1106. class TeamTests(SeleniumTestCase):
  1107. def setUp(self):
  1108. super(TeamTests, self).setUp()
  1109. self.password = 'asdf'
  1110. self.user = User.objects.create_user(
  1111. main_email='user@domain.com', password=self.password,
  1112. first_name='', last_name='')
  1113. def get_login_url(self):
  1114. return reverse('dtracker-accounts-login')
  1115. def get_create_team_url(self):
  1116. return reverse('dtracker-teams-create')
  1117. def get_team_url(self, team_name):
  1118. team = Team.objects.get(name=team_name)
  1119. return team.get_absolute_url()
  1120. def get_delete_team_url(self, team_name):
  1121. team = Team.objects.get(name=team_name)
  1122. return reverse('dtracker-team-delete', kwargs={
  1123. 'slug': team.slug,
  1124. })
  1125. def get_team_deleted_url(self):
  1126. return reverse('dtracker-team-deleted')
  1127. def get_update_team_url(self, team_name):
  1128. team = Team.objects.get(name=team_name)
  1129. return reverse('dtracker-team-update', kwargs={
  1130. 'slug': team.slug,
  1131. })
  1132. def get_subscriptions_url(self):
  1133. return reverse('dtracker-accounts-subscriptions')
  1134. def assert_team_packages_equal(self, team, package_names):
  1135. team_package_names = [p.name for p in team.packages.all()]
  1136. self.assertEqual(len(package_names), len(team_package_names))
  1137. for package_name in package_names:
  1138. self.assertIn(package_name, team_package_names)
  1139. def log_in(self, user=None, password=None):
  1140. """
  1141. Helper method which logs the user in, without taking any shortcuts (it
  1142. goes through the steps to fill in the form and submit it).
  1143. """
  1144. if user is None:
  1145. user = self.user
  1146. if password is None:
  1147. password = self.password
  1148. self.get_page(self.get_login_url())
  1149. self.input_to_element('id_username', user.main_email)
  1150. self.input_to_element('id_password', password)
  1151. self.send_enter('id_password')
  1152. def log_out(self):
  1153. """
  1154. Logs the currently logged in user out.
  1155. """
  1156. self.browser.find_element_by_id("account-logout").click()
  1157. def test_create_team(self):
  1158. """
  1159. Tests that a logged in user can create a new team.
  1160. """
  1161. # The user tries going to the page to create a new team
  1162. self.get_page(self.get_create_team_url())
  1163. # However, he is not signed in so he is redirected to the login
  1164. self.assertIn(self.get_login_url(), self.browser.current_url)
  1165. self.wait_response(1)
  1166. # The user then logs in
  1167. self.log_in()
  1168. # ...and tries again
  1169. self.get_page(self.get_create_team_url())
  1170. # This time he is presented with a page that has a form allowing him to
  1171. # create a new team.
  1172. self.assert_element_with_id_in_page('create-team-form')
  1173. # The user forgets to input the team name, initialy.
  1174. self.send_enter('id_name')
  1175. # Since a name is required, an error is returned
  1176. self.assert_in_page_body('This field is required')
  1177. # The user inputs the team name, but not a maintainer email
  1178. team_name = 'New team'
  1179. self.input_to_element('id_name', team_name)
  1180. self.send_enter('id_name')
  1181. self.wait_response(1)
  1182. # The user is now redirected to the team's page
  1183. self.assert_current_url_equal(self.get_team_url(team_name))
  1184. # === The team actually exists ===
  1185. self.assertEqual(1, Team.objects.filter(name=team_name).count())
  1186. # === The user is its owner ===
  1187. team = Team.objects.get(name=team_name)
  1188. self.assertEqual(self.user, team.owner)
  1189. # The user goes back to the team creation page now
  1190. self.get_page(self.get_create_team_url())
  1191. # He tries creating a new team with the same name
  1192. self.input_to_element('id_name', team_name)
  1193. self.send_enter('id_name')
  1194. # This time, the team creation process fails because the team name is
  1195. # not unique.
  1196. self.assert_in_page_body('Team with this Name already exists')
  1197. def test_create_team_maintainer_email(self):
  1198. """
  1199. Tests creating a team with a maintainer email set.
  1200. The team should become automatically associated with the maintainer's
  1201. packages.
  1202. """
  1203. # === Set up some packages maintained by the same maintainer ===
  1204. package_names = [
  1205. 'pkg1',
  1206. 'pkg2',
  1207. ]
  1208. maintainer_email = 'maintainer@domain.com'
  1209. maintainer = ContributorName.objects.create(
  1210. contributor_email=UserEmail.objects.create(
  1211. email=maintainer_email))
  1212. for package_name in package_names:
  1213. SourcePackage.objects.create(
  1214. source_package_name=SourcePackageName.objects.create(
  1215. name=package_name),
  1216. version='1.0.0',
  1217. maintainer=maintainer)
  1218. # === Create a package with no maintainer ===
  1219. SourcePackageName.objects.create(name='dummy-package')
  1220. # === -- ===
  1221. # The user logs in and accesses the create team page.
  1222. self.log_in()
  1223. self.get_page(self.get_create_team_url())
  1224. # He inputs both the team name and the maintaner name.
  1225. team_name = 'Team name'
  1226. self.input_to_element('id_name', team_name)
  1227. self.input_to_element('id_maintainer_email', maintainer_email)
  1228. self.send_enter('id_maintainer_email')
  1229. self.wait_response(1)
  1230. # The team is successfully created and the user can see the
  1231. # maintainer's packages already in the team's page
  1232. for package_name in package_names:
  1233. self.assert_in_page_body(package_name)
  1234. # === The team really is associated with the packages? ===
  1235. team = Team.objects.all()[0]
  1236. self.assert_team_packages_equal(team, package_names)
  1237. # The user now wants to associate the team with a different maintainer
  1238. # that maintains other packages
  1239. new_package_name = 'pkg3'
  1240. new_maintainer_email = 'new-maintainer@domain.com'
  1241. new_maintainer = ContributorName.objects.create(
  1242. contributor_email=UserEmail.objects.create(
  1243. email=new_maintainer_email))
  1244. SourcePackage.objects.create(
  1245. source_package_name=SourcePackageName.objects.create(
  1246. name=new_package_name),
  1247. version='1.0.0',
  1248. maintainer=new_maintainer)
  1249. self.get_element_by_id('update-team-button').click()
  1250. # The user modifies the maintainer field
  1251. self.clear_element_text('id_maintainer_email')
  1252. self.input_to_element('id_maintainer_email', new_maintainer_email)
  1253. self.send_enter('id_maintainer_email')
  1254. self.wait_response(1)
  1255. # The user is directed back to the team page where he can see all the
  1256. # packages previously associated with the team, as well as the ones
  1257. # associated to the new maintainer.
  1258. self.assert_in_page_body(new_package_name)
  1259. for package_name in package_names:
  1260. self.assert_in_page_body(package_name)
  1261. # === The team is really associated with all these packages? ===
  1262. self.assert_team_packages_equal(
  1263. team, package_names + [new_package_name])
  1264. def test_delete_team(self):
  1265. """
  1266. Tests that the owner can delete a team.
  1267. """
  1268. # === Set up a team owned by the user ===
  1269. team_name = 'Team name'
  1270. Team.objects.create_with_slug(owner=self.user, name=team_name)
  1271. # Before logging in the user opens the team page
  1272. self.get_page(self.get_team_url(team_name))
  1273. # He does not see the delete button
  1274. self.assert_not_in_page_body("Delete")
  1275. # He goes directly to the deletion URL
  1276. self.get_page(self.get_delete_team_url(team_name))
  1277. # But permission is denied to the user
  1278. self.assert_not_in_page_body(team_name)
  1279. # He now logs in
  1280. self.log_in()
  1281. self.get_page(self.get_team_url(team_name))
  1282. # The delete button is now offered to the user
  1283. self.assert_element_with_id_in_page('delete-team-button')
  1284. # So the user decides to click it.
  1285. self.get_element_by_id('delete-team-button').click()
  1286. self.wait_response(1)
  1287. # He is now faced with a popup asking him to confirm the team deletion
  1288. cancel_button = self.get_element_by_id('team-delete-cancel-button')
  1289. confirm_button = self.get_element_by_id('confirm-team-delete-button')
  1290. self.assertTrue(confirm_button.is_displayed())
  1291. self.assertTrue(cancel_button.is_displayed())
  1292. # The user decides to cancel the deletion
  1293. cancel_button.click()
  1294. self.wait_response(1)
  1295. # He is still found in the team page and the team has not been deleted
  1296. self.assert_current_url_equal(self.get_team_url(team_name))
  1297. # === The team is still here ===
  1298. self.assertEqual(1, Team.objects.count())
  1299. # The user now decides he really wants to delete the team
  1300. self.get_element_by_id('delete-team-button').click()
  1301. self.wait_response(1)
  1302. self.get_element_by_id('confirm-team-delete-button').click()
  1303. self.wait_response(1)
  1304. # He is now taken to a page informing him the team has been
  1305. # successfully deleted.
  1306. self.assert_current_url_equal(self.get_team_deleted_url())
  1307. # === The team is also really deleted? ===
  1308. self.assertEqual(0, Team.objects.count())
  1309. def test_update_team(self):
  1310. """
  1311. Tests that the team owner can update the team's basic info.
  1312. """
  1313. # === Set up a team owned by the user ===
  1314. team_name = 'Team name'
  1315. Team.objects.create_with_slug(owner=self.user, name=team_name)
  1316. # Before logging in the user opens the team page
  1317. self.get_page(self.get_team_url(team_name))
  1318. # He does not see the update button
  1319. self.assert_not_in_page_body("Update")
  1320. # He goes directly to the update URL
  1321. self.get_page(self.get_update_team_url(team_name))
  1322. # But permission is denied to the user
  1323. self.assert_not_in_page_body(team_name)
  1324. # The user now logs in
  1325. self.log_in()
  1326. self.get_page(self.get_team_url(team_name))
  1327. # He can now see the update button
  1328. self.assert_element_with_id_in_page('update-team-button')
  1329. self.get_element_by_id('update-team-button').click()
  1330. # He is found on the update page now
  1331. self.assert_current_url_equal(self.get_update_team_url(team_name))
  1332. # ...with a form that lets him update the team's info
  1333. self.assert_element_with_id_in_page('update-team-form')
  1334. # The user modifies the team's description
  1335. new_description = "This is a new description"
  1336. self.input_to_element('id_description', new_description)
  1337. self.send_enter('id_name')
  1338. self.wait_response(1)
  1339. # The user is taken back to the team's page
  1340. self.assert_current_url_equal(self.get_team_url(team_name))
  1341. # The updated information is displayed in the page already
  1342. self.assert_in_page_body(new_description)
  1343. # === The team's info is actually updated? ===
  1344. team = Team.objects.all()[0]
  1345. self.assertEqual(new_description, team.description)
  1346. # The user now wants to update the team's name without affecting the
  1347. # team's URL.
  1348. old_url = self.get_team_url(team_name)
  1349. self.get_element_by_id('update-team-button').click()
  1350. self.clear_element_text('id_name')
  1351. new_name = team_name + ' new name'
  1352. self.input_to_element('id_name', new_name)
  1353. self.send_enter('id_name')
  1354. self.wait_response(1)
  1355. # The user is now found back at the team page which contains the
  1356. # updated name
  1357. self.assert_in_page_body(new_name)
  1358. # However, the package's URL is still the same
  1359. self.assert_current_url_equal(old_url)
  1360. # Now the user wants to modify the team's url without modifying its
  1361. # name.
  1362. self.get_element_by_id('update-team-button').click()
  1363. old_slug = team.slug
  1364. self.clear_element_text('id_slug')
  1365. new_slug = old_slug + '-new-slug'
  1366. self.input_to_element('id_slug', new_slug)
  1367. self.send_enter('id_slug')
  1368. self.wait_response(1)
  1369. # The user is once again back on the team page.
  1370. # The URL has been modified now to contain the new team slug.
  1371. self.assertIn(new_slug, self.browser.current_url)
  1372. # === The slug really is updated? ===
  1373. self.assertEqual(new_slug, Team.objects.all()[0].slug)
  1374. def test_package_management(self):
  1375. """
  1376. Tests that adding/removing packages from the team works as expected.
  1377. """
  1378. # === Set up a team owned by the user ===
  1379. team_name = 'Team name'
  1380. team = Team.objects.create_with_slug(owner=self.user, name=team_name)
  1381. # === Set up some packages which the user can add to the team ===
  1382. package_names = [
  1383. 'pkg1',
  1384. 'pkg2',
  1385. ]
  1386. for package_name in package_names:
  1387. PackageName.objects.create(name=package_name)
  1388. # === -- ===
  1389. # The user opens the team page without logging in
  1390. self.get_page(self.get_team_url(team_name))
  1391. # He cannot see the form to add a package
  1392. form = self.get_element_by_id('add-team-package-form')
  1393. self.assertIsNone(form)
  1394. # The user logs in and opens the team page
  1395. self.log_in()
  1396. self.get_page(self.get_team_url(team_name))
  1397. # He can see the form to add packages now
  1398. self.assert_element_with_id_in_page('add-team-package-form')
  1399. # He types the name of the package he wants to add
  1400. self.input_to_element('id_package_name', package_names[0])
  1401. # ...and submits the form
  1402. self.send_enter('id_package_name')
  1403. self.wait_response(1)
  1404. # The user is still in the team page
  1405. self.assert_current_url_equal(self.get_team_url(team_name))
  1406. # He can now see the package he added in the list of packages
  1407. self.assert_in_page_body(package_names[0])
  1408. # He tries adding a new package: one that does not exist
  1409. self.input_to_element('id_package_name', 'this-does-not-exist')
  1410. self.send_enter('id_package_name')
  1411. self.wait_response(1)
  1412. # The user is still in the team page, but nothing is changed when it
  1413. # comes to the list of packages.
  1414. self.assert_not_in_page_body('this-does-not-exist')
  1415. # The user now wants to remove the package from the team
  1416. # He can see a button next to the team's name
  1417. remove_button = self.browser.find_element_by_css_selector(
  1418. '.remove-package-from-team-button')
  1419. remove_button.click()
  1420. self.wait_response(1)
  1421. # A popup is displayed asking the user to confirm the removal
  1422. # The user decides to cancel the operation
  1423. self.get_element_by_id('remove-package-cancel-button').click()
  1424. self.wait_response(1)
  1425. # He is still found on the team page and the package is not removed
  1426. self.assert_current_url_equal(self.get_team_url(team_name))
  1427. # === The package is not removed? ===
  1428. self.assertEqual(1, team.packages.count())
  1429. # The user decides to definitely remove the package now
  1430. remove_button.click()
  1431. self.wait_response(1)
  1432. self.get_element_by_id('confirm-remove-package-button').click()
  1433. self.wait_response(1)
  1434. # The user is still on the team page, but the package is not longer
  1435. # a part of the team.
  1436. self.assert_current_url_equal(self.get_team_url(team_name))
  1437. self.assert_not_in_page_body(package_names[0])
  1438. # === The package is really removed from the team ===
  1439. self.assertEqual(0, team.packages.count())
  1440. def test_team_access(self):
  1441. """
  1442. Tests joining and leaving a team.
  1443. """
  1444. # === Set up a team and a user who isn't the owner of the team ===
  1445. team_name = 'Team name'
  1446. team = Team.objects.create_with_slug(owner=self.user, name=team_name)
  1447. user = User.objects.create_user(
  1448. main_email='other@domain.com',
  1449. password=self.password)
  1450. UserEmail.objects.get_or_create(email=user.main_email)
  1451. # === end setup ===
  1452. # The user logs in and goes to the team page
  1453. self.log_in(user)
  1454. self.get_page(self.get_team_url(team_name))
  1455. # He can see a button allowing him to join the team.
  1456. self.assert_element_with_id_in_page('join-team-button')
  1457. # ...so he clicks it!
  1458. self.get_element_by_id('join-team-button').click()
  1459. self.wait_response(1)
  1460. # The user is still found in the team page, but now he is a member of
  1461. # the team.
  1462. self.assert_element_with_id_in_page('add-team-package-form')
  1463. # === The user is really a member? ===
  1464. self.assertTrue(team.user_is_member(user))
  1465. # Which means he can leave the team now.
  1466. self.assert_element_with_id_in_page('leave-team-button')
  1467. # So he does that.
  1468. self.get_element_by_id('leave-team-button').click()
  1469. self.wait_response(1)
  1470. # The user is now again not a member of the team
  1471. self.assert_element_with_id_in_page('join-team-button')
  1472. # === He really isn't a member any more. ===
  1473. self.assertFalse(team.user_is_member(user))
  1474. # The user now logs out
  1475. self.log_out()
  1476. # And tries clicking the join team button
  1477. self.get_element_by_id('join-team-button').click()
  1478. self.wait_response(1)
  1479. # But he is redirected to the login page
  1480. self.assert_element_with_id_in_page('form-login')
  1481. # === The privacy of the team is switched to private. ===
  1482. team.public = False
  1483. team.save()
  1484. # When the user opens the page again, the join button is replaced with
  1485. # a link to contact the owner.
  1486. self.get_page(self.get_team_url(team_name))
  1487. self.assert_in_page_body('Contact the owner')
  1488. def test_owner_members_management(self):
  1489. """
  1490. Tests that a team owner is able to add/remove members from a separate
  1491. panel.
  1492. """
  1493. team_name = 'Team name'
  1494. Team.objects.create_with_slug(owner=self.user, name=team_name)
  1495. self.log_in()
  1496. self.get_page(self.get_team_url(team_name))
  1497. # The user opens the member management page
  1498. self.get_element_by_id('manage-team-button').click()
  1499. # The user wants to add a new team member
  1500. new_team_member = 'member@domain.com'
  1501. self.input_to_element('id_email', new_team_member)
  1502. self.send_enter('id_email')
  1503. self.wait_response(1)
  1504. # The user is still in the same page, but can see the new member in the
  1505. # list of all members
  1506. self.assert_in_page_body(new_team_member)
  1507. # === The membership is marked muted, though ===
  1508. membership = TeamMembership.objects.all()[0]
  1509. self.assertTrue(membership.muted)
  1510. # === Email was sent to the new member asking him to confirm it ===
  1511. self.assertEqual(1, len(mail.outbox))
  1512. self.assertIn(new_team_member, mail.outbox[0].to)
  1513. # The user now decides to remove the team member
  1514. button = \
  1515. self.browser.find_element_by_css_selector('.remove-user-button')
  1516. button.click()
  1517. self.wait_response(1)
  1518. # The user is no longer a part of the team
  1519. self.assert_not_in_page_body(new_team_member)
  1520. def test_toggle_team_mute(self):
  1521. """
  1522. Tests that a team member is able to mute and unmute a team membership
  1523. from the subscription details page.
  1524. """
  1525. # === -- ===
  1526. team_name = 'Team name'
  1527. team = Team.objects.create_with_slug(owner=self.user, name=team_name)
  1528. membership = team.add_members([self.user.emails.all()[0]])[0]
  1529. # === -- ===
  1530. # The user logs in and goes to his subscriptions page
  1531. self.log_in()
  1532. self.get_page(self.get_subscriptions_url())
  1533. # He can see a button offering him to mute the team membership
  1534. self.assert_in_page_body('Mute')
  1535. # So he clicks it!
  1536. btn = self.get_element_by_class('toggle-team-mute')
  1537. btn.click()
  1538. self.wait_response(1)
  1539. # The user is still in he same page, but now he has a warning that his
  1540. # team membership is muted
  1541. self.assert_element_with_class_in_page('mute-warning')
  1542. # === The membership is actually muted? ===
  1543. membership = TeamMembership.objects.get(pk=membership.pk)
  1544. self.assertTrue(membership.muted)
  1545. # The user now wants to revert this.
  1546. # He can see the unmute button
  1547. self.assert_in_page_body('Unmute')
  1548. # He clicks it.
  1549. btn = self.get_element_by_class('toggle-team-mute')
  1550. btn.click()
  1551. self.wait_response(1)
  1552. # Once again, the user is still in the subscriptions page, but the
  1553. # button has reverted back to the mute button
  1554. self.assert_in_page_body('Mute')
  1555. # And the warning is gone
  1556. self.assertIsNone(self.get_element_by_class('mute-warning'))