Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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.
13
//
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/>.
16
 
17
/**
18
 * Unit tests for (some of) mod/assign/lib.php.
19
 *
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
24
 */
25
namespace mod_assign;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
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');
33
 
34
use core_calendar\local\api as calendar_local_api;
35
use core_calendar\local\event\container as calendar_event_container;
36
use mod_assign_test_generator;
37
 
38
/**
39
 * Unit tests for (some of) mod/assign/lib.php.
40
 *
41
 * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
42
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43
 */
44
class lib_test extends \advanced_testcase {
45
 
46
    // Use the generator helper.
47
    use mod_assign_test_generator;
48
 
49
    /**
50
     * Test that assign_print_recent_activity shows ungraded submitted assignments.
51
     */
11 efrain 52
    public function test_print_recent_activity(): void {
1 efrain 53
        $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');
57
        $assign = $this->create_instance($course);
58
        $this->submit_for_grading($student, $assign);
59
 
60
        $this->setUser($teacher);
61
        $this->expectOutputRegex('/submitted:/');
62
        assign_print_recent_activity($course, true, time() - 3600);
63
    }
64
 
65
    /**
66
     * Test that assign_print_recent_activity does not display any warnings when a custom fullname has been configured.
67
     */
11 efrain 68
    public function test_print_recent_activity_fullname(): void {
1 efrain 69
        $this->resetAfterTest();
70
        $course = $this->getDataGenerator()->create_course();
71
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
72
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
73
        $assign = $this->create_instance($course);
74
        $this->submit_for_grading($student, $assign);
75
 
76
        $this->setUser($teacher);
77
        $this->expectOutputRegex('/submitted:/');
78
        set_config('fullnamedisplay', 'firstname, lastnamephonetic');
79
        assign_print_recent_activity($course, false, time() - 3600);
80
    }
81
 
82
    /**
83
     * Test that assign_print_recent_activity shows the blind marking ID.
84
     */
11 efrain 85
    public function test_print_recent_activity_fullname_blind_marking(): void {
1 efrain 86
        $this->resetAfterTest();
87
        $course = $this->getDataGenerator()->create_course();
88
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
89
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
90
 
91
        $assign = $this->create_instance($course, [
92
                'blindmarking' => 1,
93
            ]);
94
        $this->add_submission($student, $assign);
95
        $this->submit_for_grading($student, $assign);
96
 
97
        $this->setUser($teacher);
98
        $uniqueid = $assign->get_uniqueid_for_user($student->id);
99
        $expectedstr = preg_quote(get_string('participant', 'mod_assign'), '/') . '.*' . $uniqueid;
100
        $this->expectOutputRegex("/{$expectedstr}/");
101
        assign_print_recent_activity($course, false, time() - 3600);
102
    }
103
 
104
    /**
105
     * Test that assign_get_recent_mod_activity fetches the assignment correctly.
106
     */
11 efrain 107
    public function test_assign_get_recent_mod_activity(): void {
1 efrain 108
        $this->resetAfterTest();
109
        $course = $this->getDataGenerator()->create_course();
110
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
111
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
112
        $assign = $this->create_instance($course);
113
        $this->add_submission($student, $assign);
114
        $this->submit_for_grading($student, $assign);
115
 
116
        $index = 1;
117
        $activities = [
118
            $index => (object) [
119
                'type' => 'assign',
120
                'cmid' => $assign->get_course_module()->id,
121
            ],
122
        ];
123
 
124
        $this->setUser($teacher);
125
        assign_get_recent_mod_activity($activities, $index, time() - HOURSECS, $course->id, $assign->get_course_module()->id);
126
 
127
        $activity = $activities[1];
128
        $this->assertEquals("assign", $activity->type);
129
        $this->assertEquals($student->id, $activity->user->id);
130
    }
131
 
132
    /**
133
     * Ensure that assign_user_complete displays information about drafts.
134
     */
11 efrain 135
    public function test_assign_user_complete(): void {
1 efrain 136
        global $PAGE, $DB;
137
 
138
        $this->resetAfterTest();
139
        $course = $this->getDataGenerator()->create_course();
140
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
141
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
142
        $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
143
        $this->add_submission($student, $assign);
144
 
145
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array('id' => $assign->get_course_module()->id)));
146
 
147
        $submission = $assign->get_user_submission($student->id, true);
148
        $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
149
        $DB->update_record('assign_submission', $submission);
150
 
151
        $this->expectOutputRegex('/Draft/');
152
        assign_user_complete($course, $student, $assign->get_course_module(), $assign->get_instance());
153
    }
154
 
155
    /**
156
     * Ensure that assign_user_outline fetches updated grades.
157
     */
11 efrain 158
    public function test_assign_user_outline(): void {
1 efrain 159
        $this->resetAfterTest();
160
        $course = $this->getDataGenerator()->create_course();
161
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
162
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
163
        $assign = $this->create_instance($course);
164
 
165
        $this->add_submission($student, $assign);
166
        $this->submit_for_grading($student, $assign);
167
        $this->mark_submission($teacher, $assign, $student, 50.0);
168
 
169
        $this->setUser($teacher);
170
        $data = $assign->get_user_grade($student->id, true);
171
        $data->grade = '50.5';
172
        $assign->update_grade($data);
173
 
174
        $result = assign_user_outline($course, $student, $assign->get_course_module(), $assign->get_instance());
175
 
176
        $this->assertMatchesRegularExpression('/50.5/', $result->info);
177
    }
178
 
179
    /**
180
     * Tests for mod_assign_refresh_events.
181
     */
