Merge branch 'MDL-64506' of git://github.com/Chocolate-lightning/moodle
[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  * @coversDefaultClass file_system
39  */
40 class core_files_file_system_testcase extends advanced_testcase {
42     public function setUp() {
43         get_file_storage(true);
44     }
46     public function tearDown() {
47         get_file_storage(true);
48     }
50     /**
51      * Helper function to help setup and configure the virtual file system stream.
52      *
53      * @param   array $filedir Directory structure and content of the filedir
54      * @param   array $trashdir Directory structure and content of the sourcedir
55      * @param   array $sourcedir Directory structure and content of a directory used for source files for tests
56      * @return  \org\bovigo\vfs\vfsStream
57      */
58     protected function setup_vfile_root($content = []) {
59         $vfileroot = \org\bovigo\vfs\vfsStream::setup('root', null, $content);
61         return $vfileroot;
62     }
64     /**
65      * Helper to create a stored file objectw with the given supplied content.
66      *
67      * @param   string  $filecontent The content of the mocked file
68      * @param   string  $filename The file name to use in the stored_file
69      * @param   array   $mockedmethods A list of methods you intend to override
70      *                  If no methods are specified, only abstract functions are mocked.
71      * @return stored_file
72      */
73     protected function get_stored_file($filecontent, $filename = null, $mockedmethods = null) {
74         $contenthash = file_storage::hash_from_string($filecontent);
75         if (empty($filename)) {
76             $filename = $contenthash;
77         }
79         $file = $this->getMockBuilder(stored_file::class)
80             ->setMethods($mockedmethods)
81             ->setConstructorArgs([
82                 get_file_storage(),
83                 (object) [
84                     'contenthash' => $contenthash,
85                     'filesize' => strlen($filecontent),
86                     'filename' => $filename,
87                 ]
88             ])
89             ->getMock();
91         return $file;
92     }
94     /**
95      * Get a testable mock of the abstract file_system class.
96      *
97      * @param   array   $mockedmethods A list of methods you intend to override
98      *                  If no methods are specified, only abstract functions are mocked.
99      * @return file_system
100      */
101     protected function get_testable_mock($mockedmethods = []) {
102         $fs = $this->getMockBuilder(file_system::class)
103             ->setMethods($mockedmethods)
104             ->getMockForAbstractClass();
106         return $fs;
107     }
109     /**
110      * Ensure that the file system is not clonable.
111      *
112      * @covers ::<!public>
113      */
114     public function test_not_cloneable() {
115         $reflection = new ReflectionClass('file_system');
116         $this->assertFalse($reflection->isCloneable());
117     }
119     /**
120      * Ensure that the filedir file_system extension is used by default.
121      *
122      * @covers ::<!public>
123      */
124     public function test_default_class() {
125         $this->resetAfterTest();
127         // Ensure that the alternative_file_system_class is null.
128         global $CFG;
129         $CFG->alternative_file_system_class = null;
131         $storage = get_file_storage();
132         $fs = $storage->get_file_system();
133         $this->assertInstanceOf(file_system::class, $fs);
134         $this->assertEquals(file_system_filedir::class, get_class($fs));
135     }
137     /**
138      * Ensure that the specified file_system extension class is used.
139      *
140      * @covers ::<!public>
141      */
142     public function test_supplied_class() {
143         global $CFG;
144         $this->resetAfterTest();
146         // Mock the file_system.
147         // Mocks create a new child of the mocked class which is perfect for this test.
148         $filesystem = $this->getMockBuilder('file_system')
149             ->disableOriginalConstructor()
150             ->getMock();
151         $CFG->alternative_file_system_class = get_class($filesystem);
153         $storage = get_file_storage();
154         $fs = $storage->get_file_system();
155         $this->assertInstanceOf(file_system::class, $fs);
156         $this->assertEquals(get_class($filesystem), get_class($fs));
157     }
159     /**
160      * Test that the readfile function outputs content to disk.
161      *
162      * @covers ::readfile
163      * @covers ::<!public>
164      */
165     public function test_readfile_remote() {
166         global $CFG;
168         // Mock the filesystem.
169         $filecontent = 'example content';
170         $vfileroot = $this->setup_vfile_root(['sourcefile' => $filecontent]);
171         $filepath = \org\bovigo\vfs\vfsStream::url('root/sourcefile');
173         $file = $this->get_stored_file($filecontent);
175         // Mock the file_system class.
176         // We need to override the get_remote_path_from_storedfile function.
177         $fs = $this->get_testable_mock([
178             'get_remote_path_from_storedfile',
179             'is_file_readable_locally_by_storedfile',
180             'get_local_path_from_storedfile',
181         ]);
182         $fs->method('get_remote_path_from_storedfile')->willReturn($filepath);
183         $fs->method('is_file_readable_locally_by_storedfile')->willReturn(false);
184         $fs->expects($this->never())->method('get_local_path_from_storedfile');
186         // Note: It is currently not possible to mock readfile_allow_large
187         // because file_system is in the global namespace.
188         // We must therefore check for expected output. This is not ideal.
189         $this->expectOutputString($filecontent);
190         $fs->readfile($file);
191     }
193     /**
194      * Test that the readfile function outputs content to disk.
195      *
196      * @covers ::readfile
197      * @covers ::<!public>
198      */
199     public function test_readfile_local() {
200         global $CFG;
202         // Mock the filesystem.
203         $filecontent = 'example content';
204         $vfileroot = $this->setup_vfile_root(['sourcefile' => $filecontent]);
205         $filepath = \org\bovigo\vfs\vfsStream::url('root/sourcefile');
207         $file = $this->get_stored_file($filecontent);
209         // Mock the file_system class.
210         // We need to override the get_remote_path_from_storedfile function.
211         $fs = $this->get_testable_mock([
212             'get_remote_path_from_storedfile',
213             'is_file_readable_locally_by_storedfile',
214             'get_local_path_from_storedfile',
215         ]);
216         $fs->method('is_file_readable_locally_by_storedfile')->willReturn(true);
217         $fs->expects($this->never())->method('get_remote_path_from_storedfile');
218         $fs->expects($this->once())->method('get_local_path_from_storedfile')->willReturn($filepath);
220         // Note: It is currently not possible to mock readfile_allow_large
221         // because file_system is in the global namespace.
222         // We must therefore check for expected output. This is not ideal.
223         $this->expectOutputString($filecontent);
224         $fs->readfile($file);
225     }
227     /**
228      * Test that the get_local_path_from_storedfile function functions
229      * correctly when called with various args.
230      *
231      * @dataProvider get_local_path_from_storedfile_provider
232      * @param   array   $args The additional args to pass to get_local_path_from_storedfile
233      * @param   bool    $fetch Whether the combination of args should have caused a fetch
234      *
235      * @covers ::get_local_path_from_storedfile
236      * @covers ::<!public>
237      */
238     public function test_get_local_path_from_storedfile($args, $fetch) {
239         $filepath = '/path/to/file';
240         $filecontent = 'example content';
242         // Get the filesystem mock.
243         $fs = $this->get_testable_mock([
244             'get_local_path_from_hash',
245         ]);
246         $fs->expects($this->once())
247             ->method('get_local_path_from_hash')
248             ->with($this->equalTo(file_storage::hash_from_string($filecontent)), $this->equalTo($fetch))
249             ->willReturn($filepath);
251         $file = $this->get_stored_file($filecontent);
253         $method = new ReflectionMethod(file_system::class, 'get_local_path_from_storedfile');
254         $method->setAccessible(true);
255         $result = $method->invokeArgs($fs, array_merge([$file], $args));
257         $this->assertEquals($filepath, $result);
258     }
260     /**
261      * Ensure that the default implementation of get_remote_path_from_storedfile
262      * simply calls get_local_path_from_storedfile without requiring a
263      * fetch.
264      *
265      * @covers ::get_remote_path_from_storedfile
266      * @covers ::<!public>
267      */
268     public function test_get_remote_path_from_storedfile() {
269         $filepath = '/path/to/file';
270         $filecontent = 'example content';
272         $fs = $this->get_testable_mock([
273             'get_remote_path_from_hash',
274         ]);
276         $fs->expects($this->once())
277             ->method('get_remote_path_from_hash')
278             ->with($this->equalTo(file_storage::hash_from_string($filecontent)), $this->equalTo(false))
279             ->willReturn($filepath);
281         $file = $this->get_stored_file($filecontent);
283         $method = new ReflectionMethod(file_system::class, 'get_remote_path_from_storedfile');
284         $method->setAccessible(true);
285         $result = $method->invokeArgs($fs, [$file]);
287         $this->assertEquals($filepath, $result);
288     }
290     /**
291      * Test the stock implementation of is_file_readable_locally_by_hash with a valid file.
292      *
293      * This should call get_local_path_from_hash and check the readability
294      * of the file.
295      *
296      * Fetching the file is optional.
297      *
298      * @covers ::is_file_readable_locally_by_hash
299      * @covers ::<!public>
300      */
301     public function test_is_file_readable_locally_by_hash() {
302         $filecontent = 'example content';
303         $contenthash = file_storage::hash_from_string($filecontent);
304         $filepath = __FILE__;
306         $fs = $this->get_testable_mock([
307             'get_local_path_from_hash',
308         ]);
310         $fs->method('get_local_path_from_hash')
311             ->with($this->equalTo($contenthash), $this->equalTo(false))
312             ->willReturn($filepath);
314         $this->assertTrue($fs->is_file_readable_locally_by_hash($contenthash));
315     }
317     /**
318      * Test the stock implementation of is_file_readable_locally_by_hash with an empty file.
319      *
320      * @covers ::is_file_readable_locally_by_hash
321      * @covers ::<!public>
322      */
323     public function test_is_file_readable_locally_by_hash_empty() {
324         $filecontent = '';
325         $contenthash = file_storage::hash_from_string($filecontent);
327         $fs = $this->get_testable_mock([
328             'get_local_path_from_hash',
329         ]);
331         $fs->expects($this->never())
332             ->method('get_local_path_from_hash');
334         $this->assertTrue($fs->is_file_readable_locally_by_hash($contenthash));
335     }
337     /**
338      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
339      *
340      * @covers ::is_file_readable_remotely_by_hash
341      * @covers ::<!public>
342      */
343     public function test_is_file_readable_remotely_by_hash() {
344         $filecontent = 'example content';
345         $contenthash = file_storage::hash_from_string($filecontent);
347         $fs = $this->get_testable_mock([
348             'get_remote_path_from_hash',
349         ]);
351         $fs->method('get_remote_path_from_hash')
352             ->with($this->equalTo($contenthash), $this->equalTo(false))
353             ->willReturn(__FILE__);
355         $this->assertTrue($fs->is_file_readable_remotely_by_hash($contenthash));
356     }
358     /**
359      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
360      *
361      * @covers ::is_file_readable_remotely_by_hash
362      * @covers ::<!public>
363      */
364     public function test_is_file_readable_remotely_by_hash_empty() {
365         $filecontent = '';
366         $contenthash = file_storage::hash_from_string($filecontent);
368         $fs = $this->get_testable_mock([
369             'get_remote_path_from_hash',
370         ]);
372         $fs->expects($this->never())
373             ->method('get_remote_path_from_hash');
375         $this->assertTrue($fs->is_file_readable_remotely_by_hash($contenthash));
376     }
378     /**
379      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
380      *
381      * @covers ::is_file_readable_remotely_by_hash
382      * @covers ::<!public>
383      */
384     public function test_is_file_readable_remotely_by_hash_not_found() {
385         $filecontent = 'example content';
386         $contenthash = file_storage::hash_from_string($filecontent);
388         $fs = $this->get_testable_mock([
389             'get_remote_path_from_hash',
390         ]);
392         $fs->method('get_remote_path_from_hash')
393             ->with($this->equalTo($contenthash), $this->equalTo(false))
394             ->willReturn('/path/to/nonexistent/file');
396         $this->assertFalse($fs->is_file_readable_remotely_by_hash($contenthash));
397     }
399     /**
400      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
401      *
402      * @covers ::is_file_readable_remotely_by_storedfile
403      * @covers ::<!public>
404      */
405     public function test_is_file_readable_remotely_by_storedfile() {
406         $file = $this->get_stored_file('example content');
408         $fs = $this->get_testable_mock([
409             'get_remote_path_from_storedfile',
410         ]);
412         $fs->method('get_remote_path_from_storedfile')
413             ->willReturn(__FILE__);
415         $this->assertTrue($fs->is_file_readable_remotely_by_storedfile($file));
416     }
418     /**
419      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
420      *
421      * @covers ::is_file_readable_remotely_by_storedfile
422      * @covers ::<!public>
423      */
424     public function test_is_file_readable_remotely_by_storedfile_empty() {
425         $fs = $this->get_testable_mock([
426             'get_remote_path_from_storedfile',
427         ]);
429         $fs->expects($this->never())
430             ->method('get_remote_path_from_storedfile');
432         $file = $this->get_stored_file('');
433         $this->assertTrue($fs->is_file_readable_remotely_by_storedfile($file));
434     }
436     /**
437      * Test the stock implementation of is_file_readable_locally_by_storedfile with an empty file.
438      *
439      * @covers ::is_file_readable_locally_by_storedfile
440      * @covers ::<!public>
441      */
442     public function test_is_file_readable_locally_by_storedfile_empty() {
443         $fs = $this->get_testable_mock([
444             'get_local_path_from_storedfile',
445         ]);
447         $fs->expects($this->never())
448             ->method('get_local_path_from_storedfile');
450         $file = $this->get_stored_file('');
451         $this->assertTrue($fs->is_file_readable_locally_by_storedfile($file));
452     }
454     /**
455      * Test the stock implementation of is_file_readable_remotely_by_storedfile with a valid file.
456      *
457      * @covers ::is_file_readable_locally_by_storedfile
458      * @covers ::<!public>
459      */
460     public function test_is_file_readable_remotely_by_storedfile_not_found() {
461         $file = $this->get_stored_file('example content');
463         $fs = $this->get_testable_mock([
464             'get_remote_path_from_storedfile',
465         ]);
467         $fs->method('get_remote_path_from_storedfile')
468             ->willReturn(__LINE__);
470         $this->assertFalse($fs->is_file_readable_remotely_by_storedfile($file));
471     }
473     /**
474      * Test the stock implementation of is_file_readable_locally_by_storedfile with a valid file.
475      *
476      * @covers ::is_file_readable_locally_by_storedfile
477      * @covers ::<!public>
478      */
479     public function test_is_file_readable_locally_by_storedfile_unreadable() {
480         $fs = $this->get_testable_mock([
481             'get_local_path_from_storedfile',
482         ]);
483         $file = $this->get_stored_file('example content');
485         $fs->method('get_local_path_from_storedfile')
486             ->with($this->equalTo($file), $this->equalTo(false))
487             ->willReturn('/path/to/nonexistent/file');
489         $this->assertFalse($fs->is_file_readable_locally_by_storedfile($file));
490     }
492     /**
493      * Test the stock implementation of is_file_readable_locally_by_storedfile with a valid file should pass fetch.
494      *
495      * @covers ::is_file_readable_locally_by_storedfile
496      * @covers ::<!public>
497      */
498     public function test_is_file_readable_locally_by_storedfile_passes_fetch() {
499         $fs = $this->get_testable_mock([
500             'get_local_path_from_storedfile',
501         ]);
502         $file = $this->get_stored_file('example content');
504         $fs->method('get_local_path_from_storedfile')
505             ->with($this->equalTo($file), $this->equalTo(true))
506             ->willReturn('/path/to/nonexistent/file');
508         $this->assertFalse($fs->is_file_readable_locally_by_storedfile($file, true));
509     }
511     /**
512      * Ensure that is_file_removable returns correctly for an empty file.
513      *
514      * @covers ::is_file_removable
515      * @covers ::<!public>
516      */
517     public function test_is_file_removable_empty() {
518         $filecontent = '';
519         $contenthash = file_storage::hash_from_string($filecontent);
521         $method = new ReflectionMethod(file_system::class, 'is_file_removable');
522         $method->setAccessible(true);
523         $result = $method->invokeArgs(null, [$contenthash]);
524         $this->assertFalse($result);
525     }
527     /**
528      * Ensure that is_file_removable returns false if the file is still in use.
529      *
530      * @covers ::is_file_removable
531      * @covers ::<!public>
532      */
533     public function test_is_file_removable_in_use() {
534         $this->resetAfterTest();
535         global $DB;
537         $filecontent = 'example content';
538         $contenthash = file_storage::hash_from_string($filecontent);
540         $DB = $this->getMockBuilder(\moodle_database::class)
541             ->setMethods(['record_exists'])
542             ->getMockForAbstractClass();
543         $DB->method('record_exists')->willReturn(true);
545         $method = new ReflectionMethod(file_system::class, 'is_file_removable');
546         $method->setAccessible(true);
547         $result = $method->invokeArgs(null, [$contenthash]);
549         $this->assertFalse($result);
550     }
552     /**
553      * Ensure that is_file_removable returns false if the file is not in use.
554      *
555      * @covers ::is_file_removable
556      * @covers ::<!public>
557      */
558     public function test_is_file_removable_not_in_use() {
559         $this->resetAfterTest();
560         global $DB;
562         $filecontent = 'example content';
563         $contenthash = file_storage::hash_from_string($filecontent);
565         $DB = $this->getMockBuilder(\moodle_database::class)
566             ->setMethods(['record_exists'])
567             ->getMockForAbstractClass();
568         $DB->method('record_exists')->willReturn(false);
570         $method = new ReflectionMethod(file_system::class, 'is_file_removable');
571         $method->setAccessible(true);
572         $result = $method->invokeArgs(null, [$contenthash]);
574         $this->assertTrue($result);
575     }
577     /**
578      * Test the stock implementation of get_content.
579      *
580      * @covers ::get_content
581      * @covers ::<!public>
582      */
583     public function test_get_content() {
584         global $CFG;
586         // Mock the filesystem.
587         $filecontent = 'example content';
588         $vfileroot = $this->setup_vfile_root(['sourcefile' => $filecontent]);
589         $filepath = \org\bovigo\vfs\vfsStream::url('root/sourcefile');
591         $file = $this->get_stored_file($filecontent);
593         // Mock the file_system class.
594         // We need to override the get_remote_path_from_storedfile function.
595         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
596         $fs->method('get_remote_path_from_storedfile')->willReturn($filepath);
598         $result = $fs->get_content($file);
600         $this->assertEquals($filecontent, $result);
601     }
603     /**
604      * Test the stock implementation of get_content.
605      *
606      * @covers ::get_content
607      * @covers ::<!public>
608      */
609     public function test_get_content_empty() {
610         global $CFG;
612         $filecontent = '';
613         $file = $this->get_stored_file($filecontent);
615         // Mock the file_system class.
616         // We need to override the get_remote_path_from_storedfile function.
617         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
618         $fs->expects($this->never())
619             ->method('get_remote_path_from_storedfile');
621         $result = $fs->get_content($file);
623         $this->assertEquals($filecontent, $result);
624     }
626     /**
627      * Ensure that the list_files function requires a local copy of the
628      * file, and passes the path to the packer.
629      *
630      * @covers ::list_files
631      * @covers ::<!public>
632      */
633     public function test_list_files() {
634         $filecontent = 'example content';
635         $file = $this->get_stored_file($filecontent);
636         $filepath = __FILE__;
637         $expectedresult = (object) [];
639         // Mock the file_system class.
640         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
641         $fs->method('get_local_path_from_storedfile')
642             ->with($this->equalTo($file), $this->equalTo(true))
643             ->willReturn(__FILE__);
645         $packer = $this->getMockBuilder(file_packer::class)
646             ->setMethods(['list_files'])
647             ->getMockForAbstractClass();
649         $packer->expects($this->once())
650             ->method('list_files')
651             ->with($this->equalTo($filepath))
652             ->willReturn($expectedresult);
654         $result = $fs->list_files($file, $packer);
656         $this->assertEquals($expectedresult, $result);
657     }
659     /**
660      * Ensure that the extract_to_pathname function requires a local copy of the
661      * file, and passes the path to the packer.
662      *
663      * @covers ::extract_to_pathname
664      * @covers ::<!public>
665      */
666     public function test_extract_to_pathname() {
667         $filecontent = 'example content';
668         $file = $this->get_stored_file($filecontent);
669         $filepath = __FILE__;
670         $expectedresult = (object) [];
671         $outputpath = '/path/to/output';
673         // Mock the file_system class.
674         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
675         $fs->method('get_local_path_from_storedfile')
676             ->with($this->equalTo($file), $this->equalTo(true))
677             ->willReturn(__FILE__);
679         $packer = $this->getMockBuilder(file_packer::class)
680             ->setMethods(['extract_to_pathname'])
681             ->getMockForAbstractClass();
683         $packer->expects($this->once())
684             ->method('extract_to_pathname')
685             ->with($this->equalTo($filepath), $this->equalTo($outputpath), $this->equalTo(null), $this->equalTo(null))
686             ->willReturn($expectedresult);
688         $result = $fs->extract_to_pathname($file, $packer, $outputpath);
690         $this->assertEquals($expectedresult, $result);
691     }
693     /**
694      * Ensure that the extract_to_storage function requires a local copy of the
695      * file, and passes the path to the packer.
696      *
697      * @covers ::extract_to_storage
698      * @covers ::<!public>
699      */
700     public function test_extract_to_storage() {
701         $filecontent = 'example content';
702         $file = $this->get_stored_file($filecontent);
703         $filepath = __FILE__;
704         $expectedresult = (object) [];
705         $outputpath = '/path/to/output';
707         // Mock the file_system class.
708         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
709         $fs->method('get_local_path_from_storedfile')
710             ->with($this->equalTo($file), $this->equalTo(true))
711             ->willReturn(__FILE__);
713         $packer = $this->getMockBuilder(file_packer::class)
714             ->setMethods(['extract_to_storage'])
715             ->getMockForAbstractClass();
717         $packer->expects($this->once())
718             ->method('extract_to_storage')
719             ->with(
720                 $this->equalTo($filepath),
721                 $this->equalTo(42),
722                 $this->equalTo('component'),
723                 $this->equalTo('filearea'),
724                 $this->equalTo('itemid'),
725                 $this->equalTo('pathbase'),
726                 $this->equalTo('userid'),
727                 $this->equalTo(null)
728             )
729             ->willReturn($expectedresult);
731         $result = $fs->extract_to_storage($file, $packer, 42, 'component','filearea', 'itemid', 'pathbase', 'userid');
733         $this->assertEquals($expectedresult, $result);
734     }
736     /**
737      * Ensure that the add_storedfile_to_archive function requires a local copy of the
738      * file, and passes the path to the archive.
739      *
740      * @covers ::<!public>
741      */
742     public function test_add_storedfile_to_archive_directory() {
743         $file = $this->get_stored_file('', '.');
744         $archivepath = 'example';
745         $expectedresult = (object) [];
747         // Mock the file_system class.
748         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
749         $fs->method('get_local_path_from_storedfile')
750             ->with($this->equalTo($file), $this->equalTo(true))
751             ->willReturn(__FILE__);
753         $archive = $this->getMockBuilder(file_archive::class)
754             ->setMethods([
755                 'add_directory',
756                 'add_file_from_pathname',
757             ])
758             ->getMockForAbstractClass();
760         $archive->expects($this->once())
761             ->method('add_directory')
762             ->with($this->equalTo($archivepath))
763             ->willReturn($expectedresult);
765         $archive->expects($this->never())
766             ->method('add_file_from_pathname');
768         $result = $fs->add_storedfile_to_archive($file, $archive, $archivepath);
770         $this->assertEquals($expectedresult, $result);
771     }
773     /**
774      * Ensure that the add_storedfile_to_archive function requires a local copy of the
775      * file, and passes the path to the archive.
776      *
777      * @covers ::<!public>
778      */
779     public function test_add_storedfile_to_archive_file() {
780         $file = $this->get_stored_file('example content');
781         $filepath = __LINE__;
782         $archivepath = 'example';
783         $expectedresult = (object) [];
785         // Mock the file_system class.
786         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
787         $fs->method('get_local_path_from_storedfile')
788             ->with($this->equalTo($file), $this->equalTo(true))
789             ->willReturn($filepath);
791         $archive = $this->getMockBuilder(file_archive::class)
792             ->setMethods([
793                 'add_directory',
794                 'add_file_from_pathname',
795             ])
796             ->getMockForAbstractClass();
798         $archive->expects($this->never())
799             ->method('add_directory');
801         $archive->expects($this->once())
802             ->method('add_file_from_pathname')
803             ->with(
804                 $this->equalTo($archivepath),
805                 $this->equalTo($filepath)
806             )
807             ->willReturn($expectedresult);
809         $result = $fs->add_storedfile_to_archive($file, $archive, $archivepath);
811         $this->assertEquals($expectedresult, $result);
812     }
814     /**
815      * Ensure that the add_to_curl_request function requires a local copy of the
816      * file, and passes the path to curl_file_create.
817      *
818      * @covers ::add_to_curl_request
819      * @covers ::<!public>
820      */
821     public function test_add_to_curl_request() {
822         $file = $this->get_stored_file('example content');
823         $filepath = __FILE__;
824         $archivepath = 'example';
825         $key = 'myfile';
827         // Mock the file_system class.
828         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
829         $fs->method('get_local_path_from_storedfile')
830             ->with($this->equalTo($file), $this->equalTo(true))
831             ->willReturn($filepath);
833         $request = (object) ['_tmp_file_post_params' => []];
834         $fs->add_to_curl_request($file, $request, $key);
835         $this->assertArrayHasKey($key, $request->_tmp_file_post_params);
836         $this->assertEquals($filepath, $request->_tmp_file_post_params[$key]->name);
837     }
839     /**
840      * Ensure that test_get_imageinfo_not_image returns false if the file
841      * passed was deemed to not be an image.
842      *
843      * @covers ::get_imageinfo
844      * @covers ::<!public>
845      */
846     public function test_get_imageinfo_not_image() {
847         $filecontent = 'example content';
848         $file = $this->get_stored_file($filecontent);
850         $fs = $this->get_testable_mock([
851             'is_image_from_storedfile',
852         ]);
854         $fs->expects($this->once())
855             ->method('is_image_from_storedfile')
856             ->with($this->equalTo($file))
857             ->willReturn(false);
859         $this->assertFalse($fs->get_imageinfo($file));
860     }
862     /**
863      * Ensure that test_get_imageinfo_not_image returns imageinfo.
864      *
865      * @covers ::get_imageinfo
866      * @covers ::<!public>
867      */
868     public function test_get_imageinfo() {
869         $filepath = '/path/to/file';
870         $filecontent = 'example content';
871         $expectedresult = (object) [];
872         $file = $this->get_stored_file($filecontent);
874         $fs = $this->get_testable_mock([
875             'is_image_from_storedfile',
876             'get_local_path_from_storedfile',
877             'get_imageinfo_from_path',
878         ]);
880         $fs->expects($this->once())
881             ->method('is_image_from_storedfile')
882             ->with($this->equalTo($file))
883             ->willReturn(true);
885         $fs->expects($this->once())
886             ->method('get_local_path_from_storedfile')
887             ->with($this->equalTo($file), $this->equalTo(true))
888             ->willReturn($filepath);
890         $fs->expects($this->once())
891             ->method('get_imageinfo_from_path')
892             ->with($this->equalTo($filepath))
893             ->willReturn($expectedresult);
895         $this->assertEquals($expectedresult, $fs->get_imageinfo($file));
896     }
898     /**
899      * Ensure that is_image_from_storedfile always returns false for an
900      * empty file size.
901      *
902      * @covers ::is_image_from_storedfile
903      * @covers ::<!public>
904      */
905     public function test_is_image_empty_filesize() {
906         $filecontent = 'example content';
907         $file = $this->get_stored_file($filecontent, null, ['get_filesize']);
909         $file->expects($this->once())
910             ->method('get_filesize')
911             ->willReturn(0);
913         $fs = $this->get_testable_mock();
914         $this->assertFalse($fs->is_image_from_storedfile($file));
915     }
917     /**
918      * Ensure that is_image_from_storedfile behaves correctly based on
919      * mimetype.
920      *
921      * @dataProvider is_image_from_storedfile_provider
922      * @param   string  $mimetype Mimetype to test
923      * @param   bool    $isimage Whether this mimetype should be detected as an image
924      * @covers ::is_image_from_storedfile
925      * @covers ::<!public>
926      */
927     public function test_is_image_from_storedfile_mimetype($mimetype, $isimage) {
928         $filecontent = 'example content';
929         $file = $this->get_stored_file($filecontent, null, ['get_mimetype']);
931         $file->expects($this->once())
932             ->method('get_mimetype')
933             ->willReturn($mimetype);
935         $fs = $this->get_testable_mock();
936         $this->assertEquals($isimage, $fs->is_image_from_storedfile($file));
937     }
939     /**
940      * Test that get_imageinfo_from_path returns an appropriate response
941      * for an image.
942      *
943      * @covers ::get_imageinfo_from_path
944      * @covers ::<!public>
945      */
946     public function test_get_imageinfo_from_path() {
947         $filepath = __DIR__ . "/fixtures/testimage.jpg";
949         // Get the filesystem mock.
950         $fs = $this->get_testable_mock();
952         $method = new ReflectionMethod(file_system::class, 'get_imageinfo_from_path');
953         $method->setAccessible(true);
954         $result = $method->invokeArgs($fs, [$filepath]);
956         $this->assertArrayHasKey('width', $result);
957         $this->assertArrayHasKey('height', $result);
958         $this->assertArrayHasKey('mimetype', $result);
959         $this->assertEquals('image/jpeg', $result['mimetype']);
960     }
962     /**
963      * Test that get_imageinfo_from_path returns an appropriate response
964      * for a file which is not an image.
965      *
966      * @covers ::get_imageinfo_from_path
967      * @covers ::<!public>
968      */
969     public function test_get_imageinfo_from_path_no_image() {
970         $filepath = __FILE__;
972         // Get the filesystem mock.
973         $fs = $this->get_testable_mock();
975         $method = new ReflectionMethod(file_system::class, 'get_imageinfo_from_path');
976         $method->setAccessible(true);
977         $result = $method->invokeArgs($fs, [$filepath]);
979         $this->assertFalse($result);
980     }
982     /**
983      * Ensure that get_content_file_handle returns a valid file handle.
984      *
985      * @covers ::get_content_file_handle
986      * @covers ::<!public>
987      */
988     public function test_get_content_file_handle_default() {
989         $filecontent = 'example content';
990         $file = $this->get_stored_file($filecontent);
992         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
993         $fs->method('get_remote_path_from_storedfile')
994             ->willReturn(__FILE__);
996         // Note: We are unable to determine the mode in which the $fh was opened.
997         $fh = $fs->get_content_file_handle($file);
998         $this->assertTrue(is_resource($fh));
999         $this->assertEquals('stream', get_resource_type($fh));
1000         fclose($fh);
1001     }
1003     /**
1004      * Ensure that get_content_file_handle returns a valid file handle for a gz file.
1005      *
1006      * @covers ::get_content_file_handle
1007      * @covers ::<!public>
1008      */
1009     public function test_get_content_file_handle_gz() {
1010         $filecontent = 'example content';
1011         $file = $this->get_stored_file($filecontent);
1013         $fs = $this->get_testable_mock(['get_local_path_from_storedfile']);
1014         $fs->method('get_local_path_from_storedfile')
1015             ->willReturn(__DIR__ . "/fixtures/test.tgz");
1017         // Note: We are unable to determine the mode in which the $fh was opened.
1018         $fh = $fs->get_content_file_handle($file, stored_file::FILE_HANDLE_GZOPEN);
1019         $this->assertTrue(is_resource($fh));
1020         gzclose($fh);
1021     }
1023     /**
1024      * Ensure that get_content_file_handle returns an exception when calling for a invalid file handle type.
1025      *
1026      * @covers ::get_content_file_handle
1027      * @covers ::<!public>
1028      */
1029     public function test_get_content_file_handle_invalid() {
1030         $filecontent = 'example content';
1031         $file = $this->get_stored_file($filecontent);
1033         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
1034         $fs->method('get_remote_path_from_storedfile')
1035             ->willReturn(__FILE__);
1037         $this->expectException('coding_exception', 'Unexpected file handle type');
1038         $fs->get_content_file_handle($file, -1);
1039     }
1041     /**
1042      * Test that mimetype_from_hash returns the correct mimetype with
1043      * a file whose filename suggests mimetype.
1044      *
1045      * @covers ::mimetype_from_hash
1046      * @covers ::<!public>
1047      */
1048     public function test_mimetype_from_hash_using_filename() {
1049         $filepath = '/path/to/file/not/currently/on/disk';
1050         $filecontent = 'example content';
1051         $filename = 'test.jpg';
1052         $contenthash = file_storage::hash_from_string($filecontent);
1054         $fs = $this->get_testable_mock(['get_remote_path_from_hash']);
1055         $fs->method('get_remote_path_from_hash')->willReturn($filepath);
1057         $result = $fs->mimetype_from_hash($contenthash, $filename);
1058         $this->assertEquals('image/jpeg', $result);
1059     }
1061     /**
1062      * Test that mimetype_from_hash returns the correct mimetype with
1063      * a locally available file whose filename does not suggest mimetype.
1064      *
1065      * @covers ::mimetype_from_hash
1066      * @covers ::<!public>
1067      */
1068     public function test_mimetype_from_hash_using_file_content() {
1069         $filecontent = 'example content';
1070         $contenthash = file_storage::hash_from_string($filecontent);
1071         $filename = 'example';
1073         $filepath = __DIR__ . "/fixtures/testimage.jpg";
1074         $fs = $this->get_testable_mock(['get_local_path_from_hash']);
1075         $fs->method('get_local_path_from_hash')->willReturn($filepath);
1077         $result = $fs->mimetype_from_hash($contenthash, $filename);
1078         $this->assertEquals('image/jpeg', $result);
1079     }
1081     /**
1082      * Test that mimetype_from_hash returns the correct mimetype with
1083      * a remotely available file whose filename does not suggest mimetype.
1084      *
1085      * @covers ::mimetype_from_hash
1086      * @covers ::<!public>
1087      */
1088     public function test_mimetype_from_hash_using_file_content_remote() {
1089         $filepath = '/path/to/file/not/currently/on/disk';
1090         $filecontent = 'example content';
1091         $contenthash = file_storage::hash_from_string($filecontent);
1092         $filename = 'example';
1094         $filepath = __DIR__ . "/fixtures/testimage.jpg";
1096         $fs = $this->get_testable_mock([
1097             'get_remote_path_from_hash',
1098             'is_file_readable_locally_by_hash',
1099             'get_local_path_from_hash',
1100         ]);
1102         $fs->method('get_remote_path_from_hash')->willReturn('/path/to/remote/file');
1103         $fs->method('is_file_readable_locally_by_hash')->willReturn(false);
1104         $fs->method('get_local_path_from_hash')->willReturn($filepath);
1106         $result = $fs->mimetype_from_hash($contenthash, $filename);
1107         $this->assertEquals('image/jpeg', $result);
1108     }
1110     /**
1111      * Test that mimetype_from_storedfile returns the correct mimetype with
1112      * a file whose filename suggests mimetype.
1113      *
1114      * @covers ::mimetype_from_storedfile
1115      * @covers ::<!public>
1116      */
1117     public function test_mimetype_from_storedfile_empty() {
1118         $file = $this->get_stored_file('');
1120         $fs = $this->get_testable_mock();
1121         $result = $fs->mimetype_from_storedfile($file);
1122         $this->assertNull($result);
1123     }
1125     /**
1126      * Test that mimetype_from_storedfile returns the correct mimetype with
1127      * a file whose filename suggests mimetype.
1128      *
1129      * @covers ::mimetype_from_storedfile
1130      * @covers ::<!public>
1131      */
1132     public function test_mimetype_from_storedfile_using_filename() {
1133         $filepath = '/path/to/file/not/currently/on/disk';
1134         $fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
1135         $fs->method('get_remote_path_from_storedfile')->willReturn($filepath);
1137         $file = $this->get_stored_file('example content', 'test.jpg');
1139         $result = $fs->mimetype_from_storedfile($file);
1140         $this->assertEquals('image/jpeg', $result);
1141     }
1143     /**
1144      * Test that mimetype_from_storedfile returns the correct mimetype with
1145      * a locally available file whose filename does not suggest mimetype.
1146      *
1147      * @covers ::mimetype_from_storedfile
1148      * @covers ::<!public>
1149      */
1150     public function test_mimetype_from_storedfile_using_file_content() {
1151         $filepath = __DIR__ . "/fixtures/testimage.jpg";
1152         $fs = $this->get_testable_mock(['get_local_path_from_hash']);
1153         $fs->method('get_local_path_from_hash')->willReturn($filepath);
1155         $file = $this->get_stored_file('example content');
1157         $result = $fs->mimetype_from_storedfile($file);
1158         $this->assertEquals('image/jpeg', $result);
1159     }
1161     /**
1162      * Test that mimetype_from_storedfile returns the correct mimetype with
1163      * a remotely available file whose filename does not suggest mimetype.
1164      *
1165      * @covers ::mimetype_from_storedfile
1166      * @covers ::<!public>
1167      */
1168     public function test_mimetype_from_storedfile_using_file_content_remote() {
1169         $filepath = __DIR__ . "/fixtures/testimage.jpg";
1171         $fs = $this->get_testable_mock([
1172             'is_file_readable_locally_by_hash',
1173             'get_local_path_from_hash',
1174         ]);
1176         $fs->method('is_file_readable_locally_by_hash')->willReturn(false);
1177         $fs->method('get_local_path_from_hash')->will($this->onConsecutiveCalls('/path/to/remote/file', $filepath));
1179         $file = $this->get_stored_file('example content');
1181         $result = $fs->mimetype_from_storedfile($file);
1182         $this->assertEquals('image/jpeg', $result);
1183     }
1185     /**
1186      * Data Provider for is_image_from_storedfile tests.
1187      *
1188      * @return array
1189      */
1190     public function is_image_from_storedfile_provider() {
1191         return array(
1192             'Standard image'            => array('image/png', true),
1193             'Made up document/image'    => array('document/image', false),
1194         );
1195     }
1197     /**
1198      * Data provider for get_local_path_from_storedfile tests.
1199      *
1200      * @return array
1201      */
1202     public function get_local_path_from_storedfile_provider() {
1203         return [
1204             'default args (nofetch)' => [
1205                 'args' => [],
1206                 'fetch' => 0,
1207             ],
1208             'explicit: nofetch' => [
1209                 'args' => [false],
1210                 'fetch' => 0,
1211             ],
1212             'explicit: fetch' => [
1213                 'args' => [true],
1214                 'fetch' => 1,
1215             ],
1216         ];
1217     }