diff options
author | Arnold Daniels <arnold@jasny.net> | 2016-10-20 19:50:36 +0200 |
---|---|---|
committer | Arnold Daniels <arnold@jasny.net> | 2016-10-20 19:50:36 +0200 |
commit | aa31c40618b0cc7b43b7a1cb107e97b49e2c06f1 (patch) | |
tree | 6159c1ed5e95142c1e64e548f5a5b2323e1d48d2 | |
parent | 08b0a5385f5d58f73f33514f0a4b0f2985d042c7 (diff) | |
parent | 17e7a0af39e479c554d5dc4a064678d9fde71fc0 (diff) | |
download | router-aa31c40618b0cc7b43b7a1cb107e97b49e2c06f1.zip router-aa31c40618b0cc7b43b7a1cb107e97b49e2c06f1.tar.gz router-aa31c40618b0cc7b43b7a1cb107e97b49e2c06f1.tar.bz2 |
Merge remote-tracking branch 'origin/router-cleanup'
-rw-r--r-- | src/Router.php | 12 | ||||
-rw-r--r-- | src/Router/Middleware/BasePath.php | 118 | ||||
-rw-r--r-- | src/Router/Middleware/ErrorHandler.php | 54 | ||||
-rw-r--r-- | src/Router/Middleware/ErrorPage.php | 75 | ||||
-rw-r--r-- | src/Router/Middleware/NotFound.php | 105 | ||||
-rw-r--r-- | src/Router/Routes/Glob.php | 6 | ||||
-rw-r--r-- | tests/Router/Middleware/BasePathTest.php | 235 | ||||
-rw-r--r-- | tests/Router/Middleware/ErrorHandlerTest.php | 86 | ||||
-rw-r--r-- | tests/Router/Middleware/ErrorPageTest.php | 152 | ||||
-rw-r--r-- | tests/Router/Middleware/NotFoundTest.php | 265 | ||||
-rw-r--r-- | tests/Router/Routes/GlobTest.php | 35 | ||||
-rw-r--r-- | tests/RouterTest.php | 10 |
12 files changed, 1139 insertions, 14 deletions
diff --git a/src/Router.php b/src/Router.php index a7d4bca..6d38678 100644 --- a/src/Router.php +++ b/src/Router.php @@ -115,7 +115,7 @@ class Router * @param ResponseInterface $response * @return ResponseInterface */ - final public function run(ServerRequestInterface $request, ResponseInterface $response) + final public function handle(ServerRequestInterface $request, ResponseInterface $response) { return $this->__invoke($request, $response); } @@ -130,11 +130,11 @@ class Router */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next = null) { - $handle = [$this, 'handle']; + $run = [$this, 'run']; - #Call to $this->handle will be executed last in the chain of middlewares - $next = function(ServerRequestInterface $request, ResponseInterface $response) use ($next, $handle) { - return call_user_func($handle, $request, $response, $next); + #Call to $this->run will be executed last in the chain of middlewares + $next = function(ServerRequestInterface $request, ResponseInterface $response) use ($next, $run) { + return call_user_func($run, $request, $response, $next); }; #Build middlewares call chain, so that the last added was executed in first place @@ -155,7 +155,7 @@ class Router * @param callback $next * @return ResponseInterface */ - protected function handle(ServerRequestInterface $request, ResponseInterface $response, $next = null) + public function run(ServerRequestInterface $request, ResponseInterface $response, $next = null) { $glob = new Glob($this->routes); $route = $glob->getRoute($request); diff --git a/src/Router/Middleware/BasePath.php b/src/Router/Middleware/BasePath.php new file mode 100644 index 0000000..a80d029 --- /dev/null +++ b/src/Router/Middleware/BasePath.php @@ -0,0 +1,118 @@ +<?php + +namespace Jasny\Router\Middleware; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Set base path for request + */ +class BasePath +{ + /** + * Base path + * @var string + **/ + protected $basePath = ''; + + /** + * Set base path + * + * @param string $basePath + */ + public function __construct($basePath) + { + if (!$basePath || !is_string($basePath) || $basePath === '/') { + throw new \InvalidArgumentException("Base path must be a string with at list one url segment"); + } + + $this->basePath = $this->normalizePath($basePath); + } + + /** + * Get base path + * + * @return string + */ + public function getBasePath() + { + return $this->basePath; + } + + /** + * Run middleware action + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param callback $next + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next) + { + if (!is_callable($next)) { + throw new \InvalidArgumentException("'next' should be a callback"); + } + + $uri = $request->getUri(); + $path = $this->normalizePath($uri->getPath()); + + if (!$this->hasBasePath($path)) return $this->setError($response); + + $noBase = $this->getBaselessPath($path); + $noBaseUri = $uri->withPath($noBase); + $request = $request->withUri($noBaseUri)->withAttribute('original_uri', $uri); + + return call_user_func($next, $request, $response); + } + + /** + * Remove base path from given path + * + * @param string $path + * @return string + */ + protected function getBaselessPath($path) + { + return substr($path, strlen($this->getBasePath())) ?: '/'; + } + + /** + * Normalize path + * + * @param string $path + * @return string + */ + protected function normalizePath($path) + { + return '/' . ltrim($path, '/'); + } + + /** + * Check that path starts with base path + * + * @param string $path + * @return boolean + */ + protected function hasBasePath($path) + { + return strpos($path . '/', $this->getBasePath() . '/') === 0; + } + + /** + * Set error response + * + * @param ResponseInterface $response + * @return ResponseInterface + */ + protected function setError($response) + { + $message = 'Not Found'; + + $body = $response->getBody(); + $body->rewind(); + $body->write($message); + + return $response->withStatus(404, $message)->withBody($body); + } +} diff --git a/src/Router/Middleware/ErrorHandler.php b/src/Router/Middleware/ErrorHandler.php new file mode 100644 index 0000000..789c455 --- /dev/null +++ b/src/Router/Middleware/ErrorHandler.php @@ -0,0 +1,54 @@ +<?php + +namespace Jasny\Router\Middleware; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Handle error in following middlewares/app actions + */ +class ErrorHandler +{ + /** + * Run middleware action + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param callback $next + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next = null) + { + if ($next && !is_callable($next)) { + throw new \InvalidArgumentException("'next' should be a callback"); + } + + $error = false; + + try { + $response = $next ? call_user_func($next, $request, $response) : $response; + } catch(\Throwable $e) { + $error = true; + } catch(\Exception $e) { #This block can be removed when migrating to PHP7, because Throwable represents both Exception and Error + $error = true; + } + + return $error ? $this->handleError($response) : $response; + } + + /** + * Handle caught error + * + * @param ResponseInterface $response + * @return ResponseInterface + */ + protected function handleError($response) + { + $body = $response->getBody(); + $body->rewind(); + $body->write('Unexpected error'); + + return $response->withStatus(500, 'Internal Server Error')->withBody($body); + } +} diff --git a/src/Router/Middleware/ErrorPage.php b/src/Router/Middleware/ErrorPage.php new file mode 100644 index 0000000..7064b1e --- /dev/null +++ b/src/Router/Middleware/ErrorPage.php @@ -0,0 +1,75 @@ +<?php + +namespace Jasny\Router\Middleware; + +use Jasny\Router; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Route to error page on error + */ +class ErrorPage +{ + /** + * Router + * @var Router + */ + protected $router = null; + + /** + * Class constructor + * + * @param Router $routes + */ + public function __construct(Router $router) + { + $this->router = $router; + } + + /** + * Get router connected to middleware + * + * @return Router + */ + public function getRouter() + { + return $this->router; + } + + /** + * Run middleware action + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param callback $next + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next = null) + { + if ($next && !is_callable($next)) { + throw new \InvalidArgumentException("'next' should be a callback"); + } + + $response = $next ? call_user_func($next, $request, $response) : $response; + $status = $response->getStatusCode(); + + if (!$this->isErrorStatus($status)) return $response; + + $uri = $request->getUri()->withPath("/$status"); + $request = $request->withUri($uri, true); + + return $this->getRouter()->run($request, $response); + } + + /** + * Detect if response has error status code + * + * @param int $status + * @return boolean + */ + protected function isErrorStatus($status) + { + return $status >= 400 && $status < 600; + } +} diff --git a/src/Router/Middleware/NotFound.php b/src/Router/Middleware/NotFound.php new file mode 100644 index 0000000..97b4a51 --- /dev/null +++ b/src/Router/Middleware/NotFound.php @@ -0,0 +1,105 @@ +<?php + +namespace Jasny\Router\Middleware; + +use Jasny\Router\Routes; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Set response to 'not found' or 'method not allowed' if route is non exist + */ +class NotFound +{ + /** + * Routes + * @var Routes + */ + protected $routes = null; + + /** + * Action for 'not found' case + * @var callback|int + **/ + protected $notFound = null; + + /** + * Action for 'method not allowed' case + * @var callback|int + **/ + protected $methodNotAllowed = null; + + /** + * Class constructor + * + * @param Routes $routes + * @param callback|int $notFound + * @param callback|int $methodNotAllowed + */ + public function __construct(Routes $routes, $notFound = 404, $methodNotAllowed = null) + { + if (!(is_numeric($notFound) && $notFound >= 100 && $notFound <= 599) && !is_callable($notFound)) { + throw new \InvalidArgumentException("'Not found' parameter should be a code in range 100-599 or a callback"); + } + + if ($methodNotAllowed && !(is_numeric($methodNotAllowed) && $methodNotAllowed >= 100 && $methodNotAllowed <= 599) && !is_callable($methodNotAllowed)) { + throw new \InvalidArgumentException("'Method not allowed' parameter should be a code in range 100-599 or a callback"); + } + + $this->routes = $routes; + $this->notFound = $notFound; + $this->methodNotAllowed = $methodNotAllowed; + } + + /** + * Get routes + * + * @return Routes + */ + public function getRoutes() + { + return $this->routes; + } + + /** + * Run middleware action + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param callback $next + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next = null) + { + if ($next && !is_callable($next)) { + throw new \InvalidArgumentException("'next' should be a callback"); + } + + if ($this->getRoutes()->hasRoute($request)) { + return $next ? $next($request, $response) : $response; + } + + $status = $this->methodNotAllowed && $this->getRoutes()->hasRoute($request, false) ? + $this->methodNotAllowed : $this->notFound; + + return is_numeric($status) ? $this->simpleResponse($response, $status) : call_user_func($status, $request, $response); + } + + /** + * Simple response + * + * @param ResponseInterface $response + * @param int $code + * @return ResponseInterface + */ + protected function simpleResponse(ResponseInterface $response, $code) + { + $message = 'Not Found'; + + $body = $response->getBody(); + $body->rewind(); + $body->write($message); + + return $response->withStatus($code, $message)->withBody($body); + } +} diff --git a/src/Router/Routes/Glob.php b/src/Router/Routes/Glob.php index 1d5af33..fcece76 100644 --- a/src/Router/Routes/Glob.php +++ b/src/Router/Routes/Glob.php @@ -145,7 +145,7 @@ class Glob extends ArrayObject implements Routes if ($path !== '/') $path = rtrim($path, '/'); if ($this->fnmatch($path, $url)) { - if ((empty($inc) || in_array($method, $inc)) && !in_array($method, $excl)) { + if (!$method || ((empty($inc) || in_array($method, $inc)) && !in_array($method, $excl))) { $ret = $route; break; } @@ -356,9 +356,9 @@ class Glob extends ArrayObject implements Routes * @param ServerRequestInterface $request * @return boolean */ - public function hasRoute(ServerRequestInterface $request) + public function hasRoute(ServerRequestInterface $request, $withMethod = true) { - $route = $this->findRoute($request->getUri(), $request->getMethod()); + $route = $this->findRoute($request->getUri(), $withMethod ? $request->getMethod() : null); return isset($route); } diff --git a/tests/Router/Middleware/BasePathTest.php b/tests/Router/Middleware/BasePathTest.php new file mode 100644 index 0000000..eb9a2e8 --- /dev/null +++ b/tests/Router/Middleware/BasePathTest.php @@ -0,0 +1,235 @@ +<?php + +use Jasny\Router\Middleware\BasePath; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; + +class BasePathTest extends PHPUnit_Framework_TestCase +{ + /** + * Test creating middleware with invalid parameter + * + * @dataProvider invalidConstructProvider + */ + public function testInvalidConstruct($basePath) + { + $this->expectException(\InvalidArgumentException::class); + + $pathHandler = new BasePath($basePath); + } + + /** + * Provide data for testing invalid BasePath creation + * + * @return array + */ + public function invalidConstructProvider() + { + return [ + [''], + ['/'], + [null], + [false], + [['test']], + [(object)['test']], + [12345] + ]; + } + + /** + * Test creating BasePath instance + * + * @dataProvider validConstructProvider + * @param string $basePath + */ + public function testValidConstruct($basePath, $validBasePath) + { + $pathHandler = new BasePath($basePath); + + $this->assertNotEmpty($pathHandler->getBasePath(), "Empty base path"); + $this->assertEquals($validBasePath, $pathHandler->getBasePath(), "Base path was not set correctly"); + } + + /** + * Provide data for testing BasePath creation + * + * @return array + */ + public function validConstructProvider() + { + return [ + ['/foo', '/foo'], + ['/foo/', '/foo/'], + ['foo/', '/foo/'], + ['/foo/bar', '/foo/bar'], + ['foo/bar', '/foo/bar'], + ['/foo/bar/', '/foo/bar/'], + ['/foo/bar-zet/', '/foo/bar-zet/'] + ]; + } + + /** + * Test invoke with invalid 'next' param + */ + public function testInvokeInvalidNext() + { + $middleware = new BasePath('/foo'); + list($request, $response) = $this->getRequests(); + + $this->expectException(\InvalidArgumentException::class); + + $result = $middleware($request, $response, 'not_callable'); + } + + /** + * Test case when given request path does not starts with given base path + * + * @dataProvider notFoundProvider + * @param string $basePath + * @param string $path + */ + public function testNotFound($basePath, $path) + { + $middleware = new BasePath($basePath); + list($request, $response) = $this->getRequests(); + + $this->expectRequestGetPath($request, $path); + $this->expectNotFound($response); + + $result = $middleware($request, $response, function($response, $request) { + $response->nextCalled = true; + + return $response; + }); + + $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object"); + $this->assertFalse(isset($response->nextCalled), "'next' was called"); + } + + /** + * Provide data for testing BasePath creation + * + * @return array + */ + public function notFoundProvider() + { + return [ + ['/foo', '/bar'], + ['/foo', '/bar/foo'], + ['/foo/bar', '/zet/foo/bar'], + ['/foo/bar', '/foo/bar-/teta'], + ['/foo/bar', '/foo/bar-zet/teta'], + ['/foo/bar', '/foo/ba'], + ['/foo/bar', '/foo'], + ['/f', '/foo'], + ]; + } + + /** + * Test correct case, when path contains base path + * + * @dataProvider foundProvider + * @param string $basePath + * @param string $path + * @param string $noBasePath + */ + public function testFound($basePath, $path, $noBasePath) + { + $middleware = new BasePath($basePath); + list($request, $response) = $this->getRequests(); + + $this->expectRequestSetBasePath($request, $basePath, $path, $noBasePath); + + $result = $middleware($request, $response, function($request, $response) { + $response->nextCalled = true; + + return $response; + }); + + $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object"); + $this->assertTrue($response->nextCalled, "'next' was not called"); + } + + /** + * Provide data for testing BasePath creation + * + * @return array + */ + public function foundProvider() + { + return [ + ['/foo', '/foo', '/'], + ['foo', '/foo', '/'], + ['/foo', '/foo/bar', '/bar'], + ['/foo/bar', '/foo/bar', '/'], + ['foo/bar', '/foo/bar', '/'], + ['/foo/bar', '/foo/bar/zet', '/zet'], + ['/f', '/f/foo', '/foo'], + ['f', '/f/foo', '/foo'], + ]; + } + + /** + * Get requests for testing + * + * @param string $path + * @return array + */ + public function getRequests($path = null) + { + $request = $this->createMock(ServerRequestInterface::class); + $response = $this->createMock(ResponseInterface::class); + + return [$request, $response]; + } + + /** + * Expect that request will return a path + * + * @param ServerRequestInterface $request + * @param string $path + */ + public function expectRequestGetPath(ServerRequestInterface $request, $path) + { + $uri = $this->createMock(UriInterface::class); + $uri->expects($this->once())->method('getPath')->will($this->returnValue($path)); + $request->expects($this->once())->method('getUri')->will($this->returnValue($uri)); + } + + /** + * Expect for setting base path for request + * + * @param ServerRequestInterface $request + * @param string $basePath + * @param string $path + * @param string $noBasePath + */ + public function expectRequestSetBasePath(ServerRequestInterface $request, $basePath, $path, $noBasePath) + { + $uri = $this->createMock(UriInterface::class); + $uri->expects($this->once())->method('getPath')->will($this->returnValue($path)); + $uri->expects($this->once())->method('withPath')->with($this->equalTo($noBasePath))->will($this->returnSelf()); + + $request->expects($this->once())->method('getUri')->will($this->returnValue($uri)); + $request->expects($this->once())->method('withUri')->with($this->equalTo($uri))->will($this->returnSelf()); + $request->expects($this->once())->method('withAttribute')->with($this->equalTo('original_uri'), $this->equalTo($uri))->will($this->returnSelf()); + } + + /** + * Expect for not found error + * + * @param ResponseInterface $response + */ + public function expectNotFound(ResponseInterface $response) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('rewind'); + $stream->expects($this->once())->method('write')->with($this->equalTo('Not Found')); + + $response->method('getBody')->will($this->returnValue($stream)); + $response->expects($this->once())->method('withBody')->with($this->equalTo($stream))->will($this->returnSelf()); + $response->expects($this->once())->method('withStatus')->with($this->equalTo(404), $this->equalTo('Not Found'))->will($this->returnSelf()); + } +} diff --git a/tests/Router/Middleware/ErrorHandlerTest.php b/tests/Router/Middleware/ErrorHandlerTest.php new file mode 100644 index 0000000..c4b6313 --- /dev/null +++ b/tests/Router/Middleware/ErrorHandlerTest.php @@ -0,0 +1,86 @@ +<?php + +use Jasny\Router\Middleware\ErrorHandler; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +class ErrorHandlerTest extends PHPUnit_Framework_TestCase +{ + /** + * Test invoke with invalid 'next' param + */ + public function testInvokeInvalidNext() + { + $middleware = new ErrorHandler(); + list($request, $response) = $this->getRequests(); + + $this->expectException(\InvalidArgumentException::class); + + $result = $middleware($request, $response, 'not_callable'); + } + + /** + * Test that exception in 'next' callback is caught + */ + public function testInvokeCatchError() + { + $middleware = new ErrorHandler(); + list($request, $response) = $this->getRequests(); + + $this->expectCatchError($response); + + $result = $middleware($request, $response, function($request, $response) { + throw new Exception('Test exception'); + }); + + $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object"); + } + + /** + * Test case when there is no error + */ + public function testInvokeNoError() + { + $middleware = new ErrorHandler(); + list($request, $response) = $this->getRequests(); + + $result = $middleware($request, $response, function($request, $response) { + $response->nextCalled = true; + + return $response; + }); + + $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object"); + $this->assertTrue($result->nextCalled, "'next' was not called"); + } + + /** + * Get requests for testing + * + * @return array + */ + public function getRequests() + { + $request = $this->createMock(ServerRequestInterface::class); + $response = $this->createMock(ResponseInterface::class); + + return [$request, $response]; + } + + /** + * Expect for error + * + * @param ResponseInterface $response + */ + public function expectCatchError($response) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('rewind'); + $stream->expects($this->once())->method('write')->with($this->equalTo('Unexpected error')); + + $response->method('getBody')->will($this->returnValue($stream)); + $response->expects($this->once())->method('withBody')->with($this->equalTo($stream))->will($this->returnSelf()); + $response->expects($this->once())->method('withStatus')->with($this->equalTo(500), $this->equalTo('Internal Server Error'))->will($this->returnSelf()); + } +} diff --git a/tests/Router/Middleware/ErrorPageTest.php b/tests/Router/Middleware/ErrorPageTest.php new file mode 100644 index 0000000..da87cd0 --- /dev/null +++ b/tests/Router/Middleware/ErrorPageTest.php @@ -0,0 +1,152 @@ +<?php + +use Jasny\Router; +use Jasny\Router\Routes\Glob; +use Jasny\Router\Middleware\ErrorPage; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; + +class ErrorPageTest extends PHPUnit_Framework_TestCase +{ + /** + * Test invoke with invalid 'next' param + */ + public function testInvokeInvalidNext() + { + $middleware = new ErrorPage($this->getRouter()); + list($request, $response) = $this->getRequests(); + + $this->expectException(\InvalidArgumentException::class); + + $result = $middleware($request, $response, 'not_callable'); + } + + /** + * Test error and not-error cases with calling 'next' callback + * + * @dataProvider invokeProvider + * @param int $statusCode + */ + public function testInvokeNext($statusCode) + { + $isError = $statusCode >= 400; + $router = $this->getRouter(); + $middleware = new ErrorPage($router); + list($request, $response) = $this->getRequests($statusCode); + + $isError ? + $this->expectSetError($router, $request, $response, $statusCode) : + $this->notExpectSetError($router, $request, $response, $statusCode); + + $result = $middleware($request, $response, function($request, $response) { + $response->nextCalled = true; + + return $response; + }); + + $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object"); + $this->assertTrue($result->nextCalled, "'next' was not called"); + } + + /** + * Test error and not-error cases without calling 'next' callback + * + * @dataProvider invokeProvider + * @param int $statusCode + */ + public function testInvokeNoNext($statusCode) + { + $isError = $statusCode >= 400; + $router = $this->getRouter(); + $middleware = new ErrorPage($router); + list($request, $response) = $this->getRequests($statusCode); + + $isError ? + $this->expectSetError($router, $request, $response, $statusCode) : + $this->notExpectSetError($router, $request, $response, $statusCode); + + $result = $middleware($request, $response); + + $this->assertEquals($response, $result, "Middleware should return response object"); + } + + /** + * Provide data for testing '__invoke' method + * + * @return array + */ + public function invokeProvider() + { + return [ + [200], + [300], + [400], + [404], + [500], + [503] + ]; + } + + /** + * Get requests for testing + * + * @param int $statusCode + * @return array + */ + public function getRequests($statusCode = null) + { + $request = $this->createMock(ServerRequestInterface::class); + $response = $this->createMock(ResponseInterface::class); + + if ($statusCode) { + $response->method('getStatusCode')->will($this->returnValue($statusCode)); + } + + return [$request, $response]; + } + + /** + * @return Router + */ + public function getRouter() + { + return $this->getMockBuilder(Router::class)->disableOriginalConstructor()->getMock(); + } + + /** + * Expect for error + * + * @param Router $router + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param int $statusCode + */ + public function expectSetError($router, $request, $response, $statusCode) + { + $uri = $this->createMock(UriInterface::class); + + $uri->expects($this->once())->method('withPath')->with($this->equalTo("/$statusCode"))->will($this->returnSelf()); + $request->expects($this->once())->method('getUri')->will($this->returnValue($uri)); + $request->expects($this->once())->method('withUri')->with($this->equalTo($uri), $this->equalTo(true))->will($this->returnSelf()); + $router->expects($this->once())->method('run')->with($this->equalTo($request), $this->equalTo($response))->will($this->returnValue($response)); + } + + /** + * Not expect for error + * + * @param Router $router + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param int $statusCode + */ + public function notExpectSetError($router, $request, $response, $statusCode) + { + $uri = $this->createMock(UriInterface::class); + + $uri->expects($this->never())->method('withPath'); + $request->expects($this->never())->method('getUri'); + $request->expects($this->never())->method('withUri'); + $router->expects($this->never())->method('run'); + } +} diff --git a/tests/Router/Middleware/NotFoundTest.php b/tests/Router/Middleware/NotFoundTest.php new file mode 100644 index 0000000..0c40a68 --- /dev/null +++ b/tests/Router/Middleware/NotFoundTest.php @@ -0,0 +1,265 @@ +<?php + +use Jasny\Router\Routes; +use Jasny\Router\Middleware\NotFound; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +class NotFoundTest extends PHPUnit_Framework_TestCase +{ + /** + * Test creating object with false parameters + * + * @dataProvider constructProvider + * @param string $notFound + * @param string $notAllowed + * @param boolean $positive + */ + public function testConstruct($notFound, $notAllowed, $positive) + { + if (!$positive) $this->expectException(\InvalidArgumentException::class); + + $middleware = new NotFound($this->getRoutes(), $notFound, $notAllowed); + + if ($positive) $this->skipTest(); + } + + /** + * Provide data for testing '__contruct' + */ + public function constructProvider() + { + return [ + [null, 405, false], + [true, true, false], + [99, null, false], + [600, null, false], + [404, 99, false], + [404, 600, false], + [200, 405, true], + [404, 200, true] + ]; + } + + /** + * Test invoke with invalid 'next' param + */ + public function testInvokeInvalidNext() + { + $middleware = new NotFound($this->getRoutes(), 404, 405); + list($request, $response) = $this->getRequests(); + + $this->expectException(\InvalidArgumentException::class); + + $result = $middleware($request, $response, 'not_callable'); + } + + /** + * Test that 'next' callback is invoked when route is found + * + * @dataProvider invokeProvider + * @param callback|int $notFound + * @param callback|int $notAllowed + * @param callback $next + */ + public function testInvokeFound($notFound, $notAllowed, $next) + { + if (!$next) return $this->skipTest(); + + list($request, $response) = $this->getRequests(); + $routes = $this->getRoutes(); + $middleware = new NotFound($routes, $notFound, $notAllowed); + + $this->expectRoute($routes, $request, 'found'); + $this->notExpectSimpleError($response); + + $result = $middleware($request, $response, $next); + + $this->assertEquals(get_class($response), get_class($result), "Result must be an instance of 'ResponseInterface'"); + $this->assertTrue($result->nextCalled, "'next' was not called"); + $this->assertFalse(isset($result->notAllowedCalled), "'Not allowed' callback was called"); + $this->assertFalse(isset($result->notFoundCalled), "'Not found' callback was called"); + } + + /** + * Test __invoke method in case of route is found with another method + * + * @dataProvider invokeProvider + * @param callback|int $notFound + * @param callback|int $notAllowed + * @param callback $next + */ + public function testInvokeNotAllowed($notFound, $notAllowed, $next) + { + if (!$notAllowed) return $this->skipTest(); + + list($request, $response) = $this->getRequests(); + $routes = $this->getRoutes(); + $middleware = new NotFound($routes, $notFound, $notAllowed); + + $this->expectRoute($routes, $request, 'notAllowed'); + if (is_numeric($notAllowed)) { + $this->expectSimpleError($response, $notAllowed); + } + + $result = $middleware($request, $response, $next); + + $this->assertEquals(get_class($response), get_class($result), "Result must be an instance of 'ResponseInterface'"); + $this->assertFalse(isset($result->nextCalled), "'next' was called"); + + if (is_callable($notAllowed)) { + $this->assertTrue($result->notAllowedCalled, "'Not allowed' callback was not called"); + } + } + + /** + * Test __invoke method in case of route not found at all + * + * @dataProvider invokeProvider + * @param callback|int $notFound + * @param callback|int $notAllowed + * @param callback $next + */ + public function testInvokeNotFound($notFound, $notAllowed, $next) + { + list($request, $response) = $this->getRequests(); + $routes = $this->getRoutes(); + $middleware = new NotFound($routes, $notFound, $notAllowed); + + $case = $notAllowed ? 'notFoundTwice' : 'notFoundOnce'; + $this->expectRoute($routes, $request, $case); + + if (is_numeric($notFound)) { + $this->expectSimpleError($response, $notFound); + } + + $result = $middleware($request, $response, $next); + + $this->assertEquals(get_class($response), get_class($result), "Result must be an instance of 'ResponseInterface'"); + $this->assertFalse(isset($result->nextCalled), "'next' was called"); + + if (is_callable($notAllowed)) { + $this->assertFalse(isset($result->notAllowedCalled), "'Not allowed' callback was called"); + } + if (is_callable($notFound)) { + $this->assertTrue($result->notFoundCalled, "'Not found' callback was not called"); + } + } + + /** + * Set expectations on finding route + * + * @param Routes $routes + * @param ServerRequestInterface $request + * @param string $case + */ + public function expectRoute($routes, $request, $case) + { + if ($case === 'found' || $case === 'notFoundOnce') { + $found = $case === 'found'; + + $routes->expects($this->once())->method('hasRoute') + ->with($this->equalTo($request))->will($this->returnValue($found)); + } elseif ($case === 'notAllowed' || $case === 'notFoundTwice') { + $routes->expects($this->exactly(2))->method('hasRoute') + ->withConsecutive( + [$this->equalTo($request)], + [$this->equalTo($request), $this->equalTo(false)] + )->will($this->returnCallback(function($request, $searchMethod = true) use ($case) { + return $case === 'notFoundTwice' ? false : !$searchMethod; + })); + } + } + + /** + * Provide data for testing invoke method + */ + public function invokeProvider() + { + $callbacks = []; + foreach (['notFound', 'notAllowed', 'next'] as $type) { + $var = $type . 'Called'; + + $callbacks[$type] = function($request, $response) use ($var) { + $response->$var = true; + return $response; + }; + } + + return [ + [404, 405, $callbacks['next']], + [404, 405, null], + [404, null, $callbacks['next']], + [404, null, null], + [$callbacks['notFound'], $callbacks['notAllowed'], $callbacks['next']], + [$callbacks['notFound'], $callbacks['notAllowed'], null], + [$callbacks['notFound'], null, $callbacks['next']], + [$callbacks['notFound'], null, null] + ]; + } + + /** + * Expect that response is set to simple deny answer + * + * @param ResponseInterface $response + * @param int $statusCode + */ + public function expectSimpleError(ResponseInterface $response, $statusCode) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('rewind'); + $stream->expects($this->once())->method('write')->with($this->equalTo('Not Found')); + + $response->method('getBody')->will($this->returnValue($stream)); + $response->expects($this->once())->method('withBody')->with($this->equalTo($stream))->will($this->returnSelf()); + $response->expects($this->once())->method('withStatus')->with($this->equalTo($statusCode), $this->equalTo('Not Found'))->will($this->returnSelf()); + } + + /** + * Expect that there would be no simple error response + * + * @param ResponseInterface $response + */ + public function notExpectSimpleError(ResponseInterface $response) + { + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->never())->method('rewind'); + $stream->expects($this->never())->method('write'); + + $response->expects($this->never())->method('getBody'); + $response->expects($this->never())->method('withBody'); + $response->expects($this->never())->method('withStatus'); + } + + /** + * Get requests for testing + * + * @return array + */ + public function getRequests() + { + $request = $this->createMock(ServerRequestInterface::class); + $response = $this->createMock(ResponseInterface::class); + + return [$request, $response]; + } + + /** + * Get routes array + * + * @return Routes + */ + public function getRoutes() + { + return $this->getMockBuilder(Routes::class)->disableOriginalConstructor()->getMock(); + } + + /** + * Skip the test pass + */ + public function skipTest() + { + return $this->assertTrue(true); + } +} diff --git a/tests/Router/Routes/GlobTest.php b/tests/Router/Routes/GlobTest.php index e1ab551..3ed4d21 100644 --- a/tests/Router/Routes/GlobTest.php +++ b/tests/Router/Routes/GlobTest.php @@ -260,6 +260,41 @@ class GlobTest extends \PHPUnit_Framework_TestCase } /** + * Test checking if route exists regardless of request method + * + * @dataProvider getHasRouteNoMethodProvider + */ + public function testHasRouteNoMethod($uri, $method) + { + $values = [ + '/' => ['controller' => 'value0'], + '/foo/bar' => ['controller' => 'value1'], + '/foo +GET' => ['controller' => 'value2'], + '/bar/foo/zet -POST' => ['controller' => 'value3'] + ]; + + $glob = new Glob($values); + $request = $this->getServerRequest($uri, $method); + + $this->assertTrue($glob->hasRoute($request, false), "Route not exists"); + } + + /** + * Provide data for creating ServerRequestInterface objects + */ + public function getHasRouteNoMethodProvider() + { + return [ + ['/', 'GET'], + ['/foo/bar', 'GET'], + ['/foo', 'GET'], + ['/foo', 'POST'], + ['/bar/foo/zet', 'GET'], + ['/bar/foo/zet', 'POST'] + ]; + } + + /** * Test binding simple string when getting route */ public function testBindVarString() diff --git a/tests/RouterTest.php b/tests/RouterTest.php index c5be4e2..46574c4 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -24,9 +24,9 @@ class RouterTest extends PHPUnit_Framework_TestCase } /** - * Test that on router 'run', method '__invoke' is called + * Test that on router 'handle', method '__invoke' is called */ - public function testRun() + public function testHandle() { $router = $this->createMock(Router::class, ['__invoke']); list($request, $response) = $this->getRequests(); @@ -35,7 +35,7 @@ class RouterTest extends PHPUnit_Framework_TestCase return ['request' => $arg1, 'response' => $arg2]; })); - $result = $router->run($request, $response); + $result = $router->handle($request, $response); $this->assertEquals($request, $result['request'], "Request was not processed correctly"); $this->assertEquals($response, $result['response'], "Response was not processed correctly"); @@ -153,7 +153,7 @@ class RouterTest extends PHPUnit_Framework_TestCase { $routes = [ '/foo' => Route::create(['fn' => function($request, $response) { - $response->testMiddlewareCalls[] = 'handle'; + $response->testMiddlewareCalls[] = 'run'; return $response; }]) ]; @@ -169,7 +169,7 @@ class RouterTest extends PHPUnit_Framework_TestCase return $response; }); - $this->assertEquals(['first','last','handle','outer'], $response->testMiddlewareCalls, "Actions were executed in wrong order"); + $this->assertEquals(['first','last','run','outer'], $response->testMiddlewareCalls, "Actions were executed in wrong order"); } /** |