From 02084fa3727ef88b0d8a8396316fb7aafce295bf Mon Sep 17 00:00:00 2001 From: Jody Bailey <110463597+JodyBaileyy@users.noreply.github.com> Date: Tue, 4 Jul 2023 16:38:58 +0200 Subject: [PATCH] feat: added endpoint for context needed for recommendations experiment (#32645) * feat: added endpoint for context needed for recommendations experiment * chore: Removed unnecessary decorator --- .../learner_recommendations/serializers.py | 6 +++ .../tests/test_serializers.py | 37 +++++++++++++++++++ .../tests/test_views.py | 30 +++++++++++++++ .../learner_recommendations/urls.py | 3 ++ .../learner_recommendations/views.py | 32 ++++++++++++++++ 5 files changed, 108 insertions(+) diff --git a/lms/djangoapps/learner_recommendations/serializers.py b/lms/djangoapps/learner_recommendations/serializers.py index 24e0a4ddd186..779ef9f9bff0 100644 --- a/lms/djangoapps/learner_recommendations/serializers.py +++ b/lms/djangoapps/learner_recommendations/serializers.py @@ -86,6 +86,12 @@ class AboutPageRecommendationsSerializer(serializers.Serializer): ) +class RecommendationsContextSerializer(serializers.Serializer): + """Serializer for recommendations context""" + + countryCode = serializers.CharField(allow_blank=True) + + class CrossProductRecommendationsSerializer(serializers.Serializer): """ Cross product recommendation courses for course about page diff --git a/lms/djangoapps/learner_recommendations/tests/test_serializers.py b/lms/djangoapps/learner_recommendations/tests/test_serializers.py index 47b0ae80372c..6b69f140308a 100644 --- a/lms/djangoapps/learner_recommendations/tests/test_serializers.py +++ b/lms/djangoapps/learner_recommendations/tests/test_serializers.py @@ -6,6 +6,7 @@ from lms.djangoapps.learner_recommendations.serializers import ( DashboardRecommendationsSerializer, + RecommendationsContextSerializer, CrossProductRecommendationsSerializer, CrossProductAndAmplitudeRecommendationsSerializer, AmplitudeRecommendationsSerializer @@ -91,6 +92,42 @@ def test_happy_path(self): ) +class TestRecommendationsContextSerializer(TestCase): + """Tests for RecommendationsContextSerializer""" + + def test_successful_serialization(self): + """Test that context data serializes correctly""" + + serialized_data = RecommendationsContextSerializer( + { + "countryCode": "US", + } + ).data + + self.assertDictEqual( + serialized_data, + { + "countryCode": "US", + }, + ) + + def test_empty_response_serialization(self): + """Test that an empty response serializes correctly""" + + serialized_data = RecommendationsContextSerializer( + { + "countryCode": "", + } + ).data + + self.assertDictEqual( + serialized_data, + { + "countryCode": "", + }, + ) + + class TestCrossProductRecommendationsSerializers(TestCase): """ Tests for the CrossProductRecommendationsSerializer, diff --git a/lms/djangoapps/learner_recommendations/tests/test_views.py b/lms/djangoapps/learner_recommendations/tests/test_views.py index 8d5edd1602e3..87c9e905ea07 100644 --- a/lms/djangoapps/learner_recommendations/tests/test_views.py +++ b/lms/djangoapps/learner_recommendations/tests/test_views.py @@ -157,6 +157,36 @@ def test_successful_response( assert segment_mock.call_args[0][1] == "edx.bi.user.recommendations.viewed" +class TestRecommendationsContextView(APITestCase): + """Unit tests for the Recommendations Context View""" + + def setUp(self): + super().setUp() + self.user = UserFactory() + self.password = "test" + self.url = reverse_lazy("learner_recommendations:recommendations_context") + + @mock.patch("lms.djangoapps.learner_recommendations.views.country_code_from_ip") + def test_successful_response(self, country_code_from_ip_mock): + """Test that country code gets sent back when authenticated""" + + country_code_from_ip_mock.return_value = "za" + self.client.login(username=self.user.username, password=self.password) + + response = self.client.get(self.url) + response_data = json.loads(response.content) + + self.assertEqual(response_data["countryCode"], "za") + + def test_unauthenticated_response(self): + """ + Test that a 401 is sent back if an anauthenticated user calls endpoint + """ + response = self.client.get(self.url) + + self.assertEqual(response.status_code, 401) + + class TestCrossProductRecommendationsView(APITestCase): """Unit tests for the Cross Product Recommendations View""" diff --git a/lms/djangoapps/learner_recommendations/urls.py b/lms/djangoapps/learner_recommendations/urls.py index 3966255bde97..e428616f0d6d 100644 --- a/lms/djangoapps/learner_recommendations/urls.py +++ b/lms/djangoapps/learner_recommendations/urls.py @@ -25,4 +25,7 @@ re_path(r"^courses/$", views.DashboardRecommendationsApiView.as_view(), name="courses"), + re_path(r'^recommendations_context/$', + views.RecommendationsContextView.as_view(), + name='recommendations_context'), ] diff --git a/lms/djangoapps/learner_recommendations/views.py b/lms/djangoapps/learner_recommendations/views.py index ed465bdf8e65..f43c7a4cdc58 100644 --- a/lms/djangoapps/learner_recommendations/views.py +++ b/lms/djangoapps/learner_recommendations/views.py @@ -37,6 +37,7 @@ from lms.djangoapps.learner_recommendations.serializers import ( AboutPageRecommendationsSerializer, DashboardRecommendationsSerializer, + RecommendationsContextSerializer, CrossProductAndAmplitudeRecommendationsSerializer, CrossProductRecommendationsSerializer, AmplitudeRecommendationsSerializer, @@ -195,6 +196,37 @@ def get(self, request, course_id): ) +class RecommendationsContextView(APIView): + """ + *Example Request* + + GET /api/learner_recommendations/recommendations_context/ + """ + + authentication_classes = ( + JwtAuthentication, + SessionAuthenticationAllowInactiveUser, + ) + permission_classes = (IsAuthenticated, NotJwtRestrictedApplication) + + def get(self, request): + """ + Returns the context needed for the recommendations experiment: + - Country Code + """ + ip_address = get_client_ip(request)[0] + country_code = country_code_from_ip(ip_address) + + return Response( + RecommendationsContextSerializer( + { + "countryCode": country_code, + } + ).data, + status=200, + ) + + class ProductRecommendationsView(APIView): """ **Example Request**