11 efrain 182
    public function test_assign_refresh_events(): void {
1 efrain 183
        global $DB;
184
 
185
        $this->resetAfterTest();
186
 
187
        $duedate = time();
188
        $newduedate = $duedate + DAYSECS;
189
 
190
        $this->setAdminUser();
191
 
192
        $course = $this->getDataGenerator()->create_course();
193
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
194
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
195
        $assign = $this->create_instance($course, [
196
                'duedate' => $duedate,
197
            ]);
198
 
199
        $instance = $assign->get_instance();
200
        $eventparams = [
201
            'modulename' => 'assign',
202
            'instance' => $instance->id,
203
            'eventtype' => ASSIGN_EVENT_TYPE_DUE,
204
            'groupid' => 0
205
        ];
206
 
207
        // Make sure the calendar event for assignment 1 matches the initial due date.
208
        $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
209
        $this->assertEquals($eventtime, $duedate);
210
 
211
        // Manually update assignment 1's due date.
212
        $DB->update_record('assign', (object) [
213
            'id' => $instance->id,
214
            'duedate' => $newduedate,
215
            'course' => $course->id
216
        ]);
217
 
218
        // Then refresh the assignment events of assignment 1's course.
219
        $this->assertTrue(assign_refresh_events($course->id));
220
 
221
        // Confirm that the assignment 1's due date event now has the new due date after refresh.
222
        $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
223
        $this->assertEquals($eventtime, $newduedate);
224
 
225
        // Create a second course and assignment.
226
        $othercourse = $this->getDataGenerator()->create_course();;
227
        $otherassign = $this->create_instance($othercourse, [
228
            'duedate' => $duedate,
229
        ]);
230
        $otherinstance = $otherassign->get_instance();
231
 
232
        // Manually update assignment 1 and 2's due dates.
233
        $newduedate += DAYSECS;
234
        $DB->update_record('assign', (object)[
235
            'id' => $instance->id,
236
            'duedate' => $newduedate,
237
            'course' => $course->id
238
        ]);
239
        $DB->update_record('assign', (object)[
240
            'id' => $otherinstance->id,
241
            'duedate' => $newduedate,
242
            'course' => $othercourse->id
243
        ]);
244
 
245
        // Refresh events of all courses and check the calendar events matches the new date.
246
        $this->assertTrue(assign_refresh_events());
247
 
248
        // Check the due date calendar event for assignment 1.
249
        $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
250
        $this->assertEquals($eventtime, $newduedate);
251
 
252
        // Check the due date calendar event for assignment 2.
253
        $eventparams['instance'] = $otherinstance->id;
254
        $eventtime = $DB->get_field('event', 'timestart', $eventparams, MUST_EXIST);
255
        $this->assertEquals($eventtime, $newduedate);
256
 
257
        // In case the course ID is passed as a numeric string.
258
        $this->assertTrue(assign_refresh_events('' . $course->id));
259
 
260
        // Non-existing course ID.
261
        $this->assertFalse(assign_refresh_events(-1));
262
 
263
        // Invalid course ID.
264
        $this->assertFalse(assign_refresh_events('aaa'));
265
    }
266
 
11 efrain 267
    public function test_assign_core_calendar_is_event_visible_duedate_event_as_teacher(): void {
1 efrain 268
        $this->resetAfterTest();
269
        $course = $this->getDataGenerator()->create_course();
270
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
271
        $assign = $this->create_instance($course);
272
 
273
        $this->setAdminUser();
274
 
275
        // Create a calendar event.
276
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
277
 
278
        // The teacher should see the due date event.
279
        $this->setUser($teacher);
280
        $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
281
    }
282
 
11 efrain 283
    public function test_assign_core_calendar_is_event_visible_duedate_event_for_teacher(): void {
1 efrain 284
        $this->resetAfterTest();
285
        $course = $this->getDataGenerator()->create_course();
286
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
287
        $assign = $this->create_instance($course);
288
 
289
        $this->setAdminUser();
290
 
291
        // Create a calendar event.
292
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
293
 
294
        // Now, log out.
295
        $this->setUser();
296
 
297
        // The teacher should see the due date event.
298
        $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $teacher->id));
299
    }
300
 
11 efrain 301
    public function test_assign_core_calendar_is_event_visible_duedate_event_as_student(): void {
1 efrain 302
        $this->resetAfterTest();
303
        $course = $this->getDataGenerator()->create_course();
304
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
305
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
306
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
307
 
308
        $this->setAdminUser();
309
 
310
        // Create a calendar event.
311
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
312
 
313
        // The student should care about the due date event.
314
        $this->setUser($student);
315
        $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
316
    }
317
 
11 efrain 318
    public function test_assign_core_calendar_is_event_visible_duedate_event_for_student(): void {
1 efrain 319
        $this->resetAfterTest();
320
        $course = $this->getDataGenerator()->create_course();
321
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
322
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
323
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
324
 
325
        $this->setAdminUser();
326
 
327
        // Create a calendar event.
328
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
329
 
330
        // Now, log out.
331
        $this->setUser();
332
 
333
        // The student should care about the due date event.
334
        $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $student->id));
335
    }
336
 
11 efrain 337
    public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_teacher(): void {
1 efrain 338
        $this->resetAfterTest();
339
        $course = $this->getDataGenerator()->create_course();
340
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
341
        $assign = $this->create_instance($course);
342
 
343
        // Create a calendar event.
344
        $this->setAdminUser();
345
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
346
 
347
        // The teacher should see the due date event.
348
        $this->setUser($teacher);
349
        $this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
350
    }
351
 
352
 
11 efrain 353
    public function test_assign_core_calendar_is_event_visible_gradingduedate_event_for_teacher(): void {
1 efrain 354
        $this->resetAfterTest();
355
        $course = $this->getDataGenerator()->create_course();
356
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
357
        $assign = $this->create_instance($course);
358
 
359
        // Create a calendar event.
360
        $this->setAdminUser();
361
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
362
 
363
        // Now, log out.
364
        $this->setUser();
365
 
366
        // The teacher should see the due date event.
367
        $this->assertTrue(mod_assign_core_calendar_is_event_visible($event, $teacher->id));
368
    }
369
 
11 efrain 370
    public function test_assign_core_calendar_is_event_visible_gradingduedate_event_as_student(): void {
1 efrain 371
        $this->resetAfterTest();
372
        $course = $this->getDataGenerator()->create_course();
373
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
374
        $assign = $this->create_instance($course);
375
 
376
        // Create a calendar event.
377
        $this->setAdminUser();
378
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
379
 
380
        // The student should not see the due date event.
381
        $this->setUser($student);
382
        $this->assertFalse(mod_assign_core_calendar_is_event_visible($event));
383
    }
384
 
385
 
11 efrain 386
    public function test_assign_core_calendar_is_event_visible_gradingduedate_event_for_student(): void {
1 efrain 387
        $this->resetAfterTest();
388
        $course = $this->getDataGenerator()->create_course();
389
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
390
        $assign = $this->create_instance($course);
391
 
392
        // Create a calendar event.
393
        $this->setAdminUser();
394
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
395
 
396
        // Now, log out.
397
        $this->setUser();
398
 
399
        // The student should not see the due date event.
400
        $this->assertFalse(mod_assign_core_calendar_is_event_visible($event, $student->id));
401
    }
402
 
