diff --git a/openedx/core/djangoapps/programs/rest_api/v1/tests/test_views.py b/openedx/core/djangoapps/programs/rest_api/v1/tests/test_views.py index 2864f41a92..11428591db 100644 --- a/openedx/core/djangoapps/programs/rest_api/v1/tests/test_views.py +++ b/openedx/core/djangoapps/programs/rest_api/v1/tests/test_views.py @@ -10,6 +10,7 @@ from django.test.utils import override_settings from django.urls import reverse_lazy from enterprise.models import EnterpriseCourseEnrollment +from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import ( CourseEnrollmentFactory, UserFactory, @@ -144,7 +145,7 @@ class TestProgramProgressDetailView(ProgramsApiConfigMixin, SharedModuleStoreTes @skip_unless_lms -class TestProgramsView(SharedModuleStoreTestCase, ProgramCacheMixin): +class TestProgramsEnterpriseView(SharedModuleStoreTestCase, ProgramCacheMixin): """Unit tests for the program details page.""" enterprise_uuid = str(uuid4()) @@ -196,7 +197,7 @@ class TestProgramsView(SharedModuleStoreTestCase, ProgramCacheMixin): @with_site_configuration(configuration={"COURSE_CATALOG_API_URL": "foo"}) @override_settings(FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True)) @enterprise_is_enabled() - def test_program_list(self): + def test_program_list_enterprise(self): """ Verify API returns proper response. """ @@ -243,3 +244,92 @@ class TestProgramsView(SharedModuleStoreTestCase, ProgramCacheMixin): response = self.client.get(self.url) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, []) + + +@skip_unless_lms +class TestProgramsB2CView(SharedModuleStoreTestCase, ProgramCacheMixin): + """Unit tests for the program details page.""" + + program_uuid = str(uuid4()) + url = reverse_lazy("openedx.core.djangoapps.programs:v0:program_list_b2c") + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.user = UserFactory() + modulestore_course = ModuleStoreCourseFactory() + course_run = CourseRunFactory(key=str(modulestore_course.id)) + course = CourseFactory(course_runs=[course_run]) + + CourseEnrollmentFactory(is_active=True, course_id=modulestore_course.id, user=cls.user) + + cls.program = ProgramFactory( + uuid=cls.program_uuid, + courses=[course], + title="Journey to cooking", + type="MicroMasters", + authoring_organizations=[ + { + "key": "MAX", + "logo_image_url": "http://test.org/media/organization/logos/test-logo.png", + } + ], + ) + cls.site = SiteFactory(domain="test.localhost") + + def setUp(self): + super().setUp() + self.client.login(username=self.user.username, password=self.TEST_PASSWORD) + self.set_program_in_catalog_cache(self.program_uuid, self.program) + ProgramEnrollmentFactory.create( + user=self.user, + program_uuid=self.program_uuid, + external_user_key="0001", + ) + + @with_site_configuration(configuration={"COURSE_CATALOG_API_URL": "foo"}) + def test_program_list_b2c(self): + """ + Verify API returns proper response. + """ + cache.set( + SITE_PROGRAM_UUIDS_CACHE_KEY_TPL.format(domain=self.site.domain), + [self.program_uuid], + None, + ) + + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + program = response.data[0] + + assert len(program) + assert program["uuid"] == self.program["uuid"] + assert program["title"] == self.program["title"] + assert program["type"] == self.program["type"] + assert program["authoring_organizations"] == self.program["authoring_organizations"] + assert program["banner_image"] == self.program["banner_image"] + assert program["progress"] == { + "uuid": self.program["uuid"], + "completed": 0, + "in_progress": 0, + "not_started": 1, + "all_unenrolled": False, + } + + @with_site_configuration(configuration={"COURSE_CATALOG_API_URL": "foo"}) + def test_program_empty_list_if_no_enrollments(self): + """ + Verify API returns empty response if no enrollments exists for a learner. + """ + CourseEnrollment.objects.filter(user=self.user).delete() + + cache.set( + SITE_PROGRAM_UUIDS_CACHE_KEY_TPL.format(domain=self.site.domain), + [self.program_uuid], + None, + ) + + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, []) diff --git a/openedx/core/djangoapps/programs/rest_api/v1/urls.py b/openedx/core/djangoapps/programs/rest_api/v1/urls.py index 415a543a92..f6ee9628cf 100644 --- a/openedx/core/djangoapps/programs/rest_api/v1/urls.py +++ b/openedx/core/djangoapps/programs/rest_api/v1/urls.py @@ -18,6 +18,11 @@ urlpatterns = [ Programs.as_view(), name="program_list", ), + re_path( + "^programs/$", + Programs.as_view(), + name="program_list_b2c", + ), re_path( rf"^programs/(?P{PROGRAM_UUID_PATTERN})/progress_details/$", ProgramProgressDetailView.as_view(), diff --git a/openedx/core/djangoapps/programs/rest_api/v1/views.py b/openedx/core/djangoapps/programs/rest_api/v1/views.py index a5bf939e1e..77c5a171fd 100644 --- a/openedx/core/djangoapps/programs/rest_api/v1/views.py +++ b/openedx/core/djangoapps/programs/rest_api/v1/views.py @@ -33,8 +33,9 @@ class Programs(APIView): permission_classes = (IsAuthenticated,) - def get(self, request: "HttpRequest", enterprise_uuid: str) -> "HttpResponse": - """For an enterprise learner, get list of enrolled programs with progress. + def get(self, request: "HttpRequest", enterprise_uuid: str = "") -> "HttpResponse": + """For a learner, get list of enrolled programs with progress. + If an enterprise UUID ias provided, filter out all non-enterprise enrollments for the learner. **Example Request** @@ -89,8 +90,12 @@ class Programs(APIView): """ user: "AnonymousUser | User" = request.user - enrollments = list(self._get_enterprise_course_enrollments(enterprise_uuid, user)) - # return empty reponse if no enterprise enrollments exists for a user + if enterprise_uuid: + enrollments = list(self._get_enterprise_course_enrollments(enterprise_uuid, user)) + else: + enrollments = list(get_course_enrollments(user)) + + # return empty reponse if no enrollments exists for a user if not enrollments: return Response([])