Proyectos de Subversion Moodle

Rev

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