Skip to content

Commit

Permalink
fix: change course mode to course enrollment to calculate audit trial…
Browse files Browse the repository at this point in the history
… expired
  • Loading branch information
varshamenon4 committed Jan 6, 2025
1 parent 148e724 commit 0cfe0b4
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 61 deletions.
10 changes: 7 additions & 3 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,23 @@ Change Log
Unreleased
**********

4.6.3 - 2025-01-06
******************
* Uses CourseEnrollment instead of CourseMode to get the upgrade deadline required to calculate if a learner's audit trial is expired.
* Updated setup docs

4.6.2 - 2025-12-18
4.6.2 - 2024-12-18
******************
* Fixed the params for expiration_date in the admin table for audit trial.
* Add ENABLE_XPERT_AUDIT instructions.

4.6.1 - 2025-12-17
4.6.1 - 2024-12-17
******************
* Added an admin table for the LearningAssistantAuditTrial model. This table includes an expiration_date valued that is
calculated based on the start_date.

4.6.0 - 2025-12-10
4.6.0 - 2024-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
Expand Down
2 changes: 1 addition & 1 deletion learning_assistant/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
Plugin for a learning assistant backend, intended for use within edx-platform.
"""

__version__ = '4.6.2'
__version__ = '4.6.3'

default_app_config = 'learning_assistant.apps.LearningAssistantConfig' # pylint: disable=invalid-name
29 changes: 15 additions & 14 deletions learning_assistant/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import datetime
import logging
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone

from django.conf import settings
from django.contrib.auth import get_user_model
Expand All @@ -12,11 +12,6 @@
from jinja2 import BaseLoader, Environment
from opaque_keys import InvalidKeyError

try:
from common.djangoapps.course_modes.models import CourseMode
except ImportError:
CourseMode = None

from learning_assistant.constants import ACCEPTED_CATEGORY_TYPES, CATEGORY_TYPE_MAP
from learning_assistant.data import LearningAssistantAuditTrialData, LearningAssistantCourseEnabledData
from learning_assistant.models import (
Expand Down Expand Up @@ -307,19 +302,25 @@ def get_or_create_audit_trial(user):
)


def audit_trial_is_expired(audit_trial_data, courserun_key):
"""
Given a user (User), get or create the corresponding LearningAssistantAuditTrial trial object.
def audit_trial_is_expired(enrollment, audit_trial_data):
"""
course_mode = CourseMode.objects.get(course=courserun_key)
Given an enrollment and audit_trial_data, return whether the audit trial is expired as a boolean.
Arguments:
* enrollment (CourseEnrollment): the user course enrollment
* audit_trial_data (LearningAssistantAuditTrialData): the data related to the audit trial
upgrade_deadline = course_mode.expiration_datetime()
Returns:
* audit_trial_is_expired (boolean): whether the audit trial is expired
"""
upgrade_deadline = enrollment.upgrade_deadline
today = datetime.now(tz=timezone.utc)

# If the upgrade deadline has passed, return True for expired. Upgrade deadline is an optional attribute of a
# CourseMode, so if it's None, then do not return True.
days_until_upgrade_deadline = datetime.now() - upgrade_deadline if upgrade_deadline else None
# CourseEnrollment, so if it's None, then do not return True.
days_until_upgrade_deadline = today - upgrade_deadline if upgrade_deadline else None
if days_until_upgrade_deadline is not None and days_until_upgrade_deadline >= timedelta(days=0):
return True

