diff --git a/.travis.yml b/.travis.yml index 7017cfe..34e44dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,8 @@ php: before_script: - travis_retry composer self-update - - travis_retry composer --prefer-source install - - composer require satooshi/php-coveralls + - travis_retry composer install --prefer-source + - composer require satooshi/php-coveralls --prefer-source script: - mkdir -p build/logs diff --git a/README.md b/README.md index 3002593..6070a52 100644 --- a/README.md +++ b/README.md @@ -237,7 +237,7 @@ $file->to_array(); ## Response -The response to a request is represented by a [Response](http://icanboogie.org/docs/class-ICanBoogie.HTTP.Response.html) instance. +The response to a request is represented by a [Response][] instance. ```php status->code; // 302 +$response->status->is_redirect; // true +``` + + + + + ## Headers -HTTP headers are represented by a [Headers](http://icanboogie.org/docs/class-ICanBoogie.HTTP.Headers.html) -instance. There are used by requests and responses, but can also be used to create the headers -string of the `mail()` command. +HTTP headers are represented by a [Headers][] instance. There are used by requests and +responses, but can also be used to create the headers string of the `mail()` command. @@ -828,6 +845,7 @@ ICanBoogie/HTTP is licensed under the New BSD License - See the [LICENSE](LICENS [DispatchEvent]: http://icanboogie.org/docs/class-ICanBoogie.HTTP.Dispatcher.DispatchEvent.html [Dispatcher]: http://icanboogie.org/docs/class-ICanBoogie.HTTP.Dispatcher.html [DispatcherInterface]: http://icanboogie.org/docs/class-ICanBoogie.HTTP.DispatcherInterface.html +[Headers]: http://icanboogie.org/docs/class-ICanBoogie.HTTP.Headers.html [Response]: http://icanboogie.org/docs/class-ICanBoogie.HTTP.Response.html [RedirectResponse]: http://icanboogie.org/docs/class-ICanBoogie.HTTP.RedirectResponse.html [NotFound]: http://icanboogie.org/docs/class-ICanBoogie.HTTP.NotFound.html diff --git a/composer.json b/composer.json index d4cf7c0..ef7087e 100644 --- a/composer.json +++ b/composer.json @@ -27,13 +27,9 @@ "psr-4": { "ICanBoogie\\HTTP\\": "lib/" }, - "classmap": [ - "lib/RescueEvent.php" - ], - "files": [ "helpers.php" ] } } diff --git a/lib/Dispatcher.php b/lib/Dispatcher.php index 30f7a58..70aa91a 100644 --- a/lib/Dispatcher.php +++ b/lib/Dispatcher.php @@ -335,11 +335,17 @@ public function __construct($callable) $this->callable = $callable; } + /** + * @inheritdoc + */ public function __invoke(Request $request) { return call_user_func($this->callable, $request); } + /** + * @inheritdoc + */ public function rescue(\Exception $exception, Request $request) { throw $exception; diff --git a/lib/RedirectResponse.php b/lib/RedirectResponse.php index 4f089a2..5c01df7 100644 --- a/lib/RedirectResponse.php +++ b/lib/RedirectResponse.php @@ -25,7 +25,7 @@ class RedirectResponse extends Response * * @throws \InvalidArgumentException if the provided status code is not a redirect. */ - public function __construct($url, $status=302, array $headers=[]) + public function __construct($url, $status = 302, array $headers = []) { parent::__construct ( @@ -53,7 +53,7 @@ function(Response $response) { $status, [ 'Location' => $url ] + $headers ); - if (!$this->is_redirect) + if (!$this->status->is_redirect) { throw new \InvalidArgumentException("The HTTP status code is not a redirect: {$status}."); } diff --git a/lib/Response.php b/lib/Response.php index 3f08bb8..63ce99b 100644 --- a/lib/Response.php +++ b/lib/Response.php @@ -11,31 +11,30 @@ namespace ICanBoogie\HTTP; +use ICanBoogie\Accessor\AccessorTrait; + /** * A response to a HTTP request. * - * @property integer $status The HTTP status code. - * See: {@link set_status()} {@link get_status()} - * @property string $status_message The HTTP status message. - * See: {@link set_status_message()} {@link get_status_message()} + * @property Status|mixed $status + * * @property int $ttl Adjusts the `s-maxage` part of the `Cache-Control` header field definition according to the `Age` header field definition. * See: {@link set_ttl()} {@link get_ttl()} - * * @property int $age Shortcut to the `Age` header field definition. * See: {@link set_age()} {@link get_age()} - * @property \ICanBoogie\HTTP\Headers\CacheControl $cache_control Shortcut to the `Cache-Control` header field definition. + * @property Headers\CacheControl $cache_control Shortcut to the `Cache-Control` header field definition. * See: {@link set_cache_control()} {@link get_cache_control()} * @property int $content_length Shortcut to the `Content-Length` header field definition. * See: {@link set_content_length()} {@link get_content_length()} - * @property \ICanBoogie\HTTP\Headers\ContentType $content_type Shortcut to the `Content-Type` header field definition. + * @property Headers\ContentType $content_type Shortcut to the `Content-Type` header field definition. * See: {@link set_content_type()} {@link get_content_type()} - * @property \ICanBoogie\HTTP\Headers\Date $date Shortcut to the `Date` header field definition. + * @property Headers\Date $date Shortcut to the `Date` header field definition. * See: {@link set_date()} {@link get_date()} * @property string $etag Shortcut to the `Etag` header field definition. * See: {@link set_etag()} {@link get_etag()} - * @property \ICanBoogie\HTTP\Headers\Date $expires Shortcut to the `Expires` header field definition. + * @property Headers\Date $expires Shortcut to the `Expires` header field definition. * See: {@link set_expires()} {@link get_expires()} - * @property \ICanBoogie\HTTP\Headers\Date $last_modified Shortcut to the `Last-Modified` header field definition. + * @property Headers\Date $last_modified Shortcut to the `Last-Modified` header field definition. * See: {@link set_last_modified()} {@link get_last_modified()} * @property string $location Shortcut to the `Location` header field definition. * See: {@link set_location()} {@link get_location()} @@ -44,80 +43,15 @@ * See: {@link set_body()} {@link get_body()} * * @property-read boolean $is_cacheable {@link get_is_cacheable()} - * @property-read boolean $is_client_error {@link get_is_client_error()} - * @property-read boolean $is_empty {@link get_is_empty()} - * @property-read boolean $is_forbidden {@link get_is_forbidden()} * @property-read boolean $is_fresh {@link get_is_fresh()} - * @property-read boolean $is_informational {@link get_is_informational()} - * @property-read boolean $is_not_found {@link get_is_not_found()} - * @property-read boolean $is_ok {@link get_is_ok()} * @property-read boolean $is_private {@link get_is_private()} - * @property-read boolean $is_redirect {@link get_is_redirect()} - * @property-read boolean $is_server_error {@link get_is_server_error()} - * @property-read boolean $is_successful {@link get_is_successful()} - * @property-read boolean $is_valid {@link get_is_valid()} * @property-read boolean $is_validateable {@link get_is_validateable()} * * @see http://tools.ietf.org/html/rfc2616 */ class Response { - use \ICanBoogie\PrototypeTrait; - - /** - * HTTP status messages. - * - * @var array[int]string - */ - static public $status_messages = [ - - 100 => "Continue", - 101 => "Switching Protocols", - - 200 => "OK", - 201 => "Created", - 202 => "Accepted", - 203 => "Non-Authoritative Information", - 204 => "No Content", - 205 => "Reset Content", - 206 => "Partial Content", - - 300 => "Multiple Choices", - 301 => "Moved Permanently", - 302 => "Found", - 303 => "See Other", - 304 => "Not Modified", - 305 => "Use Proxy", - 307 => "Temporary Redirect", - - 400 => "Bad Request", - 401 => "Unauthorized", - 402 => "Payment Required", - 403 => "Forbidden", - 404 => "Not Found", - 405 => "Method Not Allowed", - 406 => "Not Acceptable", - 407 => "Proxy Authentication Required", - 408 => "Request Timeout", - 409 => "Conflict", - 410 => "Gone", - 411 => "Length Required", - 412 => "Precondition Failed", - 413 => "Request Entity Too Large", - 414 => "Request-URI Too Long", - 415 => "Unsupported Media Type", - 416 => "Requested Range Not Satisfiable", - 417 => "Expectation Failed", - 418 => "I'm a teapot", - - 500 => "Internal Server Error", - 501 => "Not Implemented", - 502 => "Bad Gateway", - 503 => "Service Unavailable", - 504 => "Gateway Timeout", - 505 => "HTTP Version Not Supported" - - ]; + use AccessorTrait; /** * Response headers. @@ -138,7 +72,7 @@ class Response * properties. * * @param mixed $body The body of the response. - * @param int $status The status code of the response. + * @param Status|int $status The status code of the response. * @param Headers|array $headers The initial header fields of the response. */ public function __construct($body=null, $status=200, $headers=[]) @@ -168,11 +102,12 @@ public function __construct($body=null, $status=200, $headers=[]) } /** - * Clones the {@link $headers] property. + * Clones the {@link $headers} and {@link $status} properties. */ public function __clone() { $this->headers = clone $this->headers; + $this->status = clone $this->status; } /** @@ -195,7 +130,7 @@ public function __toString() $body = ob_get_clean(); - return "HTTP/{$this->version} {$this->status} {$this->status_message}\r\n" + return "HTTP/{$this->version} {$this->status}\r\n" . $header . "\r\n" . $body; @@ -209,8 +144,7 @@ public function __toString() /** * Issues the HTTP response. * - * The header is modified according to the {@link version}, {@link status} and - * {@link status_message} properties. + * The header is modified according to the {@link version} and {@link status} properties. * * The usual behavior of the response is to echo its body and then terminate the script. But * if its body is `null` the following happens : @@ -251,12 +185,12 @@ public function __invoke() header_remove('Pragma'); header_remove('X-Powered-By'); - header("HTTP/{$this->version} {$this->status} {$this->status_message}"); + header("HTTP/{$this->version} {$this->status}"); $headers(); } - if ($body === null && ($this->location || !$this->is_ok)) + if ($body === null && ($this->location || !$this->status->is_ok)) { return; } @@ -292,58 +226,24 @@ protected function finalize(Headers &$headers, &$body) /** * Status of the HTTP response. * - * @var int + * @var Status */ private $status; - /** - * Message describing the status code. - * - * @var string - */ - public $status_message; - /** * Sets response status code and optionally status message. * - * This method is the setter for the {@link $status} property. - * * @param integer|array $status HTTP status code or HTTP status code and HTTP status message. - * - * @throws \InvalidArgumentException When the HTTP status code is not valid. */ protected function set_status($status) { - $status_message = null; - - if (is_array($status)) - { - list($status, $status_message) = $status; - } - - $this->status = (int) $status; - - if (!$this->is_valid) - { - throw new StatusCodeNotValid($status); - } - - if ($status_message === null) - { - unset($this->status_message); - } - else - { - $this->status_message = $status_message; - } + $this->status = Status::from($status); } /** - * Returns the response status code. + * Returns the response status. * - * This method is the getter for the {@link $status} property. - * - * @return integer + * @return Status */ protected function get_status() { @@ -436,19 +336,6 @@ protected function echo_body($body) } } - /** - * Returns the message associated with the status code. - * - * This method is the volatile getter for the {@link $status_message} property and is only - * called if the property is not accessible. - * - * @return string - */ - protected function get_status_message() - { - return self::$status_messages[$this->status]; - } - /** * Sets the value of the `Location` header field. * @@ -678,171 +565,6 @@ protected function get_ttl() return null; } - /** - * Set the `cacheable` property of the `Cache-Control` header field to `private` or `public`. - * - * @param boolean $value Set `cacheable` to `private` if `true`, `public` if `false`. - */ - protected function set_is_private($value) - { - $this->cache_control->cacheable = $value ? 'private' : 'public'; - } - - /** - * Checks that the `cacheable` property of the `Cache-Control` header field is `private`. - * - * @return boolean - */ - protected function get_is_private() - { - return $this->cache_control->cacheable == 'private'; - } - - /** - * Checks if the response is valid. - * - * A response is considered valid when its status is between 100 and 600, 100 included. - * - * Note: This method is the getter for the `is_valid` magic property. - * - * @return boolean - */ - protected function get_is_valid() - { - return $this->status >= 100 && $this->status < 600; - } - - /** - * Checks if the response is informational. - * - * A response is considered informational when its status is between 100 and 200, 100 included. - * - * Note: This method is the getter for the `is_informational` magic property. - * - * @return boolean - */ - protected function get_is_informational() - { - return $this->status >= 100 && $this->status < 200; - } - - /** - * Checks if the response is successful. - * - * A response is considered successful when its status is between 200 and 300, 200 included. - * - * Note: This method is the getter for the `is_successful` magic property. - * - * @return boolean - */ - protected function get_is_successful() - { - return $this->status >= 200 && $this->status < 300; - } - - /** - * Checks if the response is a redirection. - * - * A response is considered to be a redirection when its status is between 300 and 400, 300 - * included. - * - * Note: This method is the getter for the `is_redirect` magic property. - * - * @return boolean - */ - protected function get_is_redirect() - { - return $this->status >= 300 && $this->status < 400; - } - - /** - * Checks if the response is a client error. - * - * A response is considered a client error when its status is between 400 and 500, 400 - * included. - * - * Note: This method is the getter for the `is_client_error` magic property. - * - * @return boolean - */ - protected function get_is_client_error() - { - return $this->status >= 400 && $this->status < 500; - } - - /** - * Checks if the response is a server error. - * - * A response is considered a server error when its status is between 500 and 600, 500 - * included. - * - * Note: This method is the getter for the `is_server_error` magic property. - * - * @return boolean - */ - protected function get_is_server_error() - { - return $this->status >= 500 && $this->status < 600; - } - - /** - * Checks if the response is ok. - * - * A response is considered ok when its status is 200. - * - * Note: This method is the getter for the `is_ok` magic property. - * - * @return boolean - */ - protected function get_is_ok() - { - return $this->status == 200; - } - - /** - * Checks if the response is forbidden. - * - * A response is forbidden ok when its status is 403. - * - * Note: This method is the getter for the `is_forbidden` magic property. - * - * @return boolean - */ - protected function get_is_forbidden() - { - return $this->status == 403; - } - - /** - * Checks if the response is not found. - * - * A response is considered not found when its status is 404. - * - * Note: This method is the getter for the `is_not_found` magic property. - * - * @return boolean - */ - protected function get_is_not_found() - { - return $this->status == 404; - } - - /** - * Checks if the response is empty. - * - * A response is considered empty when its status is 201, 204 or 304. - * - * Note: This method is the getter for the `is_empty` magic property. - * - * @return boolean - */ - protected function get_is_empty() - { - static $range = [ 201, 204, 304 ]; - - return in_array($this->status, $range); - } - /** * Checks that the response includes header fields that can be used to validate the response * with the origin server using a conditional GET request. @@ -858,23 +580,16 @@ protected function get_is_validateable() * Checks that the response is worth caching under any circumstance. * * Responses marked _private_ with an explicit `Cache-Control` directive are considered - * uncacheable. + * not cacheable. * * Responses with neither a freshness lifetime (Expires, max-age) nor cache validator - * (`Last-Modified`, `ETag`) are considered uncacheable. + * (`Last-Modified`, `ETag`) are considered not cacheable. * * @return boolean */ protected function get_is_cacheable() { - static $range = [ 200, 203, 300, 301, 302, 404, 410 ]; - - if (!in_array($this->status, $range)) - { - return false; - } - - if ($this->cache_control->no_store || $this->cache_control->cacheable == 'private') + if (!$this->status->is_cacheable || $this->cache_control->no_store || $this->cache_control->cacheable == 'private') { return false; } diff --git a/lib/Status.php b/lib/Status.php new file mode 100644 index 0000000..7503a0e --- /dev/null +++ b/lib/Status.php @@ -0,0 +1,348 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ICanBoogie\HTTP; + +use ICanBoogie\Accessor\AccessorTrait; + +/** + * Class Status + * + * @package ICanBoogie\HTTP + * + * @property int $code + * @property string $message + * + * @property-read bool $is_cacheable Whether the status is cacheable. + * @property-read bool $is_client_error Whether the status is a client error. + * @property-read bool $is_empty Whether the status is empty. + * @property-read bool $is_forbidden Whether the status is forbidden. + * @property-read bool $is_informational Whether the status is informational. + * @property-read bool $is_not_found Whether the status is not found. + * @property-read bool $is_ok Whether the status is ok. + * @property-read bool $is_redirect Whether the status is a redirection. + * @property-read bool $is_server_error Whether the status is a server error. + * @property-read bool $is_successful Whether the status is successful. + * @property-read bool $is_valid Whether the status is valid. + */ +class Status +{ + use AccessorTrait; + + /** + * HTTP status codes and messages. + * + * @var array + */ + static public $codes_and_messages = [ + + 100 => "Continue", + 101 => "Switching Protocols", + + 200 => "OK", + 201 => "Created", + 202 => "Accepted", + 203 => "Non-Authoritative Information", + 204 => "No Content", + 205 => "Reset Content", + 206 => "Partial Content", + + 300 => "Multiple Choices", + 301 => "Moved Permanently", + 302 => "Found", + 303 => "See Other", + 304 => "Not Modified", + 305 => "Use Proxy", + 307 => "Temporary Redirect", + + 400 => "Bad Request", + 401 => "Unauthorized", + 402 => "Payment Required", + 403 => "Forbidden", + 404 => "Not Found", + 405 => "Method Not Allowed", + 406 => "Not Acceptable", + 407 => "Proxy Authentication Required", + 408 => "Request Timeout", + 409 => "Conflict", + 410 => "Gone", + 411 => "Length Required", + 412 => "Precondition Failed", + 413 => "Request Entity Too Large", + 414 => "Request-URI Too Long", + 415 => "Unsupported Media Type", + 416 => "Requested Range Not Satisfiable", + 417 => "Expectation Failed", + 418 => "I'm a teapot", + + 500 => "Internal Server Error", + 501 => "Not Implemented", + 502 => "Bad Gateway", + 503 => "Service Unavailable", + 504 => "Gateway Timeout", + 505 => "HTTP Version Not Supported" + + ]; + + /** + * Creates a new instance from the provided status. + * + * @param $status + * + * @return Status + * + * @throws \InvalidArgumentException When the HTTP status code is not valid. + */ + static public function from($status) + { + if ($status instanceof self) + { + return $status; + } + + $message = null; + + if (is_array($status)) + { + list($code, $message) = $status; + } + elseif (is_numeric($status)) + { + $code = (int) $status; + } + else + { + if (!preg_match('/^(\d{3})\s+(.+)$/', $status, $matches)) + { + throw new \InvalidArgumentException("Invalid status: $status."); + } + + list(, $code, $message) = $matches; + } + + return new static($code, $message); + } + + /** + * Asserts that a status code is valid. + * + * @param $code + * + * @throws StatusCodeNotValid if the status code is not valid. + */ + static private function assert_code_is_valid($code) + { + if ($code >= 100 && $code < 600) + { + return; + } + + throw new StatusCodeNotValid($code); + } + + /** + * Status code. + * + * @var int + */ + private $code; + + protected function set_code($code) + { + self::assert_code_is_valid($code); + + $this->code = $code; + } + + protected function get_code() + { + return $this->code; + } + + /** + * Whether the status is valid. + * + * A status is considered valid when its code is between 100 and 600, 100 included. + * + * @return bool + */ + protected function get_is_valid() + { + return $this->code >= 100 && $this->code < 600; + } + + /** + * Whether the status is informational. + * + * A status is considered informational when its code is between 100 and 200, 100 included. + * + * @return bool + */ + protected function get_is_informational() + { + return $this->code >= 100 && $this->code < 200; + } + + /** + * Whether the status is successful. + * + * A status is considered successful when its code is between 200 and 300, 200 included. + * + * @return bool + */ + protected function get_is_successful() + { + return $this->code >= 200 && $this->code < 300; + } + + /** + * Whether the status is a redirection. + * + * A status is considered to be a redirection when its code is between 300 and 400, 300 + * included. + * + * @return bool + */ + protected function get_is_redirect() + { + return $this->code >= 300 && $this->code < 400; + } + + /** + * Whether the status is a client error. + * + * A status is considered a client error when its code is between 400 and 500, 400 + * included. + * + * @return bool + */ + protected function get_is_client_error() + { + return $this->code >= 400 && $this->code < 500; + } + + /** + * Whether the status is a server error. + * + * A status is considered a server error when its code is between 500 and 600, 500 + * included. + * + * @return bool + */ + protected function get_is_server_error() + { + return $this->code >= 500 && $this->code < 600; + } + + /** + * Whether the status is ok. + * + * A status is considered ok when its code is 200. + * + * @return bool + */ + protected function get_is_ok() + { + return $this->code == 200; + } + + /** + * Whether the status is forbidden. + * + * A status is considered forbidden ok when its code is 403. + * + * @return bool + */ + protected function get_is_forbidden() + { + return $this->code == 403; + } + + /** + * Whether the status is not found. + * + * A status is considered not found when its code is 404. + * + * @return bool + */ + protected function get_is_not_found() + { + return $this->code == 404; + } + + /** + * Whether the status is empty. + * + * A status is considered empty when its code is 201, 204 or 304. + * + * @return bool + */ + protected function get_is_empty() + { + static $range = [ 201, 204, 304 ]; + + return in_array($this->code, $range); + } + + /** + * Whether the status is cacheable. + * + * @return bool + */ + protected function get_is_cacheable() + { + static $range = [ 200, 203, 300, 301, 302, 404, 410 ]; + + return in_array($this->code, $range); + } + + /** + * Message describing the status code. + * + * @var string + */ + private $message; + + protected function set_message($message) + { + $this->message = $message; + } + + protected function get_message() + { + $message = $this->message; + $code = $this->code; + + if (!$message && $code) + { + $message = self::$codes_and_messages[$code]; + } + + return $message; + } + + /** + * @param int $code + * @param string|null $message + */ + public function __construct($code = 200, $message = null) + { + self::assert_code_is_valid($code); + + $this->code = $code; + $this->message = $message ?: self::$codes_and_messages[$code]; + } + + public function __toString() + { + return "$this->code " . $this->get_message(); + } +} diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index 3bd30d5..b3b516b 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -159,7 +159,8 @@ public function test_head_fallback() $response = $dispatcher($request); $this->assertInstanceOf('ICanBoogie\HTTP\Response', $response); - $this->assertEquals(200, $response->status); + $this->assertInstanceOf('ICanBoogie\HTTP\Status', $response->status); + $this->assertEquals(200, $response->status->code); $this->assertEquals(strlen($message), $response->content_length); $this->assertEquals('yes', $response->headers['X-Was-Get']); $this->assertNull($response->body); diff --git a/tests/RedirectResponseTest.php b/tests/RedirectResponseTest.php index 02dfd6b..dd7dcb9 100644 --- a/tests/RedirectResponseTest.php +++ b/tests/RedirectResponseTest.php @@ -4,11 +4,14 @@ class RedirectResponseTest extends \PHPUnit_Framework_TestCase { + /** + * @covers \ICanBoogie\HTTP\RedirectResponse::__construct + */ public function test_construct() { $r = new RedirectResponse("/go/to/there"); - $this->assertTrue($r->is_redirect); - $this->assertEquals(302, $r->status); + $this->assertTrue($r->status->is_redirect); + $this->assertEquals(302, $r->status->code); $this->assertEquals("/go/to/there", $r->location); } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index f77a73d..d41b4ff 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -35,9 +35,7 @@ public function test_write_readonly_properties($property) public function provide_test_write_readonly_properties() { - $properties = 'is_valid is_informational is_successful is_redirect is_client_error' - . ' is_server_error is_ok is_forbidden is_not_found is_empty is_validateable' - . ' is_cacheable is_fresh'; + $properties = 'is_validateable is_cacheable is_fresh'; return array_map(function($v) { return (array) $v; }, explode(' ', $properties)); } @@ -110,16 +108,27 @@ public function test_preserve_content_length() $this->assertEquals("HTTP/1.0 200 OK\r\nContent-Length: 123\r\nDate: {$r->date}\r\n\r\n", (string) $r); } - public function test_is_private() + /** + * @covers \ICanBoogie\HTTP\Response::get_is_cacheable + * @dataProvider provide_test_is_cacheable + * + * @param Response $response + * @param bool $expected + */ + public function test_is_cacheable($response, $expected) { - $r = new Response; - $this->assertEmpty($r->cache_control->cacheable); - $this->assertFalse($r->is_private); - $r->is_private = true; - $this->assertTrue($r->is_private); - $this->assertEquals('private', $r->cache_control->cacheable); - $r->is_private = false; - $this->assertFalse($r->is_private); - $this->assertEquals('public', $r->cache_control->cacheable); + $this->assertEquals($expected, $response->is_cacheable); + } + + public function provide_test_is_cacheable() + { + return [ + + [ new Response('A', 200), true ], + [ new Response('A', 200, [ 'Cache-Control' => "public" ]), true ], + [ new Response('A', 200, [ 'Cache-Control' => "private" ]), false ], + [ new Response('A', 405), false ] + + ]; } } diff --git a/tests/StatusTest.php b/tests/StatusTest.php new file mode 100644 index 0000000..9f8fc7f --- /dev/null +++ b/tests/StatusTest.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ICanBoogie\HTTP; + +class StatusTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provide_test_write_readonly_properties + * @expectedException \ICanBoogie\PropertyNotWritable + * + * @param string $property Property name. + */ + public function test_write_readonly_properties($property) + { + $status = new Status; + $status->$property = null; + } + + public function provide_test_write_readonly_properties() + { + $properties = 'is_valid is_informational is_successful is_redirect is_client_error' + . ' is_server_error is_ok is_forbidden is_not_found is_empty'; + + return array_map(function($v) { return (array) $v; }, explode(' ', $properties)); + } + + /** + * @expectedException \ICanBoogie\HTTP\StatusCodeNotValid + * @covers \ICanBoogie\HTTP\Status::set_code + */ + public function test_set_code_invalid() + { + $status = new Status; + $status->code = 12345; + } + + /** + * @covers \ICanBoogie\HTTP\Status::from + * @dataProvider provide_test_from + */ + public function test_from($source, $expected) + { + $status = Status::from($source); + $this->assertEquals($expected, (string) $status); + } + + public function provide_test_from() + { + return [ + + [ 200 , "200 OK" ], + [ [ 200, "Madonna" ], "200 Madonna" ], + [ "200 Madonna", "200 Madonna" ] + + ]; + } + + /** + * @covers \ICanBoogie\HTTP\Status::from + * @expectedException \InvalidArgumentException + */ + public function test_from_invalid_status() + { + Status::from("Invalid status"); + } + + /** + * @covers \ICanBoogie\HTTP\Status::from + * @expectedException \ICanBoogie\HTTP\StatusCodeNotValid + */ + public function test_from_invalid_status_code() + { + Status::from("987 Invalid"); + } + + /** + * @covers \ICanBoogie\HTTP\Status::__construct + */ + public function test_constructor() + { + $status = new Status(301, "Over the rainbow"); + + $this->assertEquals(301, $status->code); + $this->assertEquals("Over the rainbow", $status->message); + $this->assertEquals("301 Over the rainbow", (string) $status); + + $status->message = null; + $this->assertEquals("Moved Permanently", $status->message); + $this->assertEquals("301 Moved Permanently", (string) $status); + } + + /** + * @covers \ICanBoogie\HTTP\Status::__construct + * @expectedException \ICanBoogie\HTTP\StatusCodeNotValid + */ + public function test_constructor_with_invalid_code() + { + new Status(987); + } + + /** + * @covers \ICanBoogie\HTTP\Status::get_is_cacheable + * @dataProvider provide_test_is_cacheable + */ + public function test_is_cacheable($code, $expected) + { + $status = new Status($code); + $this->assertEquals($expected, $status->is_cacheable); + } + + public function provide_test_is_cacheable() + { + return [ + + [ 200, true ], + [ 201, false ], + [ 202, false ], + [ 203, true ], + [ 300, true ], + [ 301, true ], + [ 302, true ], + [ 303, false ], + [ 404, true ], + [ 405, false ], + [ 410, true ] + + ]; + } +}