Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

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