Proyectos de Subversion Moodle

Rev

Rev 11 | | 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/locallib.php.
19
 *
20
 * @package    mod_assign
1441 ariadna 21
 * @category   test
1 efrain 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
use mod_assign_grade_form;
28
use mod_assign_test_generator;
29
use mod_assign_testable_assign;
30
 
31
defined('MOODLE_INTERNAL') || die();
32
 
33
global $CFG;
34
require_once($CFG->dirroot . '/mod/assign/locallib.php');
35
require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
36
 
37
/**
38
 * Unit tests for (some of) mod/assign/locallib.php.
39
 *
40
 * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
41
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42
 */
1441 ariadna 43
final class locallib_test extends \advanced_testcase {
1 efrain 44
    // Use the generator helper.
45
    use mod_assign_test_generator;
46
 
47
    /** @var array */
48
    public $extrastudents;
49
 
50
    /** @var array */
51
    public $extrasuspendedstudents;
52
 
11 efrain 53
    public function test_return_links(): void {
1 efrain 54
        global $PAGE;
55
 
56
        $this->resetAfterTest();
57
        $course = $this->getDataGenerator()->create_course();
58
 
59
        $assign = $this->create_instance($course);
60
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
61
 
62
        $assign->register_return_link('RETURNACTION', ['param' => 1]);
63
        $this->assertEquals('RETURNACTION', $assign->get_return_action());
64
        $this->assertEquals(['param' => 1], $assign->get_return_params());
65
    }
66
 
11 efrain 67
    public function test_get_feedback_plugins(): void {
1 efrain 68
        $this->resetAfterTest();
69
        $course = $this->getDataGenerator()->create_course();
70
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
71
 
72
        $this->setUser($teacher);
73
        $assign = $this->create_instance($course);
74
        $installedplugins = array_keys(\core_component::get_plugin_list('assignfeedback'));
75
 
76
        foreach ($assign->get_feedback_plugins() as $plugin) {
77
            $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins');
78
        }
79
    }
80
 
11 efrain 81
    public function test_get_submission_plugins(): void {
1 efrain 82
        $this->resetAfterTest();
83
        $course = $this->getDataGenerator()->create_course();
84
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
85
 
86
        $this->setUser($teacher);
87
        $assign = $this->create_instance($course);
88
        $installedplugins = array_keys(\core_component::get_plugin_list('assignsubmission'));
89
 
90
        foreach ($assign->get_submission_plugins() as $plugin) {
91
            $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins');
92
        }
93
    }
94
 
11 efrain 95
    public function test_is_blind_marking(): void {
1 efrain 96
        $this->resetAfterTest();
97
        $course = $this->getDataGenerator()->create_course();
98
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
99
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
100
 
101
        $this->setUser($teacher);
102
        $assign = $this->create_instance($course, ['blindmarking' => 1]);
103
        $this->assertEquals(true, $assign->is_blind_marking());
104
 
105
        // Test cannot see student names.
106
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
107
        $output = $assign->get_renderer()->render($gradingtable);
108
        $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
109
 
110
        // Test students cannot reveal identities.
111
        $nopermission = false;
112
        $student->ignoresesskey = true;
113
        $this->setUser($student);
114
        $this->expectException('required_capability_exception');
115
        $assign->reveal_identities();
116
        $student->ignoresesskey = false;
117
 
118
        // Test teachers cannot reveal identities.
119
        $nopermission = false;
120
        $teacher->ignoresesskey = true;
121
        $this->setUser($teacher);
122
        $this->expectException('required_capability_exception');
123
        $assign->reveal_identities();
124
        $teacher->ignoresesskey = false;
125
 
126
        // Test sesskey is required.
127
        $this->setUser($teacher);
128
        $this->expectException('moodle_exception');
129
        $assign->reveal_identities();
130
 
131
        // Test editingteacher can reveal identities if sesskey is ignored.
132
        $teacher->ignoresesskey = true;
133
        $this->setUser($teacher);
134
        $assign->reveal_identities();
135
        $this->assertEquals(false, $assign->is_blind_marking());
136
        $teacher->ignoresesskey = false;
137
 
138
        // Test student names are visible.
139
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
140
        $output = $assign->get_renderer()->render($gradingtable);
141
        $this->assertEquals(false, strpos($output, get_string('hiddenuser', 'assign')));
142
 
143
        // Set this back to default.
144
        $teacher->ignoresesskey = false;
145
    }
146
 
147
    /**
148
     * Data provider for test_get_assign_perpage
149
     *
1441 ariadna 150
     * @return array[] Provider data
1 efrain 151
     */
1441 ariadna 152
    public static function get_assign_perpage_provider(): array {
153
        return [
154
            [
1 efrain 155
                'maxperpage' => -1,
1441 ariadna 156
                'userprefs' => [
1 efrain 157
                    -1 => -1,
158
                    10 => 10,
159
                    20 => 20,
160
                    50 => 50,
1441 ariadna 161
                ],
162
            ],
163
            [
1 efrain 164
                'maxperpage' => 15,
1441 ariadna 165
                'userprefs' => [
1 efrain 166
                    -1 => 15,
167
                    10 => 10,
168
                    20 => 15,
169
                    50 => 15,
1441 ariadna 170
                ],
171
            ],
172
        ];
1 efrain 173
    }
174
 
175
    /**
176
     * Test maxperpage
177
     *
178
     * @dataProvider get_assign_perpage_provider
179
     * @param integer $maxperpage site config value
180
     * @param array $userprefs Array of user preferences and expected page sizes
181
     */
11 efrain 182
    public function test_get_assign_perpage($maxperpage, $userprefs): void {
1 efrain 183
        $this->resetAfterTest();
184
        $course = $this->getDataGenerator()->create_course();
185
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
186
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
187
 
188
        $this->setUser($teacher);
189
        $assign = $this->create_instance($course);
190
 
191
        set_config('maxperpage', $maxperpage, 'assign');
192
        set_user_preference('assign_perpage', null);
193
        $this->assertEquals(10, $assign->get_assign_perpage());
194
        foreach ($userprefs as $pref => $perpage) {
195
            set_user_preference('assign_perpage', $pref);
196
            $this->assertEquals($perpage, $assign->get_assign_perpage());
197
        }
198
    }
199
 
200
    /**
201
     * Test filter by requires grading.
202
     *
203
     * This is specifically checking an assignment with no grade to make sure we do not
204
     * get an exception thrown when rendering the grading table for this type of assignment.
205
     */
11 efrain 206
    public function test_gradingtable_filter_by_requiresgrading_no_grade(): void {
1 efrain 207
        global $PAGE;
208
 
209
        $this->resetAfterTest();
210
 
211
        $course = $this->getDataGenerator()->create_course();
212
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
213
        $this->setUser($teacher);
214
        $assign = $this->create_instance($course, [
215
                'assignsubmission_onlinetext_enabled' => 1,
216
                'assignfeedback_comments_enabled' => 0,
1441 ariadna 217
                'grade' => GRADE_TYPE_NONE,
1 efrain 218
            ]);
219
 
1441 ariadna 220
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', [
1 efrain 221
            'id' => $assign->get_course_module()->id,
222
            'action' => 'grading',
1441 ariadna 223
        ]));
1 efrain 224
 
225
        // Render the table with the requires grading filter.
226
        $gradingtable = new \assign_grading_table($assign, 1, ASSIGN_FILTER_REQUIRE_GRADING, 0, true);
227
        $output = $assign->get_renderer()->render($gradingtable);
228
 
229
        // Test that the filter function does not throw errors for assignments with no grade.
230
        $this->assertStringContainsString(get_string('nothingtodisplay'), $output);
231
    }
232
 
233
 
234
    /**
235
     * Test submissions with extension date.
236
     */
11 efrain 237
    public function test_gradingtable_extension_due_date(): void {
1 efrain 238
        global $PAGE;
239
 
240
        $this->resetAfterTest();
241
        $course = $this->getDataGenerator()->create_course();
242
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
243
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
244
 
245
        // Setup the assignment.
246
        $this->setUser($teacher);
247
        $time = time();
248
        $assign = $this->create_instance($course, [
249
                'assignsubmission_onlinetext_enabled' => 1,
250
                'duedate' => time() - (4 * DAYSECS),
251
            ]);
1441 ariadna 252
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', [
1 efrain 253
            'id' => $assign->get_course_module()->id,
254
            'action' => 'grading',
1441 ariadna 255
        ]));
1 efrain 256
 
257
        // Check that the assignment is late.
258
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
259
        $output = $assign->get_renderer()->render($gradingtable);
260
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
261
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output);
262
 
263
        // Grant an extension.
264
        $extendedtime = $time + (2 * DAYSECS);
265
        $assign->testable_save_user_extension($student->id, $extendedtime);
266
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
267
        $output = $assign->get_renderer()->render($gradingtable);
268
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
269
        $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
270
 
271
        // Simulate a submission.
272
        $this->setUser($student);
273
        $submission = $assign->get_user_submission($student->id, true);
274
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
275
        $assign->testable_update_submission($submission, $student->id, true, false);
276
        $data = new \stdClass();
277
        $data->onlinetext_editor = [
278
            'itemid' => file_get_unused_draft_itemid(),
279
            'text' => 'Submission text',
280
            'format' => FORMAT_MOODLE,
281
        ];
282
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
283
        $plugin->save($submission, $data);
284
 
285
        // Verify output.
286
        $this->setUser($teacher);
287
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
288
        $output = $assign->get_renderer()->render($gradingtable);
289
        $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
290
        $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
291
    }
292
 
293
    /**
294
     * Test that late submissions with extension date calculate correctly.
295
     */
11 efrain 296
    public function test_gradingtable_extension_date_calculation_for_lateness(): void {
1 efrain 297
        global $PAGE;
298
 
299
        $this->resetAfterTest();
300
        $course = $this->getDataGenerator()->create_course();
301
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
302
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
303
 
304
        // Setup the assignment.
305
        $this->setUser($teacher);
306
        $time = time();
307
        $assign = $this->create_instance($course, [
308
                'assignsubmission_onlinetext_enabled' => 1,
309
                'duedate' => time() - (4 * DAYSECS),
310
            ]);
1441 ariadna 311
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', [
1 efrain 312
            'id' => $assign->get_course_module()->id,
313
            'action' => 'grading',
1441 ariadna 314
        ]));
1 efrain 315
 
316
        // Check that the assignment is late.
317
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
318
        $output = $assign->get_renderer()->render($gradingtable);
319
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
320
        $difftime = time() - $time;
321
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
322
 
323
        // Grant an extension that is in the past.
324
        $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS));
325
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
326
        $output = $assign->get_renderer()->render($gradingtable);
327
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
328
        $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
329
        $difftime = time() - $time;
330
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
331
 
332
        // Simulate a submission.
333
        $this->setUser($student);
334
        $submission = $assign->get_user_submission($student->id, true);
335
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
336
        $assign->testable_update_submission($submission, $student->id, true, false);
337
        $data = new \stdClass();
338
        $data->onlinetext_editor = [
339
            'itemid' => file_get_unused_draft_itemid(),
340
            'text' => 'Submission text',
341
            'format' => FORMAT_MOODLE,
342
        ];
343
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
344
        $plugin->save($submission, $data);
345
        $submittedtime = time();
346
 
347
        // Verify output.
348
        $this->setUser($teacher);
349
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
350
        $output = $assign->get_renderer()->render($gradingtable);
351
        $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
352
        $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
353
 
354
        $difftime = $submittedtime - $time;
1441 ariadna 355
        $this->assertStringContainsString(
356
            get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)),
357
            $output
358
        );
1 efrain 359
    }
360
 
11 efrain 361
    public function test_gradingtable_status_rendering(): void {
1 efrain 362
        global $PAGE;
363
 
364
        $this->resetAfterTest();
365
        $course = $this->getDataGenerator()->create_course();
366
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
367
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
368
 
369
        // Setup the assignment.
370
        $this->setUser($teacher);
371
        $time = time();
372
        $assign = $this->create_instance($course, [
373
            'assignsubmission_onlinetext_enabled' => 1,
374
            'duedate' => $time - (4 * DAYSECS),
375
         ]);
1441 ariadna 376
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', [
1 efrain 377
            'id' => $assign->get_course_module()->id,
378
            'action' => 'grading',
1441 ariadna 379
        ]));
1 efrain 380
 
381
        // Check that the assignment is late.
382
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
383
        $output = $assign->get_renderer()->render($gradingtable);
384
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
385
        $difftime = time() - $time;
386
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
387
 
388
        // Simulate a student viewing the assignment without submitting.
389
        $this->setUser($student);
390
        $submission = $assign->get_user_submission($student->id, true);
391
        $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
392
        $assign->testable_update_submission($submission, $student->id, true, false);
393
        $submittedtime = time();
394
 
395
        // Verify output.
396
        $this->setUser($teacher);
397
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
398
        $output = $assign->get_renderer()->render($gradingtable);
399
        $difftime = $submittedtime - $time;
400
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
401
 
402
        $document = new \DOMDocument();
403
        @$document->loadHTML($output);
404
        $xpath = new \DOMXPath($document);
1441 ariadna 405
        $this->assertEmpty($xpath->evaluate('string(//td[@id="mod_assign_grading-' . $assign->get_context()->id . '_r0_c6"])'));
1 efrain 406
    }
407
 
408
    /**
409
     * Check that group submission information is rendered correctly in the
410
     * grading table.
411
     */
11 efrain 412
    public function test_gradingtable_group_submissions_rendering(): void {
1 efrain 413
        global $PAGE;
414
 
415
        $this->resetAfterTest();
416
        $course = $this->getDataGenerator()->create_course();
417
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
418
 
419
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
420
        groups_add_member($group, $teacher);
421
 
422
        $students = [];
423
 
424
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
425
        $students[] = $student;
426
        groups_add_member($group, $student);
427
 
428
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
429
        $students[] = $student;
430
        groups_add_member($group, $student);
431
 
432
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
433
        $students[] = $student;
434
        groups_add_member($group, $student);
435
 
436
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
437
        $students[] = $student;
438
        groups_add_member($group, $student);
439
 
440
        // Verify group assignments.
441
        $this->setUser($teacher);
442
        $assign = $this->create_instance($course, [
443
            'teamsubmission' => 1,
444
            'assignsubmission_onlinetext_enabled' => 1,
445
            'submissiondrafts' => 1,
446
            'requireallteammemberssubmit' => 0,
447
        ]);
1441 ariadna 448
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', [
1 efrain 449
            'id' => $assign->get_course_module()->id,
450
            'action' => 'grading',
1441 ariadna 451
        ]));
1 efrain 452
 
453
        // Add a submission.
454
        $this->setUser($student);
455
        $data = new \stdClass();
456
        $data->onlinetext_editor = [
457
            'itemid' => file_get_unused_draft_itemid(),
458
            'text' => 'Submission text',
459
            'format' => FORMAT_MOODLE,
460
        ];
1441 ariadna 461
        $notices = [];
1 efrain 462
        $assign->save_submission($data, $notices);
463
 
464
        $submission = $assign->get_group_submission($student->id, 0, true);
465
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
466
        $assign->testable_update_submission($submission, $student->id, true, true);
467
 
468
        // Check output.
469
        $this->setUser($teacher);
470
        $gradingtable = new \assign_grading_table($assign, 4, '', 0, true);
471
        $output = $assign->get_renderer()->render($gradingtable);
472
        $document = new \DOMDocument();
473
        @$document->loadHTML($output);
474
        $xpath = new \DOMXPath($document);
475
 
476
        // The XPath expression is based on the unique ID of the table.
477
        $xpathuniqueidroot = 'mod_assign_grading-' . $assign->get_context()->id;
478
 
479
        // Check status.
1441 ariadna 480
        $this->assertSame(
481
            get_string('submissionstatus_submitted', 'assign'),
482
            $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c3"]//div[@class="submissionstatussubmitted"])')
483
        );
484
        $this->assertSame(
485
            get_string('submissionstatus_submitted', 'assign'),
486
            $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c3"]//div[@class="submissionstatussubmitted"])')
487
        );
1 efrain 488
 
489
        // Check submission last modified date.
1441 ariadna 490
        $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c6"])')));
491
        $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c6"])')));
1 efrain 492
 
493
        // Check group.
1441 ariadna 494
        $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c4"])'));
495
        $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c4"])'));
1 efrain 496
 
497
        // Check submission text.
1441 ariadna 498
        $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c7"]/div/div)'));
499
        $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c7"]/div/div)'));
1 efrain 500
 
501
        // Check comments can be made.
1441 ariadna 502
        $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r0_c8"]//textarea)'));
503
        $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r3_c8"]//textarea)'));
1 efrain 504
    }
505
 
11 efrain 506
    public function test_show_intro(): void {
1 efrain 507
        $this->resetAfterTest();
508
        $course = $this->getDataGenerator()->create_course();
509
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
510
 
511
        // Test whether we are showing the intro at the correct times.
512
        $this->setUser($teacher);
513
        $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]);
514
 
515
        $this->assertEquals(true, $assign->testable_show_intro());
516
 
517
        $tomorrow = time() + DAYSECS;
518
 
519
        $assign = $this->create_instance($course, [
520
                'alwaysshowdescription' => 0,
521
                'allowsubmissionsfromdate' => $tomorrow,
522
            ]);
523
        $this->assertEquals(false, $assign->testable_show_intro());
524
        $yesterday = time() - DAYSECS;
525
        $assign = $this->create_instance($course, [
526
                'alwaysshowdescription' => 0,
527
                'allowsubmissionsfromdate' => $yesterday,
528
            ]);
529
        $this->assertEquals(true, $assign->testable_show_intro());
530
    }
531
 
11 efrain 532
    public function test_has_submissions_or_grades(): void {
1 efrain 533
        $this->resetAfterTest();
534
        $course = $this->getDataGenerator()->create_course();
535
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
536
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
537
 
538
        $this->setUser($teacher);
539
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
540
        $instance = $assign->get_instance();
541
 
542
        // Should start empty.
543
        $this->assertEquals(false, $assign->has_submissions_or_grades());
544
 
545
        // Simulate a submission.
546
        $this->setUser($student);
547
        $submission = $assign->get_user_submission($student->id, true);
548
 
549
        // The submission is still new.
550
        $this->assertEquals(false, $assign->has_submissions_or_grades());
551
 
552
        // Submit the submission.
553
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
554
        $assign->testable_update_submission($submission, $student->id, true, false);
555
        $data = new \stdClass();
1441 ariadna 556
        $data->onlinetext_editor = [
1 efrain 557
            'itemid' => file_get_unused_draft_itemid(),
558
            'text' => 'Submission text',
1441 ariadna 559
            'format' => FORMAT_MOODLE];
1 efrain 560
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
561
        $plugin->save($submission, $data);
562
 
563
        // Now test again.
564
        $this->assertEquals(true, $assign->has_submissions_or_grades());
565
    }
566
 
11 efrain 567
    public function test_delete_grades(): void {
1 efrain 568
        $this->resetAfterTest();
569
        $course = $this->getDataGenerator()->create_course();
570
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
571
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
572
 
573
        $this->setUser($teacher);
574
        $assign = $this->create_instance($course);
575
 
576
        // Simulate adding a grade.
577
        $this->setUser($teacher);
578
        $data = new \stdClass();
579
        $data->grade = '50.0';
580
        $assign->testable_apply_grade_to_user($data, $student->id, 0);
581
 
582
        // Now see if the data is in the gradebook.
583
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
584
 
585
        $this->assertNotEquals(0, count($gradinginfo->items));
586
 
587
        $assign->testable_delete_grades();
588
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
589
 
590
        $this->assertEquals(0, count($gradinginfo->items));
591
    }
592
 
11 efrain 593
    public function test_delete_instance(): void {
1 efrain 594
        $this->resetAfterTest();
595
        $course = $this->getDataGenerator()->create_course();
596
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
597
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
598
 
599
        $this->setUser($teacher);
600
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
601
 
602
        // Simulate adding a grade.
603
        $this->setUser($teacher);
604
        $data = new \stdClass();
605
        $data->grade = '50.0';
606
        $assign->testable_apply_grade_to_user($data, $student->id, 0);
607
 
608
        // Simulate a submission.
609
        $this->add_submission($student, $assign);
610
 
611
        // Now try and delete.
612
        $this->setUser($teacher);
613
        $this->assertEquals(true, $assign->delete_instance());
614
    }
615
 
1441 ariadna 616
    /**
617
     * @covers ::assign_reset_userdata
618
     */
11 efrain 619
    public function test_reset_userdata(): void {
1 efrain 620
        global $DB;
621
 
622
        $this->resetAfterTest();
623
        $course = $this->getDataGenerator()->create_course();
624
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
625
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
626
 
627
        $now = time();
628
        $this->setUser($teacher);
629
        $assign = $this->create_instance($course, [
1441 ariadna 630
            'assignsubmission_onlinetext_enabled' => 1,
631
            'allowsubmissionsfromdate' => $now - HOURSECS,
632
            'duedate' => $now,
633
            'cutoffdate' => $now + HOURSECS,
634
            'gradingduedate' => $now + DAYSECS,
635
        ]);
1 efrain 636
 
637
        // Simulate adding a grade.
638
        $this->add_submission($student, $assign);
639
        $this->submit_for_grading($student, $assign);
640
        $this->mark_submission($teacher, $assign, $student, 50.0);
641
 
642
        // Simulate a submission.
643
        $this->setUser($student);
644
        $submission = $assign->get_user_submission($student->id, true);
645
        $data = new \stdClass();
1441 ariadna 646
        $data->onlinetext_editor = [
1 efrain 647
            'itemid' => file_get_unused_draft_itemid(),
648
            'text' => 'Submission text',
1441 ariadna 649
            'format' => FORMAT_MOODLE];
1 efrain 650
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
651
        $plugin->save($submission, $data);
652
 
653
        $this->assertEquals(true, $assign->has_submissions_or_grades());
654
        // Now try and reset.
655
        $data = new \stdClass();
656
        $data->reset_assign_submissions = 1;
657
        $data->reset_gradebook_grades = 1;
658
        $data->reset_assign_user_overrides = 1;
659
        $data->reset_assign_group_overrides = 1;
660
        $data->courseid = $course->id;
661
        $data->timeshift = DAYSECS;
662
        $this->setUser($teacher);
663
        $assign->reset_userdata($data);
664
        $this->assertEquals(false, $assign->has_submissions_or_grades());
665
 
666
        // Reload the instance data.
1441 ariadna 667
        $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
668
        $this->assertEquals($now - HOURSECS + DAYSECS, $instance->allowsubmissionsfromdate);
1 efrain 669
        $this->assertEquals($now + DAYSECS, $instance->duedate);
1441 ariadna 670
        $this->assertEquals($now + HOURSECS + DAYSECS, $instance->cutoffdate);
671
        $this->assertEquals($now + DAYSECS + DAYSECS, $instance->gradingduedate);
1 efrain 672
 
673
        // Test reset using assign_reset_userdata().
674
        $assignduedate = $instance->duedate; // Keep old updated value for comparison.
675
        $data->timeshift = (2 * DAYSECS);
676
        assign_reset_userdata($data);
1441 ariadna 677
        $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
1 efrain 678
        $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate);
