Proyectos de Subversion Moodle

Rev

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

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace mod_assign;
18
 
19
use core_external\external_api;
20
use core_external\external_settings;
21
use core_user_external;
22
use mod_assign_external;
23
use mod_assign_testable_assign;
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
global $CFG;
28
 
29
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
30
require_once($CFG->dirroot . '/mod/assign/externallib.php');
31
require_once($CFG->dirroot . '/mod/assign/tests/externallib_advanced_testcase.php');
32
require_once(__DIR__ . '/fixtures/testable_assign.php');
33
 
34
/**
35
 * External mod assign functions unit tests
36
 *
37
 * @package mod_assign
38
 * @category external
39
 * @copyright 2012 Paul Charsley
40
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41
 */
42
class externallib_test extends \mod_assign\externallib_advanced_testcase {
43
 
44
    /**
45
     * Test get_grades
46
     */
11 efrain 47
    public function test_get_grades(): void {
1 efrain 48
        global $DB, $USER;
49
 
50
        $this->resetAfterTest(true);
51
        // Create a course and assignment.
52
        $coursedata['idnumber'] = 'idnumbercourse';
53
        $coursedata['fullname'] = 'Lightwork Course';
54
        $coursedata['summary'] = 'Lightwork Course description';
55
        $coursedata['summaryformat'] = FORMAT_MOODLE;
56
        $course = self::getDataGenerator()->create_course($coursedata);
57
 
58
        $assigndata['course'] = $course->id;
59
        $assigndata['name'] = 'lightwork assignment';
60
 
61
        $assign = self::getDataGenerator()->create_module('assign', $assigndata);
62
 
63
        // Create a manual enrolment record.
64
        $manualenroldata['enrol'] = 'manual';
65
        $manualenroldata['status'] = 0;
66
        $manualenroldata['courseid'] = $course->id;
67
        $enrolid = $DB->insert_record('enrol', $manualenroldata);
68
 
69
        // Create a teacher and give them capabilities.
70
        $context = \context_course::instance($course->id);
71
        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
72
        $context = \context_module::instance($assign->cmid);
73
        $this->assignUserCapability('mod/assign:viewgrades', $context->id, $roleid);
74
 
75
        // Create the teacher's enrolment record.
76
        $userenrolmentdata['status'] = 0;
77
        $userenrolmentdata['enrolid'] = $enrolid;
78
        $userenrolmentdata['userid'] = $USER->id;
79
        $DB->insert_record('user_enrolments', $userenrolmentdata);
80
 
81
        // Create a student and give them 2 grades (for 2 attempts).
82
        $student = self::getDataGenerator()->create_user();
83
 
84
        $submission = new \stdClass();
85
        $submission->assignment = $assign->id;
86
        $submission->userid = $student->id;
87
        $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
88
        $submission->latest = 0;
89
        $submission->attemptnumber = 0;
90
        $submission->groupid = 0;
91
        $submission->timecreated = time();
92
        $submission->timemodified = time();
93
        $DB->insert_record('assign_submission', $submission);
94
 
95
        $grade = new \stdClass();
96
        $grade->assignment = $assign->id;
97
        $grade->userid = $student->id;
98
        $grade->timecreated = time();
99
        $grade->timemodified = $grade->timecreated;
100
        $grade->grader = $USER->id;
101
        $grade->grade = 50;
102
        $grade->attemptnumber = 0;
103
        $DB->insert_record('assign_grades', $grade);
104
 
105
        $submission = new \stdClass();
106
        $submission->assignment = $assign->id;
107
        $submission->userid = $student->id;
108
        $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
109
        $submission->latest = 1;
110
        $submission->attemptnumber = 1;
111
        $submission->groupid = 0;
112
        $submission->timecreated = time();
113
        $submission->timemodified = time();
114
        $DB->insert_record('assign_submission', $submission);
115
 
116
        $grade = new \stdClass();
117
        $grade->assignment = $assign->id;
118
        $grade->userid = $student->id;
119
        $grade->timecreated = time();
120
        $grade->timemodified = $grade->timecreated;
121
        $grade->grader = $USER->id;
122
        $grade->grade = 75;
123
        $grade->attemptnumber = 1;
124
        $DB->insert_record('assign_grades', $grade);
125
 
126
        $assignmentids[] = $assign->id;
127
        $result = mod_assign_external::get_grades($assignmentids);
128
 
129
        // We need to execute the return values cleaning process to simulate the web service server.
130
        $result = external_api::clean_returnvalue(mod_assign_external::get_grades_returns(), $result);
131
 
132
        // Check that the correct grade information for the student is returned.
133
        $this->assertEquals(1, count($result['assignments']));
134
        $assignment = $result['assignments'][0];
135
        $this->assertEquals($assign->id, $assignment['assignmentid']);
136
        // Should only get the last grade for this student.
137
        $this->assertEquals(1, count($assignment['grades']));
138
        $grade = $assignment['grades'][0];
139
        $this->assertEquals($student->id, $grade['userid']);
140
        // Should be the last grade (not the first).
141
        $this->assertEquals(75, $grade['grade']);
142
    }
143
 
144
    /**
145
     * Test get_assignments
146
     */
11 efrain 147
    public function test_get_assignments(): void {
1 efrain 148
        global $DB, $USER, $CFG;
149
 
150
        $this->resetAfterTest(true);
151
 
152
        // Enable multilang filter to on content and heading.
153
        filter_set_global_state('multilang', TEXTFILTER_ON);
154
        filter_set_applies_to_strings('multilang', 1);
155
        // Set WS filtering.
156
        $wssettings = external_settings::get_instance();
157
        $wssettings->set_filter(true);
158
 
159
        $category = self::getDataGenerator()->create_category(array(
160
            'name' => 'Test category'
161
        ));
162
 
163
        // Create a course.
164
        $course1 = self::getDataGenerator()->create_course(array(
165
            'idnumber' => 'idnumbercourse1',
166
            'fullname' => '<b>Lightwork Course 1</b>',      // Adding tags here to check that \core_external\util::format_string works.
167
            'shortname' => '<b>Lightwork Course 1</b>',     // Adding tags here to check that \core_external\util::format_string works.
168
            'summary' => 'Lightwork Course 1 description',
169
            'summaryformat' => FORMAT_MOODLE,
170
            'category' => $category->id
171
        ));
172
 
173
        // Create a second course, just for testing.
174
        $course2 = self::getDataGenerator()->create_course(array(
175
            'idnumber' => 'idnumbercourse2',
176
            'fullname' => 'Lightwork Course 2',
177
            'summary' => 'Lightwork Course 2 description',
178
            'summaryformat' => FORMAT_MOODLE,
179
            'category' => $category->id
180
        ));
181
 
182
        // Create the assignment module with links to a filerecord.
183
        $assign1 = self::getDataGenerator()->create_module('assign', array(
184
            'course' => $course1->id,
185
            'name' => '<span lang="en" class="multilang">English</span><span lang="es" class="multilang">Español</span>',
186
            'intro' => 'the assignment intro text here <a href="@@PLUGINFILE@@/intro.txt">link</a>',
187
            'introformat' => FORMAT_HTML,
188
            'markingworkflow' => 1,
189
            'markingallocation' => 1,
190
            'blindmarking' => 1,
191
            'markinganonymous' => 1,
192
            'activityeditor' => [
193
                'text' => 'Test activity',
194
                'format' => 1,
195
            ],
196
        ));
197
 
198
        // Add a file as assignment attachment.
199
        $context = \context_module::instance($assign1->cmid);
200
        $filerecord = array('component' => 'mod_assign', 'filearea' => 'intro', 'contextid' => $context->id, 'itemid' => 0,
201
                'filename' => 'intro.txt', 'filepath' => '/');
202
        $fs = get_file_storage();
203
        $fs->create_file_from_string($filerecord, 'Test intro file');
204
 
205
        // Create manual enrolment record.
206
        $enrolid = $DB->insert_record('enrol', (object)array(
207
            'enrol' => 'manual',
208
            'status' => 0,
209
            'courseid' => $course1->id
210
        ));
211
 
212
        // Create the user and give them capabilities.
213
        $context = \context_course::instance($course1->id);
214
        $roleid = $this->assignUserCapability('moodle/course:view', $context->id);
215
        $context = \context_module::instance($assign1->cmid);
216
        $this->assignUserCapability('mod/assign:view', $context->id, $roleid);
217
 
218
        // Create the user enrolment record.
219
        $DB->insert_record('user_enrolments', (object)array(
220
            'status' => 0,
221
            'enrolid' => $enrolid,
222
            'userid' => $USER->id
223
        ));
224
 
225
        // Add a file as assignment attachment.
226
        $filerecord = array('component' => 'mod_assign', 'filearea' => ASSIGN_INTROATTACHMENT_FILEAREA,
227
                'contextid' => $context->id, 'itemid' => 0,
228
                'filename' => 'introattachment.txt', 'filepath' => '/');
229
        $fs = get_file_storage();
230
        $fs->create_file_from_string($filerecord, 'Test intro attachment file');
231
 
232
        $result = mod_assign_external::get_assignments();
233
 
234
        // We need to execute the return values cleaning process to simulate the web service server.
235
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
236
 
237
        // Check the course and assignment are returned.
238
        $this->assertEquals(1, count($result['courses']));
239
        $course = $result['courses'][0];
240
        $this->assertEquals('Lightwork Course 1', $course['fullname']);
241
        $this->assertEquals('Lightwork Course 1', $course['shortname']);
242
        $this->assertEquals(1, count($course['assignments']));
243
        $assignment = $course['assignments'][0];
244
        $this->assertEquals($assign1->id, $assignment['id']);
245
        $this->assertEquals($course1->id, $assignment['course']);
246
        $this->assertEquals('English', $assignment['name']);
247
        $this->assertStringContainsString('the assignment intro text here', $assignment['intro']);
248
        $this->assertNotEmpty($assignment['configs']);
249
        // Check the url of the file attatched.
250
        $this->assertMatchesRegularExpression(
251
            '@"' . $CFG->wwwroot . '/webservice/pluginfile.php/\d+/mod_assign/intro/intro\.txt"@', $assignment['intro']);
252
        $this->assertEquals(1, $assignment['markingworkflow']);
253
        $this->assertEquals(1, $assignment['markingallocation']);
254
        $this->assertEquals(1, $assignment['blindmarking']);
255
        $this->assertEquals(1, $assignment['markinganonymous']);
256
        $this->assertEquals(0, $assignment['preventsubmissionnotingroup']);
257
        $this->assertEquals(0, $assignment['timelimit']);
258
        $this->assertEquals(0, $assignment['submissionattachments']);
259
        $this->assertEquals('Test activity', $assignment['activity']);
260
        $this->assertEquals(1, $assignment['activityformat']);
261
        $this->assertEmpty($assignment['activityattachments']);
262
 
263
        $this->assertCount(1, $assignment['introattachments']);
264
        $this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);
265
 
266
        // Now, hide the descritption until the submission from date.
267
        $DB->set_field('assign', 'alwaysshowdescription', 0, array('id' => $assign1->id));
268
        $DB->set_field('assign', 'allowsubmissionsfromdate', time() + DAYSECS, array('id' => $assign1->id));
269
 
270
        $result = mod_assign_external::get_assignments(array($course1->id));
271
 
272
        // We need to execute the return values cleaning process to simulate the web service server.
273
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
274
 
275
        $this->assertEquals(1, count($result['courses']));
276
        $course = $result['courses'][0];
277
        $this->assertEquals('Lightwork Course 1', $course['fullname']);
278
        $this->assertEquals(1, count($course['assignments']));
279
        $assignment = $course['assignments'][0];
280
        $this->assertEquals($assign1->id, $assignment['id']);
281
        $this->assertEquals($course1->id, $assignment['course']);
282
        $this->assertEquals('English', $assignment['name']);
283
        $this->assertArrayNotHasKey('intro', $assignment);
284
        $this->assertArrayNotHasKey('introattachments', $assignment);
285
        $this->assertEquals(1, $assignment['markingworkflow']);
286
        $this->assertEquals(1, $assignment['markingallocation']);
287
        $this->assertEquals(1, $assignment['blindmarking']);
288
        $this->assertEquals(1, $assignment['markinganonymous']);
289
        $this->assertEquals(0, $assignment['preventsubmissionnotingroup']);
290
 
291
        $result = mod_assign_external::get_assignments(array($course2->id));
292
 
293
        // We need to execute the return values cleaning process to simulate the web service server.
294
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
295
 
296
        $this->assertEquals(0, count($result['courses']));
297
        $this->assertEquals(1, count($result['warnings']));
298
 
299
        // Test with non-enrolled user, but with view capabilities.
300
        $this->setAdminUser();
301
        $result = mod_assign_external::get_assignments();
302
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
303
        $this->assertEquals(0, count($result['courses']));
304
        $this->assertEquals(0, count($result['warnings']));
305
 
306
        // Expect no courses, because we are not using the special flag.
307
        $result = mod_assign_external::get_assignments(array($course1->id));
308
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
309
        $this->assertCount(0, $result['courses']);
310
 
311
        // Now use the special flag to return courses where you are not enroled in.
312
        $result = mod_assign_external::get_assignments(array($course1->id), array(), true);
313
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
314
        $this->assertCount(1, $result['courses']);
315
 
316
        $course = $result['courses'][0];
317
        $this->assertEquals('Lightwork Course 1', $course['fullname']);
318
        $this->assertEquals(1, count($course['assignments']));
319
        $assignment = $course['assignments'][0];
320
        $this->assertEquals($assign1->id, $assignment['id']);
321
        $this->assertEquals($course1->id, $assignment['course']);
322
        $this->assertEquals('English', $assignment['name']);
323
        $this->assertArrayNotHasKey('intro', $assignment);
324
        $this->assertArrayNotHasKey('introattachments', $assignment);
325
        $this->assertEquals(1, $assignment['markingworkflow']);
326
        $this->assertEquals(1, $assignment['markingallocation']);
327
        $this->assertEquals(1, $assignment['blindmarking']);
328
        $this->assertEquals(1, $assignment['markinganonymous']);
329
        $this->assertEquals(0, $assignment['preventsubmissionnotingroup']);
330
    }
331
 
332
    /**
333
     * Test get_assignments with submissionstatement.
334
     */
11 efrain 335
    public function test_get_assignments_with_submissionstatement(): void {
1 efrain 336
        global $DB, $USER, $CFG;
337
 
338
        $this->resetAfterTest(true);
339
 
340
        // Setup test data. Create 2 assigns, one with requiresubmissionstatement and the other without it.
341
        $course = $this->getDataGenerator()->create_course();
342
        $assign = $this->getDataGenerator()->create_module('assign', array(
343
            'course' => $course->id,
344
            'requiresubmissionstatement' => 1
345
        ));
346
        $assign2 = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
347
 
348
        // Create student.
349
        $student = self::getDataGenerator()->create_user();
350
 
351
        // Users enrolments.
352
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
353
        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
354
 
355
        // Update the submissionstatement.
356
        $submissionstatement = 'This is a fake submission statement.';
357
        set_config('submissionstatement', $submissionstatement, 'assign');
358
 
359
        $this->setUser($student);
360
 
361
        $result = mod_assign_external::get_assignments();
362
        // We need to execute the return values cleaning process to simulate the web service server.
363
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
364
 
365
        // Check that the amount of courses and assignments is right.
366
        $this->assertCount(1, $result['courses']);
367
        $assignmentsret = $result['courses'][0]['assignments'];
368
        $this->assertCount(2, $assignmentsret);
369
 
370
        // Order the returned assignments by ID.
371
        usort($assignmentsret, function($a, $b) {
372
            return strcmp($a['id'], $b['id']);
373
        });
374
 
375
        // Check that the first assign contains the submission statement.
376
        $assignmentret = $assignmentsret[0];
377
        $this->assertEquals($assign->id, $assignmentret['id']);
378
        $this->assertEquals(1, $assignmentret['requiresubmissionstatement']);
379
        $this->assertEquals($submissionstatement, $assignmentret['submissionstatement']);
380
 
381
        // Check that the second assign does NOT contain the submission statement.
382
        $assignmentret = $assignmentsret[1];
383
        $this->assertEquals($assign2->id, $assignmentret['id']);
384
        $this->assertEquals(0, $assignmentret['requiresubmissionstatement']);
385
        $this->assertArrayNotHasKey('submissionstatement', $assignmentret);
386
    }
