MDL-71116 core_badges: Backpack URLs with more than 50 chars
[moodle.git] / lib / filestorage / tests / file_system_filedir_test.php
blob63918c38b8152b4336a4c71be778d7e0a7a841a2
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.
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_filedir.
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
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir . '/filestorage/file_system.php');
30 require_once($CFG->libdir . '/filestorage/file_system_filedir.php');
32 /**
33 * Unit tests for file_system_filedir.
35 * @package core_files
36 * @category files
37 * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 * @coversDefaultClass file_system_filedir
41 class core_files_file_system_filedir_testcase extends advanced_testcase {
43 /**
44 * Shared test setUp.
46 public function setUp(): void {
47 // Reset the file storage so that subsequent fetches to get_file_storage are called after
48 // configuration is prepared.
49 get_file_storage(true);
52 /**
53 * Shared teset tearDown.
55 public function tearDown(): void {
56 // Reset the file storage so that subsequent tests will use the standard file storage.
57 get_file_storage(true);
60 /**
61 * Helper function to help setup and configure the virtual file system stream.
63 * @param array $filedir Directory structure and content of the filedir
64 * @param array $trashdir Directory structure and content of the sourcedir
65 * @param array $sourcedir Directory structure and content of a directory used for source files for tests
66 * @return \org\bovigo\vfs\vfsStream
68 protected function setup_vfile_root($filedir = [], $trashdir = [], $sourcedir = null) {
69 global $CFG;
70 $this->resetAfterTest();
72 $content = [];
73 if ($filedir !== null) {
74 $content['filedir'] = $filedir;
77 if ($trashdir !== null) {
78 $content['trashdir'] = $trashdir;
81 if ($sourcedir !== null) {
82 $content['sourcedir'] = $sourcedir;
85 $vfileroot = \org\bovigo\vfs\vfsStream::setup('root', null, $content);
87 $CFG->filedir = \org\bovigo\vfs\vfsStream::url('root/filedir');
88 $CFG->trashdir = \org\bovigo\vfs\vfsStream::url('root/trashdir');
90 return $vfileroot;
93 /**
94 * Helper to create a stored file objectw with the given supplied content.
96 * @param string $filecontent The content of the mocked file
97 * @param string $filename The file name to use in the stored_file
98 * @param array $mockedmethods A list of methods you intend to override
99 * If no methods are specified, only abstract functions are mocked.
100 * @return stored_file
102 protected function get_stored_file($filecontent, $filename = null, $mockedmethods = []) {
103 $contenthash = file_storage::hash_from_string($filecontent);
104 if (empty($filename)) {
105 $filename = $contenthash;
108 $file = $this->getMockBuilder(stored_file::class)
109 ->onlyMethods($mockedmethods)
110 ->setConstructorArgs([
111 get_file_storage(),
112 (object) [
113 'contenthash' => $contenthash,
114 'filesize' => strlen($filecontent),
115 'filename' => $filename,
118 ->getMock();
120 return $file;
124 * Get a testable mock of the file_system_filedir class.
126 * @param array $mockedmethods A list of methods you intend to override
127 * If no methods are specified, only abstract functions are mocked.
128 * @return file_system
130 protected function get_testable_mock($mockedmethods = []) {
131 $fs = $this->getMockBuilder(file_system_filedir::class)
132 ->onlyMethods($mockedmethods)
133 ->getMock();
135 return $fs;
139 * Ensure that an appropriate error is shown when the filedir directory
140 * is not writable.
142 * @covers ::__construct
144 public function test_readonly_filesystem_filedir() {
145 $this->resetAfterTest();
147 // Setup the filedir but remove permissions.
148 $vfileroot = $this->setup_vfile_root(null);
150 // Make the target path readonly.
151 $vfileroot->chmod(0444)
152 ->chown(\org\bovigo\vfs\vfsStream::OWNER_USER_2);
154 // This should generate an exception.
155 $this->expectException('file_exception');
156 $this->expectExceptionMessageMatches(
157 '/Cannot create local file pool directories. Please verify permissions in dataroot./');
159 new file_system_filedir();
163 * Ensure that an appropriate error is shown when the trash directory
164 * is not writable.
166 * @covers ::__construct
168 public function test_readonly_filesystem_trashdir() {
169 $this->resetAfterTest();
171 // Setup the trashdir but remove permissions.
172 $vfileroot = $this->setup_vfile_root([], null);
174 // Make the target path readonly.
175 $vfileroot->chmod(0444)
176 ->chown(\org\bovigo\vfs\vfsStream::OWNER_USER_2);
178 // This should generate an exception.
179 $this->expectException('file_exception');
180 $this->expectExceptionMessageMatches(
181 '/Cannot create local file pool directories. Please verify permissions in dataroot./');
183 new file_system_filedir();
187 * Test that the standard Moodle warning message is put into the filedir.
189 * @covers ::__construct
191 public function test_warnings_put_in_place() {
192 $this->resetAfterTest();
194 $vfileroot = $this->setup_vfile_root(null);
196 new file_system_filedir();
197 $this->assertTrue($vfileroot->hasChild('filedir/warning.txt'));
198 $this->assertEquals(
199 'This directory contains the content of uploaded files and is controlled by Moodle code. ' .
200 'Do not manually move, change or rename any of the files and subdirectories here.',
201 $vfileroot->getChild('filedir/warning.txt')->getContent()
206 * Ensure that the default implementation of get_remote_path_from_hash
207 * simply calls get_local_path_from_hash.
209 * @covers ::get_remote_path_from_hash
211 public function test_get_remote_path_from_hash() {
212 $filecontent = 'example content';
213 $contenthash = file_storage::hash_from_string($filecontent);
214 $expectedresult = (object) [];
216 $fs = $this->get_testable_mock([
217 'get_local_path_from_hash',
220 $fs->expects($this->once())
221 ->method('get_local_path_from_hash')
222 ->with($this->equalTo($contenthash), $this->equalTo(false))
223 ->willReturn($expectedresult);
225 $method = new ReflectionMethod(file_system_filedir::class, 'get_remote_path_from_hash');
226 $method->setAccessible(true);
227 $result = $method->invokeArgs($fs, [$contenthash]);
229 $this->assertEquals($expectedresult, $result);
233 * Test the stock implementation of get_local_path_from_storedfile_with_recovery with no file found and
234 * a failed recovery.
236 * @covers ::get_local_path_from_storedfile
238 public function test_get_local_path_from_storedfile_with_recovery() {
239 $filecontent = 'example content';
240 $file = $this->get_stored_file($filecontent);
241 $fs = $this->get_testable_mock([
242 'get_local_path_from_hash',
243 'recover_file',
245 $filepath = '/path/to/nonexistent/file';
247 $fs->method('get_local_path_from_hash')
248 ->willReturn($filepath);
250 $fs->expects($this->once())
251 ->method('recover_file')
252 ->with($this->equalTo($file));
254 $file = $this->get_stored_file('example content');
255 $result = $fs->get_local_path_from_storedfile($file, true);
257 $this->assertEquals($filepath, $result);
261 * Test the stock implementation of get_local_path_from_storedfile_with_recovery with no file found and
262 * a failed recovery.
264 * @covers ::get_local_path_from_storedfile
266 public function test_get_local_path_from_storedfile_without_recovery() {
267 $filecontent = 'example content';
268 $file = $this->get_stored_file($filecontent);
269 $fs = $this->get_testable_mock([
270 'get_local_path_from_hash',
271 'recover_file',
273 $filepath = '/path/to/nonexistent/file';
275 $fs->method('get_local_path_from_hash')
276 ->willReturn($filepath);
278 $fs->expects($this->never())
279 ->method('recover_file');
281 $file = $this->get_stored_file('example content');
282 $result = $fs->get_local_path_from_storedfile($file, false);
284 $this->assertEquals($filepath, $result);
288 * Test that the correct path is generated for the supplied content
289 * hashes.
291 * @dataProvider contenthash_dataprovider
292 * @param string $hash contenthash to test
293 * @param string $hashdir Expected format of content directory
295 * @covers ::get_fulldir_from_hash
297 public function test_get_fulldir_from_hash($hash, $hashdir) {
298 global $CFG;
300 $fs = new file_system_filedir();
301 $method = new ReflectionMethod(file_system_filedir::class, 'get_fulldir_from_hash');
302 $method->setAccessible(true);
303 $result = $method->invokeArgs($fs, array($hash));
305 $expectedpath = sprintf('%s/filedir/%s', $CFG->dataroot, $hashdir);
306 $this->assertEquals($expectedpath, $result);
310 * Test that the correct path is generated for the supplied content
311 * hashes when used with a stored_file.
313 * @dataProvider contenthash_dataprovider
314 * @param string $hash contenthash to test
315 * @param string $hashdir Expected format of content directory
317 * @covers ::get_fulldir_from_storedfile
319 public function test_get_fulldir_from_storedfile($hash, $hashdir) {
320 global $CFG;
322 $file = $this->getMockBuilder('stored_file')
323 ->disableOriginalConstructor()
324 ->onlyMethods([
325 'sync_external_file',
326 'get_contenthash',
328 ->getMock();
330 $file->method('get_contenthash')->willReturn($hash);
332 $fs = new file_system_filedir();
333 $method = new ReflectionMethod('file_system_filedir', 'get_fulldir_from_storedfile');
334 $method->setAccessible(true);
335 $result = $method->invokeArgs($fs, array($file));
337 $expectedpath = sprintf('%s/filedir/%s', $CFG->dataroot, $hashdir);
338 $this->assertEquals($expectedpath, $result);
342 * Test that the correct content directory is generated for the supplied
343 * content hashes.
345 * @dataProvider contenthash_dataprovider
346 * @param string $hash contenthash to test
347 * @param string $hashdir Expected format of content directory
349 * @covers ::get_contentdir_from_hash
351 public function test_get_contentdir_from_hash($hash, $hashdir) {
352 $method = new ReflectionMethod(file_system_filedir::class, 'get_contentdir_from_hash');
353 $method->setAccessible(true);
355 $fs = new file_system_filedir();
356 $result = $method->invokeArgs($fs, array($hash));
358 $this->assertEquals($hashdir, $result);
362 * Test that the correct content path is generated for the supplied
363 * content hashes.
365 * @dataProvider contenthash_dataprovider
366 * @param string $hash contenthash to test
367 * @param string $hashdir Expected format of content directory
369 * @covers ::get_contentpath_from_hash
371 public function test_get_contentpath_from_hash($hash, $hashdir) {
372 $method = new ReflectionMethod(file_system_filedir::class, 'get_contentpath_from_hash');
373 $method->setAccessible(true);
375 $fs = new file_system_filedir();
376 $result = $method->invokeArgs($fs, array($hash));
378 $expectedpath = sprintf('%s/%s', $hashdir, $hash);
379 $this->assertEquals($expectedpath, $result);
383 * Test that the correct trash path is generated for the supplied
384 * content hashes.
386 * @dataProvider contenthash_dataprovider
387 * @param string $hash contenthash to test
388 * @param string $hashdir Expected format of content directory
390 * @covers ::get_trash_fullpath_from_hash
392 public function test_get_trash_fullpath_from_hash($hash, $hashdir) {
393 global $CFG;
395 $fs = new file_system_filedir();
396 $method = new ReflectionMethod(file_system_filedir::class, 'get_trash_fullpath_from_hash');
397 $method->setAccessible(true);
398 $result = $method->invokeArgs($fs, array($hash));
400 $expectedpath = sprintf('%s/trashdir/%s/%s', $CFG->dataroot, $hashdir, $hash);
401 $this->assertEquals($expectedpath, $result);
405 * Test that the correct trash directory is generated for the supplied
406 * content hashes.
408 * @dataProvider contenthash_dataprovider
409 * @param string $hash contenthash to test
410 * @param string $hashdir Expected format of content directory
412 * @covers ::get_trash_fulldir_from_hash
414 public function test_get_trash_fulldir_from_hash($hash, $hashdir) {
415 global $CFG;
417 $fs = new file_system_filedir();
418 $method = new ReflectionMethod(file_system_filedir::class, 'get_trash_fulldir_from_hash');
419 $method->setAccessible(true);
420 $result = $method->invokeArgs($fs, array($hash));
422 $expectedpath = sprintf('%s/trashdir/%s', $CFG->dataroot, $hashdir);
423 $this->assertEquals($expectedpath, $result);
427 * Ensure that copying a file to a target from a stored_file works as anticipated.
429 * @covers ::copy_content_from_storedfile
431 public function test_copy_content_from_storedfile() {
432 $this->resetAfterTest();
433 global $CFG;
435 $filecontent = 'example content';
436 $contenthash = file_storage::hash_from_string($filecontent);
437 $filedircontent = [
438 $contenthash => $filecontent,
440 $vfileroot = $this->setup_vfile_root($filedircontent, [], []);
442 $fs = $this->getMockBuilder(file_system_filedir::class)
443 ->disableOriginalConstructor()
444 ->onlyMethods([
445 'get_local_path_from_storedfile',
447 ->getMock();
449 $file = $this->getMockBuilder(stored_file::class)
450 ->disableOriginalConstructor()
451 ->getMock();
453 $sourcefile = \org\bovigo\vfs\vfsStream::url('root/filedir/' . $contenthash);
454 $fs->method('get_local_path_from_storedfile')->willReturn($sourcefile);
456 $targetfile = \org\bovigo\vfs\vfsStream::url('root/targetfile');
457 $CFG->preventfilelocking = true;
458 $result = $fs->copy_content_from_storedfile($file, $targetfile);
460 $this->assertTrue($result);
461 $this->assertEquals($filecontent, $vfileroot->getChild('targetfile')->getContent());
465 * Ensure that content recovery works.
467 * @covers ::recover_file
469 public function test_recover_file() {
470 $this->resetAfterTest();
472 // Setup the filedir.
473 // This contains a virtual file which has a cache mismatch.
474 $filecontent = 'example content';
475 $contenthash = file_storage::hash_from_string($filecontent);
477 $trashdircontent = [
478 '0f' => [
479 'f3' => [
480 $contenthash => $filecontent,
485 $vfileroot = $this->setup_vfile_root([], $trashdircontent);
487 $file = new stored_file(get_file_storage(), (object) [
488 'contenthash' => $contenthash,
489 'filesize' => strlen($filecontent),
492 $fs = new file_system_filedir();
493 $method = new ReflectionMethod(file_system_filedir::class, 'recover_file');
494 $method->setAccessible(true);
495 $result = $method->invokeArgs($fs, array($file));
497 // Test the output.
498 $this->assertTrue($result);
500 $this->assertEquals($filecontent, $vfileroot->getChild('filedir/0f/f3/' . $contenthash)->getContent());
505 * Ensure that content recovery works.
507 * @covers ::recover_file
509 public function test_recover_file_already_present() {
510 $this->resetAfterTest();
512 // Setup the filedir.
513 // This contains a virtual file which has a cache mismatch.
514 $filecontent = 'example content';
515 $contenthash = file_storage::hash_from_string($filecontent);
517 $filedircontent = $trashdircontent = [
518 '0f' => [
519 'f3' => [
520 $contenthash => $filecontent,
525 $vfileroot = $this->setup_vfile_root($filedircontent, $trashdircontent);
527 $file = new stored_file(get_file_storage(), (object) [
528 'contenthash' => $contenthash,
529 'filesize' => strlen($filecontent),
532 $fs = new file_system_filedir();
533 $method = new ReflectionMethod(file_system_filedir::class, 'recover_file');
534 $method->setAccessible(true);
535 $result = $method->invokeArgs($fs, array($file));
537 // Test the output.
538 $this->assertTrue($result);
540 $this->assertEquals($filecontent, $vfileroot->getChild('filedir/0f/f3/' . $contenthash)->getContent());
544 * Ensure that content recovery works.
546 * @covers ::recover_file
548 public function test_recover_file_size_mismatch() {
549 $this->resetAfterTest();
551 // Setup the filedir.
552 // This contains a virtual file which has a cache mismatch.
553 $filecontent = 'example content';
554 $contenthash = file_storage::hash_from_string($filecontent);
556 $trashdircontent = [
557 '0f' => [
558 'f3' => [
559 $contenthash => $filecontent,
563 $vfileroot = $this->setup_vfile_root([], $trashdircontent);
565 $file = new stored_file(get_file_storage(), (object) [
566 'contenthash' => $contenthash,
567 'filesize' => strlen($filecontent) + 1,
570 $fs = new file_system_filedir();
571 $method = new ReflectionMethod(file_system_filedir::class, 'recover_file');
572 $method->setAccessible(true);
573 $result = $method->invokeArgs($fs, array($file));
575 // Test the output.
576 $this->assertFalse($result);
577 $this->assertFalse($vfileroot->hasChild('filedir/0f/f3/' . $contenthash));
581 * Ensure that content recovery works.
583 * @covers ::recover_file
585 public function test_recover_file_has_mismatch() {
586 $this->resetAfterTest();
588 // Setup the filedir.
589 // This contains a virtual file which has a cache mismatch.
590 $filecontent = 'example content';
591 $contenthash = file_storage::hash_from_string($filecontent);
593 $trashdircontent = [
594 '0f' => [
595 'f3' => [
596 $contenthash => $filecontent,
600 $vfileroot = $this->setup_vfile_root([], $trashdircontent);
602 $file = new stored_file(get_file_storage(), (object) [
603 'contenthash' => $contenthash . " different",
604 'filesize' => strlen($filecontent),
607 $fs = new file_system_filedir();
608 $method = new ReflectionMethod(file_system_filedir::class, 'recover_file');
609 $method->setAccessible(true);
610 $result = $method->invokeArgs($fs, array($file));
612 // Test the output.
613 $this->assertFalse($result);
614 $this->assertFalse($vfileroot->hasChild('filedir/0f/f3/' . $contenthash));
618 * Ensure that content recovery works when the content file is in the
619 * alt trash directory.
621 * @covers ::recover_file
623 public function test_recover_file_alttrash() {
624 $this->resetAfterTest();
626 // Setup the filedir.
627 // This contains a virtual file which has a cache mismatch.
628 $filecontent = 'example content';
629 $contenthash = file_storage::hash_from_string($filecontent);
631 $trashdircontent = [
632 $contenthash => $filecontent,
634 $vfileroot = $this->setup_vfile_root([], $trashdircontent);
636 $file = new stored_file(get_file_storage(), (object) [
637 'contenthash' => $contenthash,
638 'filesize' => strlen($filecontent),
641 $fs = new file_system_filedir();
642 $method = new ReflectionMethod(file_system_filedir::class, 'recover_file');
643 $method->setAccessible(true);
644 $result = $method->invokeArgs($fs, array($file));
646 // Test the output.
647 $this->assertTrue($result);
649 $this->assertEquals($filecontent, $vfileroot->getChild('filedir/0f/f3/' . $contenthash)->getContent());
653 * Test that an appropriate error message is generated when adding a
654 * file to the pool when the pool directory structure is not writable.
656 * @covers ::recover_file
658 public function test_recover_file_contentdir_readonly() {
659 $this->resetAfterTest();
661 $filecontent = 'example content';
662 $contenthash = file_storage::hash_from_string($filecontent);
663 $filedircontent = [
664 '0f' => [],
666 $trashdircontent = [
667 $contenthash => $filecontent,
669 $vfileroot = $this->setup_vfile_root($filedircontent, $trashdircontent);
671 // Make the target path readonly.
672 $vfileroot->getChild('filedir/0f')
673 ->chmod(0444)
674 ->chown(\org\bovigo\vfs\vfsStream::OWNER_USER_2);
676 $file = new stored_file(get_file_storage(), (object) [
677 'contenthash' => $contenthash,
678 'filesize' => strlen($filecontent),
681 $fs = new file_system_filedir();
682 $method = new ReflectionMethod(file_system_filedir::class, 'recover_file');
683 $method->setAccessible(true);
684 $result = $method->invokeArgs($fs, array($file));
686 // Test the output.
687 $this->assertFalse($result);
691 * Test adding a file to the pool.
693 * @covers ::add_file_from_path
695 public function test_add_file_from_path() {
696 $this->resetAfterTest();
697 global $CFG;
699 // Setup the filedir.
700 // This contains a virtual file which has a cache mismatch.
701 $filecontent = 'example content';
702 $contenthash = file_storage::hash_from_string($filecontent);
703 $sourcedircontent = [
704 'file' => $filecontent,
707 $vfileroot = $this->setup_vfile_root([], [], $sourcedircontent);
709 // Note, the vfs file system does not support locks - prevent file locking here.
710 $CFG->preventfilelocking = true;
712 // Attempt to add the file to the file pool.
713 $fs = new file_system_filedir();
714 $sourcefile = \org\bovigo\vfs\vfsStream::url('root/sourcedir/file');
715 $result = $fs->add_file_from_path($sourcefile);
717 // Test the output.
718 $this->assertEquals($contenthash, $result[0]);
719 $this->assertEquals(core_text::strlen($filecontent), $result[1]);
720 $this->assertTrue($result[2]);
722 $this->assertEquals($filecontent, $vfileroot->getChild('filedir/0f/f3/' . $contenthash)->getContent());
726 * Test that an appropriate error message is generated when adding an
727 * unavailable file to the pool is attempted.
729 * @covers ::add_file_from_path
731 public function test_add_file_from_path_file_unavailable() {
732 $this->resetAfterTest();
734 // Setup the filedir.
735 $vfileroot = $this->setup_vfile_root();
737 $this->expectException('file_exception');
738 $this->expectExceptionMessageMatches(
739 '/Cannot read file\. Either the file does not exist or there is a permission problem\./');
741 $fs = new file_system_filedir();
742 $fs->add_file_from_path(\org\bovigo\vfs\vfsStream::url('filedir/file'));
746 * Test that an appropriate error message is generated when specifying
747 * the wrong contenthash when adding a file to the pool.
749 * @covers ::add_file_from_path
751 public function test_add_file_from_path_mismatched_hash() {
752 $this->resetAfterTest();
754 $filecontent = 'example content';
755 $contenthash = file_storage::hash_from_string($filecontent);
756 $sourcedir = [
757 'file' => $filecontent,
759 $vfileroot = $this->setup_vfile_root([], [], $sourcedir);
761 $fs = new file_system_filedir();
762 $filepath = \org\bovigo\vfs\vfsStream::url('root/sourcedir/file');
763 $fs->add_file_from_path($filepath, 'eee4943847a35a4b6942c6f96daafde06bcfdfab');
764 $this->assertDebuggingCalled("Invalid contenthash submitted for file $filepath");
768 * Test that an appropriate error message is generated when an existing
769 * file in the pool has the wrong contenthash
771 * @covers ::add_file_from_path
773 public function test_add_file_from_path_existing_content_invalid() {
774 $this->resetAfterTest();
776 $filecontent = 'example content';
777 $contenthash = file_storage::hash_from_string($filecontent);
778 $filedircontent = [
779 '0f' => [
780 'f3' => [
781 // This contains a virtual file which has a cache mismatch.
782 '0ff30941ca5acd879fd809e8c937d9f9e6dd1615' => 'different example content',
786 $sourcedir = [
787 'file' => $filecontent,
789 $vfileroot = $this->setup_vfile_root($filedircontent, [], $sourcedir);
791 // Check that we hit the jackpot.
792 $fs = new file_system_filedir();
793 $filepath = \org\bovigo\vfs\vfsStream::url('root/sourcedir/file');
794 $result = $fs->add_file_from_path($filepath);
796 // We provided a bad hash. Check that the file was replaced.
797 $this->assertDebuggingCalled("Replacing invalid content file $contenthash");
799 // Test the output.
800 $this->assertEquals($contenthash, $result[0]);
801 $this->assertEquals(core_text::strlen($filecontent), $result[1]);
802 $this->assertFalse($result[2]);
804 // Fetch the new file structure.
805 $structure = \org\bovigo\vfs\vfsStream::inspect(
806 new \org\bovigo\vfs\visitor\vfsStreamStructureVisitor()
807 )->getStructure();
809 $this->assertEquals($filecontent, $structure['root']['filedir']['0f']['f3'][$contenthash]);
813 * Test that an appropriate error message is generated when adding a
814 * file to the pool when the pool directory structure is not writable.
816 * @covers ::add_file_from_path
818 public function test_add_file_from_path_existing_cannot_write_hashpath() {
819 $this->resetAfterTest();
821 $filecontent = 'example content';
822 $contenthash = file_storage::hash_from_string($filecontent);
823 $filedircontent = [
824 '0f' => [],
826 $sourcedir = [
827 'file' => $filecontent,
829 $vfileroot = $this->setup_vfile_root($filedircontent, [], $sourcedir);
831 // Make the target path readonly.
832 $vfileroot->getChild('filedir/0f')
833 ->chmod(0444)
834 ->chown(\org\bovigo\vfs\vfsStream::OWNER_USER_2);
836 $this->expectException('file_exception');
837 $this->expectExceptionMessageMatches(
838 "/Cannot create local file pool directories. Please verify permissions in dataroot./");
840 // Attempt to add the file to the file pool.
841 $fs = new file_system_filedir();
842 $sourcefile = \org\bovigo\vfs\vfsStream::url('root/sourcedir/file');
843 $fs->add_file_from_path($sourcefile);
847 * Test adding a string to the pool.
849 * @covers ::add_file_from_string
851 public function test_add_file_from_string() {
852 $this->resetAfterTest();
853 global $CFG;
855 $filecontent = 'example content';
856 $contenthash = file_storage::hash_from_string($filecontent);
857 $vfileroot = $this->setup_vfile_root();
859 // Note, the vfs file system does not support locks - prevent file locking here.
860 $CFG->preventfilelocking = true;
862 // Attempt to add the file to the file pool.
863 $fs = new file_system_filedir();
864 $result = $fs->add_file_from_string($filecontent);
866 // Test the output.
867 $this->assertEquals($contenthash, $result[0]);
868 $this->assertEquals(core_text::strlen($filecontent), $result[1]);
869 $this->assertTrue($result[2]);
873 * Test that an appropriate error message is generated when adding a
874 * string to the pool when the pool directory structure is not writable.
876 * @covers ::add_file_from_string
878 public function test_add_file_from_string_existing_cannot_write_hashpath() {
879 $this->resetAfterTest();
881 $filecontent = 'example content';
882 $contenthash = file_storage::hash_from_string($filecontent);
884 $filedircontent = [
885 '0f' => [],
887 $vfileroot = $this->setup_vfile_root($filedircontent);
889 // Make the target path readonly.
890 $vfileroot->getChild('filedir/0f')
891 ->chmod(0444)
892 ->chown(\org\bovigo\vfs\vfsStream::OWNER_USER_2);
894 $this->expectException('file_exception');
895 $this->expectExceptionMessageMatches(
896 "/Cannot create local file pool directories. Please verify permissions in dataroot./");
898 // Attempt to add the file to the file pool.
899 $fs = new file_system_filedir();
900 $fs->add_file_from_string($filecontent);
904 * Test adding a string to the pool when an item with the same
905 * contenthash is already present.
907 * @covers ::add_file_from_string
909 public function test_add_file_from_string_existing_matches() {
910 $this->resetAfterTest();
911 global $CFG;
913 $filecontent = 'example content';
914 $contenthash = file_storage::hash_from_string($filecontent);
915 $filedircontent = [
916 '0f' => [
917 'f3' => [
918 $contenthash => $filecontent,
923 $vfileroot = $this->setup_vfile_root($filedircontent);
925 // Note, the vfs file system does not support locks - prevent file locking here.
926 $CFG->preventfilelocking = true;
928 // Attempt to add the file to the file pool.
929 $fs = new file_system_filedir();
930 $result = $fs->add_file_from_string($filecontent);
932 // Test the output.
933 $this->assertEquals($contenthash, $result[0]);
934 $this->assertEquals(core_text::strlen($filecontent), $result[1]);
935 $this->assertFalse($result[2]);
939 * Test the cleanup of deleted files when there are no files to delete.
941 * @covers ::remove_file
943 public function test_remove_file_missing() {
944 $this->resetAfterTest();
946 $filecontent = 'example content';
947 $contenthash = file_storage::hash_from_string($filecontent);
948 $vfileroot = $this->setup_vfile_root();
950 $fs = new file_system_filedir();
951 $fs->remove_file($contenthash);
953 $this->assertFalse($vfileroot->hasChild('filedir/0f/f3/' . $contenthash));
954 // No file to move to trash, so the trash path will also be empty.
955 $this->assertFalse($vfileroot->hasChild('trashdir/0f'));
956 $this->assertFalse($vfileroot->hasChild('trashdir/0f/f3'));
957 $this->assertFalse($vfileroot->hasChild('trashdir/0f/f3/' . $contenthash));
961 * Test the cleanup of deleted files when a file already exists in the
962 * trash for that path.
964 * @covers ::remove_file
966 public function test_remove_file_existing_trash() {
967 $this->resetAfterTest();
969 $filecontent = 'example content';
970 $contenthash = file_storage::hash_from_string($filecontent);
972 $filedircontent = $trashdircontent = [
973 '0f' => [
974 'f3' => [
975 $contenthash => $filecontent,
979 $trashdircontent['0f']['f3'][$contenthash] .= 'different';
980 $vfileroot = $this->setup_vfile_root($filedircontent, $trashdircontent);
982 $fs = new file_system_filedir();
983 $fs->remove_file($contenthash);
985 $this->assertFalse($vfileroot->hasChild('filedir/0f/f3/' . $contenthash));
986 $this->assertTrue($vfileroot->hasChild('trashdir/0f/f3/' . $contenthash));
987 $this->assertNotEquals($filecontent, $vfileroot->getChild('trashdir/0f/f3/' . $contenthash)->getContent());
991 * Ensure that remove_file does nothing with an empty file.
993 * @covers ::remove_file
995 public function test_remove_file_empty() {
996 $this->resetAfterTest();
997 global $DB;
999 $DB = $this->getMockBuilder(\moodle_database::class)
1000 ->onlyMethods(['record_exists'])
1001 ->getMockForAbstractClass();
1003 $DB->expects($this->never())
1004 ->method('record_exists');
1006 $fs = new file_system_filedir();
1008 $result = $fs->remove_file(file_storage::hash_from_string(''));
1009 $this->assertNull($result);
1013 * Ensure that remove_file does nothing when a file is still
1014 * in use.
1016 * @covers ::remove_file
1018 public function test_remove_file_in_use() {
1019 $this->resetAfterTest();
1020 global $DB;
1022 $filecontent = 'example content';
1023 $contenthash = file_storage::hash_from_string($filecontent);
1024 $filedircontent = [
1025 '0f' => [
1026 'f3' => [
1027 $contenthash => $filecontent,
1031 $vfileroot = $this->setup_vfile_root($filedircontent);
1033 $DB = $this->getMockBuilder(\moodle_database::class)
1034 ->onlyMethods(['record_exists'])
1035 ->getMockForAbstractClass();
1037 $DB->method('record_exists')->willReturn(true);
1039 $fs = new file_system_filedir();
1040 $result = $fs->remove_file($contenthash);
1041 $this->assertTrue($vfileroot->hasChild('filedir/0f/f3/' . $contenthash));
1042 $this->assertFalse($vfileroot->hasChild('trashdir/0f/f3/' . $contenthash));
1046 * Ensure that remove_file removes the file when it is no
1047 * longer in use.
1049 * @covers ::remove_file
1051 public function test_remove_file_expired() {
1052 $this->resetAfterTest();
1053 global $DB;
1055 $filecontent = 'example content';
1056 $contenthash = file_storage::hash_from_string($filecontent);
1057 $filedircontent = [
1058 '0f' => [
1059 'f3' => [
1060 $contenthash => $filecontent,
1064 $vfileroot = $this->setup_vfile_root($filedircontent);
1066 $DB = $this->getMockBuilder(\moodle_database::class)
1067 ->onlyMethods(['record_exists'])
1068 ->getMockForAbstractClass();
1070 $DB->method('record_exists')->willReturn(false);
1072 $fs = new file_system_filedir();
1073 $result = $fs->remove_file($contenthash);
1074 $this->assertFalse($vfileroot->hasChild('filedir/0f/f3/' . $contenthash));
1075 $this->assertTrue($vfileroot->hasChild('trashdir/0f/f3/' . $contenthash));
1079 * Test purging the cache.
1081 * @covers ::empty_trash
1083 public function test_empty_trash() {
1084 $this->resetAfterTest();
1086 $filecontent = 'example content';
1087 $contenthash = file_storage::hash_from_string($filecontent);
1089 $filedircontent = $trashdircontent = [
1090 '0f' => [
1091 'f3' => [
1092 $contenthash => $filecontent,
1096 $vfileroot = $this->setup_vfile_root($filedircontent, $trashdircontent);
1098 $fs = new file_system_filedir();
1099 $method = new ReflectionMethod(file_system_filedir::class, 'empty_trash');
1100 $method->setAccessible(true);
1101 $result = $method->invoke($fs);
1103 $this->assertTrue($vfileroot->hasChild('filedir/0f/f3/' . $contenthash));
1104 $this->assertFalse($vfileroot->hasChild('trashdir'));
1105 $this->assertFalse($vfileroot->hasChild('trashdir/0f'));
1106 $this->assertFalse($vfileroot->hasChild('trashdir/0f/f3'));
1107 $this->assertFalse($vfileroot->hasChild('trashdir/0f/f3/' . $contenthash));
1111 * Data Provider for contenthash to contendir conversion.
1113 * @return array
1115 public function contenthash_dataprovider() {
1116 return array(
1117 array(
1118 'contenthash' => 'eee4943847a35a4b6942c6f96daafde06bcfdfab',
1119 'contentdir' => 'ee/e4',
1121 array(
1122 'contenthash' => 'aef05a62ae81ca0005d2569447779af062b7cda0',
1123 'contentdir' => 'ae/f0',