679
 
680
        // Create one more assignment and reset, make sure time shifted for previous assignment is not changed.
681
        $assign2 = $this->create_instance($course, [
682
                'assignsubmission_onlinetext_enabled' => 1,
683
                'duedate' => $now,
684
            ]);
685
        $assignduedate = $instance->duedate;
686
        $data->timeshift = 3 * DAYSECS;
687
        $assign2->reset_userdata($data);
1441 ariadna 688
        $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
1 efrain 689
        $this->assertEquals($assignduedate, $instance->duedate);
1441 ariadna 690
        $instance2 = $DB->get_record('assign', ['id' => $assign2->get_instance()->id]);
1 efrain 691
        $this->assertEquals($now + 3 * DAYSECS, $instance2->duedate);
692
 
693
        // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date.
694
        $assignduedate = $instance->duedate;
695
        $assign2duedate = $instance2->duedate;
696
        $data->timeshift = (4 * DAYSECS);
697
        assign_reset_userdata($data);
1441 ariadna 698
        $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
1 efrain 699
        $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate);
1441 ariadna 700
        $instance2 = $DB->get_record('assign', ['id' => $assign2->get_instance()->id]);
1 efrain 701
        $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate);
702
    }
703
 
11 efrain 704
    public function test_plugin_settings(): void {
1 efrain 705
        global $DB;
706
 
707
        $this->resetAfterTest();
708
 
709
        $course = $this->getDataGenerator()->create_course();
710
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
711
 
712
        $now = time();
713
        $this->setUser($teacher);
714
        $assign = $this->create_instance($course, [
715
                'assignsubmission_file_enabled' => 1,
716
                'assignsubmission_file_maxfiles' => 12,
717
                'assignsubmission_file_maxsizebytes' => 10,
718
            ]);
719
 
720
        $plugin = $assign->get_submission_plugin_by_type('file');
721
        $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
722
    }
723
 
11 efrain 724
    public function test_update_calendar(): void {
1 efrain 725
        global $DB;
726
 
727
        $this->resetAfterTest();
728
 
729
        $course = $this->getDataGenerator()->create_course();
730
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
731
 
732
        $this->setUser($teacher);
733
        $userctx = \context_user::instance($teacher->id)->id;
734
 
735
        // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey.
736
        $draftid = file_get_unused_draft_itemid();
737
        $_REQUEST['introeditor'] = $draftid;
738
        $_POST['introeditor'] = $draftid;
739
        $_POST['sesskey'] = sesskey();
740
 
741
        // Write links to a draft area.
1441 ariadna 742
        $fakearealink1 = file_rewrite_pluginfile_urls(
743
            '<a href="@@PLUGINFILE@@/pic.gif">link</a>',
744
            'draftfile.php',
745
            $userctx,
746
            'user',
747
            'draft',
748
            $draftid
749
        );
750
        $fakearealink2 = file_rewrite_pluginfile_urls(
751
            '<a href="@@PLUGINFILE@@/pic.gif">new</a>',
752
            'draftfile.php',
753
            $userctx,
754
            'user',
755
            'draft',
756
            $draftid
757
        );
1 efrain 758
 
759
        // Create a new \assignment with links to a draft area.
760
        $now = time();
761
        $assign = $this->create_instance($course, [
762
                'duedate' => $now,
763
                'intro' => $fakearealink1,
1441 ariadna 764
                'introformat' => FORMAT_HTML,
1 efrain 765
            ]);
766
 
767
        // See if there is an event in the calendar.
1441 ariadna 768
        $params = ['modulename' => 'assign', 'instance' => $assign->get_instance()->id];
1 efrain 769
        $event = $DB->get_record('event', $params);
770
        $this->assertNotEmpty($event);
771
        $this->assertSame('link', $event->description);     // The pluginfile links are removed.
772
 
773
        // Make sure the same works when updating the assignment.
774
        $instance = $assign->get_instance();
775
        $instance->instance = $instance->id;
776
        $instance->intro = $fakearealink2;
777
        $instance->introformat = FORMAT_HTML;
778
        $assign->update_instance($instance);
1441 ariadna 779
        $params = ['modulename' => 'assign', 'instance' => $assign->get_instance()->id];
1 efrain 780
        $event = $DB->get_record('event', $params);
781
        $this->assertNotEmpty($event);
782
        $this->assertSame('new', $event->description);     // The pluginfile links are removed.
783
 
784
        // Create an assignment with a description that should be hidden.
785
        $assign = $this->create_instance($course, [
786
                'duedate' => $now + 160,
787
                'alwaysshowdescription' => false,
788
                'allowsubmissionsfromdate' => $now + 60,
789
                'intro' => 'Some text',
790
            ]);
791
 
792
        // Get the event from the calendar.
1441 ariadna 793
        $params = ['modulename' => 'assign', 'instance' => $assign->get_instance()->id];
1 efrain 794
        $event = $DB->get_record('event', [
795
            'modulename' => 'assign',
796
            'instance' => $assign->get_instance()->id,
797
        ]);
798
 
799
        $this->assertEmpty($event->description);
800
 
801
        // Change the allowsubmissionfromdate to the past - do this directly in the DB
802
        // because if we call the assignment update method - it will update the calendar
803
        // and we want to test that this works from cron.
1441 ariadna 804
        $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, ['id' => $assign->get_instance()->id]);
1 efrain 805
        // Run cron to update the event in the calendar.
806
        \assign::cron();
807
        $event = $DB->get_record('event', $params);
808
 
809
        $this->assertStringContainsString('Some text', $event->description);
810
    }
811
 
11 efrain 812
    public function test_update_instance(): void {
1 efrain 813
        global $DB;
814
 
815
        $this->resetAfterTest();
816
 
817
        $course = $this->getDataGenerator()->create_course();
818
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
819
 
820
        $this->setUser($teacher);
821
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
822
 
823
        $now = time();
824
        $instance = $assign->get_instance();
825
        $instance->duedate = $now;
826
        $instance->instance = $instance->id;
827
        $instance->assignsubmission_onlinetext_enabled = 1;
828
 
829
        $assign->update_instance($instance);
830
 
831
        $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
832
        $this->assertEquals($now, $instance->duedate);
833
    }
834
 
11 efrain 835
    public function test_cannot_submit_empty(): void {
1 efrain 836
        global $PAGE;
837
 
838
        $this->resetAfterTest();
839
 
840
        $course = $this->getDataGenerator()->create_course();
841
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
842
 
843
        $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
844
 
845
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
846
 
847
        // Test you cannot see the submit button for an offline assignment regardless.
848
        $this->setUser($student);
849
        $output = $assign->view_student_summary($student, true);
1441 ariadna 850
        $this->assertStringNotContainsString(
851
            get_string('submitassignment', 'assign'),
852
            $output,
853
            'Can submit empty offline assignment'
854
        );
1 efrain 855
    }
856
 
11 efrain 857
    public function test_cannot_submit_empty_no_submission(): void {
1 efrain 858
        global $PAGE;
859
 
860
        $this->resetAfterTest();
861
 
862
        $course = $this->getDataGenerator()->create_course();
863
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
864
 
865
        $assign = $this->create_instance($course, [
866
            'submissiondrafts' => 1,
867
            'assignsubmission_onlinetext_enabled' => 1,
868
        ]);
869
 
870
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
871
 
872
        // Test you cannot see the submit button for an online text assignment with no submission.
873
        $this->setUser($student);
874
        $output = $assign->view_student_summary($student, true);
1441 ariadna 875
        $this->assertStringNotContainsString(
876
            get_string('submitassignment', 'assign'),
877
            $output,
878
            'Cannot submit empty onlinetext assignment'
879
        );
1 efrain 880
    }
881
 
11 efrain 882
    public function test_can_submit_with_submission(): void {
1 efrain 883
        global $PAGE;
884
 
885
        $this->resetAfterTest();
886
 
887
        $course = $this->getDataGenerator()->create_course();
888
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
889
 
890
        $assign = $this->create_instance($course, [
891
            'submissiondrafts' => 1,
892
            'assignsubmission_onlinetext_enabled' => 1,
893
        ]);
894
 
895
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
896
 
897
        // Add a draft.
898
        $this->add_submission($student, $assign);
899
 
900
        // Test you can see the submit button for an online text assignment with a submission.
901
        $this->setUser($student);
902
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
1441 ariadna 903
        $this->assertStringContainsString(
904
            get_string('submitassignment', 'assign'),
905
            $output,
906
            'Can submit non empty onlinetext assignment'
907
        );
1 efrain 908
    }
909
 
910
    /**
911
     * Test new_submission_empty
912
     *
913
     * We only test combinations of plugins here. Individual plugins are tested
914
     * in their respective test files.
915
     *
916
     * @dataProvider new_submission_empty_testcases
917
     * @param string $data The file submission data
918
     * @param bool $expected The expected return value
919
     */
11 efrain 920
    public function test_new_submission_empty($data, $expected): void {
1 efrain 921
        $this->resetAfterTest();
922
 
923
        $course = $this->getDataGenerator()->create_course();
924
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
925
 
926
        $assign = $this->create_instance($course, [
927
                'assignsubmission_file_enabled' => 1,
928
                'assignsubmission_file_maxfiles' => 12,
929
                'assignsubmission_file_maxsizebytes' => 10,
930
                'assignsubmission_onlinetext_enabled' => 1,
931
            ]);
932
        $this->setUser($student);
933
        $submission = new \stdClass();
934
 
935
        if ($data['file'] && isset($data['file']['filename'])) {
936
            $itemid = file_get_unused_draft_itemid();
937
            $submission->files_filemanager = $itemid;
938
            $data['file'] += ['contextid' => \context_user::instance($student->id)->id, 'itemid' => $itemid];
939
            $fs = get_file_storage();
940
            $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
941
        }
942
 
943
        if ($data['onlinetext']) {
944
            $submission->onlinetext_editor = ['text' => $data['onlinetext']];
945
        }
946
 
947
        $result = $assign->new_submission_empty($submission);
948
        $this->assertTrue($result === $expected);
949
    }
950
 
951
    /**
952
     * Dataprovider for the test_new_submission_empty testcase
953
     *
1441 ariadna 954
     * @return array[] An array of testcases
1 efrain 955
     */
1441 ariadna 956
    public static function new_submission_empty_testcases(): array {
1 efrain 957
        return [
958
            'With file and onlinetext' => [
959
                [
960
                    'file' => [
961
                        'component' => 'user',
962
                        'filearea' => 'draft',
963
                        'filepath' => '/',
1441 ariadna 964
                        'filename' => 'not_a_virus.exe',
1 efrain 965
                    ],
1441 ariadna 966
                    'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu',
1 efrain 967
                ],
1441 ariadna 968
                false,
969
            ],
1 efrain 970
        ];
971
    }
972
 
11 efrain 973
    public function test_list_participants(): void {
1 efrain 974
        global $CFG;
975
 
976
        $this->resetAfterTest();
977
 
978
        $course = $this->getDataGenerator()->create_course();
979
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
980
 
981
        // Create 10 students.
982
        for ($i = 0; $i < 10; $i++) {
983
            $this->getDataGenerator()->create_and_enrol($course, 'student');
984
        }
985
 
986
        $this->setUser($teacher);
987
        $assign = $this->create_instance($course, ['grade' => 100]);
988
 
989
        $this->assertCount(10, $assign->list_participants(null, true));
990
    }
991
 
11 efrain 992
    public function test_list_participants_activeenrol(): void {
1 efrain 993
        global $CFG, $DB;
994
 
995
        $this->resetAfterTest();
996
 
997
        $course = $this->getDataGenerator()->create_course();
998
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
999
 
1000
        // Create 10 students.
1001
        for ($i = 0; $i < 10; $i++) {
1002
            $this->getDataGenerator()->create_and_enrol($course, 'student');
1003
        }
1004
 
1005
        // Create 10 suspended students.
1006
        for ($i = 0; $i < 10; $i++) {
1007
            $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1008
        }
1009
 
1010
        $this->setUser($teacher);
1011
        set_user_preference('grade_report_showonlyactiveenrol', false);
1012
        $assign = $this->create_instance($course, ['grade' => 100]);
1013
 
1014
        $this->assertCount(10, $assign->list_participants(null, true));
1015
    }
1016
 
11 efrain 1017
    public function test_list_participants_with_group_restriction(): void {
1 efrain 1018
        global $CFG;
1019
 
1020
        $this->resetAfterTest();
1021
 
1022
        $course = $this->getDataGenerator()->create_course();
1023
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1024
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1025
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1026
        $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1027
 
1028
        // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group.
1029
        $CFG->enableavailability = true;
1030
 
1031
        $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1032
        $assign = $this->create_instance($course, [
1033
            'grade' => 100,
1034
            'availability' => json_encode(
1035
                \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
1036
            ),
1037
        ]);
1038
 
1039
        groups_add_member($specialgroup, $student);
1040
        groups_add_member($specialgroup, $otherstudent);
1041
        $this->assertEquals(2, count($assign->list_participants(null, true)));
1042
    }
1043
 
11 efrain 1044
    public function test_get_participant_user_not_exist(): void {
1 efrain 1045
        $this->resetAfterTest();
1046
        $course = $this->getDataGenerator()->create_course();
1047
 
1048
        $assign = $this->create_instance($course);
1049
        $this->assertNull($assign->get_participant('-1'));
1050
    }
1051
 
11 efrain 1052
    public function test_get_participant_not_enrolled(): void {
1 efrain 1053
        $this->resetAfterTest();
1054
        $course = $this->getDataGenerator()->create_course();
1055
        $assign = $this->create_instance($course);
1056
 
1057
        $user = $this->getDataGenerator()->create_user();
1058
        $this->assertNull($assign->get_participant($user->id));
1059
    }
1060
 
11 efrain 1061
    public function test_get_participant_no_submission(): void {
1 efrain 1062
        $this->resetAfterTest();
1063
        $course = $this->getDataGenerator()->create_course();
1064
        $assign = $this->create_instance($course);
1065
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1066
 
1067
        $participant = $assign->get_participant($student->id);
1068
 
1069
        $this->assertEquals($student->id, $participant->id);
1070
        $this->assertFalse($participant->submitted);
1071
        $this->assertFalse($participant->requiregrading);
1072
        $this->assertFalse($participant->grantedextension);
1073
    }
1074
 
11 efrain 1075
    public function test_get_participant_granted_extension(): void {
1 efrain 1076
        $this->resetAfterTest();
1077
        $course = $this->getDataGenerator()->create_course();
1078
        $assign = $this->create_instance($course);
1079
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1080
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1081
 
1082
        $this->setUser($teacher);
1083
        $assign->save_user_extension($student->id, time());
1084
        $participant = $assign->get_participant($student->id);
1085
 
1086
        $this->assertEquals($student->id, $participant->id);
1087
        $this->assertFalse($participant->submitted);
1088
        $this->assertFalse($participant->requiregrading);
1089
        $this->assertTrue($participant->grantedextension);
1090
    }
1091
 
11 efrain 1092
    public function test_get_participant_with_ungraded_submission(): void {
1 efrain 1093
        $this->resetAfterTest();
1094
        $course = $this->getDataGenerator()->create_course();
1095
        $assign = $this->create_instance($course);
1096
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1097
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1098
 
1099
        // Simulate a submission.
1100
        $this->add_submission($student, $assign);
1101
        $this->submit_for_grading($student, $assign);
1102
 
1103
        $participant = $assign->get_participant($student->id);
1104
 
1105
        $this->assertEquals($student->id, $participant->id);
1106
        $this->assertTrue($participant->submitted);
1107
        $this->assertTrue($participant->requiregrading);
1108
        $this->assertFalse($participant->grantedextension);
1109
    }
1110
 
1111
    /**
1112
     * Tests that if a student with no submission who can no longer submit is not a participant.
1113
     */
11 efrain 1114
    public function test_get_participant_with_no_submission_no_capability(): void {
1 efrain 1115
        global $DB;
1116
        $this->resetAfterTest();
1117
        $course = self::getDataGenerator()->create_course();
1118
        $coursecontext = \context_course::instance($course->id);
1119
        $assign = $this->create_instance($course);
1120
        $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher');
1121
        $student = self::getDataGenerator()->create_and_enrol($course, 'student');
1122
 
1123
        // Remove the students capability to submit.
1124
        $role = $DB->get_field('role', 'id', ['shortname' => 'student']);
1125
        assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext);
1126
 
1127
        $participant = $assign->get_participant($student->id);
1128
 
1129
        self::assertNull($participant);
1130
    }
1131
 
1132
    /**
1133
     * Tests that if a student that has submitted but can no longer submit is a participant.
1134
     */
11 efrain 1135
    public function test_get_participant_with_submission_no_capability(): void {
1 efrain 1136
        global $DB;
1137
        $this->resetAfterTest();
1138
        $course = self::getDataGenerator()->create_course();
1139
        $coursecontext = \context_course::instance($course->id);
1140
        $assign = $this->create_instance($course);
1141
        $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher');
1142
        $student = self::getDataGenerator()->create_and_enrol($course, 'student');
1143
 
1144
        // Simulate a submission.
1145
        $this->add_submission($student, $assign);
1146
        $this->submit_for_grading($student, $assign);
1147
 
1148
        // Remove the students capability to submit.
1149
        $role = $DB->get_field('role', 'id', ['shortname' => 'student']);
1150
        assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext);
1151
 
1152
        $participant = $assign->get_participant($student->id);
1153
 
1154
        self::assertNotNull($participant);
1155
        self::assertEquals($student->id, $participant->id);
1156
        self::assertTrue($participant->submitted);
1157
        self::assertTrue($participant->requiregrading);
1158
        self::assertFalse($participant->grantedextension);
1159
    }
1160
 
11 efrain 1161
    public function test_get_participant_with_graded_submission(): void {
1 efrain 1162
        $this->resetAfterTest();
1163
        $course = $this->getDataGenerator()->create_course();
1164
        $assign = $this->create_instance($course);
1165
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1166
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1167
 
1168
        // Simulate a submission.
1169
        $this->add_submission($student, $assign);
1170
        $this->submit_for_grading($student, $assign);
1171
 
1172
        $this->mark_submission($teacher, $assign, $student, 50.0);
1173
 
1174
        $data = new \stdClass();
1175
        $data->grade = '50.0';
1176
        $assign->testable_apply_grade_to_user($data, $student->id, 0);
1177
 
1178
        $participant = $assign->get_participant($student->id);
1179
 
1180
        $this->assertEquals($student->id, $participant->id);
1181
        $this->assertTrue($participant->submitted);
1182
        $this->assertFalse($participant->requiregrading);
1183
        $this->assertFalse($participant->grantedextension);
1184
    }
1185
 
1186
    /**
1187
     * No active group and non-group submissions disallowed => 2 groups.
1188
     */
11 efrain 1189
    public function test_count_teams_no_active_non_group_allowed(): void {
1 efrain 1190
        $this->resetAfterTest();
1191
 
1192
        $course = $this->getDataGenerator()->create_course();
1193
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1194
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1195
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1196
 
1441 ariadna 1197
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 1198
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1199
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1200
        groups_add_member($group1, $student1);
1201
        groups_add_member($group2, $student2);
1202
 
1203
        $this->setUser($teacher);
1204
        $assign = $this->create_instance($course, ['teamsubmission' => 1]);
1205
 
1206
        $this->assertEquals(2, $assign->count_teams());
1207
    }
1208
 
1209
    /**
1210
     * No active group and non group submissions allowed => 2 groups + the default one.
1211
     */
11 efrain 1212
    public function test_count_teams_non_group_allowed(): void {
1 efrain 1213
        $this->resetAfterTest();
1214
 
1215
        $course = $this->getDataGenerator()->create_course();
1216
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1217
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1218
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1219
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1220
 
1441 ariadna 1221
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 1222
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1223
 
1441 ariadna 1224
        $this->getDataGenerator()->create_grouping_group(['groupid' => $group1->id, 'groupingid' => $grouping->id]);
1 efrain 1225
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1441 ariadna 1226
        $this->getDataGenerator()->create_grouping_group(['groupid' => $group2->id, 'groupingid' => $grouping->id]);
1 efrain 1227
 
1228
        groups_add_member($group1, $student1);
1229
        groups_add_member($group2, $student2);
1230
 
1231
        $assign = $this->create_instance($course, [
1232
            'teamsubmission' => 1,
1233
            'teamsubmissiongroupingid' => $grouping->id,
1234
            'preventsubmissionnotingroup' => false,
1235
        ]);
1236
 
1237
        $this->setUser($teacher);
1238
        $this->assertEquals(3, $assign->count_teams());
1239
 
1240
        // Active group only.
1241
        $this->assertEquals(1, $assign->count_teams($group1->id));
1242
        $this->assertEquals(1, $assign->count_teams($group2->id));
1243
    }
