MDL-59243 mod_workshop: Return creation status in create_submission WS
[moodle.git] / mod / workshop / tests / external_test.php
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/>.
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  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
31 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
32 require_once($CFG->dirroot . '/mod/workshop/lib.php');
34 use mod_workshop\external\workshop_summary_exporter;
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  */
45 class mod_workshop_external_testcase extends externallib_advanced_testcase {
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;
64     /**
65      * Set up for every test
66      */
67     public function setUp() {
68         global $DB;
69         $this->resetAfterTest();
70         $this->setAdminUser();
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);
78         // Create users.
79         $this->student = self::getDataGenerator()->create_user();
80         $this->teacher = self::getDataGenerator()->create_user();
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     }
89     /**
90      * Test test_mod_workshop_get_workshops_by_courses
91      */
92     public function test_mod_workshop_get_workshops_by_courses() {
93         global $DB;
95         // Create additional course.
96         $course2 = self::getDataGenerator()->create_course();
98         // Second workshop.
99         $record = new stdClass();
100         $record->course = $course2->id;
101         $workshop2 = self::getDataGenerator()->create_module('workshop', $record);
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);
114         self::setUser($this->student);
116         $returndescription = mod_workshop_external::get_workshops_by_courses_returns();
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);
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;
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;
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         }
153         $expectedworkshops = array($expected2, $expected1);
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);
159         $this->assertEquals($expectedworkshops, $result['workshops']);
160         $this->assertCount(0, $result['warnings']);
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']);
168         // Unenrol user from second course and alter expected workshops.
169         $enrol->unenrol_user($instance2, $this->student->id);
170         array_shift($expectedworkshops);
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']);
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     }
184     /**
185      * Test mod_workshop_get_workshop_access_information for students.
186      */
187     public function test_mod_workshop_get_workshop_access_information_student() {
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');
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();
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         }
226         // Now, specific functionalities.
227         $this->assertFalse($result['creatingsubmissionallowed']);
228         $this->assertFalse($result['modifyingsubmissionallowed']);
229         $this->assertFalse($result['assessingallowed']);
230         $this->assertFalse($result['assessingexamplesallowed']);
231         $this->assertTrue($result['examplesassessed']);
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);
239         $this->assertTrue($result['creatingsubmissionallowed']);
240         $this->assertTrue($result['modifyingsubmissionallowed']);
241         $this->assertFalse($result['assessingallowed']);
242         $this->assertFalse($result['assessingexamplesallowed']);
243         $this->assertTrue($result['examplesassessed']);
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);
251         $this->assertFalse($result['creatingsubmissionallowed']);
252         $this->assertFalse($result['modifyingsubmissionallowed']);
253         $this->assertTrue($result['assessingallowed']);
254         $this->assertFalse($result['assessingexamplesallowed']);
255         $this->assertTrue($result['examplesassessed']);
256     }
258     /**
259      * Test mod_workshop_get_workshop_access_information for teachers.
260      */
261     public function test_mod_workshop_get_workshop_access_information_teacher() {
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');
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         }
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     }
287     /**
288      * Test mod_workshop_get_user_plan for students.
289      */
290     public function test_mod_workshop_get_user_plan_student() {
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);
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.
301         // Switch phase.
302         $workshop = new workshop($this->workshop, $this->cm, $this->course);
303         $workshop->switch_phase(workshop::PHASE_SUBMISSION);
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);
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     }
312     /**
313      * Test mod_workshop_get_user_plan for teachers.
314      */
315     public function test_mod_workshop_get_user_plan_teacher() {
316         global $DB;
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);
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.
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         }
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);
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         }
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);
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     }
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     }
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     }
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);
383         // Trigger and capture the event.
384         $sink = $this->redirectEvents();
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']);
390         $events = $sink->get_events();
391         $this->assertCount(1, $events);
392         $event = array_shift($events);
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     }
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();
414         $this->setUser($this->student);
415         $this->expectException('moodle_exception');
416         mod_workshop_external::view_workshop($this->workshop->id);
417     }
419     /**
420      * Test test_add_submission.
421      */
422     public function test_add_submission() {
423         $fs = get_file_storage();
425         // Test user with full capabilities.
426         $this->setUser($this->student);
428         $title = 'Submission title';
429         $content = 'Submission contents';
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)');
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');
453         // Switch to submission phase.
454         $workshop = new workshop($this->workshop, $this->cm, $this->course);
455         $workshop->switch_phase(workshop::PHASE_SUBMISSION);
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']);
462         // Check submission created.
463         $submission = $workshop->get_submission_by_author($this->student->id);
464         $this->assertTrue($result['status']);
465         $this->assertEquals($result['submissionid'], $submission->id);
466         $this->assertEquals($title, $submission->title);
467         $this->assertEquals($content, $submission->content);
469         // Check files.
470         $contentfiles = $fs->get_area_files($this->context->id, 'mod_workshop', 'submission_content', $submission->id);
471         $this->assertCount(2, $contentfiles);
472         foreach ($contentfiles as $file) {
473             if ($file->is_directory()) {
474                 continue;
475             } else {
476                 $this->assertEquals($filenameimg, $file->get_filename());
477             }
478         }
479         $contentfiles = $fs->get_area_files($this->context->id, 'mod_workshop', 'submission_attachment', $submission->id);
480         $this->assertCount(2, $contentfiles);
481         foreach ($contentfiles as $file) {
482             if ($file->is_directory()) {
483                 continue;
484             } else {
485                 $this->assertEquals($attachfilename, $file->get_filename());
486             }
487         }
488     }
490     /**
491      * Test test_add_submission invalid phase.
492      */
493     public function test_add_submission_invalid_phase() {
494         $this->setUser($this->student);
496         $this->expectException('moodle_exception');
497         mod_workshop_external::add_submission($this->workshop->id, 'Test');
498     }
500     /**
501      * Test test_add_submission empty title.
502      */
503     public function test_add_submission_empty_title() {
504         $this->setUser($this->student);
506         // Switch to submission phase.
507         $workshop = new workshop($this->workshop, $this->cm, $this->course);
508         $workshop->switch_phase(workshop::PHASE_SUBMISSION);
510         $this->expectException('moodle_exception');
511         mod_workshop_external::add_submission($this->workshop->id, '');
512     }
514     /**
515      * Test test_add_submission already added.
516      */
517     public function test_add_submission_already_added() {
518         $this->setUser($this->student);
520         // Switch to submission phase.
521         $workshop = new workshop($this->workshop, $this->cm, $this->course);
522         $workshop->switch_phase(workshop::PHASE_SUBMISSION);
524         // Create the submission.
525         $result = mod_workshop_external::add_submission($this->workshop->id, 'My submission');
526         $result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
528         // Try to create it again.
529         $result = mod_workshop_external::add_submission($this->workshop->id, 'My submission');
530         $result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
531         $this->assertFalse($result['status']);
532         $this->assertArrayNotHasKey('submissionid', $result);
533         $this->assertCount(2, $result['warnings']);
534         $this->assertEquals('fielderror', $result['warnings'][0]['warningcode']);
535         $this->assertEquals('content_editor', $result['warnings'][0]['item']);
536         $this->assertEquals('fielderror', $result['warnings'][1]['warningcode']);
537         $this->assertEquals('attachment_filemanager', $result['warnings'][1]['item']);
538     }