request = (new ServerRequest()) ->withMethod("HEAD"); $this->body = $this->prophesize(StreamInterface::class); $this->body->isReadable()->willReturn(false); $this->body->getSize()->willReturn(1024); /** @var StreamInterface $stream */ $stream = $this->body->reveal(); $this->response = (new Response()) ->withStatus(200) ->withBody($stream); } public function testSendStatusCodeWithReasonPhrase() { $transmitter = new Transmitter(); $transmitter->transmit($this->request, $this->response); $this->assertContains("HTTP/1.1 200 OK", HeaderStack::getHeaders()); } public function testSendStatusCodeWithoutReasonPhrase() { $this->response = $this->response->withStatus(999); $transmitter = new Transmitter(); $transmitter->transmit($this->request, $this->response); $this->assertContains("HTTP/1.1 999", HeaderStack::getHeaders()); } /** @dataProvider headerProvider */ public function testSendsHeaders($header) { $this->response = $this->response ->withHeader("Content-length", ["2048"]) ->withHeader("X-foo", ["bar", "baz"]); $transmitter = new Transmitter(); $transmitter->transmit($this->request, $this->response); $this->assertContains($header, HeaderStack::getHeaders()); } public function headerProvider() { return [ ["Content-length: 2048"], ["X-foo: bar"], ["X-foo: baz"] ]; } public function testOutputsBody() { $content = "Hello, world!"; $this->body->isReadable()->willReturn(true); $this->body->__toString()->willReturn($content); $transmitter = new Transmitter(); ob_start(); $transmitter->transmit($this->request, $this->response); $captured = ob_get_contents(); ob_end_clean(); $this->assertEquals($content, $captured); } public function testOutputsBodyInChunks() { $content = "Hello, world!"; $chunkSize = 3; $position = 0; $this->body->isSeekable()->willReturn(true); $this->body->isReadable()->willReturn(true); $this->body->rewind()->willReturn(true); $this->body->eof()->willReturn(false); $this->body->read(Argument::any())->will( function ($args) use ($content, &$position) { $chunkSize = $args[0]; $chunk = substr($content, $position, $chunkSize); $position += $chunkSize; if ($position >= strlen($content)) { $this->eof()->willReturn(true); } return $chunk; } ); $transmitter = new Transmitter(); $transmitter->setChunkSize($chunkSize); ob_start(); $transmitter->transmit($this->request, $this->response); $captured = ob_get_contents(); ob_end_clean(); $this->assertEquals($content, $captured); } public function testOutputsUnseekableStreamInChunks() { $content = "Hello, world!"; $chunkSize = 3; $position = 0; $this->body->isSeekable()->willReturn(false); $this->body->isReadable()->willReturn(true); $this->body->rewind()->willThrow(new \RuntimeException()); $this->body->eof()->willReturn(false); $this->body->read(Argument::any())->will( function ($args) use ($content, &$position) { $chunkSize = $args[0]; $chunk = substr($content, $position, $chunkSize); $position += $chunkSize; if ($position >= strlen($content)) { $this->eof()->willReturn(true); } return $chunk; } ); $transmitter = new Transmitter(); $transmitter->setChunkSize($chunkSize); ob_start(); $transmitter->transmit($this->request, $this->response); $captured = ob_get_contents(); ob_end_clean(); $this->assertEquals($content, $captured); } // ------------------------------------------------------------------------ // Preparation public function testAddContentLengthHeader() { $bodySize = 1024; $this->body->isReadable()->willReturn(true); $this->body->__toString()->willReturn(""); $this->body->getSize()->willReturn($bodySize); $transmitter = new Transmitter(); $transmitter->transmit($this->request, $this->response); $this->assertContains("Content-length: $bodySize", HeaderStack::getHeaders()); } public function testDoesNotReplaceContentLengthHeaderWhenContentLengthIsAlreadySet() { $streamSize = 1024; $headerSize = 2048; $this->response = $this->response->withHeader("Content-length", $headerSize); $this->body->isReadable()->willReturn(true); $this->body->__toString()->willReturn(""); $this->body->getSize()->willReturn($streamSize); $transmitter = new Transmitter(); $transmitter->transmit($this->request, $this->response); $this->assertContains("Content-length: $headerSize", HeaderStack::getHeaders()); } public function testDoesNotAddContentLengthHeaderWhenTransferEncodingIsChunked() { $bodySize = 1024; $this->response = $this->response->withHeader("Transfer-encoding", "CHUNKED"); $this->body->isReadable()->willReturn(true); $this->body->__toString()->willReturn(""); $this->body->getSize()->willReturn($bodySize); $transmitter = new Transmitter(); $transmitter->transmit($this->request, $this->response); $this->assertArrayDoesNotContainValueWithPrefix(HeaderStack::getHeaders(), "Content-length:"); } public function testDoesNotAddContentLengthHeaderWhenBodySizeIsNull() { $this->body->isReadable()->willReturn(true); $this->body->__toString()->willReturn(""); $this->body->getSize()->willReturn(null); $transmitter = new Transmitter(); $transmitter->transmit($this->request, $this->response); $this->assertArrayDoesNotContainValueWithPrefix(HeaderStack::getHeaders(), "Content-length:"); } private function assertArrayDoesNotContainValueWithPrefix($arr, $prefix) { $normalPrefix = strtolower($prefix); foreach ($arr as $item) { $normalItem = strtolower($item); if (substr($normalItem, 0, strlen($normalPrefix)) === $normalPrefix) { $this->assertTrue(false, "Array should not contain value beginning with '$prefix' but contained '$item'"); } } $this->assertTrue(true); } }