MDL-59243 mod_workshop: New WS mod_workshop_add_submission
[moodle.git] / mod / workshop / tests / external_test.php
CommitLineData
9f1ab2db
JL
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Workshop module external functions tests
19 *
20 * @package mod_workshop
21 * @category external
22 * @copyright 2017 Juan Leyva <juan@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 * @since Moodle 3.4
25 */
26
27defined('MOODLE_INTERNAL') || die();
28
29global $CFG;
30
31require_once($CFG->dirroot . '/webservice/tests/helpers.php');
32require_once($CFG->dirroot . '/mod/workshop/lib.php');
33
34use mod_workshop\external\workshop_summary_exporter;
35
36/**
37 * Workshop module external functions tests
38 *
39 * @package mod_workshop
40 * @category external
41 * @copyright 2017 Juan Leyva <juan@moodle.com>
42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 * @since Moodle 3.4
44 */
45class mod_workshop_external_testcase extends externallib_advanced_testcase {
46
47 /** @var stdClass course object */
48 private $course;
49 /** @var stdClass workshop object */
50 private $workshop;
51 /** @var stdClass context object */
52 private $context;
53 /** @var stdClass cm object */
54 private $cm;
55 /** @var stdClass student object */
56 private $student;
57 /** @var stdClass teacher object */
58 private $teacher;
59 /** @var stdClass student role object */
60 private $studentrole;
61 /** @var stdClass teacher role object */
62 private $teacherrole;
63
64 /**
65 * Set up for every test
66 */
67 public function setUp() {
68 global $DB;
69 $this->resetAfterTest();
70 $this->setAdminUser();
71
72 // Setup test data.
73 $this->course = $this->getDataGenerator()->create_course();
74 $this->workshop = $this->getDataGenerator()->create_module('workshop', array('course' => $this->course->id));
75 $this->context = context_module::instance($this->workshop->cmid);
76 $this->cm = get_coursemodule_from_instance('workshop', $this->workshop->id);
77
78 // Create users.
79 $this->student = self::getDataGenerator()->create_user();
80 $this->teacher = self::getDataGenerator()->create_user();
81
82 // Users enrolments.
83 $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
84 $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
85 $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
86 $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
87 }
88
89 /**
90 * Test test_mod_workshop_get_workshops_by_courses
91 */
92 public function test_mod_workshop_get_workshops_by_courses() {
93 global $DB;
94
95 // Create additional course.
96 $course2 = self::getDataGenerator()->create_course();
97
98 // Second workshop.
99 $record = new stdClass();
100 $record->course = $course2->id;
101 $workshop2 = self::getDataGenerator()->create_module('workshop', $record);
102
103 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
104 $enrol = enrol_get_plugin('manual');
105 $enrolinstances = enrol_get_instances($course2->id, true);
106 foreach ($enrolinstances as $courseenrolinstance) {
107 if ($courseenrolinstance->enrol == "manual") {
108 $instance2 = $courseenrolinstance;
109 break;
110 }
111 }
112 $enrol->enrol_user($instance2, $this->student->id, $this->studentrole->id);
113
114 self::setUser($this->student);
115
116 $returndescription = mod_workshop_external::get_workshops_by_courses_returns();
117
118 // Create what we expect to be returned when querying the two courses.
119 $properties = workshop_summary_exporter::read_properties_definition();
120 $expectedfields = array_keys($properties);
121
122 // Add expected coursemodule and data.
123 $workshop1 = $this->workshop;
124 $workshop1->coursemodule = $workshop1->cmid;
125 $workshop1->introformat = 1;
126 $workshop1->introfiles = [];
127 $workshop1->instructauthorsfiles = [];
128 $workshop1->instructauthorsformat = 1;
129 $workshop1->instructreviewersfiles = [];
130 $workshop1->instructreviewersformat = 1;
131 $workshop1->conclusionfiles = [];
132 $workshop1->conclusionformat = 1;
133
134 $workshop2->coursemodule = $workshop2->cmid;
135 $workshop2->introformat = 1;
136 $workshop2->introfiles = [];
137 $workshop2->instructauthorsfiles = [];
138 $workshop2->instructauthorsformat = 1;
139 $workshop2->instructreviewersfiles = [];
140 $workshop2->instructreviewersformat = 1;
141 $workshop2->conclusionfiles = [];
142 $workshop2->conclusionformat = 1;
143
144 foreach ($expectedfields as $field) {
145 if (!empty($properties[$field]) && $properties[$field]['type'] == PARAM_BOOL) {
146 $workshop1->{$field} = (bool) $workshop1->{$field};
147 $workshop2->{$field} = (bool) $workshop2->{$field};
148 }
149 $expected1[$field] = $workshop1->{$field};
150 $expected2[$field] = $workshop2->{$field};
151 }
152
153 $expectedworkshops = array($expected2, $expected1);
154
155 // Call the external function passing course ids.
156 $result = mod_workshop_external::get_workshops_by_courses(array($course2->id, $this->course->id));
157 $result = external_api::clean_returnvalue($returndescription, $result);
158
159 $this->assertEquals($expectedworkshops, $result['workshops']);
160 $this->assertCount(0, $result['warnings']);
161
162 // Call the external function without passing course id.
163 $result = mod_workshop_external::get_workshops_by_courses();
164 $result = external_api::clean_returnvalue($returndescription, $result);
165 $this->assertEquals($expectedworkshops, $result['workshops']);
166 $this->assertCount(0, $result['warnings']);
167
168 // Unenrol user from second course and alter expected workshops.
169 $enrol->unenrol_user($instance2, $this->student->id);
170 array_shift($expectedworkshops);
171
172 // Call the external function without passing course id.
173 $result = mod_workshop_external::get_workshops_by_courses();
174 $result = external_api::clean_returnvalue($returndescription, $result);
175 $this->assertEquals($expectedworkshops, $result['workshops']);
176
177 // Call for the second course we unenrolled the user from, expected warning.
178 $result = mod_workshop_external::get_workshops_by_courses(array($course2->id));
179 $this->assertCount(1, $result['warnings']);
180 $this->assertEquals('1', $result['warnings'][0]['warningcode']);
181 $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
182 }
977fdfa3
JL
183
184 /**
185 * Test mod_workshop_get_workshop_access_information for students.
186 */
187 public function test_mod_workshop_get_workshop_access_information_student() {
188
189 self::setUser($this->student);
190 $result = mod_workshop_external::get_workshop_access_information($this->workshop->id);
191 $result = external_api::clean_returnvalue(mod_workshop_external::get_workshop_access_information_returns(), $result);
192 // Check default values for capabilities.
193 $enabledcaps = array('canpeerassess', 'cansubmit', 'canview', 'canviewauthornames', 'canviewauthorpublished',
194 'canviewpublishedsubmissions', 'canexportsubmissions');
195
196 foreach ($result as $capname => $capvalue) {
197 if (strpos($capname, 'can') !== 0) {
198 continue;
199 }
200 if (in_array($capname, $enabledcaps)) {
201 $this->assertTrue($capvalue);
202 } else {
203 $this->assertFalse($capvalue);
204 }
205 }
206 // Now, unassign some capabilities.
207 unassign_capability('mod/workshop:peerassess', $this->studentrole->id);
208 unassign_capability('mod/workshop:submit', $this->studentrole->id);
209 unset($enabledcaps[0]);
210 unset($enabledcaps[1]);
211 accesslib_clear_all_caches_for_unit_testing();
212
213 $result = mod_workshop_external::get_workshop_access_information($this->workshop->id);
214 $result = external_api::clean_returnvalue(mod_workshop_external::get_workshop_access_information_returns(), $result);
215 foreach ($result as $capname => $capvalue) {
216 if (strpos($capname, 'can') !== 0) {
217 continue;
218 }
219 if (in_array($capname, $enabledcaps)) {
220 $this->assertTrue($capvalue);
221 } else {
222 $this->assertFalse($capvalue);
223 }
224 }
225
226 // Now, specific functionalities.
227 $this->assertFalse($result['creatingsubmissionallowed']);
228 $this->assertFalse($result['modifyingsubmissionallowed']);
229 $this->assertFalse($result['assessingallowed']);
230 $this->assertFalse($result['assessingexamplesallowed']);
c2cf2450 231 $this->assertTrue($result['examplesassessed']);
977fdfa3
JL
232
233 // Switch phase.
234 $workshop = new workshop($this->workshop, $this->cm, $this->course);
235 $workshop->switch_phase(workshop::PHASE_SUBMISSION);
236 $result = mod_workshop_external::get_workshop_access_information($this->workshop->id);
237 $result = external_api::clean_returnvalue(mod_workshop_external::get_workshop_access_information_returns(), $result);
238
239 $this->assertTrue($result['creatingsubmissionallowed']);
240 $this->assertTrue($result['modifyingsubmissionallowed']);
241 $this->assertFalse($result['assessingallowed']);
242 $this->assertFalse($result['assessingexamplesallowed']);
c2cf2450 243 $this->assertTrue($result['examplesassessed']);
977fdfa3
JL
244
245 // Switch to next (to assessment).
246 $workshop = new workshop($this->workshop, $this->cm, $this->course);
247 $workshop->switch_phase(workshop::PHASE_ASSESSMENT);
248 $result = mod_workshop_external::get_workshop_access_information($this->workshop->id);
249 $result = external_api::clean_returnvalue(mod_workshop_external::get_workshop_access_information_returns(), $result);
250
251 $this->assertFalse($result['creatingsubmissionallowed']);
252 $this->assertFalse($result['modifyingsubmissionallowed']);
253 $this->assertTrue($result['assessingallowed']);
254 $this->assertFalse($result['assessingexamplesallowed']);
c2cf2450 255 $this->assertTrue($result['examplesassessed']);
977fdfa3
JL
256 }
257
258 /**
259 * Test mod_workshop_get_workshop_access_information for teachers.
260 */
261 public function test_mod_workshop_get_workshop_access_information_teacher() {
262
263 self::setUser($this->teacher);
264 $result = mod_workshop_external::get_workshop_access_information($this->workshop->id);
265 $result = external_api::clean_returnvalue(mod_workshop_external::get_workshop_access_information_returns(), $result);
266 // Check default values.
267 $disabledcaps = array('canpeerassess', 'cansubmit');
268
269 foreach ($result as $capname => $capvalue) {
270 if (strpos($capname, 'can') !== 0) {
271 continue;
272 }
273 if (in_array($capname, $disabledcaps)) {
274 $this->assertFalse($capvalue);
275 } else {
276 $this->assertTrue($capvalue);
277 }
278 }
279
280 // Now, specific functionalities.
281 $this->assertFalse($result['creatingsubmissionallowed']);
282 $this->assertFalse($result['modifyingsubmissionallowed']);
283 $this->assertFalse($result['assessingallowed']);
284 $this->assertFalse($result['assessingexamplesallowed']);
285 }
cd495029
JL
286
287 /**
288 * Test mod_workshop_get_user_plan for students.
289 */
290 public function test_mod_workshop_get_user_plan_student() {
291
292 self::setUser($this->student);
293 $result = mod_workshop_external::get_user_plan($this->workshop->id);
294 $result = external_api::clean_returnvalue(mod_workshop_external::get_user_plan_returns(), $result);
295
296 $this->assertCount(0, $result['userplan']['examples']); // No examples given.
297 $this->assertCount(5, $result['userplan']['phases']); // Always 5 phases.
298 $this->assertEquals(workshop::PHASE_SETUP, $result['userplan']['phases'][0]['code']); // First phase always setup.
299 $this->assertTrue($result['userplan']['phases'][0]['active']); // First phase "Setup" active in new workshops.
300
301 // Switch phase.
302 $workshop = new workshop($this->workshop, $this->cm, $this->course);
303 $workshop->switch_phase(workshop::PHASE_SUBMISSION);
304
305 $result = mod_workshop_external::get_user_plan($this->workshop->id);
306 $result = external_api::clean_returnvalue(mod_workshop_external::get_user_plan_returns(), $result);
307
308 $this->assertEquals(workshop::PHASE_SUBMISSION, $result['userplan']['phases'][1]['code']);
309 $this->assertTrue($result['userplan']['phases'][1]['active']); // We are now in submission phase.
310 }
311
312 /**
313 * Test mod_workshop_get_user_plan for teachers.
314 */
315 public function test_mod_workshop_get_user_plan_teacher() {
316 global $DB;
317
318 self::setUser($this->teacher);
319 $result = mod_workshop_external::get_user_plan($this->workshop->id);
320 $result = external_api::clean_returnvalue(mod_workshop_external::get_user_plan_returns(), $result);
321
322 $this->assertCount(0, $result['userplan']['examples']); // No examples given.
323 $this->assertCount(5, $result['userplan']['phases']); // Always 5 phases.
324 $this->assertEquals(workshop::PHASE_SETUP, $result['userplan']['phases'][0]['code']); // First phase always setup.
325 $this->assertTrue($result['userplan']['phases'][0]['active']); // First phase "Setup" active in new workshops.
326 $this->assertCount(4, $result['userplan']['phases'][0]['tasks']); // For new empty workshops, always 4 tasks.
327
328 foreach ($result['userplan']['phases'][0]['tasks'] as $task) {
329 if ($task['code'] == 'intro' || $task['code'] == 'instructauthors') {
330 $this->assertEquals(1, $task['completed']);
331 } else {
332 $this->assertEmpty($task['completed']);
333 }
334 }
335
336 // Do some of the tasks asked - switch phase.
337 $workshop = new workshop($this->workshop, $this->cm, $this->course);
338 $workshop->switch_phase(workshop::PHASE_SUBMISSION);
339
340 $result = mod_workshop_external::get_user_plan($this->workshop->id);
341 $result = external_api::clean_returnvalue(mod_workshop_external::get_user_plan_returns(), $result);
342 foreach ($result['userplan']['phases'][0]['tasks'] as $task) {
343 if ($task['code'] == 'intro' || $task['code'] == 'instructauthors' || $task['code'] == 'switchtonextphase') {
344 $this->assertEquals(1, $task['completed']);
345 } else {
346 $this->assertEmpty($task['completed']);
347 }
348 }
349
350 $result = mod_workshop_external::get_user_plan($this->workshop->id);
351 $result = external_api::clean_returnvalue(mod_workshop_external::get_user_plan_returns(), $result);
352
353 $this->assertEquals(workshop::PHASE_SUBMISSION, $result['userplan']['phases'][1]['code']);
354 $this->assertTrue($result['userplan']['phases'][1]['active']); // We are now in submission phase.
355 }
291645f7
JL
356
357 /**
358 * Test test_view_workshop invalid id.
359 */
360 public function test_view_workshop_invalid_id() {
361 $this->expectException('moodle_exception');
362 mod_workshop_external::view_workshop(0);
363 }
364
365 /**
366 * Test test_view_workshop user not enrolled.
367 */
368 public function test_view_workshop_user_not_enrolled() {
369 // Test not-enrolled user.
370 $usernotenrolled = self::getDataGenerator()->create_user();
371 $this->setUser($usernotenrolled);
372 $this->expectException('moodle_exception');
373 mod_workshop_external::view_workshop($this->workshop->id);
374 }
375
376 /**
377 * Test test_view_workshop user student.
378 */
379 public function test_view_workshop_user_student() {
380 // Test user with full capabilities.
381 $this->setUser($this->student);
382
383 // Trigger and capture the event.
384 $sink = $this->redirectEvents();
385
386 $result = mod_workshop_external::view_workshop($this->workshop->id);
387 $result = external_api::clean_returnvalue(mod_workshop_external::view_workshop_returns(), $result);
388 $this->assertTrue($result['status']);
389
390 $events = $sink->get_events();
391 $this->assertCount(1, $events);
392 $event = array_shift($events);
393
394 // Checking that the event contains the expected values.
395 $this->assertInstanceOf('\mod_workshop\event\course_module_viewed', $event);
396 $this->assertEquals($this->context, $event->get_context());
397 $moodleworkshop = new \moodle_url('/mod/workshop/view.php', array('id' => $this->cm->id));
398 $this->assertEquals($moodleworkshop, $event->get_url());
399 $this->assertEventContextNotUsed($event);
400 $this->assertNotEmpty($event->get_name());
401 }
402
403 /**
404 * Test test_view_workshop user missing capabilities.
405 */
406 public function test_view_workshop_user_missing_capabilities() {
407 // Test user with no capabilities.
408 // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
409 assign_capability('mod/workshop:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
410 // Empty all the caches that may be affected by this change.
411 accesslib_clear_all_caches_for_unit_testing();
412 course_modinfo::clear_instance_cache();
413
414 $this->setUser($this->student);
415 $this->expectException('moodle_exception');
416 mod_workshop_external::view_workshop($this->workshop->id);
417 }
c2cf2450
JL
418
419 /**
420 * Test test_add_submission.
421 */
422 public function test_add_submission() {
423 $fs = get_file_storage();
424
425 // Test user with full capabilities.
426 $this->setUser($this->student);
427
428 $title = 'Submission title';
429 $content = 'Submission contents';
430
431 // Create a file in a draft area for inline attachments.
432 $draftidinlineattach = file_get_unused_draft_itemid();
433 $usercontext = context_user::instance($this->student->id);
434 $filenameimg = 'shouldbeanimage.txt';
435 $filerecordinline = array(
436 'contextid' => $usercontext->id,
437 'component' => 'user',
438 'filearea' => 'draft',
439 'itemid' => $draftidinlineattach,
440 'filepath' => '/',
441 'filename' => $filenameimg,
442 );
443 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
444
445 // Create a file in a draft area for regular attachments.
446 $draftidattach = file_get_unused_draft_itemid();
447 $filerecordattach = $filerecordinline;
448 $attachfilename = 'attachment.txt';
449 $filerecordattach['filename'] = $attachfilename;
450 $filerecordattach['itemid'] = $draftidattach;
451 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
452
453 // Switch to submission phase.
454 $workshop = new workshop($this->workshop, $this->cm, $this->course);
455 $workshop->switch_phase(workshop::PHASE_SUBMISSION);
456
457 $result = mod_workshop_external::add_submission($this->workshop->id, $title, $content, FORMAT_MOODLE, $draftidinlineattach,
458 $draftidattach);
459 $result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
460 $this->assertEmpty($result['warnings']);
461
462 // Check submission created.
463 $submission = $workshop->get_submission_by_author($this->student->id);
464 $this->assertEquals($result['submissionid'], $submission->id);
465 $this->assertEquals($title, $submission->title);
466 $this->assertEquals($content, $submission->content);
467
468 // Check files.
469 $contentfiles = $fs->get_area_files($this->context->id, 'mod_workshop', 'submission_content', $submission->id);
470 $this->assertCount(2, $contentfiles);
471 foreach ($contentfiles as $file) {
472 if ($file->is_directory()) {
473 continue;
474 } else {
475 $this->assertEquals($filenameimg, $file->get_filename());
476 }
477 }
478 $contentfiles = $fs->get_area_files($this->context->id, 'mod_workshop', 'submission_attachment', $submission->id);
479 $this->assertCount(2, $contentfiles);
480 foreach ($contentfiles as $file) {
481 if ($file->is_directory()) {
482 continue;
483 } else {
484 $this->assertEquals($attachfilename, $file->get_filename());
485 }
486 }
487 }
488
489 /**
490 * Test test_add_submission invalid phase.
491 */
492 public function test_add_submission_invalid_phase() {
493 $this->setUser($this->student);
494
495 $this->expectException('moodle_exception');
496 mod_workshop_external::add_submission($this->workshop->id, 'Test');
497 }
498
499 /**
500 * Test test_add_submission empty title.
501 */
502 public function test_add_submission_empty_title() {
503 $this->setUser($this->student);
504
505 // Switch to submission phase.
506 $workshop = new workshop($this->workshop, $this->cm, $this->course);
507 $workshop->switch_phase(workshop::PHASE_SUBMISSION);
508
509 $this->expectException('moodle_exception');
510 mod_workshop_external::add_submission($this->workshop->id, '');
511 }
512
513 /**
514 * Test test_add_submission already added.
515 */
516 public function test_add_submission_already_added() {
517 $this->setUser($this->student);
518
519 // Switch to submission phase.
520 $workshop = new workshop($this->workshop, $this->cm, $this->course);
521 $workshop->switch_phase(workshop::PHASE_SUBMISSION);
522
523 // Create the submission.
524 $result = mod_workshop_external::add_submission($this->workshop->id, 'My submission');
525 $result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
526
527 // Try to create it again.
528 $result = mod_workshop_external::add_submission($this->workshop->id, 'My submission');
529 $result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
530 $this->assertEquals(0, $result['submissionid']);
531 $this->assertCount(2, $result['warnings']);
532 $this->assertEquals('fielderror', $result['warnings'][0]['warningcode']);
533 $this->assertEquals('content_editor', $result['warnings'][0]['item']);
534 $this->assertEquals('fielderror', $result['warnings'][1]['warningcode']);
535 $this->assertEquals('attachment_filemanager', $result['warnings'][1]['item']);
536 }
9f1ab2db 537}