Move RegistrationView from user_api to user_authn.
This commit is contained in:
committed by
Nimisha Asthagiri
parent
5bf2a25842
commit
e026006f9a
@@ -13,6 +13,7 @@ from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from student.models import (
|
||||
_get_all_retired_emails_by_email,
|
||||
_get_all_retired_usernames_by_username,
|
||||
@@ -258,6 +259,7 @@ def test_get_potentially_retired_user_bad_hash():
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
class TestRegisterRetiredUsername(TestCase):
|
||||
"""
|
||||
Tests to ensure that retired usernames can no longer be used in registering new accounts.
|
||||
|
||||
@@ -21,9 +21,9 @@ from social_django import utils as social_utils
|
||||
from social_django import views as social_views
|
||||
|
||||
from lms.djangoapps.commerce.tests import TEST_API_URL
|
||||
from openedx.core.djangoapps.user_api.views import RegistrationView
|
||||
from openedx.core.djangoapps.user_authn.views.login import login_user
|
||||
from openedx.core.djangoapps.user_authn.views.login_form import login_and_registration_form
|
||||
from openedx.core.djangoapps.user_authn.views.register import RegistrationView
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from openedx.core.djangoapps.user_api.accounts.settings_views import account_settings_context
|
||||
from student import models as student_models
|
||||
|
||||
@@ -40,8 +40,6 @@ urlpatterns = [
|
||||
urlpatterns += [
|
||||
url(r'^v1/account/login_session/$', user_api_views.LoginSessionView.as_view(),
|
||||
name="user_api_login_session"),
|
||||
url(r'^v1/account/registration/$', user_api_views.RegistrationView.as_view(),
|
||||
name="user_api_registration"),
|
||||
url(r'^v1/account/password_reset/$', user_api_views.PasswordResetView.as_view(),
|
||||
name="user_api_password_reset"),
|
||||
]
|
||||
|
||||
@@ -57,7 +57,7 @@ from ..accounts.api import get_account_settings
|
||||
from ..accounts.tests.retirement_helpers import ( # pylint: disable=unused-import
|
||||
RetirementTestCase,
|
||||
fake_requested_retirement,
|
||||
setup_retirement_states
|
||||
setup_retirement_states,
|
||||
)
|
||||
from ..models import UserOrgTag
|
||||
from ..tests.factories import UserPreferenceFactory
|
||||
@@ -774,1841 +774,6 @@ class PasswordResetViewTest(UserAPITestCase):
|
||||
])
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCase, RetirementTestCase):
|
||||
"""
|
||||
Tests for catching duplicate email and username validation errors within
|
||||
the registration end-points of the User API.
|
||||
"""
|
||||
|
||||
maxDiff = None
|
||||
|
||||
USERNAME = "bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "password"
|
||||
NAME = "Bob Smith"
|
||||
EDUCATION = "m"
|
||||
YEAR_OF_BIRTH = "1998"
|
||||
ADDRESS = "123 Fake Street"
|
||||
CITY = "Springfield"
|
||||
COUNTRY = "us"
|
||||
GOALS = "Learn all the things!"
|
||||
|
||||
def setUp(self):
|
||||
super(RegistrationViewValidationErrorTest, self).setUp()
|
||||
self.url = reverse("user_api_registration")
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_api.views.check_account_exists')
|
||||
def test_register_retired_email_validation_error(self, dummy_check_account_exists):
|
||||
dummy_check_account_exists.return_value = []
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Initiate retirement for the above user:
|
||||
fake_requested_retirement(User.objects.get(username=self.USERNAME))
|
||||
|
||||
# Try to create a second user with the same email address as the retired user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": "someone_else",
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_retired_email_validation_error_no_bypass_check_account_exists(self):
|
||||
"""
|
||||
This test is the same as above, except it doesn't bypass check_account_exists. Not bypassing this function
|
||||
results in the same error message, but a 409 status code rather than 400.
|
||||
"""
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Initiate retirement for the above user:
|
||||
fake_requested_retirement(User.objects.get(username=self.USERNAME))
|
||||
|
||||
# Try to create a second user with the same email address as the retired user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": "someone_else",
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_duplicate_retired_username_account_validation_error(self):
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Initiate retirement for the above user.
|
||||
fake_requested_retirement(User.objects.get(username=self.USERNAME))
|
||||
|
||||
with mock.patch('openedx.core.djangoapps.user_authn.views.register.do_create_account') as dummy_do_create_acct:
|
||||
# do_create_account should *not* be called - the duplicate retired username
|
||||
# should be detected by check_account_exists before account creation is called.
|
||||
dummy_do_create_acct.side_effect = Exception('do_create_account should *not* have been called!')
|
||||
# Try to create a second user with the same username.
|
||||
response = self.client.post(self.url, {
|
||||
"email": "someone+else@example.com",
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different username."
|
||||
).format(
|
||||
self.USERNAME
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_api.views.check_account_exists')
|
||||
def test_register_duplicate_email_validation_error(self, dummy_check_account_exists):
|
||||
dummy_check_account_exists.return_value = []
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same email address
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": "someone_else",
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_api.views.check_account_exists')
|
||||
def test_register_duplicate_username_account_validation_error(self, dummy_check_account_exists):
|
||||
dummy_check_account_exists.return_value = []
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same username
|
||||
response = self.client.post(self.url, {
|
||||
"email": "someone+else@example.com",
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
u"success": False,
|
||||
u"username": [{
|
||||
u"user_message": (
|
||||
u"An account with the Public Username '{}' already exists."
|
||||
).format(
|
||||
self.USERNAME
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_api.views.check_account_exists')
|
||||
def test_register_duplicate_username_and_email_validation_errors(self, dummy_check_account_exists):
|
||||
dummy_check_account_exists.return_value = []
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same username
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
|
||||
"""Tests for the registration end-points of the User API. """
|
||||
|
||||
maxDiff = None
|
||||
|
||||
USERNAME = "bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "password"
|
||||
NAME = "Bob Smith"
|
||||
EDUCATION = "m"
|
||||
YEAR_OF_BIRTH = "1998"
|
||||
ADDRESS = "123 Fake Street"
|
||||
CITY = "Springfield"
|
||||
COUNTRY = "us"
|
||||
GOALS = "Learn all the things!"
|
||||
PROFESSION_OPTIONS = [
|
||||
{
|
||||
"name": u'--',
|
||||
"value": u'',
|
||||
"default": True
|
||||
|
||||
},
|
||||
{
|
||||
"value": u'software engineer',
|
||||
"name": u'Software Engineer',
|
||||
"default": False
|
||||
},
|
||||
{
|
||||
"value": u'teacher',
|
||||
"name": u'Teacher',
|
||||
"default": False
|
||||
},
|
||||
{
|
||||
"value": u'other',
|
||||
"name": u'Other',
|
||||
"default": False
|
||||
}
|
||||
]
|
||||
SPECIALTY_OPTIONS = [
|
||||
{
|
||||
"name": u'--',
|
||||
"value": u'',
|
||||
"default": True
|
||||
|
||||
},
|
||||
{
|
||||
"value": "aerospace",
|
||||
"name": "Aerospace",
|
||||
"default": False
|
||||
},
|
||||
{
|
||||
"value": u'early education',
|
||||
"name": u'Early Education',
|
||||
"default": False
|
||||
},
|
||||
{
|
||||
"value": u'n/a',
|
||||
"name": u'N/A',
|
||||
"default": False
|
||||
}
|
||||
]
|
||||
link_template = u"<a href='/honor' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
|
||||
def setUp(self):
|
||||
super(RegistrationViewTest, self).setUp()
|
||||
self.url = reverse("user_api_registration")
|
||||
|
||||
@ddt.data("get", "post")
|
||||
def test_auth_disabled(self, method):
|
||||
self.assertAuthDisabled(method, self.url)
|
||||
|
||||
def test_allowed_methods(self):
|
||||
self.assertAllowedMethods(self.url, ["GET", "POST", "HEAD", "OPTIONS"])
|
||||
|
||||
def test_put_not_allowed(self):
|
||||
response = self.client.put(self.url)
|
||||
self.assertHttpMethodNotAllowed(response)
|
||||
|
||||
def test_delete_not_allowed(self):
|
||||
response = self.client.delete(self.url)
|
||||
self.assertHttpMethodNotAllowed(response)
|
||||
|
||||
def test_patch_not_allowed(self):
|
||||
response = self.client.patch(self.url)
|
||||
self.assertHttpMethodNotAllowed(response)
|
||||
|
||||
def test_register_form_default_fields(self):
|
||||
no_extra_fields_setting = {}
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"email",
|
||||
u"type": u"email",
|
||||
u"required": True,
|
||||
u"label": u"Email",
|
||||
u"instructions": u"This is what you will use to login.",
|
||||
u"restrictions": {
|
||||
"min_length": EMAIL_MIN_LENGTH,
|
||||
"max_length": EMAIL_MAX_LENGTH
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"name",
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Full Name",
|
||||
u"instructions": u"This name will be used on any certificates that you earn.",
|
||||
u"restrictions": {
|
||||
"max_length": 255
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"username",
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Public Username",
|
||||
u"instructions": u"The name that will identify you in your courses. "
|
||||
u"It cannot be changed later.",
|
||||
u"restrictions": {
|
||||
"min_length": USERNAME_MIN_LENGTH,
|
||||
"max_length": USERNAME_MAX_LENGTH
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"placeholder": "",
|
||||
u"name": u"password",
|
||||
u"type": u"password",
|
||||
u"required": True,
|
||||
u"label": u"Password",
|
||||
u"instructions": password_validators_instruction_texts(),
|
||||
u"restrictions": password_validators_restrictions(),
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 2}),
|
||||
create_validator_config('util.password_policy_validators.UppercaseValidator', {'min_upper': 3}),
|
||||
create_validator_config('util.password_policy_validators.SymbolValidator', {'min_symbol': 1}),
|
||||
])
|
||||
def test_register_form_password_complexity(self):
|
||||
no_extra_fields_setting = {}
|
||||
|
||||
# Without enabling password policy
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u'name': u'password',
|
||||
u'label': u'Password',
|
||||
u"instructions": password_validators_instruction_texts(),
|
||||
u"restrictions": password_validators_restrictions(),
|
||||
}
|
||||
)
|
||||
|
||||
msg = u'Your password must contain at least 2 characters, including '\
|
||||
u'3 uppercase letters & 1 symbol.'
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u'name': u'password',
|
||||
u'label': u'Password',
|
||||
u'instructions': msg,
|
||||
u"restrictions": password_validators_restrictions(),
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(REGISTRATION_EXTENSION_FORM='openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm')
|
||||
def test_extension_form_fields(self):
|
||||
no_extra_fields_setting = {}
|
||||
|
||||
# Verify other fields didn't disappear for some reason.
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"email",
|
||||
u"type": u"email",
|
||||
u"required": True,
|
||||
u"label": u"Email",
|
||||
u"instructions": u"This is what you will use to login.",
|
||||
u"restrictions": {
|
||||
"min_length": EMAIL_MIN_LENGTH,
|
||||
"max_length": EMAIL_MAX_LENGTH
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"favorite_editor",
|
||||
u"type": u"select",
|
||||
u"required": False,
|
||||
u"label": u"Favorite Editor",
|
||||
u"placeholder": u"cat",
|
||||
u"defaultValue": u"vim",
|
||||
u"errorMessages": {
|
||||
u'required': u'This field is required.',
|
||||
u'invalid_choice': u'Select a valid choice. %(value)s is not one of the available choices.',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"favorite_movie",
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Fav Flick",
|
||||
u"placeholder": None,
|
||||
u"defaultValue": None,
|
||||
u"errorMessages": {
|
||||
u'required': u'Please tell us your favorite movie.',
|
||||
u'invalid': u"We're pretty sure you made that movie up."
|
||||
},
|
||||
u"restrictions": {
|
||||
"min_length": TestCaseForm.MOVIE_MIN_LEN,
|
||||
"max_length": TestCaseForm.MOVIE_MAX_LEN,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
('pk', 'PK', 'Bob123', 'Bob123'),
|
||||
('Pk', 'PK', None, ''),
|
||||
('pK', 'PK', 'Bob123@edx.org', 'Bob123_edx_org'),
|
||||
('PK', 'PK', 'Bob123123123123123123123123123123123123', 'Bob123123123123123123123123123'),
|
||||
('us', 'US', 'Bob-1231231&23123+1231(2312312312@3123123123', 'Bob-1231231_23123_1231_2312312'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_register_form_third_party_auth_running_google(
|
||||
self,
|
||||
input_country_code,
|
||||
expected_country_code,
|
||||
input_username,
|
||||
expected_username):
|
||||
no_extra_fields_setting = {}
|
||||
country_options = (
|
||||
[
|
||||
{
|
||||
"name": "--",
|
||||
"value": "",
|
||||
"default": False
|
||||
}
|
||||
] + [
|
||||
{
|
||||
"value": country_code,
|
||||
"name": six.text_type(country_name),
|
||||
"default": True if country_code == expected_country_code else False
|
||||
}
|
||||
for country_code, country_name in SORTED_COUNTRIES
|
||||
]
|
||||
)
|
||||
|
||||
provider = self.configure_google_provider(enabled=True)
|
||||
with simulate_running_pipeline(
|
||||
"openedx.core.djangoapps.user_api.api.third_party_auth.pipeline", "google-oauth2",
|
||||
email="bob@example.com",
|
||||
fullname="Bob",
|
||||
username=input_username,
|
||||
country=input_country_code
|
||||
):
|
||||
self._assert_password_field_hidden(no_extra_fields_setting)
|
||||
self._assert_social_auth_provider_present(no_extra_fields_setting, provider)
|
||||
|
||||
# Email should be filled in
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"email",
|
||||
u"defaultValue": u"bob@example.com",
|
||||
u"type": u"email",
|
||||
u"required": True,
|
||||
u"label": u"Email",
|
||||
u"instructions": u"This is what you will use to login.",
|
||||
u"restrictions": {
|
||||
"min_length": EMAIL_MIN_LENGTH,
|
||||
"max_length": EMAIL_MAX_LENGTH
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Full Name should be filled in
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"name",
|
||||
u"defaultValue": u"Bob",
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Full Name",
|
||||
u"instructions": u"This name will be used on any certificates that you earn.",
|
||||
u"restrictions": {
|
||||
"max_length": NAME_MAX_LENGTH,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Username should be filled in
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"username",
|
||||
u"defaultValue": expected_username,
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Public Username",
|
||||
u"instructions": u"The name that will identify you in your courses. "
|
||||
u"It cannot be changed later.",
|
||||
u"restrictions": {
|
||||
"min_length": USERNAME_MIN_LENGTH,
|
||||
"max_length": USERNAME_MAX_LENGTH
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Country should be filled in.
|
||||
self._assert_reg_field(
|
||||
{u"country": u"required"},
|
||||
{
|
||||
u"label": u"Country or Region of Residence",
|
||||
u"name": u"country",
|
||||
u"defaultValue": expected_country_code,
|
||||
u"type": u"select",
|
||||
u"required": True,
|
||||
u"options": country_options,
|
||||
u"instructions": u"The country or region where you live.",
|
||||
u"errorMessages": {
|
||||
u"required": u"Select your country or region of residence."
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_level_of_education(self):
|
||||
self._assert_reg_field(
|
||||
{"level_of_education": "optional"},
|
||||
{
|
||||
"name": "level_of_education",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Highest level of education completed",
|
||||
"options": [
|
||||
{"value": "", "name": "--", "default": True},
|
||||
{"value": "p", "name": "Doctorate", "default": False},
|
||||
{"value": "m", "name": "Master's or professional degree", "default": False},
|
||||
{"value": "b", "name": "Bachelor's degree", "default": False},
|
||||
{"value": "a", "name": "Associate degree", "default": False},
|
||||
{"value": "hs", "name": "Secondary/high school", "default": False},
|
||||
{"value": "jhs", "name": "Junior secondary/junior high/middle school", "default": False},
|
||||
{"value": "el", "name": "Elementary/primary school", "default": False},
|
||||
{"value": "none", "name": "No formal education", "default": False},
|
||||
{"value": "other", "name": "Other education", "default": False},
|
||||
],
|
||||
"errorMessages": {
|
||||
"required": "Select the highest level of education you have completed."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_api.api._')
|
||||
def test_register_form_level_of_education_translations(self, fake_gettext):
|
||||
fake_gettext.side_effect = lambda text: text + ' TRANSLATED'
|
||||
|
||||
self._assert_reg_field(
|
||||
{"level_of_education": "optional"},
|
||||
{
|
||||
"name": "level_of_education",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Highest level of education completed TRANSLATED",
|
||||
"options": [
|
||||
{"value": "", "name": "--", "default": True},
|
||||
{"value": "p", "name": "Doctorate TRANSLATED", "default": False},
|
||||
{"value": "m", "name": "Master's or professional degree TRANSLATED", "default": False},
|
||||
{"value": "b", "name": "Bachelor's degree TRANSLATED", "default": False},
|
||||
{"value": "a", "name": "Associate degree TRANSLATED", "default": False},
|
||||
{"value": "hs", "name": "Secondary/high school TRANSLATED", "default": False},
|
||||
{"value": "jhs", "name": "Junior secondary/junior high/middle school TRANSLATED", "default": False},
|
||||
{"value": "el", "name": "Elementary/primary school TRANSLATED", "default": False},
|
||||
{"value": "none", "name": "No formal education TRANSLATED", "default": False},
|
||||
{"value": "other", "name": "Other education TRANSLATED", "default": False},
|
||||
],
|
||||
"errorMessages": {
|
||||
"required": "Select the highest level of education you have completed."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_gender(self):
|
||||
self._assert_reg_field(
|
||||
{"gender": "optional"},
|
||||
{
|
||||
"name": "gender",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Gender",
|
||||
"options": [
|
||||
{"value": "", "name": "--", "default": True},
|
||||
{"value": "m", "name": "Male", "default": False},
|
||||
{"value": "f", "name": "Female", "default": False},
|
||||
{"value": "o", "name": "Other/Prefer Not to Say", "default": False},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_api.api._')
|
||||
def test_register_form_gender_translations(self, fake_gettext):
|
||||
fake_gettext.side_effect = lambda text: text + ' TRANSLATED'
|
||||
|
||||
self._assert_reg_field(
|
||||
{"gender": "optional"},
|
||||
{
|
||||
"name": "gender",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Gender TRANSLATED",
|
||||
"options": [
|
||||
{"value": "", "name": "--", "default": True},
|
||||
{"value": "m", "name": "Male TRANSLATED", "default": False},
|
||||
{"value": "f", "name": "Female TRANSLATED", "default": False},
|
||||
{"value": "o", "name": "Other/Prefer Not to Say TRANSLATED", "default": False},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_year_of_birth(self):
|
||||
this_year = datetime.datetime.now(UTC).year
|
||||
year_options = (
|
||||
[
|
||||
{
|
||||
"value": "",
|
||||
"name": "--",
|
||||
"default": True
|
||||
}
|
||||
] + [
|
||||
{
|
||||
"value": six.text_type(year),
|
||||
"name": six.text_type(year),
|
||||
"default": False
|
||||
}
|
||||
for year in range(this_year, this_year - 120, -1)
|
||||
]
|
||||
)
|
||||
self._assert_reg_field(
|
||||
{"year_of_birth": "optional"},
|
||||
{
|
||||
"name": "year_of_birth",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Year of birth",
|
||||
"options": year_options,
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_profession_without_profession_options(self):
|
||||
self._assert_reg_field(
|
||||
{"profession": "required"},
|
||||
{
|
||||
"name": "profession",
|
||||
"type": "text",
|
||||
"required": True,
|
||||
"label": "Profession",
|
||||
"errorMessages": {
|
||||
"required": "Enter your profession."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@with_site_configuration(configuration={"EXTRA_FIELD_OPTIONS": {"profession": ["Software Engineer", "Teacher", "Other"]}})
|
||||
def test_register_form_profession_with_profession_options(self):
|
||||
self._assert_reg_field(
|
||||
{"profession": "required"},
|
||||
{
|
||||
"name": "profession",
|
||||
"type": "select",
|
||||
"required": True,
|
||||
"label": "Profession",
|
||||
"options": self.PROFESSION_OPTIONS,
|
||||
"errorMessages": {
|
||||
"required": "Select your profession."
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_specialty_without_specialty_options(self):
|
||||
self._assert_reg_field(
|
||||
{"specialty": "required"},
|
||||
{
|
||||
"name": "specialty",
|
||||
"type": "text",
|
||||
"required": True,
|
||||
"label": "Specialty",
|
||||
"errorMessages": {
|
||||
"required": "Enter your specialty."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@with_site_configuration(configuration={"EXTRA_FIELD_OPTIONS": {"specialty": ["Aerospace", "Early Education", "N/A"]}})
|
||||
def test_register_form_specialty_with_specialty_options(self):
|
||||
self._assert_reg_field(
|
||||
{"specialty": "required"},
|
||||
{
|
||||
"name": "specialty",
|
||||
"type": "select",
|
||||
"required": True,
|
||||
"label": "Specialty",
|
||||
"options": self.SPECIALTY_OPTIONS,
|
||||
"errorMessages": {
|
||||
"required": "Select your specialty."
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_mailing_address(self):
|
||||
self._assert_reg_field(
|
||||
{"mailing_address": "optional"},
|
||||
{
|
||||
"name": "mailing_address",
|
||||
"type": "textarea",
|
||||
"required": False,
|
||||
"label": "Mailing address",
|
||||
"errorMessages": {
|
||||
"required": "Enter your mailing address."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_goals(self):
|
||||
self._assert_reg_field(
|
||||
{"goals": "optional"},
|
||||
{
|
||||
"name": "goals",
|
||||
"type": "textarea",
|
||||
"required": False,
|
||||
"label": u"Tell us why you're interested in {platform_name}".format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
),
|
||||
"errorMessages": {
|
||||
"required": "Tell us your goals."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_city(self):
|
||||
self._assert_reg_field(
|
||||
{"city": "optional"},
|
||||
{
|
||||
"name": "city",
|
||||
"type": "text",
|
||||
"required": False,
|
||||
"label": "City",
|
||||
"errorMessages": {
|
||||
"required": "Enter your city."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_state(self):
|
||||
self._assert_reg_field(
|
||||
{"state": "optional"},
|
||||
{
|
||||
"name": "state",
|
||||
"type": "text",
|
||||
"required": False,
|
||||
"label": "State/Province/Region",
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_country(self):
|
||||
country_options = (
|
||||
[
|
||||
{
|
||||
"name": "--",
|
||||
"value": "",
|
||||
"default": True
|
||||
}
|
||||
] + [
|
||||
{
|
||||
"value": country_code,
|
||||
"name": six.text_type(country_name),
|
||||
"default": False
|
||||
}
|
||||
for country_code, country_name in SORTED_COUNTRIES
|
||||
]
|
||||
)
|
||||
self._assert_reg_field(
|
||||
{"country": "required"},
|
||||
{
|
||||
"label": "Country or Region of Residence",
|
||||
"name": "country",
|
||||
"type": "select",
|
||||
"instructions": "The country or region where you live.",
|
||||
"required": True,
|
||||
"options": country_options,
|
||||
"errorMessages": {
|
||||
"required": "Select your country or region of residence."
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_confirm_email(self):
|
||||
self._assert_reg_field(
|
||||
{"confirm_email": "required"},
|
||||
{
|
||||
"name": "confirm_email",
|
||||
"type": "text",
|
||||
"required": True,
|
||||
"label": "Confirm Email",
|
||||
"errorMessages": {
|
||||
"required": "The email addresses do not match.",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
MKTG_URLS={"ROOT": "https://www.test.com/", "HONOR": "honor"},
|
||||
)
|
||||
@mock.patch.dict(settings.FEATURES, {"ENABLE_MKTG_SITE": True})
|
||||
def test_registration_honor_code_mktg_site_enabled(self):
|
||||
link_template = "<a href='https://www.test.com/honor' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
link_template2 = u"<a href='#' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
link_label = "Terms of Service and Honor Code"
|
||||
link_label2 = "Privacy Policy"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required"},
|
||||
{
|
||||
"label": (u"By creating an account, you agree to the {spacing}"
|
||||
u"{link_label} {spacing}"
|
||||
u"and you acknowledge that {platform_name} and each Member process your "
|
||||
u"personal data in accordance {spacing}"
|
||||
u"with the {link_label2}.").format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_template.format(link_label=link_label),
|
||||
link_label2=link_template2.format(link_label=link_label2),
|
||||
spacing=' ' * 18
|
||||
),
|
||||
"name": "honor_code",
|
||||
"defaultValue": False,
|
||||
"type": "plaintext",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_label
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(MKTG_URLS_LINK_MAP={"HONOR": "honor"})
|
||||
@mock.patch.dict(settings.FEATURES, {"ENABLE_MKTG_SITE": False})
|
||||
def test_registration_honor_code_mktg_site_disabled(self):
|
||||
link_template = "<a href='/privacy' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
link_label = "Terms of Service and Honor Code"
|
||||
link_label2 = "Privacy Policy"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required"},
|
||||
{
|
||||
"label": (u"By creating an account, you agree to the {spacing}"
|
||||
u"{link_label} {spacing}"
|
||||
u"and you acknowledge that {platform_name} and each Member process your "
|
||||
u"personal data in accordance {spacing}"
|
||||
u"with the {link_label2}.").format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=self.link_template.format(link_label=link_label),
|
||||
link_label2=link_template.format(link_label=link_label2),
|
||||
spacing=' ' * 18
|
||||
),
|
||||
"name": "honor_code",
|
||||
"defaultValue": False,
|
||||
"type": "plaintext",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_label
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(MKTG_URLS={
|
||||
"ROOT": "https://www.test.com/",
|
||||
"HONOR": "honor",
|
||||
"TOS": "tos",
|
||||
})
|
||||
@mock.patch.dict(settings.FEATURES, {"ENABLE_MKTG_SITE": True})
|
||||
def test_registration_separate_terms_of_service_mktg_site_enabled(self):
|
||||
# Honor code field should say ONLY honor code,
|
||||
# not "terms of service and honor code"
|
||||
link_label = 'Honor Code'
|
||||
link_template = u"<a href='https://www.test.com/honor' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required", "terms_of_service": "required"},
|
||||
{
|
||||
"label": u"I agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_template.format(link_label=link_label)
|
||||
),
|
||||
"name": "honor_code",
|
||||
"defaultValue": False,
|
||||
"type": "checkbox",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_label
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Terms of service field should also be present
|
||||
link_label = "Terms of Service"
|
||||
link_template = u"<a href='https://www.test.com/tos' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required", "terms_of_service": "required"},
|
||||
{
|
||||
"label": u"I agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_template.format(link_label=link_label)
|
||||
),
|
||||
"name": "terms_of_service",
|
||||
"defaultValue": False,
|
||||
"type": "checkbox",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_label
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(MKTG_URLS_LINK_MAP={"HONOR": "honor", "TOS": "tos"})
|
||||
@mock.patch.dict(settings.FEATURES, {"ENABLE_MKTG_SITE": False})
|
||||
def test_registration_separate_terms_of_service_mktg_site_disabled(self):
|
||||
# Honor code field should say ONLY honor code,
|
||||
# not "terms of service and honor code"
|
||||
link_label = 'Honor Code'
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required", "terms_of_service": "required"},
|
||||
{
|
||||
"label": u"I agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=self.link_template.format(link_label=link_label)
|
||||
),
|
||||
"name": "honor_code",
|
||||
"defaultValue": False,
|
||||
"type": "checkbox",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} Honor Code".format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
link_label = 'Terms of Service'
|
||||
# Terms of service field should also be present
|
||||
link_template = u"<a href='/tos' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required", "terms_of_service": "required"},
|
||||
{
|
||||
"label": u"I agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_template.format(link_label=link_label)
|
||||
),
|
||||
"name": "terms_of_service",
|
||||
"defaultValue": False,
|
||||
"type": "checkbox",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} Terms of Service".format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
REGISTRATION_EXTRA_FIELDS={
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"city": "optional",
|
||||
"state": "optional",
|
||||
"country": "required",
|
||||
"honor_code": "required",
|
||||
"confirm_email": "required",
|
||||
},
|
||||
REGISTRATION_EXTENSION_FORM='openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm',
|
||||
)
|
||||
def test_field_order(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that all fields render in the correct order
|
||||
form_desc = json.loads(response.content.decode('utf-8'))
|
||||
field_names = [field["name"] for field in form_desc["fields"]]
|
||||
self.assertEqual(field_names, [
|
||||
"email",
|
||||
"name",
|
||||
"username",
|
||||
"password",
|
||||
"favorite_movie",
|
||||
"favorite_editor",
|
||||
"confirm_email",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
])
|
||||
|
||||
@override_settings(
|
||||
REGISTRATION_EXTRA_FIELDS={
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"city": "optional",
|
||||
"state": "optional",
|
||||
"country": "required",
|
||||
"honor_code": "required",
|
||||
"confirm_email": "required",
|
||||
},
|
||||
REGISTRATION_FIELD_ORDER=[
|
||||
"name",
|
||||
"username",
|
||||
"email",
|
||||
"confirm_email",
|
||||
"password",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"company",
|
||||
"title",
|
||||
"job_title",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
"terms_of_service",
|
||||
"specialty",
|
||||
"profession",
|
||||
],
|
||||
)
|
||||
def test_field_order_override(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that all fields render in the correct order
|
||||
form_desc = json.loads(response.content.decode('utf-8'))
|
||||
field_names = [field["name"] for field in form_desc["fields"]]
|
||||
self.assertEqual(field_names, [
|
||||
"name",
|
||||
"username",
|
||||
"email",
|
||||
"confirm_email",
|
||||
"password",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
])
|
||||
|
||||
@override_settings(
|
||||
REGISTRATION_EXTRA_FIELDS={
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"city": "optional",
|
||||
"state": "optional",
|
||||
"country": "required",
|
||||
"honor_code": "required",
|
||||
"confirm_email": "required",
|
||||
},
|
||||
REGISTRATION_EXTENSION_FORM='openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm',
|
||||
REGISTRATION_FIELD_ORDER=[
|
||||
"name",
|
||||
"confirm_email",
|
||||
"password",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"company",
|
||||
"title",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
"terms_of_service",
|
||||
],
|
||||
)
|
||||
def test_field_order_invalid_override(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that all fields render in the correct order
|
||||
form_desc = json.loads(response.content.decode('utf-8'))
|
||||
field_names = [field["name"] for field in form_desc["fields"]]
|
||||
self.assertEqual(field_names, [
|
||||
"email",
|
||||
"name",
|
||||
"username",
|
||||
"password",
|
||||
"favorite_movie",
|
||||
"favorite_editor",
|
||||
"confirm_email",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
])
|
||||
|
||||
def test_register(self):
|
||||
# Create a new registration
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
self.assertIn(settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, self.client.cookies)
|
||||
self.assertIn(settings.EDXMKTG_USER_INFO_COOKIE_NAME, self.client.cookies)
|
||||
|
||||
user = User.objects.get(username=self.USERNAME)
|
||||
request = RequestFactory().get('/url')
|
||||
request.user = user
|
||||
account_settings = get_account_settings(request)[0]
|
||||
|
||||
self.assertEqual(self.USERNAME, account_settings["username"])
|
||||
self.assertEqual(self.EMAIL, account_settings["email"])
|
||||
self.assertFalse(account_settings["is_active"])
|
||||
self.assertEqual(self.NAME, account_settings["name"])
|
||||
|
||||
# Verify that we've been logged in
|
||||
# by trying to access a page that requires authentication
|
||||
response = self.client.get(reverse("dashboard"))
|
||||
self.assertHttpOK(response)
|
||||
|
||||
@override_settings(REGISTRATION_EXTRA_FIELDS={
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"country": "required",
|
||||
})
|
||||
def test_register_with_profile_info(self):
|
||||
# Register, providing lots of demographic info
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"level_of_education": self.EDUCATION,
|
||||
"mailing_address": self.ADDRESS,
|
||||
"year_of_birth": self.YEAR_OF_BIRTH,
|
||||
"goals": self.GOALS,
|
||||
"country": self.COUNTRY,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify the user's account
|
||||
user = User.objects.get(username=self.USERNAME)
|
||||
request = RequestFactory().get('/url')
|
||||
request.user = user
|
||||
account_settings = get_account_settings(request)[0]
|
||||
|
||||
self.assertEqual(account_settings["level_of_education"], self.EDUCATION)
|
||||
self.assertEqual(account_settings["mailing_address"], self.ADDRESS)
|
||||
self.assertEqual(account_settings["year_of_birth"], int(self.YEAR_OF_BIRTH))
|
||||
self.assertEqual(account_settings["goals"], self.GOALS)
|
||||
self.assertEqual(account_settings["country"], self.COUNTRY)
|
||||
|
||||
@override_settings(REGISTRATION_EXTENSION_FORM='openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm')
|
||||
@mock.patch('openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm.DUMMY_STORAGE', new_callable=dict)
|
||||
@mock.patch(
|
||||
'openedx.core.djangoapps.user_api.tests.test_helpers.DummyRegistrationExtensionModel',
|
||||
)
|
||||
def test_with_extended_form(self, dummy_model, storage_dict):
|
||||
dummy_model_instance = mock.Mock()
|
||||
dummy_model.return_value = dummy_model_instance
|
||||
# Create a new registration
|
||||
self.assertEqual(storage_dict, {})
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
"favorite_movie": "Inception",
|
||||
"favorite_editor": "cat",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
self.assertIn(settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, self.client.cookies)
|
||||
self.assertIn(settings.EDXMKTG_USER_INFO_COOKIE_NAME, self.client.cookies)
|
||||
|
||||
user = User.objects.get(username=self.USERNAME)
|
||||
request = RequestFactory().get('/url')
|
||||
request.user = user
|
||||
account_settings = get_account_settings(request)[0]
|
||||
|
||||
self.assertEqual(self.USERNAME, account_settings["username"])
|
||||
self.assertEqual(self.EMAIL, account_settings["email"])
|
||||
self.assertFalse(account_settings["is_active"])
|
||||
self.assertEqual(self.NAME, account_settings["name"])
|
||||
|
||||
self.assertEqual(storage_dict, {'favorite_movie': "Inception", "favorite_editor": "cat"})
|
||||
self.assertEqual(dummy_model_instance.user, user)
|
||||
|
||||
# Verify that we've been logged in
|
||||
# by trying to access a page that requires authentication
|
||||
response = self.client.get(reverse("dashboard"))
|
||||
self.assertHttpOK(response)
|
||||
|
||||
def test_activation_email(self):
|
||||
# Register, which should trigger an activation email
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that the activation email was sent
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
sent_email = mail.outbox[0]
|
||||
self.assertEqual(sent_email.to, [self.EMAIL])
|
||||
self.assertEqual(
|
||||
sent_email.subject,
|
||||
u"Action Required: Activate your {platform} account".format(platform=settings.PLATFORM_NAME)
|
||||
)
|
||||
self.assertIn(
|
||||
u"high-quality {platform} courses".format(platform=settings.PLATFORM_NAME),
|
||||
sent_email.body
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
{"email": ""},
|
||||
{"email": "invalid"},
|
||||
{"name": ""},
|
||||
{"username": ""},
|
||||
{"username": "a"},
|
||||
{"password": ""},
|
||||
)
|
||||
def test_register_invalid_input(self, invalid_fields):
|
||||
# Initially, the field values are all valid
|
||||
data = {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
}
|
||||
|
||||
# Override the valid fields, making the input invalid
|
||||
data.update(invalid_fields)
|
||||
|
||||
# Attempt to create the account, expecting an error response
|
||||
response = self.client.post(self.url, data)
|
||||
self.assertHttpBadRequest(response)
|
||||
|
||||
@override_settings(REGISTRATION_EXTRA_FIELDS={"country": "required"})
|
||||
@ddt.data("email", "name", "username", "password", "country")
|
||||
def test_register_missing_required_field(self, missing_field):
|
||||
data = {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"country": self.COUNTRY,
|
||||
}
|
||||
|
||||
del data[missing_field]
|
||||
|
||||
# Send a request missing a field
|
||||
response = self.client.post(self.url, data)
|
||||
self.assertHttpBadRequest(response)
|
||||
|
||||
def test_register_duplicate_email(self):
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same email address
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": "someone_else",
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_duplicate_username(self):
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same username
|
||||
response = self.client.post(self.url, {
|
||||
"email": "someone+else@example.com",
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different username."
|
||||
).format(
|
||||
self.USERNAME
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_duplicate_username_and_email(self):
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same username
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different username."
|
||||
).format(
|
||||
self.USERNAME
|
||||
)
|
||||
}],
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(REGISTRATION_EXTRA_FIELDS={"honor_code": "hidden", "terms_of_service": "hidden"})
|
||||
def test_register_hidden_honor_code_and_terms_of_service(self):
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
def test_missing_fields(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"honor_code": "true",
|
||||
}
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
u"success": False,
|
||||
u"username": [{u"user_message": USERNAME_BAD_LENGTH_MSG}],
|
||||
u"password": [{u"user_message": u"This field is required."}],
|
||||
}
|
||||
)
|
||||
|
||||
def test_country_overrides(self):
|
||||
"""Test that overridden countries are available in country list."""
|
||||
# Retrieve the registration form description
|
||||
with override_settings(REGISTRATION_EXTRA_FIELDS={"country": "required"}):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
self.assertContains(response, 'Kosovo')
|
||||
|
||||
def test_create_account_not_allowed(self):
|
||||
"""
|
||||
Test case to check user creation is forbidden when ALLOW_PUBLIC_ACCOUNT_CREATION feature flag is turned off
|
||||
"""
|
||||
def _side_effect_for_get_value(value, default=None):
|
||||
"""
|
||||
returns a side_effect with given return value for a given value
|
||||
"""
|
||||
if value == 'ALLOW_PUBLIC_ACCOUNT_CREATION':
|
||||
return False
|
||||
else:
|
||||
return get_value(value, default)
|
||||
|
||||
with mock.patch('openedx.core.djangoapps.site_configuration.helpers.get_value') as mock_get_value:
|
||||
mock_get_value.side_effect = _side_effect_for_get_value
|
||||
response = self.client.post(self.url, {"email": self.EMAIL, "username": self.USERNAME})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def _assert_fields_match(self, actual_field, expected_field):
|
||||
self.assertIsNot(
|
||||
actual_field, None,
|
||||
msg=u"Could not find field {name}".format(name=expected_field["name"])
|
||||
)
|
||||
|
||||
for key in expected_field:
|
||||
self.assertEqual(
|
||||
actual_field[key], expected_field[key],
|
||||
msg=u"Expected {expected} for {key} but got {actual} instead".format(
|
||||
key=key,
|
||||
actual=actual_field[key],
|
||||
expected=expected_field[key]
|
||||
)
|
||||
)
|
||||
|
||||
def _populate_always_present_fields(self, field):
|
||||
defaults = [
|
||||
("label", ""),
|
||||
("instructions", ""),
|
||||
("placeholder", ""),
|
||||
("defaultValue", ""),
|
||||
("restrictions", {}),
|
||||
("errorMessages", {}),
|
||||
]
|
||||
field.update({
|
||||
key: value
|
||||
for key, value in defaults if key not in field
|
||||
})
|
||||
|
||||
def _assert_reg_field(self, extra_fields_setting, expected_field):
|
||||
"""Retrieve the registration form description from the server and
|
||||
verify that it contains the expected field.
|
||||
|
||||
Args:
|
||||
extra_fields_setting (dict): Override the Django setting controlling
|
||||
which extra fields are displayed in the form.
|
||||
|
||||
expected_field (dict): The field definition we expect to find in the form.
|
||||
|
||||
Raises:
|
||||
AssertionError
|
||||
|
||||
"""
|
||||
# Add in fields that are always present
|
||||
self._populate_always_present_fields(expected_field)
|
||||
|
||||
# Retrieve the registration form description
|
||||
with override_settings(REGISTRATION_EXTRA_FIELDS=extra_fields_setting):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that the form description matches what we'd expect
|
||||
form_desc = json.loads(response.content.decode('utf-8'))
|
||||
|
||||
actual_field = None
|
||||
for field in form_desc["fields"]:
|
||||
if field["name"] == expected_field["name"]:
|
||||
actual_field = field
|
||||
break
|
||||
|
||||
self._assert_fields_match(actual_field, expected_field)
|
||||
|
||||
def _assert_password_field_hidden(self, field_settings):
|
||||
self._assert_reg_field(field_settings, {
|
||||
"name": "password",
|
||||
"type": "hidden",
|
||||
"required": False
|
||||
})
|
||||
|
||||
def _assert_social_auth_provider_present(self, field_settings, backend):
|
||||
self._assert_reg_field(field_settings, {
|
||||
"name": "social_auth_provider",
|
||||
"type": "hidden",
|
||||
"required": False,
|
||||
"defaultValue": backend.name
|
||||
})
|
||||
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.ddt
|
||||
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for the User API registration endpoint with 3rd party authentication.
|
||||
"""
|
||||
CREATE_USER = False
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
__test__ = False
|
||||
|
||||
def setUp(self):
|
||||
super(ThirdPartyRegistrationTestMixin, self).setUp()
|
||||
self.url = reverse('user_api_registration')
|
||||
|
||||
def tearDown(self):
|
||||
super(ThirdPartyRegistrationTestMixin, self).tearDown()
|
||||
Partial.objects.all().delete()
|
||||
|
||||
def data(self, user=None):
|
||||
"""Returns the request data for the endpoint."""
|
||||
return {
|
||||
"provider": self.BACKEND,
|
||||
"access_token": self.access_token,
|
||||
"client_id": self.client_id,
|
||||
"honor_code": "true",
|
||||
"country": "US",
|
||||
"username": user.username if user else "test_username",
|
||||
"name": user.first_name if user else "test name",
|
||||
"email": user.email if user else "test@test.com"
|
||||
}
|
||||
|
||||
def _assert_existing_user_error(self, response):
|
||||
"""Assert that the given response was an error with the given status_code and error code."""
|
||||
self.assertEqual(response.status_code, 409)
|
||||
errors = json.loads(response.content.decode('utf-8'))
|
||||
for conflict_attribute in ["username", "email"]:
|
||||
self.assertIn(conflict_attribute, errors)
|
||||
self.assertIn("belongs to an existing account", errors[conflict_attribute][0]["user_message"])
|
||||
|
||||
def _assert_access_token_error(self, response, expected_error_message):
|
||||
"""Assert that the given response was an error for the access_token field with the given error message."""
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"access_token": [{"user_message": expected_error_message}],
|
||||
}
|
||||
)
|
||||
|
||||
def _assert_third_party_session_expired_error(self, response, expected_error_message):
|
||||
"""Assert that given response is an error due to third party session expiry"""
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"session_expired": [{"user_message": expected_error_message}],
|
||||
}
|
||||
)
|
||||
|
||||
def _verify_user_existence(self, user_exists, social_link_exists, user_is_active=None, username=None):
|
||||
"""Verifies whether the user object exists."""
|
||||
users = User.objects.filter(username=(username if username else "test_username"))
|
||||
self.assertEquals(users.exists(), user_exists)
|
||||
if user_exists:
|
||||
self.assertEquals(users[0].is_active, user_is_active)
|
||||
self.assertEqual(
|
||||
UserSocialAuth.objects.filter(user=users[0], provider=self.BACKEND).exists(),
|
||||
social_link_exists
|
||||
)
|
||||
else:
|
||||
self.assertEquals(UserSocialAuth.objects.count(), 0)
|
||||
|
||||
def test_success(self):
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
self._setup_provider_response(success=True)
|
||||
response = self.client.post(self.url, self.data())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self._verify_user_existence(user_exists=True, social_link_exists=True, user_is_active=False)
|
||||
|
||||
def test_unlinked_active_user(self):
|
||||
user = UserFactory()
|
||||
response = self.client.post(self.url, self.data(user))
|
||||
self._assert_existing_user_error(response)
|
||||
self._verify_user_existence(
|
||||
user_exists=True, social_link_exists=False, user_is_active=True, username=user.username
|
||||
)
|
||||
|
||||
def test_unlinked_inactive_user(self):
|
||||
user = UserFactory(is_active=False)
|
||||
response = self.client.post(self.url, self.data(user))
|
||||
self._assert_existing_user_error(response)
|
||||
self._verify_user_existence(
|
||||
user_exists=True, social_link_exists=False, user_is_active=False, username=user.username
|
||||
)
|
||||
|
||||
def test_user_already_registered(self):
|
||||
self._setup_provider_response(success=True)
|
||||
user = UserFactory()
|
||||
UserSocialAuth.objects.create(user=user, provider=self.BACKEND, uid=self.social_uid)
|
||||
response = self.client.post(self.url, self.data(user))
|
||||
self._assert_existing_user_error(response)
|
||||
self._verify_user_existence(
|
||||
user_exists=True, social_link_exists=True, user_is_active=True, username=user.username
|
||||
)
|
||||
|
||||
def test_social_user_conflict(self):
|
||||
self._setup_provider_response(success=True)
|
||||
user = UserFactory()
|
||||
UserSocialAuth.objects.create(user=user, provider=self.BACKEND, uid=self.social_uid)
|
||||
response = self.client.post(self.url, self.data())
|
||||
self._assert_access_token_error(response, "The provided access_token is already associated with another user.")
|
||||
self._verify_user_existence(
|
||||
user_exists=True, social_link_exists=True, user_is_active=True, username=user.username
|
||||
)
|
||||
|
||||
def test_invalid_token(self):
|
||||
self._setup_provider_response(success=False)
|
||||
response = self.client.post(self.url, self.data())
|
||||
self._assert_access_token_error(response, "The provided access_token is not valid.")
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
def test_missing_token(self):
|
||||
data = self.data()
|
||||
data.pop("access_token")
|
||||
response = self.client.post(self.url, data)
|
||||
self._assert_access_token_error(
|
||||
response,
|
||||
u"An access_token is required when passing value ({}) for provider.".format(self.BACKEND)
|
||||
)
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
def test_expired_pipeline(self):
|
||||
|
||||
"""
|
||||
Test that there is an error and account is not created
|
||||
when request is made for account creation using third (Google, Facebook etc) party with pipeline
|
||||
getting expired using browser (not mobile application).
|
||||
|
||||
NOTE: We are NOT using actual pipeline here so pipeline is always expired in this environment.
|
||||
we don't have to explicitly expire pipeline.
|
||||
|
||||
"""
|
||||
|
||||
data = self.data()
|
||||
# provider is sent along request when request is made from mobile application
|
||||
data.pop("provider")
|
||||
# to identify that request is made using browser
|
||||
data.update({"social_auth_provider": "Google"})
|
||||
response = self.client.post(self.url, data)
|
||||
self._assert_third_party_session_expired_error(
|
||||
response,
|
||||
u"Registration using {provider} has timed out.".format(provider="Google")
|
||||
)
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
|
||||
@skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled")
|
||||
class TestFacebookRegistrationView(
|
||||
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase
|
||||
):
|
||||
"""Tests the User API registration endpoint with Facebook authentication."""
|
||||
__test__ = True
|
||||
|
||||
def test_social_auth_exception(self):
|
||||
"""
|
||||
According to the do_auth method in social_core.backends.facebook.py,
|
||||
the Facebook API sometimes responds back a JSON with just False as value.
|
||||
"""
|
||||
self._setup_provider_response_with_body(200, json.dumps("false"))
|
||||
response = self.client.post(self.url, self.data())
|
||||
self._assert_access_token_error(response, "The provided access_token is not valid.")
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
|
||||
@skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled")
|
||||
class TestGoogleRegistrationView(
|
||||
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase
|
||||
):
|
||||
"""Tests the User API registration endpoint with Google authentication."""
|
||||
__test__ = True
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
class UpdateEmailOptInTestCase(UserAPITestCase, SharedModuleStoreTestCase):
|
||||
|
||||
@@ -37,8 +37,6 @@ from openedx.core.djangoapps.user_api.serializers import (
|
||||
UserPreferenceSerializer,
|
||||
UserSerializer
|
||||
)
|
||||
from openedx.core.djangoapps.user_authn.cookies import set_logged_in_cookies
|
||||
from openedx.core.djangoapps.user_authn.views.register import create_account_with_params
|
||||
from openedx.core.lib.api.permissions import ApiKeyHeaderPermission
|
||||
from student.helpers import AccountValidationError
|
||||
from util.json_request import JsonResponse
|
||||
@@ -97,114 +95,6 @@ class LoginSessionView(APIView):
|
||||
return super(LoginSessionView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class RegistrationView(APIView):
|
||||
# pylint: disable=missing-docstring
|
||||
"""HTTP end-points for creating a new user. """
|
||||
|
||||
# This end-point is available to anonymous users,
|
||||
# so do not require authentication.
|
||||
authentication_classes = []
|
||||
|
||||
@method_decorator(transaction.non_atomic_requests)
|
||||
@method_decorator(sensitive_post_parameters("password"))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(RegistrationView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
def get(self, request):
|
||||
return HttpResponse(RegistrationFormFactory().get_registration_form(request).to_json(),
|
||||
content_type="application/json")
|
||||
|
||||
@method_decorator(csrf_exempt)
|
||||
def post(self, request):
|
||||
"""Create the user's account.
|
||||
|
||||
You must send all required form fields with the request.
|
||||
|
||||
You can optionally send a "course_id" param to indicate in analytics
|
||||
events that the user registered while enrolling in a particular course.
|
||||
|
||||
Arguments:
|
||||
request (HTTPRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 200 on success
|
||||
HttpResponse: 400 if the request is not valid.
|
||||
HttpResponse: 409 if an account with the given username or email
|
||||
address already exists
|
||||
HttpResponse: 403 operation not allowed
|
||||
"""
|
||||
data = request.POST.copy()
|
||||
self._handle_terms_of_service(data)
|
||||
|
||||
response = self._handle_duplicate_email_username(data)
|
||||
if response:
|
||||
return response
|
||||
|
||||
response, user = self._create_account(request, data)
|
||||
if response:
|
||||
return response
|
||||
|
||||
response = self._create_response({}, status_code=200)
|
||||
set_logged_in_cookies(request, response, user)
|
||||
return response
|
||||
|
||||
def _handle_duplicate_email_username(self, data):
|
||||
# TODO Verify whether this check is needed here - it may be duplicated in user_api.
|
||||
email = data.get('email')
|
||||
username = data.get('username')
|
||||
|
||||
conflicts = check_account_exists(email=email, username=username)
|
||||
if conflicts:
|
||||
conflict_messages = {
|
||||
"email": accounts.EMAIL_CONFLICT_MSG.format(email_address=email),
|
||||
"username": accounts.USERNAME_CONFLICT_MSG.format(username=username),
|
||||
}
|
||||
errors = {
|
||||
field: [{"user_message": conflict_messages[field]}]
|
||||
for field in conflicts
|
||||
}
|
||||
return self._create_response(errors, status_code=409)
|
||||
|
||||
def _handle_terms_of_service(self, data):
|
||||
# Backwards compatibility: the student view expects both
|
||||
# terms of service and honor code values. Since we're combining
|
||||
# these into a single checkbox, the only value we may get
|
||||
# from the new view is "honor_code".
|
||||
# Longer term, we will need to make this more flexible to support
|
||||
# open source installations that may have separate checkboxes
|
||||
# for TOS, privacy policy, etc.
|
||||
if data.get("honor_code") and "terms_of_service" not in data:
|
||||
data["terms_of_service"] = data["honor_code"]
|
||||
|
||||
def _create_account(self, request, data):
|
||||
response, user = None, None
|
||||
try:
|
||||
user = create_account_with_params(request, data)
|
||||
except AccountValidationError as err:
|
||||
errors = {
|
||||
err.field: [{"user_message": text_type(err)}]
|
||||
}
|
||||
response = self._create_response(errors, status_code=409)
|
||||
except ValidationError as err:
|
||||
# Should only get field errors from this exception
|
||||
assert NON_FIELD_ERRORS not in err.message_dict
|
||||
# Only return first error for each field
|
||||
errors = {
|
||||
field: [{"user_message": error} for error in error_list]
|
||||
for field, error_list in err.message_dict.items()
|
||||
}
|
||||
response = self._create_response(errors, status_code=400)
|
||||
except PermissionDenied:
|
||||
response = HttpResponseForbidden(_("Account creation not allowed."))
|
||||
|
||||
return response, user
|
||||
|
||||
def _create_response(self, response_dict, status_code):
|
||||
response_dict['success'] = (status_code == 200)
|
||||
return JsonResponse(response_dict, status=status_code)
|
||||
|
||||
|
||||
class PasswordResetView(APIView):
|
||||
"""HTTP end-point for GETting a description of the password reset form. """
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from __future__ import absolute_import
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
|
||||
from openedx.core.djangoapps.user_api.views import RegistrationView
|
||||
from .views.register import RegistrationView
|
||||
from .views import auto_auth, login, logout
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ urlpatterns = [
|
||||
url(r'^login_ajax$', login.login_user, name="login"),
|
||||
url(r'^login_ajax/(?P<error>[^/]*)$', login.login_user),
|
||||
url(r'^login_refresh$', login.login_refresh, name="login_refresh"),
|
||||
url(r'^user_api/v1/account/registration/$', RegistrationView.as_view(),
|
||||
name="user_api_registration"),
|
||||
|
||||
url(r'^logout$', logout.LogoutView.as_view(), name='logout'),
|
||||
]
|
||||
|
||||
@@ -11,15 +11,21 @@ import logging
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login as django_login
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied
|
||||
from django.core.validators import ValidationError, validate_email
|
||||
from django.db import transaction
|
||||
from django.dispatch import Signal
|
||||
from django.http import HttpResponse, HttpResponseForbidden
|
||||
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import get_language
|
||||
from django.utils.translation import ugettext as _
|
||||
from pytz import UTC
|
||||
from requests import HTTPError
|
||||
from six import text_type
|
||||
from rest_framework.views import APIView
|
||||
from social_core.exceptions import AuthAlreadyAssociated, AuthException
|
||||
from social_django import utils as social_utils
|
||||
|
||||
@@ -30,16 +36,25 @@ from lms.djangoapps.discussion.notification_prefs.views import enable_notificati
|
||||
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.user_api import accounts as accounts_settings
|
||||
from openedx.core.djangoapps.user_api.api import RegistrationFormFactory
|
||||
from openedx.core.djangoapps.user_api.accounts.api import check_account_exists
|
||||
from openedx.core.djangoapps.user_api.accounts.utils import generate_password
|
||||
from openedx.core.djangoapps.user_api.preferences import api as preferences_api
|
||||
from openedx.core.djangoapps.user_authn.cookies import set_logged_in_cookies
|
||||
from student.forms import AccountCreationForm, get_registration_extension_form
|
||||
from student.helpers import authenticate_new_user, create_or_set_user_attribute_created_on_site, do_create_account
|
||||
from student.helpers import (
|
||||
authenticate_new_user,
|
||||
create_or_set_user_attribute_created_on_site,
|
||||
do_create_account,
|
||||
AccountValidationError,
|
||||
)
|
||||
from student.models import RegistrationCookieConfiguration, UserAttribute, create_comments_service_user
|
||||
from student.views import compose_and_send_activation_email
|
||||
from third_party_auth import pipeline, provider
|
||||
from third_party_auth.saml import SAP_SUCCESSFACTORS_SAML_KEY
|
||||
from track import segment
|
||||
from util.db import outer_atomic
|
||||
from util.json_request import JsonResponse
|
||||
|
||||
log = logging.getLogger("edx.student")
|
||||
AUDIT_LOG = logging.getLogger("audit")
|
||||
@@ -386,3 +401,111 @@ def _record_utm_registration_attribution(request, user):
|
||||
REGISTRATION_UTM_CREATED_AT,
|
||||
created_at_datetime
|
||||
)
|
||||
|
||||
|
||||
class RegistrationView(APIView):
|
||||
# pylint: disable=missing-docstring
|
||||
"""HTTP end-points for creating a new user. """
|
||||
|
||||
# This end-point is available to anonymous users,
|
||||
# so do not require authentication.
|
||||
authentication_classes = []
|
||||
|
||||
@method_decorator(transaction.non_atomic_requests)
|
||||
@method_decorator(sensitive_post_parameters("password"))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(RegistrationView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
def get(self, request):
|
||||
return HttpResponse(RegistrationFormFactory().get_registration_form(request).to_json(),
|
||||
content_type="application/json")
|
||||
|
||||
@method_decorator(csrf_exempt)
|
||||
def post(self, request):
|
||||
"""Create the user's account.
|
||||
|
||||
You must send all required form fields with the request.
|
||||
|
||||
You can optionally send a "course_id" param to indicate in analytics
|
||||
events that the user registered while enrolling in a particular course.
|
||||
|
||||
Arguments:
|
||||
request (HTTPRequest)
|
||||
|
||||
Returns:
|
||||
HttpResponse: 200 on success
|
||||
HttpResponse: 400 if the request is not valid.
|
||||
HttpResponse: 409 if an account with the given username or email
|
||||
address already exists
|
||||
HttpResponse: 403 operation not allowed
|
||||
"""
|
||||
data = request.POST.copy()
|
||||
self._handle_terms_of_service(data)
|
||||
|
||||
response = self._handle_duplicate_email_username(data)
|
||||
if response:
|
||||
return response
|
||||
|
||||
response, user = self._create_account(request, data)
|
||||
if response:
|
||||
return response
|
||||
|
||||
response = self._create_response({}, status_code=200)
|
||||
set_logged_in_cookies(request, response, user)
|
||||
return response
|
||||
|
||||
def _handle_duplicate_email_username(self, data):
|
||||
# TODO Verify whether this check is needed here - it may be duplicated in user_api.
|
||||
email = data.get('email')
|
||||
username = data.get('username')
|
||||
|
||||
conflicts = check_account_exists(email=email, username=username)
|
||||
if conflicts:
|
||||
conflict_messages = {
|
||||
"email": accounts_settings.EMAIL_CONFLICT_MSG.format(email_address=email), # pylint: disable=no-member
|
||||
"username": accounts_settings.USERNAME_CONFLICT_MSG.format(username=username), # pylint: disable=no-member
|
||||
}
|
||||
errors = {
|
||||
field: [{"user_message": conflict_messages[field]}]
|
||||
for field in conflicts
|
||||
}
|
||||
return self._create_response(errors, status_code=409)
|
||||
|
||||
def _handle_terms_of_service(self, data):
|
||||
# Backwards compatibility: the student view expects both
|
||||
# terms of service and honor code values. Since we're combining
|
||||
# these into a single checkbox, the only value we may get
|
||||
# from the new view is "honor_code".
|
||||
# Longer term, we will need to make this more flexible to support
|
||||
# open source installations that may have separate checkboxes
|
||||
# for TOS, privacy policy, etc.
|
||||
if data.get("honor_code") and "terms_of_service" not in data:
|
||||
data["terms_of_service"] = data["honor_code"]
|
||||
|
||||
def _create_account(self, request, data):
|
||||
response, user = None, None
|
||||
try:
|
||||
user = create_account_with_params(request, data)
|
||||
except AccountValidationError as err:
|
||||
errors = {
|
||||
err.field: [{"user_message": text_type(err)}]
|
||||
}
|
||||
response = self._create_response(errors, status_code=409)
|
||||
except ValidationError as err:
|
||||
# Should only get field errors from this exception
|
||||
assert NON_FIELD_ERRORS not in err.message_dict
|
||||
# Only return first error for each field
|
||||
errors = {
|
||||
field: [{"user_message": error} for error in error_list]
|
||||
for field, error_list in err.message_dict.items()
|
||||
}
|
||||
response = self._create_response(errors, status_code=400)
|
||||
except PermissionDenied:
|
||||
response = HttpResponseForbidden(_("Account creation not allowed."))
|
||||
|
||||
return response, user
|
||||
|
||||
def _create_response(self, response_dict, status_code):
|
||||
response_dict['success'] = (status_code == 200)
|
||||
return JsonResponse(response_dict, status=status_code)
|
||||
|
||||
1900
openedx/core/djangoapps/user_authn/views/tests/test_register.py
Normal file
1900
openedx/core/djangoapps/user_authn/views/tests/test_register.py
Normal file
@@ -0,0 +1,1900 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for account creation"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
from unittest import skipUnless
|
||||
from datetime import datetime
|
||||
|
||||
import ddt
|
||||
import httpretty
|
||||
import mock
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import mail
|
||||
from django.test import TransactionTestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from pytz import UTC
|
||||
|
||||
from social_django.models import Partial, UserSocialAuth
|
||||
|
||||
from openedx.core.djangoapps.site_configuration.helpers import get_value
|
||||
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration
|
||||
from openedx.core.djangoapps.user_api.accounts import (
|
||||
EMAIL_MAX_LENGTH,
|
||||
EMAIL_MIN_LENGTH,
|
||||
NAME_MAX_LENGTH,
|
||||
USERNAME_MAX_LENGTH,
|
||||
USERNAME_MIN_LENGTH,
|
||||
USERNAME_BAD_LENGTH_MSG,
|
||||
)
|
||||
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import ( # pylint: disable=unused-import
|
||||
RetirementTestCase,
|
||||
fake_requested_retirement,
|
||||
setup_retirement_states,
|
||||
)
|
||||
from openedx.core.djangoapps.user_api.tests.test_helpers import TestCaseForm
|
||||
from openedx.core.djangoapps.user_api.tests.test_constants import SORTED_COUNTRIES
|
||||
from openedx.core.djangoapps.user_api.tests.test_views import UserAPITestCase
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
|
||||
from student.tests.factories import UserFactory
|
||||
from third_party_auth.tests.testutil import ThirdPartyAuthTestMixin, simulate_running_pipeline
|
||||
from third_party_auth.tests.utils import (
|
||||
ThirdPartyOAuthTestMixin,
|
||||
ThirdPartyOAuthTestMixinFacebook,
|
||||
ThirdPartyOAuthTestMixinGoogle
|
||||
)
|
||||
from util.password_policy_validators import (
|
||||
create_validator_config,
|
||||
password_validators_instruction_texts,
|
||||
password_validators_restrictions
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCase, RetirementTestCase):
|
||||
"""
|
||||
Tests for catching duplicate email and username validation errors within
|
||||
the registration end-points of the User API.
|
||||
"""
|
||||
|
||||
maxDiff = None
|
||||
|
||||
USERNAME = "bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "password"
|
||||
NAME = "Bob Smith"
|
||||
EDUCATION = "m"
|
||||
YEAR_OF_BIRTH = "1998"
|
||||
ADDRESS = "123 Fake Street"
|
||||
CITY = "Springfield"
|
||||
COUNTRY = "us"
|
||||
GOALS = "Learn all the things!"
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(RegistrationViewValidationErrorTest, self).setUp()
|
||||
self.url = reverse("user_api_registration")
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_authn.views.register.check_account_exists')
|
||||
def test_register_retired_email_validation_error(self, dummy_check_account_exists):
|
||||
dummy_check_account_exists.return_value = []
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Initiate retirement for the above user:
|
||||
fake_requested_retirement(User.objects.get(username=self.USERNAME))
|
||||
|
||||
# Try to create a second user with the same email address as the retired user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": "someone_else",
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_retired_email_validation_error_no_bypass_check_account_exists(self):
|
||||
"""
|
||||
This test is the same as above, except it doesn't bypass check_account_exists. Not bypassing this function
|
||||
results in the same error message, but a 409 status code rather than 400.
|
||||
"""
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Initiate retirement for the above user:
|
||||
fake_requested_retirement(User.objects.get(username=self.USERNAME))
|
||||
|
||||
# Try to create a second user with the same email address as the retired user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": "someone_else",
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_duplicate_retired_username_account_validation_error(self):
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Initiate retirement for the above user.
|
||||
fake_requested_retirement(User.objects.get(username=self.USERNAME))
|
||||
|
||||
with mock.patch('openedx.core.djangoapps.user_authn.views.register.do_create_account') as dummy_do_create_acct:
|
||||
# do_create_account should *not* be called - the duplicate retired username
|
||||
# should be detected by check_account_exists before account creation is called.
|
||||
dummy_do_create_acct.side_effect = Exception('do_create_account should *not* have been called!')
|
||||
# Try to create a second user with the same username.
|
||||
response = self.client.post(self.url, {
|
||||
"email": "someone+else@example.com",
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different username."
|
||||
).format(
|
||||
self.USERNAME
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_authn.views.register.check_account_exists')
|
||||
def test_register_duplicate_email_validation_error(self, dummy_check_account_exists):
|
||||
dummy_check_account_exists.return_value = []
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same email address
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": "someone_else",
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_authn.views.register.check_account_exists')
|
||||
def test_register_duplicate_username_account_validation_error(self, dummy_check_account_exists):
|
||||
dummy_check_account_exists.return_value = []
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same username
|
||||
response = self.client.post(self.url, {
|
||||
"email": "someone+else@example.com",
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
u"success": False,
|
||||
u"username": [{
|
||||
u"user_message": (
|
||||
u"An account with the Public Username '{}' already exists."
|
||||
).format(
|
||||
self.USERNAME
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_authn.views.register.check_account_exists')
|
||||
def test_register_duplicate_username_and_email_validation_errors(self, dummy_check_account_exists):
|
||||
dummy_check_account_exists.return_value = []
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same username
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
|
||||
"""Tests for the registration end-points of the User API. """
|
||||
|
||||
maxDiff = None
|
||||
|
||||
USERNAME = "bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "password"
|
||||
NAME = "Bob Smith"
|
||||
EDUCATION = "m"
|
||||
YEAR_OF_BIRTH = "1998"
|
||||
ADDRESS = "123 Fake Street"
|
||||
CITY = "Springfield"
|
||||
COUNTRY = "us"
|
||||
GOALS = "Learn all the things!"
|
||||
PROFESSION_OPTIONS = [
|
||||
{
|
||||
"name": u'--',
|
||||
"value": u'',
|
||||
"default": True
|
||||
|
||||
},
|
||||
{
|
||||
"value": u'software engineer',
|
||||
"name": u'Software Engineer',
|
||||
"default": False
|
||||
},
|
||||
{
|
||||
"value": u'teacher',
|
||||
"name": u'Teacher',
|
||||
"default": False
|
||||
},
|
||||
{
|
||||
"value": u'other',
|
||||
"name": u'Other',
|
||||
"default": False
|
||||
}
|
||||
]
|
||||
SPECIALTY_OPTIONS = [
|
||||
{
|
||||
"name": u'--',
|
||||
"value": u'',
|
||||
"default": True
|
||||
|
||||
},
|
||||
{
|
||||
"value": "aerospace",
|
||||
"name": "Aerospace",
|
||||
"default": False
|
||||
},
|
||||
{
|
||||
"value": u'early education',
|
||||
"name": u'Early Education',
|
||||
"default": False
|
||||
},
|
||||
{
|
||||
"value": u'n/a',
|
||||
"name": u'N/A',
|
||||
"default": False
|
||||
}
|
||||
]
|
||||
link_template = u"<a href='/honor' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(RegistrationViewTest, self).setUp()
|
||||
self.url = reverse("user_api_registration")
|
||||
|
||||
@ddt.data("get", "post")
|
||||
def test_auth_disabled(self, method):
|
||||
self.assertAuthDisabled(method, self.url)
|
||||
|
||||
def test_allowed_methods(self):
|
||||
self.assertAllowedMethods(self.url, ["GET", "POST", "HEAD", "OPTIONS"])
|
||||
|
||||
def test_put_not_allowed(self):
|
||||
response = self.client.put(self.url)
|
||||
self.assertHttpMethodNotAllowed(response)
|
||||
|
||||
def test_delete_not_allowed(self):
|
||||
response = self.client.delete(self.url)
|
||||
self.assertHttpMethodNotAllowed(response)
|
||||
|
||||
def test_patch_not_allowed(self):
|
||||
response = self.client.patch(self.url)
|
||||
self.assertHttpMethodNotAllowed(response)
|
||||
|
||||
def test_register_form_default_fields(self):
|
||||
no_extra_fields_setting = {}
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"email",
|
||||
u"type": u"email",
|
||||
u"required": True,
|
||||
u"label": u"Email",
|
||||
u"instructions": u"This is what you will use to login.",
|
||||
u"restrictions": {
|
||||
"min_length": EMAIL_MIN_LENGTH,
|
||||
"max_length": EMAIL_MAX_LENGTH
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"name",
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Full Name",
|
||||
u"instructions": u"This name will be used on any certificates that you earn.",
|
||||
u"restrictions": {
|
||||
"max_length": 255
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"username",
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Public Username",
|
||||
u"instructions": u"The name that will identify you in your courses. "
|
||||
u"It cannot be changed later.",
|
||||
u"restrictions": {
|
||||
"min_length": USERNAME_MIN_LENGTH,
|
||||
"max_length": USERNAME_MAX_LENGTH
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"placeholder": "",
|
||||
u"name": u"password",
|
||||
u"type": u"password",
|
||||
u"required": True,
|
||||
u"label": u"Password",
|
||||
u"instructions": password_validators_instruction_texts(),
|
||||
u"restrictions": password_validators_restrictions(),
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||
create_validator_config('util.password_policy_validators.MinimumLengthValidator', {'min_length': 2}),
|
||||
create_validator_config('util.password_policy_validators.UppercaseValidator', {'min_upper': 3}),
|
||||
create_validator_config('util.password_policy_validators.SymbolValidator', {'min_symbol': 1}),
|
||||
])
|
||||
def test_register_form_password_complexity(self):
|
||||
no_extra_fields_setting = {}
|
||||
|
||||
# Without enabling password policy
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u'name': u'password',
|
||||
u'label': u'Password',
|
||||
u"instructions": password_validators_instruction_texts(),
|
||||
u"restrictions": password_validators_restrictions(),
|
||||
}
|
||||
)
|
||||
|
||||
msg = u'Your password must contain at least 2 characters, including '\
|
||||
u'3 uppercase letters & 1 symbol.'
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u'name': u'password',
|
||||
u'label': u'Password',
|
||||
u'instructions': msg,
|
||||
u"restrictions": password_validators_restrictions(),
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(REGISTRATION_EXTENSION_FORM='openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm')
|
||||
def test_extension_form_fields(self):
|
||||
no_extra_fields_setting = {}
|
||||
|
||||
# Verify other fields didn't disappear for some reason.
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"email",
|
||||
u"type": u"email",
|
||||
u"required": True,
|
||||
u"label": u"Email",
|
||||
u"instructions": u"This is what you will use to login.",
|
||||
u"restrictions": {
|
||||
"min_length": EMAIL_MIN_LENGTH,
|
||||
"max_length": EMAIL_MAX_LENGTH
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"favorite_editor",
|
||||
u"type": u"select",
|
||||
u"required": False,
|
||||
u"label": u"Favorite Editor",
|
||||
u"placeholder": u"cat",
|
||||
u"defaultValue": u"vim",
|
||||
u"errorMessages": {
|
||||
u'required': u'This field is required.',
|
||||
u'invalid_choice': u'Select a valid choice. %(value)s is not one of the available choices.',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"favorite_movie",
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Fav Flick",
|
||||
u"placeholder": None,
|
||||
u"defaultValue": None,
|
||||
u"errorMessages": {
|
||||
u'required': u'Please tell us your favorite movie.',
|
||||
u'invalid': u"We're pretty sure you made that movie up."
|
||||
},
|
||||
u"restrictions": {
|
||||
"min_length": TestCaseForm.MOVIE_MIN_LEN,
|
||||
"max_length": TestCaseForm.MOVIE_MAX_LEN,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
('pk', 'PK', 'Bob123', 'Bob123'),
|
||||
('Pk', 'PK', None, ''),
|
||||
('pK', 'PK', 'Bob123@edx.org', 'Bob123_edx_org'),
|
||||
('PK', 'PK', 'Bob123123123123123123123123123123123123', 'Bob123123123123123123123123123'),
|
||||
('us', 'US', 'Bob-1231231&23123+1231(2312312312@3123123123', 'Bob-1231231_23123_1231_2312312'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_register_form_third_party_auth_running_google(
|
||||
self,
|
||||
input_country_code,
|
||||
expected_country_code,
|
||||
input_username,
|
||||
expected_username):
|
||||
no_extra_fields_setting = {}
|
||||
country_options = (
|
||||
[
|
||||
{
|
||||
"name": "--",
|
||||
"value": "",
|
||||
"default": False
|
||||
}
|
||||
] + [
|
||||
{
|
||||
"value": country_code,
|
||||
"name": six.text_type(country_name),
|
||||
"default": True if country_code == expected_country_code else False
|
||||
}
|
||||
for country_code, country_name in SORTED_COUNTRIES
|
||||
]
|
||||
)
|
||||
|
||||
provider = self.configure_google_provider(enabled=True)
|
||||
with simulate_running_pipeline(
|
||||
"openedx.core.djangoapps.user_api.api.third_party_auth.pipeline", "google-oauth2",
|
||||
email="bob@example.com",
|
||||
fullname="Bob",
|
||||
username=input_username,
|
||||
country=input_country_code
|
||||
):
|
||||
self._assert_password_field_hidden(no_extra_fields_setting)
|
||||
self._assert_social_auth_provider_present(no_extra_fields_setting, provider)
|
||||
|
||||
# Email should be filled in
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"email",
|
||||
u"defaultValue": u"bob@example.com",
|
||||
u"type": u"email",
|
||||
u"required": True,
|
||||
u"label": u"Email",
|
||||
u"instructions": u"This is what you will use to login.",
|
||||
u"restrictions": {
|
||||
"min_length": EMAIL_MIN_LENGTH,
|
||||
"max_length": EMAIL_MAX_LENGTH
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Full Name should be filled in
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"name",
|
||||
u"defaultValue": u"Bob",
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Full Name",
|
||||
u"instructions": u"This name will be used on any certificates that you earn.",
|
||||
u"restrictions": {
|
||||
"max_length": NAME_MAX_LENGTH,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Username should be filled in
|
||||
self._assert_reg_field(
|
||||
no_extra_fields_setting,
|
||||
{
|
||||
u"name": u"username",
|
||||
u"defaultValue": expected_username,
|
||||
u"type": u"text",
|
||||
u"required": True,
|
||||
u"label": u"Public Username",
|
||||
u"instructions": u"The name that will identify you in your courses. "
|
||||
u"It cannot be changed later.",
|
||||
u"restrictions": {
|
||||
"min_length": USERNAME_MIN_LENGTH,
|
||||
"max_length": USERNAME_MAX_LENGTH
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Country should be filled in.
|
||||
self._assert_reg_field(
|
||||
{u"country": u"required"},
|
||||
{
|
||||
u"label": u"Country or Region of Residence",
|
||||
u"name": u"country",
|
||||
u"defaultValue": expected_country_code,
|
||||
u"type": u"select",
|
||||
u"required": True,
|
||||
u"options": country_options,
|
||||
u"instructions": u"The country or region where you live.",
|
||||
u"errorMessages": {
|
||||
u"required": u"Select your country or region of residence."
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_level_of_education(self):
|
||||
self._assert_reg_field(
|
||||
{"level_of_education": "optional"},
|
||||
{
|
||||
"name": "level_of_education",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Highest level of education completed",
|
||||
"options": [
|
||||
{"value": "", "name": "--", "default": True},
|
||||
{"value": "p", "name": "Doctorate", "default": False},
|
||||
{"value": "m", "name": "Master's or professional degree", "default": False},
|
||||
{"value": "b", "name": "Bachelor's degree", "default": False},
|
||||
{"value": "a", "name": "Associate degree", "default": False},
|
||||
{"value": "hs", "name": "Secondary/high school", "default": False},
|
||||
{"value": "jhs", "name": "Junior secondary/junior high/middle school", "default": False},
|
||||
{"value": "el", "name": "Elementary/primary school", "default": False},
|
||||
{"value": "none", "name": "No formal education", "default": False},
|
||||
{"value": "other", "name": "Other education", "default": False},
|
||||
],
|
||||
"errorMessages": {
|
||||
"required": "Select the highest level of education you have completed."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_api.api._')
|
||||
def test_register_form_level_of_education_translations(self, fake_gettext):
|
||||
fake_gettext.side_effect = lambda text: text + ' TRANSLATED'
|
||||
|
||||
self._assert_reg_field(
|
||||
{"level_of_education": "optional"},
|
||||
{
|
||||
"name": "level_of_education",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Highest level of education completed TRANSLATED",
|
||||
"options": [
|
||||
{"value": "", "name": "--", "default": True},
|
||||
{"value": "p", "name": "Doctorate TRANSLATED", "default": False},
|
||||
{"value": "m", "name": "Master's or professional degree TRANSLATED", "default": False},
|
||||
{"value": "b", "name": "Bachelor's degree TRANSLATED", "default": False},
|
||||
{"value": "a", "name": "Associate degree TRANSLATED", "default": False},
|
||||
{"value": "hs", "name": "Secondary/high school TRANSLATED", "default": False},
|
||||
{"value": "jhs", "name": "Junior secondary/junior high/middle school TRANSLATED", "default": False},
|
||||
{"value": "el", "name": "Elementary/primary school TRANSLATED", "default": False},
|
||||
{"value": "none", "name": "No formal education TRANSLATED", "default": False},
|
||||
{"value": "other", "name": "Other education TRANSLATED", "default": False},
|
||||
],
|
||||
"errorMessages": {
|
||||
"required": "Select the highest level of education you have completed."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_gender(self):
|
||||
self._assert_reg_field(
|
||||
{"gender": "optional"},
|
||||
{
|
||||
"name": "gender",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Gender",
|
||||
"options": [
|
||||
{"value": "", "name": "--", "default": True},
|
||||
{"value": "m", "name": "Male", "default": False},
|
||||
{"value": "f", "name": "Female", "default": False},
|
||||
{"value": "o", "name": "Other/Prefer Not to Say", "default": False},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@mock.patch('openedx.core.djangoapps.user_api.api._')
|
||||
def test_register_form_gender_translations(self, fake_gettext):
|
||||
fake_gettext.side_effect = lambda text: text + ' TRANSLATED'
|
||||
|
||||
self._assert_reg_field(
|
||||
{"gender": "optional"},
|
||||
{
|
||||
"name": "gender",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Gender TRANSLATED",
|
||||
"options": [
|
||||
{"value": "", "name": "--", "default": True},
|
||||
{"value": "m", "name": "Male TRANSLATED", "default": False},
|
||||
{"value": "f", "name": "Female TRANSLATED", "default": False},
|
||||
{"value": "o", "name": "Other/Prefer Not to Say TRANSLATED", "default": False},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_year_of_birth(self):
|
||||
this_year = datetime.now(UTC).year
|
||||
year_options = (
|
||||
[
|
||||
{
|
||||
"value": "",
|
||||
"name": "--",
|
||||
"default": True
|
||||
}
|
||||
] + [
|
||||
{
|
||||
"value": six.text_type(year),
|
||||
"name": six.text_type(year),
|
||||
"default": False
|
||||
}
|
||||
for year in range(this_year, this_year - 120, -1)
|
||||
]
|
||||
)
|
||||
self._assert_reg_field(
|
||||
{"year_of_birth": "optional"},
|
||||
{
|
||||
"name": "year_of_birth",
|
||||
"type": "select",
|
||||
"required": False,
|
||||
"label": "Year of birth",
|
||||
"options": year_options,
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_profession_without_profession_options(self):
|
||||
self._assert_reg_field(
|
||||
{"profession": "required"},
|
||||
{
|
||||
"name": "profession",
|
||||
"type": "text",
|
||||
"required": True,
|
||||
"label": "Profession",
|
||||
"errorMessages": {
|
||||
"required": "Enter your profession."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@with_site_configuration(
|
||||
configuration={
|
||||
"EXTRA_FIELD_OPTIONS": {"profession": ["Software Engineer", "Teacher", "Other"]}
|
||||
}
|
||||
)
|
||||
def test_register_form_profession_with_profession_options(self):
|
||||
self._assert_reg_field(
|
||||
{"profession": "required"},
|
||||
{
|
||||
"name": "profession",
|
||||
"type": "select",
|
||||
"required": True,
|
||||
"label": "Profession",
|
||||
"options": self.PROFESSION_OPTIONS,
|
||||
"errorMessages": {
|
||||
"required": "Select your profession."
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_form_specialty_without_specialty_options(self):
|
||||
self._assert_reg_field(
|
||||
{"specialty": "required"},
|
||||
{
|
||||
"name": "specialty",
|
||||
"type": "text",
|
||||
"required": True,
|
||||
"label": "Specialty",
|
||||
"errorMessages": {
|
||||
"required": "Enter your specialty."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@with_site_configuration(
|
||||
configuration={
|
||||
"EXTRA_FIELD_OPTIONS": {"specialty": ["Aerospace", "Early Education", "N/A"]}
|
||||
}
|
||||
)
|
||||
def test_register_form_specialty_with_specialty_options(self):
|
||||
self._assert_reg_field(
|
||||
{"specialty": "required"},
|
||||
{
|
||||
"name": "specialty",
|
||||
"type": "select",
|
||||
"required": True,
|
||||
"label": "Specialty",
|
||||
"options": self.SPECIALTY_OPTIONS,
|
||||
"errorMessages": {
|
||||
"required": "Select your specialty."
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_mailing_address(self):
|
||||
self._assert_reg_field(
|
||||
{"mailing_address": "optional"},
|
||||
{
|
||||
"name": "mailing_address",
|
||||
"type": "textarea",
|
||||
"required": False,
|
||||
"label": "Mailing address",
|
||||
"errorMessages": {
|
||||
"required": "Enter your mailing address."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_goals(self):
|
||||
self._assert_reg_field(
|
||||
{"goals": "optional"},
|
||||
{
|
||||
"name": "goals",
|
||||
"type": "textarea",
|
||||
"required": False,
|
||||
"label": u"Tell us why you're interested in {platform_name}".format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
),
|
||||
"errorMessages": {
|
||||
"required": "Tell us your goals."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_city(self):
|
||||
self._assert_reg_field(
|
||||
{"city": "optional"},
|
||||
{
|
||||
"name": "city",
|
||||
"type": "text",
|
||||
"required": False,
|
||||
"label": "City",
|
||||
"errorMessages": {
|
||||
"required": "Enter your city."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_state(self):
|
||||
self._assert_reg_field(
|
||||
{"state": "optional"},
|
||||
{
|
||||
"name": "state",
|
||||
"type": "text",
|
||||
"required": False,
|
||||
"label": "State/Province/Region",
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_country(self):
|
||||
country_options = (
|
||||
[
|
||||
{
|
||||
"name": "--",
|
||||
"value": "",
|
||||
"default": True
|
||||
}
|
||||
] + [
|
||||
{
|
||||
"value": country_code,
|
||||
"name": six.text_type(country_name),
|
||||
"default": False
|
||||
}
|
||||
for country_code, country_name in SORTED_COUNTRIES
|
||||
]
|
||||
)
|
||||
self._assert_reg_field(
|
||||
{"country": "required"},
|
||||
{
|
||||
"label": "Country or Region of Residence",
|
||||
"name": "country",
|
||||
"type": "select",
|
||||
"instructions": "The country or region where you live.",
|
||||
"required": True,
|
||||
"options": country_options,
|
||||
"errorMessages": {
|
||||
"required": "Select your country or region of residence."
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def test_registration_form_confirm_email(self):
|
||||
self._assert_reg_field(
|
||||
{"confirm_email": "required"},
|
||||
{
|
||||
"name": "confirm_email",
|
||||
"type": "text",
|
||||
"required": True,
|
||||
"label": "Confirm Email",
|
||||
"errorMessages": {
|
||||
"required": "The email addresses do not match.",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
MKTG_URLS={"ROOT": "https://www.test.com/", "HONOR": "honor"},
|
||||
)
|
||||
@mock.patch.dict(settings.FEATURES, {"ENABLE_MKTG_SITE": True})
|
||||
def test_registration_honor_code_mktg_site_enabled(self):
|
||||
link_template = "<a href='https://www.test.com/honor' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
link_template2 = u"<a href='#' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
link_label = "Terms of Service and Honor Code"
|
||||
link_label2 = "Privacy Policy"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required"},
|
||||
{
|
||||
"label": (u"By creating an account, you agree to the {spacing}"
|
||||
u"{link_label} {spacing}"
|
||||
u"and you acknowledge that {platform_name} and each Member process your "
|
||||
u"personal data in accordance {spacing}"
|
||||
u"with the {link_label2}.").format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_template.format(link_label=link_label),
|
||||
link_label2=link_template2.format(link_label=link_label2),
|
||||
spacing=' ' * 18
|
||||
),
|
||||
"name": "honor_code",
|
||||
"defaultValue": False,
|
||||
"type": "plaintext",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_label
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(MKTG_URLS_LINK_MAP={"HONOR": "honor"})
|
||||
@mock.patch.dict(settings.FEATURES, {"ENABLE_MKTG_SITE": False})
|
||||
def test_registration_honor_code_mktg_site_disabled(self):
|
||||
link_template = "<a href='/privacy' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
link_label = "Terms of Service and Honor Code"
|
||||
link_label2 = "Privacy Policy"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required"},
|
||||
{
|
||||
"label": (u"By creating an account, you agree to the {spacing}"
|
||||
u"{link_label} {spacing}"
|
||||
u"and you acknowledge that {platform_name} and each Member process your "
|
||||
u"personal data in accordance {spacing}"
|
||||
u"with the {link_label2}.").format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=self.link_template.format(link_label=link_label),
|
||||
link_label2=link_template.format(link_label=link_label2),
|
||||
spacing=' ' * 18
|
||||
),
|
||||
"name": "honor_code",
|
||||
"defaultValue": False,
|
||||
"type": "plaintext",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_label
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(MKTG_URLS={
|
||||
"ROOT": "https://www.test.com/",
|
||||
"HONOR": "honor",
|
||||
"TOS": "tos",
|
||||
})
|
||||
@mock.patch.dict(settings.FEATURES, {"ENABLE_MKTG_SITE": True})
|
||||
def test_registration_separate_terms_of_service_mktg_site_enabled(self):
|
||||
# Honor code field should say ONLY honor code,
|
||||
# not "terms of service and honor code"
|
||||
link_label = 'Honor Code'
|
||||
link_template = u"<a href='https://www.test.com/honor' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required", "terms_of_service": "required"},
|
||||
{
|
||||
"label": u"I agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_template.format(link_label=link_label)
|
||||
),
|
||||
"name": "honor_code",
|
||||
"defaultValue": False,
|
||||
"type": "checkbox",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_label
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Terms of service field should also be present
|
||||
link_label = "Terms of Service"
|
||||
link_template = u"<a href='https://www.test.com/tos' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required", "terms_of_service": "required"},
|
||||
{
|
||||
"label": u"I agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_template.format(link_label=link_label)
|
||||
),
|
||||
"name": "terms_of_service",
|
||||
"defaultValue": False,
|
||||
"type": "checkbox",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_label
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(MKTG_URLS_LINK_MAP={"HONOR": "honor", "TOS": "tos"})
|
||||
@mock.patch.dict(settings.FEATURES, {"ENABLE_MKTG_SITE": False})
|
||||
def test_registration_separate_terms_of_service_mktg_site_disabled(self):
|
||||
# Honor code field should say ONLY honor code,
|
||||
# not "terms of service and honor code"
|
||||
link_label = 'Honor Code'
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required", "terms_of_service": "required"},
|
||||
{
|
||||
"label": u"I agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=self.link_template.format(link_label=link_label)
|
||||
),
|
||||
"name": "honor_code",
|
||||
"defaultValue": False,
|
||||
"type": "checkbox",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} Honor Code".format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
link_label = 'Terms of Service'
|
||||
# Terms of service field should also be present
|
||||
link_template = u"<a href='/tos' rel='noopener' target='_blank'>{link_label}</a>"
|
||||
self._assert_reg_field(
|
||||
{"honor_code": "required", "terms_of_service": "required"},
|
||||
{
|
||||
"label": u"I agree to the {platform_name} {link_label}".format(
|
||||
platform_name=settings.PLATFORM_NAME,
|
||||
link_label=link_template.format(link_label=link_label)
|
||||
),
|
||||
"name": "terms_of_service",
|
||||
"defaultValue": False,
|
||||
"type": "checkbox",
|
||||
"required": True,
|
||||
"errorMessages": {
|
||||
"required": u"You must agree to the {platform_name} Terms of Service".format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
REGISTRATION_EXTRA_FIELDS={
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"city": "optional",
|
||||
"state": "optional",
|
||||
"country": "required",
|
||||
"honor_code": "required",
|
||||
"confirm_email": "required",
|
||||
},
|
||||
REGISTRATION_EXTENSION_FORM='openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm',
|
||||
)
|
||||
def test_field_order(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that all fields render in the correct order
|
||||
form_desc = json.loads(response.content.decode('utf-8'))
|
||||
field_names = [field["name"] for field in form_desc["fields"]]
|
||||
self.assertEqual(field_names, [
|
||||
"email",
|
||||
"name",
|
||||
"username",
|
||||
"password",
|
||||
"favorite_movie",
|
||||
"favorite_editor",
|
||||
"confirm_email",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
])
|
||||
|
||||
@override_settings(
|
||||
REGISTRATION_EXTRA_FIELDS={
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"city": "optional",
|
||||
"state": "optional",
|
||||
"country": "required",
|
||||
"honor_code": "required",
|
||||
"confirm_email": "required",
|
||||
},
|
||||
REGISTRATION_FIELD_ORDER=[
|
||||
"name",
|
||||
"username",
|
||||
"email",
|
||||
"confirm_email",
|
||||
"password",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"company",
|
||||
"title",
|
||||
"job_title",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
"terms_of_service",
|
||||
"specialty",
|
||||
"profession",
|
||||
],
|
||||
)
|
||||
def test_field_order_override(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that all fields render in the correct order
|
||||
form_desc = json.loads(response.content.decode('utf-8'))
|
||||
field_names = [field["name"] for field in form_desc["fields"]]
|
||||
self.assertEqual(field_names, [
|
||||
"name",
|
||||
"username",
|
||||
"email",
|
||||
"confirm_email",
|
||||
"password",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
])
|
||||
|
||||
@override_settings(
|
||||
REGISTRATION_EXTRA_FIELDS={
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"city": "optional",
|
||||
"state": "optional",
|
||||
"country": "required",
|
||||
"honor_code": "required",
|
||||
"confirm_email": "required",
|
||||
},
|
||||
REGISTRATION_EXTENSION_FORM='openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm',
|
||||
REGISTRATION_FIELD_ORDER=[
|
||||
"name",
|
||||
"confirm_email",
|
||||
"password",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"company",
|
||||
"title",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
"terms_of_service",
|
||||
],
|
||||
)
|
||||
def test_field_order_invalid_override(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that all fields render in the correct order
|
||||
form_desc = json.loads(response.content.decode('utf-8'))
|
||||
field_names = [field["name"] for field in form_desc["fields"]]
|
||||
self.assertEqual(field_names, [
|
||||
"email",
|
||||
"name",
|
||||
"username",
|
||||
"password",
|
||||
"favorite_movie",
|
||||
"favorite_editor",
|
||||
"confirm_email",
|
||||
"city",
|
||||
"state",
|
||||
"country",
|
||||
"gender",
|
||||
"year_of_birth",
|
||||
"level_of_education",
|
||||
"mailing_address",
|
||||
"goals",
|
||||
"honor_code",
|
||||
])
|
||||
|
||||
def test_register(self):
|
||||
# Create a new registration
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
self.assertIn(settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, self.client.cookies)
|
||||
self.assertIn(settings.EDXMKTG_USER_INFO_COOKIE_NAME, self.client.cookies)
|
||||
|
||||
user = User.objects.get(username=self.USERNAME)
|
||||
request = RequestFactory().get('/url')
|
||||
request.user = user
|
||||
account_settings = get_account_settings(request)[0]
|
||||
|
||||
self.assertEqual(self.USERNAME, account_settings["username"])
|
||||
self.assertEqual(self.EMAIL, account_settings["email"])
|
||||
self.assertFalse(account_settings["is_active"])
|
||||
self.assertEqual(self.NAME, account_settings["name"])
|
||||
|
||||
# Verify that we've been logged in
|
||||
# by trying to access a page that requires authentication
|
||||
response = self.client.get(reverse("dashboard"))
|
||||
self.assertHttpOK(response)
|
||||
|
||||
@override_settings(REGISTRATION_EXTRA_FIELDS={
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"country": "required",
|
||||
})
|
||||
def test_register_with_profile_info(self):
|
||||
# Register, providing lots of demographic info
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"level_of_education": self.EDUCATION,
|
||||
"mailing_address": self.ADDRESS,
|
||||
"year_of_birth": self.YEAR_OF_BIRTH,
|
||||
"goals": self.GOALS,
|
||||
"country": self.COUNTRY,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify the user's account
|
||||
user = User.objects.get(username=self.USERNAME)
|
||||
request = RequestFactory().get('/url')
|
||||
request.user = user
|
||||
account_settings = get_account_settings(request)[0]
|
||||
|
||||
self.assertEqual(account_settings["level_of_education"], self.EDUCATION)
|
||||
self.assertEqual(account_settings["mailing_address"], self.ADDRESS)
|
||||
self.assertEqual(account_settings["year_of_birth"], int(self.YEAR_OF_BIRTH))
|
||||
self.assertEqual(account_settings["goals"], self.GOALS)
|
||||
self.assertEqual(account_settings["country"], self.COUNTRY)
|
||||
|
||||
@override_settings(REGISTRATION_EXTENSION_FORM='openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm')
|
||||
@mock.patch('openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm.DUMMY_STORAGE', new_callable=dict)
|
||||
@mock.patch(
|
||||
'openedx.core.djangoapps.user_api.tests.test_helpers.DummyRegistrationExtensionModel',
|
||||
)
|
||||
def test_with_extended_form(self, dummy_model, storage_dict):
|
||||
dummy_model_instance = mock.Mock()
|
||||
dummy_model.return_value = dummy_model_instance
|
||||
# Create a new registration
|
||||
self.assertEqual(storage_dict, {})
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
"favorite_movie": "Inception",
|
||||
"favorite_editor": "cat",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
self.assertIn(settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, self.client.cookies)
|
||||
self.assertIn(settings.EDXMKTG_USER_INFO_COOKIE_NAME, self.client.cookies)
|
||||
|
||||
user = User.objects.get(username=self.USERNAME)
|
||||
request = RequestFactory().get('/url')
|
||||
request.user = user
|
||||
account_settings = get_account_settings(request)[0]
|
||||
|
||||
self.assertEqual(self.USERNAME, account_settings["username"])
|
||||
self.assertEqual(self.EMAIL, account_settings["email"])
|
||||
self.assertFalse(account_settings["is_active"])
|
||||
self.assertEqual(self.NAME, account_settings["name"])
|
||||
|
||||
self.assertEqual(storage_dict, {'favorite_movie': "Inception", "favorite_editor": "cat"})
|
||||
self.assertEqual(dummy_model_instance.user, user)
|
||||
|
||||
# Verify that we've been logged in
|
||||
# by trying to access a page that requires authentication
|
||||
response = self.client.get(reverse("dashboard"))
|
||||
self.assertHttpOK(response)
|
||||
|
||||
def test_activation_email(self):
|
||||
# Register, which should trigger an activation email
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that the activation email was sent
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
sent_email = mail.outbox[0]
|
||||
self.assertEqual(sent_email.to, [self.EMAIL])
|
||||
self.assertEqual(
|
||||
sent_email.subject,
|
||||
u"Action Required: Activate your {platform} account".format(platform=settings.PLATFORM_NAME)
|
||||
)
|
||||
self.assertIn(
|
||||
u"high-quality {platform} courses".format(platform=settings.PLATFORM_NAME),
|
||||
sent_email.body
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
{"email": ""},
|
||||
{"email": "invalid"},
|
||||
{"name": ""},
|
||||
{"username": ""},
|
||||
{"username": "a"},
|
||||
{"password": ""},
|
||||
)
|
||||
def test_register_invalid_input(self, invalid_fields):
|
||||
# Initially, the field values are all valid
|
||||
data = {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
}
|
||||
|
||||
# Override the valid fields, making the input invalid
|
||||
data.update(invalid_fields)
|
||||
|
||||
# Attempt to create the account, expecting an error response
|
||||
response = self.client.post(self.url, data)
|
||||
self.assertHttpBadRequest(response)
|
||||
|
||||
@override_settings(REGISTRATION_EXTRA_FIELDS={"country": "required"})
|
||||
@ddt.data("email", "name", "username", "password", "country")
|
||||
def test_register_missing_required_field(self, missing_field):
|
||||
data = {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"country": self.COUNTRY,
|
||||
}
|
||||
|
||||
del data[missing_field]
|
||||
|
||||
# Send a request missing a field
|
||||
response = self.client.post(self.url, data)
|
||||
self.assertHttpBadRequest(response)
|
||||
|
||||
def test_register_duplicate_email(self):
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same email address
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": "someone_else",
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_duplicate_username(self):
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same username
|
||||
response = self.client.post(self.url, {
|
||||
"email": "someone+else@example.com",
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different username."
|
||||
).format(
|
||||
self.USERNAME
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
def test_register_duplicate_username_and_email(self):
|
||||
# Register the first user
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Try to create a second user with the same username
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": "Someone Else",
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
"honor_code": "true",
|
||||
})
|
||||
self.assertEqual(response.status_code, 409)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"username": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different username."
|
||||
).format(
|
||||
self.USERNAME
|
||||
)
|
||||
}],
|
||||
"email": [{
|
||||
"user_message": (
|
||||
u"It looks like {} belongs to an existing account. "
|
||||
"Try again with a different email address."
|
||||
).format(
|
||||
self.EMAIL
|
||||
)
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
@override_settings(REGISTRATION_EXTRA_FIELDS={"honor_code": "hidden", "terms_of_service": "hidden"})
|
||||
def test_register_hidden_honor_code_and_terms_of_service(self):
|
||||
response = self.client.post(self.url, {
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"username": self.USERNAME,
|
||||
"password": self.PASSWORD,
|
||||
})
|
||||
self.assertHttpOK(response)
|
||||
|
||||
def test_missing_fields(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{
|
||||
"email": self.EMAIL,
|
||||
"name": self.NAME,
|
||||
"honor_code": "true",
|
||||
}
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
u"success": False,
|
||||
u"username": [{u"user_message": USERNAME_BAD_LENGTH_MSG}],
|
||||
u"password": [{u"user_message": u"This field is required."}],
|
||||
}
|
||||
)
|
||||
|
||||
def test_country_overrides(self):
|
||||
"""Test that overridden countries are available in country list."""
|
||||
# Retrieve the registration form description
|
||||
with override_settings(REGISTRATION_EXTRA_FIELDS={"country": "required"}):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
self.assertContains(response, 'Kosovo')
|
||||
|
||||
def test_create_account_not_allowed(self):
|
||||
"""
|
||||
Test case to check user creation is forbidden when ALLOW_PUBLIC_ACCOUNT_CREATION feature flag is turned off
|
||||
"""
|
||||
def _side_effect_for_get_value(value, default=None):
|
||||
"""
|
||||
returns a side_effect with given return value for a given value
|
||||
"""
|
||||
if value == 'ALLOW_PUBLIC_ACCOUNT_CREATION':
|
||||
return False
|
||||
else:
|
||||
return get_value(value, default)
|
||||
|
||||
with mock.patch('openedx.core.djangoapps.site_configuration.helpers.get_value') as mock_get_value:
|
||||
mock_get_value.side_effect = _side_effect_for_get_value
|
||||
response = self.client.post(self.url, {"email": self.EMAIL, "username": self.USERNAME})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def _assert_fields_match(self, actual_field, expected_field):
|
||||
"""
|
||||
Assert that the actual field and the expected field values match.
|
||||
"""
|
||||
self.assertIsNot(
|
||||
actual_field, None,
|
||||
msg=u"Could not find field {name}".format(name=expected_field["name"])
|
||||
)
|
||||
|
||||
for key in expected_field:
|
||||
self.assertEqual(
|
||||
actual_field[key], expected_field[key],
|
||||
msg=u"Expected {expected} for {key} but got {actual} instead".format(
|
||||
key=key,
|
||||
actual=actual_field[key],
|
||||
expected=expected_field[key]
|
||||
)
|
||||
)
|
||||
|
||||
def _populate_always_present_fields(self, field):
|
||||
"""
|
||||
Populate field dictionary with keys and values that are always present.
|
||||
"""
|
||||
defaults = [
|
||||
("label", ""),
|
||||
("instructions", ""),
|
||||
("placeholder", ""),
|
||||
("defaultValue", ""),
|
||||
("restrictions", {}),
|
||||
("errorMessages", {}),
|
||||
]
|
||||
field.update({
|
||||
key: value
|
||||
for key, value in defaults if key not in field
|
||||
})
|
||||
|
||||
def _assert_reg_field(self, extra_fields_setting, expected_field):
|
||||
"""
|
||||
Retrieve the registration form description from the server and
|
||||
verify that it contains the expected field.
|
||||
|
||||
Args:
|
||||
extra_fields_setting (dict): Override the Django setting controlling
|
||||
which extra fields are displayed in the form.
|
||||
expected_field (dict): The field definition we expect to find in the form.
|
||||
|
||||
Raises:
|
||||
AssertionError
|
||||
"""
|
||||
# Add in fields that are always present
|
||||
self._populate_always_present_fields(expected_field)
|
||||
|
||||
# Retrieve the registration form description
|
||||
with override_settings(REGISTRATION_EXTRA_FIELDS=extra_fields_setting):
|
||||
response = self.client.get(self.url)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
# Verify that the form description matches what we'd expect
|
||||
form_desc = json.loads(response.content.decode('utf-8'))
|
||||
|
||||
actual_field = None
|
||||
for field in form_desc["fields"]:
|
||||
if field["name"] == expected_field["name"]:
|
||||
actual_field = field
|
||||
break
|
||||
|
||||
self._assert_fields_match(actual_field, expected_field)
|
||||
|
||||
def _assert_password_field_hidden(self, field_settings):
|
||||
self._assert_reg_field(field_settings, {
|
||||
"name": "password",
|
||||
"type": "hidden",
|
||||
"required": False
|
||||
})
|
||||
|
||||
def _assert_social_auth_provider_present(self, field_settings, backend):
|
||||
self._assert_reg_field(field_settings, {
|
||||
"name": "social_auth_provider",
|
||||
"type": "hidden",
|
||||
"required": False,
|
||||
"defaultValue": backend.name
|
||||
})
|
||||
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.ddt
|
||||
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for the User API registration endpoint with 3rd party authentication.
|
||||
"""
|
||||
CREATE_USER = False
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
__test__ = False
|
||||
|
||||
def setUp(self):
|
||||
super(ThirdPartyRegistrationTestMixin, self).setUp()
|
||||
self.url = reverse('user_api_registration')
|
||||
|
||||
def tearDown(self):
|
||||
super(ThirdPartyRegistrationTestMixin, self).tearDown()
|
||||
Partial.objects.all().delete()
|
||||
|
||||
def data(self, user=None):
|
||||
"""Returns the request data for the endpoint."""
|
||||
return {
|
||||
"provider": self.BACKEND,
|
||||
"access_token": self.access_token,
|
||||
"client_id": self.client_id,
|
||||
"honor_code": "true",
|
||||
"country": "US",
|
||||
"username": user.username if user else "test_username",
|
||||
"name": user.first_name if user else "test name",
|
||||
"email": user.email if user else "test@test.com"
|
||||
}
|
||||
|
||||
def _assert_existing_user_error(self, response):
|
||||
"""Assert that the given response was an error with the given status_code and error code."""
|
||||
self.assertEqual(response.status_code, 409)
|
||||
errors = json.loads(response.content.decode('utf-8'))
|
||||
for conflict_attribute in ["username", "email"]:
|
||||
self.assertIn(conflict_attribute, errors)
|
||||
self.assertIn("belongs to an existing account", errors[conflict_attribute][0]["user_message"])
|
||||
|
||||
def _assert_access_token_error(self, response, expected_error_message):
|
||||
"""Assert that the given response was an error for the access_token field with the given error message."""
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"access_token": [{"user_message": expected_error_message}],
|
||||
}
|
||||
)
|
||||
|
||||
def _assert_third_party_session_expired_error(self, response, expected_error_message):
|
||||
"""Assert that given response is an error due to third party session expiry"""
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertDictEqual(
|
||||
response_json,
|
||||
{
|
||||
"success": False,
|
||||
"session_expired": [{"user_message": expected_error_message}],
|
||||
}
|
||||
)
|
||||
|
||||
def _verify_user_existence(self, user_exists, social_link_exists, user_is_active=None, username=None):
|
||||
"""Verifies whether the user object exists."""
|
||||
users = User.objects.filter(username=(username if username else "test_username"))
|
||||
self.assertEquals(users.exists(), user_exists)
|
||||
if user_exists:
|
||||
self.assertEquals(users[0].is_active, user_is_active)
|
||||
self.assertEqual(
|
||||
UserSocialAuth.objects.filter(user=users[0], provider=self.BACKEND).exists(),
|
||||
social_link_exists
|
||||
)
|
||||
else:
|
||||
self.assertEquals(UserSocialAuth.objects.count(), 0)
|
||||
|
||||
def test_success(self):
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
self._setup_provider_response(success=True)
|
||||
response = self.client.post(self.url, self.data())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self._verify_user_existence(user_exists=True, social_link_exists=True, user_is_active=False)
|
||||
|
||||
def test_unlinked_active_user(self):
|
||||
user = UserFactory()
|
||||
response = self.client.post(self.url, self.data(user))
|
||||
self._assert_existing_user_error(response)
|
||||
self._verify_user_existence(
|
||||
user_exists=True, social_link_exists=False, user_is_active=True, username=user.username
|
||||
)
|
||||
|
||||
def test_unlinked_inactive_user(self):
|
||||
user = UserFactory(is_active=False)
|
||||
response = self.client.post(self.url, self.data(user))
|
||||
self._assert_existing_user_error(response)
|
||||
self._verify_user_existence(
|
||||
user_exists=True, social_link_exists=False, user_is_active=False, username=user.username
|
||||
)
|
||||
|
||||
def test_user_already_registered(self):
|
||||
self._setup_provider_response(success=True)
|
||||
user = UserFactory()
|
||||
UserSocialAuth.objects.create(user=user, provider=self.BACKEND, uid=self.social_uid)
|
||||
response = self.client.post(self.url, self.data(user))
|
||||
self._assert_existing_user_error(response)
|
||||
self._verify_user_existence(
|
||||
user_exists=True, social_link_exists=True, user_is_active=True, username=user.username
|
||||
)
|
||||
|
||||
def test_social_user_conflict(self):
|
||||
self._setup_provider_response(success=True)
|
||||
user = UserFactory()
|
||||
UserSocialAuth.objects.create(user=user, provider=self.BACKEND, uid=self.social_uid)
|
||||
response = self.client.post(self.url, self.data())
|
||||
self._assert_access_token_error(response, "The provided access_token is already associated with another user.")
|
||||
self._verify_user_existence(
|
||||
user_exists=True, social_link_exists=True, user_is_active=True, username=user.username
|
||||
)
|
||||
|
||||
def test_invalid_token(self):
|
||||
self._setup_provider_response(success=False)
|
||||
response = self.client.post(self.url, self.data())
|
||||
self._assert_access_token_error(response, "The provided access_token is not valid.")
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
def test_missing_token(self):
|
||||
data = self.data()
|
||||
data.pop("access_token")
|
||||
response = self.client.post(self.url, data)
|
||||
self._assert_access_token_error(
|
||||
response,
|
||||
u"An access_token is required when passing value ({}) for provider.".format(self.BACKEND)
|
||||
)
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
def test_expired_pipeline(self):
|
||||
"""
|
||||
Test that there is an error and account is not created
|
||||
when request is made for account creation using third (Google, Facebook etc) party with pipeline
|
||||
getting expired using browser (not mobile application).
|
||||
|
||||
NOTE: We are NOT using actual pipeline here so pipeline is always expired in this environment.
|
||||
we don't have to explicitly expire pipeline.
|
||||
"""
|
||||
data = self.data()
|
||||
# provider is sent along request when request is made from mobile application
|
||||
data.pop("provider")
|
||||
# to identify that request is made using browser
|
||||
data.update({"social_auth_provider": "Google"})
|
||||
response = self.client.post(self.url, data)
|
||||
self._assert_third_party_session_expired_error(
|
||||
response,
|
||||
u"Registration using {provider} has timed out.".format(provider="Google")
|
||||
)
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
|
||||
@skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled")
|
||||
class TestFacebookRegistrationView(
|
||||
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase
|
||||
):
|
||||
"""Tests the User API registration endpoint with Facebook authentication."""
|
||||
__test__ = True
|
||||
|
||||
def test_social_auth_exception(self):
|
||||
"""
|
||||
According to the do_auth method in social_core.backends.facebook.py,
|
||||
the Facebook API sometimes responds back a JSON with just False as value.
|
||||
"""
|
||||
self._setup_provider_response_with_body(200, json.dumps("false"))
|
||||
response = self.client.post(self.url, self.data())
|
||||
self._assert_access_token_error(response, "The provided access_token is not valid.")
|
||||
self._verify_user_existence(user_exists=False, social_link_exists=False)
|
||||
|
||||
|
||||
@skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled")
|
||||
class TestGoogleRegistrationView(
|
||||
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase
|
||||
):
|
||||
"""Tests the User API registration endpoint with Google authentication."""
|
||||
__test__ = True
|
||||
Reference in New Issue
Block a user