Skip to content

Commit ed2e77e

Browse files
authored
Merge pull request #36499 from raccoongang/feat/api-for-shifting-all-past-due-dates
feat: api for shifting all relative past due dates
2 parents 4c05137 + 5680600 commit ed2e77e

5 files changed

Lines changed: 337 additions & 78 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
Tests utils of course expirience feature.
3+
"""
4+
import datetime
5+
6+
from django.urls import reverse
7+
from django.utils import timezone
8+
from rest_framework.test import APIRequestFactory
9+
10+
from common.djangoapps.course_modes.models import CourseMode
11+
from common.djangoapps.student.models import CourseEnrollment
12+
from common.djangoapps.util.testing import EventTestMixin
13+
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
14+
from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin
15+
from openedx.core.djangoapps.schedules.models import Schedule
16+
from openedx.features.course_experience.api.v1.utils import (
17+
reset_deadlines_for_course,
18+
reset_course_deadlines_for_user,
19+
reset_bulk_course_deadlines
20+
)
21+
from xmodule.modulestore.tests.factories import CourseFactory
22+
23+
24+
class TestResetDeadlinesForCourse(EventTestMixin, BaseCourseHomeTests, MasqueradeMixin):
25+
"""
26+
Tests for reset deadlines endpoint.
27+
"""
28+
def setUp(self): # pylint: disable=arguments-differ
29+
super().setUp("openedx.features.course_experience.api.v1.utils.tracker")
30+
self.course = CourseFactory.create(self_paced=True, start=timezone.now() - datetime.timedelta(days=1000))
31+
32+
def test_reset_deadlines_for_course(self):
33+
enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
34+
enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=100)
35+
enrollment.schedule.save()
36+
37+
request = APIRequestFactory().post(
38+
reverse("course-experience-reset-course-deadlines"), {"course_key": self.course.id}
39+
)
40+
request.user = self.user
41+
42+
reset_deadlines_for_course(request, self.course.id, {})
43+
44+
assert enrollment.schedule.start_date < Schedule.objects.get(id=enrollment.schedule.id).start_date
45+
self.assert_event_emitted(
46+
"edx.ui.lms.reset_deadlines.clicked",
47+
courserun_key=str(self.course.id),
48+
is_masquerading=False,
49+
is_staff=False,
50+
org_key=self.course.org,
51+
user_id=self.user.id,
52+
)
53+
54+
def test_reset_deadlines_with_masquerade(self):
55+
"""Staff users should be able to masquerade as a learner and reset the learner's schedule"""
56+
student_username = self.user.username
57+
student_user_id = self.user.id
58+
student_enrollment = CourseEnrollment.enroll(self.user, self.course.id)
59+
student_enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=100)
60+
student_enrollment.schedule.save()
61+
62+
staff_enrollment = CourseEnrollment.enroll(self.staff_user, self.course.id)
63+
staff_enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=30)
64+
staff_enrollment.schedule.save()
65+
66+
self.switch_to_staff()
67+
self.update_masquerade(course=self.course, username=student_username)
68+
69+
request = APIRequestFactory().post(
70+
reverse("course-experience-reset-course-deadlines"), {"course_key": self.course.id}
71+
)
72+
request.user = self.staff_user
73+
request.session = self.client.session
74+
75+
reset_deadlines_for_course(request, self.course.id, {})
76+
77+
updated_schedule = Schedule.objects.get(id=student_enrollment.schedule.id)
78+
assert updated_schedule.start_date.date() == datetime.datetime.today().date()
79+
updated_staff_schedule = Schedule.objects.get(id=staff_enrollment.schedule.id)
80+
assert updated_staff_schedule.start_date == staff_enrollment.schedule.start_date
81+
self.assert_event_emitted(
82+
"edx.ui.lms.reset_deadlines.clicked",
83+
courserun_key=str(self.course.id),
84+
is_masquerading=True,
85+
is_staff=False,
86+
org_key=self.course.org,
87+
user_id=student_user_id,
88+
)
89+
90+
def test_reset_course_deadlines_for_user(self):
91+
"""Test the reset_course_deadlines_for_user utility function directly"""
92+
enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
93+
enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=100)
94+
enrollment.schedule.save()
95+
96+
result = reset_course_deadlines_for_user(self.user, self.course.id)
97+
98+
assert result is True
99+
assert enrollment.schedule.start_date < Schedule.objects.get(id=enrollment.schedule.id).start_date
100+
101+
def test_reset_bulk_course_deadlines(self):
102+
"""Test the reset_bulk_course_deadlines utility function"""
103+
enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
104+
enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=100)
105+
enrollment.schedule.save()
106+
107+
request = APIRequestFactory().post(
108+
reverse("course-experience-reset-all-course-deadlines"), {}
109+
)
110+
request.user = self.user
111+
112+
success_keys, failed_keys = reset_bulk_course_deadlines(request, [self.course.id], {})
113+
114+
assert len(success_keys) == 1
115+
assert self.course.id in success_keys
116+
assert len(failed_keys) == 0
117+
assert enrollment.schedule.start_date < Schedule.objects.get(id=enrollment.schedule.id).start_date
Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""
22
Tests for reset deadlines endpoint.
33
"""
4+
45
import datetime
6+
from unittest import mock
57

68
import ddt
79
from django.urls import reverse
@@ -10,7 +12,6 @@
1012

1113
from common.djangoapps.course_modes.models import CourseMode
1214
from common.djangoapps.student.models import CourseEnrollment
13-
from common.djangoapps.util.testing import EventTestMixin
1415
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
1516
from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin
1617
from openedx.core.djangoapps.schedules.models import Schedule
@@ -19,14 +20,12 @@
1920

2021

2122
@ddt.ddt
22-
class ResetCourseDeadlinesViewTests(EventTestMixin, BaseCourseHomeTests, MasqueradeMixin):
23+
class ResetCourseDeadlinesViewTests(BaseCourseHomeTests, MasqueradeMixin):
2324
"""
2425
Tests for reset deadlines endpoint.
2526
"""
2627
def setUp(self): # pylint: disable=arguments-differ
27-
# Need to supply tracker name for the EventTestMixin. Also, EventTestMixin needs to come
28-
# first in class inheritance so the setUp call here appropriately works
29-
super().setUp('openedx.features.course_experience.api.v1.views.tracker')
28+
super().setUp()
3029
self.course = CourseFactory.create(self_paced=True, start=timezone.now() - datetime.timedelta(days=1000))
3130

3231
def test_reset_deadlines(self):
@@ -37,20 +36,11 @@ def test_reset_deadlines(self):
3736
response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course': self.course.id})
3837
assert response.status_code == 400
3938
assert enrollment.schedule == Schedule.objects.get(id=enrollment.schedule.id)
40-
self.assert_no_events_were_emitted()
4139

4240
# Test correct post body
4341
response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id})
4442
assert response.status_code == 200
4543
assert enrollment.schedule.start_date < Schedule.objects.get(id=enrollment.schedule.id).start_date
46-
self.assert_event_emitted(
47-
'edx.ui.lms.reset_deadlines.clicked',
48-
courserun_key=str(self.course.id),
49-
is_masquerading=False,
50-
is_staff=False,
51-
org_key=self.course.org,
52-
user_id=self.user.id,
53-
)
5444

5545
@override_waffle_flag(RELATIVE_DATES_FLAG, active=True)
5646
@override_waffle_flag(RELATIVE_DATES_DISABLE_RESET_FLAG, active=True)
@@ -62,36 +52,6 @@ def test_reset_deadlines_disabled(self):
6252
response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id})
6353
assert response.status_code == 200
6454
assert enrollment.schedule == Schedule.objects.get(id=enrollment.schedule.id)
65-
self.assert_no_events_were_emitted()
66-
67-
def test_reset_deadlines_with_masquerade(self):
68-
""" Staff users should be able to masquerade as a learner and reset the learner's schedule """
69-
student_username = self.user.username
70-
student_user_id = self.user.id
71-
student_enrollment = CourseEnrollment.enroll(self.user, self.course.id)
72-
student_enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=100)
73-
student_enrollment.schedule.save()
74-
75-
staff_enrollment = CourseEnrollment.enroll(self.staff_user, self.course.id)
76-
staff_enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=30)
77-
staff_enrollment.schedule.save()
78-
79-
self.switch_to_staff()
80-
self.update_masquerade(course=self.course, username=student_username)
81-
82-
self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id})
83-
updated_schedule = Schedule.objects.get(id=student_enrollment.schedule.id)
84-
assert updated_schedule.start_date.date() == datetime.datetime.today().date()
85-
updated_staff_schedule = Schedule.objects.get(id=staff_enrollment.schedule.id)
86-
assert updated_staff_schedule.start_date == staff_enrollment.schedule.start_date
87-
self.assert_event_emitted(
88-
'edx.ui.lms.reset_deadlines.clicked',
89-
courserun_key=str(self.course.id),
90-
is_masquerading=True,
91-
is_staff=False,
92-
org_key=self.course.org,
93-
user_id=student_user_id,
94-
)
9555