11 efrain 403
    public function test_assign_core_calendar_provide_event_action_duedate_as_teacher(): void {
1 efrain 404
        $this->resetAfterTest();
405
        $course = $this->getDataGenerator()->create_course();
406
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
407
        $assign = $this->create_instance($course);
408
 
409
        // Create a calendar event.
410
        $this->setAdminUser();
411
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
412
 
413
        // The teacher should see the event.
414
        $this->setUser($teacher);
415
        $factory = new \core_calendar\action_factory();
416
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
417
 
418
        // The teacher should not have an action for a due date event.
419
        $this->assertNull($actionevent);
420
    }
421
 
11 efrain 422
    public function test_assign_core_calendar_provide_event_action_duedate_for_teacher(): void {
1 efrain 423
        $this->resetAfterTest();
424
        $course = $this->getDataGenerator()->create_course();
425
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
426
        $assign = $this->create_instance($course);
427
 
428
        // Create a calendar event.
429
        $this->setAdminUser();
430
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
431
 
432
        // Now, log out.
433
        $this->setUser();
434
 
435
        // Decorate action event for a teacher.
436
        $factory = new \core_calendar\action_factory();
437
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $teacher->id);
438
 
439
        // The teacher should not have an action for a due date event.
440
        $this->assertNull($actionevent);
441
    }
442
 
11 efrain 443
    public function test_assign_core_calendar_provide_event_action_duedate_as_student(): void {
1 efrain 444
        $this->resetAfterTest();
445
        $course = $this->getDataGenerator()->create_course();
446
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
447
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
448
 
449
        // Create a calendar event.
450
        $this->setAdminUser();
451
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
452
 
453
        // The student should see the event.
454
        $this->setUser($student);
455
        $factory = new \core_calendar\action_factory();
456
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
457
 
458
        // Confirm the event was decorated.
459
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
460
        $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent->get_name());
461
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
462
        $this->assertEquals(1, $actionevent->get_item_count());
463
        $this->assertTrue($actionevent->is_actionable());
464
    }
465
 
11 efrain 466
    public function test_assign_core_calendar_provide_event_action_duedate_for_student(): void {
1 efrain 467
        $this->resetAfterTest();
468
        $course = $this->getDataGenerator()->create_course();
469
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
470
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
471
 
472
        // Create a calendar event.
473
        $this->setAdminUser();
474
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
475
 
476
        // Now, log out.
477
        $this->setUser();
478
 
479
        // Decorate action event for a student.
480
        $factory = new \core_calendar\action_factory();
481
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
482
 
483
        // Confirm the event was decorated.
484
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
485
        $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent->get_name());
486
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
487
        $this->assertEquals(1, $actionevent->get_item_count());
488
        $this->assertTrue($actionevent->is_actionable());
489
    }
490
 
491
    /**
492
     * Test group submissions.
493
     * @covers \assign::mod_assign_core_calendar_provide_event_action
494
     */
495
    public function test_assign_core_calendar_provide_event_action_duedate_for_group_assignment(): void {
496
        $this->resetAfterTest();
497
        $course = $this->getDataGenerator()->create_course();
498
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
499
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
500
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
501
        $group = $this->getDataGenerator()->create_group([
502
            'courseid' => $course->id,
503
        ]);
504
        groups_add_member($group->id, $student1->id);
505
        groups_add_member($group->id, $student2->id);
506
 
507
        $assign = $this->create_instance($course, [
508
            'assignsubmission_onlinetext_enabled' => 1,
509
            'requiresubmissionstatement' => 1,
510
            'teamsubmission' => 1,
511
            'completion' => 0,
512
            'completionsubmit' => 1,
513
        ]);
514
 
515
        // Create a calendar event.
516
        $this->setAdminUser();
517
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
518
        $this->setUser();
519
 
520
        $factory = new \core_calendar\action_factory();
521
        $actionevent1 = mod_assign_core_calendar_provide_event_action($event, $factory, $student1->id);
522
        $actionevent2 = mod_assign_core_calendar_provide_event_action($event, $factory, $student2->id);
523
 
524
        // Confirm the events were decorated.
525
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent1);
526
        $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent1->get_name());
527
        $this->assertInstanceOf('moodle_url', $actionevent1->get_url());
528
        $this->assertEquals(1, $actionevent1->get_item_count());
529
        $this->assertTrue($actionevent1->is_actionable());
530
 
531
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent2);
532
        $this->assertEquals(get_string('addsubmission', 'assign'), $actionevent2->get_name());
533
        $this->assertInstanceOf('moodle_url', $actionevent2->get_url());
534
        $this->assertEquals(1, $actionevent2->get_item_count());
535
        $this->assertTrue($actionevent2->is_actionable());
536
 
537
        // Submit as the student.
538
        $this->add_submission($student1, $assign);
539
        $this->submit_for_grading($student1, $assign, ['submissionstatement' => 'Hello, world!']);
540
 
541
        // Create a new calendar event.
542
        $this->setAdminUser();
543
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
544
        $this->setUser();
545
 
546
        // Confirm there were no events to action.
547
        $actionevent1 = mod_assign_core_calendar_provide_event_action($event, $factory, $student1->id);
548
        $this->assertNull($actionevent1);
549
 
550
        $actionevent2 = mod_assign_core_calendar_provide_event_action($event, $factory, $student2->id);
551
        $this->assertNull($actionevent2);
552
    }
553
 
11 efrain 554
    public function test_assign_core_calendar_provide_event_action_gradingduedate_as_teacher(): void {
1 efrain 555
        $this->resetAfterTest();
556
        $course = $this->getDataGenerator()->create_course();
557
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
558
        $assign = $this->create_instance($course);
559
 
560
        // Create a calendar event.
561
        $this->setAdminUser();
562
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
563
 
564
        $this->setUser($teacher);
565
        $factory = new \core_calendar\action_factory();
566
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
567
 
568
        // Confirm the event was decorated.
569
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
570
        $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
571
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
572
        $this->assertEquals(0, $actionevent->get_item_count());
573
        $this->assertTrue($actionevent->is_actionable());
574
    }
575
 
11 efrain 576
    public function test_assign_core_calendar_provide_event_action_gradingduedate_for_teacher(): void {
1 efrain 577
        $this->resetAfterTest();
578
        $course = $this->getDataGenerator()->create_course();
579
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
580
        $assign = $this->create_instance($course);
581
 
582
        // Create a calendar event.
583
        $this->setAdminUser();
584
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
585
 
586
        // Now, log out.
587
        $this->setUser();
588
 
589
        // Decorate action event for a teacher.
590
        $factory = new \core_calendar\action_factory();
591
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $teacher->id);
592
 