1244
 
1245
    /**
1246
     * Active group => just selected one.
1247
     */
11 efrain 1248
    public function test_count_teams_no_active_group(): void {
1 efrain 1249
        $this->resetAfterTest();
1250
 
1251
        $course = $this->getDataGenerator()->create_course();
1252
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1253
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1254
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1255
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1256
 
1441 ariadna 1257
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 1258
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1259
 
1441 ariadna 1260
        $this->getDataGenerator()->create_grouping_group(['groupid' => $group1->id, 'groupingid' => $grouping->id]);
1 efrain 1261
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1441 ariadna 1262
        $this->getDataGenerator()->create_grouping_group(['groupid' => $group2->id, 'groupingid' => $grouping->id]);
1 efrain 1263
 
1264
        groups_add_member($group1, $student1);
1265
        groups_add_member($group2, $student2);
1266
 
1267
        $assign = $this->create_instance($course, [
1268
            'teamsubmission' => 1,
1269
            'preventsubmissionnotingroup' => true,
1270
        ]);
1271
 
1272
        $this->setUser($teacher);
1273
        $this->assertEquals(2, $assign->count_teams());
1274
 
1275
        // Active group only.
1276
        $this->assertEquals(1, $assign->count_teams($group1->id));
1277
        $this->assertEquals(1, $assign->count_teams($group2->id));
1278
    }
1279
 
1280
    /**
1281
     * Active group => just selected one.
1282
     */
11 efrain 1283
    public function test_count_teams_groups_only(): void {
1 efrain 1284
        $this->resetAfterTest();
1285
 
1286
        $course = $this->getDataGenerator()->create_course();
1441 ariadna 1287
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 1288
 
1289
        $assign = $this->create_instance($course, [
1290
            'teamsubmission' => 1,
1291
            'teamsubmissiongroupingid' => $grouping->id,
1292
            'preventsubmissionnotingroup' => false,
1293
        ]);
1294
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1295
 
1296
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1297
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1298
        groups_add_member($group1, $student1);
1299
 
1300
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1301
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1302
        groups_add_member($group2, $student2);
1303
 
1441 ariadna 1304
        $this->getDataGenerator()->create_grouping_group(['groupid' => $group1->id, 'groupingid' => $grouping->id]);
1305
        $this->getDataGenerator()->create_grouping_group(['groupid' => $group2->id, 'groupingid' => $grouping->id]);
1 efrain 1306
 
1307
        $this->setUser($teacher);
1308
 
1309
        $assign = $this->create_instance($course, [
1310
            'teamsubmission' => 1,
1311
            'preventsubmissionnotingroup' => true,
1312
        ]);
1313
        $this->assertEquals(2, $assign->count_teams());
1314
    }
1315
 
11 efrain 1316
    public function test_submit_to_default_group(): void {
1 efrain 1317
        global $DB, $SESSION;
1318
 
1319
        $this->resetAfterTest();
1320
 
1321
        $course = $this->getDataGenerator()->create_course();
1322
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1323
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1324
 
1325
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1326
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1327
 
1328
        $assign = $this->create_instance($course, [
1329
            'teamsubmission' => 1,
1330
            'assignsubmission_onlinetext_enabled' => 1,
1331
            'submissiondrafts' => 0,
1332
            'groupmode' => VISIBLEGROUPS,
1333
        ]);
1334
 
1335
        $usergroup = $assign->get_submission_group($student->id);
1336
        $this->assertFalse($usergroup, 'New student is in default group');
1337
 
1338
        // Add a submission.
1339
        $this->add_submission($student, $assign);
1340
        $this->submit_for_grading($student, $assign);
1341
 
1342
        // Set active groups to all groups.
1343
        $this->setUser($teacher);
1344
        $SESSION->activegroup[$course->id]['aag'][0] = 0;
1345
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1346
 
1347
        // Set an active group.
1348
        $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
1349
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1350
    }
1351
 
11 efrain 1352
    public function test_count_submissions_no_draft(): void {
1 efrain 1353
        $this->resetAfterTest();
1354
 
1355
        $course = $this->getDataGenerator()->create_course();
1356
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1357
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1358
 
1359
        $assign = $this->create_instance($course, [
1360
            'assignsubmission_onlinetext_enabled' => 1,
1361
        ]);
1362
 
1363
        $assign->get_user_submission($student->id, true);
1364
 
1365
        // Note: Drafts count as a submission.
1366
        $this->assertEquals(0, $assign->count_grades());
1367
        $this->assertEquals(0, $assign->count_submissions());
1368
        $this->assertEquals(1, $assign->count_submissions(true));
1369
        $this->assertEquals(0, $assign->count_submissions_need_grading());
1370
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1371
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1372
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1373
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1374
    }
1375
 
11 efrain 1376
    public function test_count_submissions_draft(): void {
1 efrain 1377
        $this->resetAfterTest();
1378
 
1379
        $course = $this->getDataGenerator()->create_course();
1380
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1381
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1382
 
1383
        $assign = $this->create_instance($course, [
1384
            'assignsubmission_onlinetext_enabled' => 1,
1385
        ]);
1386
 
1387
        $this->add_submission($student, $assign);
1388
 
1389
        // Note: Drafts count as a submission.
1390
        $this->assertEquals(0, $assign->count_grades());
1391
        $this->assertEquals(1, $assign->count_submissions());
1392
        $this->assertEquals(1, $assign->count_submissions(true));
1393
        $this->assertEquals(0, $assign->count_submissions_need_grading());
1394
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1395
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1396
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1397
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1398
    }
1399
 
11 efrain 1400
    public function test_count_submissions_submitted(): void {
1 efrain 1401
        global $SESSION;
1402
 
1403
        $this->resetAfterTest();
1404
 
1405
        $course = $this->getDataGenerator()->create_course();
1406
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1407
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1408
 
1409
        $assign = $this->create_instance($course, [
1410
            'assignsubmission_onlinetext_enabled' => 1,
1411
        ]);
1412
 
1413
        $this->add_submission($student, $assign);
1414
        $this->submit_for_grading($student, $assign);
1415
 
1416
        $this->assertEquals(0, $assign->count_grades());
1417
        $this->assertEquals(1, $assign->count_submissions());
1418
        $this->assertEquals(1, $assign->count_submissions(true));
1419
        $this->assertEquals(1, $assign->count_submissions_need_grading());
1420
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1421
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1422
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1423
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1424
    }
1425
 
11 efrain 1426
    public function test_count_submissions_graded(): void {
1 efrain 1427
        $this->resetAfterTest();
1428
 
1429
        $course = $this->getDataGenerator()->create_course();
1430
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1431
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1432
 
1433
        $assign = $this->create_instance($course, [
1434
            'assignsubmission_onlinetext_enabled' => 1,
1435
        ]);
1436
 
1437
        $this->add_submission($student, $assign);
1438
        $this->submit_for_grading($student, $assign);
1439
        $this->mark_submission($teacher, $assign, $student, 50.0);
1440
 
1441
        // Although it has been graded, it is still marked as submitted.
1442
        $this->assertEquals(1, $assign->count_grades());
1443
        $this->assertEquals(1, $assign->count_submissions());
1444
        $this->assertEquals(1, $assign->count_submissions(true));
1445
        $this->assertEquals(0, $assign->count_submissions_need_grading());
1446
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1447
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1448
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1449
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1450
    }
1451
 
11 efrain 1452
    public function test_count_submissions_graded_group(): void {
1 efrain 1453
        global $SESSION;
1454
 
1455
        $this->resetAfterTest();
1456
 
1457
        $course = $this->getDataGenerator()->create_course();
1458
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1459
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1460
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1461
        $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1462
        groups_add_member($group, $student);
1463
 
1464
        $assign = $this->create_instance($course, [
1465
            'assignsubmission_onlinetext_enabled' => 1,
1466
            'groupmode' => VISIBLEGROUPS,
1467
        ]);
1468
 
1469
        $this->add_submission($student, $assign);
1470
        $this->submit_for_grading($student, $assign);
1471
 
1472
        // The user should still be listed when fetching all groups.
1473
        $this->setUser($teacher);
1474
        $SESSION->activegroup[$course->id]['aag'][0] = 0;
1475
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1476
 
1477
        // The user should still be listed when fetching just their group.
1478
        $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
1479
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1480
 
1481
        // The user should still be listed when fetching just their group.
1482
        $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
1483
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1484
    }
1485
 
1486
    // TODO
1487
    public function x_test_count_submissions_for_team() {
1488
        $this->resetAfterTest();
1489
 
1490
        $course = $this->getDataGenerator()->create_course();
1491
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1492
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1493
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1494
        $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1495
        groups_add_member($group, $student);
1496
 
1497
        $assign = $this->create_instance($course, [
1498
            'assignsubmission_onlinetext_enabled' => 1,
1499
            'teamsubmission' => 1,
1500
        ]);
1501
 
1502
        // Add a graded submission.
1503
        $this->add_submission($student, $assign);
1504
 
1505
        // Simulate adding a grade.
1506
        $this->setUser($teacher);
1507
        $data = new \stdClass();
1508
        $data->grade = '50.0';
1509
        $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1510
 
1511
        // Simulate a submission.
1512
        $this->setUser($this->extrastudents[1]);
1513
        $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1514
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1515
        $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1516
        $data = new \stdClass();
1441 ariadna 1517
        $data->onlinetext_editor = [
1 efrain 1518
            'itemid' => file_get_unused_draft_itemid(),
1519
            'text' => 'Submission text',
1441 ariadna 1520
            'format' => FORMAT_MOODLE];
1 efrain 1521
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1522
        $plugin->save($submission, $data);
1523
 
1524
        // Simulate a submission.
1525
        $this->setUser($this->extrastudents[2]);
1526
        $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1527
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1528
        $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1529
        $data = new \stdClass();
1441 ariadna 1530
        $data->onlinetext_editor = [
1 efrain 1531
            'itemid' => file_get_unused_draft_itemid(),
1532
            'text' => 'Submission text',
1441 ariadna 1533
            'format' => FORMAT_MOODLE];
1 efrain 1534
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1535
        $plugin->save($submission, $data);
1536
 
1537
        // Simulate a submission.
1538
        $this->setUser($this->extrastudents[3]);
1539
        $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1540
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1541
        $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1542
        $data = new \stdClass();
1441 ariadna 1543
        $data->onlinetext_editor = [
1 efrain 1544
            'itemid' => file_get_unused_draft_itemid(),
1545
            'text' => 'Submission text',
1441 ariadna 1546
            'format' => FORMAT_MOODLE];
1 efrain 1547
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1548
        $plugin->save($submission, $data);
1549
 
1550
        // Simulate adding a grade.
1551
        $this->setUser($teacher);
1552
        $data = new \stdClass();
1553
        $data->grade = '50.0';
1554
        $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1555
        $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1556
 
1557
        // Create a new submission with status NEW.
1558
        $this->setUser($this->extrastudents[4]);
1559
        $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1560
 
1561
        $this->assertEquals(2, $assign->count_grades());
1562
        $this->assertEquals(4, $assign->count_submissions());
1563
        $this->assertEquals(5, $assign->count_submissions(true));
1564
        $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1565
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1566
    }
1567
 
11 efrain 1568
    public function test_get_grading_userid_list_only_active(): void {
1 efrain 1569
        $this->resetAfterTest();
1570
 
1571
        $course = $this->getDataGenerator()->create_course();
1572
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1573
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1574
        $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1441 ariadna 1575
            $course,
1576
            'student',
1577
            null,
1578
            'manual',
1579
            0,
1580
            0,
1581
            ENROL_USER_SUSPENDED
1582
        );
1 efrain 1583
 
1584
        $this->setUser($teacher);
1585
 
1586
        $assign = $this->create_instance($course);
1587
        $this->assertCount(1, $assign->testable_get_grading_userid_list());
1588
    }
1589
 
11 efrain 1590
    public function test_get_grading_userid_list_all(): void {
1 efrain 1591
        $this->resetAfterTest();
1592
 
1593
        $course = $this->getDataGenerator()->create_course();
1594
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1595
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1596
        $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1441 ariadna 1597
            $course,
1598
            'student',
1599
            null,
1600
            'manual',
1601
            0,
1602
            0,
1603
            ENROL_USER_SUSPENDED
1604
        );
1 efrain 1605
 
1606
        $this->setUser($teacher);
1607
        set_user_preference('grade_report_showonlyactiveenrol', false);
1608
 
1609
        $assign = $this->create_instance($course);
1610
        $this->assertCount(2, $assign->testable_get_grading_userid_list());
1611
    }
1612
 
11 efrain 1613
    public function test_cron(): void {
1 efrain 1614
        global $PAGE;
1615
        $this->resetAfterTest();
1616
 
1617
        // First run cron so there are no messages waiting to be sent (from other tests).
1618
        \core\cron::setup_user();
1619
        \assign::cron();
1620
 
1621
        $course = $this->getDataGenerator()->create_course();
1622
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1623
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1624
 
1625
        // Now create an assignment and add some feedback.
1626
        $this->setUser($teacher);
1627
        $assign = $this->create_instance($course, [
1628
            'sendstudentnotifications' => 1,
1629
        ]);
1630
 
1631
        $this->add_submission($student, $assign);
1632
        $this->submit_for_grading($student, $assign);
1633
        $this->mark_submission($teacher, $assign, $student, 50.0);
1634
 
1635
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1636
        \core\cron::setup_user();
1637
        $sink = $this->redirectMessages();
1638
        \assign::cron();
1639
        $messages = $sink->get_messages();
1640
 
1641
        $this->assertEquals(1, count($messages));
1642
        $this->assertEquals(1, $messages[0]->notification);
1643
        $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1644
        // Test customdata.
1645
        $customdata = json_decode($messages[0]->customdata);
1646
        $this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
1647
        $this->assertEquals($assign->get_instance()->id, $customdata->instance);
1648
        $this->assertEquals('feedbackavailable', $customdata->messagetype);
1649
        $userpicture = new \user_picture($teacher);
1650
        $userpicture->size = 1; // Use f1 size.
1651
        $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
1652
        $this->assertEquals(0, $customdata->uniqueidforuser);   // Not used in this case.
1653
        $this->assertFalse($customdata->blindmarking);
1654
    }
1655
 
11 efrain 1656
    public function test_cron_without_notifications(): void {
1 efrain 1657
        $this->resetAfterTest();
1658
 
1659
        // First run cron so there are no messages waiting to be sent (from other tests).
1660
        \core\cron::setup_user();
1661
        \assign::cron();
1662
 
1663
        $course = $this->getDataGenerator()->create_course();
1664
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1665
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1666
 
1667
        // Now create an assignment and add some feedback.
1668
        $this->setUser($teacher);
1669
        $assign = $this->create_instance($course, [
1670
            'sendstudentnotifications' => 1,
1671
        ]);
1672
 
1673
        $this->add_submission($student, $assign);
1674
        $this->submit_for_grading($student, $assign);
1675
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1676
            'sendstudentnotifications' => 0,
1677
        ]);
1678
 
1679
        \core\cron::setup_user();
1680
        $sink = $this->redirectMessages();
1681
        \assign::cron();
1682
        $messages = $sink->get_messages();
1683
 
1684
        $this->assertEquals(0, count($messages));
1685
    }
1686
 
11 efrain 1687
    public function test_cron_regraded(): void {
1 efrain 1688
        $this->resetAfterTest();
1689
 
1690
        // First run cron so there are no messages waiting to be sent (from other tests).
1691
        \core\cron::setup_user();
1692
        \assign::cron();
1693
 
1694
        $course = $this->getDataGenerator()->create_course();
1695
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1696
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1697
 
1698
        // Now create an assignment and add some feedback.
1699
        $this->setUser($teacher);
1700
        $assign = $this->create_instance($course, [
1701
            'sendstudentnotifications' => 1,
1702
        ]);
1703
 
1704
        $this->add_submission($student, $assign);
1705
        $this->submit_for_grading($student, $assign);
1706
        $this->mark_submission($teacher, $assign, $student, 50.0);
1707
 
1708
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1709
        \core\cron::setup_user();
1710
        \assign::cron();
1711
 
1712
        // Regrade.
1713
        $this->mark_submission($teacher, $assign, $student, 50.0);
1714
 
1715
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1716
        \core\cron::setup_user();
1717
        $sink = $this->redirectMessages();
1718
        \assign::cron();
1719
        $messages = $sink->get_messages();
1720
 
1721
        $this->assertEquals(1, count($messages));
1722
        $this->assertEquals(1, $messages[0]->notification);
1723
        $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1724
    }
1725
 
1726
    /**
1727
     * Test delivery of grade notifications as controlled by marking workflow.
1728
     */
11 efrain 1729
    public function test_markingworkflow_cron(): void {
1 efrain 1730
        $this->resetAfterTest();
1731
 
1732
        // First run cron so there are no messages waiting to be sent (from other tests).
1733
        \core\cron::setup_user();
1734
        \assign::cron();
1735
 
1441 ariadna 1736
        $course = $this->getDataGenerator()->create_course(['shortname' => 'E&U']);
1 efrain 1737
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1738
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1739
 
1740
        // Now create an assignment and add some feedback.
1741
        $this->setUser($teacher);
1742
        $assign = $this->create_instance($course, [
1441 ariadna 1743
            'name' => 'Escaping & Unescaping',
1 efrain 1744
            'sendstudentnotifications' => 1,
1745
            'markingworkflow' => 1,
1746
        ]);
1747
 
1748
        // Mark a submission but set the workflowstate to an unreleased state.
1749
        // This should not trigger a notification.
1750
        $this->add_submission($student, $assign);
1751
        $this->submit_for_grading($student, $assign);
1752
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1753
            'sendstudentnotifications' => 1,
1754
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1755
        ]);
1756
 
1757
        \core\cron::setup_user();
1758
        $sink = $this->redirectMessages();
1759
        \assign::cron();
1760
        $messages = $sink->get_messages();
1761
 
1762
        $this->assertEquals(0, count($messages));
1763
 
1764
        // Transition to the released state.
1765
        $this->setUser($teacher);
1766
        $submission = $assign->get_user_submission($student->id, true);
1767
        $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1768
        $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1769
 
1770
        // Now run cron and see that one message was sent.
1771
        \core\cron::setup_user();
1772
        $sink = $this->redirectMessages();
1773
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1774
        \assign::cron();
1775
        $messages = $sink->get_messages();
1776
 
1777
        $this->assertEquals(1, count($messages));
1441 ariadna 1778
 
1779
        // Get some bits we will need to verify the content of the message.
1780
        $assignname = $assign->get_instance()->name;
1781
        $assignurl = (new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]))->out();
1782
        $teachername = fullname($teacher);
1783
        $assignsurl = (new \moodle_url('/mod/assign/index.php', ['id' => $course->id]))->out();
1784
        $courseurl = (new \moodle_url('/course/view.php', ['id' => $course->id]))->out();
1785
 
1786
        $message = $messages[0];
1787
        $this->assertEquals(1, $message->notification);
1788
        $this->assertEquals(format_string($assign->get_instance()->name), $message->contexturlname);
1789
        $this->assertEquals("$teachername has given feedback for assignment $assignname", $message->subject, );
1790
        $this->assertEquals("E&U -> Assignment -> Escaping & Unescaping
1791
---------------------------------------------------------------------
1792
$teachername has posted some feedback on your
1793
assignment submission for 'Escaping & Unescaping'
1794
 
1795
You can see it appended to your assignment submission:
1796
 
1797
    $assignurl
1798
 
1799
---------------------------------------------------------------------
1800
",
1801
            $message->fullmessage,
1802
        );
1803
        $this->assertEquals(
1804
            '<p><font face="sans-serif">
1805
    <a href="' . $courseurl . '">E&amp;U</a> ->
1806
    <a href="' . $assignsurl . '">Assignment</a> ->
1807
    <a href="' . $assignurl . '">Escaping &amp; Unescaping</a>
1808
</font></p>
1809
<hr>
1810
<font face="sans-serif"><p>' . $teachername . ' has posted some feedback on your ' .
1811
            'assignment submission for \'Escaping &amp; Unescaping\'.
1812
You can see it appended to your <a href="' . $assignurl .
1813
            '">assignment submission</a>.</p></font>
1814
<hr>',
1815
            $message->fullmessagehtml
1816
        );
1 efrain 1817
    }
1818
 
11 efrain 1819
    public function test_cron_message_includes_courseid(): void {
1 efrain 1820
        $this->resetAfterTest();
1821
 
1822
        // First run cron so there are no messages waiting to be sent (from other tests).
1823
        \core\cron::setup_user();
1824
        \assign::cron();
1825
 
1826
        $course = $this->getDataGenerator()->create_course();
1827
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1828
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1829
 
1830
        // Now create an assignment and add some feedback.
1831
        $this->setUser($teacher);
1832
        $assign = $this->create_instance($course, [
1833
            'sendstudentnotifications' => 1,
1834
        ]);
1835
 
1836
        // Mark a submission but set the workflowstate to an unreleased state.
1837
        // This should not trigger a notification.
1838
        $this->add_submission($student, $assign);
1839
        $this->submit_for_grading($student, $assign);
1840
        $this->mark_submission($teacher, $assign, $student);
1841
        \phpunit_util::stop_message_redirection();
1842
 
1843
        // Now run cron and see that one message was sent.
1844
        \core\cron::setup_user();
1845
        $this->preventResetByRollback();
1846
        $sink = $this->redirectEvents();
1847
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1848
        \assign::cron();
1849
 
1850
        $events = $sink->get_events();
1851
        $event = reset($events);
1852
        $this->assertInstanceOf('\core\event\notification_sent', $event);
1853
        $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1854
        $sink->close();
1855
    }
1856
 
