MDL-64506 templates: BS2 pull-x -> BS4 float-x
[moodle.git] / lib / filestorage / tests / file_system_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  * Unit tests for file_system.
19  *
20  * @package   core_files
21  * @category  phpunit
22  * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir . '/filestorage/file_system.php');
31 /**
32  * Unit tests for file_system.
33  *
34  * @package   core_files
35  * @category  phpunit
36  * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
37  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class core_files_file_system_testcase extends advanced_testcase {
41     public function setUp() {
42         get_file_storage(true);
43     }
45     public function tearDown() {
46         get_file_storage(true);
47     }
49     /**
50      * Helper function to help setup and configure the virtual file system stream.
51      *
52      * @param   array $filedir Directory structure and content of the filedir
53      * @param   array $trashdir Directory structure and content of the sourcedir
54      * @param   array $sourcedir Directory structure and content of a directory used for source files for tests
55      * @return  \org\bovigo\vfs\vfsStream
56      */
57     protected function setup_vfile_root($content = []) {
58         $vfileroot = \org\bovigo\vfs\vfsStream::setup('root', null, $content);
60         return $vfileroot;
61     }
63     /**
64      * Helper to create a stored file objectw with the given supplied content.
65      *
66      * @param   string  $filecontent The content of the mocked file
67      * @param   string  $filename The file name to use in the stored_file
68      * @param   array   $mockedmethods A list of methods you intend to override
69      *                  If no methods are specified, only abstract functions are mocked.
70      * @return stored_file
71      */
72     protected function get_stored_file($filecontent, $filename = null, $mockedmethods = null) {
73         $contenthash = file_storage::hash_from_string($filecontent);
74         if (empty($filename)) {
75             $filename = $contenthash;
76         }
78         $file = $this->getMockBuilder(stored_file::class)
79             ->setMethods($mockedmethods)
80             ->setConstructorArgs([
81                 get_file_storage(),
82                 (object) [
83                     'contenthash' => $contenthash,
84                     'filesize' => strlen($filecontent),
85                     'filename' => $filename,
86                 ]
87             ])
88             ->getMock();
90         return $file;
91     }
93     /**
94      * Get a testable mock of the abstract file_system class.
95      *
96      * @param   array   $mockedmethods A list of methods you intend to override
97      *                  If no methods are specified, only abstract functions are mocked.
98      * @return file_system
99      */
100     protected function get_testable_mock($mockedmethods = []) {
101         $fs = $this->getMockBuilder(file_system::class)
102             ->setMethods($mockedmethods)
103             ->getMockForAbstractClass();
105         return $fs;
106     }
108     /**
109      * Ensure that the file system is not clonable.
110      */
111     public function test_not_cloneable() {
112         $reflection = new ReflectionClass('file_system');
113         $this->assertFalse($reflection->isCloneable());
114     }
116     /**
117      * Ensure that the filedir file_system extension is used by default.
118      */
119     public function test_default_class() {
120         $this->resetAfterTest();
122         // Ensure that the alternative_file_system_class is null.
123         global $CFG;
124         $CFG->alternative_file_system_class = null;
126         $storage = get_file_storage();
127         $fs = $storage->get_file_system();
128         $this->assertInstanceOf(file_system::class, $fs);
129         $this->assertEquals(file_system_filedir::class, get_class($fs));
130     }
132     /**
133      * Ensure that the specified file_system extension class is used.
134      */
135     public function test_supplied_class() {
136         global $CFG;
137         $this->resetAfterTest();
139         // Mock the file_system.
140         // Mocks create a new child of the mocked class which is perfect for this test.
141         $filesystem = $this->getMockBuilder('file_system')
142             ->disableOriginalConstructor()
143             ->getMock();
144         $CFG->alternative_file_system_class = get_class($filesystem);
146         $storage = get_file_storage();
147         $fs = $storage->get_file_system();
148         $this->assertInstanceOf(file_system::class, $fs);
149         $this->assertEquals(get_class($filesystem), get_class($fs));
150     }
152     /**
153      * Test that the readfile function outputs content to disk.
154      */
155     public function test_readfile_remote() {
156         global $CFG;
158         // Mock the filesystem.
159         $filecontent = 'example content';
160         $vfileroot = $this->setup_vfile_root(['sourcefile' => $filecontent]);
161         $filepath = \org\bovigo\vfs\vfsStream::url('root/sourcefile');
163         $file = $this->get_stored_file($filecontent);
165         // Mock the file_system class.
166         // We need to override the get_remote_path_from_storedfile function.
167         $fs = $this->get_testable_mock([
168             'get_remote_path_from_storedfile',
169             'is_file_readable_locally_by_storedfile',
170             'get_local_path_from_storedfile',
171         ]);
172         $fs->method('get_remote_path_from_storedfile')->willReturn($filepath);
173         $fs->method('is_file_readable_locally_by_storedfile')->willReturn(false);
174         $fs->expects($this->never())->method('get_local_path_from_storedfile');
176         // Note: It is currently not possible to mock readfile_allow_large
177         // because file_system is in the global namespace.
178         // We must therefore check for expected output. This is not ideal.
179         $this->expectOutputString($filecontent);
180         $fs->readfile($file);
181     }
183     /**
184      * Test that the readfile function outputs content to disk.
185      */
186     public function test_readfile_local() {
187         global $CFG;
189         // Mock the filesystem.
190         $filecontent = 'example content';
191         $vfileroot = $this->setup_vfile_root(['sourcefile' => $filecontent]);
192         $filepath = \org\bovigo\vfs\vfsStream::url('root/sourcefile');
194         $file = $this->get_stored_file($filecontent);
196         // Mock the file_system class.
197         // We need to override the get_remote_path_from_storedfile function.
198         $fs = $this->get_testable_mock([
199             'get_remote_path_from_storedfile',
200             'is_file_readable_locally_by_storedfile',
201             'get_local_path_from_storedfile',
202         ]);
203         $fs->method('is_file_readable_locally_by_storedfile')->willReturn(true);
204         $fs->expects($this->never())->method('get_remote_path_from_storedfile');
205         $fs->expects($this->once())->method('get_local_path_from_storedfile')->willReturn($filepath);
207         // Note: It is currently not possible to mock readfile_allow_large
208         // because file_system is in the global namespace.
209         // We must therefore check for expected output. This is not ideal.
210         $this->expectOutputString($filecontent);
211         $fs->readfile($file);
212     }
214     /**
215      * Test that the get_local_path_from_storedfile function functions
216      * correctly when called with various args.
217      *
218      * @dataProvider get_local_path_from_storedfile_provider
219      * @param   array   $args The additional args to pass to get_local_path_from_storedfile
220      * @param   bool    $fetch Whether the combination of args should have caused a fetch
221      */
222     public function test_get_local_path_from_storedfile($args, $fetch) {
223         $filepath = '/path/to/file';
224         $filecontent = 'example content';
226         // Get the filesystem mock.
227         $fs = $this->get_testable_mock([
228             'get_local_path_from_hash',
229         ]);
230         $fs->expects($this->once())
231             ->method('get_local_path_from_hash')
232             ->with($this->equalTo(file_storage::hash_from_string($filecontent)), $this->equalTo($fetch))
233             ->willReturn($filepath);
235         $file = $this->get_stored_file($filecontent);
237         $method = new ReflectionMethod(file_system::class, 'get_local_path_from_storedfile');
238         $method->setAccessible(true);
239         $result = $method->invokeArgs($fs, array_merge([$file], $args));
241         $this->assertEquals($filepath, $result);
242     }
244     /**
245      * Ensure that the default implementation of get_remote_path_from_storedfile
246      * simply calls get_local_path_from_storedfile without requiring a
247      * fetch.
248      */
249     public function test_get_remote_path_from_storedfile() {
250         $filepath = '/path/to/file';
251         $filecontent = 'example content';
253         $fs = $this->get_testable_mock([
254             'get_remote_path_from_hash',
255         ]);
257         $fs->expects($this->once())
258             ->method('get_remote_path_from_hash')
259             ->with($this->equalTo(file_storage::hash_from_string($filecontent)), $this->equalTo(false))
260             ->willReturn($filepath);
262         $file = $this->get_stored_file($filecontent);
264         $method = new ReflectionMethod(file_system::class, 'get_remote_path_from_storedfile');
265         $method->setAccessible(true);
266         $result = $method->invokeArgs($fs, [$file]);
268         $this->assertEquals($filepath, $result);
269     }
271     /**
272      * Test the stock implementation of is_file_readable_locally_by_hash with a valid file.
273      *
274      * This should call get_local_path_from_hash and check the readability
275      * of the file.
276      *
277      * Fetching the file is optional.
278      */
279     public function test_is_file_readable_locally_by_hash() {
280         $filecontent = 'example content';
281         $contenthash = file_storage::hash_from_string($filecontent);
282         $filepath = __FILE__;
284         $fs = $this->get_testable_mock([
285             'get_local_path_from_hash',
286         ]);
288         $fs->method('get_local_path_from_hash')
289             ->with($this->equalTo($contenthash), $this->equalTo(false))
290             ->willReturn($filepath);
292         $this->assertTrue($fs->is_file_readable_locally_by_hash($contenthash));
293     }
295     /**
296      * Test the stock implementation of is_file_readable_locally_by_hash with an empty file.
297      */
298     public function test_is_file_readable_locally_by_hash_empty() {
299         $filecontent = '';
300         $contenthash = file_storage::hash_from_string($filecontent);
302         $fs = $this->get_testable_mock([
303             'get_local_path_from_hash',
304         ]);
306         $fs->expects($this->never())
307             ->method('get_local_path_from_hash');
309         $this->assertTrue($fs->is_file_readable_locally_by_hash($contenthash));
310     }
312     /**
313      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
314      */
315     public function test_is_file_readable_remotely_by_hash() {
316         $filecontent = 'example content';
317         $contenthash = file_storage::hash_from_string($filecontent);
319         $fs = $this->get_testable_mock([
320             'get_remote_path_from_hash',
321         ]);
323         $fs->method('get_remote_path_from_hash')
324             ->with($this->equalTo($contenthash), $this->equalTo(false))
325             ->willReturn(__FILE__);
327         $this->assertTrue($fs->is_file_readable_remotely_by_hash($contenthash));
328     }
330     /**
331      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
332      */
333     public function test_is_file_readable_remotely_by_hash_empty() {
334         $filecontent = '';
335         $contenthash = file_storage::hash_from_string($filecontent);
337         $fs = $this->get_testable_mock([
338             'get_remote_path_from_hash',
339         ]);
341         $fs->expects($this->never())
342             ->method('get_remote_path_from_hash');
344         $this->assertTrue($fs->is_file_readable_remotely_by_hash($contenthash));
345     }
347     /**
348      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
349      */
350     public function test_is_file_readable_remotely_by_hash_not_found() {
351         $filecontent = 'example content';
352         $contenthash = file_storage::hash_from_string($filecontent);
354         $fs = $this->get_testable_mock([
355             'get_remote_path_from_hash',
356         ]);
358         $fs->method('get_remote_path_from_hash')
359             ->with($this->equalTo($contenthash), $this->equalTo(false))
360             ->willReturn('/path/to/nonexistent/file');
362         $this->assertFalse($fs->is_file_readable_remotely_by_hash($contenthash));
363     }
365     /**
366      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
367      */
368     public function test_is_file_readable_remotely_by_storedfile() {
369         $file = $this->get_stored_file('example content');
371         $fs = $this->get_testable_mock([
372             'get_remote_path_from_storedfile',
373         ]);
375         $fs->method('get_remote_path_from_storedfile')
376             ->willReturn(__FILE__);
378         $this->assertTrue($fs->is_file_readable_remotely_by_storedfile($file));
379     }
381     /**
382      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
383      */
384     public function test_is_file_readable_remotely_by_storedfile_empty() {
385         $fs = $this->get_testable_mock([
386             'get_remote_path_from_storedfile',
387         ]);
389         $fs->expects($this->never())
390             ->method('get_remote_path_from_storedfile');
392         $file = $this->get_stored_file('');
393         $this->assertTrue($fs->is_file_readable_remotely_by_storedfile($file));
394     }
396     /**
397      * Test the stock implementation of is_file_readable_locally_by_storedfile with an empty file.
398      */
399     public function test_is_file_readable_locally_by_storedfile_empty() {
400         $fs = $this->get_testable_mock([
401             'get_local_path_from_storedfile',
402         ]);
404         $fs->expects($this->never())
405             ->method('get_local_path_from_storedfile');
407         $file = $this->get_stored_file('');
408         $this->assertTrue($fs->is_file_readable_locally_by_storedfile($file));
409     }
411     /**
412      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
413      */
414     public function test_is_file_readable_remotely_by_storedfile_not_found() {
415         $file = $this->get_stored_file('example content');
417         $fs = $this->get_testable_mock([
418             'get_remote_path_from_storedfile',
419         ]);
421         $fs->method('get_remote_path_from_storedfile')
422             ->willReturn(__LINE__);
424         $this->assertFalse($fs->is_file_readable_remotely_by_storedfile($file));
425     }
427     /**
428      * Test the stock implementation of is_file_readable_locally_by_storedfile with a valid file.
429      */
430     public function test_is_file_readable_locally_by_storedfile_unreadable() {
431         $fs = $this->get_testable_mock([
432             'get_local_path_from_storedfile',
433         ]);
434         $file = $this->get_stored_file('example content');
436         $fs->method('get_local_path_from_storedfile')
437             ->with($this->equalTo($file), $this->equalTo(false))
438             ->willReturn('/path/to/nonexistent/file');
440         $this->assertFalse($fs->is_file_readable_locally_by_storedfile($file));
441     }
443     /**
444      * Test the stock implementation of is_file_readable_locally_by_storedfile with a valid file should pass fetch.
445      */
446     public function test_is_file_readable_locally_by_storedfile_passes_fetch() {
447         $fs = $this->get_testable_mock([
448             'get_local_path_from_storedfile',
449         ]);
450         $file = $this->get_stored_file('example content');
452         $fs->method('get_local_path_from_storedfile')
453             ->with($this->equalTo($file), $this->equalTo(true))
454             ->willReturn('/path/to/nonexistent/file');
456         $this->assertFalse($fs->is_file_readable_locally_by_storedfile($file, true));
457     }
459     /**
460      * Ensure that is_file_removable returns correctly for an empty file.
461      */
462     public function test_is_file_removable_empty() {
463         $filecontent = '';
464         $contenthash = file_storage::hash_from_string($filecontent);
466         $method = new ReflectionMethod(file_system::class, 'is_file_removable');
467         $method->setAccessible(true);
468         $result = $method->invokeArgs(null, [$contenthash]);
469         $this->assertFalse($result);
470     }
472     /**
473      * Ensure that is_file_removable returns false if the file is still in use.
474      */
475     public function test_is_file_removable_in_use() {
476         $this->resetAfterTest();
477         global $DB;
479         $filecontent = 'example content';
480         $contenthash = file_storage::hash_from_string($filecontent);
482         $DB = $this->getMockBuilder(\moodle_database::class)
483             ->setMethods(['record_exists'])
484             ->getMockForAbstractClass();
485         $DB->method('record_exists')->willReturn(true);
487         $method = new ReflectionMethod(file_system::class, 'is_file_removable');
488         $method->setAccessible(true);
489         $result = $method->invokeArgs(null, [$contenthash]);
491         $this->assertFalse($result);
492     }
494     /**
495      * Ensure that is_file_removable returns false if the file is not in use.
496      */
497     public function test_is_file_removable_not_in_use() {
498         $this->resetAfterTest();
499         global $DB;
501         $filecontent = 'example content';
502         $contenthash = file_storage::hash_from_string($filecontent);
504         $DB = $this->getMockBuilder(\moodle_database::class)
505             ->setMethods(['record_exists'])
506             ->getMockForAbstractClass();
507         $DB->method('record_exists')->willReturn(false);
509         $method = new ReflectionMethod(file_system::class, 'is_file_removable');
510         $method->setAccessible(true);
511         $result = $method->invokeArgs(null, [$contenthash]);
513         $this->assertTrue($result);
514     }
516     /**
517      * Test the stock implementation of get_content.
518      */
519     public function test_get_content() {
520         global $CFG;
522         // Mock the filesystem.
523         $filecontent = 'example content';
524         $vfileroot = $this->setup_vfile_root(['sourcefile' => $filecontent]);
525         $filepath = \org\bovigo\vfs\vfsStream::url('root/sourcefile');
527         $file = $this->get_stored_file($filecontent);
529         // Mock the file_system class.
530         // We need to override the get_remote_path_from_storedfile function.
531         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
532         $fs->method('get_remote_path_from_storedfile')->willReturn($filepath);
534         $result = $fs->get_content($file);
536         $this->assertEquals($filecontent, $result);
537     }
539     /**
540      * Test the stock implementation of get_content.
541      */
542     public function test_get_content_empty() {
543         global $CFG;
545         $filecontent = '';
546         $file = $this->get_stored_file($filecontent);
548         // Mock the file_system class.
549         // We need to override the get_remote_path_from_storedfile function.
550         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
551         $fs->expects($this->never())
552             ->method('get_remote_path_from_storedfile');
554         $result = $fs->get_content($file);
556         $this->assertEquals($filecontent, $result);
557     }
559     /**
560      * Ensure that the list_files function requires a local copy of the
561      * file, and passes the path to the packer.
562      */
563     public function test_list_files() {
564         $filecontent = 'example content';
565         $file = $this->get_stored_file($filecontent);
566         $filepath = __FILE__;
567         $expectedresult = (object) [];
569         // Mock the file_system class.
570         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
571         $fs->method('get_local_path_from_storedfile')
572             ->with($this->equalTo($file), $this->equalTo(true))
573             ->willReturn(__FILE__);
575         $packer = $this->getMockBuilder(file_packer::class)
576             ->setMethods(['list_files'])
577             ->getMockForAbstractClass();
579         $packer->expects($this->once())
580             ->method('list_files')
581             ->with($this->equalTo($filepath))
582             ->willReturn($expectedresult);
584         $result = $fs->list_files($file, $packer);
586         $this->assertEquals($expectedresult, $result);
587     }
589     /**
590      * Ensure that the extract_to_pathname function requires a local copy of the
591      * file, and passes the path to the packer.
592      */
593     public function test_extract_to_pathname() {
594         $filecontent = 'example content';
595         $file = $this->get_stored_file($filecontent);
596         $filepath = __FILE__;
597         $expectedresult = (object) [];
598         $outputpath = '/path/to/output';
600         // Mock the file_system class.
601         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
602         $fs->method('get_local_path_from_storedfile')
603             ->with($this->equalTo($file), $this->equalTo(true))
604             ->willReturn(__FILE__);
606         $packer = $this->getMockBuilder(file_packer::class)
607             ->setMethods(['extract_to_pathname'])
608             ->getMockForAbstractClass();
610         $packer->expects($this->once())
611             ->method('extract_to_pathname')
612             ->with($this->equalTo($filepath), $this->equalTo($outputpath), $this->equalTo(null), $this->equalTo(null))
613             ->willReturn($expectedresult);
615         $result = $fs->extract_to_pathname($file, $packer, $outputpath);
617         $this->assertEquals($expectedresult, $result);
618     }
620     /**
621      * Ensure that the extract_to_storage function requires a local copy of the
622      * file, and passes the path to the packer.
623      */
624     public function test_extract_to_storage() {
625         $filecontent = 'example content';
626         $file = $this->get_stored_file($filecontent);
627         $filepath = __FILE__;
628         $expectedresult = (object) [];
629         $outputpath = '/path/to/output';
631         // Mock the file_system class.
632         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
633         $fs->method('get_local_path_from_storedfile')
634             ->with($this->equalTo($file), $this->equalTo(true))
635             ->willReturn(__FILE__);
637         $packer = $this->getMockBuilder(file_packer::class)
638             ->setMethods(['extract_to_storage'])
639             ->getMockForAbstractClass();
641         $packer->expects($this->once())
642             ->method('extract_to_storage')
643             ->with(
644                 $this->equalTo($filepath),
645                 $this->equalTo(42),
646                 $this->equalTo('component'),
647                 $this->equalTo('filearea'),
648                 $this->equalTo('itemid'),
649                 $this->equalTo('pathbase'),
650                 $this->equalTo('userid'),
651                 $this->equalTo(null)
652             )
653             ->willReturn($expectedresult);
655         $result = $fs->extract_to_storage($file, $packer, 42, 'component','filearea', 'itemid', 'pathbase', 'userid');
657         $this->assertEquals($expectedresult, $result);
658     }
660     /**
661      * Ensure that the add_storedfile_to_archive function requires a local copy of the
662      * file, and passes the path to the archive.
663      */
664     public function test_add_storedfile_to_archive_directory() {
665         $file = $this->get_stored_file('', '.');
666         $archivepath = 'example';
667         $expectedresult = (object) [];
669         // Mock the file_system class.
670         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
671         $fs->method('get_local_path_from_storedfile')
672             ->with($this->equalTo($file), $this->equalTo(true))
673             ->willReturn(__FILE__);
675         $archive = $this->getMockBuilder(file_archive::class)
676             ->setMethods([
677                 'add_directory',
678                 'add_file_from_pathname',
679             ])
680             ->getMockForAbstractClass();
682         $archive->expects($this->once())
683             ->method('add_directory')
684             ->with($this->equalTo($archivepath))
685             ->willReturn($expectedresult);
687         $archive->expects($this->never())
688             ->method('add_file_from_pathname');
690         $result = $fs->add_storedfile_to_archive($file, $archive, $archivepath);
692         $this->assertEquals($expectedresult, $result);
693     }
695     /**
696      * Ensure that the add_storedfile_to_archive function requires a local copy of the
697      * file, and passes the path to the archive.
698      */
699     public function test_add_storedfile_to_archive_file() {
700         $file = $this->get_stored_file('example content');
701         $filepath = __LINE__;
702         $archivepath = 'example';
703         $expectedresult = (object) [];
705         // Mock the file_system class.
706         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
707         $fs->method('get_local_path_from_storedfile')
708             ->with($this->equalTo($file), $this->equalTo(true))
709             ->willReturn($filepath);
711         $archive = $this->getMockBuilder(file_archive::class)
712             ->setMethods([
713                 'add_directory',
714                 'add_file_from_pathname',
715             ])
716             ->getMockForAbstractClass();
718         $archive->expects($this->never())
719             ->method('add_directory');
721         $archive->expects($this->once())
722             ->method('add_file_from_pathname')
723             ->with(
724                 $this->equalTo($archivepath),
725                 $this->equalTo($filepath)
726             )
727             ->willReturn($expectedresult);
729         $result = $fs->add_storedfile_to_archive($file, $archive, $archivepath);
731         $this->assertEquals($expectedresult, $result);
732     }
734     /**
735      * Ensure that the add_to_curl_request function requires a local copy of the
736      * file, and passes the path to curl_file_create.
737      */
738     public function test_add_to_curl_request() {
739         $file = $this->get_stored_file('example content');
740         $filepath = __FILE__;
741         $archivepath = 'example';
742         $key = 'myfile';
744         // Mock the file_system class.
745         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
746         $fs->method('get_local_path_from_storedfile')
747             ->with($this->equalTo($file), $this->equalTo(true))
748             ->willReturn($filepath);
750         $request = (object) ['_tmp_file_post_params' => []];
751         $fs->add_to_curl_request($file, $request, $key);
752         $this->assertArrayHasKey($key, $request->_tmp_file_post_params);
753         $this->assertEquals($filepath, $request->_tmp_file_post_params[$key]->name);
754     }
756     /**
757      * Ensure that test_get_imageinfo_not_image returns false if the file
758      * passed was deemed to not be an image.
759      */
760     public function test_get_imageinfo_not_image() {
761         $filecontent = 'example content';
762         $file = $this->get_stored_file($filecontent);
764         $fs = $this->get_testable_mock([
765             'is_image_from_storedfile',
766         ]);
768         $fs->expects($this->once())
769             ->method('is_image_from_storedfile')
770             ->with($this->equalTo($file))
771             ->willReturn(false);
773         $this->assertFalse($fs->get_imageinfo($file));
774     }
776     /**
777      * Ensure that test_get_imageinfo_not_image returns imageinfo.
778      */
779     public function test_get_imageinfo() {
780         $filepath = '/path/to/file';
781         $filecontent = 'example content';
782         $expectedresult = (object) [];
783         $file = $this->get_stored_file($filecontent);
785         $fs = $this->get_testable_mock([
786             'is_image_from_storedfile',
787             'get_local_path_from_storedfile',
788             'get_imageinfo_from_path',
789         ]);
791         $fs->expects($this->once())
792             ->method('is_image_from_storedfile')
793             ->with($this->equalTo($file))
794             ->willReturn(true);
796         $fs->expects($this->once())
797             ->method('get_local_path_from_storedfile')
798             ->with($this->equalTo($file), $this->equalTo(true))
799             ->willReturn($filepath);
801         $fs->expects($this->once())
802             ->method('get_imageinfo_from_path')
803             ->with($this->equalTo($filepath))
804             ->willReturn($expectedresult);
806         $this->assertEquals($expectedresult, $fs->get_imageinfo($file));
807     }
809     /**
810      * Ensure that is_image_from_storedfile always returns false for an
811      * empty file size.
812      */
813     public function test_is_image_empty_filesize() {
814         $filecontent = 'example content';
815         $file = $this->get_stored_file($filecontent, null, ['get_filesize']);
817         $file->expects($this->once())
818             ->method('get_filesize')
819             ->willReturn(0);
821         $fs = $this->get_testable_mock();
822         $this->assertFalse($fs->is_image_from_storedfile($file));
823     }
825     /**
826      * Ensure that is_image_from_storedfile behaves correctly based on
827      * mimetype.
828      *
829      * @dataProvider is_image_from_storedfile_provider
830      * @param   string  $mimetype Mimetype to test
831      * @param   bool    $isimage Whether this mimetype should be detected as an image
832      */
833     public function test_is_image_from_storedfile_mimetype($mimetype, $isimage) {
834         $filecontent = 'example content';
835         $file = $this->get_stored_file($filecontent, null, ['get_mimetype']);
837         $file->expects($this->once())
838             ->method('get_mimetype')
839             ->willReturn($mimetype);
841         $fs = $this->get_testable_mock();
842         $this->assertEquals($isimage, $fs->is_image_from_storedfile($file));
843     }
845     /**
846      * Test that get_imageinfo_from_path returns an appropriate response
847      * for an image.
848      */
849     public function test_get_imageinfo_from_path() {
850         $filepath = __DIR__ . "/fixtures/testimage.jpg";
852         // Get the filesystem mock.
853         $fs = $this->get_testable_mock();
855         $method = new ReflectionMethod(file_system::class, 'get_imageinfo_from_path');
856         $method->setAccessible(true);
857         $result = $method->invokeArgs($fs, [$filepath]);
859         $this->assertArrayHasKey('width', $result);
860         $this->assertArrayHasKey('height', $result);
861         $this->assertArrayHasKey('mimetype', $result);
862         $this->assertEquals('image/jpeg', $result['mimetype']);
863     }
865     /**
866      * Test that get_imageinfo_from_path returns an appropriate response
867      * for a file which is not an image.
868      */
869     public function test_get_imageinfo_from_path_no_image() {
870         $filepath = __FILE__;
872         // Get the filesystem mock.
873         $fs = $this->get_testable_mock();
875         $method = new ReflectionMethod(file_system::class, 'get_imageinfo_from_path');
876         $method->setAccessible(true);
877         $result = $method->invokeArgs($fs, [$filepath]);
879         $this->assertFalse($result);
880     }
882     /**
883      * Ensure that get_content_file_handle returns a valid file handle.
884      */
885     public function test_get_content_file_handle_default() {
886         $filecontent = 'example content';
887         $file = $this->get_stored_file($filecontent);
889         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
890         $fs->method('get_remote_path_from_storedfile')
891             ->willReturn(__FILE__);
893         // Note: We are unable to determine the mode in which the $fh was opened.
894         $fh = $fs->get_content_file_handle($file);
895         $this->assertTrue(is_resource($fh));
896         $this->assertEquals('stream', get_resource_type($fh));
897         fclose($fh);
898     }
900     /**
901      * Ensure that get_content_file_handle returns a valid file handle for a gz file.
902      */
903     public function test_get_content_file_handle_gz() {
904         $filecontent = 'example content';
905         $file = $this->get_stored_file($filecontent);
907         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
908         $fs->method('get_local_path_from_storedfile')
909             ->willReturn(__DIR__ . "/fixtures/test.tgz");
911         // Note: We are unable to determine the mode in which the $fh was opened.
912         $fh = $fs->get_content_file_handle($file, stored_file::FILE_HANDLE_GZOPEN);
913         $this->assertTrue(is_resource($fh));
914         gzclose($fh);
915     }
917     /**
918      * Ensure that get_content_file_handle returns an exception when calling for a invalid file handle type.
919      */
920     public function test_get_content_file_handle_invalid() {
921         $filecontent = 'example content';
922         $file = $this->get_stored_file($filecontent);
924         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
925         $fs->method('get_remote_path_from_storedfile')
926             ->willReturn(__FILE__);
928         $this->expectException('coding_exception', 'Unexpected file handle type');
929         $fs->get_content_file_handle($file, -1);
930     }
932     /**
933      * Test that mimetype_from_hash returns the correct mimetype with
934      * a file whose filename suggests mimetype.
935      */
936     public function test_mimetype_from_hash_using_filename() {
937         $filepath = '/path/to/file/not/currently/on/disk';
938         $filecontent = 'example content';
939         $filename = 'test.jpg';
940         $contenthash = file_storage::hash_from_string($filecontent);
942         $fs = $this->get_testable_mock(['get_remote_path_from_hash']);
943         $fs->method('get_remote_path_from_hash')->willReturn($filepath);
945         $result = $fs->mimetype_from_hash($contenthash, $filename);
946         $this->assertEquals('image/jpeg', $result);
947     }
949     /**
950      * Test that mimetype_from_hash returns the correct mimetype with
951      * a locally available file whose filename does not suggest mimetype.
952      */
953     public function test_mimetype_from_hash_using_file_content() {
954         $filecontent = 'example content';
955         $contenthash = file_storage::hash_from_string($filecontent);
956         $filename = 'example';
958         $filepath = __DIR__ . "/fixtures/testimage.jpg";
959         $fs = $this->get_testable_mock(['get_local_path_from_hash']);
960         $fs->method('get_local_path_from_hash')->willReturn($filepath);
962         $result = $fs->mimetype_from_hash($contenthash, $filename);
963         $this->assertEquals('image/jpeg', $result);
964     }
966     /**
967      * Test that mimetype_from_hash returns the correct mimetype with
968      * a remotely available file whose filename does not suggest mimetype.
969      */
970     public function test_mimetype_from_hash_using_file_content_remote() {
971         $filepath = '/path/to/file/not/currently/on/disk';
972         $filecontent = 'example content';
973         $contenthash = file_storage::hash_from_string($filecontent);
974         $filename = 'example';
976         $filepath = __DIR__ . "/fixtures/testimage.jpg";
978         $fs = $this->get_testable_mock([
979             'get_remote_path_from_hash',
980             'is_file_readable_locally_by_hash',
981             'get_local_path_from_hash',
982         ]);
984         $fs->method('get_remote_path_from_hash')->willReturn('/path/to/remote/file');
985         $fs->method('is_file_readable_locally_by_hash')->willReturn(false);
986         $fs->method('get_local_path_from_hash')->willReturn($filepath);
988         $result = $fs->mimetype_from_hash($contenthash, $filename);
989         $this->assertEquals('image/jpeg', $result);
990     }
992     /**
993      * Test that mimetype_from_storedfile returns the correct mimetype with
994      * a file whose filename suggests mimetype.
995      */
996     public function test_mimetype_from_storedfile_empty() {
997         $file = $this->get_stored_file('');
999         $fs = $this->get_testable_mock();
1000         $result = $fs->mimetype_from_storedfile($file);
1001         $this->assertNull($result);
1002     }
1004     /**
1005      * Test that mimetype_from_storedfile returns the correct mimetype with
1006      * a file whose filename suggests mimetype.
1007      */
1008     public function test_mimetype_from_storedfile_using_filename() {
1009         $filepath = '/path/to/file/not/currently/on/disk';
1010         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
1011         $fs->method('get_remote_path_from_storedfile')->willReturn($filepath);
1013         $file = $this->get_stored_file('example content', 'test.jpg');
1015         $result = $fs->mimetype_from_storedfile($file);
1016         $this->assertEquals('image/jpeg', $result);
1017     }
1019     /**
1020      * Test that mimetype_from_storedfile returns the correct mimetype with
1021      * a locally available file whose filename does not suggest mimetype.
1022      */
1023     public function test_mimetype_from_storedfile_using_file_content() {
1024         $filepath = __DIR__ . "/fixtures/testimage.jpg";
1025         $fs = $this->get_testable_mock(['get_local_path_from_hash']);
1026         $fs->method('get_local_path_from_hash')->willReturn($filepath);
1028         $file = $this->get_stored_file('example content');
1030         $result = $fs->mimetype_from_storedfile($file);
1031         $this->assertEquals('image/jpeg', $result);
1032     }
1034     /**
1035      * Test that mimetype_from_storedfile returns the correct mimetype with
1036      * a remotely available file whose filename does not suggest mimetype.
1037      */
1038     public function test_mimetype_from_storedfile_using_file_content_remote() {
1039         $filepath = __DIR__ . "/fixtures/testimage.jpg";
1041         $fs = $this->get_testable_mock([
1042             'is_file_readable_locally_by_hash',
1043             'get_local_path_from_hash',
1044         ]);
1046         $fs->method('is_file_readable_locally_by_hash')->willReturn(false);
1047         $fs->method('get_local_path_from_hash')->will($this->onConsecutiveCalls('/path/to/remote/file', $filepath));
1049         $file = $this->get_stored_file('example content');
1051         $result = $fs->mimetype_from_storedfile($file);
1052         $this->assertEquals('image/jpeg', $result);
1053     }
1055     /**
1056      * Data Provider for is_image_from_storedfile tests.
1057      *
1058      * @return array
1059      */
1060     public function is_image_from_storedfile_provider() {
1061         return array(
1062             'Standard image'            => array('image/png', true),
1063             'Made up document/image'    => array('document/image', false),
1064         );
1065     }
1067     /**
1068      * Data provider for get_local_path_from_storedfile tests.
1069      *
1070      * @return array
1071      */
1072     public function get_local_path_from_storedfile_provider() {
1073         return [
1074             'default args (nofetch)' => [
1075                 'args' => [],
1076                 'fetch' => 0,
1077             ],
1078             'explicit: nofetch' => [
1079                 'args' => [false],
1080                 'fetch' => 0,
1081             ],
1082             'explicit: fetch' => [
1083                 'args' => [true],
1084                 'fetch' => 1,
1085             ],
1086         ];
1087     }