From 936a236273acd54bdb9620ac1cd2c2ebf13e8346 Mon Sep 17 00:00:00 2001 From: Shahbaz Shabbir <32649010+shahbaz-arbisoft@users.noreply.github.com> Date: Thu, 20 Apr 2023 12:28:17 +0500 Subject: [PATCH] refactor: mfe_context response to serialize object keys to camelcase (#31930) --- .../user_authn/api/tests/test_serializers.py | 137 ++++++++++++++++++ .../user_authn/api/tests/test_views.py | 36 +++-- .../core/djangoapps/user_authn/api/views.py | 6 +- .../core/djangoapps/user_authn/serializers.py | 77 ++++++++++ 4 files changed, 241 insertions(+), 15 deletions(-) create mode 100644 openedx/core/djangoapps/user_authn/api/tests/test_serializers.py create mode 100644 openedx/core/djangoapps/user_authn/serializers.py diff --git a/openedx/core/djangoapps/user_authn/api/tests/test_serializers.py b/openedx/core/djangoapps/user_authn/api/tests/test_serializers.py new file mode 100644 index 0000000000..54e9a9c96c --- /dev/null +++ b/openedx/core/djangoapps/user_authn/api/tests/test_serializers.py @@ -0,0 +1,137 @@ +"""Tests for serializers for the MFE Context""" + +from django.test import TestCase + +from openedx.core.djangoapps.user_authn.serializers import MFEContextSerializer + + +class TestMFEContextSerializer(TestCase): + """ + High-level unit tests for MFEContextSerializer + """ + + @staticmethod + def get_mock_mfe_context_data(): + """ + Helper function to generate mock data for the MFE Context API view. + """ + + mock_context_data = { + 'context_data': { + 'currentProvider': 'edX', + 'platformName': 'edX', + 'providers': [ + { + 'id': 'oa2-facebook', + 'name': 'Facebook', + 'iconClass': 'fa-facebook', + 'iconImage': None, + 'skipHintedLogin': False, + 'skipRegistrationForm': False, + 'loginUrl': 'https://facebook.com/login', + 'registerUrl': 'https://facebook.com/register' + }, + { + 'id': 'oa2-google-oauth2', + 'name': 'Google', + 'iconClass': 'fa-google-plus', + 'iconImage': None, + 'skipHintedLogin': False, + 'skipRegistrationForm': False, + 'loginUrl': 'https://google.com/login', + 'registerUrl': 'https://google.com/register' + } + ], + 'secondaryProviders': [], + 'finishAuthUrl': 'https://edx.com/auth/finish', + 'errorMessage': None, + 'registerFormSubmitButtonText': 'Create Account', + 'autoSubmitRegForm': False, + 'syncLearnerProfileData': False, + 'countryCode': '', + 'pipeline_user_details': { + 'username': 'test123', + 'email': 'test123@edx.com', + 'fullname': 'Test Test', + 'first_name': 'Test', + 'last_name': 'Test' + } + }, + 'registration_fields': {}, + 'optional_fields': { + 'extended_profile': [] + } + } + + return mock_context_data + + @staticmethod + def get_expected_data(): + """ + Helper function to generate expected data for the MFE Context API view serializer. + """ + + expected_data = { + 'contextData': { + 'currentProvider': 'edX', + 'platformName': 'edX', + 'providers': [ + { + 'id': 'oa2-facebook', + 'name': 'Facebook', + 'iconClass': 'fa-facebook', + 'iconImage': None, + 'skipHintedLogin': False, + 'skipRegistrationForm': False, + 'loginUrl': 'https://facebook.com/login', + 'registerUrl': 'https://facebook.com/register' + }, + { + 'id': 'oa2-google-oauth2', + 'name': 'Google', + 'iconClass': 'fa-google-plus', + 'iconImage': None, + 'skipHintedLogin': False, + 'skipRegistrationForm': False, + 'loginUrl': 'https://google.com/login', + 'registerUrl': 'https://google.com/register' + } + ], + 'secondaryProviders': [], + 'finishAuthUrl': 'https://edx.com/auth/finish', + 'errorMessage': None, + 'registerFormSubmitButtonText': 'Create Account', + 'autoSubmitRegForm': False, + 'syncLearnerProfileData': False, + 'countryCode': '', + 'pipelineUserDetails': { + 'username': 'test123', + 'email': 'test123@edx.com', + 'name': 'Test Test', + 'firstName': 'Test', + 'lastName': 'Test' + } + }, + 'registrationFields': {}, + 'optionalFields': { + 'extended_profile': [] + } + } + + return expected_data + + def test_mfe_context_serializer(self): + """ + Test MFEContextSerializer with mock data that serializes data correctly + """ + + mfe_context_data = self.get_mock_mfe_context_data() + expected_data = self.get_expected_data() + output_data = MFEContextSerializer( + mfe_context_data + ).data + + self.assertDictEqual( + output_data, + expected_data + ) diff --git a/openedx/core/djangoapps/user_authn/api/tests/test_views.py b/openedx/core/djangoapps/user_authn/api/tests/test_views.py index af079f6d7c..025094d701 100644 --- a/openedx/core/djangoapps/user_authn/api/tests/test_views.py +++ b/openedx/core/djangoapps/user_authn/api/tests/test_views.py @@ -42,6 +42,8 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): hostname = socket.gethostname() ip_address = socket.gethostbyname(hostname) self.country_code = country_code_from_ip(ip_address) + self.pipeline_user_details = {'username': None, 'email': None, 'name': None, + 'firstName': None, 'lastName': None} # Several third party auth providers are created for these tests: self.configure_google_provider(enabled=True, visible=True) @@ -93,8 +95,12 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): """ Returns the MFE context """ + + if add_user_details: + self.pipeline_user_details.update({'email': 'test@test.com'}) + return { - 'context_data': { + 'contextData': { 'currentProvider': current_provider, 'platformName': settings.PLATFORM_NAME, 'providers': self.get_provider_data(params) if params else [], @@ -102,12 +108,13 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): 'finishAuthUrl': pipeline.get_complete_url(backend_name) if backend_name else None, 'errorMessage': None, 'registerFormSubmitButtonText': 'Create Account', + 'autoSubmitRegForm': False, 'syncLearnerProfileData': False, - 'pipeline_user_details': {'email': 'test@test.com'} if add_user_details else {}, - 'countryCode': self.country_code + 'countryCode': self.country_code, + 'pipelineUserDetails': self.pipeline_user_details, }, - 'registration_fields': {}, - 'optional_fields': { + 'registrationFields': {}, + 'optionalFields': { 'extended_profile': [], }, } @@ -182,7 +189,7 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): }) response = self.client.get(self.url, self.query_params) - assert response.data['context_data']['providers'] == provider_data + assert response.data['contextData']['providers'] == provider_data def test_user_country_code(self): """ @@ -191,7 +198,7 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): response = self.client.get(self.url, self.query_params) assert response.status_code == 200 - assert response.data['context_data']['countryCode'] == self.country_code + assert response.data['contextData']['countryCode'] == self.country_code @override_settings( ENABLE_DYNAMIC_REGISTRATION_FIELDS=True, @@ -205,7 +212,7 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): self.query_params.update({'is_register_page': True}) response = self.client.get(self.url, self.query_params) assert response.status_code == status.HTTP_200_OK - assert response.data['registration_fields']['fields'] == {} + assert response.data['registrationFields']['fields'] == {} @with_site_configuration( configuration={ @@ -223,8 +230,9 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): """ self.query_params.update({'is_register_page': True}) response = self.client.get(self.url, self.query_params) + assert response.status_code == status.HTTP_200_OK - assert list(response.data['registration_fields']['fields'].keys()) == ['first_name', 'last_name', 'state'] + assert list(response.data['registrationFields']['fields'].keys()) == ['first_name', 'last_name', 'state'] @override_settings( ENABLE_DYNAMIC_REGISTRATION_FIELDS=True, @@ -248,7 +256,7 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): self.query_params.update({'is_register_page': True}) response = self.client.get(self.url, self.query_params) assert response.status_code == status.HTTP_200_OK - assert response.data['optional_fields']['fields'] == expected_response + assert response.data['optionalFields']['fields'] == expected_response @with_site_configuration( configuration={ @@ -282,8 +290,9 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): } self.query_params.update({'is_register_page': True}) response = self.client.get(self.url, self.query_params) + assert response.status_code == status.HTTP_200_OK - assert response.data['optional_fields']['fields'] == expected_response + assert response.data['optionalFields']['fields'] == expected_response @with_site_configuration( configuration={ @@ -302,7 +311,7 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): self.query_params.update({'is_register_page': True}) response = self.client.get(self.url, self.query_params) assert response.status_code == status.HTTP_200_OK - assert list(response.data['optional_fields']['fields'].keys()) == ['specialty', 'goals'] + assert list(response.data['optionalFields']['fields'].keys()) == ['specialty', 'goals'] @with_site_configuration( configuration={ @@ -322,7 +331,7 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): self.query_params.update({'is_register_page': True}) response = self.client.get(self.url, self.query_params) assert response.status_code == status.HTTP_200_OK - assert list(response.data['registration_fields']['fields'].keys()) == ['specialty'] + assert list(response.data['registrationFields']['fields'].keys()) == ['specialty'] @override_settings( ENABLE_DYNAMIC_REGISTRATION_FIELDS=True, @@ -333,7 +342,6 @@ class MFEContextViewTest(ThirdPartyAuthTestMixin, APITestCase): Test that API return valid response dictionary with both required and optional fields """ response = self.client.get(self.url, self.query_params) - assert response.data == self.get_context() diff --git a/openedx/core/djangoapps/user_authn/api/views.py b/openedx/core/djangoapps/user_authn/api/views.py index 7e84a67473..2fbc014f52 100644 --- a/openedx/core/djangoapps/user_authn/api/views.py +++ b/openedx/core/djangoapps/user_authn/api/views.py @@ -15,6 +15,7 @@ from common.djangoapps.student.helpers import get_next_url_for_login_page from common.djangoapps.student.views import compose_and_send_activation_email from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.user_authn.api.helper import RegistrationFieldsContext +from openedx.core.djangoapps.user_authn.serializers import MFEContextSerializer from openedx.core.djangoapps.user_authn.views.utils import get_mfe_context @@ -65,6 +66,7 @@ class MFEContextView(APIView): context['registration_fields'].update({ 'fields': registration_fields, }) + optional_fields = RegistrationFieldsContext('optional').get_fields() if optional_fields: context['optional_fields'].update({ @@ -74,7 +76,9 @@ class MFEContextView(APIView): return Response( status=status.HTTP_200_OK, - data=context + data=MFEContextSerializer( + context + ).data ) diff --git a/openedx/core/djangoapps/user_authn/serializers.py b/openedx/core/djangoapps/user_authn/serializers.py new file mode 100644 index 0000000000..48918a89f7 --- /dev/null +++ b/openedx/core/djangoapps/user_authn/serializers.py @@ -0,0 +1,77 @@ +""" +MFE Context API Serializers +""" + +from rest_framework import serializers + + +class ProvidersSerializer(serializers.Serializer): + """ + Providers Serializers + """ + + id = serializers.CharField(allow_null=True) + name = serializers.CharField(allow_null=True) + iconClass = serializers.CharField(allow_null=True) + iconImage = serializers.CharField(allow_null=True) + skipHintedLogin = serializers.BooleanField(default=False) + skipRegistrationForm = serializers.BooleanField(default=False) + loginUrl = serializers.CharField(allow_null=True) + registerUrl = serializers.CharField(allow_null=True) + + +class PipelineUserDetailsSerializer(serializers.Serializer): + """ + Pipeline User Details Serializers + """ + + username = serializers.CharField(allow_null=True) + email = serializers.CharField(allow_null=True) + name = serializers.CharField(source='fullname', allow_null=True) + firstName = serializers.CharField(source='first_name', allow_null=True) + lastName = serializers.CharField(source='last_name', allow_null=True) + + +class ContextDataSerializer(serializers.Serializer): + """ + Context Data Serializers + """ + + currentProvider = serializers.CharField(allow_null=True) + platformName = serializers.CharField(allow_null=True) + providers = serializers.ListField( + child=ProvidersSerializer(), + allow_null=True + ) + secondaryProviders = serializers.ListField( + child=ProvidersSerializer(), + allow_null=True + ) + finishAuthUrl = serializers.CharField(allow_null=True) + errorMessage = serializers.CharField(allow_null=True) + registerFormSubmitButtonText = serializers.CharField(allow_null=True) + autoSubmitRegForm = serializers.BooleanField(default=False) + syncLearnerProfileData = serializers.BooleanField(default=False) + countryCode = serializers.CharField(allow_null=True) + pipelineUserDetails = PipelineUserDetailsSerializer(source='pipeline_user_details', allow_null=True) + + +class MFEContextSerializer(serializers.Serializer): + """ + Serializer class to convert the keys of MFE Context Response dict object to camelCase format. + """ + + contextData = ContextDataSerializer( + source='context_data', + default={} + ) + registrationFields = serializers.DictField( + source='registration_fields', + default={} + ) + optionalFields = serializers.DictField( + source='optional_fields', + default={ + 'extended_profile': [] + } + )