Merge branch 'MDL-69270-310' of git://github.com/ferranrecio/moodle into MOODLE_310_S...
[moodle.git] / contentbank / tests / contenttype_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  * Test for content bank contenttype class.
19  *
20  * @package    core_contentbank
21  * @category   test
22  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 namespace core_contentbank;
28 use stdClass;
29 use context_system;
30 use context_user;
31 use Exception;
32 use contenttype_testable\contenttype as contenttype;
33 /**
34  * Test for content bank contenttype class.
35  *
36  * @package    core_contentbank
37  * @category   test
38  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
39  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40  * @coversDefaultClass \core_contentbank\contenttype
41  *
42  */
43 class core_contenttype_contenttype_testcase extends \advanced_testcase {
45     /** @var int Identifier for the manager role. */
46     protected $managerroleid;
48     /** @var stdClass Manager user. */
49     protected $manager1;
51     /** @var stdClass Manager user. */
52     protected $manager2;
54     /** @var stdClass User. */
55     protected $user;
57     /** @var array List of contents created (every user has a key with contents created by her). */
58     protected $contents = [];
60     /** @var contenttype The contenttype instance. */
61     protected $contenttype;
63     /**
64      * Setup to ensure that fixtures are loaded.
65      */
66     public static function setupBeforeClass(): void {
67         global $CFG;
69         require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
70         require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
71     }
73     /**
74      * Tests get_contenttype_name result.
75      *
76      * @covers ::get_contenttype_name
77      */
78     public function test_get_contenttype_name() {
79         $this->resetAfterTest();
81         $systemcontext = \context_system::instance();
82         $testable = new contenttype($systemcontext);
84         $this->assertEquals('contenttype_testable', $testable->get_contenttype_name());
85     }
87     /**
88      * Tests get_plugin_name result.
89      *
90      * @covers ::get_plugin_name
91      */
92     public function test_get_plugin_name() {
93         $this->resetAfterTest();
95         $systemcontext = \context_system::instance();
96         $testable = new contenttype($systemcontext);
98         $this->assertEquals('testable', $testable->get_plugin_name());
99     }
101     /**
102      * Tests get_icon result.
103      *
104      * @covers ::get_icon
105      */
106     public function test_get_icon() {
107         $this->resetAfterTest();
109         $systemcontext = \context_system::instance();
110         $testable = new contenttype($systemcontext);
111         $record = new stdClass();
112         $record->name = 'New content';
113         $content = $testable->create_content($record);
114         $icon = $testable->get_icon($content);
115         $this->assertContains('archive', $icon);
116     }
118     /**
119      * Tests is_feature_supported behavior .
120      *
121      * @covers ::is_feature_supported
122      */
123     public function test_is_feature_supported() {
124         $this->resetAfterTest();
126         $systemcontext = \context_system::instance();
127         $testable = new contenttype($systemcontext);
129         $this->assertTrue($testable->is_feature_supported(contenttype::CAN_TEST));
130         $this->assertFalse($testable->is_feature_supported(contenttype::CAN_UPLOAD));
131     }
133     /**
134      * Tests can_upload behavior with no implemented upload feature.
135      *
136      * @covers ::can_upload
137      */
138     public function test_no_upload_feature_supported() {
139         $this->resetAfterTest();
141         $systemcontext = \context_system::instance();
142         $testable = new contenttype($systemcontext);
144         $this->setAdminUser();
145         $this->assertFalse($testable->is_feature_supported(contenttype::CAN_UPLOAD));
146         $this->assertFalse($testable->can_upload());
147     }
149     /**
150      * Test create_content() with empty data.
151      *
152      * @covers ::create_content
153      */
154     public function test_create_empty_content() {
155         $this->resetAfterTest();
157         // Create empty content.
158         $record = new stdClass();
160         $contenttype = new contenttype(context_system::instance());
161         $content = $contenttype->create_content($record);
163         $this->assertEquals('contenttype_testable', $content->get_content_type());
164         $this->assertInstanceOf('\\contenttype_testable\\content', $content);
165     }
167     /**
168      * Tests for behaviour of create_content() with data.
169      *
170      * @covers ::create_content
171      */
172     public function test_create_content() {
173         $this->resetAfterTest();
175         // Create content.
176         $record = new stdClass();
177         $record->name = 'Test content';
178         $record->configdata = '';
179         $record->contenttype = '';
181         $contenttype = new contenttype(context_system::instance());
182         $content = $contenttype->create_content($record);
184         $this->assertEquals('contenttype_testable', $content->get_content_type());
185         $this->assertInstanceOf('\\contenttype_testable\\content', $content);
186     }
188     /**
189      * Tests for behaviour of upload_content() with a file and a record.
190      *
191      * @dataProvider upload_content_provider
192      * @param bool $userecord if a predefined record has to be used.
193      *
194      * @covers ::upload_content
195      */
196     public function test_upload_content(bool $userecord): void {
197         global $USER;
199         $this->resetAfterTest();
200         $this->setAdminUser();
202         $dummy = [
203             'contextid' => context_user::instance($USER->id)->id,
204             'component' => 'user',
205             'filearea' => 'draft',
206             'itemid' => 1,
207             'filepath' => '/',
208             'filename' => 'file.h5p',
209             'userid' => $USER->id,
210         ];
211         $fs = get_file_storage();
212         $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content');
214         // Create content.
215         if ($userecord) {
216             $record = new stdClass();
217             $record->name = 'Test content';
218             $record->configdata = '';
219             $record->contenttype = '';
220             $checkname = $record->name;
221         } else {
222             $record = null;
223             $checkname = $dummyfile->get_filename();
224         }
226         $contenttype = new contenttype(context_system::instance());
227         $content = $contenttype->upload_content($dummyfile, $record);
229         $this->assertEquals('contenttype_testable', $content->get_content_type());
230         $this->assertEquals($checkname, $content->get_name());
231         $this->assertInstanceOf('\\contenttype_testable\\content', $content);
233         $file = $content->get_file();
234         $this->assertEquals($dummyfile->get_filename(), $file->get_filename());
235         $this->assertEquals($dummyfile->get_userid(), $file->get_userid());
236         $this->assertEquals($dummyfile->get_mimetype(), $file->get_mimetype());
237         $this->assertEquals($dummyfile->get_contenthash(), $file->get_contenthash());
238         $this->assertEquals('contentbank', $file->get_component());
239         $this->assertEquals('public', $file->get_filearea());
240         $this->assertEquals('/', $file->get_filepath());
241     }
243     /**
244      * Data provider for test_rename_content.
245      *
246      * @return  array
247      */
248     public function upload_content_provider() {
249         return [
250             'With record' => [true],
251             'Without record' => [false],
252         ];
253     }
255     /**
256      * Tests for behaviour of upload_content() with a file wrong file.
257      *
258      * @covers ::upload_content
259      */
260     public function test_upload_content_exception(): void {
261         global $USER, $DB;
263         $this->resetAfterTest();
264         $this->setAdminUser();
266         // The testing contenttype thows exception if filename is "error.*".
267         $dummy = [
268             'contextid' => context_user::instance($USER->id)->id,
269             'component' => 'user',
270             'filearea' => 'draft',
271             'itemid' => 1,
272             'filepath' => '/',
273             'filename' => 'error.txt',
274             'userid' => $USER->id,
275         ];
276         $fs = get_file_storage();
277         $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content');
279         $contenttype = new contenttype(context_system::instance());
280         $cbcontents = $DB->count_records('contentbank_content');
282         // We need to capture the exception to check no content is created.
283         try {
284             $content = $contenttype->upload_content($dummyfile);
285             $this->assertTrue(false);
286         } catch (Exception $e) {
287             $this->assertTrue(true);
288         }
289         $this->assertEquals($cbcontents, $DB->count_records('contentbank_content'));
290         $this->assertEquals(1, $DB->count_records('files', ['contenthash' => $dummyfile->get_contenthash()]));
291     }
293     /**
294      * Tests for behaviour of replace_content() using a dummy file.
295      *
296      * @covers ::replace_content
297      */
298     public function test_replace_content(): void {
299         global $USER;
301         $this->resetAfterTest();
302         $this->setAdminUser();
303         $context = context_system::instance();
305         // Add some content to the content bank.
306         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
307         $contents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $context);
308         $content = reset($contents);
310         $dummy = [
311             'contextid' => context_user::instance($USER->id)->id,
312             'component' => 'user',
313             'filearea' => 'draft',
314             'itemid' => 1,
315             'filepath' => '/',
316             'filename' => 'file.h5p',
317             'userid' => $USER->id,
318         ];
319         $fs = get_file_storage();
320         $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content');
322         $contenttype = new contenttype(context_system::instance());
323         $content = $contenttype->replace_content($dummyfile, $content);
325         $this->assertEquals('contenttype_testable', $content->get_content_type());
326         $this->assertInstanceOf('\\contenttype_testable\\content', $content);
328         $file = $content->get_file();
329         $this->assertEquals($dummyfile->get_userid(), $file->get_userid());
330         $this->assertEquals($dummyfile->get_contenthash(), $file->get_contenthash());
331         $this->assertEquals('contentbank', $file->get_component());
332         $this->assertEquals('public', $file->get_filearea());
333         $this->assertEquals('/', $file->get_filepath());
334     }
336     /**
337      * Tests for behaviour of replace_content() using an error file.
338      *
339      * @covers ::replace_content
340      */
341     public function test_replace_content_exception(): void {
342         global $USER;
344         $this->resetAfterTest();
345         $this->setAdminUser();
346         $context = context_system::instance();
348         // Add some content to the content bank.
349         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
350         $contents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $context);
351         $content = reset($contents);
353         $dummy = [
354             'contextid' => context_user::instance($USER->id)->id,
355             'component' => 'user',
356             'filearea' => 'draft',
357             'itemid' => 1,
358             'filepath' => '/',
359             'filename' => 'error.txt',
360             'userid' => $USER->id,
361         ];
362         $fs = get_file_storage();
363         $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content');
365         $contenttype = new contenttype(context_system::instance());
367         $this->expectException(Exception::class);
368         $content = $contenttype->replace_content($dummyfile, $content);
369     }
371     /**
372      * Test the behaviour of can_delete().
373      */
374     public function test_can_delete() {
375         global $DB;
377         $this->resetAfterTest();
378         $this->contenttype_setup_scenario_data();
380         $managercontent = array_shift($this->contents[$this->manager1->id]);
381         $usercontent = array_shift($this->contents[$this->user->id]);
383         // Check the content has been created as expected.
384         $records = $DB->count_records('contentbank_content');
385         $this->assertEquals(4, $records);
387         // Check user can only delete records created by her.
388         $this->setUser($this->user);
389         $this->assertFalse($this->contenttype->can_delete($managercontent));
390         $this->assertTrue($this->contenttype->can_delete($usercontent));
392         // Check manager can delete records all the records created.
393         $this->setUser($this->manager1);
394         $this->assertTrue($this->contenttype->can_delete($managercontent));
395         $this->assertTrue($this->contenttype->can_delete($usercontent));
397         // Unassign capability to manager role and check not can only delete their own records.
398         unassign_capability('moodle/contentbank:deleteanycontent', $this->managerroleid);
399         $this->assertTrue($this->contenttype->can_delete($managercontent));
400         $this->assertFalse($this->contenttype->can_delete($usercontent));
401         $this->setUser($this->manager2);
402         $this->assertFalse($this->contenttype->can_delete($managercontent));
403         $this->assertFalse($this->contenttype->can_delete($usercontent));
404     }
406     /**
407      * Test the behaviour of delete_content().
408      */
409     public function test_delete_content() {
410         global $DB;
412         $this->resetAfterTest();
413         $this->contenttype_setup_scenario_data();
415         // Check the content has been created as expected.
416         $this->assertEquals(4, $DB->count_records('contentbank_content'));
418         // Check the content is deleted as expected.
419         $this->setUser($this->manager1);
420         $content = array_shift($this->contents[$this->manager1->id]);
421         $deleted = $this->contenttype->delete_content($content);
422         $this->assertTrue($deleted);
423         $this->assertEquals(3, $DB->count_records('contentbank_content'));
424     }
426     /**
427      * Helper function to setup 3 users (manager1, manager2 and user) and 4 contents (3 created by manager1 and 1 by user).
428      */
429     protected function contenttype_setup_scenario_data(string $contenttype = 'contenttype_testable'): void {
430         global $DB;
431         $systemcontext = context_system::instance();
433         // Create users.
434         $this->manager1 = $this->getDataGenerator()->create_user();
435         $this->manager2 = $this->getDataGenerator()->create_user();
436         $this->managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
437         $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager1->id);
438         $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager2->id);
439         $editingteacherrolerid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
440         $this->user = $this->getDataGenerator()->create_user();
441         $this->getDataGenerator()->role_assign($editingteacherrolerid, $this->user->id);
443         // Add some content to the content bank.
444         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
445         $this->contents[$this->manager1->id] = $generator->generate_contentbank_data($contenttype, 3, $this->manager1->id);
446         $this->contents[$this->user->id] = $generator->generate_contentbank_data($contenttype, 1, $this->user->id);
448         $contenttypeclass = "\\$contenttype\\contenttype";
449         $this->contenttype = new $contenttypeclass($systemcontext);
450     }
452     /**
453      * Data provider for test_rename_content.
454      *
455      * @return  array
456      */
457     public function rename_content_provider() {
458         return [
459             'Standard name' => ['New name', 'New name', true],
460             'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017', true],
461             'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle', true],
462             'Name with tags' => ['This is <b>bold</b>', 'This is bold', true],
463             'Long name' => [str_repeat('a', 100), str_repeat('a', 100), true],
464             'Too long name' => [str_repeat('a', 300), str_repeat('a', 255), true],
465             'Empty name' => ['', 'Test content ', false],
466             'Blanks only' => ['  ', 'Test content ', false],
467         ];
468     }
470     /**
471      * Test the behaviour of rename_content().
472      *
473      * @dataProvider    rename_content_provider
474      * @param   string  $newname    The name to set
475      * @param   string   $expected   The name result
476      * @param   bool   $result   The bolean result expected when renaming
477      *
478      * @covers ::rename_content
479      */
480     public function test_rename_content(string $newname, string $expected, bool $result) {
481         global $DB;
483         $this->resetAfterTest();
485         // Create course and teacher user.
486         $course = $this->getDataGenerator()->create_course();
487         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
488         $coursecontext = \context_course::instance($course->id);
489         $contenttype = new contenttype($coursecontext);
491         // Add some content to the content bank as teacher.
492         $this->setUser($teacher);
493         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
494         $contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id);
495         $content = array_shift($contents);
497         $oldname = $content->get_name();
499         // Check the content is renamed as expected by a user with permission.
500         $renamed = $contenttype->rename_content($content, $newname);
501         $this->assertEquals($result, $renamed);
502         $record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]);
503         $this->assertEquals($expected, $record->name);
504     }
506     /**
507      * Test the behaviour of move_content().
508      */
509     public function test_move_content() {
510         global $DB;
512         $this->resetAfterTest();
513         $systemcontext = context_system::instance();
514         $course = $this->getDataGenerator()->create_course();
515         $coursecontext = \context_course::instance($course->id);
517         // Add some content to the content bank.
518         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
519         $systemcontents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $systemcontext);
520         $generator->generate_contentbank_data('contenttype_testable', 3, 0, $coursecontext);
521         $systemcontent = reset($systemcontents);
523         // Check the content has been created as expected.
524         $this->assertEquals(6, $DB->count_records('contentbank_content'));
525         $this->assertEquals(3, $DB->count_records('contentbank_content', ['contextid' => $systemcontext->id]));
526         $this->assertEquals(3, $DB->count_records('contentbank_content', ['contextid' => $coursecontext->id]));
528         // Check the content files has been created as expected.
529         $this->assertEquals(12, $DB->count_records('files', ['component' => 'contentbank']));
530         $this->assertEquals(6, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $systemcontext->id]));
531         $this->assertEquals(6, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $coursecontext->id]));
533         // Check the content is moved as expected.
534         $contenttype = new contenttype($systemcontext);
535         $this->assertTrue($contenttype->move_content($systemcontent, $coursecontext));
536         $this->assertEquals(6, $DB->count_records('contentbank_content'));
537         $this->assertEquals(2, $DB->count_records('contentbank_content', ['contextid' => $systemcontext->id]));
538         $this->assertEquals(4, $DB->count_records('contentbank_content', ['contextid' => $coursecontext->id]));
540         // Check the content files were moved as expected.
541         $this->assertEquals(12, $DB->count_records('files', ['component' => 'contentbank']));
542         $this->assertEquals(4, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $systemcontext->id]));
543         $this->assertEquals(8, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $coursecontext->id]));
544     }
546     /**
547      * Test the behaviour of can_manage().
548      *
549      * @covers ::can_manage
550      */
551     public function test_can_manage() {
552         global $DB, $USER;
554         $this->resetAfterTest();
555         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
557         // Create course and teacher user.
558         $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
559         $course = $this->getDataGenerator()->create_course();
560         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
561         $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
562         $coursecontext = \context_course::instance($course->id);
564         $contenttype = new contenttype($coursecontext);
566         // Add some content to the content bank as admin.
567         $this->setAdminUser();
568         $contentsbyadmin = $generator->generate_contentbank_data('contenttype_testable', 1, $USER->id, $coursecontext);
569         $contentbyadmin = array_shift($contentsbyadmin);
571         // Add some content to the content bank as teacher.
572         $contentsbyteacher = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id, $coursecontext);
573         $contentbyteacher = array_shift($contentsbyteacher);
575         // Check the content has been created as expected.
576         $records = $DB->count_records('contentbank_content');
577         $this->assertEquals(2, $records);
579         // Check manager can manage by default all the contents created.
580         $this->setUser($manager);
581         $this->assertTrue($contenttype->can_manage($contentbyteacher));
582         $this->assertTrue($contenttype->can_manage($contentbyadmin));
584         // Check teacher can only edit their own content.
585         $this->setUser($teacher);
586         $this->assertTrue($contenttype->can_manage($contentbyteacher));
587         $this->assertFalse($contenttype->can_manage($contentbyadmin));
589         // Unassign capability to teacher role and check they not can not edit any content.
590         unassign_capability('moodle/contentbank:manageowncontent', $teacherroleid);
591         $this->assertFalse($contenttype->can_manage($contentbyteacher));
592         $this->assertFalse($contenttype->can_manage($contentbyadmin));
593     }
595     /**
596      * Test the behaviour of can_download().
597      *
598      * @covers ::can_download
599      */
600     public function test_can_download() {
601         global $DB;
603         $this->resetAfterTest();
604         $this->contenttype_setup_scenario_data('contenttype_h5p');
606         $managercontent = array_shift($this->contents[$this->manager1->id]);
607         $usercontent = array_shift($this->contents[$this->user->id]);
609         // Check the content has been created as expected.
610         $records = $DB->count_records('contentbank_content');
611         $this->assertEquals(4, $records);
613         // Check user can download content created by anybody.
614         $this->setUser($this->user);
615         $this->assertTrue($this->contenttype->can_download($usercontent));
616         $this->assertTrue($this->contenttype->can_download($managercontent));
618         // Check manager can download all the content too.
619         $this->setUser($this->manager1);
620         $this->assertTrue($this->contenttype->can_download($managercontent));
621         $this->assertTrue($this->contenttype->can_download($usercontent));
623         // Unassign capability to manager role and check she cannot download content anymore.
624         unassign_capability('moodle/contentbank:downloadcontent', $this->managerroleid);
625         $this->assertFalse($this->contenttype->can_download($managercontent));
626         $this->assertFalse($this->contenttype->can_download($usercontent));
627     }
629     /**
630      * Tests get_download_url result.
631      *
632      * @covers ::get_download_url
633      */
634     public function test_get_download_url() {
635         global $CFG;
637         $this->resetAfterTest();
638         $this->setAdminUser();
639         $systemcontext = context_system::instance();
641         // Add some content to the content bank.
642         $filename = 'filltheblanks.h5p';
643         $filepath = $CFG->dirroot . '/h5p/tests/fixtures/' . $filename;
644         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
645         $contents = $generator->generate_contentbank_data('contenttype_testable', 1, 0, $systemcontext, true, $filepath);
646         $content = array_shift($contents);
648         // Check the URL is returned OK for a content with file.
649         $contenttype = new contenttype($systemcontext);
650         $url = $contenttype->get_download_url($content);
651         $this->assertNotEmpty($url);
652         $this->assertContains($filename, $url);
654         // Check the URL is empty when the content hasn't any file.
655         $record = new stdClass();
656         $content = $contenttype->create_content($record);
657         $url = $contenttype->get_download_url($content);
658         $this->assertEmpty($url);
659     }