11 efrain 1857
    public function test_is_graded(): void {
1 efrain 1858
        $this->resetAfterTest();
1859
 
1860
        $course = $this->getDataGenerator()->create_course();
1861
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1862
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1863
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1864
 
1865
        $assign = $this->create_instance($course);
1866
 
1867
        $this->add_submission($student, $assign);
1868
        $this->submit_for_grading($student, $assign);
1869
        $this->mark_submission($teacher, $assign, $student, 50.0);
1870
 
1871
        $this->setUser($teacher);
1872
        $this->assertEquals(true, $assign->testable_is_graded($student->id));
1873
        $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1874
    }
1875
 
11 efrain 1876
    public function test_can_grade(): void {
1 efrain 1877
        global $DB;
1878
 
1879
        $this->resetAfterTest();
1880
 
1881
        $course = $this->getDataGenerator()->create_course();
1882
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1883
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1884
 
1885
        $assign = $this->create_instance($course);
1886
 
1887
        $this->setUser($student);
1888
        $this->assertEquals(false, $assign->can_grade());
1889
 
1890
        $this->setUser($teacher);
1891
        $this->assertEquals(true, $assign->can_grade());
1892
 
1893
        // Test the viewgrades capability for other users.
1894
        $this->setUser();
1895
        $this->assertTrue($assign->can_grade($teacher->id));
1896
        $this->assertFalse($assign->can_grade($student->id));
1897
 
1898
        // Test the viewgrades capability - without mod/assign:grade.
1899
        $this->setUser($student);
1900
 
1441 ariadna 1901
        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
1 efrain 1902
        assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1903
        $this->assertEquals(false, $assign->can_grade());
1904
    }
1905
 
11 efrain 1906
    public function test_can_view_submission(): void {
1 efrain 1907
        global $DB;
1908
 
1909
        $this->resetAfterTest();
1910
 
1911
        $course = $this->getDataGenerator()->create_course();
1912
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1913
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1914
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1915
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1916
        $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1441 ariadna 1917
            $course,
1918
            'student',
1919
            null,
1920
            'manual',
1921
            0,
1922
            0,
1923
            ENROL_USER_SUSPENDED
1924
        );
1 efrain 1925
 
1926
        $assign = $this->create_instance($course);
1927
 
1928
        $this->setUser($student);
1929
        $this->assertEquals(true, $assign->can_view_submission($student->id));
1930
        $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1931
        $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1932
 
1933
        $this->setUser($teacher);
1934
        $this->assertEquals(true, $assign->can_view_submission($student->id));
1935
        $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1936
        $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1937
        $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1938
 
1939
        $this->setUser($editingteacher);
1940
        $this->assertEquals(true, $assign->can_view_submission($student->id));
1941
        $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1942
        $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1943
        $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1944
 
1945
        // Test the viewgrades capability - without mod/assign:grade.
1946
        $this->setUser($student);
1441 ariadna 1947
        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
1 efrain 1948
        assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1949
        $this->assertEquals(true, $assign->can_view_submission($student->id));
1950
        $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1951
        $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1952
        $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1953
    }
1954
 
11 efrain 1955
    public function test_update_submission(): void {
1 efrain 1956
        $this->resetAfterTest();
1957
 
1958
        $course = $this->getDataGenerator()->create_course();
1959
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1960
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1961
 
1962
        $assign = $this->create_instance($course);
1963
 
1964
        $this->add_submission($student, $assign);
1965
        $submission = $assign->get_user_submission($student->id, 0);
1966
        $assign->testable_update_submission($submission, $student->id, true, true);
1967
 
1968
        $this->setUser($teacher);
1969
 
1970
        // Verify the gradebook update.
1971
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1972
 
1973
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1974
        $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1975
    }
1976
 
11 efrain 1977
    public function test_update_submission_team(): void {
1 efrain 1978
        $this->resetAfterTest();
1979
 
1980
        $course = $this->getDataGenerator()->create_course();
1981
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1982
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1983
 
1984
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1985
        groups_add_member($group, $student);
1986
 
1987
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1988
        groups_add_member($group, $otherstudent);
1989
 
1990
        $assign = $this->create_instance($course, [
1991
            'teamsubmission' => 1,
1992
        ]);
1993
 
1994
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1995
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1996
        $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1997
 
1998
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1999
        $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
2000
        $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
2001
 
2002
        $this->add_submission($student, $assign);
2003
        $submission = $assign->get_group_submission($student->id, 0, true);
2004
        $assign->testable_update_submission($submission, $student->id, true, true);
2005
 
2006
        // Verify the gradebook update for the student.
2007
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
2008
 
2009
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
2010
        $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
2011
 
2012
        // Verify the gradebook update for the other student.
2013
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
2014
 
2015
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
2016
        $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
2017
    }
2018
 
11 efrain 2019
    public function test_update_submission_suspended(): void {
1 efrain 2020
        $this->resetAfterTest();
2021
 
2022
        $course = $this->getDataGenerator()->create_course();
2023
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2024
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2025
 
2026
        $assign = $this->create_instance($course);
2027
 
2028
        $this->add_submission($student, $assign);
2029
        $submission = $assign->get_user_submission($student->id, 0);
2030
        $assign->testable_update_submission($submission, $student->id, true, false);
2031
 
2032
        $this->setUser($teacher);
2033
 
2034
        // Verify the gradebook update.
2035
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
2036
 
2037
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
2038
        $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
2039
    }
2040
 
11 efrain 2041
    public function test_update_submission_blind(): void {
1 efrain 2042
        $this->resetAfterTest();
2043
 
2044
        $course = $this->getDataGenerator()->create_course();
2045
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2046
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2047
 
2048
        $assign = $this->create_instance($course, [
2049
            'blindmarking' => 1,
2050
        ]);
2051
 
2052
        $this->add_submission($student, $assign);
2053
        $submission = $assign->get_user_submission($student->id, 0);
2054
        $assign->testable_update_submission($submission, $student->id, true, false);
2055
 
2056
        // Verify the gradebook update.
2057
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
2058
 
2059
        // The usermodified is not set because this is blind marked.
2060
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
2061
        $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
2062
    }
2063
 
11 efrain 2064
    public function test_group_submissions_submit_for_marking_requireallteammemberssubmit(): void {
1 efrain 2065
        global $PAGE;
2066
 
2067
        $this->resetAfterTest();
2068
 
2069
        $course = $this->getDataGenerator()->create_course();
2070
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2071
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2072
 
2073
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2074
        groups_add_member($group, $student);
2075
 
2076
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2077
        groups_add_member($group, $otherstudent);
2078
 
2079
        $assign = $this->create_instance($course, [
2080
            'teamsubmission' => 1,
2081
            'assignsubmission_onlinetext_enabled' => 1,
2082
            'submissiondrafts' => 1,
2083
            'requireallteammemberssubmit' => 1,
2084
        ]);
2085
 
2086
        // Now verify group assignments.
2087
        $this->setUser($teacher);
2088
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2089
 
2090
        // Add a submission.
2091
        $this->add_submission($student, $assign);
2092
 
2093
        // Check we can see the submit button.
2094
        $this->setUser($student);
2095
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2096
        $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
2097
 
2098
        $submission = $assign->get_group_submission($student->id, 0, true);
2099
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2100
        $assign->testable_update_submission($submission, $student->id, true, true);
2101
 
2102
        // Check that the student does not see "Submit" button.
2103
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2104
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2105
 
2106
        // Change to another user in the same group.
2107
        $this->setUser($otherstudent);
2108
        $output = $assign->view_submission_action_bar($assign->get_instance(), $otherstudent);
2109
        $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
2110
 
2111
        $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2112
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2113
        $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2114
        $output = $assign->view_submission_action_bar($assign->get_instance(), $otherstudent);
2115
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2116
    }
2117
 
11 efrain 2118
    public function test_group_submissions_submit_for_marking(): void {
1 efrain 2119
        global $PAGE;
2120
 
2121
        $this->resetAfterTest();
2122
 
2123
        $course = $this->getDataGenerator()->create_course();
2124
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2125
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2126
 
2127
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2128
        groups_add_member($group, $student);
2129
 
2130
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2131
        groups_add_member($group, $otherstudent);
2132
 
2133
        // Now verify group assignments.
2134
        $this->setUser($teacher);
2135
        $time = time();
2136
        $assign = $this->create_instance($course, [
2137
            'teamsubmission' => 1,
2138
            'assignsubmission_onlinetext_enabled' => 1,
2139
            'submissiondrafts' => 1,
2140
            'requireallteammemberssubmit' => 0,
2141
            'duedate' => $time - (2 * DAYSECS),
2142
        ]);
2143
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2144
 
2145
        // Add a submission.
2146
        $this->add_submission($student, $assign);
2147
 
2148
        // Check we can see the submit button.
2149
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2150
        $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
2151
        $output = $assign->view_student_summary($student, true);
2152
        $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2153
        $difftime = time() - $time;
2154
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2155
 
2156
        $submission = $assign->get_group_submission($student->id, 0, true);
2157
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2158
        $assign->testable_update_submission($submission, $student->id, true, true);
2159
 
2160
        // Check that the student does not see "Submit" button.
2161
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2162
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2163
 
2164
        // Change to another user in the same group.
2165
        $this->setUser($otherstudent);
2166
        $output = $assign->view_submission_action_bar($assign->get_instance(), $otherstudent);
2167
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2168
 
2169
        // Check that time remaining is not overdue.
2170
        $output = $assign->view_student_summary($otherstudent, true);
2171
        $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2172
        $difftime = time() - $time;
2173
        $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2174
 
2175
        $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2176
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2177
        $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2178
        $output = $assign->view_submission_action_bar($assign->get_instance(), $otherstudent);
2179
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2180
    }
2181
 
11 efrain 2182
    public function test_submissions_open(): void {
1 efrain 2183
        global $DB;
2184
 
2185
        $this->resetAfterTest();
2186
 
2187
        $course = $this->getDataGenerator()->create_course();
2188
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2189
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2190
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2191
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2192
        $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1441 ariadna 2193
            $course,
2194
            'student',
2195
            null,
2196
            'manual',
2197
            0,
2198
            0,
2199
            ENROL_USER_SUSPENDED
2200
        );
1 efrain 2201
 
2202
        $this->setAdminUser();
2203
 
2204
        $now = time();
2205
        $tomorrow = $now + DAYSECS;
2206
        $oneweek = $now + WEEKSECS;
2207
        $yesterday = $now - DAYSECS;
2208
 
2209
        $assign = $this->create_instance($course);
2210
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2211
 
2212
        $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2213
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2214
 
2215
        $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2216
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2217
 
2218
        $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2219
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2220
 
2221
        $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2222
        $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2223
 
2224
        $assign->testable_save_user_extension($student->id, $tomorrow);
2225
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2226
 
2227
        $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2228
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2229
 
2230
        $this->setUser($student);
2231
        $submission = $assign->get_user_submission($student->id, true);
2232
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2233
        $assign->testable_update_submission($submission, $student->id, true, false);
2234
 
2235
        $this->setUser($teacher);
2236
        $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2237
    }
2238
 
11 efrain 2239
    public function test_get_graders(): void {
1 efrain 2240
        global $DB;
2241
 
2242
        $this->resetAfterTest();
2243
 
2244
        $course = $this->getDataGenerator()->create_course();
2245
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2246
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2247
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2248
 
2249
        $this->setAdminUser();
2250
 
2251
        // Create an assignment with no groups.
2252
        $assign = $this->create_instance($course);
2253
        $this->assertCount(2, $assign->testable_get_graders($student->id));
2254
    }
2255
 
11 efrain 2256
    public function test_get_graders_separate_groups(): void {
1 efrain 2257
        global $DB;
2258
 
2259
        $this->resetAfterTest();
2260
 
2261
        $course = $this->getDataGenerator()->create_course();
2262
        $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2263
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2264
        $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2265
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2266
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2267
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2268
 
1441 ariadna 2269
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 2270
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2271
        groups_add_member($group1, $student);
2272
 
2273
        $this->setAdminUser();
2274
 
2275
        // Force create an assignment with SEPARATEGROUPS.
2276
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1441 ariadna 2277
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 2278
 
2279
        $assign = $this->create_instance($course, [
2280
            'groupingid' => $grouping->id,
2281
            'groupmode' => SEPARATEGROUPS,
2282
        ]);
2283
 
2284
        $this->assertCount(4, $assign->testable_get_graders($student->id));
2285
 
2286
        // Note the second student is in a group that is not in the grouping.
2287
        // This means that we get all graders that are not in a group in the grouping.
2288
        $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2289
    }
2290
 
11 efrain 2291
    public function test_get_notified_users(): void {
1 efrain 2292
        global $CFG, $DB;
2293
 
2294
        $this->resetAfterTest();
2295
 
2296
        $course = $this->getDataGenerator()->create_course();
1441 ariadna 2297
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 2298
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1441 ariadna 2299
        $this->getDataGenerator()->create_grouping_group(['groupid' => $group1->id, 'groupingid' => $grouping->id]);
1 efrain 2300
 
2301
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2302
        groups_add_member($group1, $teacher);
2303
 
2304
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2305
        groups_add_member($group1, $editingteacher);
2306
 
2307
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2308
        groups_add_member($group1, $student);
2309
 
2310
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2311
        $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2312
 
2313
        $capability = 'mod/assign:receivegradernotifications';
2314
        $coursecontext = \context_course::instance($course->id);
1441 ariadna 2315
        $role = $DB->get_record('role', ['shortname' => 'teacher']);
1 efrain 2316
 
2317
        $this->setUser($teacher);
2318
 
2319
        // Create an assignment with no groups.
2320
        $assign = $this->create_instance($course);
2321
 
2322
        $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2323
 
2324
        // Change nonediting teachers role to not receive grader notifications.
2325
        assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2326
 
2327
        // Only the editing teachers will be returned.
2328
        $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2329
 
2330
        // Note the second student is in a group that is not in the grouping.
2331
        // This means that we get all graders that are not in a group in the grouping.
2332
        $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2333
    }
2334
 
11 efrain 2335
    public function test_get_notified_users_in_grouping(): void {
1 efrain 2336
        global $CFG, $DB;
2337
 
2338
        $this->resetAfterTest();
2339
 
2340
        $course = $this->getDataGenerator()->create_course();
1441 ariadna 2341
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 2342
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1441 ariadna 2343
        $this->getDataGenerator()->create_grouping_group(['groupid' => $group1->id, 'groupingid' => $grouping->id]);
1 efrain 2344
 
2345
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2346
        groups_add_member($group1, $teacher);
2347
 
2348
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2349
        groups_add_member($group1, $editingteacher);
2350
 
2351
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2352
        groups_add_member($group1, $student);
2353
 
2354
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2355
        $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2356
 
2357
        // Force create an assignment with SEPARATEGROUPS.
2358
        $assign = $this->create_instance($course, [
2359
            'groupingid' => $grouping->id,
2360
            'groupmode' => SEPARATEGROUPS,
2361
        ]);
2362
 
2363
        // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2364
        $this->setUser($student);
2365
        $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2366
 
2367
        // Note the second student is in a group that is not in the grouping.
2368
        // This means that we get all graders that are not in a group in the grouping.
2369
        $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2370
 
2371
        // Change nonediting teachers role to not receive grader notifications.
2372
        $capability = 'mod/assign:receivegradernotifications';
2373
        $coursecontext = \context_course::instance($course->id);
2374
        $role = $DB->get_record('role', ['shortname' => 'teacher']);
2375
        assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2376
 
2377
        // Only the editing teachers will be returned.
2378
        $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2379
 
2380
        // Note the second student is in a group that is not in the grouping.
2381
        // This means that we get all graders that are not in a group in the grouping.
2382
        // Unfortunately there are no editing teachers who are not in a group.
2383
        $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2384
    }
2385
 
11 efrain 2386
    public function test_group_members_only(): void {
1 efrain 2387
        global $CFG;
2388
 
2389
        $this->resetAfterTest();
2390
 
2391
        $course = $this->getDataGenerator()->create_course();
1441 ariadna 2392
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 2393
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2394
        $this->getDataGenerator()->create_grouping_group([
2395
            'groupid' => $group1->id,
2396
            'groupingid' => $grouping->id,
2397
        ]);
2398
 
2399
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2400
        $this->getDataGenerator()->create_grouping_group([
2401
            'groupid' => $group2->id,
2402
            'groupingid' => $grouping->id,
2403
        ]);
2404
 
2405
        $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2406
 
2407
        // Add users in the following groups
2408
        // - Teacher - Group 1.
2409
        // - Student - Group 1.
2410
        // - Student - Group 2.
2411
        // - Student - Unrelated Group
2412
        // - Student - No group.
2413
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2414
        groups_add_member($group1, $teacher);
2415
 
2416
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2417
        groups_add_member($group1, $student);
2418
 
2419
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2420
        groups_add_member($group2, $otherstudent);
2421
 
2422
        $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2423
        groups_add_member($group2, $otherstudent);
2424
 
2425
        $this->getDataGenerator()->create_and_enrol($course, 'student');
2426
 
2427
        $this->setAdminUser();
2428
 
2429
        $CFG->enableavailability = true;
2430
        $assign = $this->create_instance($course, [], [
2431
            'availability' => json_encode(
2432
                \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2433
            ),
2434
            'groupingid' => $grouping->id,
2435
        ]);
2436
 
2437
        // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2438
        // group, or the student in an unrelated group.
2439
        $this->setUser($teacher);
2440
        $participants = $assign->list_participants(0, true);
2441
        $this->assertCount(2, $participants);
2442
        $this->assertTrue(isset($participants[$student->id]));
2443
        $this->assertTrue(isset($participants[$otherstudent->id]));
2444
    }
2445
 
11 efrain 2446
    public function test_get_uniqueid_for_user(): void {
1 efrain 2447
        $this->resetAfterTest();
2448
 
2449
        $course = $this->getDataGenerator()->create_course();
2450
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2451
        $students = [];
2452
        for ($i = 0; $i < 10; $i++) {
2453
            $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2454
            $students[$student->id] = $student;
2455
        }
2456
 
2457
        $this->setUser($teacher);
2458
        $assign = $this->create_instance($course);
2459
 
2460
        foreach ($students as $student) {
2461
            $uniqueid = $assign->get_uniqueid_for_user($student->id);
2462
            $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2463
        }
2464
    }
2465
 
11 efrain 2466
    public function test_show_student_summary(): void {
1 efrain 2467
        global $CFG, $PAGE;
2468
 
2469
        $this->resetAfterTest();
2470
 
2471
        $course = $this->getDataGenerator()->create_course();
2472
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2473
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2474
        $this->setUser($teacher);
2475
        $assign = $this->create_instance($course);
2476
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2477
 
2478
        // No feedback should be available because this student has not been graded.
2479
        $this->setUser($student);
2480
        $output = $assign->view_student_summary($student, true);
2481
        $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if there is no grade');
2482
 
2483
        // Simulate adding a grade.
2484
        $this->add_submission($student, $assign);
2485
        $this->submit_for_grading($student, $assign);
2486
        $this->mark_submission($teacher, $assign, $student);
2487
 
2488
        // Now we should see the feedback.
2489
        $this->setUser($student);
2490
        $output = $assign->view_student_summary($student, true);
2491
        $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
2492
 
2493
        // Now hide the grade in gradebook.
2494
        $this->setUser($teacher);
1441 ariadna 2495
        require_once($CFG->libdir . '/gradelib.php');
2496
        $gradeitem = new \grade_item([
1 efrain 2497
            'itemtype'      => 'mod',
2498
            'itemmodule'    => 'assign',
2499
            'iteminstance'  => $assign->get_instance()->id,
1441 ariadna 2500
            'courseid'      => $course->id]);
1 efrain 2501
 
2502
        $gradeitem->set_hidden(1, false);
2503
 
2504
        // No feedback should be available because the grade is hidden.
2505
        $this->setUser($student);
2506
        $output = $assign->view_student_summary($student, true);
1441 ariadna 2507
        $this->assertDoesNotMatchRegularExpression(
2508
            '/Feedback/',
2509
            $output,
2510
            'Do not show feedback if the grade is hidden in the gradebook'
2511
        );
1 efrain 2512
 
2513
        // Freeze the context.
2514
        $this->setAdminUser();
2515
        $context = $assign->get_context();
2516
        $CFG->contextlocking = true;
2517
        $context->set_locked(true);
2518
 
2519
        // No feedback should be available because the grade is hidden.
2520
        $this->setUser($student);
2521
        $output = $assign->view_student_summary($student, true);
1441 ariadna 2522
        $this->assertDoesNotMatchRegularExpression(
2523
            '/Feedback/',
2524
            $output,
2525
            'Do not show feedback if the grade is hidden in the gradebook'
2526
        );
1 efrain 2527
 
2528
        // Show the feedback again - it should still be visible even in a frozen context.
2529
        $this->setUser($teacher);
2530
        $gradeitem->set_hidden(0, false);
2531
 
2532
        $this->setUser($student);
2533
        $output = $assign->view_student_summary($student, true);
2534
        $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
2535
    }
2536
 
