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