387
 
388
    /**
389
     * Test that get_assignments does not return intro attachments if submissionattachments enabled and there is no open submission.
390
     *
391
     * @covers \mod_assign_external::get_assignments
392
     */
11 efrain 393
    public function test_get_assignments_when_submissionattachments_is_enabled(): void {
1 efrain 394
        global $DB;
395
 
396
        $this->resetAfterTest(true);
397
 
398
        // Create data.
399
        $course1 = $this->getDataGenerator()->create_course();
400
        $user = $this->getDataGenerator()->create_user();
401
        $this->setUser($user);
402
        // First set submissionattachments to empty.
403
        $assign = self::getDataGenerator()->create_module('assign', array(
404
            'course' => $course1->id,
405
            'name' => 'Test assignment',
406
            'intro' => 'The assignment intro text here',
407
            'introformat' => FORMAT_HTML,
408
            'submissionattachments' => 0,
409
        ));
410
        $context = \context_module::instance($assign->cmid);
411
 
412
        // Enrol user as student.
413
        $this->getDataGenerator()->enrol_user($user->id, $course1->id, 'student');
414
 
415
        // Add a file as assignment attachment.
416
        $filerecord = array('component' => 'mod_assign', 'filearea' => ASSIGN_INTROATTACHMENT_FILEAREA,
417
            'contextid' => $context->id, 'itemid' => 0,
418
            'filename' => 'introattachment.txt', 'filepath' => '/');
419
        $fs = get_file_storage();
420
        $fs->create_file_from_string($filerecord, 'Test intro attachment file');
421
 
422
        // We need to execute the return values cleaning process to simulate the web service server.
423
        $result = mod_assign_external::get_assignments();
424
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
425
 
426
        $this->assertEquals(1, count($result['courses']));
427
        $course = $result['courses'][0];
428
        $this->assertEquals(1, count($course['assignments']));
429
        $assignment = $course['assignments'][0];
430
        $this->assertCount(1, $assignment['introattachments']);
431
        $this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);
432
 
433
        // Now set submissionattachments to enabled. We will close assignment so assume intro attachments are not sent.
434
        $DB->set_field('assign', 'submissionattachments', '1', ['id' => $assignment['id']]);
435
        $DB->set_field('assign', 'cutoffdate', time() - 300, ['id' => $assignment['id']]);
436
 
437
        // We need to execute the return values cleaning process to simulate the web service server.
438
        $result = mod_assign_external::get_assignments();
439
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
440
 
441
        $this->assertEquals(1, count($result['courses']));
442
        $course = $result['courses'][0];
443
        $this->assertEquals(1, count($course['assignments']));
444
        $assignment = $course['assignments'][0];
445
        $this->assertArrayNotHasKey('introattachments', $assignment);
446
 
447
        // Enrol a user as a teacher to override the setting.
448
        $this->getDataGenerator()->enrol_user($user->id, $course1->id, 'editingteacher');
449
 
450
        // We need to execute the return values cleaning process to simulate the web service server.
451
        $result = mod_assign_external::get_assignments();
452
        $result = external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
453
 
454
        $this->assertEquals(1, count($result['courses']));
455
        $course = $result['courses'][0];
456
        $this->assertEquals(1, count($course['assignments']));
457
        $assignment = $course['assignments'][0];
458
        $this->assertCount(1, $assignment['introattachments']);
459
        $this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);
460
    }
461
 
462
    /**
463
     * Test get_submissions
464
     */
11 efrain 465
    public function test_get_submissions(): void {
1 efrain 466
        global $DB, $USER;
467
 
468
        $this->resetAfterTest(true);
469
        // Create a course and assignment.
470
        $coursedata['idnumber'] = 'idnumbercourse1';
471
        $coursedata['fullname'] = 'Lightwork Course 1';
472
        $coursedata['summary'] = 'Lightwork Course 1 description';
473
        $coursedata['summaryformat'] = FORMAT_MOODLE;
474
        $course1 = self::getDataGenerator()->create_course($coursedata);
475
 
476
        $assigndata['course'] = $course1->id;
477
        $assigndata['name'] = 'lightwork assignment';
478
 
479
        $assign1 = self::getDataGenerator()->create_module('assign', $assigndata);
480
 
481
        // Create a student with an online text submission.
482
        // First attempt.
483
        $student = self::getDataGenerator()->create_user();
484
        $teacher = self::getDataGenerator()->create_user();
485
        $submission = new \stdClass();
486
        $submission->assignment = $assign1->id;
487
        $submission->userid = $student->id;
488
        $submission->timecreated = time();
489
        $submission->timemodified = $submission->timecreated;
490
        $submission->timestarted = $submission->timecreated;;
491
        $submission->status = 'draft';
492
        $submission->attemptnumber = 0;
493
        $submission->latest = 0;
494
        $sid = $DB->insert_record('assign_submission', $submission);
495
 
496
        // Second attempt.
497
        $now = time();
498
        $submission = new \stdClass();
499
        $submission->assignment = $assign1->id;
500
        $submission->userid = $student->id;
501
        $submission->timecreated = $now;
502
        $submission->timemodified = $now;
503
        $submission->timestarted = $now;
504
        $submission->status = 'submitted';
505
        $submission->attemptnumber = 1;
506
        $submission->latest = 1;
507
        $sid = $DB->insert_record('assign_submission', $submission);
508
        $submission->id = $sid;
509
 
510
        $onlinetextsubmission = new \stdClass();
511
        $onlinetextsubmission->onlinetext = "<p>online test text</p>";
512
        $onlinetextsubmission->onlineformat = 1;
513
        $onlinetextsubmission->submission = $submission->id;
514
        $onlinetextsubmission->assignment = $assign1->id;
515
        $DB->insert_record('assignsubmission_onlinetext', $onlinetextsubmission);
516
 
517
        // Enrol the teacher in the course.
518
        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
519
        $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id);
520
        $this->setUser($teacher);
521
 
522
        $assignmentids[] = $assign1->id;
523
        $result = mod_assign_external::get_submissions($assignmentids);
524
        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
525
 
526
        // Check the online text submission is NOT returned because the student is not yet enrolled in the course.
527
        $this->assertEquals(1, count($result['assignments']));
528
        $assignment = $result['assignments'][0];
529
        $this->assertEquals($assign1->id, $assignment['assignmentid']);
530
        $this->assertEquals(0, count($assignment['submissions']));
531
 
532
        // Enrol the student in the course.
533
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
534
        $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id);
535
 
536
        $result = mod_assign_external::get_submissions($assignmentids);
537
        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
538
 
539
        $this->assertEquals(1, count($result['assignments']));
540
        $assignment = $result['assignments'][0];
541
        $this->assertEquals($assign1->id, $assignment['assignmentid']);
542
        // Now, we get the submission because the user is enrolled.
543
        $this->assertEquals(1, count($assignment['submissions']));
544
        $submission = $assignment['submissions'][0];
545
        $this->assertEquals($sid, $submission['id']);
546
        $this->assertCount(1, $submission['plugins']);
547
        $this->assertEquals('notgraded', $submission['gradingstatus']);
548
        $this->assertEquals($now, $submission['timestarted']);
549
 
550
        // Test locking the context.
551
        set_config('contextlocking', 1);
552
        $context = \context_course::instance($course1->id);
553
        $context->set_locked(true);
554
 
555
        $this->setUser($teacher);
556
        $assignmentids[] = $assign1->id;
557
        $result = mod_assign_external::get_submissions($assignmentids);
558
        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
559
        $this->assertEquals(1, count($result['assignments']));
560
    }
561
 
562
    /**
563
     * Test get_submissions with teamsubmission enabled
564
     */
11 efrain 565
    public function test_get_submissions_group_submission(): void {
1 efrain 566
        global $DB;
567
 
568
        $this->resetAfterTest(true);
569
 
570
        $result = $this->create_assign_with_student_and_teacher(array(
571
            'assignsubmission_onlinetext_enabled' => 1,
572
            'teamsubmission' => 1
573
        ));
574
        $assignmodule = $result['assign'];
575
        $student = $result['student'];
576
        $teacher = $result['teacher'];
577
        $course = $result['course'];
578
        $context = \context_course::instance($course->id);
579
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
580
        $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
581
        $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
582
        $context = \context_module::instance($cm->id);
583
        $assign = new mod_assign_testable_assign($context, $cm, $course);
584
 
585
        groups_add_member($group, $student);
586
 
587
        $this->setUser($student);
588
        $submission = $assign->get_group_submission($student->id, $group->id, true);
589
        $sid = $submission->id;
590
 
591
        $this->setUser($teacher);
592
 
593
        $assignmentids[] = $assignmodule->id;
594
        $result = mod_assign_external::get_submissions($assignmentids);
595
        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
596
 
597
        $this->assertEquals(1, count($result['assignments']));
598
        $assignment = $result['assignments'][0];
599
        $this->assertEquals($assignmodule->id, $assignment['assignmentid']);
600
        $this->assertEquals(1, count($assignment['submissions']));
601
        $submission = $assignment['submissions'][0];
602
        $this->assertEquals($sid, $submission['id']);
603
        $this->assertEquals($group->id, $submission['groupid']);
604
        $this->assertEquals(0, $submission['userid']);
605
    }
606
 
607
    /**
608
     * Test get_submissions with teamsubmission enabled
609
     * and a group having a higher attemptnumber than another
610
     */
11 efrain 611
    public function test_get_submissions_group_submission_attemptnumber(): void {
1 efrain 612
        global $DB;
613
        $this->resetAfterTest(true);
614
 
615
        $result = $this->create_assign_with_student_and_teacher([
616
            'assignsubmission_onlinetext_enabled' => 1,
617
            'attemptreopenmethod' => 'manual',
618
            'teamsubmission' => 1,
619
        ]);
620
        $assignmodule = $result['assign'];
621
        $course = $result['course'];
622
 
623
        $teacher = $result['teacher'];
624
        $student1 = $result['student'];
625
        $student2 = self::getDataGenerator()->create_user();
626
 
627
        // Enrol second user into the course.
628
        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
629
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
630
 
631
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
632
        groups_add_member($group1, $student1);
633
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
634
        groups_add_member($group2, $student2);
635
 
636
        $this->setUser($student1);
637
        mod_assign_external::save_submission(
638
            $assignmodule->id,
639
            [
640
                'onlinetext_editor' => [
641
                    'text' => 'Group 1, Submission 1',
642
                    'format' => FORMAT_PLAIN,
643
                    'itemid' => file_get_unused_draft_itemid(),
644
                ]
645
            ]
646
        );
647
        $this->setUser($student2);
648
        mod_assign_external::save_submission(
649
            $assignmodule->id,
650
            [
651
                'onlinetext_editor' => [
652
                    'text' => 'Group 2, Submission 1',
653
                    'format' => FORMAT_PLAIN,
654
                    'itemid' => file_get_unused_draft_itemid(),
655
                ]
656
            ]
657
        );
658
        mod_assign_external::submit_for_grading($assignmodule->id, 1);
659
        $this->setUser($teacher);
660
        mod_assign_external::save_grade($assignmodule->id, $student2->id, 0, -1, 1, "", 1);
661
        $this->setUser($student2);
662
        mod_assign_external::save_submission(
663
            $assignmodule->id,
664
            [
665
                'onlinetext_editor' => [
666
                    'text' => 'Group 2, Submission 2',
667
                    'format' => FORMAT_PLAIN,
668
                    'itemid' => file_get_unused_draft_itemid(),
669
                ]
670
            ]
671
        );
672
 
673
        $this->setUser($teacher);
674
        $result = mod_assign_external::get_submissions([$assignmodule->id]);
675
        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
676
 
677
        $this->assertEquals(1, count($result['assignments']));
678
        [$assignment] = $result['assignments'];
679
        $this->assertEquals($assignmodule->id, $assignment['assignmentid']);
680
 
681
        $this->assertEquals(2, count($assignment['submissions']));
682
        $expectedsubmissions = ['Group 1, Submission 1', 'Group 2, Submission 2'];
683
        foreach ($assignment['submissions'] as $submission) {
684
            $this->assertContains($submission['plugins'][0]['editorfields'][0]['text'], $expectedsubmissions);
685
        }
686
    }
687
 
688
    /**
689
     * Test get_user_flags
690
     */
11 efrain 691
    public function test_get_user_flags(): void {
1 efrain 692
        global $DB, $USER;
693
 
694
        $this->resetAfterTest(true);
695
        // Create a course and assignment.
696
        $coursedata['idnumber'] = 'idnumbercourse';
697
        $coursedata['fullname'] = 'Lightwork Course';
698
        $coursedata['summary'] = 'Lightwork Course description';
699
        $coursedata['summaryformat'] = FORMAT_MOODLE;
700
        $course = self::getDataGenerator()->create_course($coursedata);
701
 
702
        $assigndata['course'] = $course->id;
703
        $assigndata['name'] = 'lightwork assignment';
704
 
705
        $assign = self::getDataGenerator()->create_module('assign', $assigndata);
706
 
707
        // Create a manual enrolment record.
708
        $manualenroldata['enrol'] = 'manual';
709
        $manualenroldata['status'] = 0;
710
        $manualenroldata['courseid'] = $course->id;
711
        $enrolid = $DB->insert_record('enrol', $manualenroldata);
712
 
713
        // Create a teacher and give them capabilities.
714
        $context = \context_course::instance($course->id);
715
        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
716
        $context = \context_module::instance($assign->cmid);
717
        $this->assignUserCapability('mod/assign:grade', $context->id, $roleid);
718
 
719
        // Create the teacher's enrolment record.
720
        $userenrolmentdata['status'] = 0;
721
        $userenrolmentdata['enrolid'] = $enrolid;
722
        $userenrolmentdata['userid'] = $USER->id;
723
        $DB->insert_record('user_enrolments', $userenrolmentdata);
724
 
725
        // Create a student and give them a user flag record.
726
        $student = self::getDataGenerator()->create_user();
727
        $userflag = new \stdClass();
728
        $userflag->assignment = $assign->id;
729
        $userflag->userid = $student->id;
730
        $userflag->locked = 0;
731
        $userflag->mailed = 0;
732
        $userflag->extensionduedate = 0;
733
        $userflag->workflowstate = 'inmarking';
734
        $userflag->allocatedmarker = $USER->id;
735
 
736
        $DB->insert_record('assign_user_flags', $userflag);
737
 
738
        $assignmentids[] = $assign->id;
739
        $result = mod_assign_external::get_user_flags($assignmentids);
740
 
741
        // We need to execute the return values cleaning process to simulate the web service server.
742
        $result = external_api::clean_returnvalue(mod_assign_external::get_user_flags_returns(), $result);
743
 
744
        // Check that the correct user flag information for the student is returned.
745
        $this->assertEquals(1, count($result['assignments']));
746
        $assignment = $result['assignments'][0];
747
        $this->assertEquals($assign->id, $assignment['assignmentid']);
748
        // Should be one user flag record.
749
        $this->assertEquals(1, count($assignment['userflags']));
750
        $userflag = $assignment['userflags'][0];
751
        $this->assertEquals($student->id, $userflag['userid']);
752
        $this->assertEquals(0, $userflag['locked']);
753
        $this->assertEquals(0, $userflag['mailed']);
754
        $this->assertEquals(0, $userflag['extensionduedate']);
755
        $this->assertEquals('inmarking', $userflag['workflowstate']);
756
        $this->assertEquals($USER->id, $userflag['allocatedmarker']);
757
    }