11 efrain 2537
    public function test_show_student_summary_with_feedback(): void {
1 efrain 2538
        global $CFG, $PAGE;
2539
 
2540
        $this->resetAfterTest();
2541
 
2542
        $course = $this->getDataGenerator()->create_course();
2543
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2544
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2545
        $this->setUser($teacher);
2546
        $assign = $this->create_instance($course, [
1441 ariadna 2547
            'assignfeedback_comments_enabled' => 1,
1 efrain 2548
        ]);
2549
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2550
 
2551
        // No feedback should be available because this student has not been graded.
2552
        $this->setUser($student);
2553
        $output = $assign->view_student_summary($student, true);
2554
        $this->assertDoesNotMatchRegularExpression('/Feedback/', $output);
2555
 
2556
        // Simulate adding a grade.
2557
        $this->add_submission($student, $assign);
2558
        $this->submit_for_grading($student, $assign);
2559
        $this->mark_submission($teacher, $assign, $student, null, [
2560
            'assignfeedbackcomments_editor' => [
2561
                'text' => 'Tomato sauce',
2562
                'format' => FORMAT_MOODLE,
2563
            ],
2564
        ]);
2565
 
2566
        // Should have feedback but no grade.
2567
        $this->setUser($student);
2568
        $output = $assign->view_student_summary($student, true);
2569
        $this->assertMatchesRegularExpression('/Feedback/', $output);
2570
        $this->assertMatchesRegularExpression('/Tomato sauce/', $output);
2571
        $this->assertDoesNotMatchRegularExpression('/Grade/', $output, 'Do not show grade when there is no grade.');
2572
        $this->assertDoesNotMatchRegularExpression('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2573
 
2574
        // Add a grade now.
2575
        $this->mark_submission($teacher, $assign, $student, 50.0, [
2576
            'assignfeedbackcomments_editor' => [
2577
                'text' => 'Bechamel sauce',
2578
                'format' => FORMAT_MOODLE,
2579
            ],
2580
        ]);
2581
 
2582
        // Should have feedback but no grade.
2583
        $this->setUser($student);
2584
        $output = $assign->view_student_summary($student, true);
2585
        $this->assertDoesNotMatchRegularExpression('/Tomato sauce/', $output);
2586
        $this->assertMatchesRegularExpression('/Bechamel sauce/', $output);
2587
        $this->assertMatchesRegularExpression('/Grade/', $output);
2588
        $this->assertMatchesRegularExpression('/Graded on/', $output);
2589
 
2590
        // Now hide the grade in gradebook.
2591
        $this->setUser($teacher);
1441 ariadna 2592
        $gradeitem = new \grade_item([
1 efrain 2593
            'itemtype'      => 'mod',
2594
            'itemmodule'    => 'assign',
2595
            'iteminstance'  => $assign->get_instance()->id,
1441 ariadna 2596
            'courseid'      => $course->id]);
1 efrain 2597
 
2598
        $gradeitem->set_hidden(1, false);
2599
 
2600
        // No feedback should be available because the grade is hidden.
2601
        $this->setUser($student);
2602
        $output = $assign->view_student_summary($student, true);
1441 ariadna 2603
        $this->assertDoesNotMatchRegularExpression(
2604
            '/Feedback/',
2605
            $output,
2606
            'Do not show feedback if the grade is hidden in the gradebook'
2607
        );
1 efrain 2608
    }
2609
 
2610
    /**
2611
     * Test reopen behavior when in "Manual" mode.
2612
     */
11 efrain 2613
    public function test_attempt_reopen_method_manual(): void {
1 efrain 2614
        global $PAGE;
2615
 
2616
        $this->resetAfterTest();
2617
        $course = $this->getDataGenerator()->create_course();
2618
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2619
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2620
 
2621
        $assign = $this->create_instance($course, [
2622
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2623
            'maxattempts' => 3,
2624
            'submissiondrafts' => 1,
2625
            'assignsubmission_onlinetext_enabled' => 1,
2626
        ]);
2627
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2628
 
2629
        // Student should be able to see an add submission button.
2630
        $this->setUser($student);
2631
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2632
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2633
 
2634
        // Add a submission.
2635
        $this->add_submission($student, $assign);
2636
        $this->submit_for_grading($student, $assign);
2637
 
2638
        // Verify the student cannot make changes to the submission.
2639
        $output = $assign->view_student_summary($student, true);
2640
        $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2641
 
2642
        // Mark the submission.
2643
        $this->mark_submission($teacher, $assign, $student);
2644
 
2645
        // Check the student can see the grade.
2646
        $this->setUser($student);
2647
        $output = $assign->view_student_summary($student, true);
2648
        $this->assertNotEquals(false, strpos($output, '50.0'));
2649
 
2650
        // Allow the student another attempt.
2651
        $teacher->ignoresesskey = true;
2652
        $this->setUser($teacher);
2653
        $result = $assign->testable_process_add_attempt($student->id);
2654
        $this->assertEquals(true, $result);
2655
 
2656
        // Check that the previous attempt is now in the submission history table.
2657
        $this->setUser($student);
2658
        $output = $assign->view_student_summary($student, true);
2659
        // Need a better check.
2660
        $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2661
 
2662
        // Check that the student now has a submission history.
2663
        $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2664
 
2665
        // Check that the student now does not have a button for Submit.
2666
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2667
        $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2668
 
2669
        // Check that the student now has a button for Add a new attempt".
2670
        $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2671
 
2672
        $this->setUser($teacher);
2673
        // Check that the grading table loads correctly and contains this user.
2674
        // This is also testing that we do not get duplicate rows in the grading table.
2675
        $gradingtable = new \assign_grading_table($assign, 100, '', 0, true);
2676
        $output = $assign->get_renderer()->render($gradingtable);
2677
        $this->assertEquals(true, strpos($output, $student->lastname));
2678
 
2679
        // Should be 1 not 2.
2680
        $this->assertEquals(1, $assign->count_submissions());
2681
        $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2682
        $this->assertEquals(0, $assign->count_submissions_need_grading());
2683
        $this->assertEquals(1, $assign->count_grades());
2684
 
2685
        // Change max attempts to unlimited.
2686
        $formdata = clone($assign->get_instance());
2687
        $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2688
        $formdata->instance = $formdata->id;
2689
        $assign->update_instance($formdata);
2690
 
2691
        // Mark the submission again.
2692
        $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2693
 
2694
        // Check the grade exists.
2695
        $this->setUser($teacher);
2696
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2697
        $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2698
 
2699
        // Check we can reopen still.
2700
        $result = $assign->testable_process_add_attempt($student->id);
2701
        $this->assertEquals(true, $result);
2702
 
2703
        // Should no longer have a grade because there is no grade for the latest attempt.
2704
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2705
        $this->assertEmpty($grades);
2706
    }
2707
 
2708
    /**
2709
     * Test reopen behavior when in "Reopen until pass" mode.
2710
     */
11 efrain 2711
    public function test_attempt_reopen_method_untilpass(): void {
1 efrain 2712
        global $PAGE;
2713
 
2714
        $this->resetAfterTest();
2715
        $course = $this->getDataGenerator()->create_course();
2716
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2717
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2718
 
2719
        $assign = $this->create_instance($course, [
2720
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2721
            'maxattempts' => 3,
2722
            'submissiondrafts' => 1,
2723
            'assignsubmission_onlinetext_enabled' => 1,
2724
        ]);
2725
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2726
 
2727
        // Set grade to pass to 80.
2728
        $gradeitem = $assign->get_grade_item();
2729
        $gradeitem->gradepass = '80.0';
2730
        $gradeitem->update();
2731
 
2732
        // Student should be able to see an add submission button.
2733
        $this->setUser($student);
2734
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2735
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2736
 
2737
        // Add a submission.
2738
        $this->add_submission($student, $assign);
2739
        $this->submit_for_grading($student, $assign);
2740
 
2741
        // Verify the student cannot make a new attempt.
2742
        $output = $assign->view_student_summary($student, true);
2743
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2744
 
2745
        // Mark the submission as non-passing.
2746
        $this->mark_submission($teacher, $assign, $student, 50.0);
2747
 
2748
        // Check the student can see the grade.
2749
        $this->setUser($student);
2750
        $output = $assign->view_student_summary($student, true);
2751
        $this->assertNotEquals(false, strpos($output, '50.0'));
2752
 
2753
        // Check that the student now has a submission history.
2754
        $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2755
 
2756
        // Check that the student now does not have a button for Submit.
2757
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2758
        $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2759
 
2760
        // Check that the student now has a button for Add a new attempt.
2761
        $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2762
 
2763
        // Add a second submission.
2764
        $this->add_submission($student, $assign);
2765
        $this->submit_for_grading($student, $assign);
2766
 
1441 ariadna 2767
        // Mark the second submission as passing.
2768
        $this->mark_submission($teacher, $assign, $student, 80.0, [], 1);
1 efrain 2769
 
2770
        // Check that the student does not have a button for Add a new attempt.
2771
        $this->setUser($student);
2772
        $output = $assign->view_student_summary($student, true);
2773
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2774
 
1441 ariadna 2775
        // Re-mark the second submission as not passing.
1 efrain 2776
        $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2777
 
2778
        // Check that the student now has a button for Add a new attempt.
2779
        $this->setUser($student);
2780
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2781
        $this->assertMatchesRegularExpression('/' . get_string('addnewattempt', 'assign') . '/', $output);
2782
        $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2783
    }
2784
 
11 efrain 2785
    public function test_attempt_reopen_method_untilpass_passing(): void {
1 efrain 2786
        global $PAGE;
2787
 
2788
        $this->resetAfterTest();
2789
        $course = $this->getDataGenerator()->create_course();
2790
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2791
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2792
 
2793
        $assign = $this->create_instance($course, [
2794
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2795
            'maxattempts' => 3,
2796
            'submissiondrafts' => 1,
2797
            'assignsubmission_onlinetext_enabled' => 1,
2798
        ]);
2799
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2800
 
2801
        // Set grade to pass to 80.
2802
        $gradeitem = $assign->get_grade_item();
2803
        $gradeitem->gradepass = '80.0';
2804
        $gradeitem->update();
2805
 
2806
        // Student should be able to see an add submission button.
2807
        $this->setUser($student);
2808
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2809
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2810
 
2811
        // Add a submission as a student.
2812
        $this->add_submission($student, $assign);
2813
        $this->submit_for_grading($student, $assign);
2814
 
2815
        // Mark the submission as passing.
2816
        $this->mark_submission($teacher, $assign, $student, 100.0);
2817
 
2818
        // Check the student can see the grade.
2819
        $this->setUser($student);
2820
        $output = $assign->view_student_summary($student, true);
2821
        $this->assertNotEquals(false, strpos($output, '100.0'));
2822
 
2823
        // Check that the student does not have a button for Add a new attempt.
2824
        $output = $assign->view_student_summary($student, true);
2825
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2826
    }
2827
 
11 efrain 2828
    public function test_attempt_reopen_method_untilpass_no_passing_requirement(): void {
1 efrain 2829
        global $PAGE;
2830
 
2831
        $this->resetAfterTest();
2832
        $course = $this->getDataGenerator()->create_course();
2833
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2834
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2835
 
2836
        $assign = $this->create_instance($course, [
2837
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2838
            'maxattempts' => 3,
2839
            'submissiondrafts' => 1,
2840
            'assignsubmission_onlinetext_enabled' => 1,
2841
        ]);
2842
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2843
 
2844
        // Set grade to pass to 0, so that no attempts should reopen.
2845
        $gradeitem = $assign->get_grade_item();
2846
        $gradeitem->gradepass = '0';
2847
        $gradeitem->update();
2848
 
2849
        // Student should be able to see an add submission button.
2850
        $this->setUser($student);
2851
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2852
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2853
 
2854
        // Add a submission.
2855
        $this->add_submission($student, $assign);
2856
        $this->submit_for_grading($student, $assign);
2857
 
2858
        // Mark the submission with any grade.
2859
        $this->mark_submission($teacher, $assign, $student, 0.0);
2860
 
2861
        // Check the student can see the grade.
2862
        $this->setUser($student);
2863
        $output = $assign->view_student_summary($student, true);
2864
        $this->assertNotEquals(false, strpos($output, '0.0'));
2865
 
2866
        // Check that the student does not have a button for Add a new attempt.
2867
        $output = $assign->view_student_summary($student, true);
2868
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2869
    }
2870
 
2871
    /**
1441 ariadna 2872
     * Test reopen behavior when in "Automatic" mode.
2873
     *
2874
     * @coversNothing
2875
     */
2876
    public function test_attempt_reopen_method_automatic(): void {
2877
        global $PAGE;
2878
 
2879
        $this->resetAfterTest();
2880
        $course = $this->getDataGenerator()->create_course();
2881
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2882
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2883
 
2884
        $assign = $this->create_instance($course, [
2885
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_AUTOMATIC,
2886
            'maxattempts' => 3,
2887
            'submissiondrafts' => 1,
2888
            'assignsubmission_onlinetext_enabled' => 1,
2889
        ]);
2890
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2891
 
2892
        // Set grade to pass to 80.
2893
        $gradeitem = $assign->get_grade_item();
2894
        $gradeitem->gradepass = '80.0';
2895
        $gradeitem->update();
2896
 
2897
        // Student should be able to see an add submission button.
2898
        $this->setUser($student);
2899
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2900
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2901
 
2902
        // Add a submission as a student.
2903
        $this->add_submission($student, $assign);
2904
        $this->submit_for_grading($student, $assign);
2905
 
2906
        // Verify the student cannot make a new attempt.
2907
        $output = $assign->view_student_summary($student, true);
2908
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2909
 
2910
        // Mark the submission as non-passing.
2911
        $this->mark_submission($teacher, $assign, $student, 50.0);
2912
 
2913
        // Check the student now has a button for Add a new attempt.
2914
        $this->setUser($student);
2915
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2916
        $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2917
 
2918
        // Add a second submission.
2919
        $this->add_submission($student, $assign);
2920
        $this->submit_for_grading($student, $assign);
2921
 
2922
        // Mark the submission as passing.
2923
        $this->mark_submission($teacher, $assign, $student, 80.0, [], 1);
2924
 
2925
        // Check the student now has a button for Add a new attempt.
2926
        $this->setUser($student);
2927
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2928
        $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2929
    }
2930
 
2931
    /**
1 efrain 2932
     * Test student visibility for each stage of the marking workflow.
2933
     */
11 efrain 2934
    public function test_markingworkflow(): void {
1 efrain 2935
        global $PAGE;
2936
 
2937
        $this->resetAfterTest();
2938
        $course = $this->getDataGenerator()->create_course();
2939
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2940
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2941
 
2942
        $assign = $this->create_instance($course, [
2943
            'markingworkflow' => 1,
2944
        ]);
2945
 
2946
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2947
 
2948
        // Mark the submission and set to notmarked.
1441 ariadna 2949
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1 efrain 2950
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2951
        ]);
2952
 
2953
        // Check the student can't see the grade.
2954
        $this->setUser($student);
2955
        $output = $assign->view_student_summary($student, true);
2956
        $this->assertEquals(false, strpos($output, '50.0'));
2957
 
2958
        // Make sure the grade isn't pushed to the gradebook.
2959
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2960
        $this->assertEmpty($grades);
2961
 
2962
        // Mark the submission and set to inmarking.
1441 ariadna 2963
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1 efrain 2964
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2965
        ]);
2966
 
2967
        // Check the student can't see the grade.
2968
        $this->setUser($student);
2969
        $output = $assign->view_student_summary($student, true);
2970
        $this->assertEquals(false, strpos($output, '50.0'));
2971
 
2972
        // Make sure the grade isn't pushed to the gradebook.
2973
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2974
        $this->assertEmpty($grades);
2975
 
2976
        // Mark the submission and set to readyforreview.
1441 ariadna 2977
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1 efrain 2978
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2979
        ]);
2980
 
2981
        // Check the student can't see the grade.
2982
        $this->setUser($student);
2983
        $output = $assign->view_student_summary($student, true);
2984
        $this->assertEquals(false, strpos($output, '50.0'));
2985
 
2986
        // Make sure the grade isn't pushed to the gradebook.
2987
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2988
        $this->assertEmpty($grades);
2989
 
2990
        // Mark the submission and set to inreview.
1441 ariadna 2991
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1 efrain 2992
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2993
        ]);
2994
 
2995
        // Check the student can't see the grade.
2996
        $this->setUser($student);
2997
        $output = $assign->view_student_summary($student, true);
2998
        $this->assertEquals(false, strpos($output, '50.0'));
2999
 
3000
        // Make sure the grade isn't pushed to the gradebook.
3001
        $grades = $assign->get_user_grades_for_gradebook($student->id);
3002
        $this->assertEmpty($grades);
3003
 
3004
        // Mark the submission and set to readyforrelease.
1441 ariadna 3005
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1 efrain 3006
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
3007
        ]);
3008
 
3009
        // Check the student can't see the grade.
3010
        $this->setUser($student);
3011
        $output = $assign->view_student_summary($student, true);
3012
        $this->assertEquals(false, strpos($output, '50.0'));
3013
 
3014
        // Make sure the grade isn't pushed to the gradebook.
3015
        $grades = $assign->get_user_grades_for_gradebook($student->id);
3016
        $this->assertEmpty($grades);
3017
 
3018
        // Mark the submission and set to released.
1441 ariadna 3019
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1 efrain 3020
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
3021
        ]);
3022
 
3023
        // Check the student can see the grade.
3024
        $this->setUser($student);
3025
        $output = $assign->view_student_summary($student, true);
3026
        $this->assertNotEquals(false, strpos($output, '50.0'));
3027
 
3028
        // Make sure the grade is pushed to the gradebook.
3029
        $grades = $assign->get_user_grades_for_gradebook($student->id);
3030
        $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
3031
    }
3032
 
3033
    /**
3034
     * Test that a student allocated a specific marker is only shown to that marker.
3035
     */
11 efrain 3036
    public function test_markerallocation(): void {
1 efrain 3037
        global $PAGE;
3038
 
3039
        $this->resetAfterTest();
3040
        $course = $this->getDataGenerator()->create_course();
3041
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3042
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3043
        $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3044
 
3045
        $assign = $this->create_instance($course, [
3046
            'markingworkflow' => 1,
1441 ariadna 3047
            'markingallocation' => 1,
1 efrain 3048
        ]);
3049
 
3050
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
3051
 
3052
        // Allocate marker to submission.
3053
        $this->mark_submission($teacher, $assign, $student, null, [
3054
            'allocatedmarker' => $teacher->id,
3055
        ]);
3056
 
3057
        // Check the allocated marker can view the submission.
3058
        $this->setUser($teacher);
3059
        $users = $assign->list_participants(0, true);
3060
        $this->assertEquals(1, count($users));
3061
        $this->assertTrue(isset($users[$student->id]));
3062
 
3063
        $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
3064
        $context = \context_module::instance($cm->id);
3065
        $assign = new mod_assign_testable_assign($context, $cm, $course);
3066
 
3067
        // Check that other teachers can't view this submission.
3068
        $this->setUser($otherteacher);
3069
        $users = $assign->list_participants(0, true);
3070
        $this->assertEquals(0, count($users));
3071
    }
3072
 
3073
    /**
3074
     * Ensure that a teacher cannot submit for students as standard.
3075
     */
11 efrain 3076
    public function test_teacher_submit_for_student(): void {
1 efrain 3077
        global $PAGE;
3078
 
3079
        $this->resetAfterTest();
3080
        $course = $this->getDataGenerator()->create_course();
3081
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3082
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
3083
 
3084
        $assign = $this->create_instance($course, [
3085
            'assignsubmission_onlinetext_enabled' => 1,
3086
            'submissiondrafts' => 1,
3087
        ]);
3088
 
3089
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
3090
 
3091
        // Add a submission but do not submit.
3092
        $this->add_submission($student, $assign, 'Student submission text');
3093
 
3094
        $this->setUser($student);
3095
        $output = $assign->view_student_summary($student, true);
3096
        $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
3097
 
3098
        // Check that a teacher can not edit the submission as they do not have the capability.
3099
        $this->setUser($teacher);
3100
        $this->expectException('moodle_exception');
3101
        $this->expectExceptionMessage('error/nopermission');
3102
        $this->add_submission($student, $assign, 'Teacher edited submission text', false);
3103
    }
3104
 
3105
    /**
3106
     * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
3107
     */
11 efrain 3108
    public function test_teacher_submit_for_student_with_capability(): void {
1 efrain 3109
        global $PAGE;
3110
 
3111
        $this->resetAfterTest();
3112
        $course = $this->getDataGenerator()->create_course();
3113
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3114
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
3115
        $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
3116
 
3117
        $assign = $this->create_instance($course, [
3118
            'assignsubmission_onlinetext_enabled' => 1,
3119
            'submissiondrafts' => 1,
3120
        ]);
3121
 
3122
        // Add the required capability.
3123
        $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3124
        assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3125
        role_assign($roleid, $teacher->id, $assign->get_context()->id);
3126
        accesslib_clear_all_caches_for_unit_testing();
3127
 
3128
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
3129
 
3130
        // Add a submission but do not submit.
3131
        $this->add_submission($student, $assign, 'Student submission text');
3132
 
3133
        $this->setUser($student);
3134
        $output = $assign->view_student_summary($student, true);
3135
        $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
3136
 
3137
        // Check that a teacher can edit the submission.
3138
        $this->setUser($teacher);
3139
        $this->add_submission($student, $assign, 'Teacher edited submission text', false);
3140
 
3141
        $this->setUser($student);
3142
        $output = $assign->view_student_summary($student, true);
3143
        $this->assertStringNotContainsString('Student submission text', $output, 'Contains student submission text');
3144
        $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains teacher edited submission text');
3145
 
3146
        // Check that the teacher can submit the students work.
3147
        $this->setUser($teacher);
3148
        $this->submit_for_grading($student, $assign, [], false);
3149
 
3150
        // Revert to draft so the student can edit it.
3151
        $assign->revert_to_draft($student->id);
3152
 
3153
        $this->setUser($student);
3154
 
3155
        // Check that the submission text was saved.
3156
        $output = $assign->view_student_summary($student, true);
3157
        $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains student submission text');
3158
 
3159
        // Check that the student can submit their work.
3160
        $this->submit_for_grading($student, $assign, []);
3161
 
3162
        $output = $assign->view_student_summary($student, true);
3163
        $this->assertStringNotContainsString(get_string('addsubmission', 'assign'), $output);
3164
 
3165
        // An editing teacher without the extra role should still be able to revert to draft.
3166
        $this->setUser($otherteacher);
3167
 
3168
        // Revert to draft so the submission is editable.
3169
        $assign->revert_to_draft($student->id);
3170
    }
3171
 
