MDL-55609 mod_assign: Remove shared setUp for all tests
[moodle.git] / mod / assign / tests / lib_test.php
blob042ef542db4d8c3b2d91313bad4d8a728865d927
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Unit tests for (some of) mod/assign/lib.php.
20 * @package mod_assign
21 * @category phpunit
22 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once($CFG->dirroot . '/mod/assign/lib.php');
31 require_once($CFG->dirroot . '/mod/assign/locallib.php');
32 require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
34 use \core_calendar\local\api as calendar_local_api;
35 use \core_calendar\local\event\container as calendar_event_container;
37 /**
38 * Unit tests for (some of) mod/assign/lib.php.
40 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 class mod_assign_lib_testcase extends advanced_testcase {
46 // Use the generator helper.
47 use mod_assign_test_generator;
49 public function test_assign_print_overview() {
50 global $DB;
52 $this->resetAfterTest();
54 $course = $this->getDataGenerator()->create_course();
55 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
56 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
58 $this->setAdminUser();
60 // Assignment with default values.
61 $firstassign = $this->create_instance($course, ['name' => 'First Assignment']);
63 // Assignment with submissions.
64 $secondassign = $this->create_instance($course, [
65 'name' => 'Assignment with submissions',
66 'duedate' => time(),
67 'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
68 'maxattempts' => 3,
69 'submissiondrafts' => 1,
70 'assignsubmission_onlinetext_enabled' => 1,
71 ]);
72 $this->add_submission($student, $secondassign);
73 $this->submit_for_grading($student, $secondassign);
74 $this->mark_submission($teacher, $secondassign, $student, 50.0);
76 // Past assignments should not show up.
77 $pastassign = $this->create_instance($course, [
78 'name' => 'Past Assignment',
79 'duedate' => time() - DAYSECS - 1,
80 'cutoffdate' => time() - DAYSECS,
81 'nosubmissions' => 0,
82 'assignsubmission_onlinetext_enabled' => 1,
83 ]);
85 // Open assignments should show up only if relevant.
86 $openassign = $this->create_instance($course, [
87 'name' => 'Open Assignment',
88 'duedate' => time(),
89 'cutoffdate' => time() + DAYSECS,
90 'nosubmissions' => 0,
91 'assignsubmission_onlinetext_enabled' => 1,
92 ]);
93 $pastsubmission = $pastassign->get_user_submission($student->id, true);
94 $opensubmission = $openassign->get_user_submission($student->id, true);
96 // Check the overview as the different users.
97 // For students , open assignments should show only when there are no valid submissions.
98 $this->setUser($student);
99 $overview = array();
100 $courses = $DB->get_records('course', array('id' => $course->id));
101 assign_print_overview($courses, $overview);
102 $this->assertDebuggingCalledCount(3);
103 $this->assertEquals(1, count($overview));
104 $this->assertRegExp('/.*Open Assignment.*/', $overview[$course->id]['assign']); // No valid submission.
105 $this->assertNotRegExp('/.*First Assignment.*/', $overview[$course->id]['assign']); // Has valid submission.
107 // And now submit the submission.
108 $opensubmission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
109 $openassign->testable_update_submission($opensubmission, $student->id, true, false);
111 $overview = array();
112 assign_print_overview($courses, $overview);
113 $this->assertDebuggingCalledCount(3);
114 $this->assertEquals(0, count($overview));
116 $this->setUser($teacher);
117 $overview = array();
118 assign_print_overview($courses, $overview);
119 $this->assertDebuggingCalledCount(3);
120 $this->assertEquals(1, count($overview));
121 // Submissions without a grade.
122 $this->assertRegExp('/.*Open Assignment.*/', $overview[$course->id]['assign']);
123 $this->assertRegExp('/.*Assignment with submissions.*/', $overview[$course->id]['assign']);
125 $this->setUser($teacher);
126 $overview = array();
127 assign_print_overview($courses, $overview);
128 $this->assertDebuggingCalledCount(3);
129 $this->assertEquals(1, count($overview));
130 // Submissions without a grade.
131 $this->assertRegExp('/.*Open Assignment.*/', $overview[$course->id]['assign']);
132 $this->assertRegExp('/.*Assignment with submissions.*/', $overview[$course->id]['assign']);
134 // Let us grade a submission.
135 $this->setUser($teacher);
136 $data = new stdClass();
137 $data->grade = '50.0';
138 $openassign->testable_apply_grade_to_user($data, $student->id, 0);
140 // The assign_print_overview expects the grade date to be after the submission date.
141 $graderecord = $DB->get_record('assign_grades', array('assignment' => $openassign->get_instance()->id,
142 'userid' => $student->id, 'attemptnumber' => 0));
143 $graderecord->timemodified += 1;
144 $DB->update_record('assign_grades', $graderecord);
146 $overview = array();
147 assign_print_overview($courses, $overview);
148 $this->assertDebuggingCalledCount(3);
149 $this->assertEquals(1, count($overview));
150 // Now assignment 4 should not show up.
151 $this->assertNotRegExp('/.*Open Assignment.*/', $overview[$course->id]['assign']);
152 $this->assertRegExp('/.*Assignment with submissions.*/', $overview[$course->id]['assign']);
154 $this->setUser($teacher);
155 $overview = array();
156 assign_print_overview($courses, $overview);
157 $this->assertDebuggingCalledCount(3);
158 $this->assertEquals(1, count($overview));
159 // Now assignment 4 should not show up.
160 $this->assertNotRegExp('/.*Open Assignment.*/', $overview[$course->id]['assign']);
161 $this->assertRegExp('/.*Assignment with submissions.*/', $overview[$course->id]['assign']);
165 * Test that assign_print_overview does not return any assignments which are Open Offline.
167 public function test_assign_print_overview_open_offline() {
168 $this->resetAfterTest();
169 $course = $this->getDataGenerator()->create_course();
170 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
172 $this->setAdminUser();
173 $openassign = $this->create_instance($course, [
174 'duedate' => time() + DAYSECS,
175 'cutoffdate' => time() + (DAYSECS * 2),
178 $this->setUser($student);
179 $overview = [];
180 assign_print_overview([$course], $overview);
182 $this->assertDebuggingCalledCount(1);
183 $this->assertEquals(0, count($overview));
187 * Test that assign_print_recent_activity shows ungraded submitted assignments.
189 public function test_print_recent_activity() {
190 $this->resetAfterTest();
191 $course = $this->getDataGenerator()->create_course();
192 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
193 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
194 $assign = $this->create_instance($course);
195 $this->submit_for_grading($student, $assign);
197 $this->setUser($teacher);
198 $this->expectOutputRegex('/submitted:/');
199 assign_print_recent_activity($course, true, time() - 3600);
203 * Test that assign_print_recent_activity does not display any warnings when a custom fullname has been configured.
205 public function test_print_recent_activity_fullname() {
206 $this->resetAfterTest();
207 $course = $this->getDataGenerator()->create_course();
208 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
209 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
210 $assign = $this->create_instance($course);
211 $this->submit_for_grading($student, $assign);
213 $this->setUser($teacher);
214 $this->expectOutputRegex('/submitted:/');
215 set_config('fullnamedisplay', 'firstname, lastnamephonetic');
216 assign_print_recent_activity($course, false, time() - 3600);
220 * Test that assign_print_recent_activity shows the blind marking ID.
222 public function test_print_recent_activity_fullname_blind_marking() {
223 $this->resetAfterTest();
224 $course = $this->getDataGenerator()->create_course();
225 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
226 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
228 $assign = $this->create_instance($course, [
229 'blindmarking' => 1,
231 $this->add_submission($student, $assign);
232 $this->submit_for_grading($student, $assign);
234 $this->setUser($teacher);
235 $uniqueid = $assign->get_uniqueid_for_user($student->id);
236 $expectedstr = preg_quote(get_string('participant', 'mod_assign'), '/') . '.*' . $uniqueid;
237 $this->expectOutputRegex("/{$expectedstr}/");
238 assign_print_recent_activity($course, false, time() - 3600);
242 * Test that assign_get_recent_mod_activity fetches the assignment correctly.
244 public function test_assign_get_recent_mod_activity() {
245 $this->resetAfterTest();
246 $course = $this->getDataGenerator()->create_course();
247 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
248 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
249 $assign = $this->create_instance($course);
250 $this->add_submission($student, $assign);
251 $this->submit_for_grading($student, $assign);
253 $index = 1;
254 $activities = [
255 $index => (object) [
256 'type' => 'assign',
257 'cmid' => $assign->get_course_module()->id,
261 $this->setUser($teacher);
262 assign_get_recent_mod_activity($activities, $index, time() - HOURSECS, $course->id, $assign->get_course_module()->id);
264 $activity = $activities[1];
265 $this->assertEquals("assign", $activity->type);
266 $this->assertEquals($student->id, $activity->user->id);
270 * Ensure that assign_user_complete displays information about drafts.
272 public function test_assign_user_complete() {
273 global $PAGE, $DB;
275 $this->resetAfterTest();
276 $course = $this->getDataGenerator()->create_course();
277 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
278 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
279 $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
280 $this->add_submission($student, $assign);
282 $PAGE->set_url(new moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
284 $submission = $assign->get_user_submission($student->id, true);
285 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
286 $DB->update_record('assign_submission', $submission);
288 $this->expectOutputRegex('/Draft/');
289 assign_user_complete($course, $student, $assign->get_course_module(), $assign->get_instance());
293 * Ensure that assign_user_outline fetches updated grades.
295 public function test_assign_user_outline() {
296 $this->resetAfterTest();
297 $course = $this->getDataGenerator()->create_course();
298 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
299 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
300 $assign = $this->create_instance($course);
302 $this->add_submission($student, $assign);
303 $this->submit_for_grading($student, $assign);
304 $this->mark_submission($teacher, $assign, $student, 50.0);
306 $this->setUser($teacher);
307 $data = $assign->get_user_grade($student->id, true);
308 $data->grade = '50.5';
309 $assign->update_grade($data);
311 $result = assign_user_outline($course, $student, $assign->get_course_module(), $assign->get_instance());
313 $this->assertRegExp('/50.5/', $result->info);
317 * Ensure that assign_get_completion_state reflects the correct status at each point.
319 public function test_assign_get_completion_state() {
320 global $DB;
322 $this->resetAfterTest();
323 $course = $this->getDataGenerator()->create_course();
324 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
325 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
326 $assign = $this->create_instance($course, [
327 'submissiondrafts' => 0,
328 'completionsubmit' => 1
331 $this->setUser($student);
332 $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
333 $this->assertFalse($result);
335 $this->add_submission($student, $assign);
336 $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
337 $this->assertFalse($result);
339 $this->submit_for_grading($student, $assign);
340 $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
341 $this->assertTrue($result);
343 $this->mark_submission($teacher, $assign, $student, 50.0);
344 $result = assign_get_completion_state($course, $assign->get_course_module(), $student->id, false);
345 $this->assertTrue($result);
349 * Tests for mod_assign_refresh_events.
351 public function test_assign_refresh_events() {
352 global $DB;
354 $this->resetAfterTest();
356 $duedate = time();
357 $newduedate = $duedate + DAYSECS;
359 $this->setAdminUser();
361 $course = $this->getDataGenerator()->create_course();
362 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
363 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
364 $assign = $this->create_instance($course, [
365 'duedate' => $duedate,
368 $instance = $assign->get_instance();
369 $eventparams = ['modulename' => 'assign', 'instance' => $instance->id];
371 // Make sure the calendar event for assignment 1 matches the initial due date.
372 $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
373 $this->assertEquals($eventtime, $duedate);
375 // Manually update assignment 1's due date.
376 $DB->update_record('assign', (object) ['id' => $instance->id, 'duedate' => $newduedate]);
378 // Then refresh the assignment events of assignment 1's course.
379 $this->assertTrue(assign_refresh_events($course->id));
381 // Confirm that the assignment 1's due date event now has the new due date after refresh.
382 $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
383 $this->assertEquals($eventtime, $newduedate);
385 // Create a second course and assignment.
386 $othercourse = $this->getDataGenerator()->create_course();;
387 $otherassign = $this->create_instance($othercourse, ['duedate' => $duedate, 'course' => $othercourse->id]);
388 $otherinstance = $otherassign->get_instance();
390 // Manually update assignment 1 and 2's due dates.
391 $newduedate += DAYSECS;
392 $DB->update_record('assign', (object)['id' => $instance->id, 'duedate' => $newduedate]);
393 $DB->update_record('assign', (object)['id' => $otherinstance->id, 'duedate' => $newduedate]);
395 // Refresh events of all courses.
396 $this->assertTrue(assign_refresh_events());
398 // Check the due date calendar event for assignment 1.
399 $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
400 $this->assertEquals($eventtime, $newduedate);
402 // Check the due date calendar event for assignment 2.
403 $eventparams['instance'] = $otherinstance->id;
404 $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
405 $this->assertEquals($eventtime, $newduedate);
407 // In case the course ID is passed as a numeric string.
408 $this->assertTrue(assign_refresh_events('' . $course->id));
410 // Non-existing course ID.
411 $this->assertFalse(assign_refresh_events(-1));
413 // Invalid course ID.
414 $this->assertFalse(assign_refresh_events('aaa'));
417 public function test_assign_core_calendar_is_event_visible_duedate_event_as_teacher() {
418 $this->resetAfterTest();
419 $course = $this->getDataGenerator()->create_course();
420 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
421 $assign = $this->create_instance($course);
423 $this->setAdminUser();
425 // Create a calendar event.
426 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
428 // The teacher should see the due date event.
429 $this->setUser($teacher);
430 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
433 public function test_assign_core_calendar_is_event_visible_duedate_event_as_student() {
434 $this->resetAfterTest();
435 $course = $this->getDataGenerator()->create_course();
436 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
437 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
438 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
440 $this->setAdminUser();
442 // Create a calendar event.
443 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
445 // The student should care about the due date event.
446 $this->setUser($student);
447 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
450 public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_teacher() {
451 $this->resetAfterTest();
452 $course = $this->getDataGenerator()->create_course();
453 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
454 $assign = $this->create_instance($course);
456 // Create a calendar event.
457 $this->setAdminUser();
458 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
460 // The teacher should see the due date event.
461 $this->setUser($teacher);
462 $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
465 public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_student() {
466 $this->resetAfterTest();
467 $course = $this->getDataGenerator()->create_course();
468 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
469 $assign = $this->create_instance($course);
471 // Create a calendar event.
472 $this->setAdminUser();
473 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
475 // The student should not see the due date event.
476 $this->setUser($student);
477 $this->assertFalse(mod_assign_core_calendar_is_event_visible($event));
480 public function test_assign_core_calendar_provide_event_action_duedate_as_teacher() {
481 $this->resetAfterTest();
482 $course = $this->getDataGenerator()->create_course();
483 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
484 $assign = $this->create_instance($course);
486 // Create a calendar event.
487 $this->setAdminUser();
488 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
490 // The teacher should see the event.
491 $this->setUser($teacher);
492 $factory = new \core_calendar\action_factory();
493 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
495 // The teacher should not have an action for a due date event.
496 $this->assertNull($actionevent);
499 public function test_assign_core_calendar_provide_event_action_duedate_as_student() {
500 $this->resetAfterTest();
501 $course = $this->getDataGenerator()->create_course();
502 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
503 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
505 // Create a calendar event.
506 $this->setAdminUser();
507 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
509 // The student should see the event.
510 $this->setUser($student);
511 $factory = new \core_calendar\action_factory();
512 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
514 // Confirm the event was decorated.
515 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
516 $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent->get_name());
517 $this->assertInstanceOf('moodle_url', $actionevent->get_url());
518 $this->assertEquals(1, $actionevent->get_item_count());
519 $this->assertTrue($actionevent->is_actionable());
522 public function test_assign_core_calendar_provide_event_action_gradingduedate_as_teacher() {
523 $this->resetAfterTest();
524 $course = $this->getDataGenerator()->create_course();
525 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
526 $assign = $this->create_instance($course);
528 // Create a calendar event.
529 $this->setAdminUser();
530 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
532 $this->setUser($teacher);
533 $factory = new \core_calendar\action_factory();
534 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
536 // Confirm the event was decorated.
537 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
538 $this->assertEquals(get_string('grade'), $actionevent->get_name());
539 $this->assertInstanceOf('moodle_url', $actionevent->get_url());
540 $this->assertEquals(0, $actionevent->get_item_count());
541 $this->assertTrue($actionevent->is_actionable());
544 public function test_assign_core_calendar_provide_event_action_gradingduedate_as_student() {
545 $this->resetAfterTest();
546 $course = $this->getDataGenerator()->create_course();
547 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
548 $assign = $this->create_instance($course);
550 // Create a calendar event.
551 $this->setAdminUser();
552 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
554 $this->setUser($student);
555 $factory = new \core_calendar\action_factory();
556 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
558 // Confirm the event was decorated.
559 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
560 $this->assertEquals(get_string('grade'), $actionevent->get_name());
561 $this->assertInstanceOf('moodle_url', $actionevent->get_url());
562 $this->assertEquals(0, $actionevent->get_item_count());
563 $this->assertFalse($actionevent->is_actionable());
566 public function test_assign_core_calendar_provide_event_action_duedate_as_student_submitted() {
567 $this->resetAfterTest();
568 $course = $this->getDataGenerator()->create_course();
569 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
570 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
571 $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
573 $this->setAdminUser();
575 // Create a calendar event.
576 $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
578 // Create an action factory.
579 $factory = new \core_calendar\action_factory();
581 // Submit as the student.
582 $this->add_submission($student, $assign);
583 $this->submit_for_grading($student, $assign);
585 // Confirm there was no event to action.
586 $factory = new \core_calendar\action_factory();
587 $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
588 $this->assertNull($actionevent);
592 * Creates an action event.
594 * @param \stdClass $course The course the assignment is in
595 * @param assign $assign The assignment to create an event for
596 * @param string $eventtype The event type. eg. ASSIGN_EVENT_TYPE_DUE.
597 * @return bool|calendar_event
599 private function create_action_event($course, $assign, $eventtype) {
600 $event = new stdClass();
601 $event->name = 'Calendar event';
602 $event->modulename = 'assign';
603 $event->courseid = $course->id;
604 $event->instance = $assign->get_instance()->id;
605 $event->type = CALENDAR_EVENT_TYPE_ACTION;
606 $event->eventtype = $eventtype;
607 $event->timestart = time();
609 return calendar_event::create($event);
613 * Test the callback responsible for returning the completion rule descriptions.
614 * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
615 * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
617 public function test_mod_assign_completion_get_active_rule_descriptions() {
618 $this->resetAfterTest();
619 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
621 $this->setAdminUser();
623 // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
624 $cm1 = $this->create_instance($course, ['completion' => '2', 'completionsubmit' => '1'])->get_course_module();
625 $cm2 = $this->create_instance($course, ['completion' => '2', 'completionsubmit' => '0'])->get_course_module();
627 // Data for the stdClass input type.
628 // This type of input would occur when checking the default completion rules for an activity type, where we don't have
629 // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
630 $moddefaults = (object) [
631 'customdata' => [
632 'customcompletionrules' => [
633 'completionsubmit' => '1',
636 'completion' => 2,
639 $activeruledescriptions = [get_string('completionsubmit', 'assign')];
640 $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
641 $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($cm2), []);
642 $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
643 $this->assertEquals(mod_assign_get_completion_active_rule_descriptions(new stdClass()), []);
647 * Test that if some grades are not set, they are left alone and not rescaled
649 public function test_assign_rescale_activity_grades_some_unset() {
650 $this->resetAfterTest();
651 $course = $this->getDataGenerator()->create_course();
652 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
653 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
654 $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
656 // As a teacher.
657 $this->setUser($teacher);
658 $assign = $this->create_instance($course);
660 // Grade the student.
661 $data = ['grade' => 50];
662 $assign->testable_apply_grade_to_user((object)$data, $student->id, 0);
664 // Try getting another students grade. This will give a grade of ASSIGN_GRADE_NOT_SET (-1).
665 $assign->get_user_grade($otherstudent->id, true);
667 // Rescale.
668 assign_rescale_activity_grades($course, $assign->get_course_module(), 0, 100, 0, 10);
670 // Get the grades for both students.
671 $studentgrade = $assign->get_user_grade($student->id, true);
672 $otherstudentgrade = $assign->get_user_grade($otherstudent->id, true);
674 // Make sure the real grade is scaled, but the ASSIGN_GRADE_NOT_SET stays the same.
675 $this->assertEquals($studentgrade->grade, 5);
676 $this->assertEquals($otherstudentgrade->grade, ASSIGN_GRADE_NOT_SET);
680 * Return false when there are not overrides for this assign instance.
682 public function test_assign_is_override_calendar_event_no_override() {
683 global $CFG, $DB;
684 require_once($CFG->dirroot . '/calendar/lib.php');
686 $this->resetAfterTest();
687 $course = $this->getDataGenerator()->create_course();
688 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
690 $this->setAdminUser();
692 $duedate = time();
693 $assign = $this->create_instance($course, ['duedate' => $duedate]);
695 $instance = $assign->get_instance();
696 $event = new \calendar_event((object)[
697 'modulename' => 'assign',
698 'instance' => $instance->id,
699 'userid' => $student->id,
702 $this->assertFalse($assign->is_override_calendar_event($event));
706 * Return false if the given event isn't an assign module event.
708 public function test_assign_is_override_calendar_event_no_nodule_event() {
709 global $CFG, $DB;
710 require_once($CFG->dirroot . '/calendar/lib.php');
712 $this->resetAfterTest();
713 $course = $this->getDataGenerator()->create_course();
714 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
716 $this->setAdminUser();
718 $userid = $student->id;
719 $duedate = time();
720 $assign = $this->create_instance($course, ['duedate' => $duedate]);
722 $instance = $assign->get_instance();
723 $event = new \calendar_event((object)[
724 'userid' => $userid
727 $this->assertFalse($assign->is_override_calendar_event($event));
731 * Return false if there is overrides for this use but they belong to another assign
732 * instance.
734 public function test_assign_is_override_calendar_event_different_assign_instance() {
735 global $CFG, $DB;
736 require_once($CFG->dirroot . '/calendar/lib.php');
738 $this->resetAfterTest();
739 $course = $this->getDataGenerator()->create_course();
740 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
742 $this->setAdminUser();
744 $duedate = time();
745 $assign = $this->create_instance($course, ['duedate' => $duedate]);
746 $instance = $assign->get_instance();
748 $otherassign = $this->create_instance($course, ['duedate' => $duedate]);
749 $otherinstance = $otherassign->get_instance();
751 $event = new \calendar_event((object) [
752 'modulename' => 'assign',
753 'instance' => $instance->id,
754 'userid' => $student->id,
757 $DB->insert_record('assign_overrides', (object) [
758 'assignid' => $otherinstance->id,
759 'userid' => $student->id,
762 $this->assertFalse($assign->is_override_calendar_event($event));
766 * Return true if there is a user override for this event and assign instance.
768 public function test_assign_is_override_calendar_event_user_override() {
769 global $CFG, $DB;
770 require_once($CFG->dirroot . '/calendar/lib.php');
772 $this->resetAfterTest();
773 $course = $this->getDataGenerator()->create_course();
774 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
776 $this->setAdminUser();
778 $duedate = time();
779 $assign = $this->create_instance($course, ['duedate' => $duedate]);
781 $instance = $assign->get_instance();
782 $event = new \calendar_event((object) [
783 'modulename' => 'assign',
784 'instance' => $instance->id,
785 'userid' => $student->id,
789 $DB->insert_record('assign_overrides', (object) [
790 'assignid' => $instance->id,
791 'userid' => $student->id,
794 $this->assertTrue($assign->is_override_calendar_event($event));
798 * Return true if there is a group override for the event and assign instance.
800 public function test_assign_is_override_calendar_event_group_override() {
801 global $CFG, $DB;
802 require_once($CFG->dirroot . '/calendar/lib.php');
804 $this->resetAfterTest();
805 $course = $this->getDataGenerator()->create_course();
807 $this->setAdminUser();
809 $duedate = time();
810 $assign = $this->create_instance($course, ['duedate' => $duedate]);
811 $instance = $assign->get_instance();
812 $group = $this->getDataGenerator()->create_group(array('courseid' => $instance->course));
814 $event = new \calendar_event((object) [
815 'modulename' => 'assign',
816 'instance' => $instance->id,
817 'groupid' => $group->id,
820 $DB->insert_record('assign_overrides', (object) [
821 'assignid' => $instance->id,
822 'groupid' => $group->id,
825 $this->assertTrue($assign->is_override_calendar_event($event));
829 * Unknown event types should not have any limit restrictions returned.
831 public function test_mod_assign_core_calendar_get_valid_event_timestart_range_unkown_event_type() {
832 global $CFG;
833 require_once($CFG->dirroot . '/calendar/lib.php');
835 $this->resetAfterTest();
836 $course = $this->getDataGenerator()->create_course();
838 $this->setAdminUser();
840 $duedate = time();
841 $assign = $this->create_instance($course, ['duedate' => $duedate]);
842 $instance = $assign->get_instance();
844 $event = new \calendar_event((object) [
845 'courseid' => $instance->course,
846 'modulename' => 'assign',
847 'instance' => $instance->id,
848 'eventtype' => 'SOME RANDOM EVENT'
851 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
852 $this->assertNull($min);
853 $this->assertNull($max);
857 * Override events should not have any limit restrictions returned.
859 public function test_mod_assign_core_calendar_get_valid_event_timestart_range_override_event() {
860 global $CFG, $DB;
861 require_once($CFG->dirroot . '/calendar/lib.php');
863 $this->resetAfterTest();
864 $course = $this->getDataGenerator()->create_course();
865 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
867 $this->setAdminUser();
869 $duedate = time();
870 $assign = $this->create_instance($course, ['duedate' => $duedate]);
871 $instance = $assign->get_instance();
873 $event = new \calendar_event((object) [
874 'courseid' => $instance->course,
875 'modulename' => 'assign',
876 'instance' => $instance->id,
877 'userid' => $student->id,
878 'eventtype' => ASSIGN_EVENT_TYPE_DUE
881 $record = (object) [
882 'assignid' => $instance->id,
883 'userid' => $student->id,
886 $DB->insert_record('assign_overrides', $record);
888 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
889 $this->assertFalse($min);
890 $this->assertFalse($max);
894 * Assignments configured without a submissions from and cutoff date should not have
895 * any limits applied.
897 public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_no_limit() {
898 global $CFG, $DB;
899 require_once($CFG->dirroot . '/calendar/lib.php');
901 $this->resetAfterTest();
902 $course = $this->getDataGenerator()->create_course();
904 $this->setAdminUser();
906 $duedate = time();
907 $assign = $this->create_instance($course, [
908 'duedate' => $duedate,
909 'allowsubmissionsfromdate' => 0,
910 'cutoffdate' => 0,
912 $instance = $assign->get_instance();
914 $event = new \calendar_event((object) [
915 'courseid' => $instance->course,
916 'modulename' => 'assign',
917 'instance' => $instance->id,
918 'eventtype' => ASSIGN_EVENT_TYPE_DUE
921 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
922 $this->assertNull($min);
923 $this->assertNull($max);
927 * Assignments should be bottom and top bound by the submissions from date and cutoff date
928 * respectively.
930 public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_with_limits() {
931 global $CFG, $DB;
932 require_once($CFG->dirroot . '/calendar/lib.php');
934 $this->resetAfterTest();
935 $course = $this->getDataGenerator()->create_course();
937 $this->setAdminUser();
939 $duedate = time();
940 $submissionsfromdate = $duedate - DAYSECS;
941 $cutoffdate = $duedate + DAYSECS;
942 $assign = $this->create_instance($course, [
943 'duedate' => $duedate,
944 'allowsubmissionsfromdate' => $submissionsfromdate,
945 'cutoffdate' => $cutoffdate,
947 $instance = $assign->get_instance();
949 $event = new \calendar_event((object) [
950 'courseid' => $instance->course,
951 'modulename' => 'assign',
952 'instance' => $instance->id,
953 'eventtype' => ASSIGN_EVENT_TYPE_DUE
956 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
957 $this->assertEquals($submissionsfromdate, $min[0]);
958 $this->assertNotEmpty($min[1]);
959 $this->assertEquals($cutoffdate, $max[0]);
960 $this->assertNotEmpty($max[1]);
964 * Assignment grading due date should not have any limits of no due date and cutoff date is set.
966 public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_no_limit() {
967 global $CFG, $DB;
968 require_once($CFG->dirroot . '/calendar/lib.php');
970 $this->resetAfterTest();
971 $course = $this->getDataGenerator()->create_course();
973 $this->setAdminUser();
975 $assign = $this->create_instance($course, [
976 'duedate' => 0,
977 'allowsubmissionsfromdate' => 0,
978 'cutoffdate' => 0,
980 $instance = $assign->get_instance();
982 $event = new \calendar_event((object) [
983 'courseid' => $instance->course,
984 'modulename' => 'assign',
985 'instance' => $instance->id,
986 'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
989 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
990 $this->assertNull($min);
991 $this->assertNull($max);
995 * Assignment grading due event is minimum bound by the due date, if it is set.
997 public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_with_due_date() {
998 global $CFG, $DB;
999 require_once($CFG->dirroot . '/calendar/lib.php');
1001 $this->resetAfterTest();
1002 $course = $this->getDataGenerator()->create_course();
1004 $this->setAdminUser();
1006 $duedate = time();
1007 $assign = $this->create_instance($course, ['duedate' => $duedate]);
1008 $instance = $assign->get_instance();
1010 $event = new \calendar_event((object) [
1011 'courseid' => $instance->course,
1012 'modulename' => 'assign',
1013 'instance' => $instance->id,
1014 'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1017 list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1018 $this->assertEquals($duedate, $min[0]);
1019 $this->assertNotEmpty($min[1]);
1020 $this->assertNull($max);
1024 * Non due date events should not update the assignment due date.
1026 public function test_mod_assign_core_calendar_event_timestart_updated_non_due_event() {
1027 global $CFG, $DB;
1028 require_once($CFG->dirroot . '/calendar/lib.php');
1030 $this->resetAfterTest();
1031 $course = $this->getDataGenerator()->create_course();
1032 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1034 $this->setAdminUser();
1036 $duedate = time();
1037 $submissionsfromdate = $duedate - DAYSECS;
1038 $cutoffdate = $duedate + DAYSECS;
1039 $assign = $this->create_instance($course, [
1040 'duedate' => $duedate,
1041 'allowsubmissionsfromdate' => $submissionsfromdate,
1042 'cutoffdate' => $cutoffdate,
1044 $instance = $assign->get_instance();
1046 $event = new \calendar_event((object) [
1047 'courseid' => $instance->course,
1048 'modulename' => 'assign',
1049 'instance' => $instance->id,
1050 'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE,
1051 'timestart' => $duedate + 1
1054 mod_assign_core_calendar_event_timestart_updated($event, $instance);
1056 $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1057 $this->assertEquals($duedate, $newinstance->duedate);
1061 * Due date override events should not change the assignment due date.
1063 public function test_mod_assign_core_calendar_event_timestart_updated_due_event_override() {
1064 global $CFG, $DB;
1065 require_once($CFG->dirroot . '/calendar/lib.php');
1067 $this->resetAfterTest();
1068 $course = $this->getDataGenerator()->create_course();
1069 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1071 $this->setAdminUser();
1073 $duedate = time();
1074 $submissionsfromdate = $duedate - DAYSECS;
1075 $cutoffdate = $duedate + DAYSECS;
1076 $assign = $this->create_instance($course, [
1077 'duedate' => $duedate,
1078 'allowsubmissionsfromdate' => $submissionsfromdate,
1079 'cutoffdate' => $cutoffdate,
1081 $instance = $assign->get_instance();
1083 $event = new \calendar_event((object) [
1084 'courseid' => $instance->course,
1085 'modulename' => 'assign',
1086 'instance' => $instance->id,
1087 'userid' => $student->id,
1088 'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1089 'timestart' => $duedate + 1
1092 $record = (object) [
1093 'assignid' => $instance->id,
1094 'userid' => $student->id,
1095 'duedate' => $duedate + 1,
1098 $DB->insert_record('assign_overrides', $record);
1100 mod_assign_core_calendar_event_timestart_updated($event, $instance);
1102 $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1103 $this->assertEquals($duedate, $newinstance->duedate);
1107 * Due date events should update the assignment due date.
1109 public function test_mod_assign_core_calendar_event_timestart_updated_due_event() {
1110 global $CFG, $DB;
1111 require_once($CFG->dirroot . '/calendar/lib.php');
1113 $this->resetAfterTest();
1114 $course = $this->getDataGenerator()->create_course();
1115 $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1117 $this->setAdminUser();
1119 $duedate = time();
1120 $newduedate = $duedate + 1;
1121 $submissionsfromdate = $duedate - DAYSECS;
1122 $cutoffdate = $duedate + DAYSECS;
1123 $assign = $this->create_instance($course, [
1124 'duedate' => $duedate,
1125 'allowsubmissionsfromdate' => $submissionsfromdate,
1126 'cutoffdate' => $cutoffdate,
1128 $instance = $assign->get_instance();
1130 $event = new \calendar_event((object) [
1131 'courseid' => $instance->course,
1132 'modulename' => 'assign',
1133 'instance' => $instance->id,
1134 'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1135 'timestart' => $newduedate
1138 mod_assign_core_calendar_event_timestart_updated($event, $instance);
1140 $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1141 $this->assertEquals($newduedate, $newinstance->duedate);
1145 * If a student somehow finds a way to update the due date calendar event
1146 * then the callback should not be executed to update the assignment due
1147 * date as well otherwise that would be a security issue.
1149 public function test_student_role_cant_update_due_event() {
1150 global $CFG, $DB;
1151 require_once($CFG->dirroot . '/calendar/lib.php');
1153 $this->resetAfterTest();
1154 $course = $this->getDataGenerator()->create_course();
1155 $context = context_course::instance($course->id);
1157 $roleid = $this->getDataGenerator()->create_role();
1158 $role = $DB->get_record('role', ['id' => $roleid]);
1159 $user = $this->getDataGenerator()->create_and_enrol($course, $role->shortname);
1161 $this->setAdminUser();
1163 $mapper = calendar_event_container::get_event_mapper();
1164 $now = time();
1165 $duedate = (new DateTime())->setTimestamp($now);
1166 $newduedate = (new DateTime())->setTimestamp($now)->modify('+1 day');
1167 $assign = $this->create_instance($course, [
1168 'course' => $course->id,
1169 'duedate' => $duedate->getTimestamp(),
1171 $instance = $assign->get_instance();
1173 $record = $DB->get_record('event', [
1174 'courseid' => $course->id,
1175 'modulename' => 'assign',
1176 'instance' => $instance->id,
1177 'eventtype' => ASSIGN_EVENT_TYPE_DUE
1180 $event = new \calendar_event($record);
1182 assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1183 assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true);
1185 $this->setUser($user);
1187 calendar_local_api::update_event_start_day(
1188 $mapper->from_legacy_event_to_event($event),
1189 $newduedate
1192 $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1193 $newevent = \calendar_event::load($event->id);
1194 // The due date shouldn't have changed even though we updated the calendar
1195 // event.
1196 $this->assertEquals($duedate->getTimestamp(), $newinstance->duedate);
1197 $this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
1201 * A teacher with the capability to modify an assignment module should be
1202 * able to update the assignment due date by changing the due date calendar
1203 * event.
1205 public function test_teacher_role_can_update_due_event() {
1206 global $CFG, $DB;
1207 require_once($CFG->dirroot . '/calendar/lib.php');
1209 $this->resetAfterTest();
1210 $course = $this->getDataGenerator()->create_course();
1211 $context = context_course::instance($course->id);
1212 $user = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1213 $roleid = $DB->get_field('role', 'id', ['shortname' => 'teacher']);
1215 $this->setAdminUser();
1217 $mapper = calendar_event_container::get_event_mapper();
1218 $now = time();
1219 $duedate = (new DateTime())->setTimestamp($now);
1220 $newduedate = (new DateTime())->setTimestamp($now)->modify('+1 day');
1221 $assign = $this->create_instance($course, [
1222 'course' => $course->id,
1223 'duedate' => $duedate->getTimestamp(),
1225 $instance = $assign->get_instance();
1227 $record = $DB->get_record('event', [
1228 'courseid' => $course->id,
1229 'modulename' => 'assign',
1230 'instance' => $instance->id,
1231 'eventtype' => ASSIGN_EVENT_TYPE_DUE
1234 $event = new \calendar_event($record);
1236 assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1237 assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true);
1239 $this->setUser($user);
1240 // Trigger and capture the event when adding a contact.
1241 $sink = $this->redirectEvents();
1243 calendar_local_api::update_event_start_day(
1244 $mapper->from_legacy_event_to_event($event),
1245 $newduedate
1248 $triggeredevents = $sink->get_events();
1249 $moduleupdatedevents = array_filter($triggeredevents, function($e) {
1250 return is_a($e, 'core\event\course_module_updated');
1253 $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1254 $newevent = \calendar_event::load($event->id);
1255 // The due date shouldn't have changed even though we updated the calendar
1256 // event.
1257 $this->assertEquals($newduedate->getTimestamp(), $newinstance->duedate);
1258 $this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
1259 // Confirm that a module updated event is fired when the module
1260 // is changed.
1261 $this->assertNotEmpty($moduleupdatedevents);