Change third party auth login failure code to a 401, to detect authentication success with no linked account. If already authenticated, redirect immediately to the dashboard. Use "Location" header correctly for 302 redirects from student views. Add utility functions for simulating a running third-party auth pipeline. Add a utility function for checking whether third party auth is enabled. Respect default values sent by the server
1379 lines
49 KiB
Python
1379 lines
49 KiB
Python
"""Tests for the user API at the HTTP request level. """
|
|
|
|
import datetime
|
|
import base64
|
|
import json
|
|
import re
|
|
|
|
from django.conf import settings
|
|
from django.core.urlresolvers import reverse
|
|
from django.core import mail
|
|
from django.test import TestCase
|
|
from django.test.utils import override_settings
|
|
from unittest import SkipTest, skipUnless
|
|
import ddt
|
|
from pytz import UTC
|
|
import mock
|
|
|
|
from user_api.api import account as account_api, profile as profile_api
|
|
|
|
from student.tests.factories import UserFactory
|
|
from user_api.tests.factories import UserPreferenceFactory
|
|
from django_comment_common import models
|
|
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
|
from third_party_auth.tests.testutil import simulate_running_pipeline
|
|
|
|
from user_api.tests.test_constants import SORTED_COUNTRIES
|
|
|
|
|
|
TEST_API_KEY = "test_api_key"
|
|
USER_LIST_URI = "/user_api/v1/users/"
|
|
USER_PREFERENCE_LIST_URI = "/user_api/v1/user_prefs/"
|
|
ROLE_LIST_URI = "/user_api/v1/forum_roles/Moderator/users/"
|
|
|
|
|
|
@override_settings(EDX_API_KEY=TEST_API_KEY)
|
|
class ApiTestCase(TestCase):
|
|
|
|
LIST_URI = USER_LIST_URI
|
|
|
|
def basic_auth(self, username, password):
|
|
return {'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('%s:%s' % (username, password))}
|
|
|
|
def request_with_auth(self, method, *args, **kwargs):
|
|
"""Issue a get request to the given URI with the API key header"""
|
|
return getattr(self.client, method)(*args, HTTP_X_EDX_API_KEY=TEST_API_KEY, **kwargs)
|
|
|
|
def get_json(self, *args, **kwargs):
|
|
"""Make a request with the given args and return the parsed JSON repsonse"""
|
|
resp = self.request_with_auth("get", *args, **kwargs)
|
|
self.assertHttpOK(resp)
|
|
self.assertTrue(resp["Content-Type"].startswith("application/json"))
|
|
return json.loads(resp.content)
|
|
|
|
def get_uri_for_user(self, target_user):
|
|
"""Given a user object, get the URI for the corresponding resource"""
|
|
users = self.get_json(USER_LIST_URI)["results"]
|
|
for user in users:
|
|
if user["id"] == target_user.id:
|
|
return user["url"]
|
|
self.fail()
|
|
|
|
def get_uri_for_pref(self, target_pref):
|
|
"""Given a user preference object, get the URI for the corresponding resource"""
|
|
prefs = self.get_json(USER_PREFERENCE_LIST_URI)["results"]
|
|
for pref in prefs:
|
|
if (pref["user"]["id"] == target_pref.user.id and pref["key"] == target_pref.key):
|
|
return pref["url"]
|
|
self.fail()
|
|
|
|
def assertAllowedMethods(self, uri, expected_methods):
|
|
"""Assert that the allowed methods for the given URI match the expected list"""
|
|
resp = self.request_with_auth("options", uri)
|
|
self.assertHttpOK(resp)
|
|
allow_header = resp.get("Allow")
|
|
self.assertIsNotNone(allow_header)
|
|
allowed_methods = re.split('[^A-Z]+', allow_header)
|
|
self.assertItemsEqual(allowed_methods, expected_methods)
|
|
|
|
def assertSelfReferential(self, obj):
|
|
"""Assert that accessing the "url" entry in the given object returns the same object"""
|
|
copy = self.get_json(obj["url"])
|
|
self.assertEqual(obj, copy)
|
|
|
|
def assertUserIsValid(self, user):
|
|
"""Assert that the given user result is valid"""
|
|
self.assertItemsEqual(user.keys(), ["email", "id", "name", "username", "preferences", "url"])
|
|
self.assertItemsEqual(
|
|
user["preferences"].items(),
|
|
[(pref.key, pref.value) for pref in self.prefs if pref.user.id == user["id"]]
|
|
)
|
|
self.assertSelfReferential(user)
|
|
|
|
def assertPrefIsValid(self, pref):
|
|
self.assertItemsEqual(pref.keys(), ["user", "key", "value", "url"])
|
|
self.assertSelfReferential(pref)
|
|
self.assertUserIsValid(pref["user"])
|
|
|
|
def assertHttpOK(self, response):
|
|
"""Assert that the given response has the status code 200"""
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def assertHttpForbidden(self, response):
|
|
"""Assert that the given response has the status code 403"""
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def assertHttpBadRequest(self, response):
|
|
"""Assert that the given response has the status code 400"""
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def assertHttpMethodNotAllowed(self, response):
|
|
"""Assert that the given response has the status code 405"""
|
|
self.assertEqual(response.status_code, 405)
|
|
|
|
|
|
class EmptyUserTestCase(ApiTestCase):
|
|
def test_get_list_empty(self):
|
|
result = self.get_json(self.LIST_URI)
|
|
self.assertEqual(result["count"], 0)
|
|
self.assertIsNone(result["next"])
|
|
self.assertIsNone(result["previous"])
|
|
self.assertEqual(result["results"], [])
|
|
|
|
|
|
class EmptyRoleTestCase(ApiTestCase):
|
|
"""Test that the endpoint supports empty result sets"""
|
|
course_id = SlashSeparatedCourseKey.from_deprecated_string("org/course/run")
|
|
LIST_URI = ROLE_LIST_URI + "?course_id=" + course_id.to_deprecated_string()
|
|
|
|
def test_get_list_empty(self):
|
|
"""Test that the endpoint properly returns empty result sets"""
|
|
result = self.get_json(self.LIST_URI)
|
|
self.assertEqual(result["count"], 0)
|
|
self.assertIsNone(result["next"])
|
|
self.assertIsNone(result["previous"])
|
|
self.assertEqual(result["results"], [])
|
|
|
|
|
|
class UserApiTestCase(ApiTestCase):
|
|
def setUp(self):
|
|
super(UserApiTestCase, self).setUp()
|
|
self.users = [
|
|
UserFactory.create(
|
|
email="test{0}@test.org".format(i),
|
|
profile__name="Test {0}".format(i)
|
|
)
|
|
for i in range(5)
|
|
]
|
|
self.prefs = [
|
|
UserPreferenceFactory.create(user=self.users[0], key="key0"),
|
|
UserPreferenceFactory.create(user=self.users[0], key="key1"),
|
|
UserPreferenceFactory.create(user=self.users[1], key="key0")
|
|
]
|
|
|
|
|
|
class RoleTestCase(UserApiTestCase):
|
|
course_id = SlashSeparatedCourseKey.from_deprecated_string("org/course/run")
|
|
LIST_URI = ROLE_LIST_URI + "?course_id=" + course_id.to_deprecated_string()
|
|
|
|
def setUp(self):
|
|
super(RoleTestCase, self).setUp()
|
|
(role, _) = models.Role.objects.get_or_create(
|
|
name=models.FORUM_ROLE_MODERATOR,
|
|
course_id=self.course_id
|
|
)
|
|
for user in self.users:
|
|
user.roles.add(role)
|
|
|
|
def test_options_list(self):
|
|
self.assertAllowedMethods(self.LIST_URI, ["OPTIONS", "GET", "HEAD"])
|
|
|
|
def test_post_list_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("post", self.LIST_URI))
|
|
|
|
def test_put_list_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.LIST_URI))
|
|
|
|
def test_patch_list_not_allowed(self):
|
|
raise SkipTest("Django 1.4's test client does not support patch")
|
|
|
|
def test_delete_list_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.LIST_URI))
|
|
|
|
def test_list_unauthorized(self):
|
|
self.assertHttpForbidden(self.client.get(self.LIST_URI))
|
|
|
|
@override_settings(DEBUG=True)
|
|
@override_settings(EDX_API_KEY=None)
|
|
def test_debug_auth(self):
|
|
self.assertHttpOK(self.client.get(self.LIST_URI))
|
|
|
|
@override_settings(DEBUG=False)
|
|
@override_settings(EDX_API_KEY=TEST_API_KEY)
|
|
def test_basic_auth(self):
|
|
# ensure that having basic auth headers in the mix does not break anything
|
|
self.assertHttpOK(
|
|
self.request_with_auth("get", self.LIST_URI,
|
|
**self.basic_auth("someuser", "somepass")))
|
|
self.assertHttpForbidden(
|
|
self.client.get(self.LIST_URI, **self.basic_auth("someuser", "somepass")))
|
|
|
|
def test_get_list_nonempty(self):
|
|
result = self.get_json(self.LIST_URI)
|
|
users = result["results"]
|
|
self.assertEqual(result["count"], len(self.users))
|
|
self.assertEqual(len(users), len(self.users))
|
|
self.assertIsNone(result["next"])
|
|
self.assertIsNone(result["previous"])
|
|
for user in users:
|
|
self.assertUserIsValid(user)
|
|
|
|
def test_required_parameter(self):
|
|
response = self.request_with_auth("get", ROLE_LIST_URI)
|
|
self.assertHttpBadRequest(response)
|
|
|
|
def test_get_list_pagination(self):
|
|
first_page = self.get_json(self.LIST_URI, data={
|
|
"page_size": 3,
|
|
"course_id": self.course_id.to_deprecated_string(),
|
|
})
|
|
self.assertEqual(first_page["count"], 5)
|
|
first_page_next_uri = first_page["next"]
|
|
self.assertIsNone(first_page["previous"])
|
|
first_page_users = first_page["results"]
|
|
self.assertEqual(len(first_page_users), 3)
|
|
|
|
second_page = self.get_json(first_page_next_uri)
|
|
self.assertEqual(second_page["count"], 5)
|
|
self.assertIsNone(second_page["next"])
|
|
second_page_prev_uri = second_page["previous"]
|
|
second_page_users = second_page["results"]
|
|
self.assertEqual(len(second_page_users), 2)
|
|
|
|
self.assertEqual(self.get_json(second_page_prev_uri), first_page)
|
|
|
|
for user in first_page_users + second_page_users:
|
|
self.assertUserIsValid(user)
|
|
all_user_uris = [user["url"] for user in first_page_users + second_page_users]
|
|
self.assertEqual(len(set(all_user_uris)), 5)
|
|
|
|
|
|
class UserViewSetTest(UserApiTestCase):
|
|
LIST_URI = USER_LIST_URI
|
|
|
|
def setUp(self):
|
|
super(UserViewSetTest, self).setUp()
|
|
self.detail_uri = self.get_uri_for_user(self.users[0])
|
|
|
|
# List view tests
|
|
|
|
def test_options_list(self):
|
|
self.assertAllowedMethods(self.LIST_URI, ["OPTIONS", "GET", "HEAD"])
|
|
|
|
def test_post_list_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("post", self.LIST_URI))
|
|
|
|
def test_put_list_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.LIST_URI))
|
|
|
|
def test_patch_list_not_allowed(self):
|
|
raise SkipTest("Django 1.4's test client does not support patch")
|
|
|
|
def test_delete_list_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.LIST_URI))
|
|
|
|
def test_list_unauthorized(self):
|
|
self.assertHttpForbidden(self.client.get(self.LIST_URI))
|
|
|
|
@override_settings(DEBUG=True)
|
|
@override_settings(EDX_API_KEY=None)
|
|
def test_debug_auth(self):
|
|
self.assertHttpOK(self.client.get(self.LIST_URI))
|
|
|
|
@override_settings(DEBUG=False)
|
|
@override_settings(EDX_API_KEY=TEST_API_KEY)
|
|
def test_basic_auth(self):
|
|
# ensure that having basic auth headers in the mix does not break anything
|
|
self.assertHttpOK(
|
|
self.request_with_auth("get", self.LIST_URI,
|
|
**self.basic_auth('someuser', 'somepass')))
|
|
self.assertHttpForbidden(
|
|
self.client.get(self.LIST_URI, **self.basic_auth('someuser', 'somepass')))
|
|
|
|
def test_get_list_nonempty(self):
|
|
result = self.get_json(self.LIST_URI)
|
|
self.assertEqual(result["count"], 5)
|
|
self.assertIsNone(result["next"])
|
|
self.assertIsNone(result["previous"])
|
|
users = result["results"]
|
|
self.assertEqual(len(users), 5)
|
|
for user in users:
|
|
self.assertUserIsValid(user)
|
|
|
|
def test_get_list_pagination(self):
|
|
first_page = self.get_json(self.LIST_URI, data={"page_size": 3})
|
|
self.assertEqual(first_page["count"], 5)
|
|
first_page_next_uri = first_page["next"]
|
|
self.assertIsNone(first_page["previous"])
|
|
first_page_users = first_page["results"]
|
|
self.assertEqual(len(first_page_users), 3)
|
|
|
|
second_page = self.get_json(first_page_next_uri)
|
|
self.assertEqual(second_page["count"], 5)
|
|
self.assertIsNone(second_page["next"])
|
|
second_page_prev_uri = second_page["previous"]
|
|
second_page_users = second_page["results"]
|
|
self.assertEqual(len(second_page_users), 2)
|
|
|
|
self.assertEqual(self.get_json(second_page_prev_uri), first_page)
|
|
|
|
for user in first_page_users + second_page_users:
|
|
self.assertUserIsValid(user)
|
|
all_user_uris = [user["url"] for user in first_page_users + second_page_users]
|
|
self.assertEqual(len(set(all_user_uris)), 5)
|
|
|
|
# Detail view tests
|
|
|
|
def test_options_detail(self):
|
|
self.assertAllowedMethods(self.detail_uri, ["OPTIONS", "GET", "HEAD"])
|
|
|
|
def test_post_detail_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("post", self.detail_uri))
|
|
|
|
def test_put_detail_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.detail_uri))
|
|
|
|
def test_patch_detail_not_allowed(self):
|
|
raise SkipTest("Django 1.4's test client does not support patch")
|
|
|
|
def test_delete_detail_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.detail_uri))
|
|
|
|
def test_get_detail_unauthorized(self):
|
|
self.assertHttpForbidden(self.client.get(self.detail_uri))
|
|
|
|
def test_get_detail(self):
|
|
user = self.users[1]
|
|
uri = self.get_uri_for_user(user)
|
|
self.assertEqual(
|
|
self.get_json(uri),
|
|
{
|
|
"email": user.email,
|
|
"id": user.id,
|
|
"name": user.profile.name,
|
|
"username": user.username,
|
|
"preferences": dict([
|
|
(user_pref.key, user_pref.value)
|
|
for user_pref in self.prefs
|
|
if user_pref.user == user
|
|
]),
|
|
"url": uri
|
|
}
|
|
)
|
|
|
|
|
|
class UserPreferenceViewSetTest(UserApiTestCase):
|
|
LIST_URI = USER_PREFERENCE_LIST_URI
|
|
|
|
def setUp(self):
|
|
super(UserPreferenceViewSetTest, self).setUp()
|
|
self.detail_uri = self.get_uri_for_pref(self.prefs[0])
|
|
|
|
# List view tests
|
|
|
|
def test_options_list(self):
|
|
self.assertAllowedMethods(self.LIST_URI, ["OPTIONS", "GET", "HEAD"])
|
|
|
|
def test_put_list_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.LIST_URI))
|
|
|
|
def test_patch_list_not_allowed(self):
|
|
raise SkipTest("Django 1.4's test client does not support patch")
|
|
|
|
def test_delete_list_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.LIST_URI))
|
|
|
|
def test_list_unauthorized(self):
|
|
self.assertHttpForbidden(self.client.get(self.LIST_URI))
|
|
|
|
@override_settings(DEBUG=True)
|
|
@override_settings(EDX_API_KEY=None)
|
|
def test_debug_auth(self):
|
|
self.assertHttpOK(self.client.get(self.LIST_URI))
|
|
|
|
def test_get_list_nonempty(self):
|
|
result = self.get_json(self.LIST_URI)
|
|
self.assertEqual(result["count"], 3)
|
|
self.assertIsNone(result["next"])
|
|
self.assertIsNone(result["previous"])
|
|
prefs = result["results"]
|
|
self.assertEqual(len(prefs), 3)
|
|
for pref in prefs:
|
|
self.assertPrefIsValid(pref)
|
|
|
|
def test_get_list_filter_key_empty(self):
|
|
result = self.get_json(self.LIST_URI, data={"key": "non-existent"})
|
|
self.assertEqual(result["count"], 0)
|
|
self.assertEqual(result["results"], [])
|
|
|
|
def test_get_list_filter_key_nonempty(self):
|
|
result = self.get_json(self.LIST_URI, data={"key": "key0"})
|
|
self.assertEqual(result["count"], 2)
|
|
prefs = result["results"]
|
|
self.assertEqual(len(prefs), 2)
|
|
for pref in prefs:
|
|
self.assertPrefIsValid(pref)
|
|
self.assertEqual(pref["key"], "key0")
|
|
|
|
def test_get_list_filter_user_empty(self):
|
|
def test_id(user_id):
|
|
result = self.get_json(self.LIST_URI, data={"user": user_id})
|
|
self.assertEqual(result["count"], 0)
|
|
self.assertEqual(result["results"], [])
|
|
test_id(self.users[2].id)
|
|
# TODO: If the given id does not match a user, then the filter is a no-op
|
|
# test_id(42)
|
|
# test_id("asdf")
|
|
|
|
def test_get_list_filter_user_nonempty(self):
|
|
user_id = self.users[0].id
|
|
result = self.get_json(self.LIST_URI, data={"user": user_id})
|
|
self.assertEqual(result["count"], 2)
|
|
prefs = result["results"]
|
|
self.assertEqual(len(prefs), 2)
|
|
for pref in prefs:
|
|
self.assertPrefIsValid(pref)
|
|
self.assertEqual(pref["user"]["id"], user_id)
|
|
|
|
def test_get_list_pagination(self):
|
|
first_page = self.get_json(self.LIST_URI, data={"page_size": 2})
|
|
self.assertEqual(first_page["count"], 3)
|
|
first_page_next_uri = first_page["next"]
|
|
self.assertIsNone(first_page["previous"])
|
|
first_page_prefs = first_page["results"]
|
|
self.assertEqual(len(first_page_prefs), 2)
|
|
|
|
second_page = self.get_json(first_page_next_uri)
|
|
self.assertEqual(second_page["count"], 3)
|
|
self.assertIsNone(second_page["next"])
|
|
second_page_prev_uri = second_page["previous"]
|
|
second_page_prefs = second_page["results"]
|
|
self.assertEqual(len(second_page_prefs), 1)
|
|
|
|
self.assertEqual(self.get_json(second_page_prev_uri), first_page)
|
|
|
|
for pref in first_page_prefs + second_page_prefs:
|
|
self.assertPrefIsValid(pref)
|
|
all_pref_uris = [pref["url"] for pref in first_page_prefs + second_page_prefs]
|
|
self.assertEqual(len(set(all_pref_uris)), 3)
|
|
|
|
# Detail view tests
|
|
|
|
def test_options_detail(self):
|
|
self.assertAllowedMethods(self.detail_uri, ["OPTIONS", "GET", "HEAD"])
|
|
|
|
def test_post_detail_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("post", self.detail_uri))
|
|
|
|
def test_put_detail_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.detail_uri))
|
|
|
|
def test_patch_detail_not_allowed(self):
|
|
raise SkipTest("Django 1.4's test client does not support patch")
|
|
|
|
def test_delete_detail_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.detail_uri))
|
|
|
|
def test_detail_unauthorized(self):
|
|
self.assertHttpForbidden(self.client.get(self.detail_uri))
|
|
|
|
def test_get_detail(self):
|
|
pref = self.prefs[1]
|
|
uri = self.get_uri_for_pref(pref)
|
|
self.assertEqual(
|
|
self.get_json(uri),
|
|
{
|
|
"user": {
|
|
"email": pref.user.email,
|
|
"id": pref.user.id,
|
|
"name": pref.user.profile.name,
|
|
"username": pref.user.username,
|
|
"preferences": dict([
|
|
(user_pref.key, user_pref.value)
|
|
for user_pref in self.prefs
|
|
if user_pref.user == pref.user
|
|
]),
|
|
"url": self.get_uri_for_user(pref.user),
|
|
},
|
|
"key": pref.key,
|
|
"value": pref.value,
|
|
"url": uri,
|
|
}
|
|
)
|
|
|
|
|
|
class PreferenceUsersListViewTest(UserApiTestCase):
|
|
LIST_URI = "/user_api/v1/preferences/key0/users/"
|
|
|
|
def test_options(self):
|
|
self.assertAllowedMethods(self.LIST_URI, ["OPTIONS", "GET", "HEAD"])
|
|
|
|
def test_put_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.LIST_URI))
|
|
|
|
def test_patch_not_allowed(self):
|
|
raise SkipTest("Django 1.4's test client does not support patch")
|
|
|
|
def test_delete_not_allowed(self):
|
|
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.LIST_URI))
|
|
|
|
def test_unauthorized(self):
|
|
self.assertHttpForbidden(self.client.get(self.LIST_URI))
|
|
|
|
@override_settings(DEBUG=True)
|
|
@override_settings(EDX_API_KEY=None)
|
|
def test_debug_auth(self):
|
|
self.assertHttpOK(self.client.get(self.LIST_URI))
|
|
|
|
def test_get_basic(self):
|
|
result = self.get_json(self.LIST_URI)
|
|
self.assertEqual(result["count"], 2)
|
|
self.assertIsNone(result["next"])
|
|
self.assertIsNone(result["previous"])
|
|
users = result["results"]
|
|
self.assertEqual(len(users), 2)
|
|
for user in users:
|
|
self.assertUserIsValid(user)
|
|
|
|
def test_get_pagination(self):
|
|
first_page = self.get_json(self.LIST_URI, data={"page_size": 1})
|
|
self.assertEqual(first_page["count"], 2)
|
|
first_page_next_uri = first_page["next"]
|
|
self.assertIsNone(first_page["previous"])
|
|
first_page_users = first_page["results"]
|
|
self.assertEqual(len(first_page_users), 1)
|
|
|
|
second_page = self.get_json(first_page_next_uri)
|
|
self.assertEqual(second_page["count"], 2)
|
|
self.assertIsNone(second_page["next"])
|
|
second_page_prev_uri = second_page["previous"]
|
|
second_page_users = second_page["results"]
|
|
self.assertEqual(len(second_page_users), 1)
|
|
|
|
self.assertEqual(self.get_json(second_page_prev_uri), first_page)
|
|
|
|
for user in first_page_users + second_page_users:
|
|
self.assertUserIsValid(user)
|
|
all_user_uris = [user["url"] for user in first_page_users + second_page_users]
|
|
self.assertEqual(len(set(all_user_uris)), 2)
|
|
|
|
|
|
@ddt.ddt
|
|
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
|
class LoginSessionViewTest(ApiTestCase):
|
|
"""Tests for the login end-points of the user API. """
|
|
|
|
USERNAME = "bob"
|
|
EMAIL = "bob@example.com"
|
|
PASSWORD = "password"
|
|
|
|
def setUp(self):
|
|
super(LoginSessionViewTest, self).setUp()
|
|
self.url = reverse("user_api_login_session")
|
|
|
|
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):
|
|
raise SkipTest("Django 1.4's test client does not support patch")
|
|
|
|
def test_login_form(self):
|
|
# Retrieve the login form
|
|
response = self.client.get(self.url, content_type="application/json")
|
|
self.assertHttpOK(response)
|
|
|
|
# Verify that the form description matches what we expect
|
|
form_desc = json.loads(response.content)
|
|
self.assertEqual(form_desc["method"], "post")
|
|
self.assertEqual(form_desc["submit_url"], self.url)
|
|
self.assertEqual(form_desc["fields"], [
|
|
{
|
|
"name": "email",
|
|
"default": "",
|
|
"type": "text",
|
|
"required": True,
|
|
"label": "E-mail",
|
|
"placeholder": "example: username@domain.com",
|
|
"instructions": "This is the e-mail address you used to register with edX",
|
|
"restrictions": {
|
|
"min_length": 3,
|
|
"max_length": 254
|
|
},
|
|
},
|
|
{
|
|
"name": "password",
|
|
"default": "",
|
|
"type": "password",
|
|
"required": True,
|
|
"label": "Password",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {
|
|
"min_length": 2,
|
|
"max_length": 75
|
|
},
|
|
},
|
|
{
|
|
"name": "remember",
|
|
"default": False,
|
|
"type": "checkbox",
|
|
"required": False,
|
|
"label": "Remember me",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
])
|
|
|
|
def test_login(self):
|
|
# Create a test user
|
|
UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
|
|
|
# Login
|
|
response = self.client.post(self.url, {
|
|
"email": self.EMAIL,
|
|
"password": self.PASSWORD,
|
|
})
|
|
self.assertHttpOK(response)
|
|
|
|
# Verify that we logged in successfully by accessing
|
|
# a page that requires authentication.
|
|
response = self.client.get(reverse("dashboard"))
|
|
self.assertHttpOK(response)
|
|
|
|
@ddt.data(
|
|
(json.dumps(True), False),
|
|
(json.dumps(False), True),
|
|
(None, True),
|
|
)
|
|
@ddt.unpack
|
|
def test_login_remember_me(self, remember_value, expire_at_browser_close):
|
|
# Create a test user
|
|
UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
|
|
|
# Login and remember me
|
|
data = {
|
|
"email": self.EMAIL,
|
|
"password": self.PASSWORD,
|
|
}
|
|
|
|
if remember_value is not None:
|
|
data["remember"] = remember_value
|
|
|
|
response = self.client.post(self.url, data)
|
|
self.assertHttpOK(response)
|
|
|
|
# Verify that the session expiration was set correctly
|
|
self.assertEqual(
|
|
self.client.session.get_expire_at_browser_close(),
|
|
expire_at_browser_close
|
|
)
|
|
|
|
def test_invalid_credentials(self):
|
|
# Create a test user
|
|
UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
|
|
|
# Invalid password
|
|
response = self.client.post(self.url, {
|
|
"email": self.EMAIL,
|
|
"password": "invalid"
|
|
})
|
|
self.assertHttpForbidden(response)
|
|
|
|
# Invalid email address
|
|
response = self.client.post(self.url, {
|
|
"email": "invalid@example.com",
|
|
"password": self.PASSWORD,
|
|
})
|
|
self.assertHttpForbidden(response)
|
|
|
|
def test_missing_login_params(self):
|
|
# Create a test user
|
|
UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
|
|
|
# Missing password
|
|
response = self.client.post(self.url, {
|
|
"email": self.EMAIL,
|
|
})
|
|
self.assertHttpBadRequest(response)
|
|
|
|
# Missing email
|
|
response = self.client.post(self.url, {
|
|
"password": self.PASSWORD,
|
|
})
|
|
self.assertHttpBadRequest(response)
|
|
|
|
# Missing both email and password
|
|
response = self.client.post(self.url, {})
|
|
|
|
|
|
@ddt.ddt
|
|
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
|
class RegistrationViewTest(ApiTestCase):
|
|
"""Tests for the registration end-points of the User API. """
|
|
|
|
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(RegistrationViewTest, self).setUp()
|
|
self.url = reverse("user_api_registration")
|
|
|
|
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):
|
|
raise SkipTest("Django 1.4's test client does not support patch")
|
|
|
|
def test_register_form_default_fields(self):
|
|
no_extra_fields_setting = {}
|
|
|
|
self._assert_reg_field(
|
|
no_extra_fields_setting,
|
|
{
|
|
u"name": u"email",
|
|
u"default": u"",
|
|
u"type": u"text",
|
|
u"required": True,
|
|
u"label": u"E-mail",
|
|
u"placeholder": u"example: username@domain.com",
|
|
u"instructions": u"This is the e-mail address you used to register with edX",
|
|
u"restrictions": {
|
|
u"min_length": 3,
|
|
u"max_length": 254
|
|
},
|
|
}
|
|
)
|
|
|
|
self._assert_reg_field(
|
|
no_extra_fields_setting,
|
|
{
|
|
u"name": u"name",
|
|
u"default": u"",
|
|
u"type": u"text",
|
|
u"required": True,
|
|
u"label": u"Full Name",
|
|
u"placeholder": u"",
|
|
u"instructions": u"Needed for any certificates you may earn",
|
|
u"restrictions": {
|
|
"max_length": 255,
|
|
}
|
|
}
|
|
)
|
|
|
|
self._assert_reg_field(
|
|
no_extra_fields_setting,
|
|
{
|
|
u"name": u"username",
|
|
u"default": u"",
|
|
u"type": u"text",
|
|
u"required": True,
|
|
u"label": u"Public Username",
|
|
u"placeholder": u"",
|
|
u"instructions": u"Will be shown in any discussions or forums you participate in (cannot be changed)",
|
|
u"restrictions": {
|
|
u"min_length": 2,
|
|
u"max_length": 30,
|
|
}
|
|
}
|
|
)
|
|
|
|
self._assert_reg_field(
|
|
no_extra_fields_setting,
|
|
{
|
|
u"name": u"password",
|
|
u"default": u"",
|
|
u"type": u"text",
|
|
u"required": True,
|
|
u"label": u"Password",
|
|
u"placeholder": u"",
|
|
u"instructions": u"",
|
|
u"restrictions": {
|
|
u"min_length": 2,
|
|
u"max_length": 75
|
|
},
|
|
}
|
|
)
|
|
|
|
def test_register_form_third_party_auth_running(self):
|
|
no_extra_fields_setting = {}
|
|
|
|
with simulate_running_pipeline(
|
|
"user_api.views.third_party_auth.pipeline",
|
|
"google-oauth2", email="bob@example.com",
|
|
fullname="Bob", username="Bob123"
|
|
):
|
|
# Password field should be hidden
|
|
self._assert_reg_field(
|
|
no_extra_fields_setting,
|
|
{
|
|
"name": "password",
|
|
"default": "",
|
|
"type": "hidden",
|
|
"required": False,
|
|
"label": "",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
# Email should be filled in
|
|
self._assert_reg_field(
|
|
no_extra_fields_setting,
|
|
{
|
|
u"name": u"email",
|
|
u"default": u"bob@example.com",
|
|
u"type": u"text",
|
|
u"required": True,
|
|
u"label": u"E-mail",
|
|
u"placeholder": u"example: username@domain.com",
|
|
u"instructions": u"This is the e-mail address you used to register with edX",
|
|
u"restrictions": {
|
|
u"min_length": 3,
|
|
u"max_length": 254
|
|
},
|
|
}
|
|
)
|
|
|
|
# Full name should be filled in
|
|
self._assert_reg_field(
|
|
no_extra_fields_setting,
|
|
{
|
|
u"name": u"name",
|
|
u"default": u"Bob",
|
|
u"type": u"text",
|
|
u"required": True,
|
|
u"label": u"Full Name",
|
|
u"placeholder": u"",
|
|
u"instructions": u"Needed for any certificates you may earn",
|
|
u"restrictions": {
|
|
"max_length": 255,
|
|
}
|
|
}
|
|
)
|
|
|
|
# Username should be filled in
|
|
self._assert_reg_field(
|
|
no_extra_fields_setting,
|
|
{
|
|
u"name": u"username",
|
|
u"default": u"Bob123",
|
|
u"type": u"text",
|
|
u"required": True,
|
|
u"label": u"Public Username",
|
|
u"placeholder": u"",
|
|
u"instructions": u"Will be shown in any discussions or forums you participate in (cannot be changed)",
|
|
u"restrictions": {
|
|
u"min_length": 2,
|
|
u"max_length": 30,
|
|
}
|
|
}
|
|
)
|
|
|
|
def test_register_form_level_of_education(self):
|
|
self._assert_reg_field(
|
|
{"level_of_education": "optional"},
|
|
{
|
|
"name": "level_of_education",
|
|
"default": "",
|
|
"type": "select",
|
|
"required": False,
|
|
"label": "Highest Level of Education Completed",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"options": [
|
|
{"value": "", "name": "--"},
|
|
{"value": "p", "name": "Doctorate"},
|
|
{"value": "m", "name": "Master's or professional degree"},
|
|
{"value": "b", "name": "Bachelor's degree"},
|
|
{"value": "a", "name": "Associate's degree"},
|
|
{"value": "hs", "name": "Secondary/high school"},
|
|
{"value": "jhs", "name": "Junior secondary/junior high/middle school"},
|
|
{"value": "el", "name": "Elementary/primary school"},
|
|
{"value": "none", "name": "None"},
|
|
{"value": "other", "name": "Other"},
|
|
],
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
def test_register_form_gender(self):
|
|
self._assert_reg_field(
|
|
{"gender": "optional"},
|
|
{
|
|
"name": "gender",
|
|
"default": "",
|
|
"type": "select",
|
|
"required": False,
|
|
"label": "Gender",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"options": [
|
|
{"value": "", "name": "--"},
|
|
{"value": "m", "name": "Male"},
|
|
{"value": "f", "name": "Female"},
|
|
{"value": "o", "name": "Other"},
|
|
],
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
def test_register_form_year_of_birth(self):
|
|
this_year = datetime.datetime.now(UTC).year
|
|
year_options = (
|
|
[{"value": "", "name": "--"}] + [
|
|
{"value": unicode(year), "name": unicode(year)}
|
|
for year in range(this_year, this_year - 120, -1)
|
|
]
|
|
)
|
|
self._assert_reg_field(
|
|
{"year_of_birth": "optional"},
|
|
{
|
|
"name": "year_of_birth",
|
|
"default": "",
|
|
"type": "select",
|
|
"required": False,
|
|
"label": "Year of Birth",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"options": year_options,
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
def test_registration_form_mailing_address(self):
|
|
self._assert_reg_field(
|
|
{"mailing_address": "optional"},
|
|
{
|
|
"name": "mailing_address",
|
|
"default": "",
|
|
"type": "textarea",
|
|
"required": False,
|
|
"label": "Mailing Address",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
def test_registration_form_goals(self):
|
|
self._assert_reg_field(
|
|
{"goals": "optional"},
|
|
{
|
|
"name": "goals",
|
|
"default": "",
|
|
"type": "textarea",
|
|
"required": False,
|
|
"label": "Please share with us your reasons for registering with edX",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
def test_registration_form_city(self):
|
|
self._assert_reg_field(
|
|
{"city": "optional"},
|
|
{
|
|
"name": "city",
|
|
"default": "",
|
|
"type": "text",
|
|
"required": False,
|
|
"label": "City",
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
def test_registration_form_country(self):
|
|
country_options = (
|
|
[{"name": "--", "value": ""}] +
|
|
[
|
|
{"value": country_code, "name": unicode(country_name)}
|
|
for country_code, country_name in SORTED_COUNTRIES
|
|
]
|
|
)
|
|
self._assert_reg_field(
|
|
{"country": "required"},
|
|
{
|
|
"label": "Country",
|
|
"name": "country",
|
|
"default": "",
|
|
"type": "select",
|
|
"required": True,
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"options": country_options,
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
@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):
|
|
self._assert_reg_field(
|
|
{"honor_code": "required"},
|
|
{
|
|
"label": "I agree to the <a href=\"https://www.test.com/honor\">Terms of Service and Honor Code</a>",
|
|
"name": "honor_code",
|
|
"default": False,
|
|
"type": "checkbox",
|
|
"required": True,
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
@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):
|
|
self._assert_reg_field(
|
|
{"honor_code": "required"},
|
|
{
|
|
"label": "I agree to the <a href=\"/honor\">Terms of Service and Honor Code</a>",
|
|
"name": "honor_code",
|
|
"default": False,
|
|
"type": "checkbox",
|
|
"required": True,
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
@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"
|
|
self._assert_reg_field(
|
|
{"honor_code": "required", "terms_of_service": "required"},
|
|
{
|
|
"label": "I agree to the <a href=\"https://www.test.com/honor\">Honor Code</a>",
|
|
"name": "honor_code",
|
|
"default": False,
|
|
"type": "checkbox",
|
|
"required": True,
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
# Terms of service field should also be present
|
|
self._assert_reg_field(
|
|
{"honor_code": "required", "terms_of_service": "required"},
|
|
{
|
|
"label": "I agree to the <a href=\"https://www.test.com/tos\">Terms of Service</a>",
|
|
"name": "terms_of_service",
|
|
"default": False,
|
|
"type": "checkbox",
|
|
"required": True,
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
@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"
|
|
self._assert_reg_field(
|
|
{"honor_code": "required", "terms_of_service": "required"},
|
|
{
|
|
"label": "I agree to the <a href=\"/honor\">Honor Code</a>",
|
|
"name": "honor_code",
|
|
"default": False,
|
|
"type": "checkbox",
|
|
"required": True,
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
# Terms of service field should also be present
|
|
self._assert_reg_field(
|
|
{"honor_code": "required", "terms_of_service": "required"},
|
|
{
|
|
"label": "I agree to the <a href=\"/tos\">Terms of Service</a>",
|
|
"name": "terms_of_service",
|
|
"default": False,
|
|
"type": "checkbox",
|
|
"required": True,
|
|
"placeholder": "",
|
|
"instructions": "",
|
|
"restrictions": {},
|
|
}
|
|
)
|
|
|
|
@override_settings(REGISTRATION_EXTRA_FIELDS={
|
|
"level_of_education": "optional",
|
|
"gender": "optional",
|
|
"year_of_birth": "optional",
|
|
"mailing_address": "optional",
|
|
"goals": "optional",
|
|
"city": "optional",
|
|
"country": "required",
|
|
"honor_code": "required",
|
|
})
|
|
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)
|
|
field_names = [field["name"] for field in form_desc["fields"]]
|
|
self.assertEqual(field_names, [
|
|
"email",
|
|
"name",
|
|
"username",
|
|
"password",
|
|
"city",
|
|
"country",
|
|
"level_of_education",
|
|
"gender",
|
|
"year_of_birth",
|
|
"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)
|
|
|
|
# Verify that the user exists
|
|
self.assertEqual(
|
|
account_api.account_info(self.USERNAME),
|
|
{
|
|
"username": self.USERNAME,
|
|
"email": self.EMAIL,
|
|
"is_active": False
|
|
}
|
|
)
|
|
|
|
# Verify that the user's full name is set
|
|
profile_info = profile_api.profile_info(self.USERNAME)
|
|
self.assertEqual(profile_info["full_name"], self.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",
|
|
"city": "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,
|
|
"city": self.CITY,
|
|
"country": self.COUNTRY,
|
|
"honor_code": "true",
|
|
})
|
|
self.assertHttpOK(response)
|
|
|
|
# Verify the profile information
|
|
profile_info = profile_api.profile_info(self.USERNAME)
|
|
self.assertEqual(profile_info["level_of_education"], self.EDUCATION)
|
|
self.assertEqual(profile_info["mailing_address"], self.ADDRESS)
|
|
self.assertEqual(profile_info["year_of_birth"], int(self.YEAR_OF_BIRTH))
|
|
self.assertEqual(profile_info["goals"], self.GOALS)
|
|
self.assertEqual(profile_info["city"], self.CITY)
|
|
self.assertEqual(profile_info["country"], self.COUNTRY)
|
|
|
|
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, "Activate Your edX Account")
|
|
self.assertIn("activate your account", 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)
|
|
self.assertEqual(response.content, json.dumps(["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)
|
|
self.assertEqual(response.content, json.dumps(["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)
|
|
self.assertEqual(response.content, json.dumps(["email", "username"]))
|
|
|
|
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
|
|
|
|
"""
|
|
# 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)
|
|
self.assertIn(expected_field, form_desc["fields"])
|