[FEATURE] Introduce Request/Response based on PSR-7
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Http / StreamTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Http;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Http\Stream;
18
19 /**
20 * Testcase for \TYPO3\CMS\Core\Http\StreamTest
21 *
22 * Adapted from https://github.com/phly/http/
23 */
24 class StreamTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
25
26 /**
27 * @var Stream
28 */
29 protected $stream;
30
31 public function setUp() {
32 $this->stream = new Stream('php://memory', 'wb+');
33 }
34
35 /**
36 * @test
37 */
38 public function canInstantiateWithStreamIdentifier() {
39 $this->assertInstanceOf(Stream::class, $this->stream);
40 }
41
42 /**
43 * @test
44 */
45 public function canInstantiteWithStreamResource() {
46 $resource = fopen('php://memory', 'wb+');
47 $stream = new Stream($resource);
48 $this->assertInstanceOf(Stream::class, $stream);
49 }
50
51 /**
52 * @test
53 */
54 public function isReadableReturnsFalseIfStreamIsNotReadable() {
55 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
56 touch($fileName);
57 $this->testFilesToDelete[] = $fileName;
58 $stream = new Stream($fileName, 'w');
59 $this->assertFalse($stream->isReadable());
60 }
61
62 /**
63 * @test
64 */
65 public function isWritableReturnsFalseIfStreamIsNotWritable() {
66 $stream = new Stream('php://memory', 'r');
67 $this->assertFalse($stream->isWritable());
68 }
69
70 /**
71 * @test
72 */
73 public function toStringRetrievesFullContentsOfStream() {
74 $message = 'foo bar';
75 $this->stream->write($message);
76 $this->assertEquals($message, (string) $this->stream);
77 }
78
79 /**
80 * @test
81 */
82 public function detachReturnsResource() {
83 $resource = fopen('php://memory', 'wb+');
84 $stream = new Stream($resource);
85 $this->assertSame($resource, $stream->detach());
86 }
87
88 /**
89 * @test
90 */
91 public function constructorRaisesExceptionWhenPassingInvalidStreamResource() {
92 $this->setExpectedException('InvalidArgumentException');
93 $stream = new Stream([' THIS WILL NOT WORK ']);
94 }
95
96 /**
97 * @test
98 */
99 public function toStringSerializationReturnsEmptyStringWhenStreamIsNotReadable() {
100 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
101 touch($fileName);
102 $this->testFilesToDelete[] = $fileName;
103 file_put_contents($fileName, 'FOO BAR');
104 $stream = new Stream($fileName, 'w');
105
106 $this->assertEquals('', $stream->__toString());
107 }
108
109 /**
110 * @test
111 */
112 public function closeClosesResource() {
113 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
114 touch($fileName);
115 $this->testFilesToDelete[] = $fileName;
116 $resource = fopen($fileName, 'wb+');
117 $stream = new Stream($resource);
118 $stream->close();
119 $this->assertFalse(is_resource($resource));
120 }
121
122 /**
123 * @test
124 */
125 public function closeUnsetsResource() {
126 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
127 touch($fileName);
128 $this->testFilesToDelete[] = $fileName;
129 $resource = fopen($fileName, 'wb+');
130 $stream = new Stream($resource);
131 $stream->close();
132
133 $this->assertNull($stream->detach());
134 }
135
136 /**
137 * @test
138 */
139 public function closeDoesNothingAfterDetach() {
140 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
141 touch($fileName);
142 $this->testFilesToDelete[] = $fileName;
143 $resource = fopen($fileName, 'wb+');
144 $stream = new Stream($resource);
145 $detached = $stream->detach();
146
147 $stream->close();
148 $this->assertTrue(is_resource($detached));
149 $this->assertSame($resource, $detached);
150 }
151
152 /**
153 * @test
154 */
155 public function getSizeReportsNullWhenNoResourcePresent() {
156 $this->stream->detach();
157 $this->assertNull($this->stream->getSize());
158 }
159
160 /**
161 * @test
162 */
163 public function tellReportsCurrentPositionInResource() {
164 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
165 $this->testFilesToDelete[] = $fileName;
166 file_put_contents($fileName, 'FOO BAR');
167 $resource = fopen($fileName, 'wb+');
168 $stream = new Stream($resource);
169
170 fseek($resource, 2);
171
172 $this->assertEquals(2, $stream->tell());
173 }
174
175 /**
176 * @test
177 */
178 public function tellRaisesExceptionIfResourceIsDetached() {
179 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
180 $this->testFilesToDelete[] = $fileName;
181 file_put_contents($fileName, 'FOO BAR');
182 $resource = fopen($fileName, 'wb+');
183 $stream = new Stream($resource);
184
185 fseek($resource, 2);
186 $stream->detach();
187 $this->setExpectedException('RuntimeException', 'No resource');
188 $stream->tell();
189 }
190
191 /**
192 * @test
193 */
194 public function eofReportsFalseWhenNotAtEndOfStream() {
195 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
196 $this->testFilesToDelete[] = $fileName;
197 file_put_contents($fileName, 'FOO BAR');
198 $resource = fopen($fileName, 'wb+');
199 $stream = new Stream($resource);
200
201 fseek($resource, 2);
202 $this->assertFalse($stream->eof());
203 }
204
205 /**
206 * @test
207 */
208 public function eofReportsTrueWhenAtEndOfStream() {
209 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
210 $this->testFilesToDelete[] = $fileName;
211 file_put_contents($fileName, 'FOO BAR');
212 $resource = fopen($fileName, 'wb+');
213 $stream = new Stream($resource);
214
215 while (!feof($resource)) {
216 fread($resource, 4096);
217 }
218 $this->assertTrue($stream->eof());
219 }
220
221 /**
222 * @test
223 */
224 public function eofReportsTrueWhenStreamIsDetached() {
225 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
226 $this->testFilesToDelete[] = $fileName;
227 file_put_contents($fileName, 'FOO BAR');
228 $resource = fopen($fileName, 'wb+');
229 $stream = new Stream($resource);
230
231 fseek($resource, 2);
232 $stream->detach();
233 $this->assertTrue($stream->eof());
234 }
235
236 /**
237 * @test
238 */
239 public function isSeekableReturnsTrueForReadableStreams() {
240 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
241 $this->testFilesToDelete[] = $fileName;
242 file_put_contents($fileName, 'FOO BAR');
243 $resource = fopen($fileName, 'wb+');
244 $stream = new Stream($resource);
245 $this->assertTrue($stream->isSeekable());
246 }
247
248 /**
249 * @test
250 */
251 public function isSeekableReturnsFalseForDetachedStreams() {
252 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
253 $this->testFilesToDelete[] = $fileName;
254 file_put_contents($fileName, 'FOO BAR');
255 $resource = fopen($fileName, 'wb+');
256 $stream = new Stream($resource);
257 $stream->detach();
258 $this->assertFalse($stream->isSeekable());
259 }
260
261 /**
262 * @test
263 */
264 public function seekAdvancesToGivenOffsetOfStream() {
265 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
266 $this->testFilesToDelete[] = $fileName;
267 file_put_contents($fileName, 'FOO BAR');
268 $resource = fopen($fileName, 'wb+');
269 $stream = new Stream($resource);
270 $stream->seek(2);
271 $this->assertEquals(2, $stream->tell());
272 }
273
274 /**
275 * @test
276 */
277 public function rewindResetsToStartOfStream() {
278 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
279 $this->testFilesToDelete[] = $fileName;
280 file_put_contents($fileName, 'FOO BAR');
281 $resource = fopen($fileName, 'wb+');
282 $stream = new Stream($resource);
283 $stream->seek(2);
284 $stream->rewind();
285 $this->assertEquals(0, $stream->tell());
286 }
287
288 /**
289 * @test
290 */
291 public function seekRaisesExceptionWhenStreamIsDetached() {
292 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
293 $this->testFilesToDelete[] = $fileName;
294 file_put_contents($fileName, 'FOO BAR');
295 $resource = fopen($fileName, 'wb+');
296 $stream = new Stream($resource);
297 $stream->detach();
298 $this->setExpectedException('RuntimeException', 'No resource');
299 $stream->seek(2);
300 }
301
302 /**
303 * @test
304 */
305 public function isWritableReturnsFalseWhenStreamIsDetached() {
306 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
307 $this->testFilesToDelete[] = $fileName;
308 file_put_contents($fileName, 'FOO BAR');
309 $resource = fopen($fileName, 'wb+');
310 $stream = new Stream($resource);
311 $stream->detach();
312 $this->assertFalse($stream->isWritable());
313 }
314
315 /**
316 * @test
317 */
318 public function writeRaisesExceptionWhenStreamIsDetached() {
319 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
320 $this->testFilesToDelete[] = $fileName;
321 file_put_contents($fileName, 'FOO BAR');
322 $resource = fopen($fileName, 'wb+');
323 $stream = new Stream($resource);
324 $stream->detach();
325 $this->setExpectedException('RuntimeException', 'No resource');
326 $stream->write('bar');
327 }
328
329 /**
330 * @test
331 */
332 public function isReadableReturnsFalseWhenStreamIsDetached() {
333 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
334 $this->testFilesToDelete[] = $fileName;
335 file_put_contents($fileName, 'FOO BAR');
336 $resource = fopen($fileName, 'wb+');
337 $stream = new Stream($resource);
338 $stream->detach();
339 $this->assertFalse($stream->isReadable());
340 }
341
342 /**
343 * @test
344 */
345 public function readRaisesExceptionWhenStreamIsDetached() {
346 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
347 $this->testFilesToDelete[] = $fileName;
348 file_put_contents($fileName, 'FOO BAR');
349 $resource = fopen($fileName, 'r');
350 $stream = new Stream($resource);
351 $stream->detach();
352 $this->setExpectedException('RuntimeException', 'No resource');
353 $stream->read(4096);
354 }
355
356 /**
357 * @test
358 */
359 public function readReturnsEmptyStringWhenAtEndOfFile() {
360 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
361 $this->testFilesToDelete[] = $fileName;
362 file_put_contents($fileName, 'FOO BAR');
363 $resource = fopen($fileName, 'r');
364 $stream = new Stream($resource);
365 while (!feof($resource)) {
366 fread($resource, 4096);
367 }
368 $this->assertEquals('', $stream->read(4096));
369 }
370
371 /**
372 * @test
373 */
374 public function getContentsReturnsEmptyStringIfStreamIsNotReadable() {
375 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
376 $this->testFilesToDelete[] = $fileName;
377 file_put_contents($fileName, 'FOO BAR');
378 $resource = fopen($fileName, 'w');
379 $stream = new Stream($resource);
380 $this->assertEquals('', $stream->getContents());
381 }
382
383 /**
384 * @return array
385 */
386 public function invalidResourcesDataProvider() {
387 $fileName = tempnam(sys_get_temp_dir(), 'PHLY');
388 $this->testFilesToDelete[] = $fileName;
389
390 return [
391 'null' => [NULL],
392 'false' => [FALSE],
393 'true' => [TRUE],
394 'int' => [1],
395 'float' => [1.1],
396 'string-non-resource' => ['foo-bar-baz'],
397 'array' => [[fopen($fileName, 'r+')]],
398 'object' => [(object) ['resource' => fopen($fileName, 'r+')]],
399 ];
400 }
401
402 /**
403 * @dataProvider invalidResourcesDataProvider
404 * @test
405 */
406 public function attachWithNonStringNonResourceRaisesException($resource) {
407 $this->setExpectedException('InvalidArgumentException', 'Invalid stream');
408 $this->stream->attach($resource);
409 }
410
411 /**
412 * @test
413 */
414 public function attachWithResourceAttachesResource() {
415 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
416 touch($fileName);
417 $this->testFilesToDelete[] = $fileName;
418 $resource = fopen($fileName, 'r+');
419 $this->stream->attach($resource);
420
421 $r = new \ReflectionProperty($this->stream, 'resource');
422 $r->setAccessible(TRUE);
423 $test = $r->getValue($this->stream);
424 $this->assertSame($resource, $test);
425 }
426
427 /**
428 * @test
429 */
430 public function attachWithStringRepresentingResourceCreatesAndAttachesResource() {
431 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
432 touch($fileName);
433 $this->testFilesToDelete[] = $fileName;
434 $this->stream->attach($fileName);
435
436 $resource = fopen($fileName, 'r+');
437 fwrite($resource, 'FooBar');
438
439 $this->stream->rewind();
440 $test = (string) $this->stream;
441 $this->assertEquals('FooBar', $test);
442 }
443
444 /**
445 * @test
446 */
447 public function getContentsShouldGetFullStreamContents() {
448 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
449 touch($fileName);
450 $this->testFilesToDelete[] = $fileName;
451 $resource = fopen($fileName, 'r+');
452 $this->stream->attach($resource);
453
454 fwrite($resource, 'FooBar');
455
456 // rewind, because current pointer is at end of stream!
457 $this->stream->rewind();
458 $test = $this->stream->getContents();
459 $this->assertEquals('FooBar', $test);
460 }
461
462 /**
463 * @test
464 */
465 public function getContentsShouldReturnStreamContentsFromCurrentPointer() {
466 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
467 touch($fileName);
468 $this->testFilesToDelete[] = $fileName;
469 $resource = fopen($fileName, 'r+');
470 $this->stream->attach($resource);
471
472 fwrite($resource, 'FooBar');
473
474 // seek to position 3
475 $this->stream->seek(3);
476 $test = $this->stream->getContents();
477 $this->assertEquals('Bar', $test);
478 }
479
480 /**
481 * @test
482 */
483 public function getMetadataReturnsAllMetadataWhenNoKeyPresent() {
484 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
485 touch($fileName);
486 $this->testFilesToDelete[] = $fileName;
487 $resource = fopen($fileName, 'r+');
488 $this->stream->attach($resource);
489
490 $expected = stream_get_meta_data($resource);
491 $test = $this->stream->getMetadata();
492
493 $this->assertEquals($expected, $test);
494 }
495
496 /**
497 * @test
498 */
499 public function getMetadataReturnsDataForSpecifiedKey() {
500 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
501 touch($fileName);
502 $this->testFilesToDelete[] = $fileName;
503 $resource = fopen($fileName, 'r+');
504 $this->stream->attach($resource);
505
506 $metadata = stream_get_meta_data($resource);
507 $expected = $metadata['uri'];
508
509 $test = $this->stream->getMetadata('uri');
510
511 $this->assertEquals($expected, $test);
512 }
513
514 /**
515 * @test
516 */
517 public function getMetadataReturnsNullIfNoDataExistsForKey() {
518 $fileName = PATH_site . 'typo3temp/' . $this->getUniqueId('test_');
519 touch($fileName);
520 $this->testFilesToDelete[] = $fileName;
521 $resource = fopen($fileName, 'r+');
522 $this->stream->attach($resource);
523
524 $this->assertNull($this->stream->getMetadata('TOTALLY_MADE_UP'));
525 }
526
527 /**
528 * @test
529 */
530 public function getSizeReturnsStreamSize() {
531 $resource = fopen(__FILE__, 'r');
532 $expected = fstat($resource);
533 $stream = new Stream($resource);
534 $this->assertEquals($expected['size'], $stream->getSize());
535 }
536 }