|
3 | 3 | """ |
4 | 4 |
|
5 | 5 | import datetime |
| 6 | +import hashlib |
6 | 7 | import json |
| 8 | +from unittest import mock |
7 | 9 | from unittest.mock import MagicMock, Mock, patch |
8 | 10 | from uuid import uuid4 |
9 | 11 |
|
| 12 | +import ddt |
10 | 13 | import pytest |
11 | 14 | import pytz |
12 | | -import ddt |
13 | | -from testfixtures import LogCapture |
14 | 15 | from celery.states import FAILURE, SUCCESS |
15 | | -from xmodule.modulestore.exceptions import ItemNotFoundError |
| 16 | +from django.http import HttpRequest |
| 17 | +from opaque_keys.edx.keys import CourseKey |
| 18 | +from testfixtures import LogCapture |
16 | 19 |
|
17 | 20 | from common.djangoapps.student.tests.factories import UserFactory |
18 | 21 | from common.test.utils import normalize_repr |
|
44 | 47 | submit_rescore_problem_for_student, |
45 | 48 | submit_reset_problem_attempts_for_all_students, |
46 | 49 | submit_reset_problem_attempts_in_entrance_exam, |
| 50 | + submit_student_enrollment_batch, |
47 | 51 | ) |
48 | 52 | from lms.djangoapps.instructor_task.api_helper import AlreadyRunningError, QueueConnectionError |
49 | 53 | from lms.djangoapps.instructor_task.data import InstructorTaskTypes |
|
52 | 56 | export_ora2_data, |
53 | 57 | export_ora2_submission_files, |
54 | 58 | generate_anonymous_ids_for_course, |
| 59 | + student_enrollment_batch, |
55 | 60 | ) |
56 | 61 | from lms.djangoapps.instructor_task.tests.factories import InstructorTaskFactory, InstructorTaskScheduleFactory |
57 | 62 | from lms.djangoapps.instructor_task.tests.test_base import ( |
58 | 63 | TEST_COURSE_KEY, |
59 | 64 | InstructorTaskCourseTestCase, |
60 | 65 | InstructorTaskModuleTestCase, |
61 | 66 | InstructorTaskTestCase, |
62 | | - TestReportMixin |
| 67 | + TestReportMixin, |
63 | 68 | ) |
| 69 | +from xmodule.modulestore.exceptions import ItemNotFoundError |
64 | 70 |
|
65 | 71 | LOG_PATH = 'lms.djangoapps.instructor_task.api' |
66 | 72 |
|
@@ -528,3 +534,98 @@ def test_process_scheduled_tasks_expect_error(self, mock_scheduled_task): |
528 | 534 | process_scheduled_instructor_tasks() |
529 | 535 |
|
530 | 536 | log.check_present((LOG_PATH, "ERROR", expected_messages[0]),) |
| 537 | + |
| 538 | + |
| 539 | +class SubmitStudentEnrollmentBatchTests(InstructorTaskCourseTestCase): |
| 540 | + """ |
| 541 | + Tests for the submit_student_enrollment_batch API function. |
| 542 | + """ |
| 543 | + |
| 544 | + def setUp(self): |
| 545 | + self.request = HttpRequest() |
| 546 | + self.course_key = CourseKey.from_string("course-v1:edX+DemoX+2025") |
| 547 | + |
| 548 | + @mock.patch("lms.djangoapps.instructor_task.api.submit_task") |
| 549 | + def test_basic_submission(self, mock_submit_task): |
| 550 | + """ |
| 551 | + Basic test with <= 5 identifiers. |
| 552 | + Verifies: task_input, task_type, task_class, task_key. |
| 553 | + """ |
| 554 | + identifiers = ["u1", "u2", "username3@example.com"] |
| 555 | + action = "enroll" |
| 556 | + mock_submit_task.return_value = "task-result" |
| 557 | + expected_input = { |
| 558 | + "action": action, |
| 559 | + "identifiers": identifiers, |
| 560 | + "auto_enroll": True, |
| 561 | + "email_students": False, |
| 562 | + "reason": "test", |
| 563 | + "secure": True, |
| 564 | + } |
| 565 | + |
| 566 | + result = submit_student_enrollment_batch( |
| 567 | + request=self.request, |
| 568 | + course_key=self.course_key, |
| 569 | + action=action, |
| 570 | + identifiers=identifiers, |
| 571 | + auto_enroll=True, |
| 572 | + email_students=False, |
| 573 | + reason="test", |
| 574 | + secure=True, |
| 575 | + ) |
| 576 | + |
| 577 | + self.assertEqual(result, "task-result") |
| 578 | + key_stub = f"{self.course_key}_{action}_{'_'.join(identifiers)}" |
| 579 | + expected_key = hashlib.md5(key_stub.encode("utf-8")).hexdigest() |
| 580 | + mock_submit_task.assert_called_once_with( |
| 581 | + self.request, |
| 582 | + InstructorTaskTypes.STUDENT_ENROLLMENT_BATCH, |
| 583 | + student_enrollment_batch, |
| 584 | + self.course_key, |
| 585 | + expected_input, |
| 586 | + expected_key, |
| 587 | + ) |
| 588 | + |
| 589 | + @mock.patch("lms.djangoapps.instructor_task.api.submit_task") |
| 590 | + def test_identifiers_are_truncated_to_5_for_key(self, mock_submit_task): |
| 591 | + """ |
| 592 | + When identifiers > 5, only first 5 go into the task_key. |
| 593 | + """ |
| 594 | + identifiers = ["u1", "u2", "u3", "u4", "u5", "u6", "u7"] |
| 595 | + |
| 596 | + submit_student_enrollment_batch( |
| 597 | + request=self.request, |
| 598 | + course_key=self.course_key, |
| 599 | + action="unenroll", |
| 600 | + identifiers=identifiers, |
| 601 | + auto_enroll=False, |
| 602 | + email_students=True, |
| 603 | + reason=None, |
| 604 | + secure=False, |
| 605 | + ) |
| 606 | + |
| 607 | + truncated = identifiers[:5] |
| 608 | + key_stub = f"{self.course_key}_unenroll_{'_'.join(truncated)}" |
| 609 | + expected_key = hashlib.md5(key_stub.encode("utf-8")).hexdigest() |
| 610 | + call_args = mock_submit_task.call_args[0] |
| 611 | + received_key = call_args[5] |
| 612 | + self.assertEqual(received_key, expected_key) |
| 613 | + |
| 614 | + @mock.patch("lms.djangoapps.instructor_task.api.submit_task") |
| 615 | + def test_already_running_error_is_propagated(self, mock_submit_task): |
| 616 | + """ |
| 617 | + submit_task may raise AlreadyRunningError; our function should not swallow it. |
| 618 | + """ |
| 619 | + mock_submit_task.side_effect = AlreadyRunningError("Task already running") |
| 620 | + |
| 621 | + with self.assertRaises(AlreadyRunningError): |
| 622 | + submit_student_enrollment_batch( |
| 623 | + request=self.request, |
| 624 | + course_key=self.course_key, |
| 625 | + action="enroll", |
| 626 | + identifiers=["john"], |
| 627 | + auto_enroll=False, |
| 628 | + email_students=False, |
| 629 | + reason=None, |
| 630 | + secure=False, |
| 631 | + ) |
0 commit comments