From 7b15d2cfca65c0e35b8f2387b5169031285791fe Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 1 Jan 2024 16:52:23 -0700 Subject: [PATCH] 100% coverage for net classes --- flight/net/Response.php | 34 ++++-- tests/ResponseTest.php | 238 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 tests/ResponseTest.php diff --git a/flight/net/Response.php b/flight/net/Response.php index 6b69c67..38d06ec 100644 --- a/flight/net/Response.php +++ b/flight/net/Response.php @@ -202,7 +202,7 @@ class Response /** * Sets caching headers for the response. * - * @param int|string|false $expires Expiration time + * @param int|string|false $expires Expiration time as time() or as strtotime() string value * * @return Response Self reference */ @@ -237,7 +237,8 @@ class Response { // Send status code header if (false !== strpos(\PHP_SAPI, 'cgi')) { - header( + // @codeCoverageIgnoreStart + $this->setRealHeader( sprintf( 'Status: %d %s', $this->status, @@ -245,8 +246,9 @@ class Response ), true ); + // @codeCoverageIgnoreEnd } else { - header( + $this->setRealHeader( sprintf( '%s %d %s', $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1', @@ -262,10 +264,10 @@ class Response foreach ($this->headers as $field => $value) { if (\is_array($value)) { foreach ($value as $v) { - header($field . ': ' . $v, false); + $this->setRealHeader($field . ': ' . $v, false); } } else { - header($field . ': ' . $value); + $this->setRealHeader($field . ': ' . $value); } } @@ -274,13 +276,27 @@ class Response $length = $this->getContentLength(); if ($length > 0) { - header('Content-Length: ' . $length); + $this->setRealHeader('Content-Length: ' . $length); } } return $this; } + /** + * Sets a real header. Mostly used for test mocking. + * + * @param string $header_string The header string you would pass to header() + * @param bool $replace The optional replace parameter indicates whether the header should replace a previous similar header, or add a second header of the same type. By default it will replace, but if you pass in false as the second argument you can force multiple headers of the same type. + * @param int $response_code The response code to send + * @return self + * @codeCoverageIgnore + */ + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): self { + header($header_string, $replace, $response_code); + return $this; + } + /** * Gets the content length. * @@ -294,7 +310,7 @@ class Response } /** - * Gets whether response was sent. + * Gets whether response body was sent. */ public function sent(): bool { @@ -307,11 +323,11 @@ class Response public function send(): void { if (ob_get_length() > 0) { - ob_end_clean(); + ob_end_clean(); // @codeCoverageIgnore } if (!headers_sent()) { - $this->sendHeaders(); + $this->sendHeaders(); // @codeCoverageIgnore } echo $this->body; diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php new file mode 100644 index 0000000..cd3e582 --- /dev/null +++ b/tests/ResponseTest.php @@ -0,0 +1,238 @@ + + * @license MIT, http://flightphp.com/license + */ + +use flight\net\Request; +use flight\net\Response; +use flight\util\Collection; + +class ResponseTest extends PHPUnit\Framework\TestCase +{ + + protected function setUp(): void + { + $_SERVER = []; + $_REQUEST = []; + // $_SERVER['REQUEST_URI'] = '/'; + // $_SERVER['SCRIPT_NAME'] = '/index.php'; + // $_SERVER['REQUEST_METHOD'] = 'GET'; + // $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + // $_SERVER['REMOTE_ADDR'] = '8.8.8.8'; + // $_SERVER['HTTP_X_FORWARDED_FOR'] = '32.32.32.32'; + // $_SERVER['HTTP_HOST'] = 'example.com'; + // $_SERVER['CONTENT_TYPE'] = ''; + + $_GET = []; + $_POST = []; + $_COOKIE = []; + $_FILES = []; + + // $this->request = new Request(); + } + + protected function tearDown(): void { + unset($_REQUEST); + unset($_SERVER); + } + + public function testStatusDefault() { + $response = new Response(); + $this->assertSame(200, $response->status()); + } + + public function testStatusValidCode() { + $response = new Response(); + $response->status(200); + $this->assertEquals(200, $response->status()); + } + + public function testStatusInvalidCode() { + $response = new Response(); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Invalid status code.'); + $response->status(999); + } + + public function testStatusReturnObject() { + $response = new Response(); + $this->assertEquals($response, $response->status(200)); + } + + public function testHeaderSingle() { + $response = new Response(); + $response->header('Content-Type', 'text/html'); + $this->assertEquals(['Content-Type' => 'text/html'], $response->headers()); + } + + public function testHeaderSingleKeepCaseSensitive() { + $response = new Response(); + $response->header('content-type', 'text/html'); + $response->header('x-test', 'test'); + $this->assertEquals(['content-type' => 'text/html', 'x-test' => 'test'], $response->headers()); + } + + public function testHeaderArray() { + $response = new Response(); + $response->header(['Content-Type' => 'text/html', 'X-Test' => 'test']); + $this->assertEquals(['Content-Type' => 'text/html', 'X-Test' => 'test'], $response->headers()); + } + + public function testHeaderReturnObject() { + $response = new Response(); + $this->assertEquals($response, $response->header('Content-Type', 'text/html')); + } + + public function testWrite() { + $response = new class extends Response { + public function getBody() { + return $this->body; + } + }; + $response->write('test'); + $this->assertEquals('test', $response->getBody()); + } + + public function testWriteEmptyString() { + $response = new class extends Response { + public function getBody() { + return $this->body; + } + }; + $response->write(''); + $this->assertEquals('', $response->getBody()); + } + + public function testWriteReturnObject() { + $response = new Response(); + $this->assertEquals($response, $response->write('test')); + } + + public function testClear() { + $response = new class extends Response { + public function getBody() { + return $this->body; + } + }; + $response->write('test'); + $response->status(404); + $response->header('Content-Type', 'text/html'); + $response->clear(); + $this->assertEquals('', $response->getBody()); + $this->assertEquals(200, $response->status()); + $this->assertEquals([], $response->headers()); + } + + public function testCacheSimple() { + $response = new Response(); + $cache_time = time() + 60; + $response->cache($cache_time); + $this->assertEquals([ + 'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT', + 'Cache-Control' => 'max-age=60' + ], $response->headers()); + } + + public function testCacheSimpleWithString() { + $response = new Response(); + $cache_time = time() + 60; + $response->cache('now +60 seconds'); + $this->assertEquals([ + 'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT', + 'Cache-Control' => 'max-age=60' + ], $response->headers()); + } + + public function testCacheSimpleWithPragma() { + $response = new Response(); + $cache_time = time() + 60; + $response->header('Pragma', 'no-cache'); + $response->cache($cache_time); + $this->assertEquals([ + 'Expires' => gmdate('D, d M Y H:i:s', $cache_time) . ' GMT', + 'Cache-Control' => 'max-age=60' + ], $response->headers()); + } + + public function testCacheFalseExpiresValue() { + $response = new Response(); + $response->cache(false); + $this->assertEquals([ + 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT', + 'Cache-Control' => [ + 'no-store, no-cache, must-revalidate', + 'post-check=0, pre-check=0', + 'max-age=0', + ], + 'Pragma' => 'no-cache' + ], $response->headers()); + } + + public function testSendHeadersRegular() { + $response = new class extends Response { + protected $test_sent_headers = []; + + protected array $headers = [ + 'Cache-Control' => [ + 'no-store, no-cache, must-revalidate', + 'post-check=0, pre-check=0', + 'max-age=0', + ] + ]; + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response + { + $this->test_sent_headers[] = $header_string; + return $this; + } + + public function getSentHeaders(): array + { + return $this->test_sent_headers; + } + }; + $response->header('Content-Type', 'text/html'); + $response->header('X-Test', 'test'); + $response->write('Something'); + + $response->sendHeaders(); + $sent_headers = $response->getSentHeaders(); + $this->assertEquals([ + 'HTTP/1.1 200 OK', + 'Cache-Control: no-store, no-cache, must-revalidate', + 'Cache-Control: post-check=0, pre-check=0', + 'Cache-Control: max-age=0', + 'Content-Type: text/html', + 'X-Test: test', + 'Content-Length: 9' + ], $sent_headers); + } + + public function testSentDefault() { + $response = new Response(); + $this->assertFalse($response->sent()); + } + + public function testSentTrue() { + $response = new class extends Response { + protected $test_sent_headers = []; + + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response + { + $this->test_sent_headers[] = $header_string; + return $this; + } + }; + $response->header('Content-Type', 'text/html'); + $response->header('X-Test', 'test'); + $response->write('Something'); + + $this->expectOutputString('Something'); + $response->send(); + $this->assertTrue($response->sent()); + } + + +}