PageRenderTime 59ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/kitsune/users/tests/test_templates.py

https://github.com/dbbhattacharya/kitsune
Python | 561 lines | 540 code | 16 blank | 5 comment | 1 complexity | a9ed8db02b34addf23355b11ffbd0d81 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, GPL-2.0, BSD-3-Clause
  1. from copy import copy
  2. import os
  3. from smtplib import SMTPRecipientsRefused
  4. from django.conf import settings
  5. from django.contrib.auth.models import User
  6. from django.contrib.auth.tokens import default_token_generator
  7. from django.contrib.sites.models import Site
  8. from django.core import mail
  9. from django.core.files import File
  10. from django.utils.http import int_to_base36
  11. import mock
  12. from nose.tools import eq_
  13. from pyquery import PyQuery as pq
  14. from kitsune.flagit.models import FlaggedObject
  15. from kitsune.kbadge.tests import award, badge
  16. from kitsune.sumo.urlresolvers import reverse
  17. from kitsune.sumo.helpers import urlparams
  18. from kitsune.sumo.tests import post, get
  19. from kitsune.users import ERROR_SEND_EMAIL
  20. from kitsune.users.forms import PasswordResetForm
  21. from kitsune.users.models import (
  22. Profile, RegistrationProfile, RegistrationManager)
  23. from kitsune.users.tests import (
  24. TestCaseBase, user, add_permission, profile, group)
  25. from kitsune.wiki.tests import revision
  26. class LoginTests(TestCaseBase):
  27. """Login tests."""
  28. def setUp(self):
  29. super(LoginTests, self).setUp()
  30. self.orig_debug = settings.DEBUG
  31. settings.DEBUG = True
  32. self.u = user(save=True)
  33. def tearDown(self):
  34. super(LoginTests, self).tearDown()
  35. settings.DEBUG = self.orig_debug
  36. def test_login_bad_password(self):
  37. '''Test login with a good username and bad password.'''
  38. response = post(self.client, 'users.login',
  39. {'username': self.u.username, 'password': 'foobar'})
  40. eq_(200, response.status_code)
  41. doc = pq(response.content)
  42. eq_('Please enter a correct username and password. Note that both '
  43. 'fields are case-sensitive.', doc('ul.errorlist li').text())
  44. def test_login_bad_username(self):
  45. '''Test login with a bad username.'''
  46. response = post(self.client, 'users.login',
  47. {'username': 'foobarbizbin', 'password': 'testpass'})
  48. eq_(200, response.status_code)
  49. doc = pq(response.content)
  50. eq_('Please enter a correct username and password. Note that both '
  51. 'fields are case-sensitive.', doc('ul.errorlist li').text())
  52. def test_login_password_disabled(self):
  53. """Test logging in as a user with PASSWORD_DISABLED doesn't 500."""
  54. self.u.set_unusable_password()
  55. self.u.save()
  56. response = self.client.post(reverse('users.login'),
  57. {'username': self.u.username,
  58. 'password': 'testpass'})
  59. eq_(200, response.status_code)
  60. def test_login(self):
  61. '''Test a valid login.'''
  62. response = self.client.post(reverse('users.login'),
  63. {'username': self.u.username,
  64. 'password': 'testpass'})
  65. eq_(302, response.status_code)
  66. eq_('http://testserver' +
  67. reverse('home', locale=settings.LANGUAGE_CODE) + '?fpa=1',
  68. response['location'])
  69. def test_login_next_parameter(self):
  70. '''Test with a valid ?next=url parameter.'''
  71. next = '/kb/new'
  72. # Verify that next parameter is set in form hidden field.
  73. response = self.client.get(
  74. urlparams(reverse('users.login'), next=next), follow=True)
  75. eq_(200, response.status_code)
  76. doc = pq(response.content)
  77. eq_(next, doc('#login input[name="next"]')[0].attrib['value'])
  78. # Verify that it gets used on form POST.
  79. response = self.client.post(reverse('users.login'),
  80. {'username': self.u.username,
  81. 'password': 'testpass',
  82. 'next': next})
  83. eq_(302, response.status_code)
  84. eq_('http://testserver' + next + '?fpa=1', response['location'])
  85. @mock.patch.object(Site.objects, 'get_current')
  86. def test_login_invalid_next_parameter(self, get_current):
  87. '''Test with an invalid ?next=http://example.com parameter.'''
  88. get_current.return_value.domain = 'testserver.com'
  89. invalid_next = 'http://foobar.com/evil/'
  90. valid_next = reverse('home', locale=settings.LANGUAGE_CODE)
  91. # Verify that _valid_ next parameter is set in form hidden field.
  92. url = urlparams(reverse('users.login'), next=invalid_next)
  93. response = self.client.get(url, follow=True)
  94. eq_(200, response.status_code)
  95. doc = pq(response.content)
  96. eq_(valid_next, doc('#login input[name="next"]')[0].attrib['value'])
  97. # Verify that it gets used on form POST.
  98. response = self.client.post(reverse('users.login'),
  99. {'username': self.u.username,
  100. 'password': 'testpass',
  101. 'next': invalid_next})
  102. eq_(302, response.status_code)
  103. eq_('http://testserver' + valid_next + '?fpa=1', response['location'])
  104. def test_ga_custom_variable_on_registered_login(self):
  105. """After logging in, there should be a ga-push data attr on body."""
  106. user_ = profile().user
  107. # User should be "Registered":
  108. response = self.client.post(reverse('users.login'),
  109. {'username': user_.username,
  110. 'password': 'testpass'},
  111. follow=True)
  112. eq_(200, response.status_code)
  113. doc = pq(response.content)
  114. assert '"Registered"' in doc('body').attr('data-ga-push')
  115. def test_ga_custom_variable_on_contributor_login(self):
  116. """After logging in, there should be a ga-push data attr on body."""
  117. user_ = profile().user
  118. # Add user to Contributors and so should be "Contributor":
  119. user_.groups.add(group(name='Contributors', save=True))
  120. response = self.client.post(reverse('users.login'),
  121. {'username': user_.username,
  122. 'password': 'testpass'},
  123. follow=True)
  124. eq_(200, response.status_code)
  125. doc = pq(response.content)
  126. assert '"Contributor"' in doc('body').attr('data-ga-push')
  127. def test_ga_custom_variable_on_admin_login(self):
  128. """After logging in, there should be a ga-push data attr on body."""
  129. user_ = profile().user
  130. # Add user to Administrators and so should be "Contributor - Admin":
  131. user_.groups.add(group(name='Administrators', save=True))
  132. response = self.client.post(reverse('users.login'),
  133. {'username': user_.username,
  134. 'password': 'testpass'},
  135. follow=True)
  136. eq_(200, response.status_code)
  137. doc = pq(response.content)
  138. assert '"Contributor - Admin"' in doc('body').attr('data-ga-push')
  139. class PasswordResetTests(TestCaseBase):
  140. def setUp(self):
  141. super(PasswordResetTests, self).setUp()
  142. self.u = user(email="valid@email.com", save=True)
  143. self.uidb36 = int_to_base36(self.u.id)
  144. self.token = default_token_generator.make_token(self.u)
  145. self.orig_debug = settings.DEBUG
  146. settings.DEBUG = True
  147. def tearDown(self):
  148. super(PasswordResetTests, self).tearDown()
  149. settings.DEBUG = self.orig_debug
  150. def test_bad_email(self):
  151. r = self.client.post(reverse('users.pw_reset'),
  152. {'email': 'foo@bar.com'})
  153. eq_(302, r.status_code)
  154. eq_('http://testserver/en-US/users/pwresetsent', r['location'])
  155. eq_(0, len(mail.outbox))
  156. @mock.patch.object(Site.objects, 'get_current')
  157. def test_success(self, get_current):
  158. get_current.return_value.domain = 'testserver.com'
  159. r = self.client.post(reverse('users.pw_reset'),
  160. {'email': self.u.email})
  161. eq_(302, r.status_code)
  162. eq_('http://testserver/en-US/users/pwresetsent', r['location'])
  163. eq_(1, len(mail.outbox))
  164. assert mail.outbox[0].subject.find('Password reset') == 0
  165. assert mail.outbox[0].body.find('pwreset/%s' % self.uidb36) > 0
  166. @mock.patch.object(PasswordResetForm, 'save')
  167. def test_smtp_error(self, pwform_save):
  168. def raise_smtp(*a, **kw):
  169. raise SMTPRecipientsRefused(recipients=[self.u.email])
  170. pwform_save.side_effect = raise_smtp
  171. r = self.client.post(reverse('users.pw_reset'),
  172. {'email': self.u.email})
  173. self.assertContains(r, unicode(ERROR_SEND_EMAIL))
  174. def _get_reset_url(self):
  175. return reverse('users.pw_reset_confirm',
  176. args=[self.uidb36, self.token])
  177. def test_bad_reset_url(self):
  178. r = self.client.get('/users/pwreset/junk/', follow=True)
  179. eq_(r.status_code, 404)
  180. r = self.client.get(reverse('users.pw_reset_confirm',
  181. args=[self.uidb36, '12-345']))
  182. eq_(200, r.status_code)
  183. doc = pq(r.content)
  184. eq_('Password reset unsuccessful', doc('article h1').text())
  185. def test_reset_fail(self):
  186. url = self._get_reset_url()
  187. r = self.client.post(url, {'new_password1': '', 'new_password2': ''})
  188. eq_(200, r.status_code)
  189. doc = pq(r.content)
  190. eq_(1, len(doc('ul.errorlist')))
  191. r = self.client.post(url, {'new_password1': 'onetwo12',
  192. 'new_password2': 'twotwo22'})
  193. eq_(200, r.status_code)
  194. doc = pq(r.content)
  195. eq_("The two password fields didn't match.",
  196. doc('ul.errorlist li').text())
  197. def test_reset_success(self):
  198. url = self._get_reset_url()
  199. new_pw = 'fjdka387fvstrongpassword!'
  200. assert self.u.check_password(new_pw) is False
  201. r = self.client.post(url, {'new_password1': new_pw,
  202. 'new_password2': new_pw})
  203. eq_(302, r.status_code)
  204. eq_('http://testserver/en-US/users/pwresetcomplete', r['location'])
  205. self.u = User.objects.get(username=self.u.username)
  206. assert self.u.check_password(new_pw)
  207. def test_reset_user_with_unusable_password(self):
  208. """Verify that user's with unusable passwords can reset them."""
  209. self.u.set_unusable_password()
  210. self.u.save()
  211. self.test_success()
  212. class EditProfileTests(TestCaseBase):
  213. def test_edit_profile(self):
  214. u = user(save=True)
  215. url = reverse('users.edit_profile')
  216. self.client.login(username=u.username, password='testpass')
  217. data = {'name': 'John Doe',
  218. 'public_email': True,
  219. 'bio': 'my bio',
  220. 'website': 'http://google.com/',
  221. 'twitter': '',
  222. 'facebook': '',
  223. 'irc_handle': 'johndoe',
  224. 'timezone': 'America/New_York',
  225. 'country': 'US',
  226. 'city': 'Disney World',
  227. 'locale': 'en-US'}
  228. r = self.client.post(url, data)
  229. eq_(302, r.status_code)
  230. profile = User.objects.get(username=u.username).get_profile()
  231. for key in data:
  232. if key != 'timezone':
  233. eq_(data[key], getattr(profile, key))
  234. eq_(data['timezone'], profile.timezone.zone)
  235. class EditAvatarTests(TestCaseBase):
  236. def setUp(self):
  237. super(EditAvatarTests, self).setUp()
  238. self.old_settings = copy(settings._wrapped.__dict__)
  239. self.u = user(save=True)
  240. profile(user=self.u)
  241. def tearDown(self):
  242. settings._wrapped.__dict__ = self.old_settings
  243. user_profile = Profile.objects.get(user__username=self.u.username)
  244. if user_profile.avatar:
  245. user_profile.avatar.delete()
  246. super(EditAvatarTests, self).tearDown()
  247. def test_large_avatar(self):
  248. settings.MAX_AVATAR_FILE_SIZE = 1024
  249. url = reverse('users.edit_avatar')
  250. self.client.login(username=self.u.username, password='testpass')
  251. with open('kitsune/upload/tests/media/test.jpg') as f:
  252. r = self.client.post(url, {'avatar': f})
  253. eq_(200, r.status_code)
  254. doc = pq(r.content)
  255. eq_('"test.jpg" is too large (12KB), the limit is 1KB',
  256. doc('.errorlist').text())
  257. def test_avatar_extensions(self):
  258. url = reverse('users.edit_avatar')
  259. self.client.login(username=self.u.username, password='testpass')
  260. with open('kitsune/upload/tests/media/test_invalid.ext') as f:
  261. r = self.client.post(url, {'avatar': f})
  262. eq_(200, r.status_code)
  263. doc = pq(r.content)
  264. eq_('Please upload an image with one of the following extensions: '
  265. 'jpg, jpeg, png, gif.', doc('.errorlist').text())
  266. def test_upload_avatar(self):
  267. """Upload a valid avatar."""
  268. user_profile = Profile.uncached.get(user__username=self.u.username)
  269. with open('kitsune/upload/tests/media/test.jpg') as f:
  270. user_profile.avatar.save('test_old.jpg', File(f), save=True)
  271. assert user_profile.avatar.name.endswith('92b516.jpg')
  272. old_path = user_profile.avatar.path
  273. assert os.path.exists(old_path), 'Old avatar is not in place.'
  274. url = reverse('users.edit_avatar')
  275. self.client.login(username=self.u.username, password='testpass')
  276. with open('kitsune/upload/tests/media/test.jpg') as f:
  277. r = self.client.post(url, {'avatar': f})
  278. eq_(302, r.status_code)
  279. eq_('http://testserver/en-US' + reverse('users.edit_profile'),
  280. r['location'])
  281. assert not os.path.exists(old_path), 'Old avatar was not removed.'
  282. def test_delete_avatar(self):
  283. """Delete an avatar."""
  284. self.test_upload_avatar()
  285. url = reverse('users.delete_avatar')
  286. self.client.login(username=self.u.username, password='testpass')
  287. r = self.client.post(url)
  288. user_profile = Profile.objects.get(user__username=self.u.username)
  289. eq_(302, r.status_code)
  290. eq_('http://testserver/en-US' + reverse('users.edit_profile'),
  291. r['location'])
  292. eq_('', user_profile.avatar.name)
  293. class ViewProfileTests(TestCaseBase):
  294. def setUp(self):
  295. self.u = user(save=True)
  296. self.profile = profile(name='', website='', user=self.u)
  297. def test_view_profile(self):
  298. r = self.client.get(reverse('users.profile', args=[self.u.username]))
  299. eq_(200, r.status_code)
  300. doc = pq(r.content)
  301. eq_(0, doc('#edit-profile-link').length)
  302. eq_(self.u.username, doc('h1.user').text())
  303. # No name set => no optional fields.
  304. eq_(0, doc('.contact').length)
  305. # Check canonical url
  306. eq_('/user/%s' % self.u.username,
  307. doc('link[rel="canonical"]')[0].attrib['href'])
  308. def test_view_profile_mine(self):
  309. """Logged in, on my profile, I see an edit link."""
  310. self.client.login(username=self.u.username, password='testpass')
  311. r = self.client.get(reverse('users.profile', args=[self.u.username]))
  312. eq_(200, r.status_code)
  313. doc = pq(r.content)
  314. eq_('Edit settings', doc('#user-nav li:last').text())
  315. self.client.logout()
  316. def test_bio_links_nofollow(self):
  317. self.profile.bio = 'http://getseo.com, [http://getseo.com]'
  318. self.profile.save()
  319. r = self.client.get(reverse('users.profile', args=[self.u.username]))
  320. eq_(200, r.status_code)
  321. doc = pq(r.content)
  322. eq_(2, len(doc('.bio a[rel="nofollow"]')))
  323. def test_num_documents(self):
  324. """Verify the number of documents contributed by user."""
  325. u = profile().user
  326. revision(creator=u, save=True)
  327. revision(creator=u, save=True)
  328. r = self.client.get(reverse('users.profile', args=[u.username]))
  329. eq_(200, r.status_code)
  330. assert '2 documents' in r.content
  331. def test_deactivate_button(self):
  332. """Check that the deactivate button is shown appropriately"""
  333. p = profile()
  334. r = self.client.get(reverse('users.profile', args=[p.user.username]))
  335. assert 'Deactivate this user' not in r.content
  336. add_permission(self.u, Profile, 'deactivate_users')
  337. self.client.login(username=self.u.username, password='testpass')
  338. r = self.client.get(reverse('users.profile', args=[p.user.username]))
  339. assert 'Deactivate this user' in r.content
  340. p.user.is_active = False
  341. p.user.save()
  342. r = self.client.get(reverse('users.profile', args=[p.user.username]))
  343. assert 'This user has been deactivated.' in r.content
  344. r = self.client.get(reverse('users.profile', args=[self.u.username]))
  345. assert 'Deactivate this user' not in r.content
  346. def test_badges_listed(self):
  347. """Verify that awarded badges appear on the profile page."""
  348. badge_title = 'awesomesauce badge'
  349. b = badge(title=badge_title, save=True)
  350. u = profile().user
  351. award(user=u, badge=b, save=True)
  352. r = self.client.get(reverse('users.profile', args=[u.username]))
  353. assert badge_title in r.content
  354. class PasswordChangeTests(TestCaseBase):
  355. def setUp(self):
  356. super(PasswordChangeTests, self).setUp()
  357. self.u = user(save=True)
  358. profile(user=self.u)
  359. self.url = reverse('users.pw_change')
  360. self.new_pw = 'fjdka387fvstrongpassword!'
  361. self.client.login(username=self.u.username, password='testpass')
  362. def test_change_password(self):
  363. assert self.u.check_password(self.new_pw) is False
  364. r = self.client.post(self.url, {'old_password': 'testpass',
  365. 'new_password1': self.new_pw,
  366. 'new_password2': self.new_pw})
  367. eq_(302, r.status_code)
  368. eq_('http://testserver/en-US/users/pwchangecomplete', r['location'])
  369. self.u = User.objects.get(username=self.u.username)
  370. assert self.u.check_password(self.new_pw)
  371. def test_bad_old_password(self):
  372. r = self.client.post(self.url, {'old_password': 'testpqss',
  373. 'new_password1': self.new_pw,
  374. 'new_password2': self.new_pw})
  375. eq_(200, r.status_code)
  376. doc = pq(r.content)
  377. eq_('Your old password was entered incorrectly. Please enter it '
  378. 'again.', doc('ul.errorlist').text())
  379. def test_new_pw_doesnt_match(self):
  380. r = self.client.post(self.url, {'old_password': 'testpqss',
  381. 'new_password1': self.new_pw,
  382. 'new_password2': self.new_pw + '1'})
  383. eq_(200, r.status_code)
  384. doc = pq(r.content)
  385. eq_("Your old password was entered incorrectly. Please enter it "
  386. "again. The two password fields didn't match.",
  387. doc('ul.errorlist').text())
  388. class ResendConfirmationTests(TestCaseBase):
  389. @mock.patch.object(Site.objects, 'get_current')
  390. def test_resend_confirmation(self, get_current):
  391. get_current.return_value.domain = 'testserver.com'
  392. RegistrationProfile.objects.create_inactive_user(
  393. 'testuser', 'testpass', 'testuser@email.com')
  394. eq_(1, len(mail.outbox))
  395. r = self.client.post(reverse('users.resend_confirmation'),
  396. {'email': 'testuser@email.com'})
  397. eq_(200, r.status_code)
  398. eq_(2, len(mail.outbox))
  399. assert mail.outbox[1].subject.find('Please confirm your email') == 0
  400. @mock.patch.object(Site.objects, 'get_current')
  401. def test_resend_confirmation_already_activated(self, get_current):
  402. get_current.return_value.domain = 'testserver.com'
  403. user_ = RegistrationProfile.objects.create_inactive_user(
  404. 'testuser', 'testpass', 'testuser@email.com')
  405. # Activate the user
  406. key = RegistrationProfile.objects.all()[0].activation_key
  407. url = reverse('users.activate', args=[user_.id, key])
  408. response = self.client.get(url, follow=True)
  409. eq_(200, response.status_code)
  410. user_ = User.objects.get(pk=user_.pk)
  411. assert user_.is_active
  412. # Delete RegistrationProfile objects.
  413. RegistrationProfile.objects.all().delete()
  414. # Resend the confirmation email
  415. r = self.client.post(reverse('users.resend_confirmation'),
  416. {'email': 'testuser@email.com'})
  417. eq_(200, r.status_code)
  418. eq_(2, len(mail.outbox))
  419. eq_(mail.outbox[1].subject.find('Account already activated'), 0)
  420. @mock.patch.object(RegistrationManager, 'send_confirmation_email')
  421. @mock.patch.object(Site.objects, 'get_current')
  422. def test_smtp_error(self, get_current, send_confirmation_email):
  423. get_current.return_value.domain = 'testserver.com'
  424. RegistrationProfile.objects.create_inactive_user(
  425. 'testuser', 'testpass', 'testuser@email.com')
  426. def raise_smtp(reg_profile):
  427. raise SMTPRecipientsRefused(recipients=[reg_profile.user.email])
  428. send_confirmation_email.side_effect = raise_smtp
  429. r = self.client.post(reverse('users.resend_confirmation'),
  430. {'email': 'testuser@email.com'})
  431. self.assertContains(r, unicode(ERROR_SEND_EMAIL))
  432. class FlagProfileTests(TestCaseBase):
  433. def test_flagged_and_deleted_profile(self):
  434. u = user(save=True)
  435. p = profile(user=u)
  436. flag_user = user(save=True)
  437. # Flag a profile and delete it
  438. f = FlaggedObject(content_object=p,
  439. reason='spam', creator_id=flag_user.id)
  440. f.save()
  441. p.delete()
  442. # Verify flagit queue
  443. u = user(save=True)
  444. add_permission(u, FlaggedObject, 'can_moderate')
  445. self.client.login(username=u.username, password='testpass')
  446. response = get(self.client, 'flagit.queue')
  447. eq_(200, response.status_code)
  448. doc = pq(response.content)
  449. eq_(1, len(doc('#flagged-queue form.update')))
  450. class ForgotUsernameTests(TestCaseBase):
  451. """Tests for the Forgot Username flow."""
  452. def test_GET(self):
  453. r = self.client.get(reverse('users.forgot_username'))
  454. eq_(200, r.status_code)
  455. @mock.patch.object(Site.objects, 'get_current')
  456. def test_POST(self, get_current):
  457. get_current.return_value.domain = 'testserver.com'
  458. u = user(save=True, email='a@b.com', is_active=True)
  459. profile(user=u) # save=True is forced.
  460. r = self.client.post(reverse('users.forgot_username'),
  461. {'email': u.email})
  462. eq_(302, r.status_code)
  463. eq_('http://testserver/en-US/users/login', r['location'])
  464. # Verify email
  465. eq_(1, len(mail.outbox))
  466. assert mail.outbox[0].subject.find('Your username on') == 0
  467. assert mail.outbox[0].body.find(u.username) > 0