MDL-46375 core_files: Split parts of file_storage into new file system
[moodle.git] / lib / filestorage / tests / file_storage_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 /lib/filestorage/file_storage.php
19  *
20  * @package   core_files
21  * @category  phpunit
22  * @copyright 2012 David Mudrak <david@moodle.com>
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 . '/filelib.php');
30 require_once($CFG->dirroot . '/repository/lib.php');
31 require_once($CFG->libdir . '/filestorage/stored_file.php');
33 class core_files_file_storage_testcase extends advanced_testcase {
35     /**
36      * Files can be created from strings.
37      */
38     public function test_create_file_from_string() {
39         global $DB;
41         $this->resetAfterTest(true);
43         // Number of files installed in the database on a fresh Moodle site.
44         $installedfiles = $DB->count_records('files', array());
46         $content = 'abcd';
47         $syscontext = context_system::instance();
48         $filerecord = array(
49             'contextid' => $syscontext->id,
50             'component' => 'core',
51             'filearea'  => 'unittest',
52             'itemid'    => 0,
53             'filepath'  => '/images/',
54             'filename'  => 'testfile.txt',
55         );
56         $pathhash = sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].$filerecord['filename']);
58         $fs = get_file_storage();
59         $file = $fs->create_file_from_string($filerecord, $content);
61         $this->assertInstanceOf('stored_file', $file);
62         $this->assertSame(sha1($content), $file->get_contenthash());
63         $this->assertSame($pathhash, $file->get_pathnamehash());
65         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>$pathhash)));
67         $method = new ReflectionMethod('file_system', 'get_local_path_from_storedfile');
68         $method->setAccessible(true);
69         $filesystem = $fs->get_file_system();
70         $location = $method->invokeArgs($filesystem, array($file, true));
72         $this->assertFileExists($location);
74         // Verify the dir placeholder files are created.
75         $this->assertEquals($installedfiles + 3, $DB->count_records('files', array()));
76         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].'/.'))));
77         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].'.'))));
79         // Tests that missing content file is recreated.
81         unlink($location);
82         $this->assertFileNotExists($location);
84         $filerecord['filename'] = 'testfile2.txt';
85         $file2 = $fs->create_file_from_string($filerecord, $content);
86         $this->assertInstanceOf('stored_file', $file2);
87         $this->assertSame($file->get_contenthash(), $file2->get_contenthash());
88         $this->assertFileExists($location);
90         $this->assertEquals($installedfiles + 4, $DB->count_records('files', array()));
92         // Test that borked content file is recreated.
94         $this->assertSame(2, file_put_contents($location, 'xx'));
96         $filerecord['filename'] = 'testfile3.txt';
97         $file3 = $fs->create_file_from_string($filerecord, $content);
98         $this->assertInstanceOf('stored_file', $file3);
99         $this->assertSame($file->get_contenthash(), $file3->get_contenthash());
100         $this->assertFileExists($location);
102         $this->assertSame($content, file_get_contents($location));
103         $this->assertDebuggingCalled();
105         $this->assertEquals($installedfiles + 5, $DB->count_records('files', array()));
106     }
108     /**
109      * Local files can be added to the filepool
110      */
111     public function test_create_file_from_pathname() {
112         global $CFG, $DB;
114         $this->resetAfterTest(true);
116         // Number of files installed in the database on a fresh Moodle site.
117         $installedfiles = $DB->count_records('files', array());
119         $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
120         $syscontext = context_system::instance();
121         $filerecord = array(
122             'contextid' => $syscontext->id,
123             'component' => 'core',
124             'filearea'  => 'unittest',
125             'itemid'    => 0,
126             'filepath'  => '/images/',
127             'filename'  => 'testimage.jpg',
128         );
129         $pathhash = sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].$filerecord['filename']);
131         $fs = get_file_storage();
132         $file = $fs->create_file_from_pathname($filerecord, $filepath);
134         $this->assertInstanceOf('stored_file', $file);
135         $this->assertSame(sha1_file($filepath), $file->get_contenthash());
137         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>$pathhash)));
139         $method = new ReflectionMethod('file_system', 'get_local_path_from_storedfile');
140         $method->setAccessible(true);
141         $filesystem = $fs->get_file_system();
142         $location = $method->invokeArgs($filesystem, array($file, true));
144         $this->assertFileExists($location);
146         // Verify the dir placeholder files are created.
147         $this->assertEquals($installedfiles + 3, $DB->count_records('files', array()));
148         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].'/.'))));
149         $this->assertTrue($DB->record_exists('files', array('pathnamehash'=>sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].'.'))));
151         // Tests that missing content file is recreated.
153         unlink($location);
154         $this->assertFileNotExists($location);
156         $filerecord['filename'] = 'testfile2.jpg';
157         $file2 = $fs->create_file_from_pathname($filerecord, $filepath);
158         $this->assertInstanceOf('stored_file', $file2);
159         $this->assertSame($file->get_contenthash(), $file2->get_contenthash());
160         $this->assertFileExists($location);
162         $this->assertEquals($installedfiles + 4, $DB->count_records('files', array()));
164         // Test that borked content file is recreated.
166         $this->assertSame(2, file_put_contents($location, 'xx'));
168         $filerecord['filename'] = 'testfile3.jpg';
169         $file3 = $fs->create_file_from_pathname($filerecord, $filepath);
170         $this->assertInstanceOf('stored_file', $file3);
171         $this->assertSame($file->get_contenthash(), $file3->get_contenthash());
172         $this->assertFileExists($location);
174         $this->assertSame(file_get_contents($filepath), file_get_contents($location));
175         $this->assertDebuggingCalled();
177         $this->assertEquals($installedfiles + 5, $DB->count_records('files', array()));
179         // Test invalid file creation.
181         $filerecord['filename'] = 'testfile4.jpg';
182         try {
183             $fs->create_file_from_pathname($filerecord, $filepath.'nonexistent');
184             $this->fail('Exception expected when trying to add non-existent stored file.');
185         } catch (Exception $e) {
186             $this->assertInstanceOf('file_exception', $e);
187         }
188     }
190     /**
191      * Tests get get file.
192      */
193     public function test_get_file() {
194         global $CFG;
196         $this->resetAfterTest(false);
198         $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
199         $syscontext = context_system::instance();
200         $filerecord = array(
201             'contextid' => $syscontext->id,
202             'component' => 'core',
203             'filearea'  => 'unittest',
204             'itemid'    => 0,
205             'filepath'  => '/images/',
206             'filename'  => 'testimage.jpg',
207         );
208         $pathhash = sha1('/'.$filerecord['contextid'].'/'.$filerecord['component'].'/'.$filerecord['filearea'].'/'.$filerecord['itemid'].$filerecord['filepath'].$filerecord['filename']);
210         $fs = get_file_storage();
211         $file = $fs->create_file_from_pathname($filerecord, $filepath);
213         $this->assertInstanceOf('stored_file', $file);
214         $this->assertEquals($syscontext->id, $file->get_contextid());
215         $this->assertEquals('core', $file->get_component());
216         $this->assertEquals('unittest', $file->get_filearea());
217         $this->assertEquals(0, $file->get_itemid());
218         $this->assertEquals('/images/', $file->get_filepath());
219         $this->assertEquals('testimage.jpg', $file->get_filename());
220         $this->assertEquals(filesize($filepath), $file->get_filesize());
221         $this->assertEquals($pathhash, $file->get_pathnamehash());
223         return $file;
224     }
226     /**
227      * Local images can be added to the filepool and their preview can be obtained
228      *
229      * @depends test_get_file
230      */
231     public function test_get_file_preview(stored_file $file) {
232         global $CFG;
234         $this->resetAfterTest();
235         $fs = get_file_storage();
237         $previewtinyicon = $fs->get_file_preview($file, 'tinyicon');
238         $this->assertInstanceOf('stored_file', $previewtinyicon);
239         $this->assertEquals('6b9864ae1536a8eeef54e097319175a8be12f07c', $previewtinyicon->get_filename());
241         $previewtinyicon = $fs->get_file_preview($file, 'thumb');
242         $this->assertInstanceOf('stored_file', $previewtinyicon);
243         $this->assertEquals('6b9864ae1536a8eeef54e097319175a8be12f07c', $previewtinyicon->get_filename());
245         $this->expectException('file_exception');
246         $fs->get_file_preview($file, 'amodewhichdoesntexist');
247     }
249     public function test_get_file_preview_nonimage() {
250         $this->resetAfterTest(true);
251         $syscontext = context_system::instance();
252         $filerecord = array(
253             'contextid' => $syscontext->id,
254             'component' => 'core',
255             'filearea'  => 'unittest',
256             'itemid'    => 0,
257             'filepath'  => '/textfiles/',
258             'filename'  => 'testtext.txt',
259         );
261         $fs = get_file_storage();
262         $fs->create_file_from_string($filerecord, 'text contents');
263         $textfile = $fs->get_file($syscontext->id, $filerecord['component'], $filerecord['filearea'],
264             $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename']);
266         $preview = $fs->get_file_preview($textfile, 'thumb');
267         $this->assertFalse($preview);
268     }
270     /**
271      * Make sure renaming is working
272      *
273      * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
274      */
275     public function test_file_renaming() {
276         global $CFG;
278         $this->resetAfterTest();
279         $fs = get_file_storage();
280         $syscontext = context_system::instance();
281         $component = 'core';
282         $filearea  = 'unittest';
283         $itemid    = 0;
284         $filepath  = '/';
285         $filename  = 'test.txt';
287         $filerecord = array(
288             'contextid' => $syscontext->id,
289             'component' => $component,
290             'filearea'  => $filearea,
291             'itemid'    => $itemid,
292             'filepath'  => $filepath,
293             'filename'  => $filename,
294         );
296         $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
297         $this->assertInstanceOf('stored_file', $originalfile);
298         $contenthash = $originalfile->get_contenthash();
299         $newpath = '/test/';
300         $newname = 'newtest.txt';
302         // This should work.
303         $originalfile->rename($newpath, $newname);
304         $file = $fs->get_file($syscontext->id, $component, $filearea, $itemid, $newpath, $newname);
305         $this->assertInstanceOf('stored_file', $file);
306         $this->assertEquals($contenthash, $file->get_contenthash());
308         // Try break it.
309         $this->expectException('file_exception');
310         $this->expectExceptionMessage('Can not create file "1/core/unittest/0/test/newtest.txt" (file exists, cannot rename)');
311         // This shall throw exception.
312         $originalfile->rename($newpath, $newname);
313     }
315     /**
316      * Create file from reference tests
317      *
318      * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
319      */
320     public function test_create_file_from_reference() {
321         global $CFG, $DB;
323         $this->resetAfterTest();
324         // Create user.
325         $generator = $this->getDataGenerator();
326         $user = $generator->create_user();
327         $this->setUser($user);
328         $usercontext = context_user::instance($user->id);
329         $syscontext = context_system::instance();
331         $fs = get_file_storage();
333         $repositorypluginname = 'user';
334         // Override repository permission.
335         $capability = 'repository/' . $repositorypluginname . ':view';
336         $guestroleid = $DB->get_field('role', 'id', array('shortname' => 'guest'));
337         assign_capability($capability, CAP_ALLOW, $guestroleid, $syscontext->id, true);
339         $args = array();
340         $args['type'] = $repositorypluginname;
341         $repos = repository::get_instances($args);
342         $userrepository = reset($repos);
343         $this->assertInstanceOf('repository', $userrepository);
345         $component = 'user';
346         $filearea  = 'private';
347         $itemid    = 0;
348         $filepath  = '/';
349         $filename  = 'userfile.txt';
351         $filerecord = array(
352             'contextid' => $usercontext->id,
353             'component' => $component,
354             'filearea'  => $filearea,
355             'itemid'    => $itemid,
356             'filepath'  => $filepath,
357             'filename'  => $filename,
358         );
360         $content = 'Test content';
361         $originalfile = $fs->create_file_from_string($filerecord, $content);
362         $this->assertInstanceOf('stored_file', $originalfile);
364         $newfilerecord = array(
365             'contextid' => $syscontext->id,
366             'component' => 'core',
367             'filearea'  => 'phpunit',
368             'itemid'    => 0,
369             'filepath'  => $filepath,
370             'filename'  => $filename,
371         );
372         $ref = $fs->pack_reference($filerecord);
373         $newstoredfile = $fs->create_file_from_reference($newfilerecord, $userrepository->id, $ref);
374         $this->assertInstanceOf('stored_file', $newstoredfile);
375         $this->assertEquals($userrepository->id, $newstoredfile->get_repository_id());
376         $this->assertEquals($originalfile->get_contenthash(), $newstoredfile->get_contenthash());
377         $this->assertEquals($originalfile->get_filesize(), $newstoredfile->get_filesize());
378         $this->assertRegExp('#' . $filename. '$#', $newstoredfile->get_reference_details());
380         // Test looking for references.
381         $count = $fs->get_references_count_by_storedfile($originalfile);
382         $this->assertEquals(1, $count);
383         $files = $fs->get_references_by_storedfile($originalfile);
384         $file = reset($files);
385         $this->assertEquals($file, $newstoredfile);
387         // Look for references by repository ID.
388         $files = $fs->get_external_files($userrepository->id);
389         $file = reset($files);
390         $this->assertEquals($file, $newstoredfile);
392         // Try convert reference to local file.
393         $importedfile = $fs->import_external_file($newstoredfile);
394         $this->assertFalse($importedfile->is_external_file());
395         $this->assertInstanceOf('stored_file', $importedfile);
396         // Still readable?
397         $this->assertEquals($content, $importedfile->get_content());
398     }
400     private function setup_three_private_files() {
402         $this->resetAfterTest();
404         $generator = $this->getDataGenerator();
405         $user = $generator->create_user();
406         $this->setUser($user->id);
407         $usercontext = context_user::instance($user->id);
408         // Create a user private file.
409         $file1 = new stdClass;
410         $file1->contextid = $usercontext->id;
411         $file1->component = 'user';
412         $file1->filearea  = 'private';
413         $file1->itemid    = 0;
414         $file1->filepath  = '/';
415         $file1->filename  = '1.txt';
416         $file1->source    = 'test';
418         $fs = get_file_storage();
419         $userfile1 = $fs->create_file_from_string($file1, 'file1 content');
420         $this->assertInstanceOf('stored_file', $userfile1);
422         $file2 = clone($file1);
423         $file2->filename = '2.txt';
424         $userfile2 = $fs->create_file_from_string($file2, 'file2 content longer');
425         $this->assertInstanceOf('stored_file', $userfile2);
427         $file3 = clone($file1);
428         $file3->filename = '3.txt';
429         $userfile3 = $fs->create_file_from_storedfile($file3, $userfile2);
430         $this->assertInstanceOf('stored_file', $userfile3);
432         $user->ctxid = $usercontext->id;
434         return $user;
435     }
437     public function test_get_area_files() {
438         $user = $this->setup_three_private_files();
439         $fs = get_file_storage();
441         // Get area files with default options.
442         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
444         // Should be the two files we added plus the folder.
445         $this->assertEquals(4, count($areafiles));
447         // Verify structure.
448         foreach ($areafiles as $key => $file) {
449             $this->assertInstanceOf('stored_file', $file);
450             $this->assertEquals($key, $file->get_pathnamehash());
451         }
453         // Get area files without a folder.
454         $folderlessfiles = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'sortorder', false);
455         // Should be the two files without folder.
456         $this->assertEquals(3, count($folderlessfiles));
458         // Verify structure.
459         foreach ($folderlessfiles as $key => $file) {
460             $this->assertInstanceOf('stored_file', $file);
461             $this->assertEquals($key, $file->get_pathnamehash());
462         }
464         // Get area files ordered by id.
465         $filesbyid  = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'id', false);
466         // Should be the two files without folder.
467         $this->assertEquals(3, count($filesbyid));
469         // Verify structure.
470         foreach ($filesbyid as $key => $file) {
471             $this->assertInstanceOf('stored_file', $file);
472             $this->assertEquals($key, $file->get_pathnamehash());
473         }
475         // Test the limit feature to retrieve each individual file.
476         $limited = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'filename', false,
477                 0, 0, 1);
478         $mapfunc = function($f) {
479             return $f->get_filename();
480         };
481         $this->assertEquals(array('1.txt'), array_values(array_map($mapfunc, $limited)));
482         $limited = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'filename', false,
483                 0, 1, 50);
484         $this->assertEquals(array('2.txt', '3.txt'), array_values(array_map($mapfunc, $limited)));
486         // Test with an itemid with no files.
487         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private', 666, 'sortorder', false);
488         // Should be none.
489         $this->assertEmpty($areafiles);
490     }
492     public function test_get_area_tree() {
493         $user = $this->setup_three_private_files();
494         $fs = get_file_storage();
496         // Get area files with default options.
497         $areatree = $fs->get_area_tree($user->ctxid, 'user', 'private', 0);
498         $this->assertEmpty($areatree['subdirs']);
499         $this->assertNotEmpty($areatree['files']);
500         $this->assertCount(3, $areatree['files']);
502         // Ensure an empty try with a fake itemid.
503         $emptytree = $fs->get_area_tree($user->ctxid, 'user', 'private', 666);
504         $this->assertEmpty($emptytree['subdirs']);
505         $this->assertEmpty($emptytree['files']);
507         // Create a subdir.
508         $dir = $fs->create_directory($user->ctxid, 'user', 'private', 0, '/testsubdir/');
509         $this->assertInstanceOf('stored_file', $dir);
511         // Add a file to the subdir.
512         $filerecord = array(
513             'contextid' => $user->ctxid,
514             'component' => 'user',
515             'filearea'  => 'private',
516             'itemid'    => 0,
517             'filepath'  => '/testsubdir/',
518             'filename'  => 'test-get-area-tree.txt',
519         );
521         $directoryfile = $fs->create_file_from_string($filerecord, 'Test content');
522         $this->assertInstanceOf('stored_file', $directoryfile);
524         $areatree = $fs->get_area_tree($user->ctxid, 'user', 'private', 0);
526         // At the top level there should still be 3 files.
527         $this->assertCount(3, $areatree['files']);
529         // There should now be a subdirectory.
530         $this->assertCount(1, $areatree['subdirs']);
532         // The test subdir is named testsubdir.
533         $subdir = $areatree['subdirs']['testsubdir'];
534         $this->assertNotEmpty($subdir);
535         // It should have one file we added.
536         $this->assertCount(1, $subdir['files']);
537         // And no subdirs itself.
538         $this->assertCount(0, $subdir['subdirs']);
540         // Verify the file is the one we added.
541         $subdirfile = reset($subdir['files']);
542         $this->assertInstanceOf('stored_file', $subdirfile);
543         $this->assertEquals($filerecord['filename'], $subdirfile->get_filename());
544     }
546     public function test_get_file_by_id() {
547         $user = $this->setup_three_private_files();
548         $fs = get_file_storage();
550         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
552         // Test get_file_by_id.
553         $filebyid = reset($areafiles);
554         $shouldbesame = $fs->get_file_by_id($filebyid->get_id());
555         $this->assertEquals($filebyid->get_contenthash(), $shouldbesame->get_contenthash());
557         // Test an id which doens't exist.
558         $doesntexist = $fs->get_file_by_id(99999);
559         $this->assertFalse($doesntexist);
560     }
562     public function test_get_file_by_hash() {
563         $user = $this->setup_three_private_files();
564         $fs = get_file_storage();
566         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
567         // Test get_file_by_hash.
568         $filebyhash = reset($areafiles);
569         $shouldbesame = $fs->get_file_by_hash($filebyhash->get_pathnamehash());
570         $this->assertEquals($filebyhash->get_id(), $shouldbesame->get_id());
572         // Test an hash which doens't exist.
573         $doesntexist = $fs->get_file_by_hash('DOESNTEXIST');
574         $this->assertFalse($doesntexist);
575     }
577     public function test_get_external_files() {
578         $user = $this->setup_three_private_files();
579         $fs = get_file_storage();
581         $repos = repository::get_instances(array('type'=>'user'));
582         $userrepository = reset($repos);
583         $this->assertInstanceOf('repository', $userrepository);
585         // No aliases yet.
586         $exfiles = $fs->get_external_files($userrepository->id, 'id');
587         $this->assertEquals(array(), $exfiles);
589         // Create three aliases linking the same original: $aliasfile1 and $aliasfile2 are
590         // created via create_file_from_reference(), $aliasfile3 created from $aliasfile2.
591         $originalfile = null;
592         foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
593             if (!$areafile->is_directory()) {
594                 $originalfile = $areafile;
595                 break;
596             }
597         }
598         $this->assertInstanceOf('stored_file', $originalfile);
599         $originalrecord = array(
600             'contextid' => $originalfile->get_contextid(),
601             'component' => $originalfile->get_component(),
602             'filearea'  => $originalfile->get_filearea(),
603             'itemid'    => $originalfile->get_itemid(),
604             'filepath'  => $originalfile->get_filepath(),
605             'filename'  => $originalfile->get_filename(),
606         );
608         $aliasrecord = $this->generate_file_record();
609         $aliasrecord->filepath = '/foo/';
610         $aliasrecord->filename = 'one.txt';
612         $ref = $fs->pack_reference($originalrecord);
613         $aliasfile1 = $fs->create_file_from_reference($aliasrecord, $userrepository->id, $ref);
615         $aliasrecord->filepath = '/bar/';
616         $aliasrecord->filename = 'uno.txt';
617         // Change the order of the items in the array to make sure that it does not matter.
618         ksort($originalrecord);
619         $ref = $fs->pack_reference($originalrecord);
620         $aliasfile2 = $fs->create_file_from_reference($aliasrecord, $userrepository->id, $ref);
622         $aliasrecord->filepath = '/bar/';
623         $aliasrecord->filename = 'jedna.txt';
624         $aliasfile3 = $fs->create_file_from_storedfile($aliasrecord, $aliasfile2);
626         // Make sure we get three aliases now.
627         $exfiles = $fs->get_external_files($userrepository->id, 'id');
628         $this->assertEquals(3, count($exfiles));
629         foreach ($exfiles as $exfile) {
630             $this->assertTrue($exfile->is_external_file());
631         }
632         // Make sure they all link the same original (thence that all are linked with the same
633         // record in {files_reference}).
634         $this->assertEquals($aliasfile1->get_referencefileid(), $aliasfile2->get_referencefileid());
635         $this->assertEquals($aliasfile3->get_referencefileid(), $aliasfile2->get_referencefileid());
636     }
638     public function test_create_directory_contextid_negative() {
639         $fs = get_file_storage();
641         $this->expectException('file_exception');
642         $fs->create_directory(-1, 'core', 'unittest', 0, '/');
643     }
645     public function test_create_directory_contextid_invalid() {
646         $fs = get_file_storage();
648         $this->expectException('file_exception');
649         $fs->create_directory('not an int', 'core', 'unittest', 0, '/');
650     }
652     public function test_create_directory_component_invalid() {
653         $fs = get_file_storage();
654         $syscontext = context_system::instance();
656         $this->expectException('file_exception');
657         $fs->create_directory($syscontext->id, 'bad/component', 'unittest', 0, '/');
658     }
660     public function test_create_directory_filearea_invalid() {
661         $fs = get_file_storage();
662         $syscontext = context_system::instance();
664         $this->expectException('file_exception');
665         $fs->create_directory($syscontext->id, 'core', 'bad-filearea', 0, '/');
666     }
668     public function test_create_directory_itemid_negative() {
669         $fs = get_file_storage();
670         $syscontext = context_system::instance();
672         $this->expectException('file_exception');
673         $fs->create_directory($syscontext->id, 'core', 'unittest', -1, '/');
674     }
676     public function test_create_directory_itemid_invalid() {
677         $fs = get_file_storage();
678         $syscontext = context_system::instance();
680         $this->expectException('file_exception');
681         $fs->create_directory($syscontext->id, 'core', 'unittest', 'notanint', '/');
682     }
684     public function test_create_directory_filepath_invalid() {
685         $fs = get_file_storage();
686         $syscontext = context_system::instance();
688         $this->expectException('file_exception');
689         $fs->create_directory($syscontext->id, 'core', 'unittest', 0, '/not-with-trailing/or-leading-slash');
690     }
692     public function test_get_directory_files() {
693         $user = $this->setup_three_private_files();
694         $fs = get_file_storage();
696         $dir = $fs->create_directory($user->ctxid, 'user', 'private', 0, '/testsubdir/');
697         $this->assertInstanceOf('stored_file', $dir);
699         // Add a file to the subdir.
700         $filerecord = array(
701             'contextid' => $user->ctxid,
702             'component' => 'user',
703             'filearea'  => 'private',
704             'itemid'    => 0,
705             'filepath'  => '/testsubdir/',
706             'filename'  => 'test-get-area-tree.txt',
707         );
709         $directoryfile = $fs->create_file_from_string($filerecord, 'Test content');
710         $this->assertInstanceOf('stored_file', $directoryfile);
712         // Don't recurse without dirs.
713         $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', false, false, 'id');
714         // 3 files only.
715         $this->assertCount(3, $files);
716         foreach ($files as $key => $file) {
717             $this->assertInstanceOf('stored_file', $file);
718             $this->assertEquals($key, $file->get_pathnamehash());
719         }
721         // Don't recurse with dirs.
722         $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', false, true, 'id');
723         // 3 files + 1 directory.
724         $this->assertCount(4, $files);
725         foreach ($files as $key => $file) {
726             $this->assertInstanceOf('stored_file', $file);
727             $this->assertEquals($key, $file->get_pathnamehash());
728         }
730         // Recurse with dirs.
731         $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', true, true, 'id');
732         // 3 files + 1 directory +  1 subdir file.
733         $this->assertCount(5, $files);
734         foreach ($files as $key => $file) {
735             $this->assertInstanceOf('stored_file', $file);
736             $this->assertEquals($key, $file->get_pathnamehash());
737         }
739         // Recurse without dirs.
740         $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', true, false, 'id');
741         // 3 files +  1 subdir file.
742         $this->assertCount(4, $files);
743         foreach ($files as $key => $file) {
744             $this->assertInstanceOf('stored_file', $file);
745             $this->assertEquals($key, $file->get_pathnamehash());
746         }
747     }
749     public function test_search_references() {
750         $user = $this->setup_three_private_files();
751         $fs = get_file_storage();
752         $repos = repository::get_instances(array('type'=>'user'));
753         $repo = reset($repos);
755         $alias1 = array(
756             'contextid' => $user->ctxid,
757             'component' => 'user',
758             'filearea'  => 'private',
759             'itemid'    => 0,
760             'filepath'  => '/aliases/',
761             'filename'  => 'alias-to-1.txt'
762         );
764         $alias2 = array(
765             'contextid' => $user->ctxid,
766             'component' => 'user',
767             'filearea'  => 'private',
768             'itemid'    => 0,
769             'filepath'  => '/aliases/',
770             'filename'  => 'another-alias-to-1.txt'
771         );
773         $reference = file_storage::pack_reference(array(
774             'contextid' => $user->ctxid,
775             'component' => 'user',
776             'filearea'  => 'private',
777             'itemid'    => 0,
778             'filepath'  => '/',
779             'filename'  => '1.txt'
780         ));
782         // There are no aliases now.
783         $result = $fs->search_references($reference);
784         $this->assertEquals(array(), $result);
786         $result = $fs->search_references_count($reference);
787         $this->assertSame($result, 0);
789         // Create two aliases and make sure they are returned.
790         $fs->create_file_from_reference($alias1, $repo->id, $reference);
791         $fs->create_file_from_reference($alias2, $repo->id, $reference);
793         $result = $fs->search_references($reference);
794         $this->assertTrue(is_array($result));
795         $this->assertEquals(count($result), 2);
796         foreach ($result as $alias) {
797             $this->assertTrue($alias instanceof stored_file);
798         }
800         $result = $fs->search_references_count($reference);
801         $this->assertSame($result, 2);
803         // The method can't be used for references to files outside the filepool.
804         $exceptionthrown = false;
805         try {
806             $fs->search_references('http://dl.dropbox.com/download/1234567/naked-dougiamas.jpg');
807         } catch (file_reference_exception $e) {
808             $exceptionthrown = true;
809         }
810         $this->assertTrue($exceptionthrown);
812         $exceptionthrown = false;
813         try {
814             $fs->search_references_count('http://dl.dropbox.com/download/1234567/naked-dougiamas.jpg');
815         } catch (file_reference_exception $e) {
816             $exceptionthrown = true;
817         }
818         $this->assertTrue($exceptionthrown);
819     }
821     public function test_delete_area_files() {
822         $user = $this->setup_three_private_files();
823         $fs = get_file_storage();
825         // Get area files with default options.
826         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
827         // Should be the two files we added plus the folder.
828         $this->assertEquals(4, count($areafiles));
829         $fs->delete_area_files($user->ctxid, 'user', 'private');
831         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
832         // Should be the two files we added plus the folder.
833         $this->assertEquals(0, count($areafiles));
834     }
836     public function test_delete_area_files_itemid() {
837         $user = $this->setup_three_private_files();
838         $fs = get_file_storage();
840         // Get area files with default options.
841         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
842         // Should be the two files we added plus the folder.
843         $this->assertEquals(4, count($areafiles));
844         $fs->delete_area_files($user->ctxid, 'user', 'private', 9999);
846         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
847         $this->assertEquals(4, count($areafiles));
848     }
850     public function test_delete_area_files_select() {
851         $user = $this->setup_three_private_files();
852         $fs = get_file_storage();
854         // Get area files with default options.
855         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
856         // Should be the two files we added plus the folder.
857         $this->assertEquals(4, count($areafiles));
858         $fs->delete_area_files_select($user->ctxid, 'user', 'private', '!= :notitemid', array('notitemid'=>9999));
860         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
861         // Should be the two files we added plus the folder.
862         $this->assertEquals(0, count($areafiles));
863     }
865     public function test_delete_component_files() {
866         $user = $this->setup_three_private_files();
867         $fs = get_file_storage();
869         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
870         $this->assertEquals(4, count($areafiles));
871         $fs->delete_component_files('user');
872         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
873         $this->assertEquals(0, count($areafiles));
874     }
876     public function test_create_file_from_url() {
877         $this->resetAfterTest(true);
879         $syscontext = context_system::instance();
880         $filerecord = array(
881             'contextid' => $syscontext->id,
882             'component' => 'core',
883             'filearea'  => 'unittest',
884             'itemid'    => 0,
885             'filepath'  => '/downloadtest/',
886         );
887         $url = $this->getExternalTestFileUrl('/test.html');
889         $fs = get_file_storage();
891         // Test creating file without filename.
892         $file1 = $fs->create_file_from_url($filerecord, $url);
893         $this->assertInstanceOf('stored_file', $file1);
895         // Set filename.
896         $filerecord['filename'] = 'unit-test-filename.html';
897         $file2 = $fs->create_file_from_url($filerecord, $url);
898         $this->assertInstanceOf('stored_file', $file2);
900         // Use temporary file.
901         $filerecord['filename'] = 'unit-test-with-temp-file.html';
902         $file3 = $fs->create_file_from_url($filerecord, $url, null, true);
903         $file3 = $this->assertInstanceOf('stored_file', $file3);
904     }
906     public function test_cron() {
907         $this->resetAfterTest(true);
909         // Note: this is only testing DB compatibility atm, rather than
910         // that work is done.
911         $fs = get_file_storage();
913         $this->expectOutputRegex('/Cleaning up/');
914         $fs->cron();
915     }
917     public function test_is_area_empty() {
918         $user = $this->setup_three_private_files();
919         $fs = get_file_storage();
921         $this->assertFalse($fs->is_area_empty($user->ctxid, 'user', 'private'));
923         // File area with madeup itemid should be empty.
924         $this->assertTrue($fs->is_area_empty($user->ctxid, 'user', 'private', 9999));
925         // Still empty with dirs included.
926         $this->assertTrue($fs->is_area_empty($user->ctxid, 'user', 'private', 9999, false));
927     }
929     public function test_move_area_files_to_new_context() {
930         $this->resetAfterTest(true);
932         // Create a course with a page resource.
933         $course = $this->getDataGenerator()->create_course();
934         $page1 = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
935         $page1context = context_module::instance($page1->cmid);
937         // Add a file to the page.
938         $fs = get_file_storage();
939         $filerecord = array(
940             'contextid' => $page1context->id,
941             'component' => 'mod_page',
942             'filearea'  => 'content',
943             'itemid'    => 0,
944             'filepath'  => '/',
945             'filename'  => 'unit-test-file.txt',
946         );
948         $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
949         $this->assertInstanceOf('stored_file', $originalfile);
951         $pagefiles = $fs->get_area_files($page1context->id, 'mod_page', 'content', 0, 'sortorder', false);
952         // Should be one file in filearea.
953         $this->assertFalse($fs->is_area_empty($page1context->id, 'mod_page', 'content'));
955         // Create a new page.
956         $page2 = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
957         $page2context = context_module::instance($page2->cmid);
959         // Newly created page area is empty.
960         $this->assertTrue($fs->is_area_empty($page2context->id, 'mod_page', 'content'));
962         // Move the files.
963         $fs->move_area_files_to_new_context($page1context->id, $page2context->id, 'mod_page', 'content');
965         // Page2 filearea should no longer be empty.
966         $this->assertFalse($fs->is_area_empty($page2context->id, 'mod_page', 'content'));
968         // Page1 filearea should now be empty.
969         $this->assertTrue($fs->is_area_empty($page1context->id, 'mod_page', 'content'));
971         $page2files = $fs->get_area_files($page2context->id, 'mod_page', 'content', 0, 'sortorder', false);
972         $movedfile = reset($page2files);
974         // The two files should have the same content hash.
975         $this->assertEquals($movedfile->get_contenthash(), $originalfile->get_contenthash());
976     }
978     public function test_convert_image() {
979         global $CFG;
981         $this->resetAfterTest(false);
983         $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
984         $syscontext = context_system::instance();
985         $filerecord = array(
986             'contextid' => $syscontext->id,
987             'component' => 'core',
988             'filearea'  => 'unittest',
989             'itemid'    => 0,
990             'filepath'  => '/images/',
991             'filename'  => 'testimage.jpg',
992         );
994         $fs = get_file_storage();
995         $original = $fs->create_file_from_pathname($filerecord, $filepath);
997         $filerecord['filename'] = 'testimage-converted-10x10.jpg';
998         $converted = $fs->convert_image($filerecord, $original, 10, 10, true, 100);
999         $this->assertInstanceOf('stored_file', $converted);
1001         $filerecord['filename'] = 'testimage-convereted-nosize.jpg';
1002         $converted = $fs->convert_image($filerecord, $original);
1003         $this->assertInstanceOf('stored_file', $converted);
1004     }
1006     public function test_convert_image_png() {
1007         global $CFG;
1009         $this->resetAfterTest(false);
1011         $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.png';
1012         $syscontext = context_system::instance();
1013         $filerecord = array(
1014             'contextid' => $syscontext->id,
1015             'component' => 'core',
1016             'filearea'  => 'unittest',
1017             'itemid'    => 0,
1018             'filepath'  => '/images/',
1019             'filename'  => 'testimage.png',
1020         );
1022         $fs = get_file_storage();
1023         $original = $fs->create_file_from_pathname($filerecord, $filepath);
1025         // Vanilla test.
1026         $filerecord['filename'] = 'testimage-converted-nosize.png';
1027         $vanilla = $fs->convert_image($filerecord, $original);
1028         $this->assertInstanceOf('stored_file', $vanilla);
1029         // Assert that byte 25 has the ascii value 6 for PNG-24.
1030         $this->assertTrue(ord(substr($vanilla->get_content(), 25, 1)) == 6);
1032         // 10x10 resize test; also testing for a ridiculous quality setting, which
1033         // we should if necessary scale to the 0 - 9 range.
1034         $filerecord['filename'] = 'testimage-converted-10x10.png';
1035         $converted = $fs->convert_image($filerecord, $original, 10, 10, true, 100);
1036         $this->assertInstanceOf('stored_file', $converted);
1037         // Assert that byte 25 has the ascii value 6 for PNG-24.
1038         $this->assertTrue(ord(substr($converted->get_content(), 25, 1)) == 6);
1040         // Transparency test.
1041         $filerecord['filename'] = 'testimage-converted-102x31.png';
1042         $converted = $fs->convert_image($filerecord, $original, 102, 31, true, 9);
1043         $this->assertInstanceOf('stored_file', $converted);
1044         // Assert that byte 25 has the ascii value 6 for PNG-24.
1045         $this->assertTrue(ord(substr($converted->get_content(), 25, 1)) == 6);
1047         $originalfile = imagecreatefromstring($original->get_content());
1048         $convertedfile = imagecreatefromstring($converted->get_content());
1049         $vanillafile = imagecreatefromstring($vanilla->get_content());
1051         $originalcolors = imagecolorsforindex($originalfile, imagecolorat($originalfile, 0, 0));
1052         $convertedcolors = imagecolorsforindex($convertedfile, imagecolorat($convertedfile, 0, 0));
1053         $vanillacolors = imagecolorsforindex($vanillafile, imagecolorat($vanillafile, 0, 0));
1054         $this->assertEquals(count($originalcolors), 4);
1055         $this->assertEquals(count($convertedcolors), 4);
1056         $this->assertEquals(count($vanillacolors), 4);
1057         $this->assertEquals($originalcolors['red'], $convertedcolors['red']);
1058         $this->assertEquals($originalcolors['green'], $convertedcolors['green']);
1059         $this->assertEquals($originalcolors['blue'], $convertedcolors['blue']);
1060         $this->assertEquals($originalcolors['alpha'], $convertedcolors['alpha']);
1061         $this->assertEquals($originalcolors['red'], $vanillacolors['red']);
1062         $this->assertEquals($originalcolors['green'], $vanillacolors['green']);
1063         $this->assertEquals($originalcolors['blue'], $vanillacolors['blue']);
1064         $this->assertEquals($originalcolors['alpha'], $vanillacolors['alpha']);
1065         $this->assertEquals($originalcolors['alpha'], 127);
1067     }
1069     private function generate_file_record() {
1070         $syscontext = context_system::instance();
1071         $filerecord = new stdClass();
1072         $filerecord->contextid = $syscontext->id;
1073         $filerecord->component = 'core';
1074         $filerecord->filearea = 'phpunit';
1075         $filerecord->filepath = '/';
1076         $filerecord->filename = 'testfile.txt';
1077         $filerecord->itemid = 0;
1079         return $filerecord;
1080     }
1082     /**
1083      * @expectedException        file_exception
1084      */
1085     public function test_create_file_from_storedfile_file_invalid() {
1086         $this->resetAfterTest(true);
1088         $filerecord = $this->generate_file_record();
1090         $fs = get_file_storage();
1092         // Create a file from a file id which doesn't exist.
1093         $fs->create_file_from_storedfile($filerecord,  9999);
1094     }
1096     /**
1097      * @expectedException        file_exception
1098      * @expectedExceptionMessage Invalid contextid
1099      */
1100     public function test_create_file_from_storedfile_contextid_invalid() {
1101         $this->resetAfterTest(true);
1103         $filerecord = $this->generate_file_record();
1105         $fs = get_file_storage();
1106         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1107         $this->assertInstanceOf('stored_file', $file1);
1109         $filerecord->filename = 'invalid.txt';
1110         $filerecord->contextid = 'invalid';
1112         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1113     }
1115     /**
1116      * @expectedException        file_exception
1117      * @expectedExceptionMessage Invalid component
1118      */
1119     public function test_create_file_from_storedfile_component_invalid() {
1120         $this->resetAfterTest(true);
1122         $filerecord = $this->generate_file_record();
1124         $fs = get_file_storage();
1125         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1126         $this->assertInstanceOf('stored_file', $file1);
1128         $filerecord->filename = 'invalid.txt';
1129         $filerecord->component = 'bad/component';
1131         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1132     }
1134     /**
1135      * @expectedException        file_exception
1136      * @expectedExceptionMessage Invalid filearea
1137      */
1138     public function test_create_file_from_storedfile_filearea_invalid() {
1139         $this->resetAfterTest(true);
1141         $filerecord = $this->generate_file_record();
1143         $fs = get_file_storage();
1144         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1145         $this->assertInstanceOf('stored_file', $file1);
1147         $filerecord->filename = 'invalid.txt';
1148         $filerecord->filearea = 'bad-filearea';
1150         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1151     }
1153     /**
1154      * @expectedException        file_exception
1155      * @expectedExceptionMessage Invalid itemid
1156      */
1157     public function test_create_file_from_storedfile_itemid_invalid() {
1158         $this->resetAfterTest(true);
1160         $filerecord = $this->generate_file_record();
1162         $fs = get_file_storage();
1163         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1164         $this->assertInstanceOf('stored_file', $file1);
1166         $filerecord->filename = 'invalid.txt';
1167         $filerecord->itemid = 'bad-itemid';
1169         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1170     }
1172     /**
1173      * @expectedException        file_exception
1174      * @expectedExceptionMessage Invalid file path
1175      */
1176     public function test_create_file_from_storedfile_filepath_invalid() {
1177         $this->resetAfterTest(true);
1179         $filerecord = $this->generate_file_record();
1181         $fs = get_file_storage();
1182         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1183         $this->assertInstanceOf('stored_file', $file1);
1185         $filerecord->filename = 'invalid.txt';
1186         $filerecord->filepath = 'a-/bad/-filepath';
1188         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1189     }
1191     /**
1192      * @expectedException        file_exception
1193      * @expectedExceptionMessage Invalid file name
1194      */
1195     public function test_create_file_from_storedfile_filename_invalid() {
1196         $this->resetAfterTest(true);
1198         $filerecord = $this->generate_file_record();
1200         $fs = get_file_storage();
1201         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1202         $this->assertInstanceOf('stored_file', $file1);
1204         $filerecord->filename = '';
1206         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1207     }
1209     /**
1210      * @expectedException        file_exception
1211      * @expectedExceptionMessage Invalid file timecreated
1212      */
1213     public function test_create_file_from_storedfile_timecreated_invalid() {
1214         $this->resetAfterTest(true);
1216         $filerecord = $this->generate_file_record();
1218         $fs = get_file_storage();
1219         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1220         $this->assertInstanceOf('stored_file', $file1);
1222         $filerecord->filename = 'invalid.txt';
1223         $filerecord->timecreated = 'today';
1225         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1226     }
1228     /**
1229      * @expectedException        file_exception
1230      * @expectedExceptionMessage Invalid file timemodified
1231      */
1232     public function test_create_file_from_storedfile_timemodified_invalid() {
1233         $this->resetAfterTest(true);
1235         $filerecord = $this->generate_file_record();
1237         $fs = get_file_storage();
1238         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1239         $this->assertInstanceOf('stored_file', $file1);
1241         $filerecord->filename = 'invalid.txt';
1242         $filerecord->timemodified  = 'today';
1244         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1245     }
1247     /**
1248      * @expectedException        stored_file_creation_exception
1249      * @expectedExceptionMessage Can not create file "1/core/phpunit/0/testfile.txt"
1250      */
1251     public function test_create_file_from_storedfile_duplicate() {
1252         $this->resetAfterTest(true);
1254         $filerecord = $this->generate_file_record();
1256         $fs = get_file_storage();
1257         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1258         $this->assertInstanceOf('stored_file', $file1);
1260         // Creating a file validating unique constraint.
1261         $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1262     }
1264     public function test_create_file_from_storedfile() {
1265         $this->resetAfterTest(true);
1267         $syscontext = context_system::instance();
1269         $filerecord = new stdClass();
1270         $filerecord->contextid = $syscontext->id;
1271         $filerecord->component = 'core';
1272         $filerecord->filearea = 'phpunit';
1273         $filerecord->filepath = '/';
1274         $filerecord->filename = 'testfile.txt';
1275         $filerecord->itemid = 0;
1277         $fs = get_file_storage();
1279         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1280         $this->assertInstanceOf('stored_file', $file1);
1282         $filerecord->filename = 'test-create-file-from-storedfile.txt';
1283         $file2 = $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1284         $this->assertInstanceOf('stored_file', $file2);
1286         // These will be normalised to current time..
1287         $filerecord->timecreated = -100;
1288         $filerecord->timemodified= -100;
1289         $filerecord->filename = 'test-create-file-from-storedfile-bad-dates.txt';
1291         $file3 = $fs->create_file_from_storedfile($filerecord, $file1->get_id());
1292         $this->assertInstanceOf('stored_file', $file3);
1294         $this->assertNotEquals($file3->get_timemodified(), $filerecord->timemodified);
1295         $this->assertNotEquals($file3->get_timecreated(), $filerecord->timecreated);
1296     }
1298     /**
1299      * @expectedException        file_exception
1300      * @expectedExceptionMessage Invalid contextid
1301      */
1302     public function test_create_file_from_string_contextid_invalid() {
1303         $this->resetAfterTest(true);
1305         $filerecord = $this->generate_file_record();
1306         $fs = get_file_storage();
1308         $filerecord->contextid = 'invalid';
1310         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1311     }
1313     /**
1314      * @expectedException        file_exception
1315      * @expectedExceptionMessage Invalid component
1316      */
1317     public function test_create_file_from_string_component_invalid() {
1318         $this->resetAfterTest(true);
1320         $filerecord = $this->generate_file_record();
1321         $fs = get_file_storage();
1323         $filerecord->component = 'bad/component';
1325         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1326     }
1328     /**
1329      * @expectedException        file_exception
1330      * @expectedExceptionMessage Invalid filearea
1331      */
1332     public function test_create_file_from_string_filearea_invalid() {
1333         $this->resetAfterTest(true);
1335         $filerecord = $this->generate_file_record();
1336         $fs = get_file_storage();
1338         $filerecord->filearea = 'bad-filearea';
1340         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1341     }
1343     /**
1344      * @expectedException        file_exception
1345      * @expectedExceptionMessage Invalid itemid
1346      */
1347     public function test_create_file_from_string_itemid_invalid() {
1348         $this->resetAfterTest(true);
1350         $filerecord = $this->generate_file_record();
1351         $fs = get_file_storage();
1353         $filerecord->itemid = 'bad-itemid';
1355         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1356     }
1358     /**
1359      * @expectedException        file_exception
1360      * @expectedExceptionMessage Invalid file path
1361      */
1362     public function test_create_file_from_string_filepath_invalid() {
1363         $this->resetAfterTest(true);
1365         $filerecord = $this->generate_file_record();
1366         $fs = get_file_storage();
1368         $filerecord->filepath = 'a-/bad/-filepath';
1370         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1371     }
1373     /**
1374      * @expectedException        file_exception
1375      * @expectedExceptionMessage Invalid file name
1376      */
1377     public function test_create_file_from_string_filename_invalid() {
1378         $this->resetAfterTest(true);
1380         $filerecord = $this->generate_file_record();
1381         $fs = get_file_storage();
1383         $filerecord->filename = '';
1385         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1386     }
1388     /**
1389      * @expectedException        file_exception
1390      * @expectedExceptionMessage Invalid file timecreated
1391      */
1392     public function test_create_file_from_string_timecreated_invalid() {
1393         $this->resetAfterTest(true);
1395         $filerecord = $this->generate_file_record();
1396         $fs = get_file_storage();
1398         $filerecord->timecreated = 'today';
1400         $this->expectException('file_exception');
1401         $this->expectExceptionMessage('Invalid file timecreated');
1402         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1403     }
1405     /**
1406      * @expectedException        file_exception
1407      * @expectedExceptionMessage Invalid file timemodified
1408      */
1409     public function test_create_file_from_string_timemodified_invalid() {
1410         $this->resetAfterTest(true);
1412         $filerecord = $this->generate_file_record();
1413         $fs = get_file_storage();
1415         $filerecord->timemodified  = 'today';
1417         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1418     }
1420     public function test_create_file_from_string_duplicate() {
1421         $this->resetAfterTest(true);
1423         $filerecord = $this->generate_file_record();
1424         $fs = get_file_storage();
1426         $file1 = $fs->create_file_from_string($filerecord, 'text contents');
1428         // Creating a file validating unique constraint.
1429         $this->expectException('stored_file_creation_exception');
1430         $file2 = $fs->create_file_from_string($filerecord, 'text contents');
1431     }
1433     /**
1434      * @expectedException        file_exception
1435      * @expectedExceptionMessage Invalid contextid
1436      */
1437     public function test_create_file_from_pathname_contextid_invalid() {
1438         global $CFG;
1439         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1441         $this->resetAfterTest(true);
1443         $filerecord = $this->generate_file_record();
1444         $fs = get_file_storage();
1446         $filerecord->contextid = 'invalid';
1448         $file1 = $fs->create_file_from_pathname($filerecord, $path);
1449     }
1451     /**
1452      * @expectedException        file_exception
1453      * @expectedExceptionMessage Invalid component
1454      */
1455     public function test_create_file_from_pathname_component_invalid() {
1456         global $CFG;
1457         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1459         $this->resetAfterTest(true);
1461         $filerecord = $this->generate_file_record();
1462         $fs = get_file_storage();
1464         $filerecord->component = 'bad/component';
1466         $file1 = $fs->create_file_from_pathname($filerecord, $path);
1467     }
1469     /**
1470      * @expectedException        file_exception
1471      * @expectedExceptionMessage Invalid filearea
1472      */
1473     public function test_create_file_from_pathname_filearea_invalid() {
1474         global $CFG;
1475         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1477         $this->resetAfterTest(true);
1479         $filerecord = $this->generate_file_record();
1480         $fs = get_file_storage();
1482         $filerecord->filearea = 'bad-filearea';
1484         $file1 = $fs->create_file_from_pathname($filerecord, $path);
1485     }
1487     /**
1488      * @expectedException        file_exception
1489      * @expectedExceptionMessage Invalid itemid
1490      */
1491     public function test_create_file_from_pathname_itemid_invalid() {
1492         global $CFG;
1493         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1495         $this->resetAfterTest(true);
1497         $filerecord = $this->generate_file_record();
1498         $fs = get_file_storage();
1500         $filerecord->itemid = 'bad-itemid';
1502          $file1 = $fs->create_file_from_pathname($filerecord, $path);
1503     }
1505     /**
1506      * @expectedException        file_exception
1507      * @expectedExceptionMessage Invalid file path
1508      */
1509     public function test_create_file_from_pathname_filepath_invalid() {
1510         global $CFG;
1511         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1513         $this->resetAfterTest(true);
1515         $filerecord = $this->generate_file_record();
1516         $fs = get_file_storage();
1518         $filerecord->filepath = 'a-/bad/-filepath';
1520         $file1 = $fs->create_file_from_pathname($filerecord, $path);
1521     }
1523     /**
1524      * @expectedException        file_exception
1525      * @expectedExceptionMessage Invalid file name
1526      */
1527     public function test_create_file_from_pathname_filename_invalid() {
1528         global $CFG;
1529         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1531         $this->resetAfterTest(true);
1533         $filerecord = $this->generate_file_record();
1534         $fs = get_file_storage();
1536         $filerecord->filename = '';
1538         $file1 = $fs->create_file_from_pathname($filerecord, $path);
1539     }
1541     /**
1542      * @expectedException        file_exception
1543      * @expectedExceptionMessage Invalid file timecreated
1544      */
1545     public function test_create_file_from_pathname_timecreated_invalid() {
1546         global $CFG;
1547         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1549         $this->resetAfterTest(true);
1551         $filerecord = $this->generate_file_record();
1552         $fs = get_file_storage();
1554         $filerecord->timecreated = 'today';
1556         $file1 = $fs->create_file_from_pathname($filerecord, $path);
1557     }
1559     /**
1560      * @expectedException        file_exception
1561      * @expectedExceptionMessage Invalid file timemodified
1562      */
1563     public function test_create_file_from_pathname_timemodified_invalid() {
1564         global $CFG;
1565         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1567         $this->resetAfterTest(true);
1569         $filerecord = $this->generate_file_record();
1570         $fs = get_file_storage();
1572         $filerecord->timemodified  = 'today';
1574         $file1 = $fs->create_file_from_pathname($filerecord, $path);
1575     }
1577     /**
1578      * @expectedException        stored_file_creation_exception
1579      * @expectedExceptionMessage Can not create file "1/core/phpunit/0/testfile.txt"
1580      */
1581     public function test_create_file_from_pathname_duplicate_file() {
1582         global $CFG;
1583         $this->resetAfterTest(true);
1585         $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
1587         $filerecord = $this->generate_file_record();
1588         $fs = get_file_storage();
1590         $file1 = $fs->create_file_from_pathname($filerecord, $path);
1591         $this->assertInstanceOf('stored_file', $file1);
1593         // Creating a file validating unique constraint.
1594         $file2 = $fs->create_file_from_pathname($filerecord, $path);
1595     }
1597     /**
1598      * Calling stored_file::delete_reference() on a non-reference file throws coding_exception
1599      */
1600     public function test_delete_reference_on_nonreference() {
1602         $this->resetAfterTest(true);
1603         $user = $this->setup_three_private_files();
1604         $fs = get_file_storage();
1605         $repos = repository::get_instances(array('type'=>'user'));
1606         $repo = reset($repos);
1608         $file = null;
1609         foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
1610             if (!$areafile->is_directory()) {
1611                 $file = $areafile;
1612                 break;
1613             }
1614         }
1615         $this->assertInstanceOf('stored_file', $file);
1616         $this->assertFalse($file->is_external_file());
1618         $this->expectException('coding_exception');
1619         $file->delete_reference();
1620     }
1622     /**
1623      * Calling stored_file::delete_reference() on a reference file does not affect other
1624      * symlinks to the same original
1625      */
1626     public function test_delete_reference_one_symlink_does_not_rule_them_all() {
1628         $this->resetAfterTest(true);
1629         $user = $this->setup_three_private_files();
1630         $fs = get_file_storage();
1631         $repos = repository::get_instances(array('type'=>'user'));
1632         $repo = reset($repos);
1634         // Create two aliases linking the same original.
1636         $originalfile = null;
1637         foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
1638             if (!$areafile->is_directory()) {
1639                 $originalfile = $areafile;
1640                 break;
1641             }
1642         }
1643         $this->assertInstanceOf('stored_file', $originalfile);
1645         // Calling delete_reference() on a non-reference file.
1647         $originalrecord = array(
1648             'contextid' => $originalfile->get_contextid(),
1649             'component' => $originalfile->get_component(),
1650             'filearea'  => $originalfile->get_filearea(),
1651             'itemid'    => $originalfile->get_itemid(),
1652             'filepath'  => $originalfile->get_filepath(),
1653             'filename'  => $originalfile->get_filename(),
1654         );
1656         $aliasrecord = $this->generate_file_record();
1657         $aliasrecord->filepath = '/A/';
1658         $aliasrecord->filename = 'symlink.txt';
1660         $ref = $fs->pack_reference($originalrecord);
1661         $aliasfile1 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
1663         $aliasrecord->filepath = '/B/';
1664         $aliasrecord->filename = 'symlink.txt';
1665         $ref = $fs->pack_reference($originalrecord);
1666         $aliasfile2 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
1668         // Refetch A/symlink.txt file.
1669         $symlink1 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
1670             $aliasrecord->filearea, $aliasrecord->itemid, '/A/', 'symlink.txt');
1671         $this->assertTrue($symlink1->is_external_file());
1673         // Unlink the A/symlink.txt file.
1674         $symlink1->delete_reference();
1675         $this->assertFalse($symlink1->is_external_file());
1677         // Make sure that B/symlink.txt has not been affected.
1678         $symlink2 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
1679             $aliasrecord->filearea, $aliasrecord->itemid, '/B/', 'symlink.txt');
1680         $this->assertTrue($symlink2->is_external_file());
1681     }
1683     /**
1684      * Make sure that when internal file is updated all references to it are
1685      * updated immediately. When it is deleted, the references are converted
1686      * to true copies.
1687      */
1688     public function test_update_reference_internal() {
1689         purge_all_caches();
1690         $this->resetAfterTest(true);
1691         $user = $this->setup_three_private_files();
1692         $fs = get_file_storage();
1693         $repos = repository::get_instances(array('type' => 'user'));
1694         $repo = reset($repos);
1696         // Create two aliases linking the same original.
1698         $areafiles = array_values($fs->get_area_files($user->ctxid, 'user', 'private', false, 'filename', false));
1700         $originalfile = $areafiles[0];
1701         $this->assertInstanceOf('stored_file', $originalfile);
1702         $contenthash = $originalfile->get_contenthash();
1703         $filesize = $originalfile->get_filesize();
1705         $substitutefile = $areafiles[1];
1706         $this->assertInstanceOf('stored_file', $substitutefile);
1707         $newcontenthash = $substitutefile->get_contenthash();
1708         $newfilesize = $substitutefile->get_filesize();
1710         $originalrecord = array(
1711             'contextid' => $originalfile->get_contextid(),
1712             'component' => $originalfile->get_component(),
1713             'filearea'  => $originalfile->get_filearea(),
1714             'itemid'    => $originalfile->get_itemid(),
1715             'filepath'  => $originalfile->get_filepath(),
1716             'filename'  => $originalfile->get_filename(),
1717         );
1719         $aliasrecord = $this->generate_file_record();
1720         $aliasrecord->filepath = '/A/';
1721         $aliasrecord->filename = 'symlink.txt';
1723         $ref = $fs->pack_reference($originalrecord);
1724         $symlink1 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
1725         // Make sure created alias is a reference and has the same size and contenthash as source.
1726         $this->assertEquals($contenthash, $symlink1->get_contenthash());
1727         $this->assertEquals($filesize, $symlink1->get_filesize());
1728         $this->assertEquals($repo->id, $symlink1->get_repository_id());
1729         $this->assertNotEmpty($symlink1->get_referencefileid());
1730         $referenceid = $symlink1->get_referencefileid();
1732         $aliasrecord->filepath = '/B/';
1733         $aliasrecord->filename = 'symlink.txt';
1734         $ref = $fs->pack_reference($originalrecord);
1735         $symlink2 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
1736         // Make sure created alias is a reference and has the same size and contenthash as source.
1737         $this->assertEquals($contenthash, $symlink2->get_contenthash());
1738         $this->assertEquals($filesize, $symlink2->get_filesize());
1739         $this->assertEquals($repo->id, $symlink2->get_repository_id());
1740         // Make sure both aliases have the same reference id.
1741         $this->assertEquals($referenceid, $symlink2->get_referencefileid());
1743         // Overwrite ofiginal file.
1744         $originalfile->replace_file_with($substitutefile);
1745         $this->assertEquals($newcontenthash, $originalfile->get_contenthash());
1746         $this->assertEquals($newfilesize, $originalfile->get_filesize());
1748         // References to the internal files must be synchronised immediately.
1749         // Refetch A/symlink.txt file.
1750         $symlink1 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
1751             $aliasrecord->filearea, $aliasrecord->itemid, '/A/', 'symlink.txt');
1752         $this->assertTrue($symlink1->is_external_file());
1753         $this->assertEquals($newcontenthash, $symlink1->get_contenthash());
1754         $this->assertEquals($newfilesize, $symlink1->get_filesize());
1755         $this->assertEquals($repo->id, $symlink1->get_repository_id());
1756         $this->assertEquals($referenceid, $symlink1->get_referencefileid());
1758         // Refetch B/symlink.txt file.
1759         $symlink2 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
1760             $aliasrecord->filearea, $aliasrecord->itemid, '/B/', 'symlink.txt');
1761         $this->assertTrue($symlink2->is_external_file());
1762         $this->assertEquals($newcontenthash, $symlink2->get_contenthash());
1763         $this->assertEquals($newfilesize, $symlink2->get_filesize());
1764         $this->assertEquals($repo->id, $symlink2->get_repository_id());
1765         $this->assertEquals($referenceid, $symlink2->get_referencefileid());
1767         // Remove original file.
1768         $originalfile->delete();
1770         // References must be converted to independend files.
1771         // Refetch A/symlink.txt file.
1772         $symlink1 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
1773             $aliasrecord->filearea, $aliasrecord->itemid, '/A/', 'symlink.txt');
1774         $this->assertFalse($symlink1->is_external_file());
1775         $this->assertEquals($newcontenthash, $symlink1->get_contenthash());
1776         $this->assertEquals($newfilesize, $symlink1->get_filesize());
1777         $this->assertNull($symlink1->get_repository_id());
1778         $this->assertNull($symlink1->get_referencefileid());
1780         // Refetch B/symlink.txt file.
1781         $symlink2 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
1782             $aliasrecord->filearea, $aliasrecord->itemid, '/B/', 'symlink.txt');
1783         $this->assertFalse($symlink2->is_external_file());
1784         $this->assertEquals($newcontenthash, $symlink2->get_contenthash());
1785         $this->assertEquals($newfilesize, $symlink2->get_filesize());
1786         $this->assertNull($symlink2->get_repository_id());
1787         $this->assertNull($symlink2->get_referencefileid());
1788     }
1790     public function test_get_unused_filename() {
1791         global $USER;
1792         $this->resetAfterTest(true);
1794         $fs = get_file_storage();
1795         $this->setAdminUser();
1796         $contextid = context_user::instance($USER->id)->id;
1797         $component = 'user';
1798         $filearea = 'private';
1799         $itemid = 0;
1800         $filepath = '/';
1802         // Create some private files.
1803         $file = new stdClass;
1804         $file->contextid = $contextid;
1805         $file->component = 'user';
1806         $file->filearea  = 'private';
1807         $file->itemid    = 0;
1808         $file->filepath  = '/';
1809         $file->source    = 'test';
1810         $filenames = array('foo.txt', 'foo (1).txt', 'foo (20).txt', 'foo (999)', 'bar.jpg', 'What (a cool file).jpg',
1811                 'Hurray! (1).php', 'Hurray! (2).php', 'Hurray! (9a).php', 'Hurray! (abc).php');
1812         foreach ($filenames as $key => $filename) {
1813             $file->filename = $filename;
1814             $userfile = $fs->create_file_from_string($file, "file $key $filename content");
1815             $this->assertInstanceOf('stored_file', $userfile);
1816         }
1818         // Asserting new generated names.
1819         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'unused.txt');
1820         $this->assertEquals('unused.txt', $newfilename);
1821         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo.txt');
1822         $this->assertEquals('foo (21).txt', $newfilename);
1823         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (1).txt');
1824         $this->assertEquals('foo (21).txt', $newfilename);
1825         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (2).txt');
1826         $this->assertEquals('foo (2).txt', $newfilename);
1827         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (20).txt');
1828         $this->assertEquals('foo (21).txt', $newfilename);
1829         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo');
1830         $this->assertEquals('foo', $newfilename);
1831         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (123)');
1832         $this->assertEquals('foo (123)', $newfilename);
1833         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'foo (999)');
1834         $this->assertEquals('foo (1000)', $newfilename);
1835         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar.png');
1836         $this->assertEquals('bar.png', $newfilename);
1837         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar (12).png');
1838         $this->assertEquals('bar (12).png', $newfilename);
1839         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar.jpg');
1840         $this->assertEquals('bar (1).jpg', $newfilename);
1841         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'bar (1).jpg');
1842         $this->assertEquals('bar (1).jpg', $newfilename);
1843         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'What (a cool file).jpg');
1844         $this->assertEquals('What (a cool file) (1).jpg', $newfilename);
1845         $newfilename = $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, 'Hurray! (1).php');
1846         $this->assertEquals('Hurray! (3).php', $newfilename);
1848         $this->expectException('coding_exception');
1849         $fs->get_unused_filename($contextid, $component, $filearea, $itemid, $filepath, '');
1850     }
1852     /**
1853      * Test that mimetype_from_file returns appropriate output when the
1854      * file could not be found.
1855      */
1856     public function test_mimetype_not_found() {
1857         $mimetype = file_storage::mimetype('/path/to/nonexistent/file');
1858         $this->assertEquals('document/unknown', $mimetype);
1859     }
1861     /**
1862      * Test that mimetype_from_file returns appropriate output for a known
1863      * file.
1864      *
1865      * Note: this is not intended to check that functions outside of this
1866      * file works. It is intended to validate the codepath contains no
1867      * errors and behaves as expected.
1868      */
1869     public function test_mimetype_known() {
1870         $filepath = __DIR__ . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR . 'testimage.jpg';
1871         $mimetype = file_storage::mimetype_from_file($filepath);
1872         $this->assertEquals('image/jpeg', $mimetype);
1873     }
1875     /**
1876      * Test that mimetype_from_file returns appropriate output when the
1877      * file could not be found.
1878      */
1879     public function test_mimetype_from_file_not_found() {
1880         $mimetype = file_storage::mimetype_from_file('/path/to/nonexistent/file');
1881         $this->assertEquals('document/unknown', $mimetype);
1882     }
1884     /**
1885      * Test that mimetype_from_file returns appropriate output for a known
1886      * file.
1887      *
1888      * Note: this is not intended to check that functions outside of this
1889      * file works. It is intended to validate the codepath contains no
1890      * errors and behaves as expected.
1891      */
1892     public function test_mimetype_from_file_known() {
1893         $filepath = __DIR__ . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR . 'testimage.jpg';
1894         $mimetype = file_storage::mimetype_from_file($filepath);
1895         $this->assertEquals('image/jpeg', $mimetype);
1896     }
1900 class test_stored_file_inspection extends stored_file {
1901     public static function get_pretected_pathname(stored_file $file) {
1902         return $file->get_pathname_by_contenthash();
1903     }