# If the user's trial is past its expiry date, return True for expired. Else, return False.
return audit_trial_data is None or audit_trial_data.expiration_date <= datetime.now()
return audit_trial_data is None or audit_trial_data.expiration_date <= today
4 changes: 2 additions & 2 deletions learning_assistant/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def post(self, request, course_run_id):
# next message. Otherwise, return 403
elif enrollment_mode in CourseMode.UPSELL_TO_VERIFIED_MODES: # AUDIT, HONOR
audit_trial = get_or_create_audit_trial(request.user)
is_user_audit_trial_expired = audit_trial_is_expired(audit_trial, courserun_key)
is_user_audit_trial_expired = audit_trial_is_expired(enrollment_object, audit_trial)
if is_user_audit_trial_expired:
return Response(
status=http_status.HTTP_403_FORBIDDEN,
Expand Down Expand Up @@ -383,7 +383,7 @@ def get(self, request, course_run_id):
has_trial_access = (
enrollment_mode in valid_trial_access_modes
and audit_trial
and not audit_trial_is_expired(audit_trial, courserun_key)
and not audit_trial_is_expired(enrollment_object, audit_trial)
)

if (
Expand Down
79 changes: 38 additions & 41 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Test cases for the learning-assistant api module.
"""
import itertools
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, patch

import ddt
Expand Down Expand Up @@ -491,6 +491,7 @@ class GetAuditTrialExpirationDateTests(TestCase):
"""
Test suite for get_audit_trial_expiration_date.
"""

@ddt.data(
(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),
Expand All @@ -516,6 +517,7 @@ class GetAuditTrialTests(TestCase):
"""
Test suite for get_audit_trial.
"""

@freeze_time('2024-01-01')
def setUp(self):
super().setUp()
Expand Down Expand Up @@ -548,6 +550,7 @@ class GetOrCreateAuditTrialTests(TestCase):
"""
Test suite for get_or_create_audit_trial.
"""

def setUp(self):
super().setUp()
self.user = User(username='tester', email='[email protected]')
Expand Down Expand Up @@ -596,86 +599,80 @@ def setUp(self):
self.user = User(username='tester', email='[email protected]')
self.user.save()

self.upgrade_deadline = datetime.now() + timedelta(days=1) # 1 day from now

@freeze_time('2024-01-01')
@patch('learning_assistant.api.CourseMode')
def test_upgrade_deadline_expired(self, mock_course_mode):

mock_mode = MagicMock()
mock_mode.expiration_datetime.return_value = datetime.now() - timedelta(days=1) # yesterday
mock_course_mode.objects.get.return_value = mock_mode
@freeze_time('2024-01-01 00:00:01 UTC')
def test_upgrade_deadline_expired(self):
today = datetime.now(tz=timezone.utc)
mock_enrollment = MagicMock()
mock_enrollment.upgrade_deadline = today - timedelta(days=1) # yesterday

start_date = datetime.now()
start_date = today
audit_trial_data = LearningAssistantAuditTrialData(
user_id=self.user.id,
start_date=start_date,
expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
)

self.assertEqual(audit_trial_is_expired(audit_trial_data, self.course_key), True)

@freeze_time('2024-01-01')
@patch('learning_assistant.api.CourseMode')
def test_upgrade_deadline_none(self, mock_course_mode):
self.assertEqual(audit_trial_is_expired(mock_enrollment, audit_trial_data), True)

mock_mode = MagicMock()
mock_mode.expiration_datetime.return_value = None
mock_course_mode.objects.get.return_value = mock_mode
@freeze_time('2024-01-01 00:00:01 UTC')
def test_upgrade_deadline_none(self):
today = datetime.now(tz=timezone.utc)
mock_enrollment = MagicMock()
mock_enrollment.upgrade_deadline = None

# Verify that the audit trial data is considered when determing whether an audit trial is expired and not the
# Verify that the audit trial data is considered when determining whether an audit trial is expired and not the
# upgrade deadline.
start_date = datetime.now()
start_date = today
audit_trial_data = LearningAssistantAuditTrialData(
user_id=self.user.id,
start_date=start_date,
expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
)

self.assertEqual(audit_trial_is_expired(audit_trial_data, self.course_key), False)
self.assertEqual(audit_trial_is_expired(mock_enrollment, audit_trial_data), False)

start_date = datetime.now() - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS + 1)
start_date = today - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS + 1)
audit_trial_data = LearningAssistantAuditTrialData(
user_id=self.user.id,
start_date=start_date,
expiration_date=start_date + timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
)

self.assertEqual(audit_trial_is_expired(audit_trial_data, self.course_key), True)
self.assertEqual(audit_trial_is_expired(mock_enrollment, audit_trial_data), True)

@ddt.data(
# exactly the trial deadline
datetime(year=2024, month=1, day=1) - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
datetime(year=2024, month=1, day=1, tzinfo=timezone.utc) -
timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS),
# 1 day more than trial deadline
datetime(year=2024, month=1, day=1) - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS + 1),
datetime(year=2024, month=1, day=1, tzinfo=timezone.utc) -
timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS + 1),
)
@freeze_time('2024-01-01')
@patch('learning_assistant.api.CourseMode')
def test_audit_trial_expired(self, start_date, mock_course_mode):
mock_mode = MagicMock()
mock_mode.expiration_datetime.return_value = datetime.now() + timedelta(days=1) # tomorrow
mock_course_mode.objects.get.return_value = mock_mode
@freeze_time('2024-01-01 00:00:01 UTC')
def test_audit_trial_expired(self, start_date):
today = datetime.now(tz=timezone.utc)
mock_enrollment = MagicMock()
mock_enrollment.upgrade_deadline = today + timedelta(days=1) # tomorrow

audit_trial_data = LearningAssistantAuditTrialData(
user_id=self.user.id,
start_date=start_date,
expiration_date=get_audit_trial_expiration_date(start_date),
)

self.assertEqual(audit_trial_is_expired(audit_trial_data, self.upgrade_deadline), True)
self.assertEqual(audit_trial_is_expired(mock_enrollment, audit_trial_data), True)

@freeze_time('2024-01-01')
@patch('learning_assistant.api.CourseMode')
def test_audit_trial_unexpired(self, mock_course_mode):
mock_mode = MagicMock()
mock_mode.expiration_datetime.return_value = datetime.now() + timedelta(days=1) # tomorrow
mock_course_mode.objects.get.return_value = mock_mode
@freeze_time('2024-01-01 00:00:01 UTC')
def test_audit_trial_unexpired(self):
today = datetime.now(tz=timezone.utc)
mock_enrollment = MagicMock()
mock_enrollment.upgrade_deadline = today + timedelta(days=1) # tomorrow

start_date = datetime.now() - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS - 1)
start_date = today - timedelta(days=settings.LEARNING_ASSISTANT_AUDIT_TRIAL_LENGTH_DAYS - 1)
audit_trial_data = LearningAssistantAuditTrialData(
user_id=self.user.id,
start_date=start_date,
expiration_date=get_audit_trial_expiration_date(start_date),
)

self.assertEqual(audit_trial_is_expired(audit_trial_data, self.upgrade_deadline), False)
self.assertEqual(audit_trial_is_expired(mock_enrollment, audit_trial_data), False)

0 comments on commit 0cfe0b4

Please sign in to comment.