diff --git a/eox_nelp/one_time_password/view_decorators.py b/eox_nelp/one_time_password/view_decorators.py index d0e9b88e..53a925b4 100644 --- a/eox_nelp/one_time_password/view_decorators.py +++ b/eox_nelp/one_time_password/view_decorators.py @@ -7,12 +7,13 @@ import functools import logging -from custom_reg_form.models import ExtraInfo from django.conf import settings from django.core.cache import cache from django.http import HttpResponseForbidden, JsonResponse from rest_framework import status +from eox_nelp.utils import save_extrainfo_field + logger = logging.getLogger(__name__) @@ -56,23 +57,9 @@ def wrapper(request): if not proposed_user_otp == cache.get(user_otp_key): return HttpResponseForbidden(reason="Forbidden - wrong code") - save_successfull_phone_validation(request.user) + save_extrainfo_field(request.user, "is_phone_validated", True) cache.delete(user_otp_key) return func(request) return wrapper - - -def save_successfull_phone_validation(user): - """Save in db the successfull phone_validation field with `True` - in extrainfo model of the user. - - Args: - user (User): User instance to check - """ - if extra_info := getattr(user, "extrainfo", None): - setattr(extra_info, "is_phone_validated", True) - extra_info.save() - else: - ExtraInfo.objects.create(user=user, is_phone_validated=True) # pylint: disable=no-member diff --git a/eox_nelp/tests/test_utils.py b/eox_nelp/tests/test_utils.py index cc257513..1fe098f4 100644 --- a/eox_nelp/tests/test_utils.py +++ b/eox_nelp/tests/test_utils.py @@ -5,11 +5,20 @@ GetCourseFromIdTestCase: Tests cases for the get_course_from_id method. """ from ddt import data, ddt +from django.contrib.auth import get_user_model from django.test import TestCase from mock import Mock, patch from opaque_keys.edx.keys import CourseKey -from eox_nelp.utils import camel_to_snake, extract_course_id_from_string, get_course_from_id, get_item_label +from eox_nelp.utils import ( + camel_to_snake, + extract_course_id_from_string, + get_course_from_id, + get_item_label, + save_extrainfo_field, +) + +User = get_user_model() @ddt @@ -222,3 +231,45 @@ def test_invalid_input(self, input_value): - TypeError is raised """ self.assertRaises(TypeError, camel_to_snake, input_value) + + +@ddt +class SaveExtraInfoFieldTestCase(TestCase): + """Test class for the save_extrainfo_field method.""" + @data( + ("arabic_name", "أناكين سكاي ووكر"), + ("is_phone_validated", True), + ("arabic_first_name", " أناكين"), + ("arabic_last_name", "سكاي ووكر"), + ) + def test_save_extrainfo_field(self, test_data): + """ Test right functionality. + + Expected behavior: + - Extrainfo related objed has the expected value. + """ + field = test_data[0] + value = test_data[1] + user, _ = User.objects.get_or_create(username="vader1798") + + save_extrainfo_field(user, field, value) + + self.assertEqual(getattr(user.extrainfo, field), value) + + @data( + ("arabic_name2", "loool"), + ("otp-crazy", True), + ) + def test_wrong_extra_info_field(self, test_data): + """ Test when the input is not a extra info field. + + Expected behavior: + - The user has no extra info model. + """ + field = test_data[0] + value = test_data[1] + user, _ = User.objects.get_or_create(username="vader19") + + save_extrainfo_field(user, field, value) + + self.assertFalse(hasattr(user, "extrainfo")) diff --git a/eox_nelp/user_profile/api/v1/tests/test_views.py b/eox_nelp/user_profile/api/v1/tests/test_views.py index d8b9a4b8..4b2d7a35 100644 --- a/eox_nelp/user_profile/api/v1/tests/test_views.py +++ b/eox_nelp/user_profile/api/v1/tests/test_views.py @@ -141,3 +141,37 @@ def test_account_update_extra_fields(self, cdd_task_mock): self.assertEqual(self.user.first_name, payload["first_name"]) self.assertEqual(self.user.last_name, payload["last_name"]) cdd_task_mock.delay.assert_called_with(user_id=self.user.id) + + @override_settings( + ENABLE_OTP_VALIDATION=False, + REQUIRED_USER_EXTRA_INFO_FIELDS=["arabic_first_name", "arabic_last_name"], + PEARSON_RTI_ACTIVATE_GRADED_GATE=True, + ) + @patch("eox_nelp.user_profile.api.v1.views.cdd_task") + def test_update_extra_info_fields(self, cdd_task_mock): + """ + Test that extra account user fields has been set. + + Expected behavior: + - Check the response says that the field has been updated. + - Status code 200. + - Check that update_account_settings method has called once. + - Check that user first_name has been updated. + - Check that user last_name has been updated. + - Check cdd_task async task is called with user.id + """ + payload = { + "arabic_first_name": "أناكين", + "arabic_last_name": "سكاي ووكر", + "one_time_password": "correct26", + } + url_endpoint = reverse(self.reverse_viewname) + + response = self.client.post(url_endpoint, payload, format="json") + + self.assertDictEqual(response.json(), {"message": "User's fields has been updated successfully"}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + accounts.api.update_account_settings.assert_called_once_with(self.user, payload) + self.assertEqual(self.user.extrainfo.arabic_first_name, payload["arabic_first_name"]) + self.assertEqual(self.user.extrainfo.arabic_last_name, payload["arabic_last_name"]) + cdd_task_mock.delay.assert_called_with(user_id=self.user.id) diff --git a/eox_nelp/user_profile/api/v1/views.py b/eox_nelp/user_profile/api/v1/views.py index 3c3d75e4..1de8ee0b 100644 --- a/eox_nelp/user_profile/api/v1/views.py +++ b/eox_nelp/user_profile/api/v1/views.py @@ -19,6 +19,7 @@ from eox_nelp.edxapp_wrapper.user_api import accounts, errors from eox_nelp.one_time_password.view_decorators import validate_otp from eox_nelp.pearson_vue.tasks import cdd_task +from eox_nelp.utils import save_extrainfo_field logger = logging.getLogger(__name__) @@ -65,6 +66,15 @@ def update_user_data(request): request.user.save() + # This extra code block is required since the method update_account_settings just + # allows to update fields defined in the AccountUserSerializer and the AccountLegacyProfileSerializer + # so some fields related ExtraInfo are not editable in the standad implementation. + required_user_extra_info_fields = getattr(settings, 'REQUIRED_USER_EXTRA_INFO_FIELDS', []) + if required_user_extra_info_fields: + for field, value in request.data.items(): + if field in required_user_extra_info_fields: + save_extrainfo_field(request.user, field, value) + except errors.AccountValidationError as err: return Response({"field_errors": err.field_errors}, status=status.HTTP_400_BAD_REQUEST) except errors.AccountUpdateError as err: diff --git a/eox_nelp/utils.py b/eox_nelp/utils.py index 35d498f8..78b21b8c 100644 --- a/eox_nelp/utils.py +++ b/eox_nelp/utils.py @@ -2,6 +2,7 @@ import re from copy import copy +from custom_reg_form.models import ExtraInfo from opaque_keys.edx.keys import CourseKey from eox_nelp.edxapp_wrapper.course_overviews import get_course_overviews @@ -154,3 +155,22 @@ def camel_to_snake(string): String in snake case. """ return re.sub(r'(?