addons-server/apps/users/tests/test_views.py

1797 строки
70 KiB
Python

from datetime import datetime, timedelta
import json
from django.conf import settings
from django.core import mail
from django import http
from django.core.cache import cache
from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator
from django.forms.models import model_to_dict
from django.utils.http import int_to_base36
import jingo
from jingo.helpers import datetime as datetime_filter
from mock import patch
from nose.tools import eq_
from nose import SkipTest
from tower import strip_whitespace
import waffle
# Unused, but needed so that we can patch jingo.
from waffle import helpers
import amo
import amo.tests
from abuse.models import AbuseReport
from access.models import Group, GroupUser
from addons.models import Addon, AddonUser, AddonPremium
from amo.helpers import urlparams
from amo.pyquery_wrapper import PyQuery as pq
from amo.urlresolvers import reverse
from bandwagon.models import Collection, CollectionAddon, CollectionWatcher
from devhub.models import ActivityLog
from market.models import PreApprovalUser, Price
import paypal
from reviews.models import Review
from stats.models import Contribution
from users.models import BlacklistedPassword, UserProfile, UserNotification
import users.notifications as email
from users.utils import EmailResetCode, UnsubscribeCode
from webapps.models import Installed
def check_sidebar_links(self, expected):
r = self.client.get(self.url)
eq_(r.status_code, 200)
links = pq(r.content)('#secondary-nav ul a')
amo.tests.check_links(expected, links)
eq_(links.filter('.selected').attr('href'), self.url)
class UserViewBase(amo.tests.TestCase):
fixtures = ['users/test_backends']
def setUp(self):
self.client = amo.tests.TestClient()
self.client.get('/')
self.user = User.objects.get(id='4043307')
self.user_profile = self.user.get_profile()
def get_profile(self):
return UserProfile.objects.get(id=self.user.id)
class TestAjax(UserViewBase):
def setUp(self):
super(TestAjax, self).setUp()
self.client.login(username='jbalogh@mozilla.com', password='foo')
def test_ajax_404(self):
r = self.client.get(reverse('users.ajax'), follow=True)
eq_(r.status_code, 404)
def test_ajax_success(self):
r = self.client.get(reverse('users.ajax'), {'q': 'fligtar@gmail.com'},
follow=True)
data = json.loads(r.content)
eq_(data, {'status': 1, 'message': '', 'id': 9945,
'name': u'Justin Scott \u0627\u0644\u062a\u0637\u0628'})
def test_ajax_xss(self):
self.user_profile.display_name = '<script>alert("xss")</script>'
self.user_profile.save()
assert '<script>' in self.user_profile.display_name, (
'Expected <script> to be in display name')
r = self.client.get(reverse('users.ajax'),
{'q': self.user_profile.email})
data = r.content
assert '<script>' not in data
assert '&lt;script&gt;' in data
def test_ajax_failure_incorrect_email(self):
r = self.client.get(reverse('users.ajax'), {'q': 'incorrect'},
follow=True)
data = json.loads(r.content)
eq_(data,
{'status': 0,
'message': 'A user with that email address does not exist.'})
def test_ajax_failure_no_email(self):
r = self.client.get(reverse('users.ajax'), {'q': ''}, follow=True)
data = json.loads(r.content)
eq_(data,
{'status': 0,
'message': 'An email address is required.'})
def test_forbidden(self):
self.client.logout()
r = self.client.get(reverse('users.ajax'))
eq_(r.status_code, 401)
class TestEdit(UserViewBase):
def setUp(self):
super(TestEdit, self).setUp()
self.client.login(username='jbalogh@mozilla.com', password='foo')
self.user = UserProfile.objects.get(username='jbalogh')
self.url = reverse('users.edit')
self.data = {'username': 'jbalogh', 'email': 'jbalogh@mozilla.com',
'oldpassword': 'foo', 'password': 'longenough',
'password2': 'longenough'}
def test_password_logs(self):
res = self.client.post(self.url, self.data)
eq_(res.status_code, 302)
eq_(self.user.userlog_set
.filter(activity_log__action=amo.LOG.CHANGE_PASSWORD.id)
.count(), 1)
def test_password_empty(self):
admingroup = Group(rules='Admin:EditAnyUser')
admingroup.save()
GroupUser.objects.create(group=admingroup, user=self.user)
homepage = {'username': 'jbalogh', 'email': 'jbalogh@mozilla.com',
'homepage': 'http://cbc.ca'}
res = self.client.post(self.url, homepage)
eq_(res.status_code, 302)
def test_password_blacklisted(self):
BlacklistedPassword.objects.create(password='password')
bad = self.data.copy()
bad['password'] = 'password'
res = self.client.post(self.url, bad)
eq_(res.status_code, 200)
eq_(res.context['form'].is_valid(), False)
eq_(res.context['form'].errors['password'],
[u'That password is not allowed.'])
def test_password_short(self):
bad = self.data.copy()
bad['password'] = 'short'
res = self.client.post(self.url, bad)
eq_(res.status_code, 200)
eq_(res.context['form'].is_valid(), False)
eq_(res.context['form'].errors['password'],
[u'Must be 8 characters or more.'])
def test_email_change_mail_sent(self):
data = {'username': 'jbalogh',
'email': 'jbalogh.changed@mozilla.com',
'display_name': 'DJ SurfNTurf', }
r = self.client.post(self.url, data, follow=True)
self.assertRedirects(r, self.url)
self.assertContains(r, "An email has been sent to %s" % data['email'])
# The email shouldn't change until they confirm, but the name should
u = User.objects.get(id='4043307').get_profile()
self.assertEquals(u.name, 'DJ SurfNTurf')
self.assertEquals(u.email, 'jbalogh@mozilla.com')
eq_(len(mail.outbox), 1)
assert mail.outbox[0].subject.find('Please confirm your email') == 0
assert mail.outbox[0].body.find('%s/emailchange/' % self.user.id) > 0
@patch.object(settings, 'APP_PREVIEW', True)
def test_email_cant_change(self):
data = {'username': 'jbalogh',
'email': 'jbalogh.changed@mozilla.com',
'display_name': 'DJ SurfNTurf', }
res = self.client.post(self.url, data, follow=True)
eq_(res.status_code, 200)
eq_(len(pq(res.content)('div.error')), 1)
eq_(len(mail.outbox), 0)
def test_edit_bio(self):
eq_(self.get_profile().bio, None)
data = {'username': 'jbalogh',
'email': 'jbalogh.changed@mozilla.com',
'bio': 'xxx unst unst'}
r = self.client.post(self.url, data, follow=True)
self.assertRedirects(r, self.url)
self.assertContains(r, data['bio'])
eq_(unicode(self.get_profile().bio), data['bio'])
data['bio'] = 'yyy unst unst'
r = self.client.post(self.url, data, follow=True)
self.assertRedirects(r, self.url)
self.assertContains(r, data['bio'])
eq_(unicode(self.get_profile().bio), data['bio'])
def check_default_choices(self, choices, checked=True):
doc = pq(self.client.get(self.url).content)
eq_(doc('input[name=notifications]:checkbox').length, len(choices))
for id, label in choices:
box = doc('input[name=notifications][value=%s]' % id)
if checked:
eq_(box.filter(':checked').length, 1)
else:
eq_(box.length, 1)
parent = box.parent('label')
if checked:
eq_(parent.find('.msg').length, 1) # Check for "NEW" message.
eq_(parent.remove('.msg, .req').text(), label)
def post_notifications(self, choices):
self.check_default_choices(choices)
self.data['notifications'] = []
r = self.client.post(self.url, self.data)
self.assertRedirects(r, self.url, 302)
eq_(UserNotification.objects.count(), len(email.NOTIFICATION))
eq_(UserNotification.objects.filter(enabled=True).count(),
len(filter(lambda x: x.mandatory, email.NOTIFICATIONS)))
self.check_default_choices(choices, checked=False)
def test_edit_notifications(self):
# Make jbalogh a developer.
AddonUser.objects.create(user=self.user,
addon=Addon.objects.create(type=amo.ADDON_EXTENSION))
choices = email.NOTIFICATIONS_CHOICES
self.check_default_choices(choices)
self.data['notifications'] = [2, 4, 6]
r = self.client.post(self.url, self.data)
self.assertRedirects(r, self.url, 302)
mandatory = [n.id for n in email.NOTIFICATIONS if n.mandatory]
total = len(self.data['notifications'] + mandatory)
eq_(UserNotification.objects.count(), len(email.NOTIFICATION))
eq_(UserNotification.objects.filter(enabled=True).count(), total)
doc = pq(self.client.get(self.url, self.data).content)
eq_(doc('input[name=notifications]:checked').length, total)
eq_(doc('.more-none').length, len(email.NOTIFICATION_GROUPS))
eq_(doc('.more-all').length, len(email.NOTIFICATION_GROUPS))
@patch.object(settings, 'APP_PREVIEW', True)
def test_edit_app_notifications(self):
AddonUser.objects.create(user=self.user,
addon=Addon.objects.create(type=amo.ADDON_EXTENSION))
self.post_notifications(email.APP_NOTIFICATIONS_CHOICES)
def test_edit_notifications_non_dev(self):
self.post_notifications(email.NOTIFICATIONS_CHOICES_NOT_DEV)
@patch.object(settings, 'APP_PREVIEW', True)
def test_edit_app_notifications_non_dev(self):
self.post_notifications(email.APP_NOTIFICATIONS_CHOICES_NOT_DEV)
def _test_edit_notifications_non_dev_error(self):
self.data['notifications'] = [2, 4, 6]
r = self.client.post(self.url, self.data)
assert r.context['form'].errors['notifications']
def test_edit_notifications_non_dev_error(self):
self._test_edit_notifications_non_dev_error()
@patch.object(settings, 'APP_PREVIEW', True)
def test_edit_app_notifications_non_dev_error(self):
self._test_edit_notifications_non_dev_error()
def test_collections_toggles(self):
r = self.client.get(self.url)
eq_(r.status_code, 200)
doc = pq(r.content)
eq_(doc('#profile-misc').length, 1,
'Collections options should be visible.')
@patch.object(settings, 'APP_PREVIEW', True)
def test_apps_collections_toggles(self):
r = self.client.get(self.url)
eq_(r.status_code, 200)
doc = pq(r.content)
eq_(doc('#profile-misc').length, 0,
'Collections options should not be visible.')
class TestEditAdmin(UserViewBase):
fixtures = ['base/users']
def setUp(self):
self.client.login(username='admin@mozilla.com', password='password')
self.regular = self.get_user()
self.url = reverse('users.admin_edit', args=[self.regular.pk])
def get_data(self):
data = model_to_dict(self.regular)
data['admin_log'] = 'test'
for key in ['password', 'resetcode_expires']:
del data[key]
return data
def get_user(self):
# Using pk so that we can still get the user after anonymize.
return UserProfile.objects.get(pk=999)
def test_edit(self):
res = self.client.get(self.url)
eq_(res.status_code, 200)
def test_edit_forbidden(self):
self.client.logout()
self.client.login(username='editor@mozilla.com', password='password')
res = self.client.get(self.url)
eq_(res.status_code, 403)
def test_edit_forbidden_anon(self):
self.client.logout()
res = self.client.get(self.url)
eq_(res.status_code, 302)
def test_anonymize(self):
data = self.get_data()
data['anonymize'] = True
data['nickname'] = ''
res = self.client.post(self.url, data)
eq_(res.status_code, 302)
eq_(self.get_user().password, "sha512$Anonymous$Password")
def test_anonymize_fails(self):
data = self.get_data()
data['anonymize'] = True
data['email'] = 'something@else.com'
res = self.client.post(self.url, data)
eq_(res.status_code, 200)
eq_(self.get_user().password, self.regular.password) # Hasn't changed.
def test_admin_logs_edit(self):
data = self.get_data()
data['email'] = 'something@else.com'
self.client.post(self.url, data)
res = ActivityLog.objects.filter(action=amo.LOG.ADMIN_USER_EDITED.id)
eq_(res.count(), 1)
assert self.get_data()['admin_log'] in res[0]._arguments
def test_admin_logs_anonymize(self):
data = self.get_data()
data['anonymize'] = True
self.client.post(self.url, data)
res = (ActivityLog.objects
.filter(action=amo.LOG.ADMIN_USER_ANONYMIZED.id))
eq_(res.count(), 1)
assert self.get_data()['admin_log'] in res[0]._arguments
def test_admin_no_password(self):
data = self.get_data()
data.update({'password': 'pass1234',
'password2': 'pass1234',
'oldpassword': 'password'})
self.client.post(self.url, data)
logs = ActivityLog.objects.filter
eq_(logs(action=amo.LOG.CHANGE_PASSWORD.id).count(), 0)
res = logs(action=amo.LOG.ADMIN_USER_EDITED.id)
eq_(res.count(), 1)
eq_(res[0].details['password'][0], u'****')
class TestPasswordAdmin(UserViewBase):
fixtures = ['base/users']
def setUp(self):
self.client.login(username='editor@mozilla.com', password='password')
self.url = reverse('users.edit')
self.correct = {'username': 'editor',
'email': 'editor@mozilla.com',
'oldpassword': 'password', 'password': 'longenough',
'password2': 'longenough'}
def test_password_admin(self):
res = self.client.post(self.url, self.correct, follow=False)
eq_(res.status_code, 200)
eq_(res.context['form'].is_valid(), False)
eq_(res.context['form'].errors['password'],
[u'Letters and numbers required.'])
def test_password(self):
UserProfile.objects.get(username='editor').groups.all().delete()
res = self.client.post(self.url, self.correct, follow=False)
eq_(res.status_code, 302)
class TestEmailChange(UserViewBase):
def setUp(self):
super(TestEmailChange, self).setUp()
self.token, self.hash = EmailResetCode.create(self.user.id,
'nobody@mozilla.org')
def test_fail(self):
# Completely invalid user, valid code
url = reverse('users.emailchange', args=[1234, self.token, self.hash])
r = self.client.get(url, follow=True)
eq_(r.status_code, 404)
# User is in the system, but not attached to this code, valid code
url = reverse('users.emailchange', args=[9945, self.token, self.hash])
r = self.client.get(url, follow=True)
eq_(r.status_code, 400)
# Valid user, invalid code
url = reverse('users.emailchange', args=[self.user.id, self.token,
self.hash[:-3]])
r = self.client.get(url, follow=True)
eq_(r.status_code, 400)
def test_success(self):
self.assertEqual(self.user_profile.email, 'jbalogh@mozilla.com')
url = reverse('users.emailchange', args=[self.user.id, self.token,
self.hash])
r = self.client.get(url, follow=True)
eq_(r.status_code, 200)
u = User.objects.get(id=self.user.id).get_profile()
self.assertEqual(u.email, 'nobody@mozilla.org')
class TestLogin(UserViewBase):
fixtures = ['users/test_backends', 'base/addon_3615']
def setUp(self):
super(TestLogin, self).setUp()
self.url = reverse('users.login')
self.data = {'username': 'jbalogh@mozilla.com', 'password': 'foo'}
def test_client_login(self):
"""
This is just here to make sure Test Client's login() works with
our custom code.
"""
assert not self.client.login(username='jbalogh@mozilla.com',
password='wrong')
assert self.client.login(**self.data)
# waffle: browserid-login
@patch.dict(jingo.env.globals['waffle'], {'switch': lambda x: True})
def test_for_profile_completion_url(self):
res = self.client.get(self.url)
eq_(res.status_code, 200)
doc = pq(res.content)
eq_(doc('.browserid-login').attr('data-profile-form-url'),
reverse('users.complete_profile_form'))
def test_double_login(self):
r = self.client.post(self.url, self.data, follow=True)
self.assertRedirects(r, '/en-US/firefox/')
# If you go to the login page when you're already logged in we bounce
# you.
r = self.client.get(self.url, follow=True)
self.assertRedirects(r, '/en-US/firefox/')
r = self.client.get(self.url + '?to=/de/firefox/', follow=True)
self.assertRedirects(r, '/de/firefox/')
r = self.client.get(self.url + '?to=http://xx.com', follow=True)
self.assertRedirects(r, '/en-US/firefox/')
@amo.tests.mobile_test
def test_mobile_login(self):
r = self.client.get(self.url)
eq_(r.status_code, 200)
doc = pq(r.content)('header')
eq_(doc('nav').length, 1)
eq_(doc('#home').length, 1)
eq_(doc('#auth-nav li.login').length, 0)
@amo.tests.mobile_test
@patch.object(settings, 'APP_PREVIEW', True)
def test_mobile_login_apps_preview(self):
r = self.client.get(self.url)
eq_(r.status_code, 200)
doc = pq(r.content)('header')
eq_(doc('nav').length, 1)
eq_(doc('#home').length, 0)
eq_(doc('#auth-nav li.login').length, 0)
def test_login_ajax(self):
url = reverse('users.login_modal')
r = self.client.get(url)
eq_(r.status_code, 200)
res = self.client.post(url, data=self.data)
eq_(res.status_code, 302)
@patch.object(waffle, 'switch_is_active', lambda x: True)
def test_login_paypal(self):
addon = Addon.objects.all()[0]
price = Price.objects.create(price='0.99')
AddonPremium.objects.create(addon=addon, price=price)
addon.update(premium_type=amo.ADDON_PREMIUM)
url = reverse('addons.purchase.start', args=[addon.slug])
r = self.client.get_ajax(url)
eq_(r.status_code, 200)
res = self.client.post_ajax(url, data=self.data)
eq_(res.status_code, 200)
def test_login_ajax_error(self):
url = reverse('users.login_modal')
data = self.data
data['username'] = ''
res = self.client.post(url, data=self.data)
eq_(res.context['form'].errors['username'][0],
'This field is required.')
def test_login_ajax_wrong(self):
url = reverse('users.login_modal')
data = self.data
data['username'] = 'jeffb@mozilla.com'
res = self.client.post(url, data=self.data)
text = 'Please enter a correct username and password.'
assert res.context['form'].errors['__all__'][0].startswith(text)
def test_login_no_recaptcha(self):
res = self.client.post(self.url, data=self.data)
eq_(res.status_code, 302)
@patch('ratelimit.backends.cachebe.CacheBackend.limit')
def test_login_recaptcha(self, limit):
raise SkipTest
limit.return_value = True
res = self.client.post(self.url, data=self.data)
eq_(res.status_code, 403)
@patch.object(settings, 'RECAPTCHA_PRIVATE_KEY', 'something')
@patch.object(settings, 'LOGIN_RATELIMIT_USER', 2)
def test_login_attempts_recaptcha(self):
res = self.client.post(self.url, data=self.data)
eq_(res.status_code, 200)
assert res.context['form'].fields.get('recaptcha')
@patch.object(settings, 'RECAPTCHA_PRIVATE_KEY', 'something')
def test_login_shown_recaptcha(self):
data = self.data.copy()
data['recaptcha_shown'] = ''
res = self.client.post(self.url, data=data)
eq_(res.status_code, 200)
assert res.context['form'].fields.get('recaptcha')
@patch.object(settings, 'RECAPTCHA_PRIVATE_KEY', 'something')
@patch.object(settings, 'LOGIN_RATELIMIT_USER', 2)
@patch('captcha.fields.ReCaptchaField.clean')
def test_login_with_recaptcha(self, clean):
clean.return_value = ''
data = self.data.copy()
data.update({'recaptcha': '', 'recaptcha_shown': ''})
res = self.client.post(self.url, data=data)
eq_(res.status_code, 302)
def test_login_fails_increment(self):
# It increments even when the form is wrong.
user = UserProfile.objects.filter(email=self.data['username'])
eq_(user.get().failed_login_attempts, 3)
self.client.post(self.url, data={'username': self.data['username']})
eq_(user.get().failed_login_attempts, 4)
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch('httplib2.Http.request')
def test_browserid_login_success(self, http_request):
"""
A success response from BrowserID results in successful login.
"""
url = reverse('users.browserid_login')
http_request.return_value = (200, json.dumps({'status': 'okay',
'email': 'jbalogh@mozilla.com'}))
res = self.client.post(url, data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 200)
# If they're already logged in we return fast.
eq_(self.client.post(url).status_code, 200)
def _make_admin_user(self, email):
"""
Create a user with at least one admin privilege.
"""
p = UserProfile(username='admin', email=email,
password='hunter2', created=datetime.now(), pk=998)
p.create_django_user()
admingroup = Group.objects.create(rules='Admin:EditAnyUser')
GroupUser.objects.create(group=admingroup, user=p)
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch('httplib2.Http.request')
def test_browserid_restricted_login(self, http_request):
"""
A success response from BrowserID for accounts restricted to
password login results in a 400 error, for which the frontend
will display a message about the restriction.
"""
email = 'admin@mozilla.com'
http_request.return_value = (200, json.dumps({'status': 'okay',
'email': email}))
self._make_admin_user(email)
res = self.client.post(reverse('users.browserid_login'),
data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 400)
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch('httplib2.Http.request')
def test_browserid_no_account(self, http_request):
"""
BrowserID login for an email address with no account creates a
new account.
"""
email = 'newuser@example.com'
http_request.return_value = (200, json.dumps({'status': 'okay',
'email': email}))
res = self.client.post(reverse('users.browserid_login'),
data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 200)
profiles = UserProfile.objects.filter(email=email)
eq_(len(profiles), 1)
eq_(profiles[0].username, 'newuser')
data = json.loads(res.content)
eq_(data['profile_needs_completion'], True)
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch.object(settings, 'FORCE_PROFILE_COMPLETION', False)
@patch('httplib2.Http.request')
def test_disable_profile_completion(self, http_request):
email = 'newuser@example.com'
http_request.return_value = (200, json.dumps({'status': 'okay',
'email': email}))
res = self.client.post(reverse('users.browserid_login'),
data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 200)
data = json.loads(res.content)
eq_(data['profile_needs_completion'], False)
@patch.object(settings, 'REGISTER_USER_LIMIT', 1)
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch('httplib2.Http.request')
def test_browserid_register_limit(self, http_request):
"""
Account creation via BrowserID respects
settings.REGISTER_USER_LIMIT.
"""
http_request.return_value = (200, json.dumps(
{'status': 'okay',
'email': 'extrauser@example.com'}))
res = self.client.post(reverse('users.browserid_login'),
data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 401)
_m = ('Sorry, no more registrations are allowed. '
'<a href="https://developer.mozilla.org/en-US/apps">'
'Learn more</a>')
eq_(res.content, _m)
profile_count = UserProfile.objects.count()
eq_(profile_count, 4)
@patch.object(settings, 'REGISTER_USER_LIMIT', 1)
@patch.object(settings, 'REGISTER_OVERRIDE_TOKEN', 'mozilla')
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch('httplib2.Http.request')
def test_override_browserid_register_limit(self, http_request):
email = 'override-user@example.com'
http_request.return_value = (200, json.dumps({'status': 'okay',
'email': email}))
self.client.cookies['reg_override_token'] = 'mozilla'
res = self.client.post(reverse('users.browserid_login'),
data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 200)
profiles = UserProfile.objects.filter(email=email)
eq_(len(profiles), 1)
eq_(profiles[0].username, 'override-user')
@patch.object(settings, 'REGISTER_USER_LIMIT', 1)
@patch.object(settings, 'REGISTER_OVERRIDE_TOKEN', 'mozilla')
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch('httplib2.Http.request')
def test_override_browserid_register_wrong_token(self, http_request):
email = 'override-user@example.com'
http_request.return_value = (200, json.dumps({'status': 'okay',
'email': email}))
self.client.cookies['reg_override_token'] = 'netscape'
res = self.client.post(reverse('users.browserid_login'),
data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 401)
@patch.object(settings, 'REGISTER_OVERRIDE_TOKEN', 'letmein')
def test_override_token_sets_cookie(self):
res = self.client.get(self.url + '?ro=letmein')
eq_(res.status_code, 200)
eq_(self.client.cookies['reg_override_token'].value, 'letmein')
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch('httplib2.Http.request')
def test_browserid_login_failure(self, http_request):
"""
A failure response from BrowserID results in login failure.
"""
http_request.return_value = (200, json.dumps({'status': 'busted'}))
res = self.client.post(reverse('users.browserid_login'),
data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 401)
assert 'BrowserID authentication failure' in res.content
@patch.object(settings, 'REGISTER_USER_LIMIT', 100)
@patch('django.contrib.auth.views.login')
def test_registration_open(self, login):
def assert_registration_open(request, extra_context=None, **kwargs):
assert not extra_context['registration_closed']
return http.HttpResponse(200)
login.side_effect = assert_registration_open
self.client.get(self.url)
assert login.called
@patch.object(settings, 'REGISTER_USER_LIMIT', 1)
@patch('django.contrib.auth.views.login')
def test_registration_closed(self, login):
def assert_registration_open(request, extra_context=None, **kwargs):
assert extra_context['registration_closed']
return http.HttpResponse(200)
login.side_effect = assert_registration_open
self.client.get(self.url)
assert login.called
@patch.object(settings, 'REGISTER_USER_LIMIT', 0)
@patch('django.contrib.auth.views.login')
def test_registration_open_when_no_limit_set(self, login):
def assert_registration_open(request, extra_context=None, **kwargs):
assert not extra_context['registration_closed'], (
'Expected registration to be open')
return http.HttpResponse(200)
login.side_effect = assert_registration_open
self.client.get(self.url)
assert login.called
@patch.object(waffle, 'switch_is_active', lambda x: True)
@patch('httplib2.Http.request')
def test_browserid_duplicate_username(self, http_request):
email = 'jbalogh@example.com' # existing
http_request.return_value = (200, json.dumps({'status': 'okay',
'email': email}))
res = self.client.post(reverse('users.browserid_login'),
data=dict(assertion='fake-assertion',
audience='fakeamo.org'))
eq_(res.status_code, 200)
profiles = UserProfile.objects.filter(email=email)
eq_(profiles[0].username, 'jbalogh2')
# Note: lower level unit tests for this functionality are in
# TestAutoCreateUsername()
class TestProfileCompletion(UserViewBase):
fixtures = ['users/test_backends', 'base/addon_3615']
def setUp(self):
self.profile = UserProfile.objects.get(email='jbalogh@mozilla.com')
assert self.client.login(username=self.profile.email, password='foo')
def test_show_form(self):
res = self.client.get(reverse('users.complete_profile_form'))
eq_(res.status_code, 200)
doc = pq(res.content)
eq_(doc('form').attr('action'), reverse('users.complete_profile'))
def test_use_logged_in_user(self):
res = self.client.get(reverse('users.complete_profile_form'))
eq_(res.context['profile_form'].instance.pk, self.profile.pk)
def test_complete(self):
res = self.client.post(reverse('users.complete_profile'),
dict(username='newusername',
display_name=u'Ivan Kristi\u0107'))
eq_(res.status_code, 200)
pr = UserProfile.objects.get(pk=self.profile.pk)
eq_(pr.username, 'newusername')
eq_(pr.display_name, u'Ivan Kristi\u0107')
@patch.object(settings, 'RECAPTCHA_PRIVATE_KEY', '')
@patch('users.models.UserProfile.log_login_attempt')
class TestFailedCount(UserViewBase):
fixtures = ['users/test_backends', 'base/addon_3615']
def setUp(self):
super(TestFailedCount, self).setUp()
self.url = reverse('users.login')
self.data = {'username': 'jbalogh@mozilla.com', 'password': 'foo'}
def log_calls(self, obj):
return [call[0][0] for call in obj.call_args_list]
def test_login_passes(self, log_login_attempt):
self.client.post(self.url, data=self.data)
eq_(self.log_calls(log_login_attempt), [True])
def test_login_fails(self, log_login_attempt):
self.client.post(self.url, data={'username': self.data['username']})
eq_(self.log_calls(log_login_attempt), [False])
def test_login_deleted(self, log_login_attempt):
(UserProfile.objects.get(email=self.data['username'])
.update(deleted=True))
self.client.post(self.url, data={'username': self.data['username']})
eq_(self.log_calls(log_login_attempt), [False])
def test_login_confirmation(self, log_login_attempt):
(UserProfile.objects.get(email=self.data['username'])
.update(confirmationcode='123'))
self.client.post(self.url, data={'username': self.data['username']})
eq_(self.log_calls(log_login_attempt), [False])
def test_login_get(self, log_login_attempt):
self.client.get(self.url, data={'username': self.data['username']})
eq_(log_login_attempt.called, False)
def test_login_get_no_data(self, log_login_attempt):
self.client.get(self.url)
eq_(log_login_attempt.called, False)
class TestUnsubscribe(UserViewBase):
fixtures = ['base/users']
def setUp(self):
self.user = User.objects.get(email='editor@mozilla.com')
self.user_profile = self.user.get_profile()
def test_correct_url_update_notification(self):
# Make sure the user is subscribed
perm_setting = email.NOTIFICATIONS[0]
un = UserNotification.objects.create(notification_id=perm_setting.id,
user=self.user_profile,
enabled=True)
# Create a URL
token, hash = UnsubscribeCode.create(self.user.email)
url = reverse('users.unsubscribe', args=[token, hash,
perm_setting.short])
# Load the URL
r = self.client.get(url)
doc = pq(r.content)
# Check that it was successful
assert doc('#unsubscribe-success').length
assert doc('#standalone').length
eq_(doc('#standalone ul li').length, 1)
# Make sure the user is unsubscribed
un = UserNotification.objects.filter(notification_id=perm_setting.id,
user=self.user)
eq_(un.count(), 1)
eq_(un.all()[0].enabled, False)
def test_correct_url_new_notification(self):
# Make sure the user is subscribed
assert not UserNotification.objects.count()
# Create a URL
perm_setting = email.NOTIFICATIONS[0]
token, hash = UnsubscribeCode.create(self.user.email)
url = reverse('users.unsubscribe', args=[token, hash,
perm_setting.short])
# Load the URL
r = self.client.get(url)
doc = pq(r.content)
# Check that it was successful
assert doc('#unsubscribe-success').length
assert doc('#standalone').length
eq_(doc('#standalone ul li').length, 1)
# Make sure the user is unsubscribed
un = UserNotification.objects.filter(notification_id=perm_setting.id,
user=self.user)
eq_(un.count(), 1)
eq_(un.all()[0].enabled, False)
def test_wrong_url(self):
perm_setting = email.NOTIFICATIONS[0]
token, hash = UnsubscribeCode.create(self.user.email)
hash = hash[::-1] # Reverse the hash, so it's wrong
url = reverse('users.unsubscribe', args=[token, hash,
perm_setting.short])
r = self.client.get(url)
doc = pq(r.content)
eq_(doc('#unsubscribe-fail').length, 1)
class TestReset(UserViewBase):
fixtures = ['base/users']
def setUp(self):
user = User.objects.get(email='editor@mozilla.com').get_profile()
self.token = [int_to_base36(user.id),
default_token_generator.make_token(user)]
def test_reset_msg(self):
res = self.client.get(reverse('users.pwreset_confirm',
args=self.token))
assert 'For your account' in res.content
def test_reset_fails(self):
res = self.client.post(reverse('users.pwreset_confirm',
args=self.token),
data={'new_password1': 'spassword',
'new_password2': 'spassword'})
eq_(res.context['form'].errors['new_password1'][0],
'Letters and numbers required.')
class TestLogout(UserViewBase):
def test_success(self):
user = UserProfile.objects.get(email='jbalogh@mozilla.com')
self.client.login(username=user.email, password='foo')
r = self.client.get('/', follow=True)
eq_(pq(r.content.decode('utf-8'))('.account .user').text(),
user.display_name)
eq_(pq(r.content)('.account .user').attr('title'), user.email)
r = self.client.get('/users/logout', follow=True)
assert not pq(r.content)('.account .user')
def test_redirect(self):
self.client.login(username='jbalogh@mozilla.com', password='foo')
self.client.get('/', follow=True)
url = '/en-US/firefox/about'
r = self.client.get(urlparams(reverse('users.logout'), to=url),
follow=True)
self.assertRedirects(r, url, status_code=302)
# Test a valid domain. Note that assertRedirects doesn't work on
# external domains
url = urlparams(reverse('users.logout'), to='/addon/new',
domain='builder')
r = self.client.get(url, follow=True)
to, code = r.redirect_chain[0]
self.assertEqual(to, 'https://builder.addons.mozilla.org/addon/new')
self.assertEqual(code, 302)
# Test an invalid domain
url = urlparams(reverse('users.logout'), to='/en-US/firefox/about',
domain='http://evil.com')
r = self.client.get(url, follow=True)
self.assertRedirects(r, '/en-US/firefox/about', status_code=302)
class TestRegistration(UserViewBase):
def test_new_confirm(self):
waffle.models.Switch.objects.create(name='zamboni-login', active=True)
# User doesn't have a confirmation code.
url = reverse('users.confirm', args=[self.user.id, 'code'])
r = self.client.get(url, follow=True)
is_anonymous = pq(r.content)('body').attr('data-anonymous')
eq_(json.loads(is_anonymous), True)
self.user_profile.update(confirmationcode='code')
# URL has the wrong confirmation code.
url = reverse('users.confirm', args=[self.user.id, 'blah'])
r = self.client.get(url, follow=True)
self.assertContains(r, 'Invalid confirmation code!')
# URL has the right confirmation code.
url = reverse('users.confirm', args=[self.user.id, 'code'])
r = self.client.get(url, follow=True)
self.assertContains(r, 'Successfully verified!')
def test_legacy_confirm(self):
# User doesn't have a valid confirmation code.
url = reverse('users.confirm', args=[self.user.id, 'blah'])
r = self.client.get(url, follow=True)
# Ensure we don't show django messages for registrations via Remora.
eq_(pq(r.content)('.notification-box').length, 0)
self.user_profile.update(confirmationcode='code')
url = reverse('users.confirm', args=[self.user.id, 'code'])
r = self.client.get(url, follow=True)
eq_(pq(r.content)('.notification-box').length, 0)
def test_new_confirm_resend(self):
waffle.models.Switch.objects.create(name='zamboni-login', active=True)
# User doesn't have a confirmation code.
url = reverse('users.confirm.resend', args=[self.user.id])
r = self.client.get(url, follow=True)
self.user_profile.update(confirmationcode='code')
# URL has the right confirmation code now.
r = self.client.get(url, follow=True)
self.assertContains(r, 'An email has been sent to your address')
def test_legacy_confirm_resend(self):
self.user_profile.update(confirmationcode='code')
url = reverse('users.confirm.resend', args=[self.user.id])
r = self.client.get(url, follow=True)
# Ensure we don't show django messages for Remora.
eq_(pq(r.content)('.notification-box').length, 0)
class TestProfileLinks(UserViewBase):
fixtures = ['base/featured', 'users/test_backends']
def test_edit_buttons(self):
"""Ensure admin/user edit buttons are shown."""
def get_links(id):
"""Grab profile, return edit links."""
url = reverse('users.profile', args=[id])
r = self.client.get(url)
return pq(r.content)('#profile-actions a')
# Anonymous user.
links = get_links(self.user.id)
eq_(links.length, 1)
eq_(links.eq(0).attr('href'), reverse('users.abuse',
args=[self.user.id]))
# Non-admin, someone else's profile.
self.client.login(username='jbalogh@mozilla.com', password='foo')
links = get_links(9945)
eq_(links.length, 1)
eq_(links.eq(0).attr('href'), reverse('users.abuse', args=[9945]))
# Non-admin, own profile.
links = get_links(self.user.id)
eq_(links.length, 1)
eq_(links.eq(0).attr('href'), reverse('users.edit'))
# Admin, someone else's profile.
admingroup = Group(rules='Admin:EditAnyUser')
admingroup.save()
GroupUser.objects.create(group=admingroup, user=self.user_profile)
cache.clear()
# Admin, own profile.
links = get_links(self.user.id)
eq_(links.length, 2)
eq_(links.eq(0).attr('href'), reverse('users.edit'))
# TODO XXX Uncomment when we have real user editing pages
#eq_(links.eq(1).attr('href') + "/",
#reverse('admin:users_userprofile_change', args=[self.user.id]))
def test_amouser(self):
# request.amo_user should be a special guy.
self.client.login(username='jbalogh@mozilla.com', password='foo')
response = self.client.get(reverse('home'))
request = response.context['request']
assert hasattr(request.amo_user, 'mobile_addons')
assert hasattr(request.user.get_profile(), 'mobile_addons')
assert hasattr(request.amo_user, 'favorite_addons')
assert hasattr(request.user.get_profile(), 'favorite_addons')
class TestProfileSections(amo.tests.TestCase):
fixtures = ['base/apps', 'base/users', 'base/addon_3615',
'base/addon_5299_gcal', 'base/collections',
'reviews/dev-reply.json']
def setUp(self):
self.user = UserProfile.objects.get(id=10482)
self.url = reverse('users.profile', args=[self.user.id])
def test_my_addons(self):
eq_(pq(self.client.get(self.url).content)('.num-addons a').length, 0)
AddonUser.objects.create(user=self.user, addon_id=3615)
AddonUser.objects.create(user=self.user, addon_id=5299)
r = self.client.get(self.url)
a = r.context['addons'].object_list
eq_(list(a), sorted(a, key=lambda x: x.weekly_downloads, reverse=True))
doc = pq(r.content)
eq_(doc('.num-addons a[href="#my-addons"]').length, 1)
items = doc('#my-addons .item')
eq_(items.length, 2)
eq_(items('.install[data-addon=3615]').length, 1)
eq_(items('.install[data-addon=5299]').length, 1)
def test_my_reviews(self):
r = Review.objects.filter(reply_to=None)[0]
r.user_id = self.user.id
r.save()
cache.clear()
eq_(list(self.user.reviews), [r])
r = self.client.get(self.url)
doc = pq(r.content)('#reviews')
assert not doc.hasClass('full'), (
'reviews should not have "full" class when there are collections')
eq_(doc('.item').length, 1)
eq_(doc('#review-218207').length, 1)
# Edit Review form should be present.
self.assertTemplateUsed(r, 'reviews/edit_review.html')
def test_my_reviews_delete_link(self):
review = Review.objects.filter(reply_to=None)[0]
review.user_id = 999
review.save()
cache.clear()
slug = Addon.objects.get(id=review.addon_id).slug
delete_url = reverse('addons.reviews.delete', args=[slug, review.pk])
# Admins get the Delete Review link.
self.client.login(username='admin@mozilla.com', password='password')
r = self.client.get(reverse('users.profile', args=[999]))
doc = pq(r.content)('#reviews')
r = doc('#review-218207 .item-actions a.delete-review')
eq_(r.length, 1)
eq_(r.attr('href'), delete_url)
# Editors get the Delete Review link.
self.client.login(username='editor@mozilla.com')
r = self.client.get(reverse('users.profile', args=[999]))
doc = pq(r.content)('#reviews')
r = doc('#review-218207 .item-actions a.delete-review')
eq_(r.length, 1)
eq_(r.attr('href'), delete_url)
# Author does not get Delete Review link.
self.client.login(username='regular@mozilla.com', password='password')
r = self.client.get(self.url)
doc = pq(r.content)('#reviews')
r = doc('#review-218207 .item-actions a.delete-review')
eq_(r.length, 0)
def test_my_reviews_no_pagination(self):
r = self.client.get(self.url)
assert len(self.user.addons_listed) <= 10, (
'This user should have fewer than 10 add-ons.')
eq_(pq(r.content)('#my-addons .paginator').length, 0)
def test_my_reviews_pagination(self):
for i in xrange(20):
AddonUser.objects.create(user=self.user, addon_id=3615)
assert len(self.user.addons_listed) > 10, (
'This user should have way more than 10 add-ons.')
r = self.client.get(self.url)
eq_(pq(r.content)('#my-addons .paginator').length, 1)
def test_my_collections_followed(self):
coll = Collection.objects.all()[0]
CollectionWatcher.objects.create(collection=coll, user=self.user)
mine = Collection.objects.listed().filter(following__user=self.user)
eq_(list(mine), [coll])
r = self.client.get(self.url)
self.assertTemplateUsed(r, 'bandwagon/users/collection_list.html')
eq_(list(r.context['fav_coll']), [coll])
doc = pq(r.content)
eq_(doc('#reviews.full').length, 0)
ul = doc('#my-collections #my-favorite')
eq_(ul.length, 1)
li = ul.find('li')
eq_(li.length, 1)
a = li.find('a')
eq_(a.attr('href'), coll.get_url_path())
eq_(a.text(), unicode(coll.name))
def test_my_collections_created(self):
coll = Collection.objects.listed().filter(author=self.user)
eq_(len(coll), 1)
r = self.client.get(self.url)
self.assertTemplateUsed(r, 'bandwagon/users/collection_list.html')
eq_(list(r.context['own_coll']), list(coll))
doc = pq(r.content)
eq_(doc('#reviews.full').length, 0)
ul = doc('#my-collections #my-created')
eq_(ul.length, 1)
li = ul.find('li')
eq_(li.length, 1)
a = li.find('a')
eq_(a.attr('href'), coll[0].get_url_path())
eq_(a.text(), unicode(coll[0].name))
def test_no_my_collections(self):
Collection.objects.filter(author=self.user).delete()
r = self.client.get(self.url)
self.assertTemplateNotUsed(r, 'bandwagon/users/collection_list.html')
doc = pq(r.content)
eq_(doc('#my-collections').length, 0)
eq_(doc('#reviews.full').length, 1)
def test_review_abuse_form(self):
r = self.client.get(self.url)
self.assertTemplateUsed(r, 'reviews/report_review.html')
def test_user_abuse_form(self):
abuse_url = reverse('users.abuse', args=[self.user.id])
r = self.client.get(self.url)
doc = pq(r.content)
button = doc('#profile-actions #report-user-abuse')
eq_(button.length, 1)
eq_(button.attr('href'), abuse_url)
modal = doc('#popup-staging #report-user-modal.modal')
eq_(modal.length, 1)
eq_(modal('form').attr('action'), abuse_url)
eq_(modal('textarea[name=text]').length, 1)
self.assertTemplateUsed(r, 'users/report_abuse.html')
def test_no_self_abuse(self):
self.client.login(username='clouserw@gmail.com', password='password')
r = self.client.get(self.url)
doc = pq(r.content)
eq_(doc('#profile-actions #report-user-abuse').length, 0)
eq_(doc('#popup-staging #report-user-modal.modal').length, 0)
self.assertTemplateNotUsed(r, 'users/report_abuse.html')
class TestReportAbuse(amo.tests.TestCase):
fixtures = ['base/users']
def setUp(self):
settings.RECAPTCHA_PRIVATE_KEY = 'something'
self.full_page = reverse('users.abuse', args=[10482])
@patch('captcha.fields.ReCaptchaField.clean')
def test_abuse_anonymous(self, clean):
clean.return_value = ""
self.client.post(self.full_page, {'text': 'spammy'})
eq_(len(mail.outbox), 1)
assert 'spammy' in mail.outbox[0].body
report = AbuseReport.objects.get(user=10482)
eq_(report.message, 'spammy')
eq_(report.reporter, None)
def test_abuse_anonymous_fails(self):
r = self.client.post(self.full_page, {'text': 'spammy'})
assert 'recaptcha' in r.context['abuse_form'].errors
def test_abuse_logged_in(self):
self.client.login(username='regular@mozilla.com', password='password')
self.client.post(self.full_page, {'text': 'spammy'})
eq_(len(mail.outbox), 1)
assert 'spammy' in mail.outbox[0].body
report = AbuseReport.objects.get(user=10482)
eq_(report.message, 'spammy')
eq_(report.reporter.email, 'regular@mozilla.com')
r = self.client.get(self.full_page)
eq_(pq(r.content)('.notification-box h2').length, 1)
class TestPurchases(amo.tests.TestCase):
fixtures = ['base/users']
def setUp(self):
waffle.models.Switch.objects.create(name='marketplace', active=True)
waffle.models.Flag.objects.create(name='allow-refund', everyone=True)
self.url = reverse('users.purchases')
self.client.login(username='regular@mozilla.com', password='password')
self.user = User.objects.get(email='regular@mozilla.com')
self.user_profile = self.user.get_profile()
self.addon, self.con = None, None
self.addons = []
for x in range(1, 5):
price = Price.objects.create(price=10 - x)
addon = Addon.objects.create(type=amo.ADDON_EXTENSION,
name='t%s' % x,
guid='t%s' % x)
AddonPremium.objects.create(price=price, addon=addon)
con = Contribution.objects.create(user=self.user_profile,
addon=addon, amount='%s.00' % x,
type=amo.CONTRIB_PURCHASE)
con.created = datetime(2011, 11, 1)
con.save()
if not self.addon and not self.con:
self.addon, self.con = addon, con
self.addons.append(addon)
def make_contribution(self, addon, amt, type, day, user=None):
c = Contribution.objects.create(user=user or self.user_profile,
addon=addon, amount=amt, type=type)
# This needs to be a date in the past for contribution sorting
# to work, so don't change this - or get scared by this.
c.created = datetime(2011, 11, day)
c.save()
return c
def test_in_menu(self):
doc = pq(self.client.get(self.url).content)
assert 'My Purchases' in doc('li.account li').text()
def test_in_sidebar(self):
# Populate this user's favorites.
c = Collection.objects.create(type=amo.COLLECTION_FAVORITES,
author=self.user_profile)
CollectionAddon.objects.create(addon=Addon.objects.all()[0],
collection=c)
expected = [
('My Profile', self.user_profile.get_url_path()),
('Account Settings', reverse('users.edit')),
('My Collections', reverse('collections.mine')),
('My Favorites', reverse('collections.mine', args=['favorites'])),
('My Purchases', self.url),
]
check_sidebar_links(self, expected)
@patch.object(settings, 'APP_PREVIEW', True)
def test_in_apps_sidebar(self):
expected = [
('My Profile', self.user_profile.get_url_path()),
('Account Settings', reverse('users.edit')),
('My Purchases', self.url),
]
check_sidebar_links(self, expected)
def test_not_purchase(self):
self.client.logout()
eq_(self.client.get(self.url).status_code, 302)
def test_no_purchases(self):
Contribution.objects.all().delete()
Installed.objects.all().delete()
res = self.client.get(self.url)
eq_(res.status_code, 200)
def test_purchase_list(self):
res = self.client.get(self.url)
eq_(res.status_code, 200)
eq_(len(res.context['addons'].object_list), 4)
@amo.tests.mobile_test
def test_mobile_purchase_list(self):
res = self.client.get(self.url)
eq_(res.status_code, 200)
eq_(len(res.context['addons'].object_list), 4)
self.assertTemplateUsed(res, 'users/mobile/purchases.html')
def test_purchase_date(self):
# Some date that's not the same as the contribution.
self.addon.update(created=datetime(2011, 10, 15))
res = self.client.get(self.url)
eq_(res.status_code, 200)
node = pq(res.content)('.purchase').eq(0).text()
ts = datetime_filter(self.con.created)
assert ts in node, '%s not found' % ts
def get_order(self, order):
res = self.client.get('%s?sort=%s' % (self.url, order))
return [str(c.name) for c in res.context['addons'].object_list]
def test_ordering(self):
eq_(self.get_order('name'), ['t1', 't2', 't3', 't4'])
eq_(self.get_order('price'), ['t4', 't3', 't2', 't1'])
def test_ordering_purchased(self):
# Create some free add-ons/apps to make sure those are also listed.
for x in xrange(1, 3):
addon = Addon.objects.create(type=amo.ADDON_EXTENSION,
name='f%s' % x, guid='f%s' % x)
Installed.objects.create(addon=addon, user=self.user_profile)
for addon in self.addons:
purchase = addon.addonpurchase_set.get(user=self.user_profile)
purchase.update(created=datetime.now() + timedelta(days=addon.id))
# Purchase an app on behalf of a different user, which shouldn't
# affect the ordering of my purchases.
clouserw = User.objects.get(email='clouserw@gmail.com').get_profile()
self.make_contribution(self.addons[2], '1.00', amo.CONTRIB_PURCHASE, 5,
user=clouserw)
self.addons[2].addonpurchase_set.get(user=clouserw).update(
created=datetime.now() + timedelta(days=999))
default = ['t4', 't3', 't2', 't1', 'f1', 'f2']
eq_(self.get_order(''), default)
eq_(self.get_order('purchased'), default)
Addon.objects.get(guid='t2').addonpurchase_set.all()[0].update(
created=datetime.now() + timedelta(days=999))
eq_(self.get_order('purchased'), ['t2', 't4', 't3', 't1', 'f1', 'f2'])
def get_pq(self):
r = self.client.get(self.url, dict(sort='name'))
eq_(r.status_code, 200)
return pq(r.content)('#purchases')
def _test_price(self):
assert '$1.00' in self.get_pq()('.purchase').eq(0).text()
def test_price(self):
self._test_price()
@amo.tests.mobile_test
def test_mobile_price(self):
self._test_price()
def _test_price_locale(self):
self.url = self.url.replace('/en-US', '/fr')
assert u'1,00' in self.get_pq()('.purchase').eq(0).text()
def test_price_locale(self):
self._test_price_locale()
@amo.tests.mobile_test
def test_mobile_price_locale(self):
self._test_price_locale()
def test_receipt(self):
res = self.client.get(reverse('users.purchases.receipt',
args=[self.addon.pk]))
eq_([a.pk for a in res.context['addons'].object_list], [self.addon.pk])
def test_receipt_404(self):
url = reverse('users.purchases.receipt', args=[545])
eq_(self.client.get(url).status_code, 404)
def test_receipt_view(self):
res = self.client.get(reverse('users.purchases.receipt',
args=[self.addon.pk]))
eq_(pq(res.content)('#sorter a').attr('href'),
reverse('users.purchases'))
@amo.tests.mobile_test
def test_mobile_receipt_view(self):
res = self.client.get(reverse('users.purchases.receipt',
args=[self.addon.pk]))
eq_(pq(res.content)('#sort-menu a').attr('href'),
reverse('users.purchases'))
def _test_purchases_attribute(self):
doc = pq(self.client.get(self.url).content)
ids = list(Addon.objects.values_list('pk', flat=True).order_by('pk'))
eq_(doc('body').attr('data-purchases'),
','.join([str(i) for i in ids]))
def test_purchases_attribute(self):
self._test_purchases_attribute()
@amo.tests.mobile_test
def test_mobile_purchases_attribute(self):
self._test_purchases_attribute()
def test_no_purchases_attribute(self):
self.user_profile.addonpurchase_set.all().delete()
doc = pq(self.client.get(self.url).content)
eq_(doc('body').attr('data-purchases'), '')
def _test_refund_link(self):
eq_(self.get_pq()('a.request-support').eq(0).attr('href'),
self.get_url())
def test_refund_link(self):
self._test_refund_link()
@amo.tests.mobile_test
def test_mobile_refund_link(self):
self._test_refund_link()
def get_url(self, *args):
return reverse('users.support', args=[self.con.pk] + list(args))
def test_support_not_logged_in(self):
self.client.logout()
eq_(self.client.get(self.get_url()).status_code, 302)
def test_support_not_mine(self):
self.client.login(username='admin@mozilla.com', password='password')
eq_(self.client.get(self.get_url()).status_code, 404)
def test_support_page(self):
doc = pq(self.client.get(self.get_url()).content)
eq_(doc('section.primary a').attr('href'), self.get_url('author'))
def test_support_page_other(self):
self.addon.support_url = 'http://cbc.ca'
self.addon.save()
doc = pq(self.client.get(self.get_url()).content)
eq_(doc('section.primary a').attr('href'), self.get_url('site'))
def test_support_site(self):
self.addon.support_url = 'http://cbc.ca'
self.addon.save()
doc = pq(self.client.get(self.get_url('site')).content)
eq_(doc('section.primary a').attr('href'), self.addon.support_url)
def test_contact(self):
data = {'text': 'Lorem ipsum dolor sit amet, consectetur'}
res = self.client.post(self.get_url('author'), data)
eq_(res.status_code, 302)
def test_contact_mails(self):
self.addon.support_email = 'a@a.com'
self.addon.save()
data = {'text': 'Lorem ipsum dolor sit amet, consectetur'}
self.client.post(self.get_url('author'), data)
eq_(len(mail.outbox), 1)
email = mail.outbox[0]
eq_(email.to, ['a@a.com'])
eq_(email.from_email, 'regular@mozilla.com')
def test_contact_fails(self):
res = self.client.post(self.get_url('author'), {'b': 'c'})
assert 'text' in res.context['form'].errors
def test_contact_mozilla(self):
data = {'text': 'Lorem ipsum dolor sit amet, consectetur'}
res = self.client.post(self.get_url('mozilla'), data)
eq_(res.status_code, 302)
def test_contact_mozilla_mails(self):
data = {'text': 'Lorem ipsum dolor sit amet, consectetur'}
self.client.post(self.get_url('mozilla'), data)
eq_(len(mail.outbox), 1)
email = mail.outbox[0]
eq_(email.to, [settings.MARKETPLACE_EMAIL])
eq_(email.from_email, 'regular@mozilla.com')
assert 'Lorem' in email.body
def test_contact_mozilla_fails(self):
res = self.client.post(self.get_url('mozilla'), {'b': 'c'})
assert 'text' in res.context['form'].errors
def test_refund_remove(self):
res = self.client.post(self.get_url('request'), {'remove': 1})
eq_(res.status_code, 302)
def test_refund_remove_fails(self):
res = self.client.post(self.get_url('request'), {})
eq_(res.status_code, 200)
def test_refund_webapp_no_form(self):
self.addon.update(type=amo.ADDON_WEBAPP)
res = self.client.get(self.get_url('request'), {})
assert 'remove' not in str(pq(res.content)('input')), (
'There should be no input asking to remove the app.')
def test_refund_webapp_passes(self):
self.addon.update(type=amo.ADDON_WEBAPP)
res = self.client.post(self.get_url('request'), {})
eq_(res.status_code, 302)
def test_skip_fails(self):
res = self.client.post(self.get_url('reason'), {})
self.assertRedirects(res, self.get_url('request'))
def test_request(self):
self.client.post(self.get_url('request'), {'remove': 1})
res = self.client.post(self.get_url('reason'), {'text': 'something'})
eq_(res.status_code, 302)
@patch('stats.models.Contribution.is_instant_refund')
def test_request_mails(self, is_instant_refund):
is_instant_refund.return_value = False
self.addon.support_email = 'a@a.com'
self.addon.save()
self.client.post(self.get_url('request'), {'remove': 1})
self.client.post(self.get_url('reason'), {'text': 'something'})
eq_(len(mail.outbox), 1)
email = mail.outbox[0]
eq_(email.to, ['a@a.com'])
eq_(email.from_email, 'nobody@mozilla.org')
assert '$1.00' in email.body
@patch('stats.models.Contribution.is_instant_refund')
def test_request_fails(self, is_instant_refund):
is_instant_refund.return_value = False
self.addon.support_email = 'a@a.com'
self.addon.save()
self.client.post(self.get_url('request'), {'remove': 1})
res = self.client.post(self.get_url('reason'), {})
eq_(res.status_code, 200)
@patch('stats.models.Contribution.is_instant_refund')
@patch('paypal.refund')
def test_request_instant(self, is_instant_refund, refund):
is_instant_refund.return_value = True
self.client.post(self.get_url('request'), {'remove': 1})
res = self.client.post(self.get_url('reason'), {})
assert refund.called
eq_(res.status_code, 302)
def test_free_shows_up(self):
Contribution.objects.all().delete()
res = self.client.get(self.url)
eq_(sorted(a.guid for a in res.context['addons']),
['t1', 't2', 't3', 't4'])
def test_others_free_dont(self):
Contribution.objects.all().delete()
other = UserProfile.objects.get(pk=10482)
Installed.objects.all()[0].update(user=other)
res = self.client.get(self.url)
eq_(len(res.context['addons']), 3)
def test_purchase_multiple(self):
Contribution.objects.create(user=self.user_profile,
addon=self.addon, amount='1.00',
type=amo.CONTRIB_PURCHASE)
eq_(self.get_pq()('.vitals').eq(0)('.purchase').length, 2)
def _test_refunded(self):
self.make_contribution(Addon.objects.get(guid='t1'), '-1.00',
amo.CONTRIB_REFUND, 2)
item = self.get_pq()('.item').eq(0)
assert item.hasClass('refunded'), (
"Expected '.item' to have 'refunded' class")
assert item.find('.refund-notice'), 'Expected refund message'
def test_refunded(self):
self._test_refunded()
@amo.tests.mobile_test
def test_mobile_refunded(self):
self._test_refunded()
def _test_repurchased(self):
addon = Addon.objects.get(guid='t1')
c = [
self.make_contribution(addon, '-1.00', amo.CONTRIB_REFUND, 2),
self.make_contribution(addon, '1.00', amo.CONTRIB_PURCHASE, 3)
]
item = self.get_pq()('.item').eq(0)
assert not item.hasClass('reversed'), (
"Unexpected 'refunded' class on '.item'")
assert not item.find('.refund-notice'), 'Unexpected refund message'
purchases = item.find('.contributions')
eq_(purchases.find('.request-support').length, 1)
eq_(purchases.find('li').eq(2).find('.request-support').attr('href'),
reverse('users.support', args=[c[1].id]))
def test_repurchased(self):
self._test_repurchased()
@amo.tests.mobile_test
def test_mobile_repurchased(self):
self._test_repurchased()
def _test_rerefunded(self):
addon = Addon.objects.get(guid='t1')
self.make_contribution(addon, '-1.00', amo.CONTRIB_REFUND, 2)
self.make_contribution(addon, '1.00', amo.CONTRIB_PURCHASE, 3)
self.make_contribution(addon, '-1.00', amo.CONTRIB_REFUND, 4)
item = self.get_pq()('.item').eq(0)
assert item.hasClass('refunded'), (
"Unexpected 'refunded' class on '.item'")
assert item.find('.refund-notice'), 'Expected refund message'
assert not item.find('a.request-support'), (
"Unexpected 'Request Support' link")
def test_rerefunded(self):
self._test_rerefunded()
@amo.tests.mobile_test
def test_mobile_rerefunded(self):
self._test_rerefunded()
def _test_chargeback(self):
addon = Addon.objects.get(guid='t1')
self.make_contribution(addon, '-1.00', amo.CONTRIB_CHARGEBACK, 2)
item = self.get_pq()('.item').eq(0)
assert item.hasClass('reversed'), (
"Expected '.item' to have 'reversed' class")
assert not item.find('a.request-support'), (
"Unexpected 'Request Support' link")
def test_chargeback(self):
self._test_chargeback()
@amo.tests.mobile_test
def test_mobile_chargeback(self):
self._test_chargeback()
class TestPreapproval(amo.tests.TestCase):
fixtures = ['base/users.json']
def setUp(self):
waffle.models.Flag.objects.create(name='allow-pre-auth',
everyone=True)
self.user = UserProfile.objects.get(pk=999)
assert self.client.login(username=self.user.email,
password='password')
def get_url(self, status=None):
if status:
return reverse('users.payments', args=[status])
return reverse('users.payments')
def test_preapproval_denied(self):
self.client.logout()
eq_(self.client.get(self.get_url()).status_code, 302)
def test_preapproval_allowed(self):
eq_(self.client.get(self.get_url()).status_code, 200)
def test_preapproval_setup(self):
doc = pq(self.client.get(self.get_url()).content)
eq_(doc('#preapproval').attr('action'),
reverse('users.payments.preapproval'))
@patch.object(settings, 'APP_PREVIEW', True)
def test_sidebar(self):
waffle.models.Switch.objects.create(name='marketplace', active=True)
self.url = self.get_url()
expected = [
('My Profile', self.user.get_url_path()),
('Account Settings', reverse('users.edit')),
('My Purchases', reverse('users.purchases')),
('Payment Profile', self.url),
]
check_sidebar_links(self, expected)
@patch.object(settings, 'APP_PREVIEW', True)
def test_sidebar_complete(self):
r = self.client.get(self.get_url('complete'))
eq_(r.status_code, 200)
eq_(pq(r.content)('#secondary-nav .selected').attr('href'),
self.get_url())
@patch('paypal.get_preapproval_key')
def test_fake_preapproval(self, get_preapproval_key):
get_preapproval_key.return_value = {'preapprovalKey': 'xyz'}
res = self.client.post(reverse('users.payments.preapproval'))
ssn = self.client.session['setup-preapproval']
eq_(ssn['key'], 'xyz')
# Checking it's in the future at least 353 just so this test will work
# on leap years at 11:59pm.
assert (ssn['expiry'] - datetime.today()).days > 353
eq_(res['Location'], paypal.get_preapproval_url('xyz'))
def test_preapproval_complete(self):
ssn = self.client.session
ssn['setup-preapproval'] = {'key': 'xyz'}
ssn.save()
res = self.client.post(self.get_url('complete'))
eq_(res.status_code, 200)
eq_(self.user.preapprovaluser.paypal_key, 'xyz')
# Check that re-loading doesn't error.
res = self.client.post(self.get_url('complete'))
eq_(res.status_code, 200)
def test_preapproval_cancel(self):
PreApprovalUser.objects.create(user=self.user, paypal_key='xyz')
res = self.client.post(self.get_url('cancel'))
eq_(res.status_code, 200)
eq_(self.user.preapprovaluser.paypal_key, 'xyz')
eq_(pq(res.content)('#preapproval').attr('action'),
self.get_url('remove'))
def test_preapproval_remove(self):
PreApprovalUser.objects.create(user=self.user, paypal_key='xyz')
res = self.client.post(self.get_url('remove'))
eq_(res.status_code, 200)
eq_(self.user.preapprovaluser.paypal_key, '')
eq_(pq(res.content)('#preapproval').attr('action'),
reverse('users.payments.preapproval'))