593
        // Confirm the event was decorated.
594
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
595
        $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
596
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
597
        $this->assertEquals(0, $actionevent->get_item_count());
598
        $this->assertTrue($actionevent->is_actionable());
599
    }
600
 
11 efrain 601
    public function test_assign_core_calendar_provide_event_action_gradingduedate_as_student(): void {
1 efrain 602
        $this->resetAfterTest();
603
        $course = $this->getDataGenerator()->create_course();
604
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
605
        $assign = $this->create_instance($course);
606
 
607
        // Create a calendar event.
608
        $this->setAdminUser();
609
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
610
 
611
        $this->setUser($student);
612
        $factory = new \core_calendar\action_factory();
613
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
614
 
615
        // Confirm the event was decorated.
616
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
617
        $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
618
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
619
        $this->assertEquals(0, $actionevent->get_item_count());
620
        $this->assertFalse($actionevent->is_actionable());
621
    }
622
 
11 efrain 623
    public function test_assign_core_calendar_provide_event_action_gradingduedate_for_student(): void {
1 efrain 624
        $this->resetAfterTest();
625
        $course = $this->getDataGenerator()->create_course();
626
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
627
        $assign = $this->create_instance($course);
628
 
629
        // Create a calendar event.
630
        $this->setAdminUser();
631
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_GRADINGDUE);
632
 
633
        // Now, log out.
634
        $this->setUser();
635
 
636
        // Decorate action event for a student.
637
        $factory = new \core_calendar\action_factory();
638
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
639
 
640
        // Confirm the event was decorated.
641
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
642
        $this->assertEquals(get_string('gradenoun'), $actionevent->get_name());
643
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
644
        $this->assertEquals(0, $actionevent->get_item_count());
645
        $this->assertFalse($actionevent->is_actionable());
646
    }
647
 
11 efrain 648
    public function test_assign_core_calendar_provide_event_action_duedate_as_student_submitted(): void {
1 efrain 649
        $this->resetAfterTest();
650
        $course = $this->getDataGenerator()->create_course();
651
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
652
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
653
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
654
 
655
        $this->setAdminUser();
656
 
657
        // Create a calendar event.
658
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
659
 
660
        // Create an action factory.
661
        $factory = new \core_calendar\action_factory();
662
 
663
        // Submit as the student.
664
        $this->add_submission($student, $assign);
665
        $this->submit_for_grading($student, $assign);
666
 
667
        // Confirm there was no event to action.
668
        $factory = new \core_calendar\action_factory();
669
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
670
        $this->assertNull($actionevent);
671
    }
672
 
11 efrain 673
    public function test_assign_core_calendar_provide_event_action_duedate_for_student_submitted(): void {
1 efrain 674
        $this->resetAfterTest();
675
        $course = $this->getDataGenerator()->create_course();
676
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
677
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
678
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
679
 
680
        $this->setAdminUser();
681
 
682
        // Create a calendar event.
683
        $event = $this->create_action_event($course, $assign, ASSIGN_EVENT_TYPE_DUE);
684
 
685
        // Create an action factory.
686
        $factory = new \core_calendar\action_factory();
687
 
688
        // Submit as the student.
689
        $this->add_submission($student, $assign);
690
        $this->submit_for_grading($student, $assign);
691
 
692
        // Now, log out.
693
        $this->setUser();
694
 
695
        // Confirm there was no event to action.
696
        $factory = new \core_calendar\action_factory();
697
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
698
        $this->assertNull($actionevent);
699
    }
700
 
11 efrain 701
    public function test_assign_core_calendar_provide_event_action_already_completed(): void {
1 efrain 702
        $this->resetAfterTest();
703
        set_config('enablecompletion', 1);
704
        $this->setAdminUser();
705
 
706
        // Create the activity.
707
        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
708
        $assign = $this->create_instance($course,
709
         ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]);
710
 
711
        // Get some additional data.
712
        $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
713
 
714
        // Create a calendar event.
715
        $event = $this->create_action_event($course, $assign,
716
            \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
717
 
718
        // Mark the activity as completed.
719
        $completion = new \completion_info($course);
720
        $completion->set_module_viewed($cm);
721
 
722
        // Create an action factory.
723
        $factory = new \core_calendar\action_factory();
724
 
725
        // Decorate action event.
726
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
727
 
728
        // Ensure result was null.
729
        $this->assertNull($actionevent);
730
    }
731
 
11 efrain 732
    public function test_assign_core_calendar_provide_event_action_already_completed_for_user(): void {
1 efrain 733
        $this->resetAfterTest();
734
        set_config('enablecompletion', 1);
735
        $this->setAdminUser();
736
 
737
        // Create the activity.
738
        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
739
        $assign = $this->create_instance($course,
740
         ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]);
741
 
742
        // Enrol a student in the course.
743
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
744
 
745
        // Get some additional data.
746
        $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
747
 
748
        // Create a calendar event.
749
        $event = $this->create_action_event($course, $assign,
750
            \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
751
 
752
        // Mark the activity as completed for the student.
753
        $completion = new \completion_info($course);
754
        $completion->set_module_viewed($cm, $student->id);
755
 
756
        // Create an action factory.
757
        $factory = new \core_calendar\action_factory();
758
 
759
        // Decorate action event for the student.
760
        $actionevent = mod_assign_core_calendar_provide_event_action($event, $factory, $student->id);
761
 
762
        // Ensure result was null.
763
        $this->assertNull($actionevent);
764
    }
765
 
766
    /**
767
     * Creates an action event.
768
     *
769
     * @param \stdClass $course The course the assignment is in
770
     * @param assign $assign The assignment to create an event for
771
     * @param string $eventtype The event type. eg. ASSIGN_EVENT_TYPE_DUE.
772
     * @return bool|calendar_event
773
     */
774
    private function create_action_event($course, $assign, $eventtype) {
775
        $event = new \stdClass();
776
        $event->name = 'Calendar event';
777
        $event->modulename  = 'assign';
778
        $event->courseid = $course->id;
779
        $event->instance = $assign->get_instance()->id;
780
        $event->type = CALENDAR_EVENT_TYPE_ACTION;
781
        $event->eventtype = $eventtype;
782
        $event->timestart = time();
783
 
784
        return \calendar_event::create($event);
785
    }
