Skip to content

Commit fb077cb

Browse files
committed
feat: add tests for async enrollment and unenrollment processes
1 parent 066c845 commit fb077cb

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

lms/djangoapps/instructor/tests/test_api.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,6 +1904,206 @@ def test_get_enrollment_status(self):
19041904
res_json = json.loads(response.content.decode('utf-8'))
19051905
assert res_json['enrollment_status'] == 'Enrollment status for nonotever@example.com: never enrolled'
19061906

1907+
@patch("lms.djangoapps.instructor_task.api.submit_student_enrollment_batch")
1908+
def test_enroll_async_processing_success(self, mock_submit_task):
1909+
"""Test async enrollment with async_processing=True"""
1910+
mock_task = Mock()
1911+
mock_task.task_id = "test-task-id-123"
1912+
mock_task.task_state = "QUEUED"
1913+
mock_submit_task.return_value = mock_task
1914+
url = reverse("students_update_enrollment", kwargs={"course_id": str(self.course.id)})
1915+
1916+
response = self.client.post(
1917+
url,
1918+
{
1919+
"identifiers": self.notenrolled_student.email,
1920+
"action": "enroll",
1921+
"email_students": False,
1922+
"async_processing": True,
1923+
},
1924+
)
1925+
1926+
self.assertEqual(response.status_code, 200)
1927+
res_json = json.loads(response.content.decode("utf-8"))
1928+
1929+
# Verify async response structure
1930+
self.assertEqual(res_json, {
1931+
"action": "enroll",
1932+
"auto_enroll": False,
1933+
"async_processing": True,
1934+
"task_id": "test-task-id-123",
1935+
"task_state": "QUEUED",
1936+
"message": "Async enroll task submitted for 1 students",
1937+
"total_students": 1
1938+
})
1939+
1940+
# Verify the task was called with correct parameters
1941+
self.assertTrue(mock_submit_task.called)
1942+
call_args = mock_submit_task.call_args
1943+
self.assertEqual(call_args[1]["course_key"], self.course.id)
1944+
self.assertEqual(call_args[1]["action"], "enroll")
1945+
self.assertEqual(call_args[1]["identifiers"], [self.notenrolled_student.email])
1946+
1947+
@patch("lms.djangoapps.instructor_task.api.submit_student_enrollment_batch")
1948+
def test_unenroll_async_processing_success(self, mock_submit_task):
1949+
"""Test async unenrollment with async_processing=True"""
1950+
mock_task = Mock()
1951+
mock_task.task_id = "test-unenroll-task-456"
1952+
mock_task.task_state = "QUEUED"
1953+
mock_submit_task.return_value = mock_task
1954+
url = reverse("students_update_enrollment", kwargs={"course_id": str(self.course.id)})
1955+
1956+
response = self.client.post(
1957+
url,
1958+
{
1959+
"identifiers": self.enrolled_student.email,
1960+
"action": "unenroll",
1961+
"email_students": True,
1962+
"async_processing": True,
1963+
},
1964+
)
1965+
1966+
self.assertEqual(response.status_code, 200)
1967+
res_json = json.loads(response.content.decode("utf-8"))
1968+
1969+
self.assertEqual(res_json, {
1970+
"action": "unenroll",
1971+
"auto_enroll": False,
1972+
"async_processing": True,
1973+
"task_id": "test-unenroll-task-456",
1974+
"task_state": "QUEUED",
1975+
"message": "Async unenroll task submitted for 1 students",
1976+
"total_students": 1
1977+
})
1978+
1979+
@patch("lms.djangoapps.instructor_task.api.submit_student_enrollment_batch")
1980+
def test_async_enrollment_already_running_error(self, mock_submit_task):
1981+
"""Test handling of AlreadyRunningError in async mode"""
1982+
mock_submit_task.side_effect = AlreadyRunningError("Task already running")
1983+
url = reverse("students_update_enrollment", kwargs={"course_id": str(self.course.id)})
1984+
1985+
response = self.client.post(
1986+
url, {"identifiers": self.notenrolled_student.email, "action": "enroll", "async_processing": True}
1987+
)
1988+
1989+
self.assertEqual(response.status_code, 200)
1990+
res_json = json.loads(response.content.decode("utf-8"))
1991+
self.assertTrue(res_json["async_processing"])
1992+
self.assertIn("error", res_json)
1993+
self.assertIn("already running", res_json["error"].lower())
1994+
self.assertEqual(res_json["action"], "enroll")
1995+
1996+
@patch("lms.djangoapps.instructor_task.api.submit_student_enrollment_batch")
1997+
def test_async_enrollment_multiple_identifiers(self, mock_submit_task):
1998+
"""Test async enrollment with multiple student identifiers"""
1999+
student2 = UserFactory(username="Student2", email="student2@example.com")
2000+
student3 = UserFactory(username="Student3", email="student3@example.com")
2001+
mock_task = Mock()
2002+
mock_task.task_id = "test-bulk-task-789"
2003+
mock_task.task_state = "QUEUED"
2004+
mock_submit_task.return_value = mock_task
2005+
identifiers = f"{self.notenrolled_student.email},{student2.email},{student3.email}"
2006+
url = reverse("students_update_enrollment", kwargs={"course_id": str(self.course.id)})
2007+
2008+
response = self.client.post(
2009+
url,
2010+
{
2011+
"identifiers": identifiers,
2012+
"action": "enroll",
2013+
"email_students": False,
2014+
"async_processing": True,
2015+
"auto_enroll": True,
2016+
},
2017+
)
2018+
2019+
self.assertEqual(response.status_code, 200)
2020+
res_json = json.loads(response.content.decode("utf-8"))
2021+
self.assertEqual(res_json["total_students"], 3)
2022+
self.assertTrue(res_json["auto_enroll"])
2023+
2024+
# Verify task was called with all identifiers
2025+
call_args = mock_submit_task.call_args
2026+
identifiers_list = call_args[1]["identifiers"]
2027+
self.assertEqual(len(identifiers_list), 3)
2028+
self.assertIn(self.notenrolled_student.email, identifiers_list)
2029+
self.assertIn(student2.email, identifiers_list)
2030+
self.assertIn(student3.email, identifiers_list)
2031+
2032+
def test_async_processing_default_false(self):
2033+
"""Test that async_processing defaults to False for backward compatibility"""
2034+
url = reverse("students_update_enrollment", kwargs={"course_id": str(self.course.id)})
2035+
2036+
response = self.client.post(
2037+
url,
2038+
{
2039+
"identifiers": self.notenrolled_student.email,
2040+
"action": "enroll",
2041+
"email_students": False,
2042+
# async_processing not provided
2043+
},
2044+
)
2045+
2046+
self.assertEqual(response.status_code, 200)
2047+
res_json = json.loads(response.content.decode("utf-8"))
2048+
2049+
# Should have sync response structure (with 'results')
2050+
self.assertIn("results", res_json)
2051+
self.assertNotIn("async_processing", res_json)
2052+
self.assertEqual(res_json["action"], "enroll")
2053+
2054+
@patch("lms.djangoapps.instructor_task.api.submit_student_enrollment_batch")
2055+
def test_async_enrollment_with_reason(self, mock_submit_task):
2056+
"""Test async enrollment with reason field"""
2057+
mock_task = Mock()
2058+
mock_task.task_id = "test-task-with-reason"
2059+
mock_task.task_state = "QUEUED"
2060+
mock_submit_task.return_value = mock_task
2061+
url = reverse("students_update_enrollment", kwargs={"course_id": str(self.course.id)})
2062+
2063+
response = self.client.post(
2064+
url,
2065+
{
2066+
"identifiers": self.notenrolled_student.email,
2067+
"action": "enroll",
2068+
"email_students": False,
2069+
"async_processing": True,
2070+
"reason": "Testing async enrollment",
2071+
},
2072+
)
2073+
2074+
self.assertEqual(response.status_code, 200)
2075+
res_json = json.loads(response.content.decode("utf-8"))
2076+
self.assertTrue(res_json["async_processing"])
2077+
2078+
# Verify reason was passed to task
2079+
call_args = mock_submit_task.call_args
2080+
self.assertEqual(call_args[1]["reason"], "Testing async enrollment")
2081+
2082+
def test_sync_enrollment_still_works(self):
2083+
"""Test that synchronous enrollment still works (async_processing=False)"""
2084+
url = reverse("students_update_enrollment", kwargs={"course_id": str(self.course.id)})
2085+
2086+
response = self.client.post(
2087+
url,
2088+
{
2089+
"identifiers": self.notenrolled_student.email,
2090+
"action": "enroll",
2091+
"email_students": False,
2092+
"async_processing": False,
2093+
},
2094+
)
2095+
2096+
self.assertEqual(response.status_code, 200)
2097+
res_json = json.loads(response.content.decode("utf-8"))
2098+
2099+
# Should have sync response structure
2100+
self.assertIn("results", res_json)
2101+
self.assertEqual(len(res_json["results"]), 1)
2102+
self.assertTrue(res_json["results"][0]["success"])
2103+
2104+
# Verify actual enrollment happened
2105+
self.assertTrue(CourseEnrollment.is_enrolled(self.notenrolled_student, self.course.id))
2106+
19072107

19082108
@ddt.ddt
19092109
class TestInstructorAPIBulkBetaEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTestCase):

0 commit comments

Comments
 (0)