Completed
Push — feature/6.x ( db50a0...aa9894 )
by Schlaefer
03:28
created

testAddFailureFilenameHasNoExtension()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 12
rs 10
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * Saito - The Threaded Web Forum
6
 *
7
 * @copyright Copyright (c) the Saito Project Developers
8
 * @link https://github.com/Schlaefer/Saito
9
 * @license http://opensource.org/licenses/MIT
10
 */
11
12
namespace ImageUploader\Test\TestCase\Controller;
13
14
use Api\Error\Exception\GenericApiException;
15
use Authentication\Authenticator\UnauthenticatedException;
16
use Cake\Core\Configure;
17
use Cake\Core\Plugin;
18
use Cake\Datasource\Exception\RecordNotFoundException;
19
use Cake\Filesystem\File;
20
use Cake\ORM\TableRegistry;
21
use ImageUploader\Model\Table\UploadsTable;
22
use Laminas\Diactoros\UploadedFile;
23
use Saito\Exception\SaitoForbiddenException;
24
use Saito\Test\IntegrationTestCase;
25
26
class UploadsControllerTest extends IntegrationTestCase
27
{
28
    public $fixtures = [
29
        'app.Category',
30
        'app.Entry',
31
        'app.Setting',
32
        'app.User',
33
        'app.UserBlock',
34
        'app.UserIgnore',
35
        'app.UserRead',
36
        'app.UserOnline',
37
        'plugin.ImageUploader.Uploads',
38
    ];
39
40
    /**
41
     * @var File dummy file
42
     */
43
    private $file;
44
45
    public function setUp(): void
46
    {
47
        parent::setUp();
48
49
        Configure::write('Saito.language', 'bzs');
50
51
        $this->file = new File(TMP . 'my new-upload.png');
52
        $this->mockMediaFile($this->file);
53
    }
54
55
    public function tearDown(): void
56
    {
57
        $this->file->delete();
58
        unset($this->file);
59
60
        parent::tearDown();
61
    }
62
63
    public function testAddNotAuthorized()
64
    {
65
        $this->expectException(UnauthenticatedException::class);
66
67
        $this->post('api/v2/uploads', []);
68
    }
69
70
    public function testAddFailureUploadBelongsToDifferentUser()
71
    {
72
        $this->loginJwt(3);
73
74
        $this->expectException(SaitoForbiddenException::class);
75
76
        $this->upload($this->file, 1);
77
    }
78
79
    public function testAddFailureUserDoesNotExist()
80
    {
81
        $this->loginJwt(1);
82
83
        $this->expectException(RecordNotFoundException::class);
84
85
        $this->upload($this->file, 9999);
86
    }
87
88
    /**
89
     * png is successfully uploaded and converted to jpeg
90
     */
91
    public function testAddSuccess()
92
    {
93
        $this->loginJwt(1);
94
95
        $this->upload($this->file, 1);
96
        $response = json_decode((string)$this->_response->getBody(), true);
97
98
        $this->assertResponseCode(200);
99
100
        $this->assertWithinRange(
101
            time(),
102
            strtotime($response['data']['attributes']['created']),
103
            3
104
        );
105
        unset($response['data']['attributes']['created']);
106
107
        $this->assertGreaterThan(0, $response['data']['attributes']['size']);
108
        unset($response['data']['attributes']['size']);
109
110
        $expected = [
111
            'data' => [
112
                'id' => 3,
113
                'type' => 'uploads',
114
                'attributes' => [
115
                    'id' => 3,
116
                    'mime' => 'image/jpeg',
117
                    'name' => '1_ebd536d37aff03f2b570329b20ece832.jpg',
118
                    'thumbnail_url' => 'http://localhost/api/v2/uploads/thumb/3?h=e1fddb2ea8f448fac14ec06b88d4ce94',
119
                    'title' => 'my new-upload.png',
120
                    'url' => 'http://localhost/useruploads/1_ebd536d37aff03f2b570329b20ece832.jpg',
121
                ],
122
            ],
123
        ];
124
        $this->assertEquals($expected, $response);
125
126
        $Uploads = TableRegistry::getTableLocator()->get('ImageUploader.Uploads');
127
        $upload = $Uploads->get(3);
128
129
        $this->assertSame('1_ebd536d37aff03f2b570329b20ece832.jpg', $upload->get('name'));
130
        $this->assertSame('image/jpeg', $upload->get('type'));
131
        $this->assertTrue($upload->get('file')->exists());
132
    }
133
134
    public function testAddSvg()
135
    {
136
        $this->loginJwt(1);
137
138
        $this->file = new File(TMP . 'tmp_svg.svg');
139
        $this->file->write('<?xml version="1.0" encoding="UTF-8" ?>
140
            <svg width="9" height="9" style="background:red;"></svg>');
141
        $this->upload($this->file);
142
143
        $response = json_decode((string)$this->_response->getBody(), true);
144
145
        $this->assertResponseCode(200);
146
147
        $this->assertWithinRange(
148
            time(),
149
            strtotime($response['data']['attributes']['created']),
150
            3
151
        );
152
        unset($response['data']['attributes']['created']);
153
154
        $expected = [
155
            'data' => [
156
                'id' => 3,
157
                'type' => 'uploads',
158
                'attributes' => [
159
                    'id' => 3,
160
                    'mime' => 'image/svg+xml',
161
                    'name' => '1_853fe7aa4ef213b0c11f4b739cf444a8.svg',
162
                    'size' => 108,
163
                    'thumbnail_url' => 'http://localhost/api/v2/uploads/thumb/3?h=1d57b148ad44d4caf90fa1cd98729678',
164
                    'title' => 'tmp_svg.svg',
165
                    'url' => 'http://localhost/useruploads/1_853fe7aa4ef213b0c11f4b739cf444a8.svg',
166
                ],
167
            ],
168
        ];
169
        $this->assertEquals($expected, $response);
170
171
        $Uploads = TableRegistry::getTableLocator()->get('ImageUploader.Uploads');
172
        $upload = $Uploads->get(3);
173
174
        $this->assertSame('1_853fe7aa4ef213b0c11f4b739cf444a8.svg', $upload->get('name'));
175
        $this->assertSame('image/svg+xml', $upload->get('type'));
176
        $this->assertTrue($upload->get('file')->exists());
177
    }
178
179
    public function testAddMimeTypeConversion()
180
    {
181
        $this->loginJwt(1);
182
183
        $this->file = new File(TMP . 'test.mp4');
184
        $fixture = new File(Plugin::path('ImageUploader') . 'tests/Fixture/test-application-octo.mp4');
185
        $fixture->copy($this->file->path);
186
        $this->assertEquals('application/octet-stream', $this->file->mime());
187
188
        $this->upload($this->file);
189
190
        $this->assertResponseOk();
191
192
        $Uploads = TableRegistry::getTableLocator()->get('ImageUploader.Uploads');
193
        $upload = $Uploads->get(3);
194
        $this->assertSame('test.mp4', $upload->get('title'));
195
        $this->assertSame('video/mp4', $upload->get('type'));
196
    }
197
198
    public function testRemoveExifData()
199
    {
200
        $this->loginJwt(1);
201
        unset($this->file);
202
        $this->file = new File(TMP . 'tmp_exif.jpg');
203
204
        $fixture = new File($path = Plugin::path('ImageUploader') . 'tests/Fixture/exif-with-location.jpg');
205
        $fixture->copy($this->file->path);
206
207
        $readExif = function (File $file) {
208
            //@codingStandardsIgnoreStart
209
            return @exif_read_data($file->path);
210
            //@codingStandardsIgnoreEnd
211
        };
212
        $exif = $readExif($this->file);
213
        $this->assertNotEmpty($exif['SectionsFound']);
214
        $this->assertStringContainsString('EXIF', $exif['SectionsFound']);
215
        $this->assertStringContainsString('IFD0', $exif['SectionsFound']);
216
217
        $this->upload($this->file);
218
219
        $response = json_decode((string)$this->_response->getBody(), true);
220
221
        $this->assertResponseCode(200);
222
223
        $Uploads = TableRegistry::getTableLocator()->get('ImageUploader.Uploads');
224
        $upload = $Uploads->find('all')->last();
225
226
        $exif = $readExif($upload->get('file'));
227
        $this->assertStringNotContainsString('EXIF', $exif['SectionsFound']);
228
        $this->assertStringNotContainsString('IFD0', $exif['SectionsFound']);
229
    }
230
231
    public function testAddFailureMaxUploadsPerUser()
232
    {
233
        Configure::read('Saito.Settings.uploader')->setMaxNumberOfUploadsPerUser(1);
234
        $this->loginJwt(1);
235
236
        $Uploads = TableRegistry::getTableLocator()->get('ImageUploader.Uploads');
237
        $count = $Uploads->find()->count();
238
239
        $this->expectException(GenericApiException::class);
240
        $this->expectExceptionMessage('validation.error.maxNumberOfItems');
241
242
        $this->upload($this->file);
243
244
        $this->assertEquals($count, $Uploads->find()->count());
245
    }
246
247
    public function testAddFailureMaxDocumentSize()
248
    {
249
        Configure::read('Saito.Settings.uploader')
250
            ->setMaxNumberOfUploadsPerUser(10)
251
            ->addType('image/png', 10);
252
253
        $this->loginJwt(1);
254
255
        $Uploads = TableRegistry::getTableLocator()->get('ImageUploader.Uploads');
256
        $count = $Uploads->find()->count();
257
258
        $this->expectException(GenericApiException::class);
259
        $this->expectExceptionMessage('validation.error.fileSize');
260
261
        $this->upload($this->file);
262
263
        $this->assertEquals($count, $Uploads->find()->count());
264
    }
265
266
    public function testAddFailureDoubleUpload()
267
    {
268
        $this->loginJwt(1);
269
        // Make sure to test a file that may get transformed on upload (e.g. PNG
270
        // to JEPG).
271
        $file = new File(TMP . 'my new-upload.png');
272
        $this->mockMediaFile($file);
273
        $this->upload($file);
274
275
        $this->expectException(GenericApiException::class);
276
        $this->expectExceptionCode(400);
277
        $this->expectExceptionMessage('validation.error.fileExists');
278
279
        $this->loginJwt(1);
280
        $this->upload($file);
281
282
        $file->delete();
283
    }
284
285
    public function testAddFailureFilenameToLong()
286
    {
287
        $this->loginJwt(1);
288
        $max = UploadsTable::FILENAME_MAXLENGTH;
289
        $file = new File(TMP . str_pad('', $max + 1, '0') . '.png');
290
        $this->mockMediaFile($file);
291
292
        $this->expectException(GenericApiException::class);
293
        $this->expectExceptionCode(400);
294
        $this->expectExceptionMessage('vld.uploads.title.maxlength');
295
        $this->upload($file);
296
297
        $file->delete();
298
    }
299
300
    public function testAddFailureFilenameHasNoExtension()
301
    {
302
        $this->loginJwt(1);
303
        $pathWithoutExtension = TMP . 'file';
304
        $this->file->copy($pathWithoutExtension);
305
        $file = new File($pathWithoutExtension);
306
307
        $this->expectException(GenericApiException::class);
308
        $this->expectExceptionMessage('add.failure.noext');
309
        $this->upload($file);
310
311
        $file->delete();
312
    }
313
314
    public function testAddFailureFileDidntMakeIt()
315
    {
316
        $this->loginJwt(1);
317
        $this->expectException(GenericApiException::class);
318
        $this->expectExceptionMessage('add.failure');
319
        $this->post('api/v2/uploads.json', []);
320
    }
321
322
    public function testIndexNoAuthorization()
323
    {
324
        $this->expectException(UnauthenticatedException::class);
325
326
        $this->get('api/v2/uploads');
327
    }
328
329
    public function testIndexFailureUploadBelongsToDifferentUser()
330
    {
331
        $this->loginJwt(3);
332
333
        $this->expectException(SaitoForbiddenException::class);
334
335
        $this->get('api/v2/uploads/?id=1');
336
    }
337
338
    public function testIndexSuccess()
339
    {
340
        $this->loginJwt(3);
341
342
        $this->get('api/v2/uploads?id=3');
343
344
        $response = json_decode((string)$this->_response->getBody(), true);
345
346
        $this->assertResponseCode(200);
347
348
        $this->assertEquals(
349
            1526404380,
350
            strtotime($response['data'][0]['attributes']['created'])
351
        );
352
        unset($response['data'][0]['attributes']['created']);
353
354
        $expected = [
355
            'data' => [
356
                [
357
                    'id' => 2,
358
                    'type' => 'uploads',
359
                    'attributes' => [
360
                        'id' => 2,
361
                        'mime' => 'image/jpeg',
362
                        'name' => '3-another-upload.jpg',
363
                        'size' => 50000,
364
                        'thumbnail_url' => 'http://localhost/api/v2/uploads/thumb/2?h=be7ef71551c4245f82223d0c8e652eee',
365
                        'title' => '3-another-upload.jpg',
366
                        'url' => 'http://localhost/useruploads/3-another-upload.jpg',
367
                    ],
368
                ],
369
            ],
370
        ];
371
        $this->assertEquals($expected, $response);
372
    }
373
374
    public function testDeleteNoAuthorization()
375
    {
376
        $this->expectException(UnauthenticatedException::class);
377
378
        $this->delete('api/v2/uploads/1');
379
    }
380
381
    public function testDeleteFailureUploadBelongsToDifferentUser()
382
    {
383
        $this->loginJwt(3);
384
385
        $this->expectException(SaitoForbiddenException::class);
386
387
        $this->delete('api/v2/uploads/1');
388
    }
389
390
    public function testUploadDeleteFailureNotFound()
391
    {
392
        $this->loginJwt(1);
393
        $this->expectException(RecordNotFoundException::class);
394
        $this->delete('api/v2/uploads/9999');
395
    }
396
397
    public function testDeleteSuccess()
398
    {
399
        $this->loginJwt(1);
400
        $Uploads = TableRegistry::getTableLocator()->get('ImageUploader.Uploads');
401
        $upload = $Uploads->get(1);
402
        $this->assertNotEmpty($Uploads->get(1));
403
        $this->mockMediaFile($upload->get('file'));
404
405
        $this->delete('api/v2/uploads/1');
406
407
        $response = json_decode((string)$this->_response->getBody(), true);
408
409
        $this->assertResponseCode(204);
410
411
        $this->assertFalse($Uploads->exists(1));
412
    }
413
414
    /**
415
     * Sends a file to upload api
416
     *
417
     * @param File $file The file to send
418
     * @param mixed $userId The user-ID to upload to
419
     */
420
    private function upload(File $file, $userId = 1)
421
    {
422
        $data = [
423
            'upload' => [
424
                0 => [
425
                    'file' => new UploadedFile(
426
                        $file->path,
427
                        $file->size(),
428
                        UPLOAD_ERR_OK,
429
                        $file->name() . ($file->ext() ? '.' . $file->ext() : ''),
430
                        $file->mime(),
431
                    ),
432
                ],
433
            ],
434
        ];
435
        if ($userId) {
436
            $data['userId'] = (string)$userId;
437
        }
438
        $this->post('api/v2/uploads.json', $data);
439
    }
440
}
441