9656
def test_post_unauthenticated_user(self):
9757
self.client.logout()
@@ -115,3 +75,52 @@ def test_mobile_get_unauthenticated_user(self):
11575
self.client.logout()
11676
response = self.client.get(reverse('course-experience-course-deadlines-mobile', args=[self.course.id]))
11777
assert response.status_code == 401
78+
79+
80+
class ResetAllRelativeCourseDeadlinesViewTests(BaseCourseHomeTests, MasqueradeMixin):
81+
"""
82+
Tests for reset all relative deadlines endpoint.
83+
"""
84+
85+
def setUp(self): # pylint: disable=arguments-differ
86+
super().setUp()
87+
self.course = CourseFactory.create(self_paced=True, start=timezone.now() - datetime.timedelta(days=1000))
88+
self.enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
89+
self.enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=100)
90+
self.enrollment.schedule.save()
91+
92+
def test_reset_all_course_deadlines(self):
93+
"""
94+
Test reset all course deadlines endpoint
95+
"""
96+
response = self.client.post(
97+
reverse("course-experience-reset-all-course-deadlines"),
98+
{},
99+
)
100+
assert response.status_code == 200
101+
assert self.enrollment.schedule.start_date < Schedule.objects.get(id=self.enrollment.schedule.id).start_date
102+
assert str(self.course.id) in response.data.get("success_course_keys")
103+
104+
def test_reset_all_course_deadlines_failure(self):
105+
"""
106+
Raise exception on reset_bulk_course_deadlines and assert if failure course id is returned
107+
"""
108+
with mock.patch(
109+
"openedx.features.course_experience.api.v1.views.reset_bulk_course_deadlines",
110+
return_value=([], [self.course.id]),
111+
):
112+
response = self.client.post(reverse("course-experience-reset-all-course-deadlines"), {})
113+
114+
assert response.status_code == 200
115+
assert str(self.course.id) in response.data.get("failed_course_keys")
116+
117+
def test_post_unauthenticated_user(self):
118+
"""
119+
Test reset all relative course deadlines endpoint for unauthenticated user
120+
"""
121+
self.client.logout()
122+
response = self.client.post(
123+
reverse("course-experience-reset-all-course-deadlines"),
124+
{},
125+
)
126+
assert response.status_code == 401

openedx/features/course_experience/api/v1/urls.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44

55

66
from django.conf import settings
7-
from django.urls import re_path
7+
from django.urls import re_path, path
88

9-
from openedx.features.course_experience.api.v1.views import reset_course_deadlines, CourseDeadlinesMobileView
9+
from openedx.features.course_experience.api.v1.views import (
10+
reset_course_deadlines,
11+
reset_all_course_deadlines,
12+
CourseDeadlinesMobileView,
13+
)
1014

1115
urlpatterns = []
1216

@@ -17,6 +21,11 @@
1721
reset_course_deadlines,
1822
name='course-experience-reset-course-deadlines'
1923
),
24+
path(
25+
'v1/reset_all_course_deadlines/',
26+
reset_all_course_deadlines,
27+
name='course-experience-reset-all-course-deadlines',
28+
)
2029
]
2130

2231
# URL for retrieving course deadlines info

0 commit comments

Comments
 (0)