758
 
759
    /**
760
     * Test get_user_mappings
761
     */
11 efrain 762
    public function test_get_user_mappings(): void {
1 efrain 763
        global $DB, $USER;
764
 
765
        $this->resetAfterTest(true);
766
        // Create a course and assignment.
767
        $coursedata['idnumber'] = 'idnumbercourse';
768
        $coursedata['fullname'] = 'Lightwork Course';
769
        $coursedata['summary'] = 'Lightwork Course description';
770
        $coursedata['summaryformat'] = FORMAT_MOODLE;
771
        $course = self::getDataGenerator()->create_course($coursedata);
772
 
773
        $assigndata['course'] = $course->id;
774
        $assigndata['name'] = 'lightwork assignment';
775
 
776
        $assign = self::getDataGenerator()->create_module('assign', $assigndata);
777
 
778
        // Create a manual enrolment record.
779
        $manualenroldata['enrol'] = 'manual';
780
        $manualenroldata['status'] = 0;
781
        $manualenroldata['courseid'] = $course->id;
782
        $enrolid = $DB->insert_record('enrol', $manualenroldata);
783
 
784
        // Create a teacher and give them capabilities.
785
        $context = \context_course::instance($course->id);
786
        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
787
        $context = \context_module::instance($assign->cmid);
788
        $this->assignUserCapability('mod/assign:revealidentities', $context->id, $roleid);
789
 
790
        // Create the teacher's enrolment record.
791
        $userenrolmentdata['status'] = 0;
792
        $userenrolmentdata['enrolid'] = $enrolid;
793
        $userenrolmentdata['userid'] = $USER->id;
794
        $DB->insert_record('user_enrolments', $userenrolmentdata);
795
 
796
        // Create a student and give them a user mapping record.
797
        $student = self::getDataGenerator()->create_user();
798
        $mapping = new \stdClass();
799
        $mapping->assignment = $assign->id;
800
        $mapping->userid = $student->id;
801
 
802
        $DB->insert_record('assign_user_mapping', $mapping);
803
 
804
        $assignmentids[] = $assign->id;
805
        $result = mod_assign_external::get_user_mappings($assignmentids);
806
 
807
        // We need to execute the return values cleaning process to simulate the web service server.
808
        $result = external_api::clean_returnvalue(mod_assign_external::get_user_mappings_returns(), $result);
809
 
810
        // Check that the correct user mapping information for the student is returned.
811
        $this->assertEquals(1, count($result['assignments']));
812
        $assignment = $result['assignments'][0];
813
        $this->assertEquals($assign->id, $assignment['assignmentid']);
814
        // Should be one user mapping record.
815
        $this->assertEquals(1, count($assignment['mappings']));
816
        $mapping = $assignment['mappings'][0];
817
        $this->assertEquals($student->id, $mapping['userid']);
818
    }
819
 
820
    /**
821
     * Test lock_submissions
822
     */
11 efrain 823
    public function test_lock_submissions(): void {
1 efrain 824
        global $DB, $USER;
825
 
826
        $this->resetAfterTest(true);
827
        // Create a course and assignment and users.
828
        $course = self::getDataGenerator()->create_course();
829
 
830
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
831
        $params['course'] = $course->id;
832
        $params['assignsubmission_onlinetext_enabled'] = 1;
833
        $instance = $generator->create_instance($params);
834
        $cm = get_coursemodule_from_instance('assign', $instance->id);
835
        $context = \context_module::instance($cm->id);
836
 
837
        $assign = new \assign($context, $cm, $course);
838
 
839
        $student1 = self::getDataGenerator()->create_user();
840
        $student2 = self::getDataGenerator()->create_user();
841
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
842
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
843
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
844
        $teacher = self::getDataGenerator()->create_user();
845
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
846
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
847
 
848
        // Create a student1 with an online text submission.
849
        // Simulate a submission.
850
        $this->setUser($student1);
851
        $submission = $assign->get_user_submission($student1->id, true);
852
        $data = new \stdClass();
853
        $data->onlinetext_editor = array(
854
            'itemid' => file_get_unused_draft_itemid(),
855
            'text' => 'Submission text',
856
            'format' => FORMAT_MOODLE);
857
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
858
        $plugin->save($submission, $data);
859
 
860
        // Ready to test.
861
        $this->setUser($teacher);
862
        $students = array($student1->id, $student2->id);
863
        $result = mod_assign_external::lock_submissions($instance->id, $students);
864
        $result = external_api::clean_returnvalue(mod_assign_external::lock_submissions_returns(), $result);
865
 
866
        // Check for 0 warnings.
867
        $this->assertEquals(0, count($result));
868
 
869
        $this->setUser($student2);
870
        $submission = $assign->get_user_submission($student2->id, true);
871
        $data = new \stdClass();
872
        $data->onlinetext_editor = array(
873
            'itemid' => file_get_unused_draft_itemid(),
874
            'text' => 'Submission text',
875
            'format' => FORMAT_MOODLE);
876
        $notices = array();
877
        $this->expectException(\moodle_exception::class);
878
        $assign->save_submission($data, $notices);
879
    }
880
 
881
    /**
882
     * Test unlock_submissions
883
     */
11 efrain 884
    public function test_unlock_submissions(): void {
1 efrain 885
        global $DB, $USER;
886
 
887
        $this->resetAfterTest(true);
888
        // Create a course and assignment and users.
889
        $course = self::getDataGenerator()->create_course();
890
 
891
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
892
        $params['course'] = $course->id;
893
        $params['assignsubmission_onlinetext_enabled'] = 1;
894
        $instance = $generator->create_instance($params);
895
        $cm = get_coursemodule_from_instance('assign', $instance->id);
896
        $context = \context_module::instance($cm->id);
897
 
898
        $assign = new \assign($context, $cm, $course);
899
 
900
        $student1 = self::getDataGenerator()->create_user();
901
        $student2 = self::getDataGenerator()->create_user();
902
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
903
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
904
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
905
        $teacher = self::getDataGenerator()->create_user();
906
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
907
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
908
 
909
        // Create a student1 with an online text submission.
910
        // Simulate a submission.
911
        $this->setUser($student1);
912
        $submission = $assign->get_user_submission($student1->id, true);
913
        $data = new \stdClass();
914
        $data->onlinetext_editor = array(
915
            'itemid' => file_get_unused_draft_itemid(),
916
            'text' => 'Submission text',
917
            'format' => FORMAT_MOODLE);
918
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
919
        $plugin->save($submission, $data);
920
 
921
        // Ready to test.
922
        $this->setUser($teacher);
923
        $students = array($student1->id, $student2->id);
924
        $result = mod_assign_external::lock_submissions($instance->id, $students);
925
        $result = external_api::clean_returnvalue(mod_assign_external::lock_submissions_returns(), $result);
926
 
927
        // Check for 0 warnings.
928
        $this->assertEquals(0, count($result));
929
 
930
        $result = mod_assign_external::unlock_submissions($instance->id, $students);
931
        $result = external_api::clean_returnvalue(mod_assign_external::unlock_submissions_returns(), $result);
932
 
933
        // Check for 0 warnings.
934
        $this->assertEquals(0, count($result));
935
 
936
        $this->setUser($student2);
937
        $submission = $assign->get_user_submission($student2->id, true);
938
        $data = new \stdClass();
939
        $data->onlinetext_editor = array(
940
            'itemid' => file_get_unused_draft_itemid(),
941
            'text' => 'Submission text',
942
            'format' => FORMAT_MOODLE);
943
        $notices = array();
944
        $assign->save_submission($data, $notices);
945
    }
946
 
947
    /**
948
     * Test submit_for_grading
949
     */
11 efrain 950
    public function test_submit_for_grading(): void {
1 efrain 951
        global $DB, $USER;
952
 
953
        $this->resetAfterTest(true);
954
        // Create a course and assignment and users.
955
        $course = self::getDataGenerator()->create_course();
956
 
957
        set_config('submissionreceipts', 0, 'assign');
958
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
959
        $params['course'] = $course->id;
960
        $params['assignsubmission_onlinetext_enabled'] = 1;
961
        $params['submissiondrafts'] = 1;
962
        $params['sendnotifications'] = 0;
963
        $params['requiresubmissionstatement'] = 1;
964
        $instance = $generator->create_instance($params);
965
        $cm = get_coursemodule_from_instance('assign', $instance->id);
966
        $context = \context_module::instance($cm->id);
967
 
968
        $assign = new \assign($context, $cm, $course);
969
 
970
        $student1 = self::getDataGenerator()->create_user();
971
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
972
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
973
 
974
        // Create a student1 with an online text submission.
975
        // Simulate a submission.
976
        $this->setUser($student1);
977
        $submission = $assign->get_user_submission($student1->id, true);
978
        $data = new \stdClass();
979
        $data->onlinetext_editor = array(
980
            'itemid' => file_get_unused_draft_itemid(),
981
            'text' => 'Submission text',
982
            'format' => FORMAT_MOODLE);
983
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
984
        $plugin->save($submission, $data);
985
 
986
        $result = mod_assign_external::submit_for_grading($instance->id, false);
987
        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
988
 
989
        // Should be 1 fail because the submission statement was not aceptted.
990
        $this->assertEquals(1, count($result));
991
 
992
        $result = mod_assign_external::submit_for_grading($instance->id, true);
993
        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
994
 
995
        // Check for 0 warnings.
996
        $this->assertEquals(0, count($result));
997
 
998
        $submission = $assign->get_user_submission($student1->id, false);
999
 
1000
        $this->assertEquals(ASSIGN_SUBMISSION_STATUS_SUBMITTED, $submission->status);
1001
    }
1002
 
1003
    /**
1004
     * Test save_user_extensions
1005
     */
11 efrain 1006
    public function test_save_user_extensions(): void {
1 efrain 1007
        global $DB, $USER;
1008
 
1009
        $this->resetAfterTest(true);
1010
        // Create a course and assignment and users.
1011
        $course = self::getDataGenerator()->create_course();
1012
 
1013
        $teacher = self::getDataGenerator()->create_user();
1014
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1015
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1016
        $this->setUser($teacher);
1017
 
1018
        $now = time();
1019
        $yesterday = $now - 24 * 60 * 60;
1020
        $tomorrow = $now + 24 * 60 * 60;
1021
        set_config('submissionreceipts', 0, 'assign');
1022
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1023
        $params['course'] = $course->id;
1024
        $params['submissiondrafts'] = 1;
1025
        $params['sendnotifications'] = 0;
1026
        $params['duedate'] = $yesterday;
1027
        $params['cutoffdate'] = $now - 10;
1028
        $instance = $generator->create_instance($params);
1029
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1030
        $context = \context_module::instance($cm->id);
1031
 
1032
        $assign = new \assign($context, $cm, $course);
1033
 
1034
        $student1 = self::getDataGenerator()->create_user();
1035
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1036
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1037
 
1038
        $this->setUser($student1);
1039
        $result = mod_assign_external::submit_for_grading($instance->id, true);
1040
        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
1041
 
1042
        // Check for 0 warnings.
1043
        $this->assertEquals(1, count($result));
1044
 
1045
        $this->setUser($teacher);
1046
        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));
1047
        $result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);
1048
        $this->assertEquals(1, count($result));
1049
 
1050
        $this->setUser($teacher);
1051
        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($yesterday - 10));
1052
        $result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);
1053
        $this->assertEquals(1, count($result));
1054
 
1055
        $this->setUser($teacher);
1056
        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($tomorrow));
1057
        $result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);
1058
        $this->assertEquals(0, count($result));
1059
 
1060
        $this->setUser($student1);
1061
        $result = mod_assign_external::submit_for_grading($instance->id, true);
1062
        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
1063
        $this->assertEquals(0, count($result));
1064
 
1065
        $this->setUser($student1);
1066
        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));
1067
        $result = external_api::clean_returnvalue(mod_assign_external::save_user_extensions_returns(), $result);
1068
    }
1069
 
1070
    /**
1071
     * Test reveal_identities
1072
     */
11 efrain 1073
    public function test_reveal_identities(): void {
1 efrain 1074
        global $DB, $USER;
1075
 
1076
        $this->resetAfterTest(true);
1077
        // Create a course and assignment and users.
1078
        $course = self::getDataGenerator()->create_course();
1079
 
1080
        $teacher = self::getDataGenerator()->create_user();
1081
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1082
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1083
        $this->setUser($teacher);
1084
 
1085
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1086
        $params['course'] = $course->id;
1087
        $params['submissiondrafts'] = 1;
1088
        $params['sendnotifications'] = 0;
1089
        $params['blindmarking'] = 1;
1090
        $instance = $generator->create_instance($params);
1091
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1092
        $context = \context_module::instance($cm->id);
1093
 
1094
        $assign = new \assign($context, $cm, $course);
1095
 
1096
        $student1 = self::getDataGenerator()->create_user();
1097
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1098
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1099
 
1100
        $this->setUser($student1);
1101
        $this->expectException(\required_capability_exception::class);
1102
        $result = mod_assign_external::reveal_identities($instance->id);
1103
        $result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);
1104
        $this->assertEquals(1, count($result));
1105
        $this->assertEquals(true, $assign->is_blind_marking());
1106
 
1107
        $this->setUser($teacher);
1108
        $result = mod_assign_external::reveal_identities($instance->id);
1109
        $result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);
1110
        $this->assertEquals(0, count($result));
1111
        $this->assertEquals(false, $assign->is_blind_marking());
1112
 
1113
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1114
        $params['course'] = $course->id;
1115
        $params['submissiondrafts'] = 1;
1116
        $params['sendnotifications'] = 0;
1117
        $params['blindmarking'] = 0;
1118
        $instance = $generator->create_instance($params);
1119
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1120
        $context = \context_module::instance($cm->id);
1121
 
1122
        $assign = new \assign($context, $cm, $course);
1123
        $result = mod_assign_external::reveal_identities($instance->id);
1124
        $result = external_api::clean_returnvalue(mod_assign_external::reveal_identities_returns(), $result);
1125
        $this->assertEquals(1, count($result));
1126
        $this->assertEquals(false, $assign->is_blind_marking());
1127
 
1128
    }
1129
 
1130
    /**
1131
     * Test revert_submissions_to_draft
1132
     */
