Merge branch 'MDL-68334-master' of git://github.com/lucaboesch/moodle
[moodle.git] / contentbank / tests / contentbank_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 extensions manager.
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 defined('MOODLE_INTERNAL') || die();
30 use advanced_testcase;
31 use context_course;
32 use context_coursecat;
33 use context_system;
35 global $CFG;
36 require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
37 require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
39 /**
40  * Test for extensions manager.
41  *
42  * @package    core_contentbank
43  * @category   test
44  * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
45  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46  * @coversDefaultClass \core_contentbank\contentbank
47  */
48 class core_contentbank_testcase extends advanced_testcase {
50     /**
51      * Setup to ensure that fixtures are loaded.
52      */
53     public static function setupBeforeClass(): void {
54         global $CFG;
56         require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
57     }
59     /**
60      * Data provider for test_get_extension_supporter.
61      *
62      * @return  array
63      */
64     public function get_extension_provider() {
65         return [
66             'H5P file' => ['something.h5p', '.h5p'],
67             'PDF file' => ['something.pdf', '.pdf']
68         ];
69     }
71     /**
72      * Tests for get_extension() function.
73      *
74      * @dataProvider    get_extension_provider
75      * @param   string  $filename    The filename given
76      * @param   string   $expected   The extension of the file
77      *
78      * @covers ::get_extension
79      */
80     public function test_get_extension(string $filename, string $expected) {
81         $this->resetAfterTest();
83         $cb = new contentbank();
85         $extension = $cb->get_extension($filename);
86         $this->assertEquals($expected, $extension);
87     }
89     /**
90      * Data provider for test_load_context_supported_extensions.
91      *
92      * @return  array
93      */
94     public function get_extension_supporters_provider() {
95         return [
96             'H5P first' => [['.h5p' => ['h5p', 'testable']], '.h5p', 'h5p'],
97             'Testable first (but upload not implemented)' => [['.h5p' => ['testable', 'h5p']], '.h5p', 'h5p'],
98         ];
99     }
101     /**
102      * Tests for get_extension_supporter() function with admin permissions.
103      *
104      * @dataProvider    get_extension_supporters_provider
105      * @param   array   $supporters   The content type plugin supporters for each extension
106      * @param   string  $extension    The extension of the file given
107      * @param   string  $expected   The supporter contenttype of the file
108      *
109      * @covers ::load_context_supported_extensions
110      */
111     public function test_get_extension_supporter_for_admins(array $supporters, string $extension, string $expected) {
112         $this->resetAfterTest();
114         $cb = new contentbank();
116         $systemcontext = context_system::instance();
118         // All contexts allowed for admins.
119         $this->setAdminUser();
120         $contextsupporters = $cb->load_context_supported_extensions($systemcontext);
121         $this->assertArrayHasKey($extension, $contextsupporters);
122         $this->assertEquals($expected, $contextsupporters[$extension]);
123     }
125     /**
126      * Tests for get_extension_supporter() function with user default permissions.
127      *
128      * @dataProvider    get_extension_supporters_provider
129      * @param   array   $supporters   The content type plugin supporters for each extension
130      * @param   string  $extension    The extension of the file given
131      * @param   string  $expected   The supporter contenttype of the file
132      *
133      * @covers ::load_context_supported_extensions
134      */
135     public function test_get_extension_supporter_for_users(array $supporters, string $extension, string $expected) {
136         $this->resetAfterTest();
138         $cb = new contentbank();
139         $systemcontext = context_system::instance();
141         // Set a user with no permissions.
142         $user = $this->getDataGenerator()->create_user();
143         $this->setUser($user);
145         // Users with no capabilities can't upload content.
146         $contextsupporters = $cb->load_context_supported_extensions($systemcontext);
147         $this->assertEquals([], $contextsupporters);
148     }
150     /**
151      * Tests for get_extension_supporter() function with teacher defaul permissions.
152      *
153      * @dataProvider    get_extension_supporters_provider
154      * @param   array   $supporters   The content type plugin supporters for each extension
155      * @param   string  $extension    The extension of the file given
156      * @param   string  $expected   The supporter contenttype of the file
157      *
158      * @covers ::load_context_supported_extensions
159      */
160     public function test_get_extension_supporter_for_teachers(array $supporters, string $extension, string $expected) {
161         $this->resetAfterTest();
163         $cb = new contentbank();
165         $course = $this->getDataGenerator()->create_course();
166         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
167         $this->setUser($teacher);
168         $coursecontext = context_course::instance($course->id);
170         // Teachers has permission in their context to upload supported by H5P content type.
171         $contextsupporters = $cb->load_context_supported_extensions($coursecontext);
172         $this->assertArrayHasKey($extension, $contextsupporters);
173         $this->assertEquals($expected, $contextsupporters[$extension]);
174     }
176     /**
177      * Tests for get_extension_supporter() function.
178      *
179      * @dataProvider    get_extension_supporters_provider
180      * @param   array   $supporters   The content type plugin supporters for each extension
181      * @param   string  $extension    The extension of the file given
182      * @param   string  $expected   The supporter contenttype of the file
183      *
184      * @covers ::get_extension_supporter
185      */
186     public function test_get_extension_supporter(array $supporters, string $extension, string $expected) {
187         $this->resetAfterTest();
189         $cb = new contentbank();
190         $systemcontext = context_system::instance();
191         $this->setAdminUser();
193         $supporter = $cb->get_extension_supporter($extension, $systemcontext);
194         $this->assertEquals($expected, $supporter);
195     }
197     /**
198      * Test the behaviour of search_contents().
199      *
200      * @dataProvider search_contents_provider
201      * @param  string $search String to search.
202      * @param  string $where Context where to search.
203      * @param  int $expectedresult Expected result.
204      * @param  array $contexts List of contexts where to create content.
205      */
206     public function test_search_contents(?string $search, string $where, int $expectedresult, array $contexts = [],
207             array $contenttypes = null): void {
208         global $DB;
210         $this->resetAfterTest();
212         // Create users.
213         $managerroleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
214         $manager = $this->getDataGenerator()->create_user();
215         $this->getDataGenerator()->role_assign($managerroleid, $manager->id);
217         // Create a category and a course.
218         $coursecat = $this->getDataGenerator()->create_category();
219         $course = $this->getDataGenerator()->create_course();
220         $existingcontexts = [];
221         $existingcontexts['system'] = \context_system::instance();
222         $existingcontexts['category'] = \context_coursecat::instance($coursecat->id);
223         $existingcontexts['course'] = \context_course::instance($course->id);
225         if (empty($where)) {
226             $contextid = 0;
227         } else {
228             $contextid = $existingcontexts[$where]->id;
229         }
231         // Add some content to the content bank.
232         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
233         foreach ($contexts as $context) {
234             $contextinstance = $existingcontexts[$context];
235             $records = $generator->generate_contentbank_data('contenttype_h5p', 3,
236                 $manager->id, $contextinstance, false);
237         }
239         // Search for some content.
240         $cb = new contentbank();
241         $contents = $cb->search_contents($search, $contextid, $contenttypes);
243         $this->assertCount($expectedresult, $contents);
244         if (!empty($contents) && !empty($search)) {
245             foreach ($contents as $content) {
246                 $this->assertContains($search, $content->get_name());
247             }
248         }
249     }
251     /**
252      * Data provider for test_search_contents().
253      *
254      * @return array
255      */
256     public function search_contents_provider(): array {
258         return [
259             'Search all content in all contexts' => [
260                 null,
261                 '',
262                 9,
263                 ['system', 'category', 'course']
264             ],
265             'Search in all contexts for existing string in all contents' => [
266                 'content',
267                 '',
268                 9,
269                 ['system', 'category', 'course']
270             ],
271             'Search in all contexts for unexisting string in all contents' => [
272                 'chocolate',
273                 '',
274                 0,
275                 ['system', 'category', 'course']
276             ],
277             'Search in all contexts for existing string in some contents' => [
278                 '1',
279                 '',
280                 3,
281                 ['system', 'category', 'course']
282             ],
283             'Search in all contexts for existing string in some contents (create only 1 context)' => [
284                 '1',
285                 '',
286                 1,
287                 ['system']
288             ],
289             'Search in system context for existing string in all contents' => [
290                 'content',
291                 'system',
292                 3,
293                 ['system', 'category', 'course']
294             ],
295             'Search in category context for unexisting string in all contents' => [
296                 'chocolate',
297                 'category',
298                 0,
299                 ['system', 'category', 'course']
300             ],
301             'Search in course context for existing string in some contents' => [
302                 '1',
303                 'course',
304                 1,
305                 ['system', 'category', 'course']
306             ],
307             'Search in system context' => [
308                 null,
309                 'system',
310                 3,
311                 ['system', 'category', 'course']
312             ],
313             'Search in course context with existing content' => [
314                 null,
315                 'course',
316                 3,
317                 ['system', 'category', 'course']
318             ],
319             'Search in course context without existing content' => [
320                 null,
321                 'course',
322                 0,
323                 ['system', 'category']
324             ],
325             'Search in an empty contentbank' => [
326                 null,
327                 '',
328                 0,
329                 []
330             ],
331             'Search in a context in an empty contentbank' => [
332                 null,
333                 'system',
334                 0,
335                 []
336             ],
337             'Search for a string in an empty contentbank' => [
338                 'content',
339                 '',
340                 0,
341                 []
342             ],
343             'Search with unexisting content-type' => [
344                 null,
345                 'course',
346                 0,
347                 ['system', 'category', 'course'],
348                 ['contenttype_unexisting'],
349             ],
350         ];
351     }
353     /**
354      * Test create_content_from_file function.
355      *
356      * @covers ::create_content_from_file
357      */
358     public function test_create_content_from_file() {
359         global $USER;
361         $this->resetAfterTest();
362         $this->setAdminUser();
363         $systemcontext = \context_system::instance();
364         $name = 'dummy_h5p.h5p';
366         // Create a dummy H5P file.
367         $dummyh5p = array(
368             'contextid' => $systemcontext->id,
369             'component' => 'contentbank',
370             'filearea' => 'public',
371             'itemid' => 1,
372             'filepath' => '/',
373             'filename' => $name,
374             'userid' => $USER->id
375         );
376         $fs = get_file_storage();
377         $dummyh5pfile = $fs->create_file_from_string($dummyh5p, 'Dummy H5Pcontent');
379         $cb = new contentbank();
380         $content = $cb->create_content_from_file($systemcontext, $USER->id, $dummyh5pfile);
382         $this->assertEquals('contenttype_h5p', $content->get_content_type());
383         $this->assertInstanceOf('\\contenttype_h5p\\content', $content);
384         $this->assertEquals($name, $content->get_name());
385     }
387     /**
388      * Test the behaviour of delete_contents().
389      *
390      * @covers  ::delete_contents
391      */
392     public function test_delete_contents() {
393         global $DB;
395         $this->resetAfterTest();
396         $cb = new \core_contentbank\contentbank();
398         // Create a category and two courses.
399         $systemcontext = context_system::instance();
400         $coursecat = $this->getDataGenerator()->create_category();
401         $coursecatcontext = context_coursecat::instance($coursecat->id);
402         $course1 = $this->getDataGenerator()->create_course();
403         $course1context = context_course::instance($course1->id);
404         $course2 = $this->getDataGenerator()->create_course();
405         $course2context = context_course::instance($course2->id);
407         // Add some content to the content bank.
408         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
409         $systemcontent = $generator->generate_contentbank_data(null, 3, 0, $systemcontext);
410         $categorycontent = $generator->generate_contentbank_data(null, 3, 0, $coursecatcontext);
411         $course1content = $generator->generate_contentbank_data(null, 3, 0, $course1context);
412         $course2content = $generator->generate_contentbank_data(null, 3, 0, $course2context);
414         // Check the content has been created as expected.
415         $this->assertEquals(12, $DB->count_records('contentbank_content'));
417         // Check the system content is deleted as expected and the rest of the content is not.
418         $this->assertTrue($cb->delete_contents($systemcontext));
419         $this->assertEquals(0, $DB->count_records('contentbank_content', ['contextid' => $systemcontext->id]));
420         // And the rest of the context content exists.
421         $this->assertEquals(9, $DB->count_records('contentbank_content'));
423         // Check the course category content is deleted as expected and the rest of the content is not.
424         $this->assertTrue($cb->delete_contents($coursecatcontext));
425         $this->assertEquals(0, $DB->count_records('contentbank_content', ['contextid' => $coursecatcontext->id]));
426         // And the rest of the context content exists.
427         $this->assertEquals(6, $DB->count_records('contentbank_content'));
429         // Check the course content is deleted as expected and the rest of the content is not.
430         $this->assertTrue($cb->delete_contents($course1context));
431         $this->assertEquals(0, $DB->count_records('contentbank_content', ['contextid' => $course1context->id]));
432         // And the rest of the context content exists.
433         $this->assertEquals(3, $DB->count_records('contentbank_content'));
434     }
436     /**
437      * Test the behaviour of delete_contents() for empty content bank.
438      *
439      * @covers  ::delete_contents
440      */
441     public function test_delete_contents_for_empty_contentbank() {
443         $this->resetAfterTest();
444         $cb = new \core_contentbank\contentbank();
446         // Create a category and two courses.
447         $systemcontext = \context_system::instance();
448         $coursecat = $this->getDataGenerator()->create_category();
449         $coursecatcontext = \context_coursecat::instance($coursecat->id);
450         $course = $this->getDataGenerator()->create_course();
451         $coursecontext = \context_course::instance($course->id);
453         // Check there's no error when trying to delete content from an empty content bank.
454         $this->assertTrue($cb->delete_contents($systemcontext));
455         $this->assertTrue($cb->delete_contents($coursecatcontext));
456         $this->assertTrue($cb->delete_contents($coursecontext));
457     }
459     /**
460      * Test the behaviour of move_contents().
461      *
462      * @covers  ::move_contents
463      */
464     public function test_move_contents() {
465         global $DB;
467         $this->resetAfterTest();
468         $cb = new \core_contentbank\contentbank();
470         // Create a category and two courses.
471         $course1 = $this->getDataGenerator()->create_course();
472         $course1context = context_course::instance($course1->id);
473         $course2 = $this->getDataGenerator()->create_course();
474         $course2context = context_course::instance($course2->id);
476         // Add some content to the content bank.
477         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
478         $course1content = $generator->generate_contentbank_data(null, 3, 0, $course1context);
479         $course2content = $generator->generate_contentbank_data(null, 3, 0, $course2context);
481         // Check the content has been created as expected.
482         $this->assertEquals(6, $DB->count_records('contentbank_content'));
483         $this->assertEquals(3, $DB->count_records('contentbank_content', ['contextid' => $course1context->id]));
485         // Check the content is moved to another context as expected and the rest of the content is not.
486         $this->assertTrue($cb->move_contents($course1context, $course2context));
487         $this->assertEquals(6, $DB->count_records('contentbank_content'));
488         $this->assertEquals(0, $DB->count_records('contentbank_content', ['contextid' => $course1context->id]));
489         $this->assertEquals(6, $DB->count_records('contentbank_content', ['contextid' => $course2context->id]));
490     }
492     /**
493      * Test the behaviour of move_contents() for empty content bank.
494      *
495      * @covers  ::move_contents
496      */
497     public function test_move_contents_for_empty_contentbank() {
499         $this->resetAfterTest();
500         $cb = new \core_contentbank\contentbank();
502         // Create a category and two courses.
503         $systemcontext = \context_system::instance();
504         $course = $this->getDataGenerator()->create_course();
505         $coursecontext = \context_course::instance($course->id);
507         // Check there's no error when trying to move content context from an empty content bank.
508         $this->assertTrue($cb->delete_contents($systemcontext, $coursecontext));
509     }
511     /**
512      * Data provider for get_contenttypes_with_capability_feature.
513      *
514      * @return  array
515      */
516     public function get_contenttypes_with_capability_feature_provider(): array {
517         return [
518             'no-contenttypes_enabled' => [
519                 'contenttypesenabled' => [],
520                 'contenttypescanfeature' => [],
521             ],
522             'contenttype_enabled_noeditable' => [
523                 'contenttypesenabled' => ['testable'],
524                 'contenttypescanfeature' => [],
525             ],
526             'contenttype_enabled_editable' => [
527                 'contenttypesenabled' => ['testable'],
528                 'contenttypescanfeature' => ['testable'],
529             ],
530             'no-contenttype_enabled_editable' => [
531                 'contenttypesenabled' => [],
532                 'contenttypescanfeature' => ['testable'],
533             ],
534         ];
535     }
537     /**
538      * Tests for get_contenttypes_with_capability_feature() function.
539      *
540      * @dataProvider    get_contenttypes_with_capability_feature_provider
541      * @param   array $contenttypesenabled Content types enabled.
542      * @param   array $contenttypescanfeature Content types the user has the permission to use the feature.
543      *
544      * @covers ::get_contenttypes_with_capability_feature
545      */
546     public function test_get_contenttypes_with_capability_feature(array $contenttypesenabled, array $contenttypescanfeature): void {
547         $this->resetAfterTest();
549         $cb = new contentbank();
551         $plugins = [];
553         // Content types not enabled where the user has permission to use a feature.
554         if (empty($contenttypesenabled) && !empty($contenttypescanfeature)) {
555             $enabled = false;
557             // Mock core_plugin_manager class and the method get_plugins_of_type.
558             $pluginmanager = $this->getMockBuilder(\core_plugin_manager::class)
559                 ->disableOriginalConstructor()
560                 ->setMethods(['get_plugins_of_type'])
561                 ->getMock();
563             // Replace protected singletoninstance reference (core_plugin_manager property) with mock object.
564             $ref = new \ReflectionProperty(\core_plugin_manager::class, 'singletoninstance');
565             $ref->setAccessible(true);
566             $ref->setValue(null, $pluginmanager);
568             // Return values of get_plugins_of_type method.
569             foreach ($contenttypescanfeature as $contenttypepluginname) {
570                 $contenttypeplugin = new \stdClass();
571                 $contenttypeplugin->name = $contenttypepluginname;
572                 $contenttypeplugin->type = 'contenttype';
573                 // Add the feature to the fake content type.
574                 $classname = "\\contenttype_$contenttypepluginname\\contenttype";
575                 $classname::$featurestotest = ['test2'];
576                 $plugins[] = $contenttypeplugin;
577             }
579             // Set expectations and return values.
580             $pluginmanager->expects($this->once())
581                 ->method('get_plugins_of_type')
582                 ->with('contenttype')
583                 ->willReturn($plugins);
584         } else {
585             $enabled = true;
586             // Get access to private property enabledcontenttypes.
587             $rc = new \ReflectionClass(\core_contentbank\contentbank::class);
588             $rcp = $rc->getProperty('enabledcontenttypes');
589             $rcp->setAccessible(true);
591             foreach ($contenttypesenabled as $contenttypename) {
592                 $plugins["\\contenttype_$contenttypename\\contenttype"] = $contenttypename;
593                 // Add to the testable contenttype the feature to test.
594                 if (in_array($contenttypename, $contenttypescanfeature)) {
595                     $classname = "\\contenttype_$contenttypename\\contenttype";
596                     $classname::$featurestotest = ['test2'];
597                 }
598             }
599             // Set as enabled content types only those in the test.
600             $rcp->setValue($cb, $plugins);
601         }
603         $actual = $cb->get_contenttypes_with_capability_feature('test2', null, $enabled);
604         $this->assertEquals($contenttypescanfeature, array_values($actual));
605     }