Completed
Pull Request — master (#289)
by Damian
02:03
created

AssetAdminTest::testItFiltersByParentInSearch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
rs 9.2
cc 1
eloc 16
nc 1
nop 0
1
<?php
2
3
namespace SilverStripe\AssetAdmin\Tests\Controller;
4
5
use SilverStripe\AssetAdmin\Tests\Controller\AssetAdminTest\FileExtension;
6
use SilverStripe\AssetAdmin\Tests\Controller\AssetAdminTest\FolderExtension;
7
use SilverStripe\Assets\File;
8
use SilverStripe\Assets\Folder;
9
use SilverStripe\Control\Director;
10
use SilverStripe\Control\Session;
11
use SilverStripe\Dev\FunctionalTest;
12
use SilverStripe\ORM\Versioning\Versioned;
13
use SilverStripe\Security\SecurityToken;
14
use AssetStoreTest_SpyStore;
15
16
/**
17
 * Tests {@see AssetAdmin}
18
 */
19
class AssetAdminTest extends FunctionalTest
20
{
21
22
    protected static $fixture_file = 'AssetAdminTest.yml';
23
24
    /**
25
     * @var Session
26
     */
27
    protected $session = null;
28
29
    public function setUp()
30
    {
31
        parent::setUp();
32
33
        AssetStoreTest_SpyStore::activate('AssetAdminTest');
34
        $memberID = $this->logInWithPermission('ADMIN');
35
        $this->session = Session::create(array('loggedInAs' => $memberID));
36
37
        File::add_extension(FileExtension::class);
38
        Folder::add_extension(FolderExtension::class);
39
40
        // Create a test folders for each of the fixture references
41
        foreach (File::get()->filter('ClassName', Folder::class) as $folder) {
42
            /** @var Folder $folder */
43
            $folder->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
44
        }
45
46
        // Create a test files for each of the fixture references
47
        $content = str_repeat('x', 1000000);
48
        foreach (File::get()->exclude('ClassName', Folder::class) as $file) {
49
            /** @var File $file */
50
            $file->setFromString($content, $file->generateFilename());
51
            $file->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
52
        }
53
54
        // Override FunctionalTest defaults
55
        SecurityToken::enable();
56
        $this->session->inst_set('SecurityID', SecurityToken::inst()->getValue());
57
    }
58
59
    public function tearDown()
60
    {
61
        File::remove_extension(FileExtension::class);
62
        Folder::remove_extension(FolderExtension::class);
63
64
        AssetStoreTest_SpyStore::reset();
65
        parent::tearDown();
66
    }
67
68
    public function testItCreatesFolder()
69
    {
70
        $folder1 = $this->objFromFixture(Folder::class, 'folder1');
71
72
        $response = Director::test(
73
            'admin/assets/api/createFolder',
74
            [
75
                'ParentID' => $folder1->ID,
76
                'Name' => 'testItCreatesFolder',
77
                'SecurityID' => SecurityToken::inst()->getValue(),
78
            ],
79
            $this->session,
80
            'POST'
81
        );
82
        $this->assertFalse($response->isError());
83
        $responseData = json_decode($response->getBody(), true);
84
        $newFolder = Folder::get()->byID($responseData['id']);
85
        $this->assertNotNull($newFolder);
86
        $this->assertEquals($folder1->ID, $newFolder->ParentID);
87
        $this->assertEquals('testItCreatesFolder', $newFolder->Name);
88
    }
89
90 View Code Duplication
    public function testItRestrictsCreateFolderByCanCreate()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
91
    {
92
        $folder = $this->objFromFixture(Folder::class, 'folder1');
93
94
        $response = Director::test(
95
            'admin/assets/api/createFolder',
96
            [
97
                'ParentID' => $folder->ID,
98
                'Name' => 'disallowCanCreate',
99
                'SecurityID' => SecurityToken::inst()->getValue(),
100
            ],
101
            $this->session,
102
            'POST'
103
        );
104
        $this->assertTrue($response->isError());
105
        $this->assertEquals(403, $response->getStatusCode());
106
    }
107
108 View Code Duplication
    public function testItRestrictsCreateFolderByCanAddChildren()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
109
    {
110
        $folder = $this->objFromFixture(Folder::class, 'disallowCanAddChildren');
111
112
        $response = Director::test(
113
            'admin/assets/api/createFolder',
114
            [
115
                'ParentID' => $folder->ID,
116
                'Name' => 'testItRestrictsCreateFolderByCanAddChildren',
117
                'SecurityID' => SecurityToken::inst()->getValue(),
118
            ],
119
            $this->session,
120
            'POST'
121
        );
122
        $this->assertTrue($response->isError());
123
        $this->assertEquals(403, $response->getStatusCode());
124
    }
125
126
    public function testItCreatesFile()
0 ignored issues
show
Coding Style introduced by
testItCreatesFile uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
127
    {
128
        $folder1 = $this->objFromFixture(Folder::class, 'folder1');
129
130
        /** @skipUpgrade */
131
        $fileData = array('Upload' => $this->getUploadFile('Upload', 'testItCreatesFile.txt'));
132
        $_FILES = $fileData;
133
        $postedData = array_merge(
134
            $fileData,
135
            [
136
                    'ParentID' => $folder1->ID,
137
                    'SecurityID' => SecurityToken::inst()->getValue(),
138
                ]
139
        );
140
        $response = Director::test(
141
            'admin/assets/api/createFile',
142
            $postedData,
143
            $this->session,
144
            'POST'
145
        );
146
        $this->assertFalse($response->isError());
147
        $responseData = json_decode($response->getBody(), true);
148
        $newFile = File::get()->byID($responseData[0]['id']);
149
        $this->assertNotNull($newFile);
150
        $this->assertEquals($folder1->ID, $newFile->ParentID);
151
        $this->assertEquals('testItCreatesFile.txt', $newFile->Name);
152
153
        // Test that duplicate uploads are renamed
154
        $response = Director::test(
155
            'admin/assets/api/createFile',
156
            $postedData,
157
            $this->session,
158
            'POST'
159
        );
160
        $this->assertFalse($response->isError());
161
        $responseData = json_decode($response->getBody(), true);
162
        $newFile2 = File::get()->byID($responseData[0]['id']);
163
        $this->assertNotNull($newFile2);
164
        $this->assertEquals($folder1->ID, $newFile2->ParentID);
165
        $this->assertNotEquals($newFile->ID, $newFile2->ID);
166
        $this->assertEquals('testItCreatesFile-v2.txt', $newFile2->Name);
167
    }
168
169 View Code Duplication
    public function testItRestrictsCreateFileOnCanCreate()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
testItRestrictsCreateFileOnCanCreate uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
170
    {
171
        $folder = $this->objFromFixture(Folder::class, 'folder1');
172
173
        $fileData = array('Upload' => $this->getUploadFile('Upload', 'disallowCanCreate.txt'));
174
        $_FILES = $fileData;
175
        $response = Director::test(
176
            'admin/assets/api/createFile',
177
            array_merge(
178
                $fileData,
179
                [
180
                    'ParentID' => $folder->ID,
181
                    'SecurityID' => SecurityToken::inst()->getValue(),
182
                ]
183
            ),
184
            $this->session,
185
            'POST'
186
        );
187
        $this->assertTrue($response->isError());
188
        $this->assertEquals(403, $response->getStatusCode());
189
    }
190
191 View Code Duplication
    public function testItRestrictsCreateFileOnCanAddChildren()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
testItRestrictsCreateFileOnCanAddChildren uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
192
    {
193
        $folder = $this->objFromFixture(Folder::class, 'disallowCanAddChildren');
194
195
        /** @skipUpgrade */
196
        $fileData = array('Upload' => $this->getUploadFile('Upload', 'test.txt'));
197
        $_FILES = $fileData;
198
        $response = Director::test(
199
            'admin/assets/api/createFile',
200
            array_merge(
201
                $fileData,
202
                [
203
                    'ParentID' => $folder->ID,
204
                    'SecurityID' => SecurityToken::inst()->getValue(),
205
                ]
206
            ),
207
            $this->session,
208
            'POST'
209
        );
210
        $this->assertTrue($response->isError());
211
        $this->assertEquals(403, $response->getStatusCode());
212
    }
213
214
    public function testItRestrictsCreateFileOnExtension()
0 ignored issues
show
Coding Style introduced by
testItRestrictsCreateFileOnExtension uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
215
    {
216
        $folder1 = $this->objFromFixture(
217
            Folder::class,
218
            'folder1'
219
        );
220
221
        /** @skipUpgrade */
222
        $fileData = array('Upload' => $this->getUploadFile('Upload', 'disallowed.php'));
223
        $_FILES = $fileData;
224
        $response = Director::test(
225
            'admin/assets/api/createFile',
226
            array_merge(
227
                $fileData,
228
                [
229
                    'ParentID' => $folder1->ID,
230
                    'SecurityID' => SecurityToken::inst()->getValue(),
231
                ]
232
            ),
233
            $this->session,
234
            'POST'
235
        );
236
        $this->assertTrue($response->isError());
237
        $this->assertEquals(400, $response->getStatusCode());
238
        $responseData = json_decode($response->getBody(), true);
239
        $this->assertContains(
240
            'Extension is not allowed',
241
            $responseData['error'][0]
242
        );
243
    }
244
245
    public function testItFiltersByDateInSearch()
246
    {
247
        $file1 = $this->objFromFixture(File::class, 'file1');
248
        $file2 = $this->objFromFixture(File::class, 'file2');
249
250
        // Force creation times
251
        $file1->Created = '2014-01-05 23:11:39';
252
        $file1->write();
253
        $file2->Created = '2014-01-06 12:00:00';
254
        $file2->write();
255
256
        // Mock searches for 4th Jan
257
        $results = $this->getResultsForSearch([
258
            'CreatedFrom' => '2014-01-04',
259
            'CreatedTo' => '2014-01-04'
260
        ]);
261
        $this->assertEquals(count($results['files']), 0);
262
263
        // Mock searches for 5th Jan
264
        $results = $this->getResultsForSearch([
265
            'CreatedFrom' => '2014-01-05',
266
            'CreatedTo' => '2014-01-05'
267
        ]);
268
        $this->assertEquals(count($results['files']), 1);
269
        $this->assertContains($file1->ID, array_column($results['files'], 'id'));
270
271
272
        // Mock searches for 5th-6th Jan
273
        $results = $this->getResultsForSearch([
274
            'CreatedFrom' => '2014-01-05',
275
            'CreatedTo' => '2014-01-06'
276
        ]);
277
        $this->assertEquals(count($results['files']), 2);
278
        $this->assertContains($file1->ID, array_column($results['files'], 'id'));
279
        $this->assertContains($file2->ID, array_column($results['files'], 'id'));
280
281
        // Mock searches for 6th Jan
282
        $results = $this->getResultsForSearch([
283
            'CreatedFrom' => '2014-01-06',
284
            'CreatedTo' => '2014-01-06'
285
        ]);
286
        $this->assertEquals(count($results['files']), 1);
287
        $this->assertContains($file2->ID, array_column($results['files'], 'id'));
288
289
        // Mock searches for 7th Jan
290
        $results = $this->getResultsForSearch([
291
            'CreatedFrom' => '2014-01-07',
292
            'CreatedTo' => '2014-01-07'
293
        ]);
294
        $this->assertEquals(count($results['files']), 0);
295
    }
296
297
298
    public function testItDoesNotFilterByDefaultInSearch()
299
    {
300
        $rootfile = $this->objFromFixture(File::class, 'rootfile');
301
        $file1 = $this->objFromFixture(File::class, 'file1');
302
        $folder1 = $this->objFromFixture(Folder::class, 'folder1');
303
304
        $results = $this->getResultsForSearch();
305
        $this->assertContains(
306
            $rootfile->ID,
307
            array_column($results['files'], 'id'),
308
            'Contains top level file'
309
        );
310
        $this->assertContains(
311
            $folder1->ID,
312
            array_column($results['files'], 'id'),
313
            'Contains top level folder'
314
        );
315
        $this->assertContains(
316
            $file1->ID,
317
            array_column($results['files'], 'id'),
318
            'Contains files in subfolder'
319
        );
320
    }
321
322
    public function testItFiltersByParentInSearch()
323
    {
324
        $file1 = $this->objFromFixture(File::class, 'file1');
325
        $file2 = $this->objFromFixture(File::class, 'file2');
326
        $file1Folder = $file1->Parent();
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on SilverStripe\ORM\DataObject. Did you maybe mean parentClass()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
327
        $file2Folder = $file2->Parent();
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on SilverStripe\ORM\DataObject. Did you maybe mean parentClass()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
328
329
        $results = $this->getResultsForSearch(['Name' => $file1->Name, 'ParentID' => $file1Folder->ID]);
330
        $this->assertEquals(count($results['files']), 1);
331
        $this->assertContains(
332
            $file1->ID,
333
            array_column($results['files'], 'id'),
334
            'Returns file when contained in correct folder'
335
        );
336
337
        $results = $this->getResultsForSearch(['Name' => $file1->Name, 'ParentID' => $file2Folder->ID]);
338
        $this->assertEquals(
339
            count($results['files']),
340
            0,
341
            'Does not return file when contained in different folder'
342
        );
343
    }
344
345
    public function testItFiltersByNameInSearch()
346
    {
347
        $file1 = $this->objFromFixture(File::class, 'file1');
348
349
        $results = $this->getResultsForSearch(['Name' => $file1->Name]);
350
        $this->assertEquals(
351
            count($results['files']),
352
            1,
353
            'Finds by Name property'
354
        );
355
        $this->assertContains($file1->ID, array_column($results['files'], 'id'));
356
357
        $results = $this->getResultsForSearch(['Name' => 'First']);
358
        $this->assertEquals(
359
            count($results['files']),
360
            1,
361
            'Finds by Title property'
362
        );
363
        $this->assertContains($file1->ID, array_column($results['files'], 'id'));
364
    }
365
366
    public function testItRestrictsViewInSearch()
367
    {
368
        $allowedFile = $this->objFromFixture(File::class, 'file1');
369
        $disallowedFile = $this->objFromFixture(File::class, 'disallowCanView');
370
371
        $results = $this->getResultsForSearch(['Name' => $allowedFile->Name]);
372
        $this->assertEquals(count($results['files']), 1);
373
        $this->assertContains($allowedFile->ID, array_column($results['files'], 'id'));
374
375
        $results = $this->getResultsForSearch(['Name' => $disallowedFile->Name]);
376
        $this->assertEquals(count($results['files']), 0);
377
    }
378
379
    public function testItRestrictsViewInReadFolder()
380
    {
381
        $folder1 = $this->objFromFixture(Folder::class, 'folder1');
382
        $allowedFile = $this->objFromFixture(File::class, 'file1');
383
        $disallowedFile = $this->objFromFixture(File::class, 'disallowCanView');
384
385
        $response = $this->get('admin/assets/api/readFolder?' . http_build_query(['id' => $folder1->ID]));
386
        $files = json_decode($response->getBody(), true);
387
        $this->assertArrayHasKey('files', $files);
388
        $ids = array_map(function ($file) {
389
            return $file['id'];
390
        }, $files['files']);
391
        $this->assertContains($allowedFile->ID, $ids);
392
        $this->assertEquals($allowedFile->ParentID, $folder1->ID);
393
        $this->assertNotContains($disallowedFile->ID, $ids);
394
        $this->assertEquals($disallowedFile->ParentID, $folder1->ID);
395
    }
396
397
    public function testItRestrictsUpdateFile()
398
    {
399
        $allowedFile = $this->objFromFixture(File::class, 'file1');
400
        $disallowedFile = $this->objFromFixture(File::class, 'disallowCanEdit');
401
402
        $response = Director::test(
403
            'admin/assets/FileEditForm',
404
            [
405
                'action_save' => 1,
406
                'ID' => $allowedFile->ID,
407
                'Name' => 'disallowCanEdit.txt',
408
                'Title' => 'new',
409
                'SecurityID' => SecurityToken::inst()->getValue(),
410
            ],
411
            $this->session
412
        );
413
        $this->assertFalse($response->isError());
414
415
        $response = Director::test(
416
            'admin/assets/FileEditForm',
417
            [
418
                'action_save' => 1,
419
                'ID' => $disallowedFile->ID,
420
                'Title' => 'new',
421
                'SecurityID' => SecurityToken::inst()->getValue(),
422
            ],
423
            $this->session
424
        );
425
        $this->assertTrue($response->isError());
426
    }
427
428
    public function testItRestrictsDelete()
429
    {
430
        $allowedFile = $this->objFromFixture(File::class, 'file1');
431
        $disallowedFile = $this->objFromFixture(
432
            File::class,
433
            'disallowCanDelete'
434
        );
435
436
        $response = Director::test(
437
            'admin/assets/api/delete',
438
            null,
439
            $this->session,
440
            'DELETE',
441
            http_build_query([
442
                'ids' => [$allowedFile->ID, $disallowedFile->ID],
443
                'SecurityID' => SecurityToken::inst()->getValue(),
444
            ])
445
        );
446
        $this->assertTrue($response->isError());
447
448
        $response = Director::test(
449
            'admin/assets/api/delete',
450
            null,
451
            $this->session,
452
            'DELETE',
453
            http_build_query([
454
                'ids' => [$allowedFile->ID],
455
                'SecurityID' => SecurityToken::inst()->getValue(),
456
            ])
457
        );
458
        $this->assertFalse($response->isError());
459
    }
460
461
    /**
462
     * @param array $params
463
     * @return array
464
     */
465
    protected function getResultsForSearch($params = array())
466
    {
467
        $response = $this->get('admin/assets/api/search?' . http_build_query($params));
468
        $this->assertFalse($response->isError());
469
470
        return json_decode($response->getBody(), true);
471
    }
472
473
    /**
474
     * @param string $paramName
475
     * @param string $tmpFileName
476
     * @return array Emulating an entry in the $_FILES superglobal
477
     */
478
    protected function getUploadFile($paramName, $tmpFileName = 'AssetAdminTest.txt')
0 ignored issues
show
Unused Code introduced by
The parameter $paramName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
479
    {
480
        $tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName;
481
        $tmpFileContent = '';
482
        for ($i = 0; $i < 10000; $i++) {
483
            $tmpFileContent .= '0';
484
        }
485
        file_put_contents($tmpFilePath, $tmpFileContent);
486
487
        // emulates the $_FILES array
488
        return array(
489
            'name' => $tmpFileName,
490
            'type' => 'text/plaintext',
491
            'size' => filesize($tmpFilePath),
492
            'tmp_name' => $tmpFilePath,
493
            'error' => UPLOAD_ERR_OK,
494
        );
495
    }
496
}
497