summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArnold Daniels <arnold@jasny.net>2016-10-20 19:50:36 +0200
committerArnold Daniels <arnold@jasny.net>2016-10-20 19:50:36 +0200
commitaa31c40618b0cc7b43b7a1cb107e97b49e2c06f1 (patch)
tree6159c1ed5e95142c1e64e548f5a5b2323e1d48d2
parent08b0a5385f5d58f73f33514f0a4b0f2985d042c7 (diff)
parent17e7a0af39e479c554d5dc4a064678d9fde71fc0 (diff)
downloadrouter-aa31c40618b0cc7b43b7a1cb107e97b49e2c06f1.zip
router-aa31c40618b0cc7b43b7a1cb107e97b49e2c06f1.tar.gz
router-aa31c40618b0cc7b43b7a1cb107e97b49e2c06f1.tar.bz2
Merge remote-tracking branch 'origin/router-cleanup'
-rw-r--r--src/Router.php12
-rw-r--r--src/Router/Middleware/BasePath.php118
-rw-r--r--src/Router/Middleware/ErrorHandler.php54
-rw-r--r--src/Router/Middleware/ErrorPage.php75
-rw-r--r--src/Router/Middleware/NotFound.php105
-rw-r--r--src/Router/Routes/Glob.php6
-rw-r--r--tests/Router/Middleware/BasePathTest.php235
-rw-r--r--tests/Router/Middleware/ErrorHandlerTest.php86
-rw-r--r--tests/Router/Middleware/ErrorPageTest.php152
-rw-r--r--tests/Router/Middleware/NotFoundTest.php265
-rw-r--r--tests/Router/Routes/GlobTest.php35
-rw-r--r--tests/RouterTest.php10
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");
}
/**