From 39d2f2185e60b2fe17585f1b636f060c75d20eef Mon Sep 17 00:00:00 2001 From: Michael Roytman Date: Wed, 11 Dec 2024 13:57:06 -0500 Subject: [PATCH] feat: add audit_trial_length attribute to ChatSummaryView response (#143) In order for the frontend to know the currently configured trial length of an audit trial in days, an audit_trial_length attribute is added to the ChatSummaryView response. This property is supplied whether or not a learner has an audit trial. It represents the number of days in an audit trial as currently configured. It does not represent the number of days in the learner's current trial. --- CHANGELOG.rst | 6 ++++++ learning_assistant/__init__.py | 2 +- learning_assistant/api.py | 13 ++----------- learning_assistant/utils.py | 25 +++++++++++++++++++++++++ learning_assistant/views.py | 11 +++++++---- tests/test_api.py | 26 ++++++++++++++------------ tests/test_utils.py | 31 ++++++++++++++++++++++++++++++- tests/test_views.py | 18 ++++++++++++++++++ 8 files changed, 103 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 96feb25..113eca0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,12 @@ Change Log Unreleased ********** +4.6.0 - 2025-12-10 +****************** +* Add an audit_trial_length_days attribute to the response returned by the ChatSummaryView, representing the + number of days in an audit trial as currently configured. It does not necessarily represent the number of days in the + learner's current trial. + 4.5.0 - 2024-12-04 ****************** * Add local setup to readme diff --git a/learning_assistant/__init__.py b/learning_assistant/__init__.py index b14e6c9..dd0c62e 100644 --- a/learning_assistant/__init__.py +++ b/learning_assistant/__init__.py @@ -2,6 +2,6 @@ Plugin for a learning assistant backend, intended for use within edx-platform. """ -__version__ = '4.5.0' +__version__ = '4.6.0' default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name diff --git a/learning_assistant/api.py b/learning_assistant/api.py index 9483888..3f1ff5c 100644 --- a/learning_assistant/api.py +++ b/learning_assistant/api.py @@ -34,6 +34,7 @@ traverse_block_pre_order, ) from learning_assistant.text_utils import html_to_text +from learning_assistant.utils import get_audit_trial_length_days log = logging.getLogger(__name__) User = get_user_model() @@ -247,17 +248,7 @@ def get_audit_trial_expiration_date(start_date): Returns: * expiration_date (datetime): the expiration date of the audit trial """ - default_trial_length_days = 14 - - trial_length_days = getattr(settings, 'LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS', default_trial_length_days) - - if trial_length_days is None: - trial_length_days = default_trial_length_days - - # If LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS is set to a negative number, assume it should be 0. - # pylint: disable=consider-using-max-builtin - if trial_length_days < 0: - trial_length_days = 0 + trial_length_days = get_audit_trial_length_days() expiration_datetime = start_date + timedelta(days=trial_length_days) return expiration_datetime diff --git a/learning_assistant/utils.py b/learning_assistant/utils.py index 79a9884..3a7ff59 100644 --- a/learning_assistant/utils.py +++ b/learning_assistant/utils.py @@ -112,3 +112,28 @@ def user_role_is_staff(role): * bool: whether the user's role is that of a staff member """ return role in ('staff', 'instructor') + + +def get_audit_trial_length_days(): + """ + Return the length of an audit trial in days. + + Arguments: + * None + + Returns: + * int: the length of an audit trial in days + """ + default_trial_length_days = 14 + + trial_length_days = getattr(settings, 'LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS', default_trial_length_days) + + if trial_length_days is None: + trial_length_days = default_trial_length_days + + # If LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS is set to a negative number, assume it should be 0. + # pylint: disable=consider-using-max-builtin + if trial_length_days < 0: + trial_length_days = 0 + + return trial_length_days diff --git a/learning_assistant/views.py b/learning_assistant/views.py index 58351e1..87d5713 100644 --- a/learning_assistant/views.py +++ b/learning_assistant/views.py @@ -35,7 +35,7 @@ from learning_assistant.models import LearningAssistantMessage from learning_assistant.serializers import MessageSerializer from learning_assistant.toggles import chat_history_enabled -from learning_assistant.utils import get_chat_response, user_role_is_staff +from learning_assistant.utils import get_audit_trial_length_days, get_chat_response, user_role_is_staff log = logging.getLogger(__name__) @@ -335,10 +335,11 @@ def get(self, request, course_run_id): "timestamp": "2024-12-02T15:04:40.084584Z" } ], - "trial": { + "audit_trial": { "start_date": "2024-12-02T14:59:16.148236Z", - "expiration_date": "2024-12-16T14:59:16.148236Z" - } + "expiration_date": "2024-12-26T14:59:16.148236Z", + }, + "audit_trial_length_days": 14 } """ try: @@ -409,4 +410,6 @@ def get(self, request, course_run_id): data['audit_trial'] = trial_data + data['audit_trial_length_days'] = get_audit_trial_length_days() + return Response(status=http_status.HTTP_200_OK, data=data) diff --git a/tests/test_api.py b/tests/test_api.py index eda7602..3937856 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -492,22 +492,24 @@ class GetAuditTrialExpirationDateTests(TestCase): Test suite for get_audit_trial_expiration_date. """ @ddt.data( - (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 15, 0, 0, 0), None), - (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 2, 1, 0, 0, 0), None), + (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 2, 0, 0, 0), 1), + (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 1, 19, 0, 0, 0), 1), + (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 8, 0, 0, 0), 7), + (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 1, 25, 0, 0, 0), 7), (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 15, 0, 0, 0), 14), (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 2, 1, 0, 0, 0), 14), - (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 1, 0, 0, 0), -1), - (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 1, 18, 0, 0, 0), -1), - (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 1, 0, 0, 0), 0), - (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 1, 18, 0, 0, 0), 0), - (datetime(2024, 1, 1, 0, 0, 0), datetime(2024, 1, 4, 0, 0, 0), 3), - (datetime(2024, 1, 18, 0, 0, 0), datetime(2024, 1, 21, 0, 0, 0), 3), ) @ddt.unpack - def test_expiration_date(self, start_date, expected_expiration_date, trial_length_days): - with override_settings(LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS=trial_length_days): - expiration_date = get_audit_trial_expiration_date(start_date) - self.assertEqual(expected_expiration_date, expiration_date) + @patch('learning_assistant.api.get_audit_trial_length_days') + def test_expiration_date( + self, start_date, + expected_expiration_date, + trial_length_days, + mock_get_audit_trial_length_days + ): + mock_get_audit_trial_length_days.return_value = trial_length_days + expiration_date = get_audit_trial_expiration_date(start_date) + self.assertEqual(expected_expiration_date, expiration_date) class GetAuditTrialTests(TestCase): diff --git a/tests/test_utils.py b/tests/test_utils.py index b81830e..1ab0315 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,7 +10,12 @@ from django.test import TestCase, override_settings from requests.exceptions import ConnectTimeout -from learning_assistant.utils import get_chat_response, get_reduced_message_list, user_role_is_staff +from learning_assistant.utils import ( + get_audit_trial_length_days, + get_chat_response, + get_reduced_message_list, + user_role_is_staff, +) @ddt.ddt @@ -145,3 +150,27 @@ class UserRoleIsStaffTests(TestCase): @ddt.unpack def test_user_role_is_staff(self, role, expected_value): self.assertEqual(user_role_is_staff(role), expected_value) + + +@ddt.ddt +class GetAuditTrialLengthDaysTests(TestCase): + """ + Tests for the get_audit_trial_length_days helper function. + """ + @ddt.data( + (None, 14), + (0, 0), + (-7, 0), + (7, 7), + (14, 14), + (28, 28), + ) + @ddt.unpack + def test_get_audit_trial_length_days_with_value(self, setting_value, expected_value): + with patch.object(settings, 'LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS', setting_value): + self.assertEqual(get_audit_trial_length_days(), expected_value) + + @override_settings() + def test_get_audit_trial_length_days_no_setting(self): + del settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS + self.assertEqual(get_audit_trial_length_days(), 14) diff --git a/tests/test_views.py b/tests/test_views.py index a70aad5..e0581b2 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -495,12 +495,14 @@ def test_invalid_course_id(self, mock_course_key): ['verified', 'credit', 'no-id', 'audit', None], # course mode [True, False], # trial available [True, False], # trial expired + [7, 14], # trial length ) ) @ddt.unpack @patch('learning_assistant.views.audit_trial_is_expired') @patch('learning_assistant.views.chat_history_enabled') @patch('learning_assistant.views.learning_assistant_enabled') + @patch('learning_assistant.views.get_audit_trial_length_days') @patch('learning_assistant.views.get_user_role') @patch('learning_assistant.views.CourseEnrollment') @patch('learning_assistant.views.CourseMode') @@ -512,9 +514,11 @@ def test_chat_summary_with_access_instructor( course_mode_mock_value, trial_available, audit_trial_is_expired_mock_value, + audit_trial_length_days_mock_value, mock_mode, mock_enrollment, mock_get_user_role, + mock_get_audit_trial_length_days, mock_learning_assistant_enabled, mock_chat_history_enabled, mock_audit_trial_is_expired, @@ -555,6 +559,7 @@ def test_chat_summary_with_access_instructor( # Set up audit trial data. mock_audit_trial_is_expired.return_value = audit_trial_is_expired_mock_value + mock_get_audit_trial_length_days.return_value = audit_trial_length_days_mock_value trial_start_date = datetime(2024, 1, 1, 0, 0, 0) if trial_available: @@ -594,6 +599,7 @@ def test_chat_summary_with_access_instructor( expected_trial_data['expiration_date'] = trial_start_date + timedelta(days=14) self.assertEqual(response.data['audit_trial'], expected_trial_data) + self.assertEqual(response.data['audit_trial_length_days'], audit_trial_length_days_mock_value) @ddt.data( *product( @@ -603,12 +609,14 @@ def test_chat_summary_with_access_instructor( ['verified', 'credit', 'no-id'], # course mode [True, False], # trial available [True, False], # trial expired + [7, 14], # trial length ) ) @ddt.unpack @patch('learning_assistant.views.audit_trial_is_expired') @patch('learning_assistant.views.chat_history_enabled') @patch('learning_assistant.views.learning_assistant_enabled') + @patch('learning_assistant.views.get_audit_trial_length_days') @patch('learning_assistant.views.get_user_role') @patch('learning_assistant.views.CourseEnrollment') @patch('learning_assistant.views.CourseMode') @@ -620,9 +628,11 @@ def test_chat_summary_with_full_access_student( course_mode_mock_value, trial_available, audit_trial_is_expired_mock_value, + audit_trial_length_days_mock_value, mock_mode, mock_enrollment, mock_get_user_role, + mock_get_audit_trial_length_days, mock_learning_assistant_enabled, mock_chat_history_enabled, mock_audit_trial_is_expired, @@ -663,6 +673,7 @@ def test_chat_summary_with_full_access_student( # Set up audit trial data. mock_audit_trial_is_expired.return_value = audit_trial_is_expired_mock_value + mock_get_audit_trial_length_days.return_value = audit_trial_length_days_mock_value trial_start_date = datetime(2024, 1, 1, 0, 0, 0) if trial_available: @@ -702,6 +713,7 @@ def test_chat_summary_with_full_access_student( expected_trial_data['expiration_date'] = trial_start_date + timedelta(days=14) self.assertEqual(response.data['audit_trial'], expected_trial_data) + self.assertEqual(response.data['audit_trial_length_days'], audit_trial_length_days_mock_value) @ddt.data( *product( @@ -711,12 +723,14 @@ def test_chat_summary_with_full_access_student( ['audit'], # course mode [True, False], # trial available [True, False], # trial expired + [7, 14], # trial length ) ) @ddt.unpack @patch('learning_assistant.views.audit_trial_is_expired') @patch('learning_assistant.views.chat_history_enabled') @patch('learning_assistant.views.learning_assistant_enabled') + @patch('learning_assistant.views.get_audit_trial_length_days') @patch('learning_assistant.views.get_user_role') @patch('learning_assistant.views.CourseEnrollment') @patch('learning_assistant.views.CourseMode') @@ -728,9 +742,11 @@ def test_chat_summary_with_trial_access_student( course_mode_mock_value, trial_available, audit_trial_is_expired_mock_value, + audit_trial_length_days_mock_value, mock_mode, mock_enrollment, mock_get_user_role, + mock_get_audit_trial_length_days, mock_learning_assistant_enabled, mock_chat_history_enabled, mock_audit_trial_is_expired, @@ -771,6 +787,7 @@ def test_chat_summary_with_trial_access_student( # Set up audit trial data. mock_audit_trial_is_expired.return_value = audit_trial_is_expired_mock_value + mock_get_audit_trial_length_days.return_value = audit_trial_length_days_mock_value trial_start_date = datetime(2024, 1, 1, 0, 0, 0) if trial_available: @@ -810,3 +827,4 @@ def test_chat_summary_with_trial_access_student( expected_trial_data['expiration_date'] = trial_start_date + timedelta(days=14) self.assertEqual(response.data['audit_trial'], expected_trial_data) + self.assertEqual(response.data['audit_trial_length_days'], audit_trial_length_days_mock_value)