diff options
author | Arnold Daniels <arnold@jasny.net> | 2016-11-18 18:28:24 +0100 |
---|---|---|
committer | Arnold Daniels <arnold@jasny.net> | 2016-11-18 18:28:24 +0100 |
commit | 43c6d835943b322c036e9ee47800d694cb6bb5de (patch) | |
tree | f56809890b4e65c2f4790fbd8be9593a3538a72c | |
parent | 934b380f473b4e85e807f07d6bf516f4e227e112 (diff) | |
download | controller-43c6d835943b322c036e9ee47800d694cb6bb5de.zip controller-43c6d835943b322c036e9ee47800d694cb6bb5de.tar.gz controller-43c6d835943b322c036e9ee47800d694cb6bb5de.tar.bz2 |
Refactored CheckRequest and CheckResponse traits
WIP Controller\Output
Restructuring the tests (one test per trait)
-rw-r--r-- | phpunit.xml.dist | 2 | ||||
-rw-r--r-- | src/Controller.php | 3 | ||||
-rw-r--r-- | src/Controller/CheckResponse.php | 23 | ||||
-rw-r--r-- | src/Controller/Input.php | 13 | ||||
-rw-r--r-- | src/Controller/Output.php (renamed from src/Controller/Respond.php) | 125 | ||||
-rw-r--r-- | tests/Controller/CheckRequestTest.php | 87 | ||||
-rw-r--r-- | tests/Controller/CheckResponseTest.php | 53 | ||||
-rw-r--r-- | tests/Controller/OutputTest.php | 499 | ||||
-rw-r--r-- | tests/Controller/RouteActionTest.php | 3 | ||||
-rw-r--r-- | tests/Controller/Session/FlashTest.php (renamed from tests/FlashTest.php) | 0 | ||||
-rw-r--r-- | tests/Controller/SessionTest.php | 750 | ||||
-rw-r--r-- | tests/Controller/View/TwigTest.php (renamed from tests/View/TwigTest.php) | 0 | ||||
-rw-r--r-- | tests/ControllerTest.php | 723 | ||||
-rw-r--r-- | tests/bootstrap.php | 4 | ||||
-rw-r--r-- | tests/support/RouteActionController.php | 13 | ||||
-rw-r--r-- | tests/support/SessionController.php | 13 | ||||
-rw-r--r-- | tests/support/TestController.php | 50 | ||||
-rw-r--r-- | tests/support/TestHelper.php | 27 |
18 files changed, 1592 insertions, 796 deletions
diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1861d7c..e8a1ac3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,7 +2,7 @@ <phpunit colors="true" - bootstrap="vendor/autoload.php" + bootstrap="tests/bootstrap.php" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" diff --git a/src/Controller.php b/src/Controller.php index b3014f9..e928339 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -10,7 +10,8 @@ use Psr\Http\Message\ResponseInterface; */ abstract class Controller { - use Controller\Respond, + use Controller\Input, + Controller\Output, Controller\CheckRequest, Controller\CheckResponse; diff --git a/src/Controller/CheckResponse.php b/src/Controller/CheckResponse.php index 7c02d46..7509778 100644 --- a/src/Controller/CheckResponse.php +++ b/src/Controller/CheckResponse.php @@ -18,6 +18,17 @@ trait CheckResponse /** + * Check if response is a 1xx informational + * + * @return boolean + */ + public function isInformational() + { + $code = $this->getResponse()->getStatusCode() ?: 200; + return $code >= 100 && $code < 200; + } + + /** * Check if response is 2xx succesful, or empty * * @return boolean @@ -25,8 +36,7 @@ trait CheckResponse public function isSuccessful() { $code = $this->getResponse()->getStatusCode() ?: 200; - - return !$code || ($code >= 200 && $code < 300); + return $code >= 200 && $code < 300; } /** @@ -37,7 +47,6 @@ trait CheckResponse public function isRedirection() { $code = $this->getResponse()->getStatusCode() ?: 200; - return $code >= 300 && $code < 400; } @@ -49,7 +58,6 @@ trait CheckResponse public function isClientError() { $code = $this->getResponse()->getStatusCode() ?: 200; - return $code >= 400 && $code < 500; } @@ -60,8 +68,9 @@ trait CheckResponse */ public function isServerError() { - return $this->getResponse()->getStatusCode() ?: 200 >= 500; - } + $code = $this->getResponse()->getStatusCode() ?: 200; + return $code >= 500 && $code < 600; + } /** * Check if response is 4xx or 5xx error @@ -71,5 +80,5 @@ trait CheckResponse public function isError() { return $this->isClientError() || $this->isServerError(); - } + } } diff --git a/src/Controller/Input.php b/src/Controller/Input.php new file mode 100644 index 0000000..7f5ce35 --- /dev/null +++ b/src/Controller/Input.php @@ -0,0 +1,13 @@ +<?php + +namespace Jasny\Controller; + +use Psr\Http\Message\ServerRequestInterface; + +/** + * Methods for a controller to read from the request + */ +trait Input +{ + +} diff --git a/src/Controller/Respond.php b/src/Controller/Output.php index 02b2cfb..a6160f1 100644 --- a/src/Controller/Respond.php +++ b/src/Controller/Output.php @@ -8,9 +8,14 @@ use Dflydev\ApacheMimeTypes\PhpRepository as ApacheMimeTypes; /** * Methods for a controller to send a response */ -trait Respond +trait Output { /** + * @var string + */ + protected $defaultFormat; + + /** * Get response. set for controller * * @return ResponseInterface @@ -33,6 +38,17 @@ trait Respond /** + * If a non scalar value is passed without an format, use this format + * + * @param string $format Format by extention or MIME + */ + public function byDefaultSerializeTo($format) + { + $this->defaultFormat = $format; + } + + + /** * Set a response header * * @param string $header @@ -96,40 +112,66 @@ trait Respond } /** - * Response with created 201 code, and optionaly redirect to created location + * Response with created 201 code, and optionally the created location * * @param string $location Url of created resource */ - public function created($location = '') + public function created($location = null) { $this->respondWith(201); - if ($location) { + if (!empty($location)) { $this->setResponseHeader('Location', $location); } } /** + * Response with 203 Accepted + */ + public function accepted() + { + $this->respondWith(202); + } + + /** * Response with 204 No Content + * + * @param int $code 204 (No Content) or 205 (Reset Content) */ - public function noContent() + public function noContent($code = 204) { - $this->respondWith(204); + $this->respondWith($code); + } + + /** + * Respond with a 206 Partial content with `Content-Range` header + * + * @param int $rangeFrom Beginning of the range in bytes + * @param int $rangeTo End of the range in bytes + * @param int $totalSize Total size in bytes + */ + public function partialContent($rangeFrom, $rangeTo, $totalSize) + { + $this->respondWith(206); + + $this->setResponseHeader('Content-Range', "bytes {$rangeFrom}-{$rangeTo}/{$totalSize}"); + $this->setResponseHeader('Content-Length', $rangeTo - $rangeFrom); } + /** - * Redirect to url + * Redirect to url and output a short message with the link * * @param string $url - * @param int $code 301 (Moved Permanently), 303 (See Other) or 307 (Temporary Redirect) + * @param int $code 301 (Moved Permanently), 302 (Found), 303 (See Other) or 307 (Temporary Redirect) */ public function redirect($url, $code = 303) { - $this->respondWith($code, 'text/html'); + $this->respondWith($code); $this->setResponseHeader('Location', $url); $urlHtml = htmlentities($url); - $this->output('You are being redirected to <a href="' . $urlHtml . '">' . $urlHtml . '</a>'); + $this->output('You are being redirected to <a href="' . $urlHtml . '">' . $urlHtml . '</a>', 'text/html'); } /** @@ -141,6 +183,14 @@ trait Respond { $this->redirect($this->getLocalReferer() ?: '/'); } + + /** + * Respond with 304 Not Modified + */ + public function notModified() + { + $this->respondWith(304); + } /** @@ -188,7 +238,7 @@ trait Respond * * @param string $message */ - public function forbidden($message = "Forbidden") + public function forbidden($message = "Access denied") { $this->respondWith(403); $this->output($message); @@ -198,7 +248,7 @@ trait Respond * Respond with 404 Not Found * * @param string $message - * @param int $code HTTP status code (404 or 405) + * @param int $code 404 (Not Found), 405 (Method not allowed) or 410 (Gone) */ public function notFound($message = "Not found", $code = 404) { @@ -273,16 +323,53 @@ trait Respond */ protected function serializeData($data, $contentType) { - if ($contentType == 'json') { - return (is_string($data) && (json_decode($data) !== null || !json_last_error())) - ? $data : json_encode($data); + if (is_scalar($data)) { + return (string)$data; + } + + $format = preg_replace('~^.+/~', '', $contentType); + $method = 'serializeDataTo' . $format; + + if (method_exists($this, $method)) { + return $this->$method($data); + } + + if (is_object($data) && method_exists($data, '__toString')) { + return (string)$data; + } + + throw new \Exception("Unable to serialize data to $format"); + } + + /** + * Serialize data to JSON + * + * @param mixed $data + * @return string + */ + private function serializeDataToJson($data) + { + return json_encode($data); + } + + /** + * Serialize data to XML + * + * @param mixed $data + * @return string + */ + private function serializeDataToXml($data) + { + if ($data instanceof \SimpleXMLElement) { + return $data->asXML(); } - if (!is_scalar($data)) { - throw new \Exception("Unable to serialize data to {$contentType}"); + if ($data instanceof \DOMNode) { + return $data->ownerDocument->saveXML($data); } - return $data; + $type = (is_object($data) ? get_class($data) . ' ' : '') . gettype($data); + throw new \Exception("Unable to serialize $type to XML"); } /** @@ -300,7 +387,7 @@ trait Respond $this->setResponseHeader('Content-Type', $contentType); } - $content = $this->serializeData($data, $contentType); + $content = $this->serializeData($data, $format); $this->getResponse()->getBody()->write($content); } diff --git a/tests/Controller/CheckRequestTest.php b/tests/Controller/CheckRequestTest.php new file mode 100644 index 0000000..8ba2e23 --- /dev/null +++ b/tests/Controller/CheckRequestTest.php @@ -0,0 +1,87 @@ +<?php + +namespace Jasny\Controller; + +use Psr\Http\Message\ServerRequestInterface; +use Jasny\Controller\TestHelper; + +/** + * @covers Jasny\Controller\CheckRequest + */ +class CheckRequestTest extends \PHPUnit_Framework_TestCase +{ + use TestHelper; + + /** + * Provide data for testing functions that determine request method + * + * @return array + */ + public function requestMethodProvider() + { + return [ + ['GET'], ['POST'], ['PUT'], ['DELETE'], ['HEAD'] + ]; + } + + /** + * Test functions that check request method + * + * @dataProvider requestMethodProvider + * @param string $method + */ + public function testRequestMethod($method) + { + $request = $this->createMock(ServerRequestInterface::class); + $request->method('getMethod')->will($this->returnValue($method)); + + $controller = $this->getController(['getRequest']); + $controller->method('getRequest')->will($this->returnValue($request)); + + $this->assertEquals($method === 'GET', $controller->isGetRequest()); + $this->assertEquals($method === 'POST', $controller->isPostRequest()); + $this->assertEquals($method === 'PUT', $controller->isPutRequest()); + $this->assertEquals($method === 'DELETE', $controller->isDeleteRequest()); + $this->assertEquals($method === 'HEAD', $controller->isHeadRequest()); + } + + /** + * Provide data fot testing 'getLocalReferer' function + * + * @return array + */ + public function localRefererProvider() + { + return [ + ['http://google.com/path', 'example.com', null], + ['http://example.com/', 'example.com', '/'], + ['http://www.example.com/path', 'example.com', null], + ]; + } + + /** + * Test 'getLocalReferer' funtion + * + * @dataProvider localRefererProvider + * @param string $referer + * @param string $host + * @param boolean $local + */ + public function testLocalReferer($referer, $host, $local) + { + $request = $this->createMock(ServerRequestInterface::class); + $request->expects($this->exactly(2))->method('getHeaderLine')->withConsecutive( + [$this->equalTo('HTTP_REFERER')], + [$this->equalTo('HTTP_HOST')] + )->willReturnOnConsecutiveCalls($referer, $host); + + $controller = $this->getController(['getRequest']); + $controller->method('getRequest')->will($this->returnValue($request)); + + $result = $controller->getLocalReferer(); + + $local ? + $this->assertEquals($referer, $result, "Local referer should be returned") : + $this->assertEquals('', $result, "Local referer should not be returned"); + } +} diff --git a/tests/Controller/CheckResponseTest.php b/tests/Controller/CheckResponseTest.php new file mode 100644 index 0000000..e43b5b5 --- /dev/null +++ b/tests/Controller/CheckResponseTest.php @@ -0,0 +1,53 @@ +<?php + +use Psr\Http\Message\ResponseInterface; +use Jasny\Controller\TestHelper; + +/** + * @covers Jasny\Controller\CheckResponse + */ +class ControllerTest extends PHPUnit_Framework_TestCase +{ + use TestHelper; + + /** + * Provide data for testing status methods + * + * @return array + */ + public function responseStatusProvider() + { + return [ + [null, 'successful'], + [100, 'informational'], [199, 'informational'], + [200, 'successful'], [201, 'successful'], [299, 'successful'], + [300, 'redirect'], [304, 'redirect'], [399, 'redirect'], + [400, 'client error'], [403, 'client error'], [499, 'client error'], + [500, 'server error'], [503, 'server error'], + [999, 'unkown'] + ]; + } + + /** + * Test functions that check response status code + * @dataProvider responseStatusProvider + * + * @param int $code status code + * @param string $type + */ + public function testResponseStatus($code, $type) + { + $response = $this->createMock(ResponseInterface::class); + $response->method('getStatusCode')->will($this->returnValue($code)); + + $controller = $this->getController(['getResponse']); + $controller->method('getResponse')->willReturn($response); + + $this->assertSame($type === 'informational', $controller->isInformational(), 'isInformational'); + $this->assertSame($type === 'successful', $controller->isSuccessful(), 'isSuccessful'); + $this->assertSame($type === 'redirect', $controller->isRedirection(), 'isRedirection'); + $this->assertSame($type === 'client error', $controller->isClientError(), 'isClientError'); + $this->assertSame($type === 'server error', $controller->isServerError(), 'isServerError'); + $this->assertSame(in_array($type, ['client error', 'server error']), $controller->isError(), 'isError'); + } +} diff --git a/tests/Controller/OutputTest.php b/tests/Controller/OutputTest.php new file mode 100644 index 0000000..9952d6f --- /dev/null +++ b/tests/Controller/OutputTest.php @@ -0,0 +1,499 @@ +<?php + +namespace Jasny\Controller; + +use Jasny\Controller; +use Jasny\Flash; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; +use Jasny\Controller\TestHelper; + +/** + * @covers Jasny\Controller\Output + */ +class OutputTest extends \PHPUnit_Framework_TestCase +{ + use TestHelper; + + /** + * Provide data for testing error messages functions + * + * @return array + */ + public function statusProvider() + { + return [ + ['ok', 200], + ['created', 201], + ['accepted', 202], + ['noContent', 204], + ['partialContent', 206], + + ['redirect', 303, ['example.com']], + ['back', 303], + ['notModified', 304], + + ['badRequest', 400], + ['requireAuth', 401], + ['requireLogin', 401], + ['paymentRequired', 402], + ['forbidden', 403], + ['notFound', 404], + ['conflict', 409], + ['tooManyRequests', 429] + ]; + } + + /** + * Test functions that deal with error messages + * @dataProvider statusProvider + * + * @param string $function + * @param int $code Status code + * @param array $args + */ + public function testImplicitStatus($function, $code, $args = []) + { + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('withStatus')->with($code)->willReturnSelf(); + $response->expects($this->any())->method('withHeader')->willReturnSelf(); + + $controller = $this->getController(['getResponse']); + $controller->method('getResponse')->willReturn($response); + + $controller->$function(...$args); + } + + + /** + * Test functions that deal with error messages + * @dataProvider statusProvider + * + * @param string $function + * @param int $code Status code + */ + public function testImplicitStatusMessage($function, $code, $args) + { + $message = 'Test message'; + + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($message); + + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('withStatus')->with($code)->willReturnSelf(); + $response->expects($this->once())->method('getBody')->willReturn($stream); + + $controller = $this->getController(['getResponse']); + $controller->method('getResponse')->willReturn($response); + + + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Test setting flash + * + * @dataProvider flashProvider + * @param object $data + */ + public function testFlash($data) + { + $controller = $this->getMockBuilder(Controller::class)->disableOriginalConstructor()->getMockForAbstractClass(); + + $flash = $controller->flash(); + $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); + $this->assertEmpty($flash->get(), "Flash data should be empty"); + + $flash = $controller->flash($data->type, $data->message); + $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); + $this->assertEquals($data, $flash->get(), "Flash data is incorrect"); + + $flash = $controller->flash(); + $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); + $this->assertEquals($data, $flash->get(), "Flash data is incorrect"); + + $flash->clear(); + } + + /** + * Test setting flash + * + * @return array + */ + public function flashProvider() + { + return [ + [(object)['type' => 'test_type', 'message' => 'Test message']] + ]; + } + + /** + * Test respondWith function + * + * @dataProvider respondWithProvider + * @param int|string $code + * @param string $format + * @param int $setCode Actual code that will be set in response + * @param string $contentType + */ + public function testRespondWith($code, $format, $setCode, $contentType) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectResponseWith($response, $setCode, $contentType); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->respondWith($code, $format); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Test function respondWith + * + * @return array + */ + public function respondWithProvider() + { + return [ + [200, 'json', 200, 'application/json'], + [200, 'application/json', 200, 'application/json'], + [204, null, 204, null], + ['204 Created', null, 204, null], + ['json', null, null, 'application/json'] + ]; + } + + /** + * Test functions that are simple wrappers around respondWith function + * + * @dataProvider respondWithWrappersProvider + * @param string $function + * @param int $code + */ + public function testResponseWithWrappers($function, $code) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectResponseWith($response, $code); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->{$function}(); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing respondWith wrappers + * + * @return array + */ + public function respondWithWrappersProvider() + { + return [ + ['ok', 200], + ['noContent', 204] + ]; + } + + /** + * Test 'created' function + * + * @dataProvider createdProvider + * @param string $location + */ + public function testCreated($location) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $response->expects($this->once())->method('withStatus')->with($this->equalTo(201))->will($this->returnSelf()); + if ($location) { + $response->expects($this->once())->method('withHeader')->with($this->equalTo('Location'), $this->equalTo($location))->will($this->returnSelf()); + } + + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->created($location); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing 'created' function + * + * @return array + */ + public function createdProvider() + { + return [ + [''], ['/some-path/test'] + ]; + } + + /** + * Test 'redirect' function + * + * @dataProvider redirectProvider + * @param string $url + * @param int $code + * @param boolean $default + */ + public function testRedirect($url, $code, $default) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectRedirect($response, $url, $code); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $default ? + $controller->redirect($url) : + $controller->redirect($url, $code); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing 'redirect' function + * + * @return array + */ + public function redirectProvider() + { + return [ + ['/test-url', 303, true], + ['/test-url', 301, false] + ]; + } + + /** + * Test 'requireLogin' function + * + * @dataProvider requireLoginProvider + * @param string $function + */ + public function testRequireLogin($function) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectRedirect($response, '/401', 303); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->{$function}(); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing 'requireLogon' function + * + * @return array + */ + public function requireLoginProvider() + { + return [ + ['requireLogin'], ['requireAuth'] + ]; + } + + /** + * Test 'getLocalReferer' funtion + * + * @dataProvider localRefererProvider + * @param string $referer + * @param string $host + * @param boolean $local + */ + public function testLocalReferer($referer, $host, $local) + { + $controller = $this->getController(['getRequest']); + list($request) = $this->getRequests(); + + $this->expectLocalReferer($request, $referer, $host); + $controller->method('getRequest')->will($this->returnValue($request)); + + $result = $controller->getLocalReferer(); + + $local ? + $this->assertEquals($referer, $result, "Local referer should be returned") : + $this->assertEquals('', $result, "Local referer should not be returned"); + } + + /** + * Test 'back' function + * + * @dataProvider localRefererProvider + * @param string $referer + * @param string $host + * @param boolean $local + */ + public function testBack($referer, $host, $local) + { + $controller = $this->getController(['getRequest', 'getResponse']); + list($request, $response) = $this->getRequests(); + + $this->expectLocalReferer($request, $referer, $host); + $this->expectRedirect($response, $local ? $referer : '/', 303); + + $controller->method('getRequest')->will($this->returnValue($request)); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->back(); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data fot testing 'getLocalReferer' function + * + * @return array + */ + public function localRefererProvider() + { + return [ + ['http://not-local-host.com/path', 'local-host.com', false], + ['http://local-host.com/path', 'local-host.com', true] + ]; + } + + /** + * Expect for 'getLocalReferer' function to work correctly + * + * @param ServerRequestInterface $request + * @param string $referer + * @param string $host + */ + public function expectLocalReferer($request, $referer, $host) + { + $request->expects($this->exactly(2))->method('getHeaderLine')->withConsecutive( + [$this->equalTo('HTTP_REFERER')], + [$this->equalTo('HTTP_HOST')] + )->will($this->returnCallback(function($header) use ($referer, $host) { + return $header === 'HTTP_REFERER' ? $referer : $host; + })); + } + + /** + * Expect for redirect + * + * @param ResponseInterface $response + * @param string $url + * @param int $code + */ + public function expectRedirect($response, $url, $code) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($this->equalTo('You are being redirected to <a href="' . $url . '">' . $url . '</a>')); + + $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); + $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()); + $response->expects($this->exactly(2))->method('withHeader')->withConsecutive( + [$this->equalTo('Content-Type'), $this->equalTo('text/html')], + [$this->equalTo('Location'), $this->equalTo($url)] + )->will($this->returnSelf()); + } + + /** + * Expect correct work of respondWith function + * + * @param ResponseInterface $response + * @param int $code + * @param string $contentType + */ + public function expectResponseWith($response, $code, $contentType = null) + { + $code ? + $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()) : + $response->expects($this->never())->method('withStatus')->with($this->equalTo($code)); + + $contentType ? + $response->expects($this->once())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType))->will($this->returnSelf()) : + $response->expects($this->never())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType)); + } + + /** + * Expects that output will be set to content + * + * @param ResponseInterface $response + * @param string $content + * @param string $contentType + */ + public function expectOutput($response, $content, $contentType) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($this->equalTo($content)); + + $response->expects($this->once())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType))->will($this->returnSelf()); + $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); + } + + /** + * Provide data for testing output + * + * @return array + */ + public function outputProvider() + { + $xml = simplexml_load_string( + "<?xml version='1.0'?> + <document> + <tag1>Test tag</tag1> + <tag2>Test</tag2> + </document>" + ); + + return [ + ['test_string', 'text', 'text/plain'], + ['javascript:test_call();', 'js', 'application/javascript'], + ['test {}', 'css', 'text/css'], + ['{ "testKey": "testValue" }', 'json', 'application/json'], + [['testKey' => 'testValue'], 'json', 'application/json'], + [$xml, 'xml', 'application/xml'] + ]; + } + + /** + * Test output + * + * @dataProvider outputProvider + * @param mixed $data + * @param string $format + * @param string $contentType + */ + public function testOutput($data, $format, $contentType) + { + $response = $this->createMock(ResponseInterface::class); + + $controller = $this->getController(['getResponse']); + $controller->method('getResponse')->willReturn($response); + + if (is_scalar($data)) { + $content = $data; + } elseif ($format === 'json') { + $content = json_encode($data); + + if ($callback) $content = "$callback($content)"; + } elseif ($format === 'xml') { + $content = $data->asXML(); + } + + $this->expectOutput($response, $content, $contentType); + + if ($callback) { + $request->method('getQueryParams')->will($this->returnValue(['callback' => $callback])); + } + + $controller->method('getRequest')->will($this->returnValue($request)); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->output($data, $format); + + $this->assertEquals($result, $response, "Output should return response instance"); + } +} diff --git a/tests/Controller/RouteActionTest.php b/tests/Controller/RouteActionTest.php index edc4bd7..cd630e0 100644 --- a/tests/Controller/RouteActionTest.php +++ b/tests/Controller/RouteActionTest.php @@ -1,7 +1,6 @@ <?php -require_once dirname(__DIR__) . '/support/TestController.php'; - +use Jasny\Controller; use Jasny\Controller\RouteAction; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; diff --git a/tests/FlashTest.php b/tests/Controller/Session/FlashTest.php index c17c446..c17c446 100644 --- a/tests/FlashTest.php +++ b/tests/Controller/Session/FlashTest.php diff --git a/tests/Controller/SessionTest.php b/tests/Controller/SessionTest.php new file mode 100644 index 0000000..b6a6b2d --- /dev/null +++ b/tests/Controller/SessionTest.php @@ -0,0 +1,750 @@ +<?php + +use Jasny\Controller; +use Jasny\Flash; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +/** + * @covers Jasny\Controller + */ +class ControllerTest extends PHPUnit_Framework_TestCase +{ + /** + * Get mock for controller + * + * @param array $methods Methods to mock + * @return Controller + */ + public function getController($methods = []) + { + $builder = $this->getMockBuilder(Controller::class)->disableOriginalConstructor(); + if ($methods) { + $builder->setMethods($methods); + } + + return $builder->getMockForAbstractClass(); + } + + /** + * Test running controller + */ + public function testInvoke() + { + $test = $this; + $controller = $this->getController(); + + $request = $this->createMock(ServerRequestInterface::class); + $response = $this->createMock(ResponseInterface::class); + $finalResponse = $this->createMock(ResponseInterface::class); + + $controller->expects($this->once())->method('run') + ->willReturnCallback(Closure::bind(function() use ($test, $request, $response, $finalResponse) { + $test->assertSame($request, $this->getRequest()); + $test->assertSame($response, $this->getResponse()); + + return $finalResponse; + }, $controller, Controller::class)); + + $result = $controller($request, $response); + + $this->assertEquals($finalResponse, $result); + } + + /** + * Test response status functions if response object is not set + */ + public function testResponseStatusEmptyResponse() + { + $controller = $this->getController(); + $data = $this->getStatusCodesMap(null); + + foreach ($data as $func => $value) { + $this->assertEquals($value, $controller->$func(), "Method '$func' returns incorrect value"); + } + } + + /** + * Test functions that check response status code + * + * @dataProvider responseStatusProvider + * @param int status code + */ + public function testResponseStatus($code) + { + $controller = $this->getController(); + list($request, $response) = $this->getRequests(); + $response->method('getStatusCode')->will($this->returnValue($code)); + + $controller($request, $response); + + $data = $this->getStatusCodesMap($code); + + foreach ($data as $func => $value) { + $this->assertEquals($value, $controller->$func(), "Method '$func' returns incorrect value"); + } + + $this->assertEquals($data['isClientError'] || $data['isServerError'], $controller->isError() + , "Method 'isError' returns incorrect value"); + } + + /** + * Provide data for testing status methods + * + * @return array + */ + public function responseStatusProvider() + { + return [ + [null], [199], + [200], [201], [299], + [300], [304], [399], + [400], [403], [499], + [500], [503] + ]; + } + + /** + * Test functions that check request method + * + * @dataProvider requestMethodProvider + * @param string $method + */ + public function testRequestMethod($method) + { + $controller = $this->getController(); + list($request, $response) = $this->getRequests(); + $request->method('getMethod')->will($this->returnValue($method)); + + $controller($request, $response); + + $data = $this->getMethodsMap($method); + + foreach ($data as $func => $value) { + $this->assertEquals($value, $controller->$func(), "Method '$func' returns incorrect value"); + } + } + + /** + * Provide data for testing functions that determine request method + * + * @return array + */ + public function requestMethodProvider() + { + return [ + ['GET'], ['POST'], ['PUT'], ['DELETE'], ['HEAD'] + ]; + } + + /** + * Test encodeData method, positive tests + * + * @dataProvider encodeDataPositiveProvider + * @param mixed $data + * @param string $format + * @param string $callback Callback name for testing jsonp request + */ + public function testEncodeDataPositive($data, $format, $callback = null) + { + $this->markTestSkipped(); + + $controller = $this->getController(['getRequest']); + list($request) = $this->getRequests(); + + if ($callback) { + $request->method('getQueryParams')->will($this->returnValue(['callback' => $callback])); + } + + $controller->method('getRequest')->will($this->returnValue($request)); + + $result = $controller->encodeData($data, $format); + $expect = null; + + if ($format === 'json') { + $expect = json_encode($data); + + if ($callback) $expect = "$callback($expect)"; + } else { + $expect = $data->asXML(); + } + + $this->assertNotEmpty($result, "Result should not be empty"); + $this->assertEquals($expect, $result, "Data was not encoded correctly"); + } + + /** + * Provide data for testing encodeData method + * + * @return array + */ + public function encodeDataPositiveProvider() + { + $xml = simplexml_load_string( + "<?xml version='1.0'?> + <document> + <tag1>Test tag</tag1> + <tag2>Test</tag2> + </document>" + ); + + return [ + ['test_string', 'json'], + [['testKey' => 'testValue'], 'json'], + [['testKey' => 'testValue'], 'json', 'test_callback'], + ['', 'json'], + ['', 'json', 'test_callback'], + [$xml, 'xml'] + ]; + } + + /** + * Test encodeData method, negative tests + * + * @dataProvider encodeDataNegativeProvider + * @param mixed $data + * @param string $format + */ + public function testEncodeDataNegative($data, $format) + { + $this->markTestSkipped(); + + $controller = $this->getController(['getRequest']); + list($request) = $this->getRequests(); + + $controller->method('getRequest')->will($this->returnValue($request)); + $this->expectException(\InvalidArgumentException::class); + + $result = $controller->encodeData($data, $format); + } + + /** + * Provide data for testing encodeData method + * + * @return array + */ + public function encodeDataNegativeProvider() + { + return [ + ['test_string', 'html'], + ['test_string', 'jpg'] + ]; + } + + /** + * Test output + * + * @dataProvider outputProvider + * @param mixed $data + * @param string $format + * @param string $contentType + * @param string $callback Callback name for testing jsonp request + */ + public function testOutput($data, $format, $contentType, $callback = '') + { + $controller = $this->getController(['getRequest', 'getResponse']); + list($request, $response) = $this->getRequests(); + + if (is_scalar($data)) { + $content = $data; + } elseif ($format === 'json') { + $content = json_encode($data); + + if ($callback) $content = "$callback($content)"; + } elseif ($format === 'xml') { + $content = $data->asXML(); + } + + $this->expectOutput($response, $content, $contentType); + + if ($callback) { + $request->method('getQueryParams')->will($this->returnValue(['callback' => $callback])); + } + + $controller->method('getRequest')->will($this->returnValue($request)); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->output($data, $format); + + $this->assertEquals($result, $response, "Output should return response instance"); + } + + /** + * Provide data for testing output + * + * @return array + */ + public function outputProvider() + { + $xml = simplexml_load_string( + "<?xml version='1.0'?> + <document> + <tag1>Test tag</tag1> + <tag2>Test</tag2> + </document>" + ); + + return [ + ['test_string', 'text', 'text/plain'], + ['javascript:test_call();', 'js', 'application/javascript'], + ['test {}', 'css', 'text/css'], + ['test_string', 'json', 'application/json'], + [['testKey' => 'testValue'], 'json', 'application/json'], + [['testKey' => 'testValue'], 'json', 'application/json', 'test_callback'], + ['', 'json', 'application/json'], + ['', 'json', 'application/json', 'test_callback'], + [$xml, 'xml', 'application/xml'] + ]; + } + + /** + * Test functions that deal with error messages + * + * @dataProvider errorMessagesProvider + * @param string $function + * @param int $code + * @param boolean $default Is code default for this function + */ + public function testErrorMessages($function, $code, $default) + { + $message = 'Test message'; + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectErrorMessage($response, $message, $code); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $default ? + $controller->{$function}($message) : + $controller->{$function}($message, $code); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing error messages functions + * + * @return array + */ + public function errorMessagesProvider() + { + return [ + ['error', 400, true], + ['error', 403, false], + ['tooManyRequests', 429, true], + ['tooManyRequests', 400, false], + ['conflict', 409, true], + ['conflict', 403, false], + ['notFound', 404, true], + ['notFound', 400, false], + ['forbidden', 403, true], + ['forbidden', 409, false], + ['badRequest', 400, true], + ['badRequest', 403, false] + ]; + } + + /** + * Test setting flash + * + * @dataProvider flashProvider + * @param object $data + */ + public function testFlash($data) + { + $controller = $this->getMockBuilder(Controller::class)->disableOriginalConstructor()->getMockForAbstractClass(); + + $flash = $controller->flash(); + $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); + $this->assertEmpty($flash->get(), "Flash data should be empty"); + + $flash = $controller->flash($data->type, $data->message); + $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); + $this->assertEquals($data, $flash->get(), "Flash data is incorrect"); + + $flash = $controller->flash(); + $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); + $this->assertEquals($data, $flash->get(), "Flash data is incorrect"); + + $flash->clear(); + } + + /** + * Test setting flash + * + * @return array + */ + public function flashProvider() + { + return [ + [(object)['type' => 'test_type', 'message' => 'Test message']] + ]; + } + + /** + * Test respondWith function + * + * @dataProvider respondWithProvider + * @param int|string $code + * @param string $format + * @param int $setCode Actual code that will be set in response + * @param string $contentType + */ + public function testRespondWith($code, $format, $setCode, $contentType) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectResponseWith($response, $setCode, $contentType); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->respondWith($code, $format); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Test function respondWith + * + * @return array + */ + public function respondWithProvider() + { + return [ + [200, 'json', 200, 'application/json'], + [200, 'application/json', 200, 'application/json'], + [204, null, 204, null], + ['204 Created', null, 204, null], + ['json', null, null, 'application/json'] + ]; + } + + /** + * Test functions that are simple wrappers around respondWith function + * + * @dataProvider respondWithWrappersProvider + * @param string $function + * @param int $code + */ + public function testResponseWithWrappers($function, $code) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectResponseWith($response, $code); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->{$function}(); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing respondWith wrappers + * + * @return array + */ + public function respondWithWrappersProvider() + { + return [ + ['ok', 200], + ['noContent', 204] + ]; + } + + /** + * Test 'created' function + * + * @dataProvider createdProvider + * @param string $location + */ + public function testCreated($location) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $response->expects($this->once())->method('withStatus')->with($this->equalTo(201))->will($this->returnSelf()); + if ($location) { + $response->expects($this->once())->method('withHeader')->with($this->equalTo('Location'), $this->equalTo($location))->will($this->returnSelf()); + } + + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->created($location); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing 'created' function + * + * @return array + */ + public function createdProvider() + { + return [ + [''], ['/some-path/test'] + ]; + } + + /** + * Test 'redirect' function + * + * @dataProvider redirectProvider + * @param string $url + * @param int $code + * @param boolean $default + */ + public function testRedirect($url, $code, $default) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectRedirect($response, $url, $code); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $default ? + $controller->redirect($url) : + $controller->redirect($url, $code); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing 'redirect' function + * + * @return array + */ + public function redirectProvider() + { + return [ + ['/test-url', 303, true], + ['/test-url', 301, false] + ]; + } + + /** + * Test 'requireLogin' function + * + * @dataProvider requireLoginProvider + * @param string $function + */ + public function testRequireLogin($function) + { + $controller = $this->getController(['getResponse']); + list(, $response) = $this->getRequests(); + + $this->expectRedirect($response, '/401', 303); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->{$function}(); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data for testing 'requireLogon' function + * + * @return array + */ + public function requireLoginProvider() + { + return [ + ['requireLogin'], ['requireAuth'] + ]; + } + + /** + * Test 'getLocalReferer' funtion + * + * @dataProvider localRefererProvider + * @param string $referer + * @param string $host + * @param boolean $local + */ + public function testLocalReferer($referer, $host, $local) + { + $controller = $this->getController(['getRequest']); + list($request) = $this->getRequests(); + + $this->expectLocalReferer($request, $referer, $host); + $controller->method('getRequest')->will($this->returnValue($request)); + + $result = $controller->getLocalReferer(); + + $local ? + $this->assertEquals($referer, $result, "Local referer should be returned") : + $this->assertEquals('', $result, "Local referer should not be returned"); + } + + /** + * Test 'back' function + * + * @dataProvider localRefererProvider + * @param string $referer + * @param string $host + * @param boolean $local + */ + public function testBack($referer, $host, $local) + { + $controller = $this->getController(['getRequest', 'getResponse']); + list($request, $response) = $this->getRequests(); + + $this->expectLocalReferer($request, $referer, $host); + $this->expectRedirect($response, $local ? $referer : '/', 303); + + $controller->method('getRequest')->will($this->returnValue($request)); + $controller->method('getResponse')->will($this->returnValue($response)); + + $result = $controller->back(); + + $this->assertEquals($result, $response, "Response object should be returned"); + } + + /** + * Provide data fot testing 'getLocalReferer' function + * + * @return array + */ + public function localRefererProvider() + { + return [ + ['http://not-local-host.com/path', 'local-host.com', false], + ['http://local-host.com/path', 'local-host.com', true] + ]; + } + + /** + * Expect for 'getLocalReferer' function to work correctly + * + * @param ServerRequestInterface $request + * @param string $referer + * @param string $host + */ + public function expectLocalReferer($request, $referer, $host) + { + $request->expects($this->exactly(2))->method('getHeaderLine')->withConsecutive( + [$this->equalTo('HTTP_REFERER')], + [$this->equalTo('HTTP_HOST')] + )->will($this->returnCallback(function($header) use ($referer, $host) { + return $header === 'HTTP_REFERER' ? $referer : $host; + })); + } + + /** + * Expect for redirect + * + * @param ResponseInterface $response + * @param string $url + * @param int $code + */ + public function expectRedirect($response, $url, $code) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($this->equalTo('You are being redirected to <a href="' . $url . '">' . $url . '</a>')); + + $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); + $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()); + $response->expects($this->exactly(2))->method('withHeader')->withConsecutive( + [$this->equalTo('Content-Type'), $this->equalTo('text/html')], + [$this->equalTo('Location'), $this->equalTo($url)] + )->will($this->returnSelf()); + } + + /** + * Expect correct work of respondWith function + * + * @param ResponseInterface $response + * @param int $code + * @param string $contentType + */ + public function expectResponseWith($response, $code, $contentType = null) + { + $code ? + $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()) : + $response->expects($this->never())->method('withStatus')->with($this->equalTo($code)); + + $contentType ? + $response->expects($this->once())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType))->will($this->returnSelf()) : + $response->expects($this->never())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType)); + } + + /** + * Expect for correct work of error message functions + * + * @param ResponseInterface $response + * @param string $message + * @param int $code + */ + public function expectErrorMessage($response, $message, $code) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($this->equalTo($message)); + + $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()); + $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); + } + + /** + * Expects that output will be set to content + * + * @param ResponseInterface $response + * @param string $content + * @param string $contentType + */ + public function expectOutput($response, $content, $contentType) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($this->equalTo($content)); + + $response->expects($this->once())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType))->will($this->returnSelf()); + $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); + } + + /** + * Get request and response instances + * + * @return array + */ + public function getRequests() + { + return [ + $this->createMock(ServerRequestInterface::class), + $this->createMock(ResponseInterface::class) + ]; + } + + /** + * Get map of status codes to states + * + * @param int $code + * @return [] + */ + public function getStatusCodesMap($code) + { + return [ + 'isSuccessful' => !$code || ($code >= 200 && $code < 300), + 'isRedirection' => $code >= 300 && $code < 400, + 'isClientError' => $code >= 400 && $code < 500, + 'isServerError' => $code >= 500 + ]; + } + + /** + * Get map of request methods + * + * @param string $method + * @return array + */ + public function getMethodsMap($method) + { + return [ + 'isGetRequest' => $method === 'GET', + 'isPostRequest' => $method === 'POST', + 'isPutRequest' => $method === 'PUT', + 'isDeleteRequest' => $method === 'DELETE', + 'isHeadRequest' => $method === 'HEAD' + ]; + } +} diff --git a/tests/View/TwigTest.php b/tests/Controller/View/TwigTest.php index 662681d..662681d 100644 --- a/tests/View/TwigTest.php +++ b/tests/Controller/View/TwigTest.php diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index b6a6b2d..3e3b39c 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -1,32 +1,19 @@ <?php +namespace Jasny; + use Jasny\Controller; -use Jasny\Flash; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; +use Jasny\Controller\TestHelper; /** * @covers Jasny\Controller */ -class ControllerTest extends PHPUnit_Framework_TestCase +class ControllerTest extends \PHPUnit_Framework_TestCase { - /** - * Get mock for controller - * - * @param array $methods Methods to mock - * @return Controller - */ - public function getController($methods = []) - { - $builder = $this->getMockBuilder(Controller::class)->disableOriginalConstructor(); - if ($methods) { - $builder->setMethods($methods); - } - - return $builder->getMockForAbstractClass(); - } - + use TestHelper; + /** * Test running controller */ @@ -40,7 +27,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase $finalResponse = $this->createMock(ResponseInterface::class); $controller->expects($this->once())->method('run') - ->willReturnCallback(Closure::bind(function() use ($test, $request, $response, $finalResponse) { + ->willReturnCallback(\Closure::bind(function() use ($test, $request, $response, $finalResponse) { $test->assertSame($request, $this->getRequest()); $test->assertSame($response, $this->getResponse()); @@ -51,700 +38,4 @@ class ControllerTest extends PHPUnit_Framework_TestCase $this->assertEquals($finalResponse, $result); } - - /** - * Test response status functions if response object is not set - */ - public function testResponseStatusEmptyResponse() - { - $controller = $this->getController(); - $data = $this->getStatusCodesMap(null); - - foreach ($data as $func => $value) { - $this->assertEquals($value, $controller->$func(), "Method '$func' returns incorrect value"); - } - } - - /** - * Test functions that check response status code - * - * @dataProvider responseStatusProvider - * @param int status code - */ - public function testResponseStatus($code) - { - $controller = $this->getController(); - list($request, $response) = $this->getRequests(); - $response->method('getStatusCode')->will($this->returnValue($code)); - - $controller($request, $response); - - $data = $this->getStatusCodesMap($code); - - foreach ($data as $func => $value) { - $this->assertEquals($value, $controller->$func(), "Method '$func' returns incorrect value"); - } - - $this->assertEquals($data['isClientError'] || $data['isServerError'], $controller->isError() - , "Method 'isError' returns incorrect value"); - } - - /** - * Provide data for testing status methods - * - * @return array - */ - public function responseStatusProvider() - { - return [ - [null], [199], - [200], [201], [299], - [300], [304], [399], - [400], [403], [499], - [500], [503] - ]; - } - - /** - * Test functions that check request method - * - * @dataProvider requestMethodProvider - * @param string $method - */ - public function testRequestMethod($method) - { - $controller = $this->getController(); - list($request, $response) = $this->getRequests(); - $request->method('getMethod')->will($this->returnValue($method)); - - $controller($request, $response); - - $data = $this->getMethodsMap($method); - - foreach ($data as $func => $value) { - $this->assertEquals($value, $controller->$func(), "Method '$func' returns incorrect value"); - } - } - - /** - * Provide data for testing functions that determine request method - * - * @return array - */ - public function requestMethodProvider() - { - return [ - ['GET'], ['POST'], ['PUT'], ['DELETE'], ['HEAD'] - ]; - } - - /** - * Test encodeData method, positive tests - * - * @dataProvider encodeDataPositiveProvider - * @param mixed $data - * @param string $format - * @param string $callback Callback name for testing jsonp request - */ - public function testEncodeDataPositive($data, $format, $callback = null) - { - $this->markTestSkipped(); - - $controller = $this->getController(['getRequest']); - list($request) = $this->getRequests(); - - if ($callback) { - $request->method('getQueryParams')->will($this->returnValue(['callback' => $callback])); - } - - $controller->method('getRequest')->will($this->returnValue($request)); - - $result = $controller->encodeData($data, $format); - $expect = null; - - if ($format === 'json') { - $expect = json_encode($data); - - if ($callback) $expect = "$callback($expect)"; - } else { - $expect = $data->asXML(); - } - - $this->assertNotEmpty($result, "Result should not be empty"); - $this->assertEquals($expect, $result, "Data was not encoded correctly"); - } - - /** - * Provide data for testing encodeData method - * - * @return array - */ - public function encodeDataPositiveProvider() - { - $xml = simplexml_load_string( - "<?xml version='1.0'?> - <document> - <tag1>Test tag</tag1> - <tag2>Test</tag2> - </document>" - ); - - return [ - ['test_string', 'json'], - [['testKey' => 'testValue'], 'json'], - [['testKey' => 'testValue'], 'json', 'test_callback'], - ['', 'json'], - ['', 'json', 'test_callback'], - [$xml, 'xml'] - ]; - } - - /** - * Test encodeData method, negative tests - * - * @dataProvider encodeDataNegativeProvider - * @param mixed $data - * @param string $format - */ - public function testEncodeDataNegative($data, $format) - { - $this->markTestSkipped(); - - $controller = $this->getController(['getRequest']); - list($request) = $this->getRequests(); - - $controller->method('getRequest')->will($this->returnValue($request)); - $this->expectException(\InvalidArgumentException::class); - - $result = $controller->encodeData($data, $format); - } - - /** - * Provide data for testing encodeData method - * - * @return array - */ - public function encodeDataNegativeProvider() - { - return [ - ['test_string', 'html'], - ['test_string', 'jpg'] - ]; - } - - /** - * Test output - * - * @dataProvider outputProvider - * @param mixed $data - * @param string $format - * @param string $contentType - * @param string $callback Callback name for testing jsonp request - */ - public function testOutput($data, $format, $contentType, $callback = '') - { - $controller = $this->getController(['getRequest', 'getResponse']); - list($request, $response) = $this->getRequests(); - - if (is_scalar($data)) { - $content = $data; - } elseif ($format === 'json') { - $content = json_encode($data); - - if ($callback) $content = "$callback($content)"; - } elseif ($format === 'xml') { - $content = $data->asXML(); - } - - $this->expectOutput($response, $content, $contentType); - - if ($callback) { - $request->method('getQueryParams')->will($this->returnValue(['callback' => $callback])); - } - - $controller->method('getRequest')->will($this->returnValue($request)); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->output($data, $format); - - $this->assertEquals($result, $response, "Output should return response instance"); - } - - /** - * Provide data for testing output - * - * @return array - */ - public function outputProvider() - { - $xml = simplexml_load_string( - "<?xml version='1.0'?> - <document> - <tag1>Test tag</tag1> - <tag2>Test</tag2> - </document>" - ); - - return [ - ['test_string', 'text', 'text/plain'], - ['javascript:test_call();', 'js', 'application/javascript'], - ['test {}', 'css', 'text/css'], - ['test_string', 'json', 'application/json'], - [['testKey' => 'testValue'], 'json', 'application/json'], - [['testKey' => 'testValue'], 'json', 'application/json', 'test_callback'], - ['', 'json', 'application/json'], - ['', 'json', 'application/json', 'test_callback'], - [$xml, 'xml', 'application/xml'] - ]; - } - - /** - * Test functions that deal with error messages - * - * @dataProvider errorMessagesProvider - * @param string $function - * @param int $code - * @param boolean $default Is code default for this function - */ - public function testErrorMessages($function, $code, $default) - { - $message = 'Test message'; - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $this->expectErrorMessage($response, $message, $code); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $default ? - $controller->{$function}($message) : - $controller->{$function}($message, $code); - - $this->assertEquals($result, $response, "Response object should be returned"); - } - - /** - * Provide data for testing error messages functions - * - * @return array - */ - public function errorMessagesProvider() - { - return [ - ['error', 400, true], - ['error', 403, false], - ['tooManyRequests', 429, true], - ['tooManyRequests', 400, false], - ['conflict', 409, true], - ['conflict', 403, false], - ['notFound', 404, true], - ['notFound', 400, false], - ['forbidden', 403, true], - ['forbidden', 409, false], - ['badRequest', 400, true], - ['badRequest', 403, false] - ]; - } - - /** - * Test setting flash - * - * @dataProvider flashProvider - * @param object $data - */ - public function testFlash($data) - { - $controller = $this->getMockBuilder(Controller::class)->disableOriginalConstructor()->getMockForAbstractClass(); - - $flash = $controller->flash(); - $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); - $this->assertEmpty($flash->get(), "Flash data should be empty"); - - $flash = $controller->flash($data->type, $data->message); - $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); - $this->assertEquals($data, $flash->get(), "Flash data is incorrect"); - - $flash = $controller->flash(); - $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); - $this->assertEquals($data, $flash->get(), "Flash data is incorrect"); - - $flash->clear(); - } - - /** - * Test setting flash - * - * @return array - */ - public function flashProvider() - { - return [ - [(object)['type' => 'test_type', 'message' => 'Test message']] - ]; - } - - /** - * Test respondWith function - * - * @dataProvider respondWithProvider - * @param int|string $code - * @param string $format - * @param int $setCode Actual code that will be set in response - * @param string $contentType - */ - public function testRespondWith($code, $format, $setCode, $contentType) - { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $this->expectResponseWith($response, $setCode, $contentType); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->respondWith($code, $format); - - $this->assertEquals($result, $response, "Response object should be returned"); - } - - /** - * Test function respondWith - * - * @return array - */ - public function respondWithProvider() - { - return [ - [200, 'json', 200, 'application/json'], - [200, 'application/json', 200, 'application/json'], - [204, null, 204, null], - ['204 Created', null, 204, null], - ['json', null, null, 'application/json'] - ]; - } - - /** - * Test functions that are simple wrappers around respondWith function - * - * @dataProvider respondWithWrappersProvider - * @param string $function - * @param int $code - */ - public function testResponseWithWrappers($function, $code) - { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $this->expectResponseWith($response, $code); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->{$function}(); - - $this->assertEquals($result, $response, "Response object should be returned"); - } - - /** - * Provide data for testing respondWith wrappers - * - * @return array - */ - public function respondWithWrappersProvider() - { - return [ - ['ok', 200], - ['noContent', 204] - ]; - } - - /** - * Test 'created' function - * - * @dataProvider createdProvider - * @param string $location - */ - public function testCreated($location) - { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $response->expects($this->once())->method('withStatus')->with($this->equalTo(201))->will($this->returnSelf()); - if ($location) { - $response->expects($this->once())->method('withHeader')->with($this->equalTo('Location'), $this->equalTo($location))->will($this->returnSelf()); - } - - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->created($location); - - $this->assertEquals($result, $response, "Response object should be returned"); - } - - /** - * Provide data for testing 'created' function - * - * @return array - */ - public function createdProvider() - { - return [ - [''], ['/some-path/test'] - ]; - } - - /** - * Test 'redirect' function - * - * @dataProvider redirectProvider - * @param string $url - * @param int $code - * @param boolean $default - */ - public function testRedirect($url, $code, $default) - { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $this->expectRedirect($response, $url, $code); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $default ? - $controller->redirect($url) : - $controller->redirect($url, $code); - - $this->assertEquals($result, $response, "Response object should be returned"); - } - - /** - * Provide data for testing 'redirect' function - * - * @return array - */ - public function redirectProvider() - { - return [ - ['/test-url', 303, true], - ['/test-url', 301, false] - ]; - } - - /** - * Test 'requireLogin' function - * - * @dataProvider requireLoginProvider - * @param string $function - */ - public function testRequireLogin($function) - { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $this->expectRedirect($response, '/401', 303); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->{$function}(); - - $this->assertEquals($result, $response, "Response object should be returned"); - } - - /** - * Provide data for testing 'requireLogon' function - * - * @return array - */ - public function requireLoginProvider() - { - return [ - ['requireLogin'], ['requireAuth'] - ]; - } - - /** - * Test 'getLocalReferer' funtion - * - * @dataProvider localRefererProvider - * @param string $referer - * @param string $host - * @param boolean $local - */ - public function testLocalReferer($referer, $host, $local) - { - $controller = $this->getController(['getRequest']); - list($request) = $this->getRequests(); - - $this->expectLocalReferer($request, $referer, $host); - $controller->method('getRequest')->will($this->returnValue($request)); - - $result = $controller->getLocalReferer(); - - $local ? - $this->assertEquals($referer, $result, "Local referer should be returned") : - $this->assertEquals('', $result, "Local referer should not be returned"); - } - - /** - * Test 'back' function - * - * @dataProvider localRefererProvider - * @param string $referer - * @param string $host - * @param boolean $local - */ - public function testBack($referer, $host, $local) - { - $controller = $this->getController(['getRequest', 'getResponse']); - list($request, $response) = $this->getRequests(); - - $this->expectLocalReferer($request, $referer, $host); - $this->expectRedirect($response, $local ? $referer : '/', 303); - - $controller->method('getRequest')->will($this->returnValue($request)); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->back(); - - $this->assertEquals($result, $response, "Response object should be returned"); - } - - /** - * Provide data fot testing 'getLocalReferer' function - * - * @return array - */ - public function localRefererProvider() - { - return [ - ['http://not-local-host.com/path', 'local-host.com', false], - ['http://local-host.com/path', 'local-host.com', true] - ]; - } - - /** - * Expect for 'getLocalReferer' function to work correctly - * - * @param ServerRequestInterface $request - * @param string $referer - * @param string $host - */ - public function expectLocalReferer($request, $referer, $host) - { - $request->expects($this->exactly(2))->method('getHeaderLine')->withConsecutive( - [$this->equalTo('HTTP_REFERER')], - [$this->equalTo('HTTP_HOST')] - )->will($this->returnCallback(function($header) use ($referer, $host) { - return $header === 'HTTP_REFERER' ? $referer : $host; - })); - } - - /** - * Expect for redirect - * - * @param ResponseInterface $response - * @param string $url - * @param int $code - */ - public function expectRedirect($response, $url, $code) - { - $stream = $this->createMock(StreamInterface::class); - $stream->expects($this->once())->method('write')->with($this->equalTo('You are being redirected to <a href="' . $url . '">' . $url . '</a>')); - - $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); - $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()); - $response->expects($this->exactly(2))->method('withHeader')->withConsecutive( - [$this->equalTo('Content-Type'), $this->equalTo('text/html')], - [$this->equalTo('Location'), $this->equalTo($url)] - )->will($this->returnSelf()); - } - - /** - * Expect correct work of respondWith function - * - * @param ResponseInterface $response - * @param int $code - * @param string $contentType - */ - public function expectResponseWith($response, $code, $contentType = null) - { - $code ? - $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()) : - $response->expects($this->never())->method('withStatus')->with($this->equalTo($code)); - - $contentType ? - $response->expects($this->once())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType))->will($this->returnSelf()) : - $response->expects($this->never())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType)); - } - - /** - * Expect for correct work of error message functions - * - * @param ResponseInterface $response - * @param string $message - * @param int $code - */ - public function expectErrorMessage($response, $message, $code) - { - $stream = $this->createMock(StreamInterface::class); - $stream->expects($this->once())->method('write')->with($this->equalTo($message)); - - $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()); - $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); - } - - /** - * Expects that output will be set to content - * - * @param ResponseInterface $response - * @param string $content - * @param string $contentType - */ - public function expectOutput($response, $content, $contentType) - { - $stream = $this->createMock(StreamInterface::class); - $stream->expects($this->once())->method('write')->with($this->equalTo($content)); - - $response->expects($this->once())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType))->will($this->returnSelf()); - $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); - } - - /** - * Get request and response instances - * - * @return array - */ - public function getRequests() - { - return [ - $this->createMock(ServerRequestInterface::class), - $this->createMock(ResponseInterface::class) - ]; - } - - /** - * Get map of status codes to states - * - * @param int $code - * @return [] - */ - public function getStatusCodesMap($code) - { - return [ - 'isSuccessful' => !$code || ($code >= 200 && $code < 300), - 'isRedirection' => $code >= 300 && $code < 400, - 'isClientError' => $code >= 400 && $code < 500, - 'isServerError' => $code >= 500 - ]; - } - - /** - * Get map of request methods - * - * @param string $method - * @return array - */ - public function getMethodsMap($method) - { - return [ - 'isGetRequest' => $method === 'GET', - 'isPostRequest' => $method === 'POST', - 'isPutRequest' => $method === 'PUT', - 'isDeleteRequest' => $method === 'DELETE', - 'isHeadRequest' => $method === 'HEAD' - ]; - } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..7648e13 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,4 @@ +<?php + +$loader = require __DIR__ . '/../vendor/autoload.php'; +$loader->addPsr4('Jasny\\Controller\\', __DIR__ . '/support'); diff --git a/tests/support/RouteActionController.php b/tests/support/RouteActionController.php new file mode 100644 index 0000000..66625a9 --- /dev/null +++ b/tests/support/RouteActionController.php @@ -0,0 +1,13 @@ +<?php + +namespace Jasny\Controller; + +use Jasny\Controller; + +/** + * Class for testing 'RouteAction' trait + */ +class RouteActionController extends Controller +{ + use Controller\RouteAction; +} diff --git a/tests/support/SessionController.php b/tests/support/SessionController.php new file mode 100644 index 0000000..6758419 --- /dev/null +++ b/tests/support/SessionController.php @@ -0,0 +1,13 @@ +<?php + +namespace Jasny\Controller; + +use Jasny\Controller; + +/** + * Class for testing 'Session' trait + */ +abstract class SessionController extends Controller +{ + use Controller\Session; +} diff --git a/tests/support/TestController.php b/tests/support/TestController.php deleted file mode 100644 index e0bdb7b..0000000 --- a/tests/support/TestController.php +++ /dev/null @@ -1,50 +0,0 @@ -<?php - -use Jasny\Controller; -use Jasny\Controller\RouteAction; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\ResponseInterface; - -/** - * Class for testing 'RouteAction' trait - */ -class TestController extends Controller -{ - use RouteAction; - - /** - * Test action for executing router - * - * @param mixed $param1 - * @param mixed $param2 - * @return ResponseInterface - */ - public function testRunAction($param1, $param2 = 'defaultValue') - { - $response = $this->getResponse(); - - $response->actionCalled = true; - $response->param1 = $param1; - $response->param2 = $param2; - - return $response; - } - - /** - * Test action for executing router - * - * @param mixed $param1 - * @param mixed $param2 - * @return ResponseInterface - */ - public function defaultAction($param1, $param2 = 'defaultValue') - { - $response = $this->getResponse(); - - $response->defaultActionCalled = true; - $response->param1 = $param1; - $response->param2 = $param2; - - return $response; - } -} diff --git a/tests/support/TestHelper.php b/tests/support/TestHelper.php new file mode 100644 index 0000000..01347f4 --- /dev/null +++ b/tests/support/TestHelper.php @@ -0,0 +1,27 @@ +<?php + +namespace Jasny\Controller; + +use Jasny\Controller; + +/** + * Additional test methods + */ +trait TestHelper +{ + /** + * Get mock for controller + * + * @param array $methods Methods to mock + * @return Controller|\PHPUnit_Framework_MockObject_MockObject + */ + public function getController($methods = []) + { + $builder = $this->getMockBuilder(Controller::class)->disableOriginalConstructor(); + if ($methods) { + $builder->setMethods($methods); + } + + return $builder->getMockForAbstractClass(); + } +} |