11 efrain 1133
    public function test_revert_submissions_to_draft(): void {
1 efrain 1134
        global $DB, $USER;
1135
 
1136
        $this->resetAfterTest(true);
1137
        set_config('submissionreceipts', 0, 'assign');
1138
        // Create a course and assignment and users.
1139
        $course = self::getDataGenerator()->create_course();
1140
 
1141
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1142
        $params['course'] = $course->id;
1143
        $params['sendnotifications'] = 0;
1144
        $params['submissiondrafts'] = 1;
1145
        $instance = $generator->create_instance($params);
1146
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1147
        $context = \context_module::instance($cm->id);
1148
 
1149
        $assign = new \assign($context, $cm, $course);
1150
 
1151
        $student1 = self::getDataGenerator()->create_user();
1152
        $student2 = self::getDataGenerator()->create_user();
1153
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1154
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1155
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1156
        $teacher = self::getDataGenerator()->create_user();
1157
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1158
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1159
 
1160
        // Create a student1 with an online text submission.
1161
        // Simulate a submission.
1162
        $this->setUser($student1);
1163
        $result = mod_assign_external::submit_for_grading($instance->id, true);
1164
        $result = external_api::clean_returnvalue(mod_assign_external::submit_for_grading_returns(), $result);
1165
        $this->assertEquals(0, count($result));
1166
 
1167
        // Ready to test.
1168
        $this->setUser($teacher);
1169
        $students = array($student1->id, $student2->id);
1170
        $result = mod_assign_external::revert_submissions_to_draft($instance->id, array($student1->id));
1171
        $result = external_api::clean_returnvalue(mod_assign_external::revert_submissions_to_draft_returns(), $result);
1172
 
1173
        // Check for 0 warnings.
1174
        $this->assertEquals(0, count($result));
1175
    }
1176
 
1177
    /**
1178
     * Test save_submission
1179
     */
11 efrain 1180
    public function test_save_submission(): void {
1 efrain 1181
        global $DB, $USER;
1182
 
1183
        $this->resetAfterTest(true);
1184
        // Create a course and assignment and users.
1185
        $course = self::getDataGenerator()->create_course();
1186
 
1187
        $teacher = self::getDataGenerator()->create_user();
1188
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1189
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1190
        $this->setUser($teacher);
1191
 
1192
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1193
        $params['course'] = $course->id;
1194
        $params['assignsubmission_onlinetext_enabled'] = 1;
1195
        $params['assignsubmission_file_enabled'] = 1;
1196
        $params['assignsubmission_file_maxfiles'] = 5;
1197
        $params['assignsubmission_file_maxsizebytes'] = 1024 * 1024;
1198
        $instance = $generator->create_instance($params);
1199
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1200
        $context = \context_module::instance($cm->id);
1201
 
1202
        $assign = new \assign($context, $cm, $course);
1203
 
1204
        $student1 = self::getDataGenerator()->create_user();
1205
        $student2 = self::getDataGenerator()->create_user();
1206
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1207
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1208
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1209
        // Create a student1 with an online text submission.
1210
        // Simulate a submission.
1211
        $this->setUser($student1);
1212
 
1213
        // Create a file in a draft area.
1214
        $draftidfile = file_get_unused_draft_itemid();
1215
 
1216
        $usercontext = \context_user::instance($student1->id);
1217
        $filerecord = array(
1218
            'contextid' => $usercontext->id,
1219
            'component' => 'user',
1220
            'filearea'  => 'draft',
1221
            'itemid'    => $draftidfile,
1222
            'filepath'  => '/',
1223
            'filename'  => 'testtext.txt',
1224
        );
1225
 
1226
        $fs = get_file_storage();
1227
        $fs->create_file_from_string($filerecord, 'text contents');
1228
 
1229
        // Create another file in a different draft area.
1230
        $draftidonlinetext = file_get_unused_draft_itemid();
1231
 
1232
        $filerecord = array(
1233
            'contextid' => $usercontext->id,
1234
            'component' => 'user',
1235
            'filearea'  => 'draft',
1236
            'itemid'    => $draftidonlinetext,
1237
            'filepath'  => '/',
1238
            'filename'  => 'shouldbeanimage.txt',
1239
        );
1240
 
1241
        $fs->create_file_from_string($filerecord, 'image contents (not really)');
1242
 
1243
        // Now try a submission.
1244
        $submissionpluginparams = array();
1245
        $submissionpluginparams['files_filemanager'] = $draftidfile;
1246
        $onlinetexteditorparams = array(
1247
            'text' => '<p>Yeeha!</p>',
1248
            'format' => 1,
1249
            'itemid' => $draftidonlinetext);
1250
        $submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;
1251
        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
1252
        $result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);
1253
 
1254
        $this->assertEquals(0, count($result));
1255
 
1256
        // Set up a due and cutoff passed date.
1257
        $instance->duedate = time() - WEEKSECS;
1258
        $instance->cutoffdate = time() - WEEKSECS;
1259
        $DB->update_record('assign', $instance);
1260
 
1261
        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
1262
        $result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);
1263
 
1264
        $this->assertCount(1, $result);
1265
        $this->assertEquals(get_string('duedatereached', 'assign'), $result[0]['item']);
1266
    }
1267
 
1268
    /**
1269
     * Test save_grade
1270
     */
11 efrain 1271
    public function test_save_grade(): void {
1 efrain 1272
        global $DB, $USER;
1273
 
1274
        $this->resetAfterTest(true);
1275
        // Create a course and assignment and users.
1276
        $course = self::getDataGenerator()->create_course();
1277
 
1278
        $teacher = self::getDataGenerator()->create_user();
1279
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1280
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1281
        $this->setUser($teacher);
1282
 
1283
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1284
        $params['course'] = $course->id;
1285
        $params['assignfeedback_file_enabled'] = 1;
1286
        $params['assignfeedback_comments_enabled'] = 1;
1287
        $instance = $generator->create_instance($params);
1288
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1289
        $context = \context_module::instance($cm->id);
1290
 
1291
        $assign = new \assign($context, $cm, $course);
1292
 
1293
        $student1 = self::getDataGenerator()->create_user();
1294
        $student2 = self::getDataGenerator()->create_user();
1295
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1296
        $this->getDataGenerator()->enrol_user($student1->id,
1297
                                              $course->id,
1298
                                              $studentrole->id);
1299
        $this->getDataGenerator()->enrol_user($student2->id,
1300
                                              $course->id,
1301
                                              $studentrole->id);
1302
        // Simulate a grade.
1303
        $this->setUser($teacher);
1304
 
1305
        // Create a file in a draft area.
1306
        $draftidfile = file_get_unused_draft_itemid();
1307
 
1308
        $usercontext = \context_user::instance($teacher->id);
1309
        $filerecord = array(
1310
            'contextid' => $usercontext->id,
1311
            'component' => 'user',
1312
            'filearea'  => 'draft',
1313
            'itemid'    => $draftidfile,
1314
            'filepath'  => '/',
1315
            'filename'  => 'testtext.txt',
1316
        );
1317
 
1318
        $fs = get_file_storage();
1319
        $fs->create_file_from_string($filerecord, 'text contents');
1320
 
1321
        // Now try a grade.
1322
        $feedbackpluginparams = array();
1323
        $feedbackpluginparams['files_filemanager'] = $draftidfile;
1324
        $feedbackeditorparams = array('text' => 'Yeeha!',
1325
                                        'format' => 1);
1326
        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
1327
        $result = mod_assign_external::save_grade(
1328
            $instance->id,
1329
            $student1->id,
1330
            50.0,
1331
            -1,
1332
            true,
1333
            'released',
1334
            false,
1335
            $feedbackpluginparams);
1336
        // No warnings.
1337
        $this->assertNull($result);
1338
 
1339
        $result = mod_assign_external::get_grades(array($instance->id));
1340
        $result = external_api::clean_returnvalue(mod_assign_external::get_grades_returns(), $result);
1341
 
1342
        $this->assertEquals((float)$result['assignments'][0]['grades'][0]['grade'], '50.0');
1343
    }
1344
 
1345
    /**
1346
     * Test save grades with advanced grading data
1347
     */
11 efrain 1348
    public function test_save_grades_with_advanced_grading(): void {
1 efrain 1349
        global $DB, $USER;
1350
 
1351
        $this->resetAfterTest(true);
1352
        // Create a course and assignment and users.
1353
        $course = self::getDataGenerator()->create_course();
1354
 
1355
        $teacher = self::getDataGenerator()->create_user();
1356
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1357
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1358
 
1359
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1360
        $params['course'] = $course->id;
1361
        $params['assignfeedback_file_enabled'] = 0;
1362
        $params['assignfeedback_comments_enabled'] = 0;
1363
        $instance = $generator->create_instance($params);
1364
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1365
        $context = \context_module::instance($cm->id);
1366
 
1367
        $assign = new \assign($context, $cm, $course);
1368
 
1369
        $student1 = self::getDataGenerator()->create_user();
1370
        $student2 = self::getDataGenerator()->create_user();
1371
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1372
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1373
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1374
 
1375
        $this->setUser($teacher);
1376
 
1377
        $feedbackpluginparams = array();
1378
        $feedbackpluginparams['files_filemanager'] = 0;
1379
        $feedbackeditorparams = array('text' => '', 'format' => 1);
1380
        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
1381
 
1382
        // Create advanced grading data.
1383
        // Create grading area.
1384
        $gradingarea = array(
1385
            'contextid' => $context->id,
1386
            'component' => 'mod_assign',
1387
            'areaname' => 'submissions',
1388
            'activemethod' => 'rubric'
1389
        );
1390
        $areaid = $DB->insert_record('grading_areas', $gradingarea);
1391
 
1392
        // Create a rubric grading definition.
1393
        $rubricdefinition = array (
1394
            'areaid' => $areaid,
1395
            'method' => 'rubric',
1396
            'name' => 'test',
1397
            'status' => 20,
1398
            'copiedfromid' => 1,
1399
            'timecreated' => 1,
1400
            'usercreated' => $teacher->id,
1401
            'timemodified' => 1,
1402
            'usermodified' => $teacher->id,
1403
            'timecopied' => 0
1404
        );
1405
        $definitionid = $DB->insert_record('grading_definitions', $rubricdefinition);
1406
 
1407
        // Create a criterion with a level.
1408
        $rubriccriteria = array (
1409
             'definitionid' => $definitionid,
1410
             'sortorder' => 1,
1411
             'description' => 'Demonstrate an understanding of disease control',
1412
             'descriptionformat' => 0
1413
        );
1414
        $criterionid = $DB->insert_record('gradingform_rubric_criteria', $rubriccriteria);
1415
        $rubriclevel1 = array (
1416
            'criterionid' => $criterionid,
1417
            'score' => 50,
1418
            'definition' => 'pass',
1419
            'definitionformat' => 0
1420
        );
1421
        $rubriclevel2 = array (
1422
            'criterionid' => $criterionid,
1423
            'score' => 100,
1424
            'definition' => 'excellent',
1425
            'definitionformat' => 0
1426
        );
1427
        $rubriclevel3 = array (
1428
            'criterionid' => $criterionid,
1429
            'score' => 0,
1430
            'definition' => 'fail',
1431
            'definitionformat' => 0
1432
        );
1433
        $levelid1 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel1);
1434
        $levelid2 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel2);
1435
        $levelid3 = $DB->insert_record('gradingform_rubric_levels', $rubriclevel3);
1436
 
1437
        // Create the filling.
1438
        $student1filling = array (
1439
            'criterionid' => $criterionid,
1440
            'levelid' => $levelid1,
1441
            'remark' => 'well done you passed',
1442
            'remarkformat' => 0
1443
        );
1444
 
1445
        $student2filling = array (
1446
            'criterionid' => $criterionid,
1447
            'levelid' => $levelid2,
1448
            'remark' => 'Excellent work',
1449
            'remarkformat' => 0
1450
        );
1451
 
1452
        $student1criteria = array(array('criterionid' => $criterionid, 'fillings' => array($student1filling)));
1453
        $student1advancedgradingdata = array('rubric' => array('criteria' => $student1criteria));
1454
 
1455
        $student2criteria = array(array('criterionid' => $criterionid, 'fillings' => array($student2filling)));
1456
        $student2advancedgradingdata = array('rubric' => array('criteria' => $student2criteria));
1457
 
1458
        $grades = array();
1459
        $student1gradeinfo = array();
1460
        $student1gradeinfo['userid'] = $student1->id;
1461
        $student1gradeinfo['grade'] = 0; // Ignored since advanced grading is being used.
1462
        $student1gradeinfo['attemptnumber'] = -1;
1463
        $student1gradeinfo['addattempt'] = true;
1464
        $student1gradeinfo['workflowstate'] = 'released';
1465
        $student1gradeinfo['plugindata'] = $feedbackpluginparams;
1466
        $student1gradeinfo['advancedgradingdata'] = $student1advancedgradingdata;
1467
        $grades[] = $student1gradeinfo;
1468
 
1469
        $student2gradeinfo = array();
1470
        $student2gradeinfo['userid'] = $student2->id;
1471
        $student2gradeinfo['grade'] = 0; // Ignored since advanced grading is being used.
1472
        $student2gradeinfo['attemptnumber'] = -1;
1473
        $student2gradeinfo['addattempt'] = true;
1474
        $student2gradeinfo['workflowstate'] = 'released';
1475
        $student2gradeinfo['plugindata'] = $feedbackpluginparams;
1476
        $student2gradeinfo['advancedgradingdata'] = $student2advancedgradingdata;
1477
        $grades[] = $student2gradeinfo;
1478
 
1479
        $result = mod_assign_external::save_grades($instance->id, false, $grades);
1480
        $this->assertNull($result);
1481
 
1482
        $student1grade = $DB->get_record('assign_grades',
1483
                                         array('userid' => $student1->id, 'assignment' => $instance->id),
1484
                                         '*',
1485
                                         MUST_EXIST);
1486
        $this->assertEquals((float)$student1grade->grade, '50.0');
1487
 
1488
        $student2grade = $DB->get_record('assign_grades',
1489
                                         array('userid' => $student2->id, 'assignment' => $instance->id),
1490
                                         '*',
1491
                                         MUST_EXIST);
1492
        $this->assertEquals((float)$student2grade->grade, '100.0');
1493
    }
1494
 
1495
    /**
1496
     * Test save grades for a team submission
1497
     */
