1<?php
2namespace GuzzleHttp\Tests\Psr7;
3
4use ReflectionProperty;
5use GuzzleHttp\Psr7\Stream;
6use GuzzleHttp\Psr7\UploadedFile;
7
8/**
9 * @covers GuzzleHttp\Psr7\UploadedFile
10 */
11class UploadedFileTest extends BaseTest
12{
13    private $cleanup;
14
15    protected function setUp()
16    {
17        $this->cleanup = [];
18    }
19
20    protected function tearDown()
21    {
22        foreach ($this->cleanup as $file) {
23            if (is_scalar($file) && file_exists($file)) {
24                unlink($file);
25            }
26        }
27    }
28
29    public function invalidStreams()
30    {
31        return [
32            'null'         => [null],
33            'true'         => [true],
34            'false'        => [false],
35            'int'          => [1],
36            'float'        => [1.1],
37            'array'        => [['filename']],
38            'object'       => [(object) ['filename']],
39        ];
40    }
41
42    /**
43     * @dataProvider invalidStreams
44     */
45    public function testRaisesExceptionOnInvalidStreamOrFile($streamOrFile)
46    {
47        $this->expectException('InvalidArgumentException');
48
49        new UploadedFile($streamOrFile, 0, UPLOAD_ERR_OK);
50    }
51
52    public function invalidSizes()
53    {
54        return [
55            'null'   => [null],
56            'float'  => [1.1],
57            'array'  => [[1]],
58            'object' => [(object) [1]],
59        ];
60    }
61
62    /**
63     * @dataProvider invalidSizes
64     */
65    public function testRaisesExceptionOnInvalidSize($size)
66    {
67        $this->expectException('InvalidArgumentException', 'size');
68
69        new UploadedFile(fopen('php://temp', 'wb+'), $size, UPLOAD_ERR_OK);
70    }
71
72    public function invalidErrorStatuses()
73    {
74        return [
75            'null'     => [null],
76            'true'     => [true],
77            'false'    => [false],
78            'float'    => [1.1],
79            'string'   => ['1'],
80            'array'    => [[1]],
81            'object'   => [(object) [1]],
82            'negative' => [-1],
83            'too-big'  => [9],
84        ];
85    }
86
87    /**
88     * @dataProvider invalidErrorStatuses
89     */
90    public function testRaisesExceptionOnInvalidErrorStatus($status)
91    {
92        $this->expectException('InvalidArgumentException', 'status');
93
94        new UploadedFile(fopen('php://temp', 'wb+'), 0, $status);
95    }
96
97    public function invalidFilenamesAndMediaTypes()
98    {
99        return [
100            'true'   => [true],
101            'false'  => [false],
102            'int'    => [1],
103            'float'  => [1.1],
104            'array'  => [['string']],
105            'object' => [(object) ['string']],
106        ];
107    }
108
109    /**
110     * @dataProvider invalidFilenamesAndMediaTypes
111     */
112    public function testRaisesExceptionOnInvalidClientFilename($filename)
113    {
114        $this->expectException('InvalidArgumentException', 'filename');
115
116        new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, $filename);
117    }
118
119    /**
120     * @dataProvider invalidFilenamesAndMediaTypes
121     */
122    public function testRaisesExceptionOnInvalidClientMediaType($mediaType)
123    {
124        $this->expectException('InvalidArgumentException', 'media type');
125
126        new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, 'foobar.baz', $mediaType);
127    }
128
129    public function testGetStreamReturnsOriginalStreamObject()
130    {
131        $stream = new Stream(fopen('php://temp', 'r'));
132        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
133
134        $this->assertSame($stream, $upload->getStream());
135    }
136
137    public function testGetStreamReturnsWrappedPhpStream()
138    {
139        $stream = fopen('php://temp', 'wb+');
140        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
141        $uploadStream = $upload->getStream()->detach();
142
143        $this->assertSame($stream, $uploadStream);
144    }
145
146    public function testGetStreamReturnsStreamForFile()
147    {
148        $this->cleanup[] = $stream = tempnam(sys_get_temp_dir(), 'stream_file');
149        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
150        $uploadStream = $upload->getStream();
151        $r = new ReflectionProperty($uploadStream, 'filename');
152        $r->setAccessible(true);
153
154        $this->assertSame($stream, $r->getValue($uploadStream));
155    }
156
157    public function testSuccessful()
158    {
159        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
160        $upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain');
161
162        $this->assertEquals($stream->getSize(), $upload->getSize());
163        $this->assertEquals('filename.txt', $upload->getClientFilename());
164        $this->assertEquals('text/plain', $upload->getClientMediaType());
165
166        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'successful');
167        $upload->moveTo($to);
168        $this->assertFileExists($to);
169        $this->assertEquals($stream->__toString(), file_get_contents($to));
170    }
171
172    public function invalidMovePaths()
173    {
174        return [
175            'null'   => [null],
176            'true'   => [true],
177            'false'  => [false],
178            'int'    => [1],
179            'float'  => [1.1],
180            'empty'  => [''],
181            'array'  => [['filename']],
182            'object' => [(object) ['filename']],
183        ];
184    }
185
186    /**
187     * @dataProvider invalidMovePaths
188     */
189    public function testMoveRaisesExceptionForInvalidPath($path)
190    {
191        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
192        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
193
194        $this->cleanup[] = $path;
195
196        $this->expectException('InvalidArgumentException', 'path');
197        $upload->moveTo($path);
198    }
199
200    public function testMoveCannotBeCalledMoreThanOnce()
201    {
202        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
203        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
204
205        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
206        $upload->moveTo($to);
207        $this->assertFileExists($to);
208
209        $this->expectException('RuntimeException', 'moved');
210        $upload->moveTo($to);
211    }
212
213    public function testCannotRetrieveStreamAfterMove()
214    {
215        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
216        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
217
218        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
219        $upload->moveTo($to);
220        $this->assertFileExists($to);
221
222        $this->expectException('RuntimeException', 'moved');
223        $upload->getStream();
224    }
225
226    public function nonOkErrorStatus()
227    {
228        return [
229            'UPLOAD_ERR_INI_SIZE'   => [ UPLOAD_ERR_INI_SIZE ],
230            'UPLOAD_ERR_FORM_SIZE'  => [ UPLOAD_ERR_FORM_SIZE ],
231            'UPLOAD_ERR_PARTIAL'    => [ UPLOAD_ERR_PARTIAL ],
232            'UPLOAD_ERR_NO_FILE'    => [ UPLOAD_ERR_NO_FILE ],
233            'UPLOAD_ERR_NO_TMP_DIR' => [ UPLOAD_ERR_NO_TMP_DIR ],
234            'UPLOAD_ERR_CANT_WRITE' => [ UPLOAD_ERR_CANT_WRITE ],
235            'UPLOAD_ERR_EXTENSION'  => [ UPLOAD_ERR_EXTENSION ],
236        ];
237    }
238
239    /**
240     * @dataProvider nonOkErrorStatus
241     */
242    public function testConstructorDoesNotRaiseExceptionForInvalidStreamWhenErrorStatusPresent($status)
243    {
244        $uploadedFile = new UploadedFile('not ok', 0, $status);
245        $this->assertSame($status, $uploadedFile->getError());
246    }
247
248    /**
249     * @dataProvider nonOkErrorStatus
250     */
251    public function testMoveToRaisesExceptionWhenErrorStatusPresent($status)
252    {
253        $uploadedFile = new UploadedFile('not ok', 0, $status);
254        $this->expectException('RuntimeException', 'upload error');
255        $uploadedFile->moveTo(__DIR__ . '/' . sha1(uniqid('', true)));
256    }
257
258    /**
259     * @dataProvider nonOkErrorStatus
260     */
261    public function testGetStreamRaisesExceptionWhenErrorStatusPresent($status)
262    {
263        $uploadedFile = new UploadedFile('not ok', 0, $status);
264        $this->expectException('RuntimeException', 'upload error');
265        $uploadedFile->getStream();
266    }
267
268    public function testMoveToCreatesStreamIfOnlyAFilenameWasProvided()
269    {
270        $this->cleanup[] = $from = tempnam(sys_get_temp_dir(), 'copy_from');
271        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'copy_to');
272
273        copy(__FILE__, $from);
274
275        $uploadedFile = new UploadedFile($from, 100, UPLOAD_ERR_OK, basename($from), 'text/plain');
276        $uploadedFile->moveTo($to);
277
278        $this->assertFileEquals(__FILE__, $to);
279    }
280}
281