Allow entitlements to be used past course has ended.
Allow entitlements to be used past the course has ended but upgrade deadline is still in future for already enrolled learners. PROD-1497
This commit is contained in:
@@ -970,23 +970,22 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase):
|
||||
assert course_entitlement.enrollment_course_run is not None
|
||||
|
||||
@patch("entitlements.rest_api.v1.views.get_course_runs_for_course")
|
||||
def test_enrollment_closed_upgrade_open(self, mock_get_course_runs):
|
||||
def test_already_enrolled_course_ended(self, mock_get_course_runs):
|
||||
"""
|
||||
Test that user can still select a session while course enrollment
|
||||
is closed and upgrade deadline is in future.
|
||||
Test that already enrolled user can still select a session while
|
||||
course has ended but upgrade deadline is in future.
|
||||
"""
|
||||
course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED)
|
||||
mock_get_course_runs.return_value = self.return_values
|
||||
|
||||
# Setup enrollment period to be in the past
|
||||
utc_now = datetime.now(UTC)
|
||||
self.course.enrollment_start = utc_now - timedelta(days=15)
|
||||
self.course.enrollment_end = utc_now - timedelta(days=1)
|
||||
self.course.start = utc_now - timedelta(days=15)
|
||||
self.course.end = utc_now - timedelta(days=1)
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
CourseOverview.update_select_courses([self.course.id], force_update=True)
|
||||
|
||||
assert CourseEnrollment.is_enrollment_closed(self.user, self.course)
|
||||
assert not CourseEnrollment.is_enrolled(self.user, self.course.id)
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.AUDIT)
|
||||
|
||||
url = reverse(
|
||||
self.ENTITLEMENTS_ENROLLMENT_NAMESPACE,
|
||||
@@ -1005,6 +1004,8 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase):
|
||||
|
||||
assert response.status_code == 201
|
||||
assert CourseEnrollment.is_enrolled(self.user, self.course.id)
|
||||
(enrolled_mode, is_active) = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
|
||||
assert is_active and (enrolled_mode == course_entitlement.mode)
|
||||
assert course_entitlement.enrollment_course_run is not None
|
||||
|
||||
@patch("entitlements.rest_api.v1.views.get_course_runs_for_course")
|
||||
|
||||
@@ -155,14 +155,15 @@ class TestCourseRunFulfillableForEntitlement(ModuleStoreTestCase):
|
||||
|
||||
assert not is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
|
||||
|
||||
def test_course_run_fulfillable_enrollment_ended_upgrade_open(self):
|
||||
def test_course_run_fulfillable_already_enrolled_course_ended(self):
|
||||
course_overview = self.create_course(
|
||||
start_from_now=-3,
|
||||
end_from_now=2,
|
||||
end_from_now=-1,
|
||||
enrollment_start_from_now=-2,
|
||||
enrollment_end_from_now=-1,
|
||||
)
|
||||
|
||||
entitlement = CourseEntitlementFactory.create(mode=CourseMode.VERIFIED)
|
||||
CourseEnrollmentFactory.create(user=entitlement.user, course_id=course_overview.id)
|
||||
|
||||
assert is_course_run_entitlement_fulfillable(course_overview.id, entitlement)
|
||||
|
||||
@@ -22,9 +22,8 @@ def is_course_run_entitlement_fulfillable(
|
||||
"""
|
||||
Checks that the current run meets the following criteria for an entitlement
|
||||
|
||||
1) Is currently running or start in the future
|
||||
2) A User can enroll in or is currently enrolled
|
||||
3) A User can upgrade to the entitlement mode
|
||||
1) A User can enroll in or is currently enrolled
|
||||
2) A User can upgrade to the entitlement mode
|
||||
|
||||
Arguments:
|
||||
course_run_key (CourseKey): The id of the Course run that is being checked.
|
||||
@@ -43,16 +42,13 @@ def is_course_run_entitlement_fulfillable(
|
||||
))
|
||||
return False
|
||||
|
||||
# Verify that the course is still running
|
||||
run_start = course_overview.start
|
||||
run_end = course_overview.end
|
||||
is_running = run_start and (not run_end or (run_end and (run_end > compare_date)))
|
||||
|
||||
# Verify that the course run can currently be enrolled, explicitly
|
||||
# not checking for enrollment end date becasue course run is still
|
||||
# fulfillable if enrollment has ended but VUD is in future
|
||||
# Verify that the course run can currently be enrolled
|
||||
enrollment_start = course_overview.enrollment_start
|
||||
can_enroll = not enrollment_start or enrollment_start < compare_date
|
||||
enrollment_end = course_overview.enrollment_end
|
||||
can_enroll = (
|
||||
(not enrollment_start or enrollment_start < compare_date)
|
||||
and (not enrollment_end or enrollment_end > compare_date)
|
||||
)
|
||||
|
||||
# Is the user already enrolled in the Course Run
|
||||
is_enrolled = CourseEnrollment.is_enrolled(entitlement.user, course_run_key)
|
||||
@@ -61,4 +57,4 @@ def is_course_run_entitlement_fulfillable(
|
||||
unexpired_paid_modes = [mode.slug for mode in CourseMode.paid_modes_for_course(course_run_key)]
|
||||
can_upgrade = unexpired_paid_modes and entitlement.mode in unexpired_paid_modes
|
||||
|
||||
return is_running and can_upgrade and (is_enrolled or can_enroll)
|
||||
return course_overview.start and can_upgrade and (is_enrolled or can_enroll)
|
||||
|
||||
@@ -169,7 +169,7 @@ describe('Course Card View', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should show a message if an there is an upcoming course run', () => {
|
||||
it('should show a message if there is an upcoming course run', () => {
|
||||
course.course_runs[0].is_enrollment_open = false;
|
||||
|
||||
setupView(course, false);
|
||||
|
||||
@@ -46,7 +46,9 @@ class CourseCardView extends Backbone.View {
|
||||
const $upgradeMessage = this.$('.upgrade-message');
|
||||
const $certStatus = this.$('.certificate-status');
|
||||
const $expiredNotification = this.$('.expired-notification');
|
||||
const courseKey = this.model.get('course_run_key');
|
||||
const expired = this.model.get('expired');
|
||||
const canUpgrade = this.model.get('upgrade_url') && !(expired === true);
|
||||
const courseUUID = this.model.get('uuid');
|
||||
const containerSelector = `#course-${courseUUID}`;
|
||||
|
||||
@@ -72,8 +74,7 @@ class CourseCardView extends Backbone.View {
|
||||
enterCourseBtn: `${containerSelector} .view-course-button`,
|
||||
availableSessions: JSON.stringify(this.model.get('course_runs')),
|
||||
entitlementUUID: this.entitlement.uuid,
|
||||
currentSessionId: this.model.isEnrolledInSession() ?
|
||||
this.model.get('course_run_key') : null,
|
||||
currentSessionId: this.model.isEnrolledInSession() && !canUpgrade ? courseKey : null,
|
||||
enrollUrl: this.model.get('enroll_url'),
|
||||
courseHomeUrl: this.model.get('course_url'),
|
||||
expiredAt: this.entitlement.expired_at,
|
||||
@@ -81,7 +82,7 @@ class CourseCardView extends Backbone.View {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.model.get('upgrade_url') && !(expired === true)) {
|
||||
if (canUpgrade) {
|
||||
this.upgradeMessage = new UpgradeMessageView({
|
||||
$el: $upgradeMessage,
|
||||
model: this.model,
|
||||
|
||||
@@ -141,7 +141,8 @@ class CourseEntitlementView extends Backbone.View {
|
||||
this.trackSessionChange(eventPage, eventAction, prevSession);
|
||||
|
||||
// With a containing backbone view, we can simply re-render the parent card
|
||||
if (this.$parentEl) {
|
||||
if (this.$parentEl &&
|
||||
this.courseCardModel.get('course_run_key') !== this.currentSessionSelection) {
|
||||
this.courseCardModel.updateCourseRun(this.currentSessionSelection);
|
||||
return;
|
||||
}
|
||||
@@ -388,6 +389,7 @@ class CourseEntitlementView extends Backbone.View {
|
||||
sessionData.forEach((session) => {
|
||||
Object.assign(session, {
|
||||
enrollment_end: CourseEntitlementView.formatDate(session.enrollment_end, dateFormat),
|
||||
session_id: session.session_id ? session.session_id : session.key,
|
||||
session_dates: this.courseCardModel.formatDateString({
|
||||
start_date: CourseEntitlementView.formatDate(session.start, dateFormat),
|
||||
advertised_start: session.advertised_start,
|
||||
|
||||
@@ -655,14 +655,19 @@ class TestSessionEntitlement(CatalogIntegrationMixin, TestCase):
|
||||
def test_unpublished_sessions_for_entitlement(self, mock_get_edx_api_data):
|
||||
"""
|
||||
Test unpublished course runs are not part of visible session entitlements when the user
|
||||
is not enrolled.
|
||||
is not enrolled and upgrade deadline is passed.
|
||||
"""
|
||||
catalog_course_run = CourseRunFactory.create(status=COURSE_UNPUBLISHED)
|
||||
catalog_course = CourseFactory(course_runs=[catalog_course_run])
|
||||
mock_get_edx_api_data.return_value = catalog_course
|
||||
course_key = CourseKey.from_string(catalog_course_run.get('key'))
|
||||
course_overview = CourseOverviewFactory.create(id=course_key, start=self.tomorrow)
|
||||
CourseModeFactory.create(mode_slug=CourseMode.VERIFIED, min_price=100, course_id=course_overview.id)
|
||||
CourseModeFactory.create(
|
||||
mode_slug=CourseMode.VERIFIED,
|
||||
min_price=100,
|
||||
course_id=course_overview.id,
|
||||
expiration_datetime=now() - timedelta(days=1)
|
||||
)
|
||||
entitlement = CourseEntitlementFactory(
|
||||
user=self.user, mode=CourseMode.VERIFIED
|
||||
)
|
||||
|
||||
@@ -553,9 +553,7 @@ def get_fulfillable_course_runs_for_entitlement(entitlement, course_runs):
|
||||
# User is enrolled in the course so we should include it in the list of enrollable sessions always
|
||||
# this will ensure it is available for the UI
|
||||
enrollable_sessions.append(course_run)
|
||||
elif (course_run.get('status') == COURSE_PUBLISHED and not
|
||||
is_enrolled_in_mode and
|
||||
is_course_run_entitlement_fulfillable(course_id, entitlement, search_time)):
|
||||
elif not is_enrolled_in_mode and is_course_run_entitlement_fulfillable(course_id, entitlement, search_time):
|
||||
enrollable_sessions.append(course_run)
|
||||
|
||||
enrollable_sessions.sort(key=lambda session: session.get('start'))
|
||||
|
||||
Reference in New Issue
Block a user