11 efrain 1498
    public function test_save_grades_with_group_submission(): void {
1 efrain 1499
        global $DB, $USER, $CFG;
1500
        require_once($CFG->dirroot . '/group/lib.php');
1501
 
1502
        $this->resetAfterTest(true);
1503
        // Create a course and assignment and users.
1504
        $course = self::getDataGenerator()->create_course();
1505
 
1506
        $teacher = self::getDataGenerator()->create_user();
1507
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1508
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1509
 
1510
        $groupingdata = array();
1511
        $groupingdata['courseid'] = $course->id;
1512
        $groupingdata['name'] = 'Group assignment grouping';
1513
 
1514
        $grouping = self::getDataGenerator()->create_grouping($groupingdata);
1515
 
1516
        $group1data = array();
1517
        $group1data['courseid'] = $course->id;
1518
        $group1data['name'] = 'Team 1';
1519
        $group2data = array();
1520
        $group2data['courseid'] = $course->id;
1521
        $group2data['name'] = 'Team 2';
1522
 
1523
        $group1 = self::getDataGenerator()->create_group($group1data);
1524
        $group2 = self::getDataGenerator()->create_group($group2data);
1525
 
1526
        groups_assign_grouping($grouping->id, $group1->id);
1527
        groups_assign_grouping($grouping->id, $group2->id);
1528
 
1529
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1530
        $params['course'] = $course->id;
1531
        $params['teamsubmission'] = 1;
1532
        $params['teamsubmissiongroupingid'] = $grouping->id;
1533
        $instance = $generator->create_instance($params);
1534
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1535
        $context = \context_module::instance($cm->id);
1536
 
1537
        $assign = new \assign($context, $cm, $course);
1538
 
1539
        $student1 = self::getDataGenerator()->create_user();
1540
        $student2 = self::getDataGenerator()->create_user();
1541
        $student3 = self::getDataGenerator()->create_user();
1542
        $student4 = self::getDataGenerator()->create_user();
1543
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1544
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1545
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
1546
        $this->getDataGenerator()->enrol_user($student3->id, $course->id, $studentrole->id);
1547
        $this->getDataGenerator()->enrol_user($student4->id, $course->id, $studentrole->id);
1548
 
1549
        groups_add_member($group1->id, $student1->id);
1550
        groups_add_member($group1->id, $student2->id);
1551
        groups_add_member($group1->id, $student3->id);
1552
        groups_add_member($group2->id, $student4->id);
1553
        $this->setUser($teacher);
1554
 
1555
        $feedbackpluginparams = array();
1556
        $feedbackpluginparams['files_filemanager'] = 0;
1557
        $feedbackeditorparams = array('text' => '', 'format' => 1);
1558
        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
1559
 
1560
        $grades1 = array();
1561
        $student1gradeinfo = array();
1562
        $student1gradeinfo['userid'] = $student1->id;
1563
        $student1gradeinfo['grade'] = 50;
1564
        $student1gradeinfo['attemptnumber'] = -1;
1565
        $student1gradeinfo['addattempt'] = true;
1566
        $student1gradeinfo['workflowstate'] = 'released';
1567
        $student1gradeinfo['plugindata'] = $feedbackpluginparams;
1568
        $grades1[] = $student1gradeinfo;
1569
 
1570
        $student2gradeinfo = array();
1571
        $student2gradeinfo['userid'] = $student2->id;
1572
        $student2gradeinfo['grade'] = 75;
1573
        $student2gradeinfo['attemptnumber'] = -1;
1574
        $student2gradeinfo['addattempt'] = true;
1575
        $student2gradeinfo['workflowstate'] = 'released';
1576
        $student2gradeinfo['plugindata'] = $feedbackpluginparams;
1577
        $grades1[] = $student2gradeinfo;
1578
 
1579
        // Expect an exception since 2 grades have been submitted for the same team.
1580
        $this->expectException(\invalid_parameter_exception::class);
1581
        $result = mod_assign_external::save_grades($instance->id, true, $grades1);
1582
        $result = external_api::clean_returnvalue(mod_assign_external::save_grades_returns(), $result);
1583
 
1584
        $grades2 = array();
1585
        $student3gradeinfo = array();
1586
        $student3gradeinfo['userid'] = $student3->id;
1587
        $student3gradeinfo['grade'] = 50;
1588
        $student3gradeinfo['attemptnumber'] = -1;
1589
        $student3gradeinfo['addattempt'] = true;
1590
        $student3gradeinfo['workflowstate'] = 'released';
1591
        $student3gradeinfo['plugindata'] = $feedbackpluginparams;
1592
        $grades2[] = $student3gradeinfo;
1593
 
1594
        $student4gradeinfo = array();
1595
        $student4gradeinfo['userid'] = $student4->id;
1596
        $student4gradeinfo['grade'] = 75;
1597
        $student4gradeinfo['attemptnumber'] = -1;
1598
        $student4gradeinfo['addattempt'] = true;
1599
        $student4gradeinfo['workflowstate'] = 'released';
1600
        $student4gradeinfo['plugindata'] = $feedbackpluginparams;
1601
        $grades2[] = $student4gradeinfo;
1602
        $result = mod_assign_external::save_grades($instance->id, true, $grades2);
1603
        $result = external_api::clean_returnvalue(mod_assign_external::save_grades_returns(), $result);
1604
        // There should be no warnings.
1605
        $this->assertEquals(0, count($result));
1606
 
1607
        $student3grade = $DB->get_record('assign_grades',
1608
            array('userid' => $student3->id, 'assignment' => $instance->id),
1609
            '*',
1610
            MUST_EXIST);
1611
        $this->assertEquals($student3grade->grade, '50.0');
1612
 
1613
        $student4grade = $DB->get_record('assign_grades',
1614
            array('userid' => $student4->id, 'assignment' => $instance->id),
1615
            '*',
1616
            MUST_EXIST);
1617
        $this->assertEquals($student4grade->grade, '75.0');
1618
    }
1619
 
1620
    /**
1621
     * Test copy_previous_attempt
1622
     */
11 efrain 1623
    public function test_copy_previous_attempt(): void {
1 efrain 1624
        global $DB, $USER;
1625
 
1626
        $this->resetAfterTest(true);
1627
        // Create a course and assignment and users.
1628
        $course = self::getDataGenerator()->create_course();
1629
 
1630
        $teacher = self::getDataGenerator()->create_user();
1631
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1632
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
1633
        $this->setUser($teacher);
1634
 
1635
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1636
        $params['course'] = $course->id;
1637
        $params['assignsubmission_onlinetext_enabled'] = 1;
1638
        $params['assignsubmission_file_enabled'] = 0;
1639
        $params['assignfeedback_file_enabled'] = 0;
1640
        $params['attemptreopenmethod'] = 'manual';
1641
        $params['maxattempts'] = 5;
1642
        $instance = $generator->create_instance($params);
1643
        $cm = get_coursemodule_from_instance('assign', $instance->id);
1644
        $context = \context_module::instance($cm->id);
1645
 
1646
        $assign = new \assign($context, $cm, $course);
1647
 
1648
        $student1 = self::getDataGenerator()->create_user();
1649
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1650
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
1651
        // Now try a submission.
1652
        $this->setUser($student1);
1653
        $draftidonlinetext = file_get_unused_draft_itemid();
1654
        $submissionpluginparams = array();
1655
        $onlinetexteditorparams = array('text' => 'Yeeha!', 'format' => 1, 'itemid' => $draftidonlinetext);
1656
        $submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;
1657
        $submissionpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
1658
        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
1659
        $result = external_api::clean_returnvalue(mod_assign_external::save_submission_returns(), $result);
1660
 
1661
        $this->setUser($teacher);
1662
        // Add a grade and reopen the attempt.
1663
        // Now try a grade.
1664
        $feedbackpluginparams = array();
1665
        $feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
1666
        $feedbackeditorparams = array('text' => 'Yeeha!', 'format' => 1);
1667
        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
1668
        $result = mod_assign_external::save_grade(
1669
            $instance->id,
1670
            $student1->id,
1671
            50.0,
1672
            -1,
1673
            true,
1674
            'released',
1675
            false,
1676
            $feedbackpluginparams);
1677
        $this->assertNull($result);
1678
 
1679
        $this->setUser($student1);
1680
        // Now copy the previous attempt.
1681
        $result = mod_assign_external::copy_previous_attempt($instance->id);
1682
        $result = external_api::clean_returnvalue(mod_assign_external::copy_previous_attempt_returns(), $result);
1683
        // No warnings.
1684
        $this->assertEquals(0, count($result));
1685
 
1686
        $this->setUser($teacher);
1687
        $result = mod_assign_external::get_submissions(array($instance->id));
1688
        $result = external_api::clean_returnvalue(mod_assign_external::get_submissions_returns(), $result);
1689
 
1690
        // Check we are now on the second attempt.
1691
        $this->assertEquals($result['assignments'][0]['submissions'][0]['attemptnumber'], 1);
1692
        // Check the plugins data is not empty.
1693
        $this->assertNotEmpty($result['assignments'][0]['submissions'][0]['plugins']);
1694
 
1695
    }
1696
 
1697
    /**
1698
     * Test set_user_flags
1699
     */
11 efrain 1700
    public function test_set_user_flags(): void {
1 efrain 1701
        global $DB, $USER;
1702
 
1703
        $this->resetAfterTest(true);
1704
        // Create a course and assignment.
1705
        $coursedata['idnumber'] = 'idnumbercourse';
1706
        $coursedata['fullname'] = 'Lightwork Course';
1707
        $coursedata['summary'] = 'Lightwork Course description';
1708
        $coursedata['summaryformat'] = FORMAT_MOODLE;
1709
        $course = self::getDataGenerator()->create_course($coursedata);
1710
 
1711
        $assigndata['course'] = $course->id;
1712
        $assigndata['name'] = 'lightwork assignment';
1713
 
1714
        $assign = self::getDataGenerator()->create_module('assign', $assigndata);
1715
 
1716
        // Create a manual enrolment record.
1717
        $manualenroldata['enrol'] = 'manual';
1718
        $manualenroldata['status'] = 0;
1719
        $manualenroldata['courseid'] = $course->id;
1720
        $enrolid = $DB->insert_record('enrol', $manualenroldata);
1721
 
1722
        // Create a teacher and give them capabilities.
1723
        $context = \context_course::instance($course->id);
1724
        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
1725
        $context = \context_module::instance($assign->cmid);
1726
        $this->assignUserCapability('mod/assign:grade', $context->id, $roleid);
1727
 
1728
        // Create the teacher's enrolment record.
1729
        $userenrolmentdata['status'] = 0;
1730
        $userenrolmentdata['enrolid'] = $enrolid;
1731
        $userenrolmentdata['userid'] = $USER->id;
1732
        $DB->insert_record('user_enrolments', $userenrolmentdata);
1733
 
1734
        // Create a student.
1735
        $student = self::getDataGenerator()->create_user();
1736
 
1737
        // Create test user flags record.
1738
        $userflags = array();
1739
        $userflag['userid'] = $student->id;
1740
        $userflag['workflowstate'] = 'inmarking';
1741
        $userflag['allocatedmarker'] = $USER->id;
1742
        $userflags = array($userflag);
1743
 
1744
        $createduserflags = mod_assign_external::set_user_flags($assign->id, $userflags);
1745
        // We need to execute the return values cleaning process to simulate the web service server.
1746
        $createduserflags = external_api::clean_returnvalue(mod_assign_external::set_user_flags_returns(), $createduserflags);
1747
 
1748
        $this->assertEquals($student->id, $createduserflags[0]['userid']);
1749
        $createduserflag = $DB->get_record('assign_user_flags', array('id' => $createduserflags[0]['id']));
1750
 
1751
        // Confirm that all data was inserted correctly.
1752
        $this->assertEquals($student->id,  $createduserflag->userid);
1753
        $this->assertEquals($assign->id, $createduserflag->assignment);
1754
        $this->assertEquals(0, $createduserflag->locked);
1755
        $this->assertEquals(2, $createduserflag->mailed);
1756
        $this->assertEquals(0, $createduserflag->extensionduedate);
1757
        $this->assertEquals('inmarking', $createduserflag->workflowstate);
1758
        $this->assertEquals($USER->id, $createduserflag->allocatedmarker);
1759
 
1760
        // Create update data.
1761
        $userflags = array();
1762
        $userflag['userid'] = $createduserflag->userid;
1763
        $userflag['workflowstate'] = 'readyforreview';
1764
        $userflags = array($userflag);
1765
 
1766
        $updateduserflags = mod_assign_external::set_user_flags($assign->id, $userflags);
1767
        // We need to execute the return values cleaning process to simulate the web service server.
1768
        $updateduserflags = external_api::clean_returnvalue(mod_assign_external::set_user_flags_returns(), $updateduserflags);
1769
 
1770
        $this->assertEquals($student->id, $updateduserflags[0]['userid']);
1771
        $updateduserflag = $DB->get_record('assign_user_flags', array('id' => $updateduserflags[0]['id']));
1772
 
1773
        // Confirm that all data was updated correctly.
1774
        $this->assertEquals($student->id,  $updateduserflag->userid);
1775
        $this->assertEquals($assign->id, $updateduserflag->assignment);
1776
        $this->assertEquals(0, $updateduserflag->locked);
1777
        $this->assertEquals(2, $updateduserflag->mailed);
1778
        $this->assertEquals(0, $updateduserflag->extensionduedate);
1779
        $this->assertEquals('readyforreview', $updateduserflag->workflowstate);
1780
        $this->assertEquals($USER->id, $updateduserflag->allocatedmarker);
1781
    }
1782
 
1783
    /**
1784
     * Test view_grading_table
1785
     */
11 efrain 1786
    public function test_view_grading_table_invalid_instance(): void {
1 efrain 1787
        global $DB;
1788
 
1789
        $this->resetAfterTest(true);
1790
 
1791
        // Setup test data.
1792
        $course = $this->getDataGenerator()->create_course();
1793
        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1794
        $context = \context_module::instance($assign->cmid);
1795
        $cm = get_coursemodule_from_instance('assign', $assign->id);
1796
 
1797
        // Test invalid instance id.
1798
        $this->expectException(\dml_missing_record_exception::class);
1799
        mod_assign_external::view_grading_table(0);
1800
    }
1801
 
1802
    /**
1803
     * Test view_grading_table
1804
     */
11 efrain 1805
    public function test_view_grading_table_not_enrolled(): void {
1 efrain 1806
        global $DB;
1807
 
1808
        $this->resetAfterTest(true);
1809
 
1810
        // Setup test data.
1811
        $course = $this->getDataGenerator()->create_course();
1812
        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1813
        $context = \context_module::instance($assign->cmid);
1814
        $cm = get_coursemodule_from_instance('assign', $assign->id);
1815
 
1816
        // Test not-enrolled user.
1817
        $user = self::getDataGenerator()->create_user();
1818
        $this->setUser($user);
1819
 
1820
        $this->expectException(\require_login_exception::class);
1821
        mod_assign_external::view_grading_table($assign->id);
1822
    }
1823
 
1824
    /**
1825
     * Test view_grading_table
1826
     */
11 efrain 1827
    public function test_view_grading_table_correct(): void {
1 efrain 1828
        global $DB;
1829
 
1830
        $this->resetAfterTest(true);
1831
 
1832
        // Setup test data.
1833
        $course = $this->getDataGenerator()->create_course();
1834
        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1835
        $context = \context_module::instance($assign->cmid);
1836
        $cm = get_coursemodule_from_instance('assign', $assign->id);
1837
 
1838
        // Test user with full capabilities.
1839
        $user = self::getDataGenerator()->create_user();
1840
        $this->setUser($user);
1841
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1842
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
1843
 
1844
        // Trigger and capture the event.
1845
        $sink = $this->redirectEvents();
1846
 
1847
        $result = mod_assign_external::view_grading_table($assign->id);
1848
        $result = external_api::clean_returnvalue(mod_assign_external::view_grading_table_returns(), $result);
1849
 
1850
        $events = $sink->get_events();
1851
        $this->assertCount(1, $events);
1852
        $event = array_shift($events);
1853
 
1854
        // Checking that the event contains the expected values.
1855
        $this->assertInstanceOf('\mod_assign\event\grading_table_viewed', $event);
1856
        $this->assertEquals($context, $event->get_context());
1857
        $moodleurl = new \moodle_url('/mod/assign/view.php', array('id' => $cm->id));
1858
        $this->assertEquals($moodleurl, $event->get_url());
1859
        $this->assertEventContextNotUsed($event);
1860
        $this->assertNotEmpty($event->get_name());
1861
    }