3172
    /**
3173
     * Ensure that disabling submit after the cutoff date works as expected.
3174
     */
11 efrain 3175
    public function test_disable_submit_after_cutoff_date(): void {
1 efrain 3176
        global $PAGE;
3177
 
3178
        $this->resetAfterTest();
3179
        $course = $this->getDataGenerator()->create_course();
3180
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3181
 
3182
        $now = time();
3183
        $tomorrow = $now + DAYSECS;
3184
        $lastweek = $now - (7 * DAYSECS);
3185
        $yesterday = $now - DAYSECS;
3186
 
3187
        $this->setAdminUser();
3188
        $assign = $this->create_instance($course, [
3189
            'duedate' => $yesterday,
3190
            'cutoffdate' => $tomorrow,
3191
            'assignsubmission_onlinetext_enabled' => 1,
3192
        ]);
3193
 
3194
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
3195
 
3196
        // Student should be able to see an add submission button.
3197
        $this->setUser($student);
3198
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
3199
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
3200
 
3201
        // Add a submission but don't submit now.
3202
        $this->add_submission($student, $assign);
3203
 
3204
        // Create another instance with cut-off and due-date already passed.
3205
        $this->setAdminUser();
3206
        $assign = $this->create_instance($course, [
3207
            'duedate' => $lastweek,
3208
            'cutoffdate' => $yesterday,
3209
            'assignsubmission_onlinetext_enabled' => 1,
3210
        ]);
3211
 
3212
        $this->setUser($student);
3213
        $output = $assign->view_student_summary($student, true);
1441 ariadna 3214
        $this->assertStringNotContainsString(
3215
            $output,
3216
            get_string('editsubmission', 'assign'),
3217
            'Should not be able to edit after cutoff date.'
3218
        );
3219
        $this->assertStringNotContainsString(
3220
            $output,
3221
            get_string('submitassignment', 'assign'),
3222
            'Should not be able to submit after cutoff date.'
3223
        );
1 efrain 3224
    }
3225
 
3226
    /**
3227
     * Testing for submission comment plugin settings.
3228
     *
3229
     * @dataProvider submission_plugin_settings_provider
3230
     * @param   bool    $globalenabled
3231
     * @param   array   $instanceconfig
3232
     * @param   bool    $isenabled
3233
     */
11 efrain 3234
    public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled): void {
1 efrain 3235
        global $CFG;
3236
 
3237
        $this->resetAfterTest();
3238
        $course = $this->getDataGenerator()->create_course();
3239
 
3240
        $CFG->usecomments = $globalenabled;
3241
        $assign = $this->create_instance($course, $instanceconfig);
3242
        $plugin = $assign->get_submission_plugin_by_type('comments');
3243
        $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3244
    }
3245
 
1441 ariadna 3246
    /**
3247
     * Data provider for test_submission_comment_plugin_settings.
3248
     *
3249
     * @return array[]
3250
     */
3251
    public static function submission_plugin_settings_provider(): array {
1 efrain 3252
        return [
3253
            'CFG->usecomments true, empty config => Enabled by default' => [
3254
                true,
3255
                [],
3256
                true,
3257
            ],
3258
            'CFG->usecomments true, config enabled => Comments enabled' => [
3259
                true,
3260
                [
3261
                    'assignsubmission_comments_enabled' => 1,
3262
                ],
3263
                true,
3264
            ],
3265
            'CFG->usecomments true, config idisabled => Comments enabled' => [
3266
                true,
3267
                [
3268
                    'assignsubmission_comments_enabled' => 0,
3269
                ],
3270
                true,
3271
            ],
3272
            'CFG->usecomments false, empty config => Disabled by default' => [
3273
                false,
3274
                [],
3275
                false,
3276
            ],
3277
            'CFG->usecomments false, config enabled => Comments disabled' => [
3278
                false,
3279
                [
3280
                    'assignsubmission_comments_enabled' => 1,
3281
                ],
3282
                false,
3283
            ],
3284
            'CFG->usecomments false, config disabled => Comments disabled' => [
3285
                false,
3286
                [
3287
                    'assignsubmission_comments_enabled' => 0,
3288
                ],
3289
                false,
3290
            ],
3291
        ];
3292
    }
3293
 
3294
    /**
3295
     * Testing for comment inline settings
3296
     */
11 efrain 3297
    public function test_feedback_comment_commentinline(): void {
1 efrain 3298
        global $CFG, $USER;
3299
 
3300
        $this->resetAfterTest();
3301
        $course = $this->getDataGenerator()->create_course();
3302
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3303
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3304
 
3305
        $sourcetext = "Hello!
3306
 
3307
I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3308
 
3309
URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3310
Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3311
 
3312
External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3313
External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3314
Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3315
Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3316
Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3317
Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3318
";
3319
 
3320
        $this->setUser($teacher);
3321
        $assign = $this->create_instance($course, [
3322
            'assignsubmission_onlinetext_enabled' => 1,
3323
            'assignfeedback_comments_enabled' => 1,
3324
            'assignfeedback_comments_commentinline' => 1,
3325
        ]);
3326
 
3327
        $this->setUser($student);
3328
 
3329
        // Add a submission but don't submit now.
3330
        $this->add_submission($student, $assign, $sourcetext);
3331
 
3332
        $this->setUser($teacher);
3333
 
3334
        $data = new \stdClass();
3335
        require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3336
        $pagination = [
3337
            'userid' => $student->id,
3338
            'rownum' => 0,
3339
            'last' => true,
3340
            'useridlistid' => $assign->get_useridlist_key_id(),
3341
            'attemptnumber' => 0,
3342
        ];
1441 ariadna 3343
        $formparams = [$assign, $data, $pagination];
1 efrain 3344
        $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3345
 
3346
        // We need to get the URL these will be transformed to.
3347
        $context = \context_user::instance($USER->id);
3348
        $itemid = $data->assignfeedbackcomments_editor['itemid'];
3349
        $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
3350
 
3351
        // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3352
        $filteredtext = "Hello!
3353
 
3354
I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3355
 
3356
URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3357
Plugin url outside a tag: $url/logo-240x60.gif
3358
 
3359
External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3360
External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3361
Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
3362
Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
3363
Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
3364
Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3365
";
3366
 
3367
        $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3368
    }
3369
 
3370
    /**
3371
     * Testing for feedback comment plugin settings.
3372
     *
3373
     * @dataProvider feedback_plugin_settings_provider
3374
     * @param   array   $instanceconfig
3375
     * @param   bool    $isenabled
3376
     */
11 efrain 3377
    public function test_feedback_plugin_settings($instanceconfig, $isenabled): void {
1 efrain 3378
        $this->resetAfterTest();
3379
        $course = $this->getDataGenerator()->create_course();
3380
 
3381
        $assign = $this->create_instance($course, $instanceconfig);
3382
        $plugin = $assign->get_feedback_plugin_by_type('comments');
3383
        $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3384
    }
3385
 
1441 ariadna 3386
    /**
3387
     * Data provider for test_feedback_plugin_settings.
3388
     *
3389
     * @return array[]
3390
     */
3391
    public static function feedback_plugin_settings_provider(): array {
1 efrain 3392
        return [
3393
            'No configuration => disabled' => [
3394
                [],
3395
                false,
3396
            ],
3397
            'Actively disabled' => [
3398
                [
3399
                    'assignfeedback_comments_enabled' => 0,
3400
                ],
3401
                false,
3402
            ],
3403
            'Actively enabled' => [
3404
                [
3405
                    'assignfeedback_comments_enabled' => 1,
3406
                ],
3407
                true,
3408
            ],
3409
        ];
3410
    }
3411
 
3412
    /**
3413
     * Testing if gradebook feedback plugin is enabled.
3414
     */
11 efrain 3415
    public function test_is_gradebook_feedback_enabled(): void {
1 efrain 3416
        $this->resetAfterTest();
3417
        $course = $this->getDataGenerator()->create_course();
3418
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3419
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3420
 
3421
        $adminconfig = get_config('assign');
3422
        $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3423
 
3424
        // Create assignment with gradebook feedback enabled and grade = 0.
3425
        $assign = $this->create_instance($course, [
3426
            "{$gradebookplugin}_enabled" => 1,
3427
            'grades' => 0,
3428
        ]);
3429
 
3430
        // Get gradebook feedback plugin.
3431
        $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3432
        $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3433
        $this->assertEquals(1, $plugin->is_enabled('enabled'));
3434
        $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3435
    }
3436
 
3437
    /**
3438
     * Testing if gradebook feedback plugin is disabled.
3439
     */
11 efrain 3440
    public function test_is_gradebook_feedback_disabled(): void {
1 efrain 3441
        $this->resetAfterTest();
3442
        $course = $this->getDataGenerator()->create_course();
3443
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3444
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3445
 
3446
        $adminconfig = get_config('assign');
3447
        $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3448
 
3449
        // Create assignment with gradebook feedback disabled and grade = 0.
3450
        $assign = $this->create_instance($course, [
3451
            "{$gradebookplugin}_enabled" => 0,
3452
            'grades' => 0,
3453
        ]);
3454
 
3455
        $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3456
        $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3457
        $this->assertEquals(0, $plugin->is_enabled('enabled'));
3458
    }
3459
 
3460
    /**
3461
     * Testing can_edit_submission.
3462
     */
11 efrain 3463
    public function test_can_edit_submission(): void {
1 efrain 3464
        $this->resetAfterTest();
3465
        $course = $this->getDataGenerator()->create_course();
3466
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3467
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3468
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3469
 
3470
        $assign = $this->create_instance($course, [
3471
            'assignsubmission_onlinetext_enabled' => 1,
3472
            'submissiondrafts' => 1,
3473
        ]);
3474
 
3475
        // Check student can edit their own submission.
3476
        $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3477
 
3478
        // Check student cannot edit others submission.
3479
        $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3480
 
3481
        // Check teacher cannot (by default) edit a students submission.
3482
        $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3483
    }
3484
 
3485
    /**
3486
     * Testing can_edit_submission with the editothersubmission capability.
3487
     */
11 efrain 3488
    public function test_can_edit_submission_with_editothersubmission(): void {
1 efrain 3489
        $this->resetAfterTest();
3490
        $course = $this->getDataGenerator()->create_course();
3491
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3492
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3493
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3494
 
3495
        $assign = $this->create_instance($course, [
3496
            'assignsubmission_onlinetext_enabled' => 1,
3497
            'submissiondrafts' => 1,
3498
        ]);
3499
 
3500
        // Add the required capability to edit a student submission.
3501
        $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3502
        assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3503
        role_assign($roleid, $teacher->id, $assign->get_context()->id);
3504
        accesslib_clear_all_caches_for_unit_testing();
3505
 
3506
        // Check student can edit their own submission.
3507
        $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3508
 
3509
        // Check student cannot edit others submission.
3510
        $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3511
 
3512
        // Retest - should now have access.
3513
        $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3514
    }
3515
 
3516
    /**
3517
     * Testing can_edit_submission
3518
     */
11 efrain 3519
    public function test_can_edit_submission_separategroups(): void {
1 efrain 3520
        $this->resetAfterTest();
3521
        $course = $this->getDataGenerator()->create_course();
3522
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3523
 
3524
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3525
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3526
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3527
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3528
 
1441 ariadna 3529
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 3530
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3531
        groups_assign_grouping($grouping->id, $group1->id);
3532
        groups_add_member($group1, $student1);
3533
        groups_add_member($group1, $student2);
3534
 
3535
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3536
        groups_assign_grouping($grouping->id, $group2->id);
3537
        groups_add_member($group2, $student3);
3538
        groups_add_member($group2, $student4);
3539
 
3540
        $assign = $this->create_instance($course, [
3541
            'assignsubmission_onlinetext_enabled' => 1,
3542
            'submissiondrafts' => 1,
3543
            'groupingid' => $grouping->id,
3544
            'groupmode' => SEPARATEGROUPS,
3545
        ]);
3546
 
3547
        // Verify a student does not have the ability to edit submissions for other users.
3548
        $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3549
        $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3550
        $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3551
        $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3552
    }
3553
 
3554
    /**
3555
     * Testing can_edit_submission
3556
     */
11 efrain 3557
    public function test_can_edit_submission_separategroups_with_editothersubmission(): void {
1 efrain 3558
        $this->resetAfterTest();
3559
        $course = $this->getDataGenerator()->create_course();
3560
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3561
 
3562
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3563
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3564
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3565
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1441 ariadna 3566
        $student5 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1 efrain 3567
 
1441 ariadna 3568
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 3569
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3570
        groups_assign_grouping($grouping->id, $group1->id);
3571
        groups_add_member($group1, $student1);
3572
        groups_add_member($group1, $student2);
3573
 
3574
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3575
        groups_assign_grouping($grouping->id, $group2->id);
3576
        groups_add_member($group2, $student3);
3577
        groups_add_member($group2, $student4);
3578
 
3579
        $assign = $this->create_instance($course, [
3580
            'assignsubmission_onlinetext_enabled' => 1,
3581
            'submissiondrafts' => 1,
3582
            'groupingid' => $grouping->id,
3583
            'groupmode' => SEPARATEGROUPS,
1441 ariadna 3584
            'preventsubmissionnotingroup' => 0,
1 efrain 3585
        ]);
3586
 
3587
        // Add the capability to the new \assignment for student 1.
3588
        $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3589
        assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3590
        role_assign($roleid, $student1->id, $assign->get_context()->id);
3591
        accesslib_clear_all_caches_for_unit_testing();
3592
 
3593
        // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3594
        $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3595
        $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3596
        $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3597
        $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
1441 ariadna 3598
        $this->assertFalse($assign->can_edit_submission($student5->id, $student1->id));
1 efrain 3599
 
3600
        // Verify other students do not have the ability to edit submissions for other users.
3601
        $this->assertTrue($assign->can_edit_submission($student2->id, $student2->id));
3602
        $this->assertFalse($assign->can_edit_submission($student1->id, $student2->id));
3603
        $this->assertFalse($assign->can_edit_submission($student3->id, $student2->id));
3604
        $this->assertFalse($assign->can_edit_submission($student4->id, $student2->id));
1441 ariadna 3605
        $this->assertFalse($assign->can_edit_submission($student5->id, $student2->id));
3606
 
3607
        // Add the required capability to edit other submissions and to view all groups to the teacher.
3608
        $roleid = create_role('Dummy role 2', 'dummyrole2', 'dummy role description');
3609
        assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3610
        assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $assign->get_context()->id);
3611
        role_assign($roleid, $teacher->id, $assign->get_context()->id);
3612
 
3613
        // Verify the teacher has the ability to edit submissions for other users including users not in a group.
3614
        $this->assertTrue($assign->can_edit_submission($student1->id, $teacher->id));
3615
        $this->assertTrue($assign->can_edit_submission($student2->id, $teacher->id));
3616
        $this->assertTrue($assign->can_edit_submission($student3->id, $teacher->id));
3617
        $this->assertTrue($assign->can_edit_submission($student4->id, $teacher->id));
3618
        $this->assertTrue($assign->can_edit_submission($student5->id, $teacher->id));
1 efrain 3619
    }
3620
 
3621
    /**
3622
     * Test if the view blind details capability works
3623
     */
11 efrain 3624
    public function test_can_view_blind_details(): void {
1 efrain 3625
        global $DB;
3626
 
3627
        $this->resetAfterTest();
3628
        $course = $this->getDataGenerator()->create_course();
3629
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3630
        $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
3631
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3632
 
3633
        $assign = $this->create_instance($course, [
3634
            'blindmarking' => 1,
3635
        ]);
3636
 
3637
        $this->assertTrue($assign->is_blind_marking());
3638
 
3639
        // Test student names are hidden to teacher.
3640
        $this->setUser($teacher);
3641
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
3642
        $output = $assign->get_renderer()->render($gradingtable);
3643
        $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));    // "Participant" is somewhere on the page.
3644
        $this->assertEquals(false, strpos($output, fullname($student)));    // Students full name doesn't appear.
3645
 
3646
        // Test student names are visible to manager.
3647
        $this->setUser($manager);
3648
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
3649
        $output = $assign->get_renderer()->render($gradingtable);
3650
        $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
3651
        $this->assertEquals(true, strpos($output, fullname($student)));
3652
    }
3653
 
3654
    /**
3655
     * Testing get_shared_group_members
3656
     */
11 efrain 3657
    public function test_get_shared_group_members(): void {
1 efrain 3658
        $this->resetAfterTest();
3659
        $course = $this->getDataGenerator()->create_course();
3660
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3661
 
3662
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3663
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3664
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3665
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3666
 
1441 ariadna 3667
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 3668
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3669
        groups_assign_grouping($grouping->id, $group1->id);
3670
        groups_add_member($group1, $student1);
3671
        groups_add_member($group1, $student2);
3672
 
3673
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3674
        groups_assign_grouping($grouping->id, $group2->id);
3675
        groups_add_member($group2, $student3);
3676
        groups_add_member($group2, $student4);
3677
 
3678
        $assign = $this->create_instance($course, [
3679
            'groupingid' => $grouping->id,
3680
            'groupmode' => SEPARATEGROUPS,
3681
        ]);
3682
 
3683
        $cm = $assign->get_course_module();
3684
 
3685
        // Get shared group members for students 0 and 1.
3686
        $groupmembers = $assign->get_shared_group_members($cm, $student1->id);
3687
        $this->assertCount(2, $groupmembers);
3688
        $this->assertContainsEquals($student1->id, $groupmembers);
3689
        $this->assertContainsEquals($student2->id, $groupmembers);
3690
 
3691
        $groupmembers = $assign->get_shared_group_members($cm, $student2->id);
3692
        $this->assertCount(2, $groupmembers);
3693
        $this->assertContainsEquals($student1->id, $groupmembers);
3694
        $this->assertContainsEquals($student2->id, $groupmembers);
3695
 
3696
        $groupmembers = $assign->get_shared_group_members($cm, $student3->id);
3697
        $this->assertCount(2, $groupmembers);
3698
        $this->assertContainsEquals($student3->id, $groupmembers);
3699
        $this->assertContainsEquals($student4->id, $groupmembers);
3700
 
3701
        $groupmembers = $assign->get_shared_group_members($cm, $student4->id);
3702
        $this->assertCount(2, $groupmembers);
3703
        $this->assertContainsEquals($student3->id, $groupmembers);
3704
        $this->assertContainsEquals($student4->id, $groupmembers);
3705
    }
3706
 
3707
    /**
3708
     * Testing get_shared_group_members
3709
     */
11 efrain 3710
    public function test_get_shared_group_members_override(): void {
1 efrain 3711
        $this->resetAfterTest();
3712
        $course = $this->getDataGenerator()->create_course();
3713
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3714
 
3715
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3716
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3717
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3718
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3719
 
1441 ariadna 3720
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 3721
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3722
        groups_assign_grouping($grouping->id, $group1->id);
3723
        groups_add_member($group1, $student1);
3724
        groups_add_member($group1, $student2);
3725
 
3726
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3727
        groups_assign_grouping($grouping->id, $group2->id);
3728
        groups_add_member($group2, $student3);
3729
        groups_add_member($group2, $student4);
3730
 
3731
        $assign = $this->create_instance($course, [
3732
            'groupingid' => $grouping->id,
3733
            'groupmode' => SEPARATEGROUPS,
3734
        ]);
3735
 
3736
        $cm = $assign->get_course_module();
3737
 
3738
        // Add the capability to access allgroups for one of the students.
3739
        $roleid = create_role('Access all groups role', 'accessallgroupsrole', '');
3740
        assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $assign->get_context()->id);
3741
        role_assign($roleid, $student1->id, $assign->get_context()->id);
3742
        accesslib_clear_all_caches_for_unit_testing();
3743
 
3744
        // Get shared group members for students 0 and 1.
3745
        $groupmembers = $assign->get_shared_group_members($cm, $student1->id);
3746
        $this->assertCount(4, $groupmembers);
3747
        $this->assertContainsEquals($student1->id, $groupmembers);
3748
        $this->assertContainsEquals($student2->id, $groupmembers);
3749
        $this->assertContainsEquals($student3->id, $groupmembers);
3750
        $this->assertContainsEquals($student4->id, $groupmembers);
3751
 
3752
        $groupmembers = $assign->get_shared_group_members($cm, $student2->id);
3753
        $this->assertCount(2, $groupmembers);
3754
        $this->assertContainsEquals($student1->id, $groupmembers);
3755
        $this->assertContainsEquals($student2->id, $groupmembers);
3756
 
3757
        $groupmembers = $assign->get_shared_group_members($cm, $student3->id);
3758
        $this->assertCount(2, $groupmembers);
3759
        $this->assertContainsEquals($student3->id, $groupmembers);
3760
        $this->assertContainsEquals($student4->id, $groupmembers);
3761
 
3762
        $groupmembers = $assign->get_shared_group_members($cm, $student4->id);
3763
        $this->assertCount(2, $groupmembers);
3764
        $this->assertContainsEquals($student3->id, $groupmembers);
3765
        $this->assertContainsEquals($student4->id, $groupmembers);
3766
    }
3767
 
3768
    /**
3769
     * Test get plugins file areas
3770
     */
