Files
Deborah Kaplan e7771d6526 fix: catch a possible exception in beta course configuration (#36172)
* fix: catch a possible exception in beta course configuration

when the learner is a beta tester, and the beta test has been set up with a large duration, the courseware djangoapp can return an overflow error on a timedelta call. In those circumstances, this defaults to returning an unmodified date.

FIXES: APER-3848
2025-01-27 13:59:02 -05:00

184 lines
8.4 KiB
Python

"""
Unit test for various Utility functions
"""
import json
from datetime import date, timedelta
from unittest.mock import patch
import ddt
from django.test import TestCase
from edx_rest_api_client.client import OAuthAPIClient
from oauth2_provider.models import Application
from requests.models import Response
from rest_framework import status
from common.djangoapps.student.tests.factories import GlobalStaffFactory, UserFactory
from lms.djangoapps.courseware.constants import UNEXPECTED_ERROR_IS_ELIGIBLE
from lms.djangoapps.courseware.tests.factories import FinancialAssistanceConfigurationFactory
from lms.djangoapps.courseware.access_utils import adjust_start_date
from lms.djangoapps.courseware.utils import (
create_financial_assistance_application,
get_financial_assistance_application_status,
is_eligible_for_financial_aid,
)
@ddt.ddt
class TestAccessUtils(TestCase):
"""Tests for the access_utils functions."""
@ddt.data(
# days_early_for_beta, is_beta_user, expected_date
(None, True, "2025-01-03"),
(2, True, "2025-01-01"),
(timedelta.max.days + 10, True, "2025-01-03"),
(None, False, "2025-01-03"),
(2, False, "2025-01-03"),
(timedelta.max.days + 10, False, "2025-01-03"),
)
@ddt.unpack
def test_adjust_start_date(self, days_early_for_beta, is_beta_user, expected_date):
"""Tests adjust_start_date
Should only modify the date if the user is beta for the course,
and `days_early_for_beta` is sensible number."""
start = date(2025, 1, 3)
expected = date.fromisoformat(expected_date)
user = "princessofpower"
course_key = "edx1+8675"
with patch("lms.djangoapps.courseware.access_utils.CourseBetaTesterRole") as role_mock:
instance = role_mock.return_value
instance.has_user.return_value = is_beta_user
adjusted_date = adjust_start_date(user, days_early_for_beta, start, course_key)
self.assertEqual(expected, adjusted_date)
@ddt.ddt
class TestFinancialAssistanceViews(TestCase):
"""
Tests new financial assistance views that communicate with edx-financial-assistance backend.
"""
def setUp(self) -> None:
super().setUp()
self.test_course_id = "course-v1:edX+Test+1"
self.user = UserFactory()
self.global_staff = GlobalStaffFactory.create()
_ = FinancialAssistanceConfigurationFactory(
api_base_url="http://financial.assistance.test:1234",
service_username=self.global_staff.username,
fa_backend_enabled_courses_percentage=100,
enabled=True,
)
_ = Application.objects.create(
name="Test Application",
user=self.global_staff,
client_type=Application.CLIENT_PUBLIC,
authorization_grant_type=Application.GRANT_CLIENT_CREDENTIALS,
)
def _mock_response(self, status_code, content=None):
"""
Generates a python core response which is used as a default response in edx-rest-api-client.
"""
mock_response = Response()
mock_response.status_code = status_code
mock_response._content = json.dumps(content).encode("utf-8") # pylint: disable=protected-access
return mock_response
@ddt.data(
{"is_eligible": True, "reason": None},
{"is_eligible": False, "reason": "This course is not eligible for financial aid"},
)
def test_is_eligible_for_financial_aid(self, response_data):
"""
Tests the functionality of is_eligible_for_financial_aid which calls edx-financial-assistance backend
to return eligibility status for financial assistance for a given course.
"""
with patch.object(OAuthAPIClient, "request") as oauth_mock:
oauth_mock.return_value = self._mock_response(status.HTTP_200_OK, response_data)
is_eligible, reason = is_eligible_for_financial_aid(self.test_course_id)
assert is_eligible is response_data.get("is_eligible")
assert reason == response_data.get("reason")
def test_is_eligible_for_financial_aid_invalid_course_id(self):
"""
Tests the functionality of is_eligible_for_financial_aid for an invalid course id.
"""
error_message = f"Invalid course id {self.test_course_id} provided"
with patch.object(OAuthAPIClient, "request") as oauth_mock:
oauth_mock.return_value = self._mock_response(status.HTTP_400_BAD_REQUEST, {"message": error_message})
is_eligible, reason = is_eligible_for_financial_aid(self.test_course_id)
assert is_eligible is False
assert reason == error_message
def test_is_eligible_for_financial_aid_invalid_unexpected_error(self):
"""
Tests the functionality of is_eligible_for_financial_aid for an unexpected error
"""
with patch.object(OAuthAPIClient, "request") as oauth_mock:
oauth_mock.return_value = self._mock_response(
status.HTTP_500_INTERNAL_SERVER_ERROR, {"error": "unexpected error occurred"}
)
is_eligible, reason = is_eligible_for_financial_aid(self.test_course_id)
assert is_eligible is False
assert reason == UNEXPECTED_ERROR_IS_ELIGIBLE
def test_get_financial_assistance_application_status(self):
"""
Tests the functionality of get_financial_assistance_application_status against a user id and a course id
edx-financial-assistance backend to return status of a financial assistance application.
"""
test_response = {"id": 123, "status": "ACCEPTED", "coupon_code": "ABCD.."}
with patch.object(OAuthAPIClient, "request") as oauth_mock:
oauth_mock.return_value = self._mock_response(status.HTTP_200_OK, test_response)
has_application, reason = get_financial_assistance_application_status(self.user.id, self.test_course_id)
assert has_application is True
assert reason == test_response
@ddt.data(
{"status": status.HTTP_400_BAD_REQUEST, "content": {"message": "Invalid course id provided"}},
{"status": status.HTTP_404_NOT_FOUND, "content": {"message": "Application details not found"}},
)
def test_get_financial_assistance_application_status_unsuccessful(self, response_data):
"""
Tests unsuccessful scenarios of get_financial_assistance_application_status
against a user id and a course id edx-financial-assistance backend.
"""
with patch.object(OAuthAPIClient, "request") as oauth_mock:
oauth_mock.return_value = self._mock_response(response_data.get("status"), response_data.get("content"))
has_application, reason = get_financial_assistance_application_status(self.user.id, self.test_course_id)
assert has_application is False
assert reason == response_data.get("content").get("message")
def test_create_financial_assistance_application(self):
"""
Tests the functionality of create_financial_assistance_application which calls edx-financial-assistance backend
to create a new financial assistance application given a form data.
"""
test_form_data = {
"lms_user_id": self.user.id,
"course_id": self.test_course_id,
}
with patch.object(OAuthAPIClient, "request") as oauth_mock:
oauth_mock.return_value = self._mock_response(status.HTTP_200_OK, {"success": True})
response = create_financial_assistance_application(form_data=test_form_data)
assert response.status_code == status.HTTP_204_NO_CONTENT
def test_create_financial_assistance_application_bad_request(self):
"""
Tests the functionality of create_financial_assistance_application which calls edx-financial-assistance backend
to create a new financial assistance application given a form data.
"""
test_form_data = {
"lms_user_id": self.user.id,
"course_id": "invalid_course_id",
}
error_response = {"message": "Invalid course id provided"}
with patch.object(OAuthAPIClient, "request") as oauth_mock:
oauth_mock.return_value = self._mock_response(status.HTTP_400_BAD_REQUEST, error_response)
response = create_financial_assistance_application(form_data=test_form_data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert json.loads(response.content.decode("utf-8")) == error_response