1862
 
1863
    /**
1864
     * Test view_grading_table
1865
     */
11 efrain 1866
    public function test_view_grading_table_without_capability(): void {
1 efrain 1867
        global $DB;
1868
 
1869
        $this->resetAfterTest(true);
1870
 
1871
        // Setup test data.
1872
        $course = $this->getDataGenerator()->create_course();
1873
        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1874
        $context = \context_module::instance($assign->cmid);
1875
        $cm = get_coursemodule_from_instance('assign', $assign->id);
1876
 
1877
        // Test user with no capabilities.
1878
        $user = self::getDataGenerator()->create_user();
1879
        $this->setUser($user);
1880
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1881
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
1882
 
1883
        // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
1884
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
1885
        assign_capability('mod/assign:view', CAP_PROHIBIT, $teacherrole->id, $context->id);
1886
        // Empty all the caches that may be affected by this change.
1887
        accesslib_clear_all_caches_for_unit_testing();
1888
        \course_modinfo::clear_instance_cache();
1889
 
1890
        $this->expectException(\require_login_exception::class);
1891
        $this->expectExceptionMessage('Course or activity not accessible. (Activity is hidden)');
1892
        mod_assign_external::view_grading_table($assign->id);
1893
    }
1894
 
1895
    /**
1896
     * Test subplugins availability
1897
     */
11 efrain 1898
    public function test_subplugins_availability(): void {
1 efrain 1899
        global $CFG;
1900
 
1901
        require_once($CFG->dirroot . '/mod/assign/adminlib.php');
1902
        $this->resetAfterTest(true);
1903
 
1904
        // Hide assignment file submissiong plugin.
1905
        $pluginmanager = new \assign_plugin_manager('assignsubmission');
1906
        $pluginmanager->hide_plugin('file');
1907
        $parameters = mod_assign_external::save_submission_parameters();
1908
 
1909
        $this->assertTrue(!isset($parameters->keys['plugindata']->keys['files_filemanager']));
1910
 
1911
        // Show it again and check that the value is returned as optional.
1912
        $pluginmanager->show_plugin('file');
1913
        $parameters = mod_assign_external::save_submission_parameters();
1914
        $this->assertTrue(isset($parameters->keys['plugindata']->keys['files_filemanager']));
1915
        $this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['files_filemanager']->required);
1916
 
1917
        // Hide feedback file submissiong plugin.
1918
        $pluginmanager = new \assign_plugin_manager('assignfeedback');
1919
        $pluginmanager->hide_plugin('file');
1920
 
1921
        $parameters = mod_assign_external::save_grade_parameters();
1922
 
1923
        $this->assertTrue(!isset($parameters->keys['plugindata']->keys['files_filemanager']));
1924
 
1925
        // Show it again and check that the value is returned as optional.
1926
        $pluginmanager->show_plugin('file');
1927
        $parameters = mod_assign_external::save_grade_parameters();
1928
 
1929
        $this->assertTrue(isset($parameters->keys['plugindata']->keys['files_filemanager']));
1930
        $this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['files_filemanager']->required);
1931
 
1932
        // Check a different one.
1933
        $pluginmanager->show_plugin('comments');
1934
        $this->assertTrue(isset($parameters->keys['plugindata']->keys['assignfeedbackcomments_editor']));
1935
        $this->assertEquals(VALUE_OPTIONAL, $parameters->keys['plugindata']->keys['assignfeedbackcomments_editor']->required);
1936
    }
1937
 
1938
    /**
1939
     * Test test_view_submission_status
1940
     */
11 efrain 1941
    public function test_view_submission_status(): void {
1 efrain 1942
        global $DB;
1943
 
1944
        $this->resetAfterTest(true);
1945
 
1946
        $this->setAdminUser();
1947
        // Setup test data.
1948
        $course = $this->getDataGenerator()->create_course();
1949
        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
1950
        $context = \context_module::instance($assign->cmid);
1951
        $cm = get_coursemodule_from_instance('assign', $assign->id);
1952
 
1953
        // Test invalid instance id.
1954
        try {
1955
            mod_assign_external::view_submission_status(0);
1956
            $this->fail('Exception expected due to invalid mod_assign instance id.');
1957
        } catch (\moodle_exception $e) {
1958
            $this->assertEquals('invalidrecord', $e->errorcode);
1959
        }
1960
 
1961
        // Test not-enrolled user.
1962
        $user = self::getDataGenerator()->create_user();
1963
        $this->setUser($user);
1964
        try {
1965
            mod_assign_external::view_submission_status($assign->id);
1966
            $this->fail('Exception expected due to not enrolled user.');
1967
        } catch (\moodle_exception $e) {
1968
            $this->assertEquals('requireloginerror', $e->errorcode);
1969
        }
1970
 
1971
        // Test user with full capabilities.
1972
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1973
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
1974
 
1975
        // Trigger and capture the event.
1976
        $sink = $this->redirectEvents();
1977
 
1978
        $result = mod_assign_external::view_submission_status($assign->id);
1979
        $result = external_api::clean_returnvalue(mod_assign_external::view_submission_status_returns(), $result);
1980
 
1981
        $events = $sink->get_events();
1982
        $this->assertCount(1, $events);
1983
        $event = array_shift($events);
1984
 
1985
        // Checking that the event contains the expected values.
1986
        $this->assertInstanceOf('\mod_assign\event\submission_status_viewed', $event);
1987
        $this->assertEquals($context, $event->get_context());
1988
        $moodleurl = new \moodle_url('/mod/assign/view.php', array('id' => $cm->id));
1989
        $this->assertEquals($moodleurl, $event->get_url());
1990
        $this->assertEventContextNotUsed($event);
1991
        $this->assertNotEmpty($event->get_name());
1992
 
1993
        // Test user with no capabilities.
1994
        // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
1995
        assign_capability('mod/assign:view', CAP_PROHIBIT, $studentrole->id, $context->id);
1996
        accesslib_clear_all_caches_for_unit_testing();
1997
        \course_modinfo::clear_instance_cache();
1998
 
1999
        try {
2000
            mod_assign_external::view_submission_status($assign->id);
2001
            $this->fail('Exception expected due to missing capability.');
2002
        } catch (\moodle_exception $e) {
2003
            $this->assertEquals('requireloginerror', $e->errorcode);
2004
        }
2005
    }
2006
 
2007
    /**
2008
     * Test get_submission_status for a draft submission.
2009
     */
11 efrain 2010
    public function test_get_submission_status_in_draft_status(): void {
1 efrain 2011
        $this->resetAfterTest(true);
2012
 
2013
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();
2014
        $studentsubmission = $assign->get_user_submission($student1->id, true);
2015
 
2016
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2017
        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2018
        $this->assertDebuggingCalled();
2019
 
2020
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2021
 
2022
        // The submission is now in draft mode.
2023
        $this->assertCount(0, $result['warnings']);
2024
        $this->assertFalse(isset($result['gradingsummary']));
2025
        $this->assertFalse(isset($result['feedback']));
2026
        $this->assertFalse(isset($result['previousattempts']));
2027
 
2028
        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2029
        $this->assertTrue($result['lastattempt']['canedit']);
2030
        $this->assertTrue($result['lastattempt']['cansubmit']);
2031
        $this->assertFalse($result['lastattempt']['locked']);
2032
        $this->assertFalse($result['lastattempt']['graded']);
2033
        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2034
        $this->assertFalse($result['lastattempt']['blindmarking']);
2035
        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2036
        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2037
 
2038
        $this->assertEquals($student1->id, $result['lastattempt']['submission']['userid']);
2039
        $this->assertEquals(0, $result['lastattempt']['submission']['attemptnumber']);
2040
        $this->assertEquals('draft', $result['lastattempt']['submission']['status']);
2041
        $this->assertEquals(0, $result['lastattempt']['submission']['groupid']);
2042
        $this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);
2043
        $this->assertEquals(1, $result['lastattempt']['submission']['latest']);
2044
 
2045
        // Map plugins based on their type - we can't rely on them being in a
2046
        // particular order, especially if 3rd party plugins are installed.
2047
        $submissionplugins = array();
2048
        foreach ($result['lastattempt']['submission']['plugins'] as $plugin) {
2049
            $submissionplugins[$plugin['type']] = $plugin;
2050
        }
2051
 
2052
        // Format expected online text.
2053
        $onlinetext = 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>';
2054
        list($expectedtext,
2055
        $expectedformat) = \core_external\util::format_text(
2056
            $onlinetext,
2057
            FORMAT_HTML,
2058
            $assign->get_context(),
2059
            'assignsubmission_onlinetext',
2060
            ASSIGNSUBMISSION_ONLINETEXT_FILEAREA,
2061
            $studentsubmission->id
2062
        );
2063
 
2064
        $this->assertEquals($expectedtext, $submissionplugins['onlinetext']['editorfields'][0]['text']);
2065
        $this->assertEquals($expectedformat, $submissionplugins['onlinetext']['editorfields'][0]['format']);
2066
        $this->assertEquals('/', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);
2067
        $this->assertEquals('t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filename']);
2068
 
2069
        // Test assignment data.
2070
        $this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
2071
    }
2072
 
2073
    /**
2074
     * Test get_submission_status for a submitted submission.
2075
     */
11 efrain 2076
    public function test_get_submission_status_in_submission_status(): void {
1 efrain 2077
        $this->resetAfterTest(true);
2078
 
2079
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);
2080
 
2081
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2082
        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2083
        $this->assertDebuggingCalled();
2084
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2085
 
2086
        $this->assertCount(0, $result['warnings']);
2087
        $this->assertFalse(isset($result['gradingsummary']));
2088
        $this->assertFalse(isset($result['feedback']));
2089
        $this->assertFalse(isset($result['previousattempts']));
2090
 
2091
        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2092
        $this->assertFalse($result['lastattempt']['canedit']);
2093
        $this->assertFalse($result['lastattempt']['cansubmit']);
2094
        $this->assertFalse($result['lastattempt']['locked']);
2095
        $this->assertFalse($result['lastattempt']['graded']);
2096
        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2097
        $this->assertFalse($result['lastattempt']['blindmarking']);
2098
        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2099
        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2100
        $this->assertNull($result['lastattempt']['submission']['timestarted']);
2101
 
2102
        // Test assignment data.
2103
        $this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
2104
    }
2105
 
2106
    /**
2107
     * Test get_submission_status using the teacher role.
2108
     */
11 efrain 2109
    public function test_get_submission_status_in_submission_status_for_teacher(): void {
1 efrain 2110
        global $DB;
2111
        $this->resetAfterTest(true);
2112
 
2113
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);
2114
 
2115
        // Now, as teacher, see the grading summary.
2116
        $this->setUser($teacher);
2117
        // First one group.
2118
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g1->id);
2119
        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2120
        $this->assertDebuggingCalled();
2121
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2122
 
2123
        $this->assertCount(0, $result['warnings']);
2124
        $this->assertFalse(isset($result['lastattempt']));
2125
        $this->assertFalse(isset($result['feedback']));
2126
        $this->assertFalse(isset($result['previousattempts']));
2127
 
2128
        $this->assertEquals(1, $result['gradingsummary']['participantcount']);
2129
        $this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);
2130
        $this->assertEquals(1, $result['gradingsummary']['submissionsenabled']);
2131
        $this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);
2132
        $this->assertEquals(1, $result['gradingsummary']['submissionssubmittedcount']);  // One student from G1 submitted.
2133
        $this->assertEquals(1, $result['gradingsummary']['submissionsneedgradingcount']);    // One student from G1 submitted.
2134
        $this->assertEmpty($result['gradingsummary']['warnofungroupedusers']);
2135
 
2136
        // Second group.
2137
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g2->id);
2138
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2139
        $this->assertCount(0, $result['warnings']);
2140
        $this->assertEquals(1, $result['gradingsummary']['participantcount']);
2141
        $this->assertEquals(0, $result['gradingsummary']['submissionssubmittedcount']); // G2 students didn't submit yet.
2142
        $this->assertEquals(0, $result['gradingsummary']['submissionsneedgradingcount']);   // G2 students didn't submit yet.
2143
 
2144
        // Should not return information for all users (missing access to all groups capability for non-editing teacher).
2145
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2146
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2147
        $this->assertCount(0, $result['warnings']);
2148
        $this->assertFalse(isset($result['gradingsummary']));
2149
 
2150
        // Should return all participants if we grant accessallgroups capability to the normal teacher role.
2151
        $context = \context_course::instance($assign->get_instance()->course);
2152
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2153
        assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id, true);
2154
        accesslib_clear_all_caches_for_unit_testing();
2155
 
2156
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2157
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2158
        $this->assertCount(0, $result['warnings']);
2159
        $this->assertEquals(2, $result['gradingsummary']['participantcount']);
2160
        $this->assertEquals(0, $result['gradingsummary']['submissiondraftscount']);
2161
        $this->assertEquals(1, $result['gradingsummary']['submissionssubmittedcount']); // One student from G1 submitted.
2162
        $this->assertEquals(1, $result['gradingsummary']['submissionsneedgradingcount']); // One student from G1 submitted.
2163
 
2164
        // Now check draft submissions.
2165
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(false);
2166
        $this->setUser($teacher);
2167
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id, 0, $g1->id);
2168
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2169
        $this->assertCount(0, $result['warnings']);
2170
        $this->assertEquals(1, $result['gradingsummary']['participantcount']);
2171
        $this->assertEquals(1, $result['gradingsummary']['submissiondraftscount']); // We have a draft submission.
2172
        $this->assertEquals(0, $result['gradingsummary']['submissionssubmittedcount']); // We have only draft submissions.
2173
        $this->assertEquals(0, $result['gradingsummary']['submissionsneedgradingcount']); // We have only draft submissions.
2174
 
2175
        // Test assignment data.
2176
        $this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
2177
    }
2178
 
2179
    /**
2180
     * Test get_submission_status for a reopened submission.
2181
     */
11 efrain 2182
    public function test_get_submission_status_in_reopened_status(): void {
1 efrain 2183
        global $USER;
2184
 
2185
        $this->resetAfterTest(true);
2186
 
2187
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);
2188
        $studentsubmission = $assign->get_user_submission($student1->id, true);
2189
 
2190
        $this->setUser($teacher);
2191
        // Grade and reopen.
2192
        $feedbackpluginparams = array();
2193
        $feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
2194
        $feedbackeditorparams = array('text' => 'Yeeha!',
2195
                                        'format' => 1);
2196
        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
2197
        $result = mod_assign_external::save_grade(
2198
            $instance->id,
2199
            $student1->id,
2200
            50.0,
2201
            -1,
2202
            false,
2203
            'released',
2204
            false,
2205
            $feedbackpluginparams);
2206
        $USER->ignoresesskey = true;
2207
        $assign->testable_process_add_attempt($student1->id);
2208
 
2209
        $this->setUser($student1);
2210
 
2211
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2212
        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2213
        $this->assertDebuggingCalled();
2214
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2215
 
2216
        $this->assertCount(0, $result['warnings']);
2217
        $this->assertFalse(isset($result['gradingsummary']));
2218
 
2219
        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2220
        $this->assertTrue($result['lastattempt']['canedit']);
2221
        $this->assertFalse($result['lastattempt']['cansubmit']);
