diff --git a/composer.json b/composer.json index cf398b3..a8623c3 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ }, "autoload-dev": { "psr-4": { + "WellRESTed\\": "test/tests/unit/", "WellRESTed\\Test\\": "test/src/" } } diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php index df6fb0a..fd81fcd 100644 --- a/src/Message/ServerRequest.php +++ b/src/Message/ServerRequest.php @@ -4,9 +4,7 @@ namespace WellRESTed\Message; use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; -use Psr\Http\Message\UriInterface; /** * Representation of an incoming, server-side HTTP request. @@ -53,22 +51,13 @@ class ServerRequest extends Request implements ServerRequestInterface // ------------------------------------------------------------------------ - /** - * Creates a new, empty representation of a server-side HTTP request. - * - * To obtain a ServerRequest representing the request sent to the server - * instantiating the request, use the factory method - * ServerRequest::getServerRequest - * - * @see ServerRequest::getServerRequest - */ - public function __construct() + public function __construct(array $serverParams = []) { parent::__construct(); - $this->attributes = []; + $this->serverParams = $serverParams; $this->cookieParams = []; $this->queryParams = []; - $this->serverParams = []; + $this->attributes = []; $this->uploadedFiles = []; } @@ -339,189 +328,6 @@ class ServerRequest extends Request implements ServerRequestInterface // ------------------------------------------------------------------------ - /** - * @param array $attributes - * @return void - */ - protected function readFromServerRequest(array $attributes = []) - { - $this->attributes = $attributes; - $this->serverParams = $_SERVER; - $this->cookieParams = $_COOKIE; - $this->readUploadedFiles($_FILES); - $this->queryParams = []; - $this->uri = $this->readUri(); - if (isset($_SERVER['QUERY_STRING'])) { - parse_str($_SERVER['QUERY_STRING'], $this->queryParams); - } - if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.0') { - // The default is 1.1, so only update if 1.0 - $this->protocolVersion = '1.0'; - } - if (isset($_SERVER['REQUEST_METHOD'])) { - $this->method = $_SERVER['REQUEST_METHOD']; - } - $headers = $this->getServerRequestHeaders(); - foreach ($headers as $key => $value) { - $this->headers[$key] = $value; - } - $this->body = $this->getStreamForBody(); - - $contentType = $this->getHeaderLine('Content-type'); - if (strpos($contentType, 'application/x-www-form-urlencoded') !== false - || strpos($contentType, 'multipart/form-data') !== false) { - $this->parsedBody = $_POST; - } - } - - protected function readUploadedFiles(array $input): void - { - $uploadedFiles = []; - foreach ($input as $name => $value) { - $this->addUploadedFilesToBranch($uploadedFiles, $name, $value); - } - $this->uploadedFiles = $uploadedFiles; - } - - protected function addUploadedFilesToBranch( - array &$branch, - string $name, - array $value - ): void { - // Check for each of the expected keys. - if (isset($value['name'], $value['type'], $value['tmp_name'], $value['error'], $value['size'])) { - // This is a file. It may be a single file, or a list of files. - - // Check if these items are arrays. - if (is_array($value['name']) - && is_array($value['type']) - && is_array($value['tmp_name']) - && is_array($value['error']) - && is_array($value['size']) - ) { - // Each item is an array. This is a list of uploaded files. - $files = []; - $keys = array_keys($value['name']); - foreach ($keys as $key) { - $files[$key] = new UploadedFile( - $value['name'][$key], - $value['type'][$key], - $value['size'][$key], - $value['tmp_name'][$key], - $value['error'][$key] - ); - } - $branch[$name] = $files; - } else { - // All expected keys are present and are not arrays. This is an uploaded file. - $uploadedFile = new UploadedFile( - $value['name'], - $value['type'], - $value['size'], - $value['tmp_name'], - $value['error'] - ); - $branch[$name] = $uploadedFile; - } - } else { - // Add another branch - $nextBranch = []; - foreach ($value as $nextName => $nextValue) { - $this->addUploadedFilesToBranch($nextBranch, $nextName, $nextValue); - } - $branch[$name] = $nextBranch; - } - } - - protected function readUri(): UriInterface - { - $uri = ''; - - $scheme = 'http'; - if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] && $_SERVER['HTTPS'] !== 'off') { - $scheme = 'https'; - } - - if (isset($_SERVER['HTTP_HOST'])) { - $authority = $_SERVER['HTTP_HOST']; - $uri .= "$scheme://$authority"; - } - - // Path and query string - if (isset($_SERVER['REQUEST_URI'])) { - $uri .= $_SERVER['REQUEST_URI']; - } - - return new Uri($uri); - } - - /** - * Return a reference to the singleton instance of the Request derived - * from the server's information about the request sent to the server. - * - * @param array $attributes Key-value pairs to add to the request. - * @return static - * @static - */ - public static function getServerRequest(array $attributes = []) - { - $request = new static(); - $request->readFromServerRequest($attributes); - return $request; - } - - /** - * Return a stream representing the request's body. - * - * Override this method to use a specific StreamInterface implementation. - * - * @return StreamInterface - */ - protected function getStreamForBody() - { - $input = fopen('php://input', 'rb'); - $temp = fopen('php://temp', 'wb+'); - stream_copy_to_stream($input, $temp); - rewind($temp); - return new Stream($temp); - } - - /** - * Read and return all request headers from the request issued to the server. - * - * @return array Associative array of headers - */ - protected function getServerRequestHeaders() - { - // http://www.php.net/manual/en/function.getallheaders.php#84262 - $headers = []; - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) === 'HTTP_') { - $name = $this->normalizeHeaderName(substr($name, 5)); - $headers[$name] = trim($value); - } elseif ($this->isContentHeader($name) && !empty(trim($value))) { - $name = $this->normalizeHeaderName($name); - $headers[$name] = trim($value); - } - } - return $headers; - } - - private function isContentHeader(string $name): bool - { - return $name === 'CONTENT_LENGTH' || $name === 'CONTENT_TYPE'; - } - - /** - * @param string $name - * @return string - */ - private function normalizeHeaderName($name) - { - $name = ucwords(strtolower(str_replace('_', ' ', $name))); - return str_replace(' ', '-', $name); - } - /** * @param array $root * @return bool diff --git a/src/Message/ServerRequestMarshaller.php b/src/Message/ServerRequestMarshaller.php new file mode 100644 index 0000000..9b67065 --- /dev/null +++ b/src/Message/ServerRequestMarshaller.php @@ -0,0 +1,201 @@ +withProtocolVersion(self::parseProtocolVersion($serverParams)) + ->withMethod(self::parseMethod($serverParams)) + ->withBody(self::readBody($inputStream)) + ->withUri(self::readUri($serverParams)) + ->withUploadedFiles(self::readUploadedFiles($fileParams)) + ->withCookieParams($cookieParams) + ->withQueryParams($queryParams); + + $headers = self::parseHeaders($serverParams); + foreach ($headers as $name => $value) { + $request = $request->withAddedHeader($name, $value); + } + + if (self::isForm($request)) { + $request = $request->withParsedBody($postParams); + } + + return $request; + } + + private static function parseQuery(array $serverParams): array + { + $queryParams = []; + if (isset($serverParams['QUERY_STRING'])) { + parse_str($serverParams['QUERY_STRING'], $queryParams); + } + return $queryParams; + } + + private static function parseProtocolVersion(array $serverParams): string + { + if (isset($serverParams['SERVER_PROTOCOL']) + && $serverParams['SERVER_PROTOCOL'] === 'HTTP/1.0') { + return '1.0'; + } + return '1.1'; + } + + private static function parseHeaders(array $serverParams): array + { + // http://www.php.net/manual/en/function.getallheaders.php#84262 + $headers = []; + foreach ($serverParams as $name => $value) { + if (substr($name, 0, 5) === 'HTTP_') { + $name = self::normalizeHeaderName(substr($name, 5)); + $headers[$name] = trim($value); + } elseif (self::isContentHeader($name) && !empty(trim($value))) { + $name = self::normalizeHeaderName($name); + $headers[$name] = trim($value); + } + } + return $headers; + } + + private static function normalizeHeaderName(string $name): string + { + $name = ucwords(strtolower(str_replace('_', ' ', $name))); + return str_replace(' ', '-', $name); + } + + private static function isContentHeader(string $name): bool + { + return $name === 'CONTENT_LENGTH' || $name === 'CONTENT_TYPE'; + } + + private static function parseMethod(array $serverParams): string + { + return $serverParams['REQUEST_METHOD'] ?? 'GET'; + } + + private static function readBody(string $inputStream): StreamInterface + { + $input = fopen($inputStream, 'rb'); + $temp = fopen('php://temp', 'wb+'); + stream_copy_to_stream($input, $temp); + rewind($temp); + return new Stream($temp); + } + + private static function readUri(array $serverParams): UriInterface + { + $uri = ''; + + $scheme = 'http'; + if (isset($serverParams['HTTPS']) && $serverParams['HTTPS'] && $serverParams['HTTPS'] !== 'off') { + $scheme = 'https'; + } + + if (isset($serverParams['HTTP_HOST'])) { + $authority = $serverParams['HTTP_HOST']; + $uri .= "$scheme://$authority"; + } + + // Path and query string + if (isset($serverParams['REQUEST_URI'])) { + $uri .= $serverParams['REQUEST_URI']; + } + + return new Uri($uri); + } + + private static function isForm(ServerRequestInterface $request): bool + { + $contentType = $request->getHeaderLine('Content-type'); + return (strpos($contentType, 'application/x-www-form-urlencoded') !== false) + || (strpos($contentType, 'multipart/form-data') !== false); + } + + private static function readUploadedFiles(array $input): array + { + $uploadedFiles = []; + foreach ($input as $name => $value) { + self::addUploadedFilesToBranch($uploadedFiles, $name, $value); + } + return $uploadedFiles; + } + + private static function addUploadedFilesToBranch( + array &$branch, + string $name, + array $value + ): void { + if (self::isUploadedFile($value)) { + if (self::isUploadedFileList($value)) { + $files = []; + $keys = array_keys($value['name']); + foreach ($keys as $key) { + $files[$key] = new UploadedFile( + $value['name'][$key], + $value['type'][$key], + $value['size'][$key], + $value['tmp_name'][$key], + $value['error'][$key] + ); + } + $branch[$name] = $files; + } else { + // Single uploaded file + $uploadedFile = new UploadedFile( + $value['name'], + $value['type'], + $value['size'], + $value['tmp_name'], + $value['error'] + ); + $branch[$name] = $uploadedFile; + } + } else { + // Add another branch + $nextBranch = []; + foreach ($value as $nextName => $nextValue) { + self::addUploadedFilesToBranch($nextBranch, $nextName, $nextValue); + } + $branch[$name] = $nextBranch; + } + } + + private static function isUploadedFile(array $value): bool + { + // Check for each of the expected keys. If all are present, this is a + // a file. It may be a single file, or a list of files. + return isset($value['name'], $value['type'], $value['tmp_name'], $value['error'], $value['size']); + } + + private static function isUploadedFileList(array $value): bool + { + // When each item is an array, this is a list of uploaded files. + return is_array($value['name']) + && is_array($value['type']) + && is_array($value['tmp_name']) + && is_array($value['error']) + && is_array($value['size']); + } +} diff --git a/src/Server.php b/src/Server.php index 613dc98..c513e9d 100644 --- a/src/Server.php +++ b/src/Server.php @@ -7,7 +7,7 @@ use Psr\Http\Message\ServerRequestInterface; use WellRESTed\Dispatching\Dispatcher; use WellRESTed\Dispatching\DispatcherInterface; use WellRESTed\Message\Response; -use WellRESTed\Message\ServerRequest; +use WellRESTed\Message\ServerRequestMarshaller; use WellRESTed\Routing\Router; use WellRESTed\Transmission\Transmitter; use WellRESTed\Transmission\TransmitterInterface; @@ -163,7 +163,8 @@ class Server private function getRequest(): ServerRequestInterface { if (!$this->request) { - $this->request = ServerRequest::getServerRequest(); + $marshaller = new ServerRequestMarshaller(); + return $marshaller->getServerRequest(); } return $this->request; } diff --git a/test/tests/unit/Message/ServerRequestMarshallerTest.php b/test/tests/unit/Message/ServerRequestMarshallerTest.php new file mode 100644 index 0000000..dddedb7 --- /dev/null +++ b/test/tests/unit/Message/ServerRequestMarshallerTest.php @@ -0,0 +1,381 @@ + 'localhost', + 'HTTP_ACCEPT' => 'application/json', + 'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded', + 'QUERY_STRING' => 'cat=molly&kitten=aggie' + ]; + + $_COOKIE = [ + 'dog' => 'Bear', + 'hamster' => 'Dusty' + ]; + + $this->marshaller = new ServerRequestMarshaller(); + } + + // ------------------------------------------------------------------------- + // Psr\Http\Message\MessageInterface + + // ------------------------------------------------------------------------- + // Protocol Version + + /** + * @dataProvider protocolVersionProvider + * @param $expectedProtocol + * @param $actualProtocol + */ + public function testProvidesProtocolVersion(string $expectedProtocol, ?string $actualProtocol): void + { + $_SERVER['SERVER_PROTOCOL'] = $actualProtocol; + $request = $this->marshaller->getServerRequest(); + $this->assertEquals($expectedProtocol, $request->getProtocolVersion()); + } + + public function protocolVersionProvider(): array + { + return [ + ['1.1', 'HTTP/1.1'], + ['1.0', 'HTTP/1.0'], + ['1.1', null], + ['1.1', 'INVALID'] + ]; + } + + // ------------------------------------------------------------------------- + // Headers + + public function testProvidesHeadersFromHttpFields(): void + { + $_SERVER = [ + 'HTTP_ACCEPT' => 'application/json', + 'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded' + ]; + $request = $this->marshaller->getServerRequest(); + $this->assertEquals(['application/json'], $request->getHeader('Accept')); + $this->assertEquals(['application/x-www-form-urlencoded'], $request->getHeader('Content-type')); + } + + public function testProvidesApacheContentHeaders(): void + { + $_SERVER = [ + 'CONTENT_LENGTH' => '1024', + 'CONTENT_TYPE' => 'application/json' + ]; + $request = $this->marshaller->getServerRequest(); + $this->assertEquals('1024', $request->getHeaderLine('Content-length')); + $this->assertEquals('application/json', $request->getHeaderLine('Content-type')); + } + + public function testDoesNotProvideEmptyApacheContentHeaders(): void + { + $_SERVER = [ + 'CONTENT_LENGTH' => '', + 'CONTENT_TYPE' => ' ' + ]; + $request = $this->marshaller->getServerRequest(); + $this->assertFalse($request->hasHeader('Content-length')); + $this->assertFalse($request->hasHeader('Content-type')); + } + + // ------------------------------------------------------------------------- + // Body + + public function testProvidesBodyFromInputStream(): void + { + $tempFilePath = tempnam(sys_get_temp_dir(), 'test'); + $content = 'Body content'; + file_put_contents($tempFilePath, $content); + + $request = $this->marshaller->getServerRequest( + null, + null, + null, + null, + null, + $tempFilePath + ); + unlink($tempFilePath); + + $this->assertEquals($content, (string) $request->getBody()); + } + + // ------------------------------------------------------------------------- + // Psr\Http\Message\RequestInterface + + // ------------------------------------------------------------------------ + // Request Target + + /** + * @dataProvider requestTargetProvider + * @param $expectedRequestTarget + * @param $actualRequestUri + */ + public function testProvidesRequestTarget(string $expectedRequestTarget, ?string $actualRequestUri): void + { + $_SERVER['REQUEST_URI'] = $actualRequestUri; + $request = $this->marshaller->getServerRequest(); + $this->assertEquals($expectedRequestTarget, $request->getRequestTarget()); + } + + public function requestTargetProvider(): array + { + return [ + ['/', '/'], + ['/hello', '/hello'], + ['/my/path.txt', '/my/path.txt'], + ['/', null] + ]; + } + + // ------------------------------------------------------------------------ + // Method + + /** + * @dataProvider methodProvider + * @param $expectedMethod + * @param $serverMethod + */ + public function testProvidesMethod($expectedMethod, $serverMethod) + { + $_SERVER['REQUEST_METHOD'] = $serverMethod; + $request = $this->marshaller->getServerRequest(); + $this->assertEquals($expectedMethod, $request->getMethod()); + } + + public function methodProvider() + { + return [ + ['GET', 'GET'], + ['POST', 'POST'], + ['DELETE', 'DELETE'], + ['PUT', 'PUT'], + ['OPTIONS', 'OPTIONS'], + ['GET', null] + ]; + } + + // ------------------------------------------------------------------------ + // URI + + /** + * @dataProvider uriProvider + * @param UriInterface $expected + * @param array $serverParams + */ + public function testProvidesUri(UriInterface $expected, array $serverParams): void + { + $request = $this->marshaller->getServerRequest($serverParams); + $this->assertEquals($expected, $request->getUri()); + } + + public function uriProvider() + { + return [ + [ + new Uri('http://localhost/path'), + [ + 'HTTPS' => 'off', + 'HTTP_HOST' => 'localhost', + 'REQUEST_URI' => '/path', + 'QUERY_STRING' => '' + ] + ], + [ + new Uri('https://foo.com/path/to/stuff?cat=molly'), + [ + 'HTTPS' => '1', + 'HTTP_HOST' => 'foo.com', + 'REQUEST_URI' => '/path/to/stuff?cat=molly', + 'QUERY_STRING' => 'cat=molly' + ] + ], + [ + new Uri('http://foo.com:8080/path/to/stuff?cat=molly'), + [ + 'HTTP' => '1', + 'HTTP_HOST' => 'foo.com:8080', + 'REQUEST_URI' => '/path/to/stuff?cat=molly', + 'QUERY_STRING' => 'cat=molly' + ] + ] + ]; + } + + // ------------------------------------------------------------------------- + // Psr\Http\Message\ServerRequestInterface + + // ------------------------------------------------------------------------- + // Server Params + + public function testProvidesServerParams(): void + { + $request = $this->marshaller->getServerRequest(); + $this->assertEquals($_SERVER, $request->getServerParams()); + } + + // ------------------------------------------------------------------------ + // Cookies + + public function testProvidesCookieParams(): void + { + $request = $this->marshaller->getServerRequest(); + $this->assertEquals($_COOKIE, $request->getCookieParams()); + } + + // ------------------------------------------------------------------------ + // Query + + public function testProvidesQueryParams(): void + { + $request = $this->marshaller->getServerRequest(); + $query = $request->getQueryParams(); + $this->assertCount(2, $query); + $this->assertEquals('molly', $query['cat']); + $this->assertEquals('aggie', $query['kitten']); + } + + // ------------------------------------------------------------------------ + // Uploaded Files + + /** + * @dataProvider uploadedFileProvider + * @param UploadedFileInterface $file + * @param array $path + */ + public function testGetServerRequestReadsUploadedFiles(UploadedFileInterface $file, array $path): void + { + $_FILES = [ + 'single' => [ + 'name' => 'single.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/php9hNlHe', + 'error' => UPLOAD_ERR_OK, + 'size' => 524 + ], + 'nested' => [ + 'level2' => [ + 'name' => 'nested.json', + 'type' => 'application/json', + 'tmp_name' => '/tmp/phpadhjk', + 'error' => UPLOAD_ERR_OK, + 'size' => 1024 + ] + ], + 'nestedList' => [ + 'level2' => [ + 'name' => [ + 0 => 'nestedList0.jpg', + 1 => 'nestedList1.jpg', + 2 => '' + ], + 'type' => [ + 0 => 'image/jpeg', + 1 => 'image/jpeg', + 2 => '' + ], + 'tmp_name' => [ + 0 => '/tmp/phpjpg0', + 1 => '/tmp/phpjpg1', + 2 => '' + ], + 'error' => [ + 0 => UPLOAD_ERR_OK, + 1 => UPLOAD_ERR_OK, + 2 => UPLOAD_ERR_NO_FILE + ], + 'size' => [ + 0 => 256, + 1 => 4096, + 2 => 0 + ] + ] + ], + 'nestedDictionary' => [ + 'level2' => [ + 'name' => [ + 'file0' => 'nestedDictionary0.jpg', + 'file1' => 'nestedDictionary1.jpg' + ], + 'type' => [ + 'file0' => 'image/png', + 'file1' => 'image/png' + ], + 'tmp_name' => [ + 'file0' => '/tmp/phppng0', + 'file1' => '/tmp/phppng1' + ], + 'error' => [ + 'file0' => UPLOAD_ERR_OK, + 'file1' => UPLOAD_ERR_OK + ], + 'size' => [ + 'file0' => 256, + 'file1' => 4096 + ] + ] + ] + ]; + $request = $this->marshaller->getServerRequest(); + $current = $request->getUploadedFiles(); + foreach ($path as $item) { + $current = $current[$item]; + } + $this->assertEquals($file, $current); + } + + public function uploadedFileProvider(): array + { + return [ + [new UploadedFile('single.txt', 'text/plain', 524, '/tmp/php9hNlHe', UPLOAD_ERR_OK), ['single']], + [new UploadedFile('nested.json', 'application/json', 1024, '/tmp/phpadhjk', UPLOAD_ERR_OK), ['nested', 'level2']], + [new UploadedFile('nestedList0.jpg', 'image/jpeg', 256, '/tmp/phpjpg0', UPLOAD_ERR_OK), ['nestedList', 'level2', 0]], + [new UploadedFile('nestedList1.jpg', 'image/jpeg', 4096, '/tmp/phpjpg1', UPLOAD_ERR_OK), ['nestedList', 'level2', 1]], + [new UploadedFile('', '', 0, '', UPLOAD_ERR_NO_FILE), ['nestedList', 'level2', 2]], + [new UploadedFile('nestedDictionary0.jpg', 'image/png', 256, '/tmp/phppng0', UPLOAD_ERR_OK), ['nestedDictionary', 'level2', 'file0']], + [new UploadedFile('nestedDictionary1.jpg', 'image/png', 4096, '/tmp/phppngg1', UPLOAD_ERR_OK), ['nestedDictionary', 'level2', 'file1']] + ]; + } + + // ------------------------------------------------------------------------ + // Parsed Body + + /** + * @dataProvider formContentTypeProvider + * @param string $contentType + */ + public function testProvidesParsedBodyForForms(string $contentType): void + { + $_SERVER['HTTP_CONTENT_TYPE'] = $contentType; + $_POST = [ + 'dog' => 'Bear' + ]; + $request = $this->marshaller->getServerRequest(); + $this->assertEquals('Bear', $request->getParsedBody()['dog']); + } + + public function formContentTypeProvider(): array + { + return [ + ['application/x-www-form-urlencoded'], + ['multipart/form-data'] + ]; + } +} diff --git a/test/tests/unit/Message/ServerRequestTest.php b/test/tests/unit/Message/ServerRequestTest.php index 8b5a08c..22916f2 100644 --- a/test/tests/unit/Message/ServerRequestTest.php +++ b/test/tests/unit/Message/ServerRequestTest.php @@ -1,380 +1,12 @@ 'localhost', - 'HTTP_ACCEPT' => 'application/json', - 'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded', - 'QUERY_STRING' => 'guinea_pig=Claude&hamster=Fizzgig' - ]; - $_COOKIE = [ - 'cat' => 'Molly' - ]; - $_FILES = []; - $_POST = [ - 'dog' => 'Bear' - ]; - $attributes = ['guinea_pig' => 'Claude']; - $request = ServerRequest::getServerRequest($attributes); - $this->assertNotNull($request); - return $request; - } - - // ------------------------------------------------------------------------ - // Marshalling Request Information - - /** - * @backupGlobals enabled - * @dataProvider protocolVersionProvider - */ - public function testGetServerRequestReadsProtocolVersion($expectedProtocol, $serverProtocol) - { - $_SERVER = [ - 'HTTP_HOST' => 'localhost', - 'SERVER_PROTOCOL' => $serverProtocol, - 'REQUEST_METHOD' => 'GET' - ]; - $request = ServerRequest::getServerRequest(); - $this->assertEquals($expectedProtocol, $request->getProtocolVersion()); - } - - public function protocolVersionProvider() - { - return [ - ['1.1', 'HTTP/1.1'], - ['1.0', 'HTTP/1.0'], - ['1.1', null], - ['1.1', 'INVALID'] - ]; - } - - /** - * @backupGlobals enabled - * @dataProvider methodProvider - */ - public function testGetServerRequestReadsMethod($expectedMethod, $serverMethod) - { - $_SERVER = [ - 'HTTP_HOST' => 'localhost', - 'REQUEST_METHOD' => $serverMethod - ]; - $request = ServerRequest::getServerRequest(); - $this->assertEquals($expectedMethod, $request->getMethod()); - } - - public function methodProvider() - { - return [ - ['GET', 'GET'], - ['POST', 'POST'], - ['DELETE', 'DELETE'], - ['PUT', 'PUT'], - ['OPTIONS', 'OPTIONS'], - ['GET', null] - ]; - } - - /** - * @backupGlobals enabled - * @dataProvider requestTargetProvider - */ - public function testGetServerRequestReadsRequestTargetFromRequest($expectedRequestTarget, $serverRequestUri) - { - $_SERVER = [ - 'HTTP_HOST' => 'localhost', - 'REQUEST_URI' => $serverRequestUri - ]; - $request = ServerRequest::getServerRequest(); - $this->assertEquals($expectedRequestTarget, $request->getRequestTarget()); - } - - public function requestTargetProvider() - { - return [ - ['/', '/'], - ['/hello', '/hello'], - ['/my/path.txt', '/my/path.txt'], - ['/', null] - ]; - } - - /** @depends testGetServerRequestReadsFromRequest */ - public function testGetServerRequestReadsHeaders($request) - { - /** @var ServerRequest $request */ - $this->assertEquals(['application/json'], $request->getHeader('Accept')); - } - - /** - * @backupGlobals enabled - */ - public function testGetServerRequestReadsContentHeaders() - { - $_SERVER = [ - 'CONTENT_LENGTH' => '1024', - 'CONTENT_TYPE' => 'application/json' - ]; - $request = ServerRequest::getServerRequest(); - $this->assertEquals('1024', $request->getHeaderLine('Content-length')); - $this->assertEquals('application/json', $request->getHeaderLine('Content-type')); - } - - /** - * @backupGlobals enabled - */ - public function testGetServerRequestDoesNotReadEmptyContentHeaders() - { - $_SERVER = [ - 'CONTENT_LENGTH' => '', - 'CONTENT_TYPE' => ' ' - ]; - $request = ServerRequest::getServerRequest(); - $this->assertFalse($request->hasHeader('Content-length')); - $this->assertFalse($request->hasHeader('Content-type')); - } - - public function testGetServerRequestReadsBody() - { - $body = new NullStream(); - $request = $this->getMockBuilder('WellRESTed\Message\ServerRequest') - ->setMethods(['getStreamForBody']) - ->getMock(); - $request->expects($this->any()) - ->method('getStreamForBody') - ->will($this->returnValue($body)); - - $called = false; - $callReadFromServerRequest = function () use (&$called) { - $called = true; - $this->readFromServerRequest(); - }; - $callReadFromServerRequest = $callReadFromServerRequest->bindTo($request, $request); - $callReadFromServerRequest(); - - $this->assertSame($body, $request->getBody()); - } - - /** - * @backupGlobals enabled - * @dataProvider uriProvider - */ - public function testGetServerRequestReadsUri($expected, $server) - { - $_SERVER = $server; - $request = ServerRequest::getServerRequest(); - $this->assertEquals($expected, $request->getUri()); - } - - public function uriProvider() - { - return [ - [ - new Uri('http://localhost/path'), - [ - 'HTTPS' => 'off', - 'HTTP_HOST' => 'localhost', - 'REQUEST_URI' => '/path', - 'QUERY_STRING' => '' - ] - ], - [ - new Uri('https://foo.com/path/to/stuff?cat=molly'), - [ - 'HTTPS' => '1', - 'HTTP_HOST' => 'foo.com', - 'REQUEST_URI' => '/path/to/stuff?cat=molly', - 'QUERY_STRING' => 'cat=molly' - ] - ], - [ - new Uri('http://foo.com:8080/path/to/stuff?cat=molly'), - [ - 'HTTP' => '1', - 'HTTP_HOST' => 'foo.com:8080', - 'REQUEST_URI' => '/path/to/stuff?cat=molly', - 'QUERY_STRING' => 'cat=molly' - ] - ] - ]; - } - - // ------------------------------------------------------------------------ - // Marshalling ServerRequest Information - - /** @depends testGetServerRequestReadsFromRequest */ - public function testGetServerRequestReadsServerParams($request) - { - /** @var ServerRequest $request */ - $this->assertEquals('localhost', $request->getServerParams()['HTTP_HOST']); - } - - /** @depends testGetServerRequestReadsFromRequest */ - public function testGetServerRequestReadsCookieParams($request) - { - /** @var ServerRequest $request */ - $this->assertEquals('Molly', $request->getCookieParams()['cat']); - } - - /** @depends testGetServerRequestReadsFromRequest */ - public function testGetServerRequestReadsQueryParams($request) - { - /** @var ServerRequest $request */ - $this->assertEquals('Claude', $request->getQueryParams()['guinea_pig']); - } - - /** - * @backupGlobals enabled - * @dataProvider uploadedFileProvider - */ - public function testGetServerRequestReadsUploadedFiles($file, $path) - { - $_SERVER = [ - 'HTTP_HOST' => 'localhost', - 'HTTP_ACCEPT' => 'application/json', - 'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded' - ]; - $_FILES = [ - 'single' => [ - 'name' => 'single.txt', - 'type' => 'text/plain', - 'tmp_name' => '/tmp/php9hNlHe', - 'error' => UPLOAD_ERR_OK, - 'size' => 524 - ], - 'nested' => [ - 'level2' => [ - 'name' => 'nested.json', - 'type' => 'application/json', - 'tmp_name' => '/tmp/phpadhjk', - 'error' => UPLOAD_ERR_OK, - 'size' => 1024 - ] - ], - 'nestedList' => [ - 'level2' => [ - 'name' => [ - 0 => 'nestedList0.jpg', - 1 => 'nestedList1.jpg', - 2 => '' - ], - 'type' => [ - 0 => 'image/jpeg', - 1 => 'image/jpeg', - 2 => '' - ], - 'tmp_name' => [ - 0 => '/tmp/phpjpg0', - 1 => '/tmp/phpjpg1', - 2 => '' - ], - 'error' => [ - 0 => UPLOAD_ERR_OK, - 1 => UPLOAD_ERR_OK, - 2 => UPLOAD_ERR_NO_FILE - ], - 'size' => [ - 0 => 256, - 1 => 4096, - 2 => 0 - ] - ] - ], - 'nestedDictionary' => [ - 'level2' => [ - 'name' => [ - 'file0' => 'nestedDictionary0.jpg', - 'file1' => 'nestedDictionary1.jpg' - ], - 'type' => [ - 'file0' => 'image/png', - 'file1' => 'image/png' - ], - 'tmp_name' => [ - 'file0' => '/tmp/phppng0', - 'file1' => '/tmp/phppng1' - ], - 'error' => [ - 'file0' => UPLOAD_ERR_OK, - 'file1' => UPLOAD_ERR_OK - ], - 'size' => [ - 'file0' => 256, - 'file1' => 4096 - ] - ] - ] - ]; - $request = ServerRequest::getServerRequest(); - $current = $request->getUploadedFiles(); - foreach ($path as $item) { - $current = $current[$item]; - } - $this->assertEquals($file, $current); - } - - public function uploadedFileProvider() - { - return [ - [new UploadedFile('single.txt', 'text/plain', 524, '/tmp/php9hNlHe', UPLOAD_ERR_OK), ['single']], - [new UploadedFile('nested.json', 'application/json', 1024, '/tmp/phpadhjk', UPLOAD_ERR_OK), ['nested', 'level2']], - [new UploadedFile('nestedList0.jpg', 'image/jpeg', 256, '/tmp/phpjpg0', UPLOAD_ERR_OK), ['nestedList', 'level2', 0]], - [new UploadedFile('nestedList1.jpg', 'image/jpeg', 4096, '/tmp/phpjpg1', UPLOAD_ERR_OK), ['nestedList', 'level2', 1]], - [new UploadedFile('', '', 0, '', UPLOAD_ERR_NO_FILE), ['nestedList', 'level2', 2]], - [new UploadedFile('nestedDictionary0.jpg', 'image/png', 256, '/tmp/phppng0', UPLOAD_ERR_OK), ['nestedDictionary', 'level2', 'file0']], - [new UploadedFile('nestedDictionary1.jpg', 'image/png', 4096, '/tmp/phppngg1', UPLOAD_ERR_OK), ['nestedDictionary', 'level2', 'file1']] - ]; - } - - /** - * @backupGlobals enabled - * @dataProvider formContentTypeProvider - */ - public function testGetServerRequestParsesFormBody($contentType) - { - $_SERVER = [ - 'HTTP_HOST' => 'localhost', - 'HTTP_CONTENT_TYPE' => $contentType, - ]; - $_COOKIE = []; - $_FILES = []; - $_POST = [ - 'dog' => 'Bear' - ]; - $request = ServerRequest::getServerRequest(); - $this->assertEquals('Bear', $request->getParsedBody()['dog']); - } - - public function formContentTypeProvider() - { - return [ - ['application/x-www-form-urlencoded'], - ['multipart/form-data'] - ]; - } - - /** @depends testGetServerRequestReadsFromRequest */ - public function testGetServerRequestProvidesAttributesIfPassed($request) - { - /** @var ServerRequest $request */ - $this->assertEquals('Claude', $request->getAttribute('guinea_pig')); - } - // ------------------------------------------------------------------------ // Server Params @@ -393,14 +25,17 @@ class ServerRequestTest extends TestCase $this->assertEquals([], $request->getCookieParams()); } - /** @depends testGetServerRequestReadsFromRequest */ - public function testWithCookieParamsCreatesNewInstance($request1) + public function testWithCookieParamsCreatesNewInstanceWithCookies() { - /** @var ServerRequest $request1 */ - $request2 = $request1->withCookieParams([ + $cookies = [ 'cat' => 'Oscar' - ]); - $this->assertNotEquals($request1->getCookieParams()['cat'], $request2->getCookieParams()['cat']); + ]; + + $request1 = new ServerRequest(); + $request2 = $request1->withCookieParams($cookies); + + $this->assertEquals($cookies, $request2->getCookieParams()); + $this->assertNotSame($request2, $request1); } // ------------------------------------------------------------------------ @@ -412,14 +47,17 @@ class ServerRequestTest extends TestCase $this->assertEquals([], $request->getQueryParams()); } - /** @depends testGetServerRequestReadsFromRequest */ - public function testWithQueryParamsCreatesNewInstance($request1) + public function testWithQueryParamsCreatesNewInstance() { - /** @var ServerRequest $request1 */ - $request2 = $request1->withQueryParams([ - 'guinea_pig' => 'Clyde' - ]); - $this->assertNotEquals($request1->getQueryParams()['guinea_pig'], $request2->getQueryParams()['guinea_pig']); + $query = [ + 'cat' => 'Aggie' + ]; + + $request1 = new ServerRequest(); + $request2 = $request1->withQueryParams($query); + + $this->assertEquals($query, $request2->getQueryParams()); + $this->assertNotSame($request2, $request1); } // ------------------------------------------------------------------------ @@ -431,19 +69,6 @@ class ServerRequestTest extends TestCase $this->assertEquals([], $request->getUploadedFiles()); } - /** @backupGlobals enabled */ - public function testGetUploadedFilesReturnsEmptyArrayWhenNoFilesAreUploaded() - { - $_SERVER = [ - 'HTTP_HOST' => 'localhost', - 'HTTP_ACCEPT' => 'application/json', - 'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded' - ]; - $_FILES = []; - $request = ServerRequest::getServerRequest(); - $this->assertSame([], $request->getUploadedFiles()); - } - public function testWithUploadedFilesCreatesNewInstance() { $uploadedFiles = [ @@ -566,18 +191,17 @@ class ServerRequestTest extends TestCase $this->assertNull($request->getParsedBody()); } - /** @depends testGetServerRequestReadsFromRequest */ - public function testWithParsedBodyCreatesNewInstance($request1) + public function testWithParsedBodyCreatesNewInstance() { - /** @var ServerRequest $request1 */ - $body1 = $request1->getParsedBody(); - - $request2 = $request1->withParsedBody([ + $body = [ 'guinea_pig' => 'Clyde' - ]); - $body2 = $request2->getParsedBody(); + ]; - $this->assertNotSame($body1, $body2); + $request1 = new ServerRequest(); + $request2 = $request1->withParsedBody($body); + + $this->assertEquals($body, $request2->getParsedBody()); + $this->assertNotSame($request2, $request1); } /** diff --git a/test/tests/unit/ServerTest.php b/test/tests/unit/ServerTest.php index a2a854d..6f42d49 100644 --- a/test/tests/unit/ServerTest.php +++ b/test/tests/unit/ServerTest.php @@ -1,19 +1,15 @@ shouldHaveBeenCalled(); } - - // ------------------------------------------------------------------------- - - public function testCreatesStockTransmitterByDefault() - { - $content = 'Hello, world!'; - - $response = (new Response()) - ->withBody(new Stream($content)); - - $server = new Server(); - $server->add(function () use ($response) { - return $response; - }); - - ob_start(); - $server->respond(); - $captured = ob_get_contents(); - ob_end_clean(); - - $this->assertEquals($content, $captured); - } }