786
 
787
    /**
788
     * Test the callback responsible for returning the completion rule descriptions.
789
     * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
790
     * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
791
     */
11 efrain 792
    public function test_mod_assign_completion_get_active_rule_descriptions(): void {
1 efrain 793
        $this->resetAfterTest();
794
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
795
 
796
        $this->setAdminUser();
797
 
798
        // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
799
        $cm1 = $this->create_instance($course, ['completion' => '2', 'completionsubmit' => '1'])->get_course_module();
800
        $cm2 = $this->create_instance($course, ['completion' => '2', 'completionsubmit' => '0'])->get_course_module();
801
 
802
        // Data for the stdClass input type.
803
        // This type of input would occur when checking the default completion rules for an activity type, where we don't have
804
        // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
805
        $moddefaults = (object) [
806
            'customdata' => [
807
                'customcompletionrules' => [
808
                    'completionsubmit' => '1',
809
                ],
810
            ],
811
            'completion' => 2,
812
        ];
813
 
814
        $activeruledescriptions = [get_string('completionsubmit', 'assign')];
815
        $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
816
        $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($cm2), []);
817
        $this->assertEquals(mod_assign_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
818
        $this->assertEquals(mod_assign_get_completion_active_rule_descriptions(new \stdClass()), []);
819
    }
820
 
821
    /**
822
     * Test that if some grades are not set, they are left alone and not rescaled
823
     */
11 efrain 824
    public function test_assign_rescale_activity_grades_some_unset(): void {
1 efrain 825
        $this->resetAfterTest();
826
        $course = $this->getDataGenerator()->create_course();
827
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
828
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
829
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
830
 
831
        // As a teacher.
832
        $this->setUser($teacher);
833
        $assign = $this->create_instance($course);
834
 
835
        // Grade the student.
836
        $data = ['grade' => 50];
837
        $assign->testable_apply_grade_to_user((object)$data, $student->id, 0);
838
 
839
        // Try getting another students grade. This will give a grade of ASSIGN_GRADE_NOT_SET (-1).
840
        $assign->get_user_grade($otherstudent->id, true);
841
 
842
        // Rescale.
843
        assign_rescale_activity_grades($course, $assign->get_course_module(), 0, 100, 0, 10);
844
 
845
        // Get the grades for both students.
846
        $studentgrade = $assign->get_user_grade($student->id, true);
847
        $otherstudentgrade = $assign->get_user_grade($otherstudent->id, true);
848
 
849
        // Make sure the real grade is scaled, but the ASSIGN_GRADE_NOT_SET stays the same.
850
        $this->assertEquals($studentgrade->grade, 5);
851
        $this->assertEquals($otherstudentgrade->grade, ASSIGN_GRADE_NOT_SET);
852
    }
853
 
854
    /**
855
     * Return false when there are not overrides for this assign instance.
856
     */
11 efrain 857
    public function test_assign_is_override_calendar_event_no_override(): void {
1 efrain 858
        global $CFG, $DB;
859
        require_once($CFG->dirroot . '/calendar/lib.php');
860
 
861
        $this->resetAfterTest();
862
        $course = $this->getDataGenerator()->create_course();
863
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
864
 
865
        $this->setAdminUser();
866
 
867
        $duedate = time();
868
        $assign = $this->create_instance($course, ['duedate' => $duedate]);
869
 
870
        $instance = $assign->get_instance();
871
        $event = new \calendar_event((object)[
872
            'modulename' => 'assign',
873
            'instance' => $instance->id,
874
            'userid' => $student->id,
875
        ]);
876
 
877
        $this->assertFalse($assign->is_override_calendar_event($event));
878
    }
879
 
880
    /**
881
     * Return false if the given event isn't an assign module event.
882
     */
11 efrain 883
    public function test_assign_is_override_calendar_event_no_nodule_event(): void {
1 efrain 884
        global $CFG, $DB;
885
        require_once($CFG->dirroot . '/calendar/lib.php');
886
 
887
        $this->resetAfterTest();
888
        $course = $this->getDataGenerator()->create_course();
889
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
890
 
891
        $this->setAdminUser();
892
 
893
        $userid = $student->id;
894
        $duedate = time();
895
        $assign = $this->create_instance($course, ['duedate' => $duedate]);
896
 
897
        $instance = $assign->get_instance();
898
        $event = new \calendar_event((object)[
899
            'userid' => $userid
900
        ]);
901
 
902
        $this->assertFalse($assign->is_override_calendar_event($event));
903
    }
904
 
905
    /**
906
     * Return false if there is overrides for this use but they belong to another assign
907
     * instance.
908
     */
11 efrain 909
    public function test_assign_is_override_calendar_event_different_assign_instance(): void {
1 efrain 910
        global $CFG, $DB;
911
        require_once($CFG->dirroot . '/calendar/lib.php');
912
 
913
        $this->resetAfterTest();
914
        $course = $this->getDataGenerator()->create_course();
915
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
916
 
917
        $this->setAdminUser();
918
 
919
        $duedate = time();
920
        $assign = $this->create_instance($course, ['duedate' => $duedate]);
921
        $instance = $assign->get_instance();
922
 
923
        $otherassign = $this->create_instance($course, ['duedate' => $duedate]);
924
        $otherinstance = $otherassign->get_instance();
925
 
926
        $event = new \calendar_event((object) [
927
            'modulename' => 'assign',
928
            'instance' => $instance->id,
929
            'userid' => $student->id,
930
        ]);
931
 
932
        $DB->insert_record('assign_overrides', (object) [
933
                'assignid' => $otherinstance->id,
934
                'userid' => $student->id,
935
            ]);
936
 
937
        $this->assertFalse($assign->is_override_calendar_event($event));
938
    }
939
 
940
    /**
941
     * Return true if there is a user override for this event and assign instance.
942
     */
11 efrain 943
    public function test_assign_is_override_calendar_event_user_override(): void {
1 efrain 944
        global $CFG, $DB;
945
        require_once($CFG->dirroot . '/calendar/lib.php');
946
 
947
        $this->resetAfterTest();
948
        $course = $this->getDataGenerator()->create_course();
949
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
950
 
951
        $this->setAdminUser();
952
 
953
        $duedate = time();
954
        $assign = $this->create_instance($course, ['duedate' => $duedate]);
955
 
956
        $instance = $assign->get_instance();
957
        $event = new \calendar_event((object) [
958
            'modulename' => 'assign',
959
            'instance' => $instance->id,
960
            'userid' => $student->id,
961
        ]);
962
 
963
 
964
        $DB->insert_record('assign_overrides', (object) [
965
                'assignid' => $instance->id,
966
                'userid' => $student->id,
967
            ]);
968
 
969
        $this->assertTrue($assign->is_override_calendar_event($event));
970
    }