2222
        $this->assertFalse($result['lastattempt']['locked']);
2223
        $this->assertFalse($result['lastattempt']['graded']);
2224
        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2225
        $this->assertFalse($result['lastattempt']['blindmarking']);
2226
        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2227
        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2228
 
2229
        // Check new attempt reopened.
2230
        $this->assertEquals($student1->id, $result['lastattempt']['submission']['userid']);
2231
        $this->assertEquals(1, $result['lastattempt']['submission']['attemptnumber']);
2232
        $this->assertEquals('reopened', $result['lastattempt']['submission']['status']);
2233
        $this->assertEquals(0, $result['lastattempt']['submission']['groupid']);
2234
        $this->assertEquals($assign->get_instance()->id, $result['lastattempt']['submission']['assignment']);
2235
        $this->assertEquals(1, $result['lastattempt']['submission']['latest']);
2236
        $this->assertCount(3, $result['lastattempt']['submission']['plugins']);
2237
 
2238
        // Now see feedback and the attempts history (remember, is a submission reopened).
2239
        // Only 2 fields (no grade, no plugins data).
2240
        $this->assertCount(2, $result['feedback']);
2241
 
2242
        // One previous attempt.
2243
        $this->assertCount(1, $result['previousattempts']);
2244
        $this->assertEquals(0, $result['previousattempts'][0]['attemptnumber']);
2245
        $this->assertEquals(50, $result['previousattempts'][0]['grade']['grade']);
2246
        $this->assertEquals($teacher->id, $result['previousattempts'][0]['grade']['grader']);
2247
        $this->assertEquals($student1->id, $result['previousattempts'][0]['grade']['userid']);
2248
 
2249
        // Map plugins based on their type - we can't rely on them being in a
2250
        // particular order, especially if 3rd party plugins are installed.
2251
        $feedbackplugins = array();
2252
        foreach ($result['previousattempts'][0]['feedbackplugins'] as $plugin) {
2253
            $feedbackplugins[$plugin['type']] = $plugin;
2254
        }
2255
        $this->assertEquals('Yeeha!', $feedbackplugins['comments']['editorfields'][0]['text']);
2256
 
2257
        $submissionplugins = array();
2258
        foreach ($result['previousattempts'][0]['submission']['plugins'] as $plugin) {
2259
            $submissionplugins[$plugin['type']] = $plugin;
2260
        }
2261
        // Format expected online text.
2262
        $onlinetext = 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>';
2263
        list($expectedtext, $expectedformat) = \core_external\util::format_text(
2264
            $onlinetext,
2265
            FORMAT_HTML,
2266
            $assign->get_context(),
2267
            'assignsubmission_onlinetext',
2268
            ASSIGNSUBMISSION_ONLINETEXT_FILEAREA,
2269
            $studentsubmission->id
2270
        );
2271
 
2272
        $this->assertEquals($expectedtext, $submissionplugins['onlinetext']['editorfields'][0]['text']);
2273
        $this->assertEquals($expectedformat, $submissionplugins['onlinetext']['editorfields'][0]['format']);
2274
        $this->assertEquals('/', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);
2275
        $this->assertEquals('t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filename']);
2276
 
2277
        // Test assignment data.
2278
        $this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
2279
    }
2280
 
2281
    /**
2282
     * Test access control for get_submission_status.
2283
     */
11 efrain 2284
    public function test_get_submission_status_access_control(): void {
1 efrain 2285
        $this->resetAfterTest(true);
2286
 
2287
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();
2288
 
2289
        $this->setUser($student2);
2290
 
2291
        // Access control test.
2292
        $this->expectException(\required_capability_exception::class);
2293
        mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id);
2294
 
2295
    }
2296
 
2297
    /**
2298
     * Test hidden grader for get_submission_status.
2299
     */
11 efrain 2300
    public function test_get_submission_status_hidden_grader(): void {
1 efrain 2301
        $this->resetAfterTest(true);
2302
 
2303
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(true);
2304
 
2305
        // Grade the assign for the student1.
2306
        $this->setUser($teacher);
2307
 
2308
        $data = new \stdClass();
2309
        $data->grade = '50.0';
2310
        $data->assignfeedbackcomments_editor = ['text' => ''];
2311
        $assign->testable_apply_grade_to_user($data, $student1->id, 0);
2312
 
2313
        $this->setUser($student1);
2314
 
2315
        // Check that the student can see the grader by default.
2316
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2317
        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2318
        $this->assertDebuggingCalled();
2319
 
2320
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2321
 
2322
        $this->assertTrue(isset($result['feedback']));
2323
        $this->assertTrue(isset($result['feedback']['grade']));
2324
        $this->assertEquals($teacher->id, $result['feedback']['grade']['grader']);
2325
 
2326
        // Now change the setting so the grader is hidden.
2327
        $this->setAdminUser();
2328
 
2329
        $instance = $assign->get_instance();
2330
        $instance->instance = $instance->id;
2331
        $instance->hidegrader = true;
2332
        $assign->update_instance($instance);
2333
 
2334
        $this->setUser($student1);
2335
 
2336
        // Check that the student cannot see the grader anymore.
2337
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2338
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2339
 
2340
        $this->assertTrue(isset($result['feedback']));
2341
        $this->assertTrue(isset($result['feedback']['grade']));
2342
        $this->assertEquals(-1, $result['feedback']['grade']['grader']);
2343
 
2344
        // Check that the teacher can see the grader.
2345
        $this->setUser($teacher);
2346
 
2347
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id, $student1->id);
2348
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2349
 
2350
        $this->assertTrue(isset($result['feedback']));
2351
        $this->assertTrue(isset($result['feedback']['grade']));
2352
        $this->assertEquals($teacher->id, $result['feedback']['grade']['grader']);
2353
 
2354
        // Test assignment data.
2355
        $this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
2356
    }
2357
 
2358
    /**
2359
     * Test get_submission_status with override for student.
2360
     */
11 efrain 2361
    public function test_get_submission_status_with_override(): void {
1 efrain 2362
        global $DB;
2363
 
2364
        $this->resetAfterTest(true);
2365
 
2366
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();
2367
 
2368
        $overridedata = new \stdClass();
2369
        $overridedata->assignid = $assign->get_instance()->id;
2370
        $overridedata->userid = $student1->id;
2371
        $overridedata->allowsubmissionsfromdate = time() + YEARSECS;
2372
        $DB->insert_record('assign_overrides', $overridedata);
2373
 
2374
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2375
        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2376
        $this->assertDebuggingCalled();
2377
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2378
 
2379
        $this->assertCount(0, $result['warnings']);
2380
        $this->assertFalse(isset($result['gradingsummary']));
2381
        $this->assertFalse(isset($result['feedback']));
2382
        $this->assertFalse(isset($result['previousattempts']));
2383
 
2384
        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2385
        $this->assertFalse($result['lastattempt']['canedit']);  // False because of override.
2386
        $this->assertFalse($result['lastattempt']['cansubmit']);
2387
        $this->assertFalse($result['lastattempt']['locked']);
2388
        $this->assertFalse($result['lastattempt']['graded']);
2389
        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2390
        $this->assertFalse($result['lastattempt']['blindmarking']);
2391
        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2392
        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2393
 
2394
        // Same assignment but user without override.
2395
        $this->setUser($student2);
2396
 
2397
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2398
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2399
 
2400
        // The submission is now in draft mode.
2401
        $this->assertCount(0, $result['warnings']);
2402
        $this->assertFalse(isset($result['gradingsummary']));
2403
        $this->assertFalse(isset($result['feedback']));
2404
        $this->assertFalse(isset($result['previousattempts']));
2405
 
2406
        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2407
        $this->assertTrue($result['lastattempt']['canedit']);  // True because there is not override for this user.
2408
        $this->assertFalse($result['lastattempt']['cansubmit']);
2409
        $this->assertFalse($result['lastattempt']['locked']);
2410
        $this->assertFalse($result['lastattempt']['graded']);
2411
        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2412
        $this->assertFalse($result['lastattempt']['blindmarking']);
2413
        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2414
        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2415
 
2416
        // Test assignment data.
2417
        $this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
2418
    }
2419
 
2420
    /**
2421
     * Test get_submission_status with time limit for student.
2422
     *
2423
     * @covers \mod_assign_external::get_submission_status
2424
     */
11 efrain 2425
    public function test_get_submission_status_with_time_limit_enabled(): void {
1 efrain 2426
        $this->resetAfterTest();
2427
 
2428
        set_config('enabletimelimit', '1', 'assign');
2429
 
2430
        // Add time limit of 5 minutes to assignment. To edit activity, activity editor must not be empty.
2431
        list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(
2432
            true, [
2433
                'timelimit' => 300,
2434
                'activityeditor' => [
2435
                    'text' => 'Test activity',
2436
                    'format' => 1,
2437
                ],
2438
            ]
2439
        );
2440
 
2441
        // Add an intro attachment.
2442
        $fs = get_file_storage();
2443
        $context = \context_module::instance($instance->cmid);
2444
        $filerecord = array(
2445
            'contextid' => $context->id,
2446
            'component' => 'mod_assign',
2447
            'filearea'  => ASSIGN_INTROATTACHMENT_FILEAREA,
2448
            'filename' => 'Test intro file',
2449
            'itemid'    => 0,
2450
            'filepath'  => '/'
2451
        );
2452
        $fs->create_file_from_string($filerecord, 'Test assign file');
2453
 
2454
        // Set optional param to indicate start time required.
2455
        $_GET['action'] = 'editsubmission';
2456
        $cm = get_coursemodule_from_instance('assign', $instance->id);
2457
        $context = \context_module::instance($cm->id);
2458
        (new \assign($context, $cm, $cm->course))->get_user_submission(0, true);
2459
 
2460
        $result = mod_assign_external::get_submission_status($assign->get_instance()->id);
2461
        // We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
2462
        $this->assertDebuggingCalled();
2463
        $result = external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
2464
 
2465
        $this->assertCount(0, $result['warnings']);
2466
        $this->assertFalse(isset($result['gradingsummary']));
2467
        $this->assertFalse(isset($result['feedback']));
2468
        $this->assertFalse(isset($result['previousattempts']));
2469
 
2470
        $this->assertTrue($result['lastattempt']['submissionsenabled']);
2471
        $this->assertFalse($result['lastattempt']['canedit']);
2472
        $this->assertFalse($result['lastattempt']['cansubmit']);
2473
        $this->assertFalse($result['lastattempt']['locked']);
2474
        $this->assertFalse($result['lastattempt']['graded']);
2475
        $this->assertEmpty($result['lastattempt']['extensionduedate']);
2476
        $this->assertFalse($result['lastattempt']['blindmarking']);
2477
        $this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
2478
        $this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
2479
        $this->assertEquals(300, $result['lastattempt']['timelimit']);
2480
        $this->assertNotNull($result['lastattempt']['submission']['timestarted']);
2481
        $this->assertLessThanOrEqual(time(), $result['lastattempt']['submission']['timestarted']);
2482
 
2483
        // Test assignment data.
2484
        $this->assertNotEmpty($result['assignmentdata']);
2485
        $this->assertEquals('Test activity', $result['assignmentdata']['activity']);
2486
        $this->assertEquals(1, $result['assignmentdata']['activityformat']);
2487
        $this->assertCount(2, $result['assignmentdata']['attachments']);
2488
        $introattachments = $result['assignmentdata']['attachments']['intro'];
2489
        $activityattachments = $result['assignmentdata']['attachments']['activity'];
2490
        $this->assertCount(1, $introattachments);
2491
        $intro = reset($introattachments);
2492
        $this->assertEquals('Test intro file', $intro['filename']);
2493
        $this->assertEmpty($activityattachments);
2494
    }
2495
 
2496
    /**
2497
     * get_participant should throw an excaption if the requested assignment doesn't exist.
2498
     */
11 efrain 2499
    public function test_get_participant_no_assignment(): void {
1 efrain 2500
        $this->resetAfterTest(true);
2501
        $this->expectException(\moodle_exception::class);
2502
        mod_assign_external::get_participant('-1', '-1', false);
2503
    }
2504
 
2505
    /**
2506
     * get_participant should throw a require_login_exception if the user doesn't have access
2507
     * to view assignments.
2508
     */
11 efrain 2509
    public function test_get_participant_no_view_capability(): void {
1 efrain 2510
        global $DB;
2511
        $this->resetAfterTest(true);
2512
 
2513
        $result = $this->create_assign_with_student_and_teacher();
2514
        $assign = $result['assign'];
2515
        $student = $result['student'];
2516
        $course = $result['course'];
2517
        $context = \context_course::instance($course->id);
2518
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2519
 
2520
        $this->setUser($student);
2521
        assign_capability('mod/assign:view', CAP_PROHIBIT, $studentrole->id, $context->id, true);
2522
 
2523
        $this->expectException(\require_login_exception::class);
2524
        mod_assign_external::get_participant($assign->id, $student->id, false);
2525
    }
2526
 
2527
    /**
2528
     * get_participant should throw a required_capability_exception if the user doesn't have access
2529
     * to view assignment grades.
2530
     */
11 efrain 2531
    public function test_get_participant_no_grade_capability(): void {
1 efrain 2532
        global $DB;
2533
        $this->resetAfterTest(true);
2534
 
2535
        $result = $this->create_assign_with_student_and_teacher();
2536
        $assign = $result['assign'];
2537
        $student = $result['student'];
2538
        $teacher = $result['teacher'];
2539
        $course = $result['course'];
2540
        $context = \context_course::instance($course->id);
2541
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2542
 
2543
        $this->setUser($teacher);
2544
        assign_capability('mod/assign:viewgrades', CAP_PROHIBIT, $teacherrole->id, $context->id, true);
2545
        assign_capability('mod/assign:grade', CAP_PROHIBIT, $teacherrole->id, $context->id, true);
2546
        accesslib_clear_all_caches_for_unit_testing();
2547
 
2548
        $this->expectException(\required_capability_exception::class);
2549
        mod_assign_external::get_participant($assign->id, $student->id, false);
2550
    }
2551
 
2552
    /**
2553
     * get_participant should throw an exception if the user isn't enrolled in the course.
2554
     */
11 efrain 2555
    public function test_get_participant_no_participant(): void {
1 efrain 2556
        global $DB;
2557
        $this->resetAfterTest(true);
2558
 
2559
        $result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true));
2560
        $student = $this->getDataGenerator()->create_user();
2561
        $assign = $result['assign'];
2562
        $teacher = $result['teacher'];
2563
 
2564
        $this->setUser($teacher);
2565
 
2566
        $this->expectException(\moodle_exception::class);
2567
        $result = mod_assign_external::get_participant($assign->id, $student->id, false);
2568
        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2569
    }
2570
 
2571
    /**
2572
     * get_participant should return a summarised list of details with a different fullname if blind
2573
     * marking is on for the requested assignment.
2574
     */
