diff --git a/enterprise_access/apps/api/v1/tests/test_bff_views.py b/enterprise_access/apps/api/v1/tests/test_bff_views.py index 21036b31..d6114d4b 100644 --- a/enterprise_access/apps/api/v1/tests/test_bff_views.py +++ b/enterprise_access/apps/api/v1/tests/test_bff_views.py @@ -571,3 +571,75 @@ def test_dashboard_with_enrollments( ], }) self.assertEqual(response.json(), expected_response_data) + + @mock_dashboard_dependencies + @mock.patch('enterprise_access.apps.api_client.lms_client.LmsApiClient.bulk_enroll_enterprise_learners') + def test_dashboard_with_default_enrollment_realizations( + self, + mock_get_enterprise_customers_for_user, + mock_get_default_enrollment_intentions_learner_status, + mock_get_subscription_licenses_for_learner, + mock_get_enterprise_course_enrollments, + mock_bulk_enroll, + ): + """ + Test the dashboard route with enrollments. + """ + self.set_jwt_cookie([{ + 'system_wide_role': SYSTEM_ENTERPRISE_LEARNER_ROLE, + 'context': self.mock_enterprise_customer_uuid, + }]) + mock_get_enterprise_customers_for_user.return_value = self.mock_enterprise_learner_response_data + + mock_activated_subscription_license = { + **self.mock_subscription_license, + 'status': 'activated', + 'activation_date': '2024-01-01T00:00:00Z', + } + mock_subscription_licenses_data = { + 'customer_agreement': self.mock_customer_agreement, + 'results': [mock_activated_subscription_license], + } + mock_get_subscription_licenses_for_learner.return_value = mock_subscription_licenses_data + + mock_get_default_enrollment_intentions_learner_status.return_value = { + "lms_user_id": self.mock_user.id, + "user_email": self.mock_user.email, + "enterprise_customer_uuid": self.mock_enterprise_customer_uuid, + "enrollment_statuses": { + "needs_enrollment": { + "enrollable": [ + { + 'applicable_enterprise_catalog_uuids': [self.mock_enterprise_catalog_uuid], + 'course_run_key': 'course-run-1', + }, + ], + "not_enrollable": [], + }, + 'already_enrolled': [], + }, + } + mock_bulk_enroll.return_value = { + 'successes': [ + {'course_run_key': 'course-run-1'}, + ], + 'failures': [], + } + mock_get_enterprise_course_enrollments.return_value = [self.mock_enterprise_course_enrollment] + + query_params = { + 'enterprise_customer_slug': self.mock_enterprise_customer_slug, + } + dashboard_url = reverse('api:v1:learner-portal-bff-dashboard') + dashboard_url += f"?{urlencode(query_params)}" + + response = self.client.post(dashboard_url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + actual_customer_uuid_arg, actual_payload_arg = mock_bulk_enroll.call_args_list[0][0] + self.assertEqual(actual_customer_uuid_arg, self.mock_enterprise_customer_uuid) + expected_payload = [ + {'user_id': self.user.lms_user_id, 'course_run_key': 'course-run-1', + 'license_uuid': mock_activated_subscription_license['uuid'], 'is_default_auto_enrollment': True}, + ] + self.assertEqual(expected_payload, actual_payload_arg) diff --git a/enterprise_access/apps/bffs/handlers.py b/enterprise_access/apps/bffs/handlers.py index 90014e05..727de13f 100644 --- a/enterprise_access/apps/bffs/handlers.py +++ b/enterprise_access/apps/bffs/handlers.py @@ -448,8 +448,37 @@ def enroll_in_redeemable_default_enterprise_enrollment_intentions(self): if subscription_catalog in applicable_catalog_to_enrollment_intention: course_run_key = enrollment_intention['course_run_key'] license_uuids_by_course_run_key[course_run_key] = self.current_active_license['uuid'] - break + response_payload = self._request_default_enrollment_realizations(license_uuids_by_course_run_key) + + if failures := response_payload.get('failures'): + self.add_error( + user_message='There were failures realizing default enrollments', + developer_message='Default realization enrollment failures: ' + json.dumps(failures), + ) + + if not self.context.data.get('default_enterprise_enrollment_realizations'): + self.context.data['default_enterprise_enrollment_realizations'] = [] + + if response_payload['successes']: + # Invalidate the default enterprise enrollment intentions and enterprise course enrollments cache + # as the previously redeemable enrollment intentions have been processed/enrolled. + self.invalidate_default_enrollment_intentions_cache() + self.invalidate_enrollments_cache() + + for enrollment in response_payload['successes']: + course_run_key = enrollment.get('course_run_key') + self.context.data['default_enterprise_enrollment_realizations'].append({ + 'course_key': course_run_key, + 'enrollment_status': 'enrolled', + 'subscription_license_uuid': license_uuids_by_course_run_key.get(course_run_key), + }) + + def _request_default_enrollment_realizations(self, license_uuids_by_course_run_key): + """ + Sends the request to bulk enroll into default enrollment intentions via the LMS + API client. + """ bulk_enrollment_payload = [] for course_run_key, license_uuid in license_uuids_by_course_run_key.items(): bulk_enrollment_payload.append({ @@ -470,34 +499,21 @@ def enroll_in_redeemable_default_enterprise_enrollment_intentions(self): user_message='There was an exception realizing default enrollments', developer_message=f'Default realization enrollment exception: {exc}', ) + response_payload = {} - if failures := response_payload.get('failures'): - self.add_error( - user_message='There were failures realizing default enrollments', - developer_message='Default realization enrollment failures: ' + json.dumps(failures), - ) - - if not self.context.data.get('default_enterprise_enrollment_realizations'): - self.context.data['default_enterprise_enrollment_realizations'] = [] + return response_payload - for enrollment in response_payload['successes']: - course_run_key = enrollment.get('course_run_key') - self.context.data['default_enterprise_enrollment_realizations'].append({ - 'course_key': course_run_key, - 'enrollment_status': 'enrolled', - 'subscription_license_uuid': license_uuids_by_course_run_key.get(course_run_key), - }) + def invalidate_default_enrollment_intentions_cache(self): + invalidate_default_enterprise_enrollment_intentions_learner_status_cache( + enterprise_customer_uuid=self.context.enterprise_customer_uuid, + lms_user_id=self.context.lms_user_id, + ) - # Invalidate the default enterprise enrollment intentions and enterprise course enrollments cache - # as the previously redeemable enrollment intentions have been processed/enrolled. - invalidate_default_enterprise_enrollment_intentions_learner_status_cache( - enterprise_customer_uuid=self.context.enterprise_customer_uuid, - lms_user_id=self.context.lms_user_id, - ) - invalidate_enterprise_course_enrollments_cache( - enterprise_customer_uuid=self.context.enterprise_customer_uuid, - lms_user_id=self.context.lms_user_id, - ) + def invalidate_enrollments_cache(self): + invalidate_enterprise_course_enrollments_cache( + enterprise_customer_uuid=self.context.enterprise_customer_uuid, + lms_user_id=self.context.lms_user_id, + ) class DashboardHandler(BaseLearnerPortalHandler): diff --git a/enterprise_access/apps/bffs/tests/test_handlers.py b/enterprise_access/apps/bffs/tests/test_handlers.py index 43dd0097..3a56e342 100644 --- a/enterprise_access/apps/bffs/tests/test_handlers.py +++ b/enterprise_access/apps/bffs/tests/test_handlers.py @@ -219,6 +219,154 @@ def test_load_and_process_staff_enterprise_customer( expected_staff_enterprise_customer = self.expected_enterprise_customer self.assertEqual(actual_staff_enterprise_customer, expected_staff_enterprise_customer) + @mock.patch('enterprise_access.apps.api_client.lms_client.LmsUserApiClient.get_enterprise_customers_for_user') + @mock.patch('enterprise_access.apps.api_client.lms_client.LmsApiClient.bulk_enroll_enterprise_learners') + def test_request_default_enrollment_realizations(self, mock_bulk_enroll, mock_get_customers): + mock_get_customers.return_value = { + **self.mock_enterprise_learner_response_data, + 'results': [], + } + license_uuids_by_course_run_key = { + 'course-run-1': 'license-1', + 'course-run-2': 'license-2', + } + context = HandlerContext(self.request) + handler = BaseLearnerPortalHandler(context) + + response = handler._request_default_enrollment_realizations(license_uuids_by_course_run_key) + + self.assertEqual(response, mock_bulk_enroll.return_value) + actual_customer_uuid_arg, actual_payload_arg = mock_bulk_enroll.call_args_list[0][0] + self.assertEqual(actual_customer_uuid_arg, context.enterprise_customer_uuid) + expected_payload = [ + {'user_id': context.lms_user_id, 'course_run_key': 'course-run-1', + 'license_uuid': 'license-1', 'is_default_auto_enrollment': True}, + {'user_id': context.lms_user_id, 'course_run_key': 'course-run-2', + 'license_uuid': 'license-2', 'is_default_auto_enrollment': True}, + ] + self.assertCountEqual(expected_payload, actual_payload_arg) + self.assertEqual(context.errors, []) + + @mock.patch('enterprise_access.apps.api_client.lms_client.LmsUserApiClient.get_enterprise_customers_for_user') + @mock.patch('enterprise_access.apps.api_client.lms_client.LmsApiClient.bulk_enroll_enterprise_learners') + def test_request_default_enrollment_realizations_exception(self, mock_bulk_enroll, mock_get_customers): + mock_get_customers.return_value = { + **self.mock_enterprise_learner_response_data, + 'results': [], + } + license_uuids_by_course_run_key = { + 'course-run-1': 'license-1', + 'course-run-2': 'license-2', + } + context = HandlerContext(self.request) + handler = BaseLearnerPortalHandler(context) + mock_bulk_enroll.side_effect = Exception('foobar') + + response = handler._request_default_enrollment_realizations(license_uuids_by_course_run_key) + + self.assertEqual(response, {}) + self.assertEqual( + context.errors, + [{ + 'developer_message': 'Default realization enrollment exception: foobar', + 'user_message': 'There was an exception realizing default enrollments', + }], + ) + + @mock.patch( + 'enterprise_access.apps.api_client.lms_client.LmsUserApiClient' + '.get_enterprise_customers_for_user') + @mock.patch( + 'enterprise_access.apps.api_client.lms_client.LmsApiClient' + '.bulk_enroll_enterprise_learners') + @mock.patch( + 'enterprise_access.apps.api_client.lms_client.LmsUserApiClient' + '.get_default_enterprise_enrollment_intentions_learner_status' + ) + def test_realize_default_enrollments( + self, mock_get_intentions, mock_bulk_enroll, mock_get_customers + ): + mock_get_customers.return_value = { + **self.mock_enterprise_learner_response_data, + 'results': [], + } + mock_get_intentions.return_value = { + "lms_user_id": self.mock_user.id, + "user_email": self.mock_user.email, + "enterprise_customer_uuid": self.mock_enterprise_customer_uuid, + "enrollment_statuses": { + "needs_enrollment": { + "enrollable": [ + { + 'applicable_enterprise_catalog_uuids': ['catalog-55', 'catalog-1'], + 'course_run_key': 'course-run-1', + }, + { + 'applicable_enterprise_catalog_uuids': ['catalog-88', 'catalog-1'], + 'course_run_key': 'course-run-2', + }, + ], + "not_enrollable": [], + }, + 'already_enrolled': [], + }, + } + mock_bulk_enroll.return_value = { + 'successes': [ + {'course_run_key': 'course-run-1'}, + ], + 'failures': [ + {'course_run_key': 'course-run-2'}, + ], + } + + context = HandlerContext(self.request) + context.data['enterprise_customer_user_subsidies'] = { + 'subscriptions': { + 'subscription_licenses_by_status': { + 'activated': [{ + 'uuid': 'license-1', + 'subscription_plan': { + 'is_current': True, + 'uuid': 'subscription-plan-1', + 'enterprise_catalog_uuid': 'catalog-1', + }, + }] + } + } + } + handler = BaseLearnerPortalHandler(context) + + handler.load_default_enterprise_enrollment_intentions() + handler.enroll_in_redeemable_default_enterprise_enrollment_intentions() + + actual_customer_uuid_arg, actual_payload_arg = mock_bulk_enroll.call_args_list[0][0] + self.assertEqual(actual_customer_uuid_arg, context.enterprise_customer_uuid) + expected_payload = [ + {'user_id': context.lms_user_id, 'course_run_key': 'course-run-1', + 'license_uuid': 'license-1', 'is_default_auto_enrollment': True}, + {'user_id': context.lms_user_id, 'course_run_key': 'course-run-2', + 'license_uuid': 'license-1', 'is_default_auto_enrollment': True}, + ] + self.assertCountEqual(expected_payload, actual_payload_arg) + self.assertEqual( + handler.context.data['default_enterprise_enrollment_realizations'], + [{ + 'course_key': 'course-run-1', + 'enrollment_status': 'enrolled', + 'subscription_license_uuid': 'license-1', + }], + ) + self.assertEqual( + handler.context.errors, + [{ + 'developer_message': ( + 'Default realization enrollment failures: [{"course_run_key": "course-run-2"}]' + ), + 'user_message': 'There were failures realizing default enrollments', + }], + ) + class TestDashboardHandler(TestHandlerContextMixin): """