971
 
972
    /**
973
     * Return true if there is a group override for the event and assign instance.
974
     */
11 efrain 975
    public function test_assign_is_override_calendar_event_group_override(): void {
1 efrain 976
        global $CFG, $DB;
977
        require_once($CFG->dirroot . '/calendar/lib.php');
978
 
979
        $this->resetAfterTest();
980
        $course = $this->getDataGenerator()->create_course();
981
 
982
        $this->setAdminUser();
983
 
984
        $duedate = time();
985
        $assign = $this->create_instance($course, ['duedate' => $duedate]);
986
        $instance = $assign->get_instance();
987
        $group = $this->getDataGenerator()->create_group(array('courseid' => $instance->course));
988
 
989
        $event = new \calendar_event((object) [
990
            'modulename' => 'assign',
991
            'instance' => $instance->id,
992
            'groupid' => $group->id,
993
        ]);
994
 
995
        $DB->insert_record('assign_overrides', (object) [
996
                'assignid' => $instance->id,
997
                'groupid' => $group->id,
998
            ]);
999
 
1000
        $this->assertTrue($assign->is_override_calendar_event($event));
1001
    }
1002
 
1003
    /**
1004
     * Unknown event types should not have any limit restrictions returned.
1005
     */
11 efrain 1006
    public function test_mod_assign_core_calendar_get_valid_event_timestart_range_unkown_event_type(): void {
1 efrain 1007
        global $CFG;
1008
        require_once($CFG->dirroot . '/calendar/lib.php');
1009
 
1010
        $this->resetAfterTest();
1011
        $course = $this->getDataGenerator()->create_course();
1012
 
1013
        $this->setAdminUser();
1014
 
1015
        $duedate = time();
1016
        $assign = $this->create_instance($course, ['duedate' => $duedate]);
1017
        $instance = $assign->get_instance();
1018
 
1019
        $event = new \calendar_event((object) [
1020
            'courseid' => $instance->course,
1021
            'modulename' => 'assign',
1022
            'instance' => $instance->id,
1023
            'eventtype' => 'SOME RANDOM EVENT'
1024
        ]);
1025
 
1026
        list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1027
        $this->assertNull($min);
1028
        $this->assertNull($max);
1029
    }
1030
 
1031
    /**
1032
     * Override events should not have any limit restrictions returned.
1033
     */
11 efrain 1034
    public function test_mod_assign_core_calendar_get_valid_event_timestart_range_override_event(): void {
1 efrain 1035
        global $CFG, $DB;
1036
        require_once($CFG->dirroot . '/calendar/lib.php');
1037
 
1038
        $this->resetAfterTest();
1039
        $course = $this->getDataGenerator()->create_course();
1040
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1041
 
1042
        $this->setAdminUser();
1043
 
1044
        $duedate = time();
1045
        $assign = $this->create_instance($course, ['duedate' => $duedate]);
1046
        $instance = $assign->get_instance();
1047
 
1048
        $event = new \calendar_event((object) [
1049
            'courseid' => $instance->course,
1050
            'modulename' => 'assign',
1051
            'instance' => $instance->id,
1052
            'userid' => $student->id,
1053
            'eventtype' => ASSIGN_EVENT_TYPE_DUE
1054
        ]);
1055
 
1056
        $record = (object) [
1057
            'assignid' => $instance->id,
1058
            'userid' => $student->id,
1059
        ];
1060
 
1061
        $DB->insert_record('assign_overrides', $record);
1062
 
1063
        list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1064
        $this->assertFalse($min);
1065
        $this->assertFalse($max);
1066
    }
1067
 
1068
    /**
1069
     * Assignments configured without a submissions from and cutoff date should not have
1070
     * any limits applied.
1071
     */
11 efrain 1072
    public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_no_limit(): void {
1 efrain 1073
        global $CFG, $DB;
1074
        require_once($CFG->dirroot . '/calendar/lib.php');
1075
 
1076
        $this->resetAfterTest();
1077
        $course = $this->getDataGenerator()->create_course();
1078
 
1079
        $this->setAdminUser();
1080
 
1081
        $duedate = time();
1082
        $assign = $this->create_instance($course, [
1083
            'duedate' => $duedate,
1084
            'allowsubmissionsfromdate' => 0,
1085
            'cutoffdate' => 0,
1086
        ]);
1087
        $instance = $assign->get_instance();
1088
 
1089
        $event = new \calendar_event((object) [
1090
            'courseid' => $instance->course,
1091
            'modulename' => 'assign',
1092
            'instance' => $instance->id,
1093
            'eventtype' => ASSIGN_EVENT_TYPE_DUE
1094
        ]);
1095
 
1096
        list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1097
        $this->assertNull($min);
1098
        $this->assertNull($max);
1099
    }
1100
 
1101
    /**
1102
     * Assignments should be bottom and top bound by the submissions from date and cutoff date
1103
     * respectively.
1104
     */
11 efrain 1105
    public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_with_limits(): void {
1 efrain 1106
        global $CFG, $DB;
1107
        require_once($CFG->dirroot . '/calendar/lib.php');
1108
 
1109
        $this->resetAfterTest();
1110
        $course = $this->getDataGenerator()->create_course();
1111
 
1112
        $this->setAdminUser();
1113
 
1114
        $duedate = time();
1115
        $submissionsfromdate = $duedate - DAYSECS;
1116
        $cutoffdate = $duedate + DAYSECS;
1117
        $assign = $this->create_instance($course, [
1118
            'duedate' => $duedate,
1119
            'allowsubmissionsfromdate' => $submissionsfromdate,
1120
            'cutoffdate' => $cutoffdate,
1121
        ]);
1122
        $instance = $assign->get_instance();
1123
 
1124
        $event = new \calendar_event((object) [
1125
            'courseid' => $instance->course,
1126
            'modulename' => 'assign',
1127
            'instance' => $instance->id,
1128
            'eventtype' => ASSIGN_EVENT_TYPE_DUE
1129
        ]);
1130
 
1131
        list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1132
        $this->assertEquals($submissionsfromdate, $min[0]);
1133
        $this->assertNotEmpty($min[1]);
1134
        $this->assertEquals($cutoffdate, $max[0]);
1135
        $this->assertNotEmpty($max[1]);
1136
    }
1137
 
1138
    /**
1139
     * Assignment grading due date should not have any limits of no due date and cutoff date is set.
1140
     */
11 efrain 1141
    public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_no_limit(): void {
1 efrain 1142
        global $CFG, $DB;
1143
        require_once($CFG->dirroot . '/calendar/lib.php');
1144
 
1145
        $this->resetAfterTest();
1146
        $course = $this->getDataGenerator()->create_course();
1147
 
1148
        $this->setAdminUser();
1149
 
1150
        $assign = $this->create_instance($course, [
1151
            'duedate' => 0,
1152
            'allowsubmissionsfromdate' => 0,
1153
            'cutoffdate' => 0,
1154
        ]);
1155
        $instance = $assign->get_instance();
1156
 
1157
        $event = new \calendar_event((object) [
1158
            'courseid' => $instance->course,
1159
            'modulename' => 'assign',
1160
            'instance' => $instance->id,
1161
            'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1162
        ]);
1163
 
1164
        list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1165
        $this->assertNull($min);
1166
        $this->assertNull($max);
1167
    }
1168
 
1169
    /**
1170
     * Assignment grading due event is minimum bound by the due date, if it is set.
1171
     */
11 efrain 1172
    public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_with_due_date(): void {
1 efrain 1173
        global $CFG, $DB;
1174
        require_once($CFG->dirroot . '/calendar/lib.php');
1175
 
1176
        $this->resetAfterTest();
1177
        $course = $this->getDataGenerator()->create_course();
1178
 
1179
        $this->setAdminUser();
1180
 
1181
        $duedate = time();
1182
        $assign = $this->create_instance($course, ['duedate' => $duedate]);
1183
        $instance = $assign->get_instance();
1184
 
1185
        $event = new \calendar_event((object) [
1186
            'courseid' => $instance->course,
1187
            'modulename' => 'assign',
1188
            'instance' => $instance->id,
1189
            'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
1190
        ]);
1191
 
1192
        list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
1193
        $this->assertEquals($duedate, $min[0]);
1194
        $this->assertNotEmpty($min[1]);
1195
        $this->assertNull($max);
1196
    }
1197
 
1198
    /**
1199
     * Non due date events should not update the assignment due date.
1200
     */
11 efrain 1201
    public function test_mod_assign_core_calendar_event_timestart_updated_non_due_event(): void {
1 efrain 1202
        global $CFG, $DB;
1203
        require_once($CFG->dirroot . '/calendar/lib.php');
1204
 
1205
        $this->resetAfterTest();
1206
        $course = $this->getDataGenerator()->create_course();
1207
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1208
 
1209
        $this->setAdminUser();
1210
 
1211
        $duedate = time();
1212
        $submissionsfromdate = $duedate - DAYSECS;
1213
        $cutoffdate = $duedate + DAYSECS;
1214
        $assign = $this->create_instance($course, [
1215
            'duedate' => $duedate,
1216
            'allowsubmissionsfromdate' => $submissionsfromdate,
1217
            'cutoffdate' => $cutoffdate,
1218
        ]);
1219
        $instance = $assign->get_instance();
1220
 
1221
        $event = new \calendar_event((object) [
1222
            'courseid' => $instance->course,
1223
            'modulename' => 'assign',
1224
            'instance' => $instance->id,
1225
            'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE,
1226
            'timestart' => $duedate + 1
1227
        ]);
1228
 
1229
        mod_assign_core_calendar_event_timestart_updated($event, $instance);
1230
 
1231
        $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1232
        $this->assertEquals($duedate, $newinstance->duedate);
1233
    }
1234
 
1235
    /**
1236
     * Due date override events should not change the assignment due date.
1237
     */
11 efrain 1238
    public function test_mod_assign_core_calendar_event_timestart_updated_due_event_override(): void {
1 efrain 1239
        global $CFG, $DB;
1240
        require_once($CFG->dirroot . '/calendar/lib.php');
1241
 
1242
        $this->resetAfterTest();
1243
        $course = $this->getDataGenerator()->create_course();
1244
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1245
 
1246
        $this->setAdminUser();
1247
 
1248
        $duedate = time();
1249
        $submissionsfromdate = $duedate - DAYSECS;
1250
        $cutoffdate = $duedate + DAYSECS;
1251
        $assign = $this->create_instance($course, [
1252
            'duedate' => $duedate,
1253
            'allowsubmissionsfromdate' => $submissionsfromdate,
1254
            'cutoffdate' => $cutoffdate,
1255
        ]);
1256
        $instance = $assign->get_instance();
1257
 
1258
        $event = new \calendar_event((object) [
1259
            'courseid' => $instance->course,
1260
            'modulename' => 'assign',
1261
            'instance' => $instance->id,
1262
            'userid' => $student->id,
1263
            'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1264
            'timestart' => $duedate + 1
1265
        ]);
1266
 
1267
        $record = (object) [
1268
            'assignid' => $instance->id,
1269
            'userid' => $student->id,
1270
            'duedate' => $duedate + 1,
1271
        ];
1272
 
1273
        $DB->insert_record('assign_overrides', $record);
1274
 
1275
        mod_assign_core_calendar_event_timestart_updated($event, $instance);
1276
 
1277
        $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1278
        $this->assertEquals($duedate, $newinstance->duedate);
1279
    }
1280
 
1281
    /**
1282
     * Due date events should update the assignment due date.
1283
     */
11 efrain 1284
    public function test_mod_assign_core_calendar_event_timestart_updated_due_event(): void {
1 efrain 1285
        global $CFG, $DB;
1286
        require_once($CFG->dirroot . '/calendar/lib.php');
1287
 
1288
        $this->resetAfterTest();
1289
        $course = $this->getDataGenerator()->create_course();
1290
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1291
 
1292
        $this->setAdminUser();
1293
 
1294
        $duedate = time();
1295
        $newduedate = $duedate + 1;
1296
        $submissionsfromdate = $duedate - DAYSECS;
1297
        $cutoffdate = $duedate + DAYSECS;
1298
        $assign = $this->create_instance($course, [
1299
            'duedate' => $duedate,
1300
            'allowsubmissionsfromdate' => $submissionsfromdate,
1301
            'cutoffdate' => $cutoffdate,
1302
        ]);
1303
        $instance = $assign->get_instance();
1304
 
1305
        $event = new \calendar_event((object) [
1306
            'courseid' => $instance->course,
1307
            'modulename' => 'assign',
1308
            'instance' => $instance->id,
1309
            'eventtype' => ASSIGN_EVENT_TYPE_DUE,
1310
            'timestart' => $newduedate
1311
        ]);
1312
 
1313
        mod_assign_core_calendar_event_timestart_updated($event, $instance);
1314
 
1315
        $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1316
        $this->assertEquals($newduedate, $newinstance->duedate);
1317
    }
