Merge branch 'MDL-69089-39' of git://github.com/aanabit/moodle into MOODLE_39_STABLE
[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      * Test the behaviour of can_delete().
295      */
296     public function test_can_delete() {
297         global $DB;
299         $this->resetAfterTest();
300         $this->contenttype_setup_scenario_data();
302         $managercontent = array_shift($this->contents[$this->manager1->id]);
303         $usercontent = array_shift($this->contents[$this->user->id]);
305         // Check the content has been created as expected.
306         $records = $DB->count_records('contentbank_content');
307         $this->assertEquals(4, $records);
309         // Check user can only delete records created by her.
310         $this->setUser($this->user);
311         $this->assertFalse($this->contenttype->can_delete($managercontent));
312         $this->assertTrue($this->contenttype->can_delete($usercontent));
314         // Check manager can delete records all the records created.
315         $this->setUser($this->manager1);
316         $this->assertTrue($this->contenttype->can_delete($managercontent));
317         $this->assertTrue($this->contenttype->can_delete($usercontent));
319         // Unassign capability to manager role and check not can only delete their own records.
320         unassign_capability('moodle/contentbank:deleteanycontent', $this->managerroleid);
321         $this->assertTrue($this->contenttype->can_delete($managercontent));
322         $this->assertFalse($this->contenttype->can_delete($usercontent));
323         $this->setUser($this->manager2);
324         $this->assertFalse($this->contenttype->can_delete($managercontent));
325         $this->assertFalse($this->contenttype->can_delete($usercontent));
326     }
328     /**
329      * Test the behaviour of delete_content().
330      */
331     public function test_delete_content() {
332         global $DB;
334         $this->resetAfterTest();
335         $this->contenttype_setup_scenario_data();
337         // Check the content has been created as expected.
338         $this->assertEquals(4, $DB->count_records('contentbank_content'));
340         // Check the content is deleted as expected.
341         $this->setUser($this->manager1);
342         $content = array_shift($this->contents[$this->manager1->id]);
343         $deleted = $this->contenttype->delete_content($content);
344         $this->assertTrue($deleted);
345         $this->assertEquals(3, $DB->count_records('contentbank_content'));
346     }
348     /**
349      * Helper function to setup 3 users (manager1, manager2 and user) and 4 contents (3 created by manager1 and 1 by user).
350      */
351     protected function contenttype_setup_scenario_data(): void {
352         global $DB;
353         $systemcontext = context_system::instance();
355         // Create users.
356         $this->manager1 = $this->getDataGenerator()->create_user();
357         $this->manager2 = $this->getDataGenerator()->create_user();
358         $this->managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
359         $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager1->id);
360         $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager2->id);
361         $this->user = $this->getDataGenerator()->create_user();
363         // Add some content to the content bank.
364         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
365         $this->contents[$this->manager1->id] = $generator->generate_contentbank_data(null, 3, $this->manager1->id);
366         $this->contents[$this->user->id] = $generator->generate_contentbank_data(null, 1, $this->user->id);
368         $this->contenttype = new \contenttype_testable\contenttype($systemcontext);
369     }
371     /**
372      * Data provider for test_rename_content.
373      *
374      * @return  array
375      */
376     public function rename_content_provider() {
377         return [
378             'Standard name' => ['New name', 'New name', true],
379             'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017', true],
380             'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle', true],
381             'Name with tags' => ['This is <b>bold</b>', 'This is bold', true],
382             'Long name' => [str_repeat('a', 100), str_repeat('a', 100), true],
383             'Too long name' => [str_repeat('a', 300), str_repeat('a', 255), true],
384             'Empty name' => ['', 'Test content ', false],
385             'Blanks only' => ['  ', 'Test content ', false],
386         ];
387     }
389     /**
390      * Test the behaviour of rename_content().
391      *
392      * @dataProvider    rename_content_provider
393      * @param   string  $newname    The name to set
394      * @param   string   $expected   The name result
395      * @param   bool   $result   The bolean result expected when renaming
396      *
397      * @covers ::rename_content
398      */
399     public function test_rename_content(string $newname, string $expected, bool $result) {
400         global $DB;
402         $this->resetAfterTest();
404         // Create course and teacher user.
405         $course = $this->getDataGenerator()->create_course();
406         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
407         $coursecontext = \context_course::instance($course->id);
408         $contenttype = new contenttype($coursecontext);
410         // Add some content to the content bank as teacher.
411         $this->setUser($teacher);
412         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
413         $contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id);
414         $content = array_shift($contents);
416         $oldname = $content->get_name();
418         // Check the content is renamed as expected by a user with permission.
419         $renamed = $contenttype->rename_content($content, $newname);
420         $this->assertEquals($result, $renamed);
421         $record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]);
422         $this->assertEquals($expected, $record->name);
423     }
425     /**
426      * Test the behaviour of move_content().
427      */
428     public function test_move_content() {
429         global $DB;
431         $this->resetAfterTest();
432         $systemcontext = context_system::instance();
433         $course = $this->getDataGenerator()->create_course();
434         $coursecontext = \context_course::instance($course->id);
436         // Add some content to the content bank.
437         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
438         $systemcontents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $systemcontext);
439         $generator->generate_contentbank_data('contenttype_testable', 3, 0, $coursecontext);
440         $systemcontent = reset($systemcontents);
442         // Check the content has been created as expected.
443         $this->assertEquals(6, $DB->count_records('contentbank_content'));
444         $this->assertEquals(3, $DB->count_records('contentbank_content', ['contextid' => $systemcontext->id]));
445         $this->assertEquals(3, $DB->count_records('contentbank_content', ['contextid' => $coursecontext->id]));
447         // Check the content files has been created as expected.
448         $this->assertEquals(12, $DB->count_records('files', ['component' => 'contentbank']));
449         $this->assertEquals(6, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $systemcontext->id]));
450         $this->assertEquals(6, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $coursecontext->id]));
452         // Check the content is moved as expected.
453         $contenttype = new contenttype($systemcontext);
454         $this->assertTrue($contenttype->move_content($systemcontent, $coursecontext));
455         $this->assertEquals(6, $DB->count_records('contentbank_content'));
456         $this->assertEquals(2, $DB->count_records('contentbank_content', ['contextid' => $systemcontext->id]));
457         $this->assertEquals(4, $DB->count_records('contentbank_content', ['contextid' => $coursecontext->id]));
459         // Check the content files were moved as expected.
460         $this->assertEquals(12, $DB->count_records('files', ['component' => 'contentbank']));
461         $this->assertEquals(4, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $systemcontext->id]));
462         $this->assertEquals(8, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $coursecontext->id]));
463     }
465     /**
466      * Test the behaviour of can_manage().
467      *
468      * @covers ::can_manage
469      */
470     public function test_can_manage() {
471         global $DB, $USER;
473         $this->resetAfterTest();
474         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
476         // Create course and teacher user.
477         $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
478         $course = $this->getDataGenerator()->create_course();
479         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
480         $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
481         $coursecontext = \context_course::instance($course->id);
483         $contenttype = new contenttype($coursecontext);
485         // Add some content to the content bank as admin.
486         $this->setAdminUser();
487         $contentsbyadmin = $generator->generate_contentbank_data('contenttype_testable', 1, $USER->id, $coursecontext);
488         $contentbyadmin = array_shift($contentsbyadmin);
490         // Add some content to the content bank as teacher.
491         $contentsbyteacher = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id, $coursecontext);
492         $contentbyteacher = array_shift($contentsbyteacher);
494         // Check the content has been created as expected.
495         $records = $DB->count_records('contentbank_content');
496         $this->assertEquals(2, $records);
498         // Check manager can manage by default all the contents created.
499         $this->setUser($manager);
500         $this->assertTrue($contenttype->can_manage($contentbyteacher));
501         $this->assertTrue($contenttype->can_manage($contentbyadmin));
503         // Check teacher can only edit their own content.
504         $this->setUser($teacher);
505         $this->assertTrue($contenttype->can_manage($contentbyteacher));
506         $this->assertFalse($contenttype->can_manage($contentbyadmin));
508         // Unassign capability to teacher role and check they not can not edit any content.
509         unassign_capability('moodle/contentbank:manageowncontent', $teacherroleid);
510         $this->assertFalse($contenttype->can_manage($contentbyteacher));
511         $this->assertFalse($contenttype->can_manage($contentbyadmin));
512     }