11 efrain 2575
    public function test_get_participant_blind_marking(): void {
1 efrain 2576
        global $DB;
2577
        $this->resetAfterTest(true);
2578
 
2579
        $result = $this->create_assign_with_student_and_teacher(array('blindmarking' => true));
2580
        $assign = $result['assign'];
2581
        $student = $result['student'];
2582
        $teacher = $result['teacher'];
2583
        $course = $result['course'];
2584
        $context = \context_course::instance($course->id);
2585
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2586
 
2587
        $this->setUser($teacher);
2588
 
2589
        $result = mod_assign_external::get_participant($assign->id, $student->id, true);
2590
        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2591
        $this->assertEquals($student->id, $result['id']);
2592
        $this->assertFalse(fullname($student) == $result['fullname']);
2593
        $this->assertFalse($result['submitted']);
2594
        $this->assertFalse($result['requiregrading']);
2595
        $this->assertFalse($result['grantedextension']);
2596
        $this->assertEquals('', $result['submissionstatus']);
2597
        $this->assertTrue($result['blindmarking']);
2598
        // Make sure we don't get any additional info.
2599
        $this->assertArrayNotHasKey('user', $result);
2600
    }
2601
 
2602
    /**
2603
     * get_participant should return a summarised list of details if requested.
2604
     */
11 efrain 2605
    public function test_get_participant_no_user(): void {
1 efrain 2606
        global $DB;
2607
        $this->resetAfterTest(true);
2608
 
2609
        $result = $this->create_assign_with_student_and_teacher();
2610
        $assignmodule = $result['assign'];
2611
        $student = $result['student'];
2612
        $teacher = $result['teacher'];
2613
        $course = $result['course'];
2614
        $context = \context_course::instance($course->id);
2615
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2616
 
2617
        // Create an assign instance to save a submission.
2618
        set_config('submissionreceipts', 0, 'assign');
2619
 
2620
        $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
2621
        $context = \context_module::instance($cm->id);
2622
 
2623
        $assign = new \assign($context, $cm, $course);
2624
 
2625
        $this->setUser($student);
2626
 
2627
        // Simulate a submission.
2628
        $data = new \stdClass();
2629
        $data->onlinetext_editor = array(
2630
            'itemid' => file_get_unused_draft_itemid(),
2631
            'text' => 'Student submission text',
2632
            'format' => FORMAT_MOODLE
2633
        );
2634
 
2635
        $notices = array();
2636
        $assign->save_submission($data, $notices);
2637
 
2638
        $data = new \stdClass;
2639
        $data->userid = $student->id;
2640
        $assign->submit_for_grading($data, array());
2641
 
2642
        $this->setUser($teacher);
2643
 
2644
        $result = mod_assign_external::get_participant($assignmodule->id, $student->id, false);
2645
        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2646
        $this->assertEquals($student->id, $result['id']);
2647
        $this->assertEquals(fullname($student), $result['fullname']);
2648
        $this->assertTrue($result['submitted']);
2649
        $this->assertTrue($result['requiregrading']);
2650
        $this->assertFalse($result['grantedextension']);
2651
        $this->assertEquals(ASSIGN_SUBMISSION_STATUS_SUBMITTED, $result['submissionstatus']);
2652
        $this->assertFalse($result['blindmarking']);
2653
        // Make sure we don't get any additional info.
2654
        $this->assertArrayNotHasKey('user', $result);
2655
    }
2656
 
2657
    /**
2658
     * get_participant should return user details if requested.
2659
     */
11 efrain 2660
    public function test_get_participant_full_details(): void {
1 efrain 2661
        global $DB;
2662
        $this->resetAfterTest(true);
2663
 
2664
        $result = $this->create_assign_with_student_and_teacher();
2665
        $assign = $result['assign'];
2666
        $student = $result['student'];
2667
        $teacher = $result['teacher'];
2668
        $course = $result['course'];
2669
        $context = \context_course::instance($course->id);
2670
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2671
 
2672
        $this->setUser($teacher);
2673
 
2674
        $result = mod_assign_external::get_participant($assign->id, $student->id, true);
2675
        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2676
        // Check some of the extended properties we get when requesting the user.
2677
        $this->assertEquals($student->id, $result['id']);
2678
        // We should get user infomation back.
2679
        $user = $result['user'];
2680
        $this->assertFalse(empty($user));
2681
        $this->assertEquals($student->firstname, $user['firstname']);
2682
        $this->assertEquals($student->lastname, $user['lastname']);
2683
        $this->assertEquals($student->email, $user['email']);
2684
    }
2685
 
2686
    /**
2687
     * get_participant should return group details if a group submission was
2688
     * submitted.
2689
     */
11 efrain 2690
    public function test_get_participant_group_submission(): void {
1 efrain 2691
        global $DB;
2692
 
2693
        $this->resetAfterTest(true);
2694
 
2695
        $result = $this->create_assign_with_student_and_teacher(array(
2696
            'assignsubmission_onlinetext_enabled' => 1,
2697
            'teamsubmission' => 1
2698
        ));
2699
        $assignmodule = $result['assign'];
2700
        $student = $result['student'];
2701
        $teacher = $result['teacher'];
2702
        $course = $result['course'];
2703
        $context = \context_course::instance($course->id);
2704
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
2705
        $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
2706
        $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
2707
        $context = \context_module::instance($cm->id);
2708
        $assign = new mod_assign_testable_assign($context, $cm, $course);
2709
 
2710
        groups_add_member($group, $student);
2711
 
2712
        $this->setUser($student);
2713
        $submission = $assign->get_group_submission($student->id, $group->id, true);
2714
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2715
        $assign->testable_update_submission($submission, $student->id, true, false);
2716
        $data = new \stdClass();
2717
        $data->onlinetext_editor = array(
2718
            'itemid' => file_get_unused_draft_itemid(),
2719
            'text' => 'Submission text',
2720
            'format' => FORMAT_MOODLE);
2721
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
2722
        $plugin->save($submission, $data);
2723
 
2724
        $this->setUser($teacher);
2725
 
2726
        $result = mod_assign_external::get_participant($assignmodule->id, $student->id, false);
2727
        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2728
        // Check some of the extended properties we get when not requesting a summary.
2729
        $this->assertEquals($student->id, $result['id']);
2730
        $this->assertEquals($group->id, $result['groupid']);
2731
        $this->assertEquals($group->name, $result['groupname']);
2732
    }
2733
 
2734
    /**
2735
     * Test get_participant() when relative dates mode is enabled on the course.
2736
     *
2737
     * @dataProvider get_participant_relative_dates_provider
2738
     * @param array $courseconfig the config to use when creating the course.
2739
     * @param array $assignconfig the config to use when creating the assignment.
2740
     * @param array $enrolconfig the enrolement to create.
2741
     * @param array $expectedproperties array of expected assign properties.
2742
     */
2743
    public function test_get_participant_relative_dates(array $courseconfig, array $assignconfig, array $enrolconfig,
11 efrain 2744
            array $expectedproperties): void {
1 efrain 2745
        $this->resetAfterTest();
2746
 
2747
        set_config('enablecourserelativedates', true); // Enable relative dates at site level.
2748
 
2749
        $course = $this->getDataGenerator()->create_course($courseconfig);
2750
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
2751
        $assignconfig['course'] = $course->id;
2752
        $instance = $generator->create_instance($assignconfig);
2753
        $cm = get_coursemodule_from_instance('assign', $instance->id);
2754
        $context = \context_module::instance($cm->id);
2755
        $assign = new \assign($context, $cm, $course);
2756
 
2757
        $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));
2758
 
2759
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher', null, 'manual', time() - 50 * DAYSECS);
2760
 
2761
        $this->setUser($teacher);
2762
        $result = mod_assign_external::get_participant($assign->get_instance()->id, $user->id, false);
2763
        $result = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $result);
2764
 
2765
        foreach ($expectedproperties as $propertyname => $propertyval) {
2766
            $this->assertEquals($propertyval, $result[$propertyname]);
2767
        }
2768
    }
2769
 
2770
    /**
2771
     * The test_get_participant_relative_dates data provider.
2772
     */
2773
    public function get_participant_relative_dates_provider() {
2774
        $timenow = time();
2775
 
2776
        return [
2777
            'Student whose enrolment starts after the course start date, relative dates mode enabled' => [
2778
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
2779
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
2780
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
2781
                    'startdate' => $timenow - 8 * DAYSECS],
2782
                'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS]
2783
            ],
2784
            'Student whose enrolment starts before the course start date, relative dates mode enabled' => [
2785
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
2786
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
2787
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
2788
                    'startdate' => $timenow - 12 * DAYSECS],
2789
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
2790
            ],
2791
        ];
2792
    }
2793
 
2794
    /**
2795
     * Test for mod_assign_external::list_participants().
2796
     *
2797
     * @throws coding_exception
2798
     */
11 efrain 2799
    public function test_list_participants_user_info_with_special_characters(): void {
1 efrain 2800
        global $CFG, $DB;
2801
        $this->resetAfterTest(true);
2802
        $CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution';
2803
 
2804
        $data = $this->create_assign_with_student_and_teacher();
2805
        $assignment = $data['assign'];
2806
        $teacher = $data['teacher'];
2807
 
2808
        // Set data for student info that contain special characters.
2809
        $student = $data['student'];
2810
        $student->idnumber = '<\'"1am@wesome&c00l"\'>';
2811
        $student->phone1 = '+63 (999) 888-7777';
2812
        $student->phone2 = '(011) [15]4-123-4567';
2813
        $student->department = 'Arts & Sciences & \' " ¢ £ © € ¥ ® < >';
2814
        $student->institution = 'University of Awesome People & \' " ¢ £ © € ¥ ® < >';
2815
        // Assert that we have valid user data.
2816
        $this->assertTrue(\core_user::validate($student));
2817
        // Update the user record.
2818
        $DB->update_record('user', $student);
2819
 
2820
        $this->setUser($teacher);
2821
        $participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, true, true);
2822
        $participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
2823
        $this->assertCount(1, $participants);
2824
 
2825
        // Asser that we have a valid response data.
2826
        $response = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
2827
        $this->assertEquals($response, $participants);
2828
 
2829
        // Check participant data.
2830
        $participant = $participants[0];
2831
        $this->assertEquals($student->idnumber, $participant['idnumber']);
2832
        $this->assertEquals($student->email, $participant['email']);
2833
        $this->assertEquals($student->phone1, $participant['phone1']);
2834
        $this->assertEquals($student->phone2, $participant['phone2']);
2835
        $this->assertEquals($student->department, $participant['department']);
2836
        $this->assertEquals($student->institution, $participant['institution']);
2837
        $this->assertEquals('', $participant['submissionstatus']);
2838
        $this->assertArrayHasKey('enrolledcourses', $participant);
2839
 
2840
        $participants = mod_assign_external::list_participants($assignment->id, 0, '', 0, 0, false, false, true);
2841
        $participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
2842
        // Check that the list of courses the participant is enrolled is not returned.
2843
        $participant = $participants[0];
2844
        $this->assertArrayNotHasKey('enrolledcourses', $participant);
2845
    }
2846
 
2847
    /**
2848
     * Test for the type of the user-related properties in mod_assign_external::list_participants_returns().
2849
     */
11 efrain 2850
    public function test_list_participants_returns_user_property_types(): void {
1 efrain 2851
        // Get user properties.
2852
        $userdesc = core_user_external::user_description();
2853
        $this->assertTrue(isset($userdesc->keys));
2854
        $userproperties = array_keys($userdesc->keys);
2855
 
2856
        // Get returns description for mod_assign_external::list_participants_returns().
2857
        $listreturns = mod_assign_external::list_participants_returns();
2858
        $this->assertTrue(isset($listreturns->content));
2859
        $listreturnsdesc = $listreturns->content->keys;
2860
 
2861
        // Iterate over list returns description's keys.
2862
        foreach ($listreturnsdesc as $key => $desc) {
2863
            // Check if key exists in user properties and the description has a type attribute.
2864
            if (in_array($key, $userproperties) && isset($desc->type)) {
2865
                try {
2866
                    // The \core_user::get_property_type() method might throw a coding_exception since
2867
                    // core_user_external::user_description() might contain properties that are not yet included in
2868
                    // core_user's $propertiescache.
2869
                    $propertytype = \core_user::get_property_type($key);
2870
 
2871
                    // Assert that user-related property types match those of the defined in core_user.
2872
                    $this->assertEquals($propertytype, $desc->type);
2873
                } catch (\coding_exception $e) {
2874
                    // All good.
2875
                }
2876
            }
2877
        }
2878
    }
2879
 
2880
    /**
2881
     * Test for WS returning group.
2882
     * @covers ::get_participant
2883
     * @covers ::list_participants
2884
     */
11 efrain 2885
    public function test_participants_info_with_groups(): void {
1 efrain 2886
        global $CFG;
2887
        $this->resetAfterTest(true);
2888
        $CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution';
2889
 
2890
        // Set up filtering.
2891
        filter_set_global_state('multilang', TEXTFILTER_ON);
2892
        filter_set_applies_to_strings('multilang', true);
2893
        external_settings::get_instance()->set_filter(true);
2894
 
2895
        $result = $this->create_assign_with_student_and_teacher([
2896
            'assignsubmission_onlinetext_enabled' => 1,
2897
            'teamsubmission' => 1
2898
        ]);
2899
        $assignmodule = $result['assign'];
2900
        $student = $result['student'];
2901
        $teacher = $result['teacher'];
2902
        $course = $result['course'];
2903
        $context = \context_course::instance($course->id);
2904
 
2905
        $gname = '<span class="multilang" lang="en">G (en)</span><span class="multilang" lang="es">G (es)</span>';
2906
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => $gname]);
2907
        groups_add_member($group, $student);
2908
 
2909
        $cm = get_coursemodule_from_instance('assign', $assignmodule->id);
2910
        new mod_assign_testable_assign($context, $cm, $course);
2911
 
2912
        // Prepare submission.
2913
        $this->setUser($teacher);
2914
 
2915
        // Test mod_assign_external::list_participants.
2916
        $participants = mod_assign_external::list_participants($assignmodule->id, $group->id, '', 0, 0, false, true, true);
2917
        $participants = external_api::clean_returnvalue(mod_assign_external::list_participants_returns(), $participants);
2918
        $this->assertEquals($group->id, $participants[0]['groupid']);
2919
        $this->assertEquals(format_string($gname, true), $participants[0]['groupname']);
2920
 
2921
        // Test mod_assign_external::get_participant.
2922
        $participant = mod_assign_external::get_participant($assignmodule->id, $student->id, true);
2923
        $participant = external_api::clean_returnvalue(mod_assign_external::get_participant_returns(), $participant);
2924
        $this->assertEquals($group->id, $participant['groupid']);
2925
        $this->assertEquals(format_string($gname, true), $participant['groupname']);
2926
    }
2927
 
2928
    /**
2929
     * Test test_view_assign
2930
     */
11 efrain 2931
    public function test_view_assign(): void {
1 efrain 2932
        global $CFG;
2933
 
2934
        $CFG->enablecompletion = 1;
2935
        $this->resetAfterTest();
2936
 
2937
        $this->setAdminUser();
2938
        // Setup test data.
2939
        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
2940
        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id),
2941
                                                            array('completion' => 2, 'completionview' => 1));
2942
        $context = \context_module::instance($assign->cmid);
2943
        $cm = get_coursemodule_from_instance('assign', $assign->id);
2944
 
2945
        $result = mod_assign_external::view_assign($assign->id);
2946
        $result = external_api::clean_returnvalue(mod_assign_external::view_assign_returns(), $result);
2947
        $this->assertTrue($result['status']);
2948
        $this->assertEmpty($result['warnings']);
2949
 
2950
        // Check completion status.
2951
        $completion = new \completion_info($course);
2952
        $completiondata = $completion->get_data($cm);
2953
        $this->assertEquals(1, $completiondata->completionstate);
2954
    }
2955
}