Proyectos de Subversion Moodle

Rev

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

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