11 efrain 3771
    public function test_get_plugins_file_areas(): void {
1 efrain 3772
        global $DB;
3773
 
3774
        $this->resetAfterTest();
3775
        $course = $this->getDataGenerator()->create_course();
3776
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3777
 
3778
        $assign = $this->create_instance($course);
3779
 
3780
        // Test that all the submission and feedback plugins are returning the expected file aras.
3781
        $usingfilearea = 0;
3782
        $coreplugins = \core_plugin_manager::standard_plugins_list('assignsubmission');
3783
        foreach ($assign->get_submission_plugins() as $plugin) {
3784
            $type = $plugin->get_type();
3785
            if (!in_array($type, $coreplugins)) {
3786
                continue;
3787
            }
3788
            $fileareas = $plugin->get_file_areas();
3789
 
3790
            if ($type == 'onlinetext') {
1441 ariadna 3791
                $this->assertEquals(['submissions_onlinetext' => 'Online text'], $fileareas);
1 efrain 3792
                $usingfilearea++;
3793
            } else if ($type == 'file') {
1441 ariadna 3794
                $this->assertEquals(['submission_files' => 'File submissions'], $fileareas);
1 efrain 3795
                $usingfilearea++;
3796
            } else {
3797
                $this->assertEmpty($fileareas);
3798
            }
3799
        }
3800
        $this->assertEquals(2, $usingfilearea);
3801
 
3802
        $usingfilearea = 0;
3803
        $coreplugins = \core_plugin_manager::standard_plugins_list('assignfeedback');
3804
        foreach ($assign->get_feedback_plugins() as $plugin) {
3805
            $type = $plugin->get_type();
3806
            if (!in_array($type, $coreplugins)) {
3807
                continue;
3808
            }
3809
            $fileareas = $plugin->get_file_areas();
3810
 
3811
            if ($type == 'editpdf') {
3812
                $checkareas = [
3813
                    'download' => 'Annotate PDF',
3814
                    'combined' => 'Annotate PDF',
3815
                    'partial' => 'Annotate PDF',
3816
                    'importhtml' => 'Annotate PDF',
3817
                    'pages' => 'Annotate PDF',
3818
                    'readonlypages' => 'Annotate PDF',
3819
                    'stamps' => 'Annotate PDF',
3820
                    'tmp_jpg_to_pdf' => 'Annotate PDF',
1441 ariadna 3821
                    'tmp_rotated_jpg' => 'Annotate PDF',
1 efrain 3822
                ];
3823
                $this->assertEquals($checkareas, $fileareas);
3824
                $usingfilearea++;
3825
            } else if ($type == 'file') {
1441 ariadna 3826
                $this->assertEquals(['feedback_files' => 'Feedback files'], $fileareas);
1 efrain 3827
                $usingfilearea++;
3828
            } else if ($type == 'comments') {
1441 ariadna 3829
                $this->assertEquals(['feedback' => 'Feedback comments'], $fileareas);
1 efrain 3830
                $usingfilearea++;
3831
            } else {
3832
                $this->assertEmpty($fileareas);
3833
            }
3834
        }
3835
        $this->assertEquals(3, $usingfilearea);
3836
    }
3837
 
3838
    /**
3839
     * Test override exists
3840
     *
3841
     * This function needs to obey the group override logic as per the assign grading table and
3842
     * the overview block.
3843
     */
11 efrain 3844
    public function test_override_exists(): void {
1 efrain 3845
        global $DB;
3846
 
3847
        $this->resetAfterTest();
3848
        $course = $this->getDataGenerator()->create_course();
3849
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3850
 
1441 ariadna 3851
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 3852
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3853
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3854
 
3855
        // Data:
3856
        // - student1 => group A only
3857
        // - student2 => group B only
3858
        // - student3 => Group A + Group B (No user override)
3859
        // - student4 => Group A + Group B (With user override)
3860
        // - student4 => No groups.
3861
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3862
        groups_add_member($group1, $student1);
3863
 
3864
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3865
        groups_add_member($group2, $student2);
3866
 
3867
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3868
        groups_add_member($group1, $student3);
3869
        groups_add_member($group2, $student3);
3870
 
3871
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3872
        groups_add_member($group1, $student4);
3873
        groups_add_member($group2, $student4);
3874
 
3875
        $student5 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3876
 
3877
        $assign = $this->create_instance($course);
3878
        $instance = $assign->get_instance();
3879
 
3880
        // Overrides for each of the groups, and a user override.
3881
        $overrides = [
3882
            (object) [
3883
                // Override for group 1, highest priority (numerically lowest sortorder).
3884
                'assignid' => $instance->id,
3885
                'groupid' => $group1->id,
3886
                'userid' => null,
3887
                'sortorder' => 1,
3888
                'allowsubmissionsfromdate' => 1,
3889
                'duedate' => 2,
3890
                'cutoffdate' => 3,
1441 ariadna 3891
                'timelimit' => null,
1 efrain 3892
            ],
3893
            (object) [
3894
                // Override for group 2, lower priority (numerically higher sortorder).
3895
                'assignid' => $instance->id,
3896
                'groupid' => $group2->id,
3897
                'userid' => null,
3898
                'sortorder' => 2,
3899
                'allowsubmissionsfromdate' => 5,
3900
                'duedate' => 6,
3901
                'cutoffdate' => 6,
1441 ariadna 3902
                'timelimit' => null,
1 efrain 3903
            ],
3904
            (object) [
3905
                // User override.
3906
                'assignid' => $instance->id,
3907
                'groupid' => null,
3908
                'userid' => $student3->id,
3909
                'sortorder' => null,
3910
                'allowsubmissionsfromdate' => 7,
3911
                'duedate' => 8,
3912
                'cutoffdate' => 9,
1441 ariadna 3913
                'timelimit' => null,
1 efrain 3914
            ],
3915
        ];
3916
 
3917
        foreach ($overrides as &$override) {
3918
            $override->id = $DB->insert_record('assign_overrides', $override);
3919
        }
3920
 
3921
        // User only in group 1 should see the group 1 override.
3922
        $this->assertEquals($overrides[0], $assign->override_exists($student1->id));
3923
 
3924
        // User only in group 2 should see the group 2 override.
3925
        $this->assertEquals($overrides[1], $assign->override_exists($student2->id));
3926
 
3927
        // User only in both groups with an override should see the user override as it has higher priority.
3928
        $this->assertEquals($overrides[2], $assign->override_exists($student3->id));
3929
 
3930
        // User only in both groups with no override should see the group 1 override as it has higher priority.
3931
        $this->assertEquals($overrides[0], $assign->override_exists($student4->id));
3932
 
3933
        // User with no overrides shoudl get nothing.
3934
        $override = $assign->override_exists($student5->id);
3935
        $this->assertNull($override->duedate);
3936
        $this->assertNull($override->cutoffdate);
3937
        $this->assertNull($override->allowsubmissionsfromdate);
3938
    }
3939
 
3940
    /**
3941
     * Test the quicksave grades processor
3942
     */
11 efrain 3943
    public function test_process_save_quick_grades(): void {
1 efrain 3944
        $this->resetAfterTest();
3945
        $course = $this->getDataGenerator()->create_course();
3946
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3947
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3948
 
3949
        $teacher->ignoresesskey = true;
3950
        $this->setUser($teacher);
3951
        $assign = $this->create_instance($course, [
1441 ariadna 3952
                'maxattempts' => ASSIGN_UNLIMITED_ATTEMPTS,
1 efrain 3953
                'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
3954
            ]);
3955
 
3956
        // Initially grade the user.
3957
        $grade = (object) [
3958
            'attemptnumber' => '',
3959
            'timemodified' => '',
3960
        ];
3961
        $data = [
3962
            "grademodified_{$student->id}" => $grade->timemodified,
3963
            "gradeattempt_{$student->id}" => $grade->attemptnumber,
3964
            "quickgrade_{$student->id}" => '60.0',
3965
        ];
3966
 
3967
        $result = $assign->testable_process_save_quick_grades($data);
3968
        $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
3969
        $grade = $assign->get_user_grade($student->id, false);
3970
        $this->assertEquals(60.0, $grade->grade);
3971
 
3972
        // Attempt to grade with a past attempts grade info.
3973
        $assign->testable_process_add_attempt($student->id);
1441 ariadna 3974
        $data = [
1 efrain 3975
            'grademodified_' . $student->id => $grade->timemodified,
3976
            'gradeattempt_' . $student->id => $grade->attemptnumber,
1441 ariadna 3977
            'quickgrade_' . $student->id => '50.0',
3978
        ];
1 efrain 3979
        $result = $assign->testable_process_save_quick_grades($data);
3980
        $this->assertStringContainsString(get_string('errorrecordmodified', 'assign'), $result);
3981
        $grade = $assign->get_user_grade($student->id, false);
3982
        $this->assertFalse($grade);
3983
 
3984
        // Attempt to grade a the attempt.
3985
        $submission = $assign->get_user_submission($student->id, false);
1441 ariadna 3986
        $data = [
1 efrain 3987
            'grademodified_' . $student->id => '',
3988
            'gradeattempt_' . $student->id => $submission->attemptnumber,
1441 ariadna 3989
            'quickgrade_' . $student->id => '40.0',
3990
        ];
1 efrain 3991
        $result = $assign->testable_process_save_quick_grades($data);
3992
        $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
3993
        $grade = $assign->get_user_grade($student->id, false);
3994
        $this->assertEquals(40.0, $grade->grade);
3995
 
3996
        // Catch grade update conflicts.
3997
        // Save old data for later.
3998
        $pastdata = $data;
3999
        // Update the grade the 'good' way.
1441 ariadna 4000
        $data = [
1 efrain 4001
            'grademodified_' . $student->id => $grade->timemodified,
4002
            'gradeattempt_' . $student->id => $grade->attemptnumber,
1441 ariadna 4003
            'quickgrade_' . $student->id => '30.0',
4004
        ];
1 efrain 4005
        $result = $assign->testable_process_save_quick_grades($data);
4006
        $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
4007
        $grade = $assign->get_user_grade($student->id, false);
4008
        $this->assertEquals(30.0, $grade->grade);
4009
 
4010
        // Now update using 'old' data. Should fail.
4011
        $result = $assign->testable_process_save_quick_grades($pastdata);
4012
        $this->assertStringContainsString(get_string('errorrecordmodified', 'assign'), $result);
4013
        $grade = $assign->get_user_grade($student->id, false);
4014
        $this->assertEquals(30.0, $grade->grade);
4015
    }
4016
 
4017
    /**
4018
     * Test updating activity completion when submitting an assessment.
4019
     */
11 efrain 4020
    public function test_update_activity_completion_records_solitary_submission(): void {
1 efrain 4021
        $this->resetAfterTest();
4022
 
4023
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
4024
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4025
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
4026
 
4027
        $this->setUser($teacher);
4028
        $assign = $this->create_instance($course, [
4029
            'grade' => 100,
4030
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
4031
            'requireallteammemberssubmit' => 0,
4032
        ]);
4033
        $cm = $assign->get_course_module();
4034
 
4035
        // Submit the assignment as the student.
4036
        $this->add_submission($student, $assign);
4037
 
4038
        // Check that completion is not met yet.
4039
        $completion = new \completion_info($course);
4040
        $completiondata = $completion->get_data($cm, false, $student->id);
4041
        $this->assertEquals(0, $completiondata->completionstate);
4042
 
4043
        // Update to mark as complete.
4044
        $submission = $assign->get_user_submission($student->id, true);
1441 ariadna 4045
        $assign->testable_update_activity_completion_records(
4046
            0,
4047
            0,
4048
            $submission,
4049
            $student->id,
4050
            COMPLETION_COMPLETE,
4051
            $completion
4052
        );
1 efrain 4053
 
4054
        // Completion should now be met.
4055
        $completiondata = $completion->get_data($cm, false, $student->id);
4056
        $this->assertEquals(1, $completiondata->completionstate);
4057
    }
4058
 
4059
    /**
4060
     * Test updating activity completion when submitting an assessment.
4061
     */
11 efrain 4062
    public function test_update_activity_completion_records_team_submission(): void {
1 efrain 4063
        $this->resetAfterTest();
4064
 
4065
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
4066
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4067
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
4068
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
4069
 
1441 ariadna 4070
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 4071
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
4072
 
4073
        groups_add_member($group1, $student);
4074
        groups_add_member($group1, $otherstudent);
4075
 
4076
        $assign = $this->create_instance($course, [
4077
            'grade' => 100,
4078
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
4079
            'teamsubmission' => 1,
4080
        ]);
4081
 
4082
        $cm = $assign->get_course_module();
4083
 
4084
        $this->add_submission($student, $assign);
4085
        $this->submit_for_grading($student, $assign, ['groupid' => $group1->id]);
4086
 
4087
        $completion = new \completion_info($course);
4088
 
4089
        // Check that completion is not met yet.
4090
        $completiondata = $completion->get_data($cm, false, $student->id);
4091
        $this->assertEquals(0, $completiondata->completionstate);
4092
 
4093
        $completiondata = $completion->get_data($cm, false, $otherstudent->id);
4094
        $this->assertEquals(0, $completiondata->completionstate);
4095
 
4096
        $submission = $assign->get_user_submission($student->id, true);
4097
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
4098
        $submission->groupid = $group1->id;
4099
 
4100
        $assign->testable_update_activity_completion_records(1, 0, $submission, $student->id, COMPLETION_COMPLETE, $completion);
4101
 
4102
        // Completion should now be met.
4103
        $completiondata = $completion->get_data($cm, false, $student->id);
4104
        $this->assertEquals(1, $completiondata->completionstate);
4105
 
4106
        $completiondata = $completion->get_data($cm, false, $otherstudent->id);
4107
        $this->assertEquals(1, $completiondata->completionstate);
4108
    }
4109
 
4110
    /**
4111
     * Test updating activity completion when submitting an assessment for MDL-67126.
4112
     */
11 efrain 4113
    public function test_update_activity_completion_records_team_submission_new(): void {
1 efrain 4114
        $this->resetAfterTest();
4115
 
4116
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
4117
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4118
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
4119
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
4120
 
1441 ariadna 4121
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1 efrain 4122
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
4123
 
4124
        groups_add_member($group1, $student);
4125
        groups_add_member($group1, $otherstudent);
4126
 
4127
        $assign = $this->create_instance($course, [
4128
            'submissiondrafts' => 0,
4129
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
4130
            'completionsubmit' => 1,
4131
            'teamsubmission' => 1,
1441 ariadna 4132
            'assignsubmission_onlinetext_enabled' => 1,
1 efrain 4133
        ]);
4134
 
4135
        $cm = $assign->get_course_module();
4136
 
4137
        $this->add_submission($student, $assign);
4138
 
4139
        $completion = new \completion_info($course);
4140
 
4141
        // Completion should now be met.
4142
        $completiondata = $completion->get_data($cm, false, $student->id);
4143
        $this->assertEquals(1, $completiondata->completionstate);
4144
 
4145
        $completiondata = $completion->get_data($cm, false, $otherstudent->id);
4146
        $this->assertEquals(1, $completiondata->completionstate);
4147
    }
4148
 
4149
    /**
4150
     * Data provider for test_fix_null_grades
4151
     * @return array[] Test data for test_fix_null_grades. Each element should contain grade, expectedcount and gradebookvalue
4152
     */
1441 ariadna 4153
    public static function fix_null_grades_provider(): array {
1 efrain 4154
        return [
4155
            'Negative less than one is errant' => [
4156
                'grade' => -0.64,
4157
                'gradebookvalue' => null,
4158
            ],
4159
            'Negative more than one is errant' => [
4160
                'grade' => -30.18,
4161
                'gradebookvalue' => null,
4162
            ],
4163
            'Negative one exactly is not errant, but shouldn\'t be pushed to gradebook' => [
4164
                'grade' => ASSIGN_GRADE_NOT_SET,
4165
                'gradebookvalue' => null,
4166
            ],
4167
            'Positive grade is not errant' => [
4168
                'grade' => 1,
4169
                'gradebookvalue' => 1,
4170
            ],
4171
            'Large grade is not errant' => [
4172
                'grade' => 100,
4173
                'gradebookvalue' => 100,
4174
            ],
4175
            'Zero grade is not errant' => [
4176
                'grade' => 0,
4177
                'gradebookvalue' => 0,
4178
            ],
4179
        ];
4180
    }
4181
 
4182
    /**
4183
     * Test fix_null_grades
4184
     * @param number $grade The grade we should set in the assign grading table.
4185
     * @param number $expectedcount The finalgrade we expect in the gradebook after fixing the grades.
4186
     * @dataProvider fix_null_grades_provider
4187
     */
11 efrain 4188
    public function test_fix_null_grades($grade, $gradebookvalue): void {
1 efrain 4189
        global $DB;
4190
 
4191
        $this->resetAfterTest();
4192
 
4193
        $course = $this->getDataGenerator()->create_course();
4194
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4195
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
4196
 
4197
        $this->setUser($teacher);
4198
        $assign = $this->create_instance($course);
4199
 
4200
        // Try getting a student's grade. This will give a grade of -1.
4201
        // Then we can override it with a bad negative grade.
4202
        $assign->get_user_grade($student->id, true);
4203
 
4204
        // Set the grade to something errant.
4205
        // We don't set the grader here, so we expect it to be -1 as a result.
4206
        $DB->set_field(
4207
            'assign_grades',
4208
            'grade',
4209
            $grade,
4210
            [
4211
                'userid' => $student->id,
4212
                'assignment' => $assign->get_instance()->id,
4213
            ]
4214
        );
4215
        $assign->grade = $grade;
4216
        $assigntemp = clone $assign->get_instance();
4217
        $assigntemp->cmidnumber = $assign->get_course_module()->idnumber;
4218
        assign_update_grades($assigntemp);
4219
 
4220
        // Check that the gradebook was updated with the assign grade. So we can guarentee test results later on.
4221
        $expectedgrade = $grade == -1 ? null : $grade; // Assign sends null to the gradebook for -1 grades.
1441 ariadna 4222
        $gradegrade = \grade_grade::fetch(['userid' => $student->id, 'itemid' => $assign->get_grade_item()->id]);
1 efrain 4223
        $this->assertEquals(-1, $gradegrade->usermodified);
4224
        $this->assertEquals($expectedgrade, $gradegrade->rawgrade);
4225
 
4226
        // Call fix_null_grades().
4227
        $method = new \ReflectionMethod(\assign::class, 'fix_null_grades');
4228
        $result = $method->invoke($assign);
4229
 
4230
        $this->assertSame(true, $result);
4231
 
1441 ariadna 4232
        $gradegrade = \grade_grade::fetch(['userid' => $student->id, 'itemid' => $assign->get_grade_item()->id]);
1 efrain 4233
 
4234
        $this->assertEquals(-1, $gradegrade->usermodified);
4235
        $this->assertEquals($gradebookvalue, $gradegrade->finalgrade);
4236
 
4237
        // Check that the grade was updated in the gradebook by fix_null_grades.
4238
        $this->assertEquals($gradebookvalue, $gradegrade->finalgrade);
4239
    }
4240
 
4241
    /**
4242
     * Test grade override displays 'Graded' for students
4243
     */
11 efrain 4244
    public function test_grade_submission_override(): void {
1 efrain 4245
        global $DB, $PAGE, $OUTPUT;
4246
 
4247
        $this->resetAfterTest();
4248
 
4249
        $course = $this->getDataGenerator()->create_course();
4250
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4251
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
4252
 
4253
        $this->setUser($teacher);
4254
        $assign = $this->create_instance($course, [
4255
            'assignsubmission_onlinetext_enabled' => 1,
4256
        ]);
4257
 
4258
        // Simulate adding a grade.
4259
        $this->setUser($teacher);
4260
        $data = new \stdClass();
4261
        $data->grade = '50.0';
4262
        $assign->testable_apply_grade_to_user($data, $student->id, 0);
4263
 
4264
        // Set grade override.
4265
        $gradegrade = \grade_grade::fetch([
4266
            'userid' => $student->id,
4267
            'itemid' => $assign->get_grade_item()->id,
4268
        ]);
4269
 
4270
        // Check that grade submission is not overridden yet.
4271
        $this->assertEquals(false, $gradegrade->is_overridden());
4272
 
4273
        // Simulate a submission.
4274
        $this->setUser($student);
4275
        $submission = $assign->get_user_submission($student->id, true);
4276
 
4277
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
4278
 
4279
        // Set override grade grade, and check that grade submission has been overridden.
4280
        $gradegrade->set_overridden(true);
4281
        $this->assertEquals(true, $gradegrade->is_overridden());
4282
 
4283
        // Check that submissionslocked message 'This assignment is not accepting submissions' does not appear for student.
4284
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
4285
        $output = $assign->get_renderer()->render($gradingtable);
4286
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
4287
 
4288
        $assignsubmissionstatus = $assign->get_assign_submission_status_renderable($student, true);
4289
        $output2 = $assign->get_renderer()->render($assignsubmissionstatus);
4290
 
4291
        // Check that submissionslocked 'This assignment is not accepting submissions' message does not appear for student.
4292
        $this->assertStringNotContainsString(get_string('submissionslocked', 'assign'), $output2);
4293
        // Check that submissionstatus_marked 'Graded' message does appear for student.
4294
        $this->assertStringContainsString(get_string('submissionstatus_marked', 'assign'), $output2);
4295
    }
4296
 
4297
    /**
4298
     * Test the result of get_filters is consistent.
4299
     */
11 efrain 4300
    public function test_get_filters(): void {
1 efrain 4301
        $this->resetAfterTest();
4302
 
4303
        $course = $this->getDataGenerator()->create_course();
4304
        $assign = $this->create_instance($course);
4305
        $valid = $assign->get_filters();
4306
 
1441 ariadna 4307
        $this->assertEquals(count($valid), 7);
1 efrain 4308
    }
4309
 
4310
    /**
4311
     * Test assign->get_instance() for a number of cases, as defined in the data provider.
4312
     *
4313
     * @dataProvider assign_get_instance_provider
4314
     * @param array $courseconfig the config to use when creating the course.
4315
     * @param array $assignconfig the config to use when creating the assignment.
4316
     * @param array $enrolconfig the config to use when enrolling the user (this will be the active user).
4317
     * @param array $expectedproperties an map containing the expected names and values for the assign instance data.
4318
     */
1441 ariadna 4319
    public function test_assign_get_instance(
4320
        array $courseconfig,
4321
        array $assignconfig,
4322
        array $enrolconfig,
4323
        array $expectedproperties
4324
    ): void {
1 efrain 4325
        $this->resetAfterTest();
4326
 
4327
        set_config('enablecourserelativedates', true); // Enable relative dates at site level.
4328
 
4329
        $course = $this->getDataGenerator()->create_course($courseconfig);
4330
        $assign = $this->create_instance($course, $assignconfig);
4331
        $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));
