-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: autogenerate username on registration (#34562)
* feat: autogenerate username on registration --------- Co-authored-by: Attiya Ishaque <[email protected]> Co-authored-by: Blue <[email protected]>
- Loading branch information
1 parent
98dd951
commit 2ce25b3
Showing
7 changed files
with
282 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,8 @@ | |
password_validators_instruction_texts, | ||
password_validators_restrictions | ||
) | ||
ENABLE_AUTO_GENERATED_USERNAME = settings.FEATURES.copy() | ||
ENABLE_AUTO_GENERATED_USERNAME['ENABLE_AUTO_GENERATED_USERNAME'] = True | ||
|
||
|
||
@ddt.ddt | ||
|
@@ -1861,6 +1863,117 @@ def test_rate_limiting_registration_view(self): | |
assert response.status_code == 403 | ||
cache.clear() | ||
|
||
@override_settings(FEATURES=ENABLE_AUTO_GENERATED_USERNAME) | ||
def test_register_with_auto_generated_username(self): | ||
""" | ||
Test registration functionality with auto-generated username. | ||
This method tests the registration process when auto-generated username | ||
feature is enabled. It creates a new user account, verifies that the user | ||
account settings are correctly set, and checks if the user is successfully | ||
logged in after registration. | ||
""" | ||
response = self.client.post(self.url, { | ||
"email": self.EMAIL, | ||
"name": self.NAME, | ||
"password": self.PASSWORD, | ||
"honor_code": "true", | ||
}) | ||
self.assertHttpOK(response) | ||
|
||
user = User.objects.get(email=self.EMAIL) | ||
request = RequestFactory().get('/url') | ||
request.user = user | ||
account_settings = get_account_settings(request)[0] | ||
|
||
assert self.EMAIL == account_settings["email"] | ||
assert not account_settings["is_active"] | ||
assert 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(FEATURES=ENABLE_AUTO_GENERATED_USERNAME) | ||
def test_register_with_empty_name(self): | ||
""" | ||
Test registration field validations when ENABLE_AUTO_GENERATED_USERNAME is enabled. | ||
Sends a POST request to the registration endpoint with empty name field. | ||
Expects a 400 Bad Request response with the corresponding validation error message for the name field. | ||
""" | ||
response = self.client.post(self.url, { | ||
"email": "[email protected]", | ||
"name": "", | ||
"password": "password", | ||
"honor_code": "true", | ||
}) | ||
assert response.status_code == 400 | ||
response_json = json.loads(response.content.decode('utf-8')) | ||
self.assertDictEqual( | ||
response_json, | ||
{ | ||
"name": [{"user_message": 'Your legal name must be a minimum of one character long'}], | ||
"error_code": "validation-error" | ||
} | ||
) | ||
|
||
@override_settings(FEATURES=ENABLE_AUTO_GENERATED_USERNAME) | ||
@mock.patch('openedx.core.djangoapps.user_authn.views.utils._get_username_prefix') | ||
@mock.patch('openedx.core.djangoapps.user_authn.views.utils.random.choices') | ||
@mock.patch('openedx.core.djangoapps.user_authn.views.utils.datetime') | ||
@mock.patch('openedx.core.djangoapps.user_authn.views.utils.get_auto_generated_username') | ||
def test_register_autogenerated_duplicate_username(self, | ||
mock_get_auto_generated_username, | ||
mock_datetime, | ||
mock_choices, | ||
mock_get_username_prefix): | ||
""" | ||
Test registering a user with auto-generated username where a duplicate username conflict occurs. | ||
Mocks various utilities to control the auto-generated username process and verifies the response content | ||
when a duplicate username conflict happens during user registration. | ||
""" | ||
mock_datetime.now.return_value.strftime.return_value = '24 03' | ||
mock_choices.return_value = ['X', 'Y', 'Z', 'A'] # Fixed random string for testing | ||
|
||
mock_get_username_prefix.return_value = None | ||
|
||
current_year_month = f"{datetime.now().year % 100}{datetime.now().month:02d}_" | ||
random_string = 'XYZA' | ||
expected_username = current_year_month + random_string | ||
mock_get_auto_generated_username.return_value = expected_username | ||
|
||
# Register the first user | ||
response = self.client.post(self.url, { | ||
"email": self.EMAIL, | ||
"name": self.NAME, | ||
"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": "[email protected]", | ||
"name": "Someone Else", | ||
"password": self.PASSWORD, | ||
"honor_code": "true", | ||
}) | ||
|
||
assert response.status_code == 409 | ||
response_json = json.loads(response.content.decode('utf-8')) | ||
response_json.pop('username_suggestions') | ||
self.assertDictEqual( | ||
response_json, | ||
{ | ||
"username": [{ | ||
"user_message": AUTHN_USERNAME_CONFLICT_MSG, | ||
}], | ||
"error_code": "duplicate-username" | ||
} | ||
) | ||
|
||
def _assert_fields_match(self, actual_field, expected_field): | ||
""" | ||
Assert that the actual field and the expected field values match. | ||
|
77 changes: 77 additions & 0 deletions
77
openedx/core/djangoapps/user_authn/views/tests/test_utils.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
""" | ||
Tests for user utils functionality. | ||
""" | ||
from django.test import TestCase | ||
from datetime import datetime | ||
from openedx.core.djangoapps.user_authn.views.utils import get_auto_generated_username, _get_username_prefix | ||
import ddt | ||
from unittest.mock import patch | ||
|
||
|
||
@ddt.ddt | ||
class TestGenerateUsername(TestCase): | ||
""" | ||
Test case for the get_auto_generated_username function. | ||
""" | ||
|
||
@ddt.data( | ||
({'first_name': 'John', 'last_name': 'Doe'}, "JD"), | ||
({'name': 'Jane Smith'}, "JS"), | ||
({'name': 'Jane'}, "J"), | ||
({'name': 'John Doe Smith'}, "JD") | ||
) | ||
@ddt.unpack | ||
def test_generate_username_from_data(self, data, expected_initials): | ||
""" | ||
Test get_auto_generated_username function. | ||
""" | ||
random_string = 'XYZA' | ||
current_year_month = f"_{datetime.now().year % 100}{datetime.now().month:02d}_" | ||
|
||
with patch('openedx.core.djangoapps.user_authn.views.utils.random.choices') as mock_choices: | ||
mock_choices.return_value = ['X', 'Y', 'Z', 'A'] | ||
|
||
username = get_auto_generated_username(data) | ||
|
||
expected_username = expected_initials + current_year_month + random_string | ||
self.assertEqual(username, expected_username) | ||
|
||
@ddt.data( | ||
({'first_name': 'John', 'last_name': 'Doe'}, "JD"), | ||
({'name': 'Jane Smith'}, "JS"), | ||
({'name': 'Jane'}, "J"), | ||
({'name': 'John Doe Smith'}, "JD"), | ||
({'first_name': 'John Doe', 'last_name': 'Smith'}, "JD"), | ||
({}, None), | ||
({'first_name': '', 'last_name': ''}, None), | ||
({'name': ''}, None), | ||
({'first_name': '阿提亚', 'last_name': '阿提亚'}, "AT"), | ||
({'first_name': 'أحمد', 'last_name': 'محمد'}, "HM"), | ||
({'name': 'أحمد محمد'}, "HM"), | ||
) | ||
@ddt.unpack | ||
def test_get_username_prefix(self, data, expected_initials): | ||
""" | ||
Test _get_username_prefix function. | ||
""" | ||
username_prefix = _get_username_prefix(data) | ||
self.assertEqual(username_prefix, expected_initials) | ||
|
||
@patch('openedx.core.djangoapps.user_authn.views.utils._get_username_prefix') | ||
@patch('openedx.core.djangoapps.user_authn.views.utils.random.choices') | ||
@patch('openedx.core.djangoapps.user_authn.views.utils.datetime') | ||
def test_get_auto_generated_username_no_prefix(self, mock_datetime, mock_choices, mock_get_username_prefix): | ||
""" | ||
Test get_auto_generated_username function when no name data is provided. | ||
""" | ||
mock_datetime.now.return_value.strftime.return_value = f"{datetime.now().year % 100} {datetime.now().month:02d}" | ||
mock_choices.return_value = ['X', 'Y', 'Z', 'A'] # Fixed random string for testing | ||
|
||
mock_get_username_prefix.return_value = None | ||
|
||
current_year_month = f"{datetime.now().year % 100}{datetime.now().month:02d}_" | ||
random_string = 'XYZA' | ||
expected_username = current_year_month + random_string | ||
|
||
username = get_auto_generated_username({}) | ||
self.assertEqual(username, expected_username) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters