2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
19 * PHPUnit tests for fileconverter API.
22 * @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') ||
die();
29 use core_files\conversion
;
30 use core_files\converter
;
33 * PHPUnit tests for fileconverter API.
36 * @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 class core_files_converter_testcase
extends advanced_testcase
{
42 * Get a testable mock of the abstract files_converter class.
44 * @param array $mockedmethods A list of methods you intend to override
45 * If no methods are specified, only abstract functions are mocked.
46 * @return \core_files\converter
48 protected function get_testable_mock($mockedmethods = []) {
49 $converter = $this->getMockBuilder(\core_files\converter
::class)
50 ->setMethods($mockedmethods)
51 ->getMockForAbstractClass();
57 * Get a testable mock of the conversion.
59 * @param array $mockedmethods A list of methods you intend to override
60 * @return \core_files\conversion
62 protected function get_testable_conversion($mockedmethods = []) {
63 $conversion = $this->getMockBuilder(\core_files\conversion
::class)
64 ->setMethods($mockedmethods)
65 ->setConstructorArgs([0, (object) []])
72 * Get a testable mock of the abstract files_converter class.
74 * @param array $mockedmethods A list of methods you intend to override
75 * If no methods are specified, only abstract functions are mocked.
76 * @return \core_files\converter_interface
78 protected function get_mocked_converter($mockedmethods = []) {
79 $converter = $this->getMockBuilder(\core_files\converter_interface
::class)
80 ->setMethods($mockedmethods)
81 ->getMockForAbstractClass();
87 * Helper to create a stored file objectw with the given supplied content.
89 * @param string $filecontent The content of the mocked file
90 * @param string $filename The file name to use in the stored_file
91 * @param array $mockedmethods A list of methods you intend to override
92 * If no methods are specified, only abstract functions are mocked.
95 protected function get_stored_file($filecontent = 'content', $filename = null, $filerecord = [], $mockedmethods = null) {
98 $contenthash = sha1($filecontent);
99 if (empty($filename)) {
100 $filename = $contenthash;
103 $filerecord['contenthash'] = $contenthash;
104 $filerecord['filesize'] = strlen($filecontent);
105 $filerecord['filename'] = $filename;
106 $filerecord['id'] = 42;
108 $file = $this->getMockBuilder(stored_file
::class)
109 ->setMethods($mockedmethods)
110 ->setConstructorArgs([get_file_storage(), (object) $filerecord])
117 * Helper to create a stored file object with the given supplied content.
119 * @param string $filecontent The content of the mocked file
120 * @param string $filename The file name to use in the stored_file
121 * @param string $filerecord Any overrides to the filerecord
122 * @return stored_file
124 protected function create_stored_file($filecontent = 'content', $filename = 'testfile.txt', $filerecord = []) {
125 $filerecord = array_merge([
126 'contextid' => context_system
::instance()->id
,
127 'component' => 'core',
128 'filearea' => 'unittest',
131 'filename' => $filename,
134 $fs = get_file_storage();
135 $file = $fs->create_file_from_string($filerecord, $filecontent);
141 * Get a mock of the file_storage API.
143 * @param array $mockedmethods A list of methods you intend to override
144 * @return file_storage
146 protected function get_file_storage_mock($mockedmethods = []) {
147 $fs = $this->getMockBuilder(\file_storage
::class)
148 ->setMethods($mockedmethods)
149 ->disableOriginalConstructor()
156 * Test the start_conversion function.
158 public function test_start_conversion_existing_single() {
159 $this->resetAfterTest();
161 $sourcefile = $this->create_stored_file();
163 $first = new conversion(0, (object) [
164 'sourcefileid' => $sourcefile->get_id(),
165 'targetformat' => 'pdf',
169 $converter = $this->get_testable_mock(['poll_conversion']);
170 $conversion = $converter->start_conversion($sourcefile, 'pdf', false);
172 // The old conversions should still be present and match the one returned.
173 $this->assertEquals($first->get('id'), $conversion->get('id'));
177 * Test the start_conversion function.
179 public function test_start_conversion_existing_multiple() {
180 $this->resetAfterTest();
182 $sourcefile = $this->create_stored_file();
184 $first = new conversion(0, (object) [
185 'sourcefileid' => $sourcefile->get_id(),
186 'targetformat' => 'pdf',
190 $second = new conversion(0, (object) [
191 'sourcefileid' => $sourcefile->get_id(),
192 'targetformat' => 'pdf',
196 $converter = $this->get_testable_mock(['poll_conversion']);
197 $conversion = $converter->start_conversion($sourcefile, 'pdf', false);
199 // The old conversions should have been removed.
200 $this->assertFalse(conversion
::get_record(['id' => $first->get('id')]));
201 $this->assertFalse(conversion
::get_record(['id' => $second->get('id')]));
205 * Test the start_conversion function.
207 public function test_start_conversion_no_existing() {
208 $this->resetAfterTest();
210 $sourcefile = $this->create_stored_file();
212 $converter = $this->get_testable_mock(['poll_conversion']);
213 $conversion = $converter->start_conversion($sourcefile, 'pdf', false);
215 $this->assertInstanceOf(\core_files\conversion
::class, $conversion);
219 * Test the get_document_converter_classes function with no enabled plugins.
221 public function test_get_document_converter_classes_no_plugins() {
222 $converter = $this->get_testable_mock(['get_enabled_plugins']);
223 $converter->method('get_enabled_plugins')->willReturn([]);
225 $method = new ReflectionMethod(\core_files\converter
::class, 'get_document_converter_classes');
226 $method->setAccessible(true);
227 $result = $method->invokeArgs($converter, ['docx', 'pdf']);
228 $this->assertEmpty($result);
232 * Test the get_document_converter_classes function when no class was found.
234 public function test_get_document_converter_classes_plugin_class_not_found() {
235 $converter = $this->get_testable_mock(['get_enabled_plugins']);
236 $converter->method('get_enabled_plugins')->willReturn([
237 'noplugin' => '\not\a\real\plugin',
240 $method = new ReflectionMethod(\core_files\converter
::class, 'get_document_converter_classes');
241 $method->setAccessible(true);
242 $result = $method->invokeArgs($converter, ['docx', 'pdf']);
243 $this->assertEmpty($result);
247 * Test the get_document_converter_classes function when the returned classes do not meet requirements.
249 public function test_get_document_converter_classes_plugin_class_requirements_not_met() {
250 $plugin = $this->getMockBuilder(\core_file_converter_requirements_not_met_test
::class)
254 $converter = $this->get_testable_mock(['get_enabled_plugins']);
255 $converter->method('get_enabled_plugins')->willReturn([
256 'test_plugin' => get_class($plugin),
259 $method = new ReflectionMethod(\core_files\converter
::class, 'get_document_converter_classes');
260 $method->setAccessible(true);
261 $result = $method->invokeArgs($converter, ['docx', 'pdf']);
262 $this->assertEmpty($result);
266 * Test the get_document_converter_classes function when the returned classes do not meet requirements.
268 public function test_get_document_converter_classes_plugin_class_met_not_supported() {
269 $plugin = $this->getMockBuilder(\core_file_converter_type_not_supported_test
::class)
273 $converter = $this->get_testable_mock(['get_enabled_plugins']);
274 $converter->method('get_enabled_plugins')->willReturn([
275 'test_plugin' => get_class($plugin),
278 $method = new ReflectionMethod(\core_files\converter
::class, 'get_document_converter_classes');
279 $method->setAccessible(true);
280 $result = $method->invokeArgs($converter, ['docx', 'pdf']);
281 $this->assertEmpty($result);
285 * Test the get_document_converter_classes function when the returned classes do not meet requirements.
287 public function test_get_document_converter_classes_plugin_class_met_and_supported() {
288 $plugin = $this->getMockBuilder(\core_file_converter_type_supported_test
::class)
291 $classname = get_class($plugin);
293 $converter = $this->get_testable_mock(['get_enabled_plugins']);
294 $converter->method('get_enabled_plugins')->willReturn([
295 'test_plugin' => $classname,
298 $method = new ReflectionMethod(\core_files\converter
::class, 'get_document_converter_classes');
299 $method->setAccessible(true);
300 $result = $method->invokeArgs($converter, ['docx', 'pdf']);
301 $this->assertCount(1, $result);
302 $this->assertNotFalse(array_search($classname, $result));
306 * Test the can_convert_storedfile_to function with a directory.
308 public function test_can_convert_storedfile_to_directory() {
309 $converter = $this->get_testable_mock();
311 // A file with filename '.' is a directory.
312 $file = $this->get_stored_file('', '.');
314 $this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
318 * Test the can_convert_storedfile_to function with an empty file.
320 public function test_can_convert_storedfile_to_emptyfile() {
321 $converter = $this->get_testable_mock();
323 // A file with filename '.' is a directory.
324 $file = $this->get_stored_file('');
326 $this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
330 * Test the can_convert_storedfile_to function with a file with indistinguished mimetype.
332 public function test_can_convert_storedfile_to_no_mimetype() {
333 $converter = $this->get_testable_mock();
335 // A file with filename '.' is a directory.
336 $file = $this->get_stored_file('example content', 'example', [
340 $this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
344 * Test the can_convert_storedfile_to function with a file with a known mimetype and extension.
346 public function test_can_convert_storedfile_to_docx() {
347 $returnvalue = (object) [];
349 $converter = $this->get_testable_mock([
350 'can_convert_format_to'
353 $types = \core_filetypes
::get_types();
355 $file = $this->get_stored_file('example content', 'example.docx', [
356 'mimetype' => $types['docx']['type'],
359 $converter->expects($this->once())
360 ->method('can_convert_format_to')
361 ->willReturn($returnvalue);
363 $result = $converter->can_convert_storedfile_to($file, 'target');
364 $this->assertEquals($returnvalue, $result);
369 * Test the can_convert_format_to function.
371 public function test_can_convert_format_to_found() {
372 $converter = $this->get_testable_mock(['get_document_converter_classes']);
374 $mock = $this->get_mocked_converter();
376 $converter->method('get_document_converter_classes')
377 ->willReturn([$mock]);
379 $result = $converter->can_convert_format_to('from', 'to');
380 $this->assertTrue($result);
384 * Test the can_convert_format_to function.
386 public function test_can_convert_format_to_not_found() {
387 $converter = $this->get_testable_mock(['get_document_converter_classes']);
389 $converter->method('get_document_converter_classes')
392 $result = $converter->can_convert_format_to('from', 'to');
393 $this->assertFalse($result);
397 * Test the can_convert_storedfile_to function with an empty file.
399 public function test_poll_conversion_in_progress() {
400 $this->resetAfterTest();
402 $converter = $this->get_testable_mock([
403 'get_document_converter_classes',
404 'get_next_converter',
407 $converter->method('get_document_converter_classes')->willReturn([]);
408 $converter->method('get_next_converter')->willReturn(false);
409 $file = $this->create_stored_file('example content', 'example', [
413 $conversion = $this->get_testable_conversion([
414 'get_converter_instance',
416 $conversion->set_sourcefile($file);
417 $conversion->set('targetformat', 'target');
418 $conversion->set('status', conversion
::STATUS_IN_PROGRESS
);
419 $conversion->create();
421 $converterinstance = $this->get_mocked_converter([
422 'poll_conversion_status',
424 $converterinstance->expects($this->once())
425 ->method('poll_conversion_status');
426 $conversion->method('get_converter_instance')->willReturn($converterinstance);
428 $converter->poll_conversion($conversion);
430 $this->assertEquals(conversion
::STATUS_IN_PROGRESS
, $conversion->get('status'));
434 * Test poll_conversion with an in-progress conversion where we are
435 * unable to instantiate the converter instance.
437 public function test_poll_conversion_in_progress_fail() {
438 $this->resetAfterTest();
440 $converter = $this->get_testable_mock([
441 'get_document_converter_classes',
442 'get_next_converter',
445 $converter->method('get_document_converter_classes')->willReturn([]);
446 $converter->method('get_next_converter')->willReturn(false);
447 $file = $this->create_stored_file('example content', 'example', [
451 $conversion = $this->get_testable_conversion([
452 'get_converter_instance',
454 $conversion->set_sourcefile($file);
455 $conversion->set('targetformat', 'target');
456 $conversion->set('status', conversion
::STATUS_IN_PROGRESS
);
457 $conversion->create();
459 $conversion->method('get_converter_instance')->willReturn(false);
461 $converter->poll_conversion($conversion);
463 $this->assertEquals(conversion
::STATUS_FAILED
, $conversion->get('status'));
467 * Test the can_convert_storedfile_to function with an empty file.
469 public function test_poll_conversion_none_supported() {
470 $this->resetAfterTest();
472 $converter = $this->get_testable_mock([
473 'get_document_converter_classes',
474 'get_next_converter',
477 $converter->method('get_document_converter_classes')->willReturn([]);
478 $converter->method('get_next_converter')->willReturn(false);
479 $file = $this->create_stored_file('example content', 'example', [
483 $conversion = new conversion(0, (object) [
484 'sourcefileid' => $file->get_id(),
485 'targetformat' => 'target',
487 $conversion->create();
489 $converter->poll_conversion($conversion);
491 $this->assertEquals(conversion
::STATUS_FAILED
, $conversion->get('status'));
495 * Test the can_convert_storedfile_to function with an empty file.
497 public function test_poll_conversion_pick_first() {
498 $this->resetAfterTest();
500 $converterinstance = $this->get_mocked_converter([
501 'start_document_conversion',
502 'poll_conversion_status',
504 $converter = $this->get_testable_mock([
505 'get_document_converter_classes',
506 'get_next_converter',
509 $converter->method('get_document_converter_classes')->willReturn([]);
510 $converter->method('get_next_converter')->willReturn(get_class($converterinstance));
511 $file = $this->create_stored_file('example content', 'example', [
515 $conversion = $this->get_testable_conversion([
516 'get_converter_instance',
518 $conversion->set_sourcefile($file);
519 $conversion->set('targetformat', 'target');
520 $conversion->set('status', conversion
::STATUS_PENDING
);
521 $conversion->create();
523 $conversion->method('get_converter_instance')->willReturn($converterinstance);
525 $converterinstance->expects($this->once())
526 ->method('start_document_conversion');
527 $converterinstance->expects($this->never())
528 ->method('poll_conversion_status');
530 $converter->poll_conversion($conversion);
532 $this->assertEquals(conversion
::STATUS_IN_PROGRESS
, $conversion->get('status'));
536 * Test the can_convert_storedfile_to function with an empty file.
538 public function test_poll_conversion_pick_subsequent() {
539 $this->resetAfterTest();
541 $converterinstance = $this->get_mocked_converter([
542 'start_document_conversion',
543 'poll_conversion_status',
545 $converterinstance2 = $this->get_mocked_converter([
546 'start_document_conversion',
547 'poll_conversion_status',
549 $converter = $this->get_testable_mock([
550 'get_document_converter_classes',
551 'get_next_converter',
554 $converter->method('get_document_converter_classes')->willReturn([]);
555 $converter->method('get_next_converter')
556 ->will($this->onConsecutiveCalls(
557 get_class($converterinstance),
558 get_class($converterinstance2)
561 $file = $this->create_stored_file('example content', 'example', [
565 $conversion = $this->get_testable_conversion([
566 'get_converter_instance',
569 $conversion->set_sourcefile($file);
570 $conversion->set('targetformat', 'target');
571 $conversion->set('status', conversion
::STATUS_PENDING
);
572 $conversion->create();
574 $conversion->method('get_status')
575 ->will($this->onConsecutiveCalls(
576 // Initial status check.
577 conversion
::STATUS_PENDING
,
578 // Second check to make sure it's still pending after polling.
579 conversion
::STATUS_PENDING
,
581 conversion
::STATUS_FAILED
,
582 // Second one succeeds.
583 conversion
::STATUS_COMPLETE
,
584 // And the final result checked in this unit test.
585 conversion
::STATUS_COMPLETE
588 $conversion->method('get_converter_instance')
589 ->will($this->onConsecutiveCalls(
594 $converterinstance->expects($this->once())
595 ->method('start_document_conversion');
596 $converterinstance->expects($this->never())
597 ->method('poll_conversion_status');
598 $converterinstance2->expects($this->once())
599 ->method('start_document_conversion');
600 $converterinstance2->expects($this->never())
601 ->method('poll_conversion_status');
603 $converter->poll_conversion($conversion);
605 $this->assertEquals(conversion
::STATUS_COMPLETE
, $conversion->get('status'));
609 * Test the start_conversion with a single converter which succeeds.
611 public function test_start_conversion_one_supported_success() {
612 $this->resetAfterTest();
614 $converter = $this->get_testable_mock([
615 'get_document_converter_classes',
618 $converter->method('get_document_converter_classes')
619 ->willReturn([\core_file_converter_type_successful
::class]);
621 $file = $this->create_stored_file('example content', 'example', [
625 $conversion = $converter->start_conversion($file, 'target');
627 $this->assertEquals(conversion
::STATUS_COMPLETE
, $conversion->get('status'));
631 * Test the start_conversion with a single converter which failes.
633 public function test_start_conversion_one_supported_failure() {
634 $this->resetAfterTest();
636 $converter = $this->get_testable_mock([
637 'get_document_converter_classes',
640 $mock = $this->get_mocked_converter(['start_document_conversion']);
641 $converter->method('get_document_converter_classes')
642 ->willReturn([\core_file_converter_type_failed
::class]);
644 $file = $this->create_stored_file('example content', 'example', [
648 $conversion = $converter->start_conversion($file, 'target');
650 $this->assertEquals(conversion
::STATUS_FAILED
, $conversion->get('status'));
654 * Test the start_conversion with two converters - fail, then succeed.
656 public function test_start_conversion_two_supported() {
657 $this->resetAfterTest();
659 $converter = $this->get_testable_mock([
660 'get_document_converter_classes',
663 $mock = $this->get_mocked_converter(['start_document_conversion']);
664 $converter->method('get_document_converter_classes')
666 \core_file_converter_type_failed
::class,
667 \core_file_converter_type_successful
::class,
670 $file = $this->create_stored_file('example content', 'example', [
674 $conversion = $converter->start_conversion($file, 'target');
676 $this->assertEquals(conversion
::STATUS_COMPLETE
, $conversion->get('status'));
680 * Ensure that get_next_converter returns false when no converters are available.
682 public function test_get_next_converter_no_converters() {
683 $rcm = new \
ReflectionMethod(converter
::class, 'get_next_converter');
684 $rcm->setAccessible(true);
686 $converter = new \core_files\
converter();
687 $result = $rcm->invoke($converter, [], null);
688 $this->assertFalse($result);
692 * Ensure that get_next_converter returns false when already on the
695 public function test_get_next_converter_only_converters() {
696 $rcm = new \
ReflectionMethod(converter
::class, 'get_next_converter');
697 $rcm->setAccessible(true);
699 $converter = new converter();
700 $result = $rcm->invoke($converter, ['example'], 'example');
701 $this->assertFalse($result);
705 * Ensure that get_next_converter returns false when already on the
708 public function test_get_next_converter_last_converters() {
709 $rcm = new \
ReflectionMethod(converter
::class, 'get_next_converter');
710 $rcm->setAccessible(true);
712 $converter = new converter();
713 $result = $rcm->invoke($converter, ['foo', 'example'], 'example');
714 $this->assertFalse($result);
718 * Ensure that get_next_converter returns the next vlaue when in a
721 public function test_get_next_converter_middle_converters() {
722 $rcm = new \
ReflectionMethod(converter
::class, 'get_next_converter');
723 $rcm->setAccessible(true);
725 $converter = new converter();
726 $result = $rcm->invoke($converter, ['foo', 'bar', 'baz', 'example'], 'bar');
727 $this->assertEquals('baz', $result);
731 * Ensure that get_next_converter returns the next vlaue when in a
734 public function test_get_next_converter_first() {
735 $rcm = new \
ReflectionMethod(converter
::class, 'get_next_converter');
736 $rcm->setAccessible(true);
738 $converter = new converter();
739 $result = $rcm->invoke($converter, ['foo', 'bar', 'baz', 'example']);
740 $this->assertEquals('foo', $result);
744 class core_file_converter_requirements_test_base
implements \core_files\converter_interface
{
747 * Whether the plugin is configured and requirements are met.
751 public static function are_requirements_met() {
756 * Convert a document to a new format and return a conversion object relating to the conversion in progress.
758 * @param conversion $conversion The file to be converted
761 public function start_document_conversion(conversion
$conversion) {
765 * Poll an existing conversion for status update.
767 * @param conversion $conversion The file to be converted
770 public function poll_conversion_status(conversion
$conversion) {
774 * Whether a file conversion can be completed using this converter.
776 * @param string $from The source type
777 * @param string $to The destination type
780 public static function supports($from, $to) {
785 * A list of the supported conversions.
789 public function get_supported_conversions() {
796 * Test class for converter support with requirements are not met.
798 * @package core_files
799 * @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
800 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
802 class core_file_converter_requirements_not_met_test
extends core_file_converter_requirements_test_base
{
806 * Test class for converter support with requirements met and conversion not supported.
808 * @package core_files
809 * @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
810 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
812 class core_file_converter_type_not_supported_test
extends core_file_converter_requirements_test_base
{
815 * Whether the plugin is configured and requirements are met.
819 public static function are_requirements_met() {
825 * Test class for converter support with requirements met and conversion supported.
827 * @package core_files
828 * @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
829 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
831 class core_file_converter_type_supported_test
extends core_file_converter_requirements_test_base
{
834 * Whether the plugin is configured and requirements are met.
838 public static function are_requirements_met() {
843 * Whether a file conversion can be completed using this converter.
845 * @param string $from The source type
846 * @param string $to The destination type
849 public static function supports($from, $to) {
855 * Test class for converter support with requirements met and successful conversion.
857 * @package core_files
858 * @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
859 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
861 class core_file_converter_type_successful
extends core_file_converter_requirements_test_base
{
864 * Convert a document to a new format and return a conversion object relating to the conversion in progress.
866 * @param conversion $conversion The file to be converted
869 public function start_document_conversion(conversion
$conversion) {
870 $conversion->set('status', conversion
::STATUS_COMPLETE
);
876 * Whether a file conversion can be completed using this converter.
878 * @param string $from The source type
879 * @param string $to The destination type
882 public static function supports($from, $to) {
888 * Test class for converter support with requirements met and failed conversion.
890 * @package core_files
891 * @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
892 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
894 class core_file_converter_type_failed
extends core_file_converter_requirements_test_base
{
897 * Whether the plugin is configured and requirements are met.
901 public static function are_requirements_met() {
906 * Convert a document to a new format and return a conversion object relating to the conversion in progress.
908 * @param conversion $conversion The file to be converted
911 public function start_document_conversion(conversion
$conversion) {
912 $conversion->set('status', conversion
::STATUS_FAILED
);
918 * Whether a file conversion can be completed using this converter.
920 * @param string $from The source type
921 * @param string $to The destination type
924 public static function supports($from, $to) {