1318
 
1319
    /**
1320
     * If a student somehow finds a way to update the due date calendar event
1321
     * then the callback should not be executed to update the assignment due
1322
     * date as well otherwise that would be a security issue.
1323
     */
11 efrain 1324
    public function test_student_role_cant_update_due_event(): void {
1 efrain 1325
        global $CFG, $DB;
1326
        require_once($CFG->dirroot . '/calendar/lib.php');
1327
 
1328
        $this->resetAfterTest();
1329
        $course = $this->getDataGenerator()->create_course();
1330
        $context = \context_course::instance($course->id);
1331
 
1332
        $roleid = $this->getDataGenerator()->create_role();
1333
        $role = $DB->get_record('role', ['id' => $roleid]);
1334
        $user = $this->getDataGenerator()->create_and_enrol($course, $role->shortname);
1335
 
1336
        $this->setAdminUser();
1337
 
1338
        $mapper = calendar_event_container::get_event_mapper();
1339
        $now = time();
1340
        $duedate = (new \DateTime())->setTimestamp($now);
1341
        $newduedate = (new \DateTime())->setTimestamp($now)->modify('+1 day');
1342
        $assign = $this->create_instance($course, [
1343
            'course' => $course->id,
1344
            'duedate' => $duedate->getTimestamp(),
1345
        ]);
1346
        $instance = $assign->get_instance();
1347
 
1348
        $record = $DB->get_record('event', [
1349
            'courseid' => $course->id,
1350
            'modulename' => 'assign',
1351
            'instance' => $instance->id,
1352
            'eventtype' => ASSIGN_EVENT_TYPE_DUE
1353
        ]);
1354
 
1355
        $event = new \calendar_event($record);
1356
 
1357
        assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1358
        assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true);
1359
 
1360
        $this->setUser($user);
1361
 
1362
        calendar_local_api::update_event_start_day(
1363
            $mapper->from_legacy_event_to_event($event),
1364
            $newduedate
1365
        );
1366
 
1367
        $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1368
        $newevent = \calendar_event::load($event->id);
1369
        // The due date shouldn't have changed even though we updated the calendar
1370
        // event.
1371
        $this->assertEquals($duedate->getTimestamp(), $newinstance->duedate);
1372
        $this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
1373
    }
1374
 
1375
    /**
1376
     * A teacher with the capability to modify an assignment module should be
1377
     * able to update the assignment due date by changing the due date calendar
1378
     * event.
1379
     */
11 efrain 1380
    public function test_teacher_role_can_update_due_event(): void {
1 efrain 1381
        global $CFG, $DB;
1382
        require_once($CFG->dirroot . '/calendar/lib.php');
1383
 
1384
        $this->resetAfterTest();
1385
        $course = $this->getDataGenerator()->create_course();
1386
        $context = \context_course::instance($course->id);
1387
        $user = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1388
        $roleid = $DB->get_field('role', 'id', ['shortname' => 'teacher']);
1389
 
1390
        $this->setAdminUser();
1391
 
1392
        $mapper = calendar_event_container::get_event_mapper();
1393
        $now = time();
1394
        $duedate = (new \DateTime())->setTimestamp($now);
1395
        $newduedate = (new \DateTime())->setTimestamp($now)->modify('+1 day');
1396
        $assign = $this->create_instance($course, [
1397
            'course' => $course->id,
1398
            'duedate' => $duedate->getTimestamp(),
1399
        ]);
1400
        $instance = $assign->get_instance();
1401
 
1402
        $record = $DB->get_record('event', [
1403
            'courseid' => $course->id,
1404
            'modulename' => 'assign',
1405
            'instance' => $instance->id,
1406
            'eventtype' => ASSIGN_EVENT_TYPE_DUE
1407
        ]);
1408
 
1409
        $event = new \calendar_event($record);
1410
 
1411
        assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1412
        assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true);
1413
 
1414
        $this->setUser($user);
1415
        // Trigger and capture the event when adding a contact.
1416
        $sink = $this->redirectEvents();
1417
 
1418
        calendar_local_api::update_event_start_day(
1419
            $mapper->from_legacy_event_to_event($event),
1420
            $newduedate
1421
        );
1422
 
1423
        $triggeredevents = $sink->get_events();
1424
        $moduleupdatedevents = array_filter($triggeredevents, function($e) {
1425
            return is_a($e, 'core\event\course_module_updated');
1426
        });
1427
 
1428
        $newinstance = $DB->get_record('assign', ['id' => $instance->id]);
1429
        $newevent = \calendar_event::load($event->id);
1430
        // The due date shouldn't have changed even though we updated the calendar
1431
        // event.
1432
        $this->assertEquals($newduedate->getTimestamp(), $newinstance->duedate);
1433
        $this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
1434
        // Confirm that a module updated event is fired when the module
1435
        // is changed.
1436
        $this->assertNotEmpty($moduleupdatedevents);
1437
    }
1438
 
1439
    /**
1440
     * A user who does not have capabilities to add events to the calendar should be able to create an assignment.
1441
     */
11 efrain 1442
    public function test_creation_with_no_calendar_capabilities(): void {
1 efrain 1443
        $this->resetAfterTest();
1444
        $course = self::getDataGenerator()->create_course();
1445
        $context = \context_course::instance($course->id);
1446
        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
1447
        $roleid = self::getDataGenerator()->create_role();
1448
        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
1449
        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
1450
        $generator = self::getDataGenerator()->get_plugin_generator('mod_assign');
1451
        // Create an instance as a user without the calendar capabilities.
1452
        $this->setUser($user);
1453
        $time = time();
1454
        $params = array(
1455
            'course' => $course->id,
1456
            'allowsubmissionsfromdate' => $time,
1457
            'duedate' => $time + 500,
1458
            'cutoffdate' => $time + 600,
1459
            'gradingduedate' => $time + 700,
1460
        );
1461
        $generator->create_instance($params);
1462
    }
1463
}