4332
 
4333
        $instance = $assign->get_instance($user->id);
4334
 
4335
        foreach ($expectedproperties as $propertyname => $propertyval) {
4336
            $this->assertEquals($propertyval, $instance->$propertyname);
4337
        }
4338
    }
4339
 
4340
    /**
4341
     * The test_assign_get_instance data provider.
1441 ariadna 4342
     *
4343
     * @return array[]
1 efrain 4344
     */
1441 ariadna 4345
    public static function assign_get_instance_provider(): array {
1 efrain 4346
        $timenow = time();
4347
 
4348
        // The get_default_instance() method shouldn't calculate any properties per-user. It should just return the record data.
4349
        // We'll confirm this works for a few different user types anyway, just like we do for get_instance().
4350
        return [
4351
            'Teacher whose enrolment starts after the course start date, relative dates mode enabled' => [
4352
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4353
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4354
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4355
                    'startdate' => $timenow - 8 * DAYSECS],
1441 ariadna 4356
                'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS],
1 efrain 4357
            ],
4358
            'Teacher whose enrolment starts before the course start date, relative dates mode enabled' => [
4359
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4360
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4361
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4362
                    'startdate' => $timenow - 12 * DAYSECS],
1441 ariadna 4363
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS],
1 efrain 4364
            ],
4365
            'Teacher whose enrolment starts after the course start date, relative dates mode disabled' => [
4366
                'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4367
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4368
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4369
                    'startdate' => $timenow - 8 * DAYSECS],
1441 ariadna 4370
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS],
1 efrain 4371
            ],
4372
            'Student whose enrolment starts after the course start date, relative dates mode enabled' => [
4373
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4374
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4375
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4376
                    'startdate' => $timenow - 8 * DAYSECS],
1441 ariadna 4377
                'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS],
1 efrain 4378
            ],
4379
            'Student whose enrolment starts before the course start date, relative dates mode enabled' => [
4380
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4381
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4382
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4383
                    'startdate' => $timenow - 12 * DAYSECS],
1441 ariadna 4384
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS],
1 efrain 4385
            ],
4386
            'Student whose enrolment starts after the course start date, relative dates mode disabled' => [
4387
                'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4388
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4389
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4390
                    'startdate' => $timenow - 8 * DAYSECS],
1441 ariadna 4391
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS],
1 efrain 4392
            ],
4393
        ];
4394
    }
4395
 
4396
    /**
4397
     * Test assign->get_default_instance() for a number of cases, as defined in the date provider.
4398
     *
4399
     * @dataProvider assign_get_default_instance_provider
4400
     * @param array $courseconfig the config to use when creating the course.
4401
     * @param array $assignconfig the config to use when creating the assignment.
4402
     * @param array $enrolconfig the config to use when enrolling the user (this will be the active user).
4403
     * @param array $expectedproperties an map containing the expected names and values for the assign instance data.
4404
     */
1441 ariadna 4405
    public function test_assign_get_default_instance(
4406
        array $courseconfig,
4407
        array $assignconfig,
4408
        array $enrolconfig,
4409
        array $expectedproperties
4410
    ): void {
1 efrain 4411
        $this->resetAfterTest();
4412
 
4413
        set_config('enablecourserelativedates', true); // Enable relative dates at site level.
4414
 
4415
        $course = $this->getDataGenerator()->create_course($courseconfig);
4416
        $assign = $this->create_instance($course, $assignconfig);
4417
        $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));
4418
 
4419
        $this->setUser($user);
4420
        $defaultinstance = $assign->get_default_instance();
4421
 
4422
        foreach ($expectedproperties as $propertyname => $propertyval) {
4423
            $this->assertEquals($propertyval, $defaultinstance->$propertyname);
4424
        }
4425
    }
4426
 
4427
    /**
4428
     * The test_assign_get_default_instance data provider.
1441 ariadna 4429
     *
4430
     * @return array[]
1 efrain 4431
     */
1441 ariadna 4432
    public static function assign_get_default_instance_provider(): array {
1 efrain 4433
        $timenow = time();
4434
 
4435
        // The get_default_instance() method shouldn't calculate any properties per-user. It should just return the record data.
4436
        // We'll confirm this works for a few different user types anyway, just like we do for get_instance().
4437
        return [
4438
            'Teacher whose enrolment starts after the course start date, relative dates mode enabled' => [
4439
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4440
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4441
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4442
                    'startdate' => $timenow - 8 * DAYSECS],
1441 ariadna 4443
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS],
1 efrain 4444
            ],
4445
            'Teacher whose enrolment starts before the course start date, relative dates mode enabled' => [
4446
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4447
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4448
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4449
                    'startdate' => $timenow - 12 * DAYSECS],
1441 ariadna 4450
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS],
1 efrain 4451
            ],
4452
            'Teacher whose enrolment starts after the course start date, relative dates mode disabled' => [
4453
                'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4454
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4455
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4456
                    'startdate' => $timenow - 8 * DAYSECS],
1441 ariadna 4457
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS],
1 efrain 4458
            ],
4459
            'Student whose enrolment starts after the course start date, relative dates mode enabled' => [
4460
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4461
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4462
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4463
                    'startdate' => $timenow - 8 * DAYSECS],
1441 ariadna 4464
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS],
1 efrain 4465
            ],
4466
        ];
4467
    }
4468
 
4469
    /**
4470
     * Test that cron task uses task API to get its last run time.
4471
     */
11 efrain 4472
    public function test_cron_use_task_api_to_get_lastruntime(): void {
1 efrain 4473
        global $DB;
4474
        $this->resetAfterTest();
4475
        $course = $this->getDataGenerator()->create_course();
4476
 
4477
        // Create an assignment which allows submissions from 3 days ago.
4478
        $assign1 = $this->create_instance($course, [
4479
            'duedate' => time() + DAYSECS,
4480
            'alwaysshowdescription' => 0,
4481
            'allowsubmissionsfromdate' => time() - 3 * DAYSECS,
4482
            'intro' => 'This one should not be re-created',
4483
        ]);
4484
 
4485
        // Create an assignment which allows submissions from 1 day ago.
4486
        $assign2 = $this->create_instance($course, [
4487
            'duedate' => time() + DAYSECS,
4488
            'alwaysshowdescription' => 0,
4489
            'allowsubmissionsfromdate' => time() - DAYSECS,
4490
            'intro' => 'This one should be re-created',
4491
        ]);
4492
 
4493
        // Set last run time 2 days ago.
4494
        $DB->set_field('task_scheduled', 'lastruntime', time() - 2 * DAYSECS, ['classname' => '\mod_assign\task\cron_task']);
4495
 
4496
        // Remove events to make sure cron will update calendar and re-create one of them.
1441 ariadna 4497
        $params = ['modulename' => 'assign', 'instance' => $assign1->get_instance()->id];
1 efrain 4498
        $DB->delete_records('event', $params);
1441 ariadna 4499
        $params = ['modulename' => 'assign', 'instance' => $assign2->get_instance()->id];
1 efrain 4500
        $DB->delete_records('event', $params);
4501
 
4502
        // Run cron.
4503
        \assign::cron();
4504
 
4505
        // Assert that calendar hasn't been updated for the first assignment as it's supposed to be
4506
        // updated as part of previous cron runs (allowsubmissionsfromdate is less than lastruntime).
1441 ariadna 4507
        $params = ['modulename' => 'assign', 'instance' => $assign1->get_instance()->id];
1 efrain 4508
        $event1 = $DB->get_record('event', $params);
4509
        $this->assertEmpty($event1);
4510
 
4511
        // Assert that calendar has been updated for the second assignment
4512
        // because its allowsubmissionsfromdate is greater than lastruntime.
1441 ariadna 4513
        $params = ['modulename' => 'assign', 'instance' => $assign2->get_instance()->id];
1 efrain 4514
        $event2 = $DB->get_record('event', $params);
4515
        $this->assertNotEmpty($event2);
4516
        $this->assertSame('This one should be re-created', $event2->description);
4517
    }
4518
 
4519
    /**
4520
     * Test that attachments should not be provided if \assign->show_intro returns false.
4521
     *
4522
     * @covers \assign::should_provide_intro_attachments
4523
     */
11 efrain 4524
    public function test_should_provide_intro_attachments_with_show_intro_disabled(): void {
1 efrain 4525
        $this->resetAfterTest();
4526
        $futuredate = time() + 300;
1441 ariadna 4527
        [$assign, $instance, $student] = $this->create_submission([
1 efrain 4528
            'alwaysshowdescription' => '0',
4529
            'allowsubmissionsfromdate' => $futuredate,
4530
        ]);
4531
        $this->assertFalse($assign->should_provide_intro_attachments($student->id));
4532
    }
4533
 
4534
    /**
4535
     * Test that attachments should be provided if user has capability to manage activity.
4536
     *
4537
     * @covers \assign::should_provide_intro_attachments
4538
     */
11 efrain 4539
    public function test_should_provide_intro_attachments_with_bypass_capability(): void {
1 efrain 4540
        $this->resetAfterTest();
1441 ariadna 4541
        [$assign, $instance, $student] = $this->create_submission([
1 efrain 4542
            'submissionattachments' => 1,
4543
        ]);
4544
        // Provide teaching role to student1 so they are able to bypass time limit restrictions on viewing attachments.
4545
        $this->getDataGenerator()->enrol_user($student->id, $instance->course, 'editingteacher');
4546
        $this->assertTrue($assign->should_provide_intro_attachments($student->id));
4547
    }
4548
 
4549
    /**
4550
     * Test that attachments should be provided if submissionattachments is disabled.
4551
     *
4552
     * @covers \assign::should_provide_intro_attachments
4553
     */
11 efrain 4554
    public function test_should_provide_intro_attachments_with_submissionattachments_disabled(): void {
1 efrain 4555
        $this->resetAfterTest();
1441 ariadna 4556
        [$assign, $instance, $student] = $this->create_submission();
1 efrain 4557
        $this->assertTrue($assign->should_provide_intro_attachments($student->id));
4558
    }
4559
 
4560
    /**
4561
     * Test that attachments should not be provided if submissionattachments is enabled with no open submission.
4562
     *
4563
     * @covers \assign::should_provide_intro_attachments
4564
     */
11 efrain 4565
    public function test_should_provide_intro_attachments_with_submissionattachments_enabled_and_submissions_closed(): void {
1 efrain 4566
        $this->resetAfterTest();
4567
        // Set cut-off date to the past.
1441 ariadna 4568
        [$assign, $instance, $student] = $this->create_submission([
1 efrain 4569
            'timelimit' => '300',
4570
            'submissionattachments' => 1,
4571
            'cutoffdate' => time() - 300,
4572
        ]);
4573
        $this->assertFalse($assign->should_provide_intro_attachments($student->id));
4574
    }
4575
 
4576
    /**
4577
     * Test that attachments should be provided if submissionattachments is enabled with an open submission.
4578
     *
4579
     * @covers \assign::should_provide_intro_attachments
4580
     */
11 efrain 4581
    public function test_should_provide_intro_attachments_submissionattachments_enabled_and_an_open_submission(): void {
1 efrain 4582
        $this->resetAfterTest();
4583
        set_config('enabletimelimit', '1', 'assign');
1441 ariadna 4584
        [$assign, $instance, $student] = $this->create_submission([
1 efrain 4585
            'timelimit' => '300',
4586
            'submissionattachments' => 1,
4587
        ]);
4588
 
4589
        // Open a submission.
4590
        $assign->get_user_submission($student->id, true);
4591
 
4592
        $this->assertTrue($assign->should_provide_intro_attachments($student->id));
4593
    }
4594
 
4595
    /**
4596
     * Test that a submission using a time limit is currently open.
4597
     *
4598
     * @covers \assign::is_attempt_in_progress
4599
     */
11 efrain 4600
    public function test_is_attempt_in_progress_with_open_submission(): void {
1 efrain 4601
        global $DB;
4602
        $this->resetAfterTest();
4603
        set_config('enabletimelimit', '1', 'assign');
1441 ariadna 4604
        [$assign, $instance, $student] = $this->create_submission([
1 efrain 4605
            'timelimit' => '300',
4606
        ]);
4607
        $submission = $assign->get_user_submission($student->id, true);
4608
        // Set a timestarted.
4609
        $submission->timestarted = time() - 300;
4610
        $DB->update_record('assign_submission', $submission);
4611
        $this->assertTrue($assign->is_attempt_in_progress());
4612
    }
4613
 
4614
    /**
4615
     * Test that a submission using a time limit is started without a start time.
4616
     *
4617
     * @covers \assign::is_attempt_in_progress
4618
     */
11 efrain 4619
    public function test_is_attempt_in_progress_with_open_submission_and_no_timestarted(): void {
1 efrain 4620
        $this->resetAfterTest();
4621
        set_config('enabletimelimit', '1', 'assign');
1441 ariadna 4622
        [$assign, $instance, $student] = $this->create_submission([
1 efrain 4623
            'timelimit' => '300',
4624
        ]);
4625
        $assign->get_user_submission($student->id, true);
4626
        $this->assertFalse($assign->is_attempt_in_progress());
4627
    }
4628
 
4629
    /**
4630
     * Test that a submission using a time limit is currently not open.
4631
     *
4632
     * @covers \assign::is_attempt_in_progress
4633
     */
11 efrain 4634
    public function test_is_attempt_in_progress_with_no_open_submission(): void {
1 efrain 4635
        global $DB;
4636
        $this->resetAfterTest();
4637
        set_config('enabletimelimit', '1', 'assign');
1441 ariadna 4638
        [$assign, $instance, $student] = $this->create_submission([
1 efrain 4639
            'timelimit' => '300',
4640
        ]);
4641
        // Clear all current submissions.
4642
        $DB->delete_records('assign_submission', ['assignment' => $instance->id]);
4643
        $this->assertFalse($assign->is_attempt_in_progress());
4644
    }
4645
 
4646
    /**
4647
     * Create a submission for testing.
4648
     * @param  array $params Optional params to use for creating assignment instance.
4649
     * @return array an array containing all the required data for testing
4650
     */
4651
    protected function create_submission(array $params = []) {
4652
        global $DB;
4653
 
4654
        // Create a course and assignment and users.
1441 ariadna 4655
        $course = self::getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1]);
1 efrain 4656
 
4657
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1441 ariadna 4658
        $params = array_merge([
1 efrain 4659
            'course' => $course->id,
4660
            'assignsubmission_file_maxfiles' => 1,
4661
            'assignsubmission_file_maxsizebytes' => 1024 * 1024,
4662
            'assignsubmission_onlinetext_enabled' => 1,
4663
            'assignsubmission_file_enabled' => 1,
4664
            'submissiondrafts' => 1,
4665
            'assignfeedback_file_enabled' => 1,
4666
            'assignfeedback_comments_enabled' => 1,
4667
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
1441 ariadna 4668
            'sendnotifications' => 0,
4669
        ], $params);
1 efrain 4670
 
4671
        set_config('submissionreceipts', 0, 'assign');
4672
 
4673
        $instance = $generator->create_instance($params);
4674
        $cm = get_coursemodule_from_instance('assign', $instance->id);
4675
        $context = \context_module::instance($cm->id);
4676
 
4677
        $assign = new \mod_assign_testable_assign($context, $cm, $course);
4678
 
4679
        $student = self::getDataGenerator()->create_user();
1441 ariadna 4680
        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
1 efrain 4681
        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
4682
 
4683
        $this->setUser($student);
4684
 
4685
        // Create a student1 with an online text submission.
4686
        // Simulate a submission.
4687
        $submission = $assign->get_user_submission($student->id, true);
4688
 
4689
        $data = new \stdClass();
1441 ariadna 4690
        $data->onlinetext_editor = [
1 efrain 4691
            'itemid' => file_get_unused_draft_itemid(),
4692
            'text' => 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>',
1441 ariadna 4693
            'format' => FORMAT_MOODLE];
1 efrain 4694
 
4695
        $draftidfile = file_get_unused_draft_itemid();
4696
        $usercontext = \context_user::instance($student->id);
1441 ariadna 4697
        $filerecord = [
1 efrain 4698
            'contextid' => $usercontext->id,
4699
            'component' => 'user',
4700
            'filearea'  => 'draft',
4701
            'itemid'    => $draftidfile,
4702
            'filepath'  => '/',
4703
            'filename'  => 't.txt',
1441 ariadna 4704
        ];
1 efrain 4705
        $fs = get_file_storage();
4706
        $fs->create_file_from_string($filerecord, 'text contents');
4707
 
4708
        $data->files_filemanager = $draftidfile;
4709
 
1441 ariadna 4710
        $notices = [];
1 efrain 4711
        $assign->save_submission($data, $notices);
4712
 
1441 ariadna 4713
        return [$assign, $instance, $student];
1 efrain 4714
    }
4715
 
4716
    /**
4717
     * Test user filtering by First name, Last name and Submission status.
4718
     *
4719
     * @covers \assign::is_userid_filtered
4720
     */
11 efrain 4721
    public function test_is_userid_filtered(): void {
1 efrain 4722
        $this->resetAfterTest();
4723
 
4724
        // Generate data and simulate student submissions.
4725
        $course = $this->getDataGenerator()->create_course();
4726
        $params1 = ['firstname' => 'Valentin', 'lastname' => 'Ivanov'];
4727
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student', $params1);
4728
        $params2 = ['firstname' => 'Nikolay', 'lastname' => 'Petrov'];
4729
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student', $params2);
4730
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
4731
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4732
        $this->setUser($student1);
4733
        $submission = $assign->get_user_submission($student1->id, true);
4734
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
4735
        $assign->testable_update_submission($submission, $student1->id, true, false);
4736
        $this->setUser($student2);
4737
        $submission = $assign->get_user_submission($student2->id, true);
4738
        $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
4739
        $assign->testable_update_submission($submission, $student2->id, true, false);
4740
        $this->setUser($teacher);
4741
 
4742
        // By default, both users should match filters.
4743
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4744
        $this->AssertTrue($assign->is_userid_filtered($student2->id));
4745
 
4746
        // Filter by First name starting with V.
4747
        $_GET['tifirst'] = 'V';
4748
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4749
        $this->AssertFalse($assign->is_userid_filtered($student2->id));
4750
 
4751
        // Add Last name to filter out both users.
4752
        $_GET['tilast'] = 'G';
4753
        $this->AssertFalse($assign->is_userid_filtered($student1->id));
4754
        $this->AssertFalse($assign->is_userid_filtered($student2->id));
4755
 
4756
        // Unsetting variables doesn't change behaviour because filters are stored in user preferences.
4757
        unset($_GET['tifirst']);
4758
        unset($_GET['tilast']);
4759
        $this->AssertFalse($assign->is_userid_filtered($student1->id));
4760
        $this->AssertFalse($assign->is_userid_filtered($student2->id));
4761
 
4762
        // Reset table preferences.
4763
        $_GET['treset'] = '1';
4764
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4765
        $this->AssertTrue($assign->is_userid_filtered($student2->id));
4766
 
4767
        // Display users with submitted submissions only.
4768
        set_user_preference('assign_filter', ASSIGN_SUBMISSION_STATUS_SUBMITTED);
4769
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4770
        $this->AssertFalse($assign->is_userid_filtered($student2->id));
4771
 
4772
        // Display users with drafts.
4773
        set_user_preference('assign_filter', ASSIGN_SUBMISSION_STATUS_DRAFT);
4774
        $this->AssertFalse($assign->is_userid_filtered($student1->id));
4775
        $this->AssertTrue($assign->is_userid_filtered($student2->id));
4776
 
4777
        // Reset the filter.
4778
        set_user_preference('assign_filter', '');
4779
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4780
        $this->AssertTrue($assign->is_userid_filtered($student2->id));
4781
    }
1441 ariadna 4782
 
4783
    /**
4784
     * Test get_error_messages like a public function.
4785
     *
4786
     * @covers \assign::get_error_messages
4787
     */
4788
    public function test_get_error_messages(): void {
4789
        $this->resetAfterTest();
4790
 
4791
        // Generate data.
4792
        $course = $this->getDataGenerator()->create_course();
4793
        $assign = $this->create_instance($course);
4794
 
4795
        // Get the empty error message list.
4796
        $result = $assign->get_error_messages();
4797
        $this->assertIsArray($result);
4798
        $this->assertEmpty($result);
4799
 
4800
        // Generate users.
4801
        $teacher  = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
4802
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
4803
 
4804
        // Set the teacher as user and try to delete the user's submission to create the error.
4805
        $this->setUser($teacher);
4806
        $assign->remove_submission($student1->id);
4807
 
4808
        // Get the created error messages.
4809
        $result = $assign->get_error_messages();
4810
        $this->assertIsArray($result);
4811
        $this->assertNotEmpty($result);
4812
    }
4813
 
4814
    /**
4815
     * Test that assignment grades are pushed to the gradebook when anonymous
4816
     * submissions and marking workflow are enabled (MDL-83195).
4817
     * @covers \assign::gradebook_item_update
4818
     */
4819
    public function test_release_grade_anon(): void {
4820
        $this->resetAfterTest();
4821
        $course = $this->getDataGenerator()->create_course();
4822
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4823
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
4824
 
4825
        $assign = $this->create_instance($course, [
4826
            'blindmarking' => 1,
4827
            'markingworkflow' => 1,
4828
            'markinganonymous' => 1,
4829
        ]);
4830
 
4831
        // Add a grade and change the workflow status to "Released".
4832
        $this->mark_submission($teacher, $assign, $student, 50.0,  [
4833
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
4834
        ]);
4835
 
4836
        // Make sure the grade has been pushed to the gradebook.
4837
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
4838
        $this->assertEquals(50, (int)$gradinginfo->items[0]->grades[$student->id]->grade);
4839
    }
1 efrain 4840
}