summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArnold Daniels <arnold@jasny.net>2016-11-03 23:45:36 +0100
committerGitHub <noreply@github.com>2016-11-03 23:45:36 +0100
commitf3d05dfca77843a869e938028824f19307780939 (patch)
tree846c5e559360e530d200d897f67b5af02f8795e5
parentaa31c40618b0cc7b43b7a1cb107e97b49e2c06f1 (diff)
parentbd4cb77fbf04923fa31a08fdd1f33f2c0db87864 (diff)
downloadrouter-1.0.0.zip
router-1.0.0.tar.gz
router-1.0.0.tar.bz2
Merge pull request #13 from jasny/fix-testsv1.0.0
Fix tests
-rw-r--r--composer.json3
-rw-r--r--phpunit.xml.dist2
-rw-r--r--src/Router.php71
-rw-r--r--src/Router/Middleware/BasePath.php24
-rw-r--r--src/Router/Middleware/ErrorHandler.php54
-rw-r--r--src/Router/Middleware/NotFound.php49
-rw-r--r--src/Router/Route.php26
-rw-r--r--src/Router/Routes/Glob.php217
-rw-r--r--src/Router/Routes/RouteBinding.php314
-rw-r--r--src/Router/Runner.php14
-rw-r--r--src/Router/Runner/Callback.php2
-rw-r--r--src/Router/Runner/Controller.php39
-rw-r--r--src/Router/Runner/PhpScript.php27
-rw-r--r--src/Router/RunnerFactory.php (renamed from src/Router/Runner/RunnerFactory.php)10
-rw-r--r--src/Router/UrlParsing.php4
-rw-r--r--tests/Router/Middleware/BasePathTest.php142
-rw-r--r--tests/Router/Middleware/ErrorHandlerTest.php86
-rw-r--r--tests/Router/Middleware/NotFoundTest.php363
-rw-r--r--tests/Router/RouteTest.php42
-rw-r--r--tests/Router/Routes/GlobTest.php323
-rw-r--r--tests/Router/Routes/RouteBindingTest.php263
-rw-r--r--tests/Router/Runner/CallbackTest.php89
-rw-r--r--tests/Router/Runner/ControllerTest.php182
-rw-r--r--tests/Router/Runner/PhpScriptTest.php152
-rw-r--r--tests/Router/Runner/RunnerFactoryTest.php41
-rw-r--r--tests/Router/RunnerFactoryTest.php58
-rw-r--r--tests/Router/RunnerTest.php53
-rw-r--r--tests/RouterTest.php342
-rw-r--r--tests/bootstrap.php4
-rw-r--r--tests/support/TestHelpers.php30
30 files changed, 1597 insertions, 1429 deletions
diff --git a/composer.json b/composer.json
index 147e6ea..ad3d7c8 100644
--- a/composer.json
+++ b/composer.json
@@ -17,7 +17,8 @@
"require": {
"php": ">=5.6.0",
"jasny/php-functions": "^2.0",
- "psr/http-message": "^1.0"
+ "psr/http-message": "^1.0",
+ "psr/log": "^1.0"
},
"require-dev": {
"jasny/php-code-quality": "^2.0"
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/Router.php b/src/Router.php
index 6d38678..5cc2108 100644
--- a/src/Router.php
+++ b/src/Router.php
@@ -2,8 +2,8 @@
namespace Jasny;
-use Jasny\Router\Runner\RunnerFactory;
-use Jasny\Router\Routes\Glob;
+use Jasny\Router\Routes;
+use Jasny\Router\RunnerFactory;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
@@ -14,9 +14,9 @@ class Router
{
/**
* Specific routes
- * @var array
+ * @var Routes
*/
- protected $routes = [];
+ protected $routes;
/**
* Middlewares actions
@@ -28,22 +28,23 @@ class Router
* Factory of Runner objects
* @var RunnerFactory
**/
- protected $factory = null;
+ protected $factory;
+
/**
* Class constructor
*
- * @param array $routes
+ * @param Routes $routes
*/
- public function __construct(array $routes)
+ public function __construct(Routes $routes)
{
$this->routes = $routes;
}
/**
- * Get a list of all routes
+ * Get a all routes
*
- * @return object
+ * @return Routes
*/
public function getRoutes()
{
@@ -51,7 +52,7 @@ class Router
}
/**
- * Get middlewares
+ * Get all middlewares
*
* @return array
*/
@@ -60,6 +61,7 @@ class Router
return $this->middlewares;
}
+
/**
* Get factory of Runner objects
*
@@ -100,7 +102,7 @@ class Router
public function add($middleware)
{
if (!is_callable($middleware)) {
- throw new \InvalidArgumentException("Middleware should be a callable");
+ throw new \InvalidArgumentException("Middleware should be callable");
}
$this->middlewares[] = $middleware;
@@ -108,6 +110,7 @@ class Router
return $this;
}
+
/**
* Run the action for the request
*
@@ -130,17 +133,16 @@ class Router
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next = null)
{
- $run = [$this, 'run'];
-
- #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);
- };
+ if (empty($this->middlewares)) {
+ return $this->run($request, $response, $next);
+ }
+
+ $stack = array_merge([[$this, 'run']], $this->middlewares);
- #Build middlewares call chain, so that the last added was executed in first place
- foreach ($this->middlewares as $middleware) {
- $next = function(ServerRequestInterface $request, ResponseInterface $response) use ($next, $middleware) {
- return $middleware($request, $response, $next);
+ // Turn the stack into a call chain
+ foreach ($stack as $handle) {
+ $next = function(ServerRequestInterface $request, ResponseInterface $response) use ($handle, $next) {
+ return $handle($request, $response, $next);
};
}
@@ -157,33 +159,32 @@ class Router
*/
public function run(ServerRequestInterface $request, ResponseInterface $response, $next = null)
{
- $glob = new Glob($this->routes);
- $route = $glob->getRoute($request);
+ $route = $this->routes->getRoute($request);
+
+ if (!$route) {
+ return $this->notFound($request, $response);
+ }
+
+ $requestWithRoute = $request->withAttribute('route', $route);
- if (!$route) return $this->notFound($response);
-
- $request->withAttribute('route', $route);
$factory = $this->getFactory();
$runner = $factory($route);
- return $runner($request, $response, $next);
+ return $runner($requestWithRoute, $response, $next);
}
/**
* Return 'Not Found' response
*
+ * @param ServerRequestInterface $request
* @param ResponseInterface $response
* @return ResponseInterface
*/
- protected function notFound(ResponseInterface $response)
+ protected function notFound(ServerRequestInterface $request, ResponseInterface $response)
{
- $message = 'Not Found';
-
- $body = $response->getBody();
- $body->rewind();
- $body->write($message);
+ $notFound = $response->withStatus(404);
+ $notFound->getBody()->write('Not Found');
- return $response->withStatus(404, $message)->withBody($body);
+ return $notFound;
}
}
-
diff --git a/src/Router/Middleware/BasePath.php b/src/Router/Middleware/BasePath.php
index a80d029..f356aed 100644
--- a/src/Router/Middleware/BasePath.php
+++ b/src/Router/Middleware/BasePath.php
@@ -57,13 +57,15 @@ class BasePath
$uri = $request->getUri();
$path = $this->normalizePath($uri->getPath());
- if (!$this->hasBasePath($path)) return $this->setError($response);
+ if (!$this->hasBasePath($path)) {
+ return $this->notFound($request, $response);
+ }
$noBase = $this->getBaselessPath($path);
$noBaseUri = $uri->withPath($noBase);
- $request = $request->withUri($noBaseUri)->withAttribute('original_uri', $uri);
+ $rewrittenRequest = $request->withUri($noBaseUri)->withAttribute('original_uri', $uri);
- return call_user_func($next, $request, $response);
+ return $next($rewrittenRequest, $response);
}
/**
@@ -100,19 +102,17 @@ class BasePath
}
/**
- * Set error response
+ * Respond with 404 Not Found
*
- * @param ResponseInterface $response
+ * @param ServerRequestInterface $request
+ * @param ResponseInterface $response
* @return ResponseInterface
*/
- protected function setError($response)
+ protected function notFound(ServerRequestInterface $request, ResponseInterface $response)
{
- $message = 'Not Found';
-
- $body = $response->getBody();
- $body->rewind();
- $body->write($message);
+ $notFound = $response->withStatus(404);
+ $notFound->getBody()->write('Not Found');
- return $response->withStatus(404, $message)->withBody($body);
+ return $notFound;
}
}
diff --git a/src/Router/Middleware/ErrorHandler.php b/src/Router/Middleware/ErrorHandler.php
deleted file mode 100644
index 789c455..0000000
--- a/src/Router/Middleware/ErrorHandler.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?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/NotFound.php b/src/Router/Middleware/NotFound.php
index 97b4a51..2484a43 100644
--- a/src/Router/Middleware/NotFound.php
+++ b/src/Router/Middleware/NotFound.php
@@ -29,6 +29,7 @@ class NotFound
**/
protected $methodNotAllowed = null;
+
/**
* Class constructor
*
@@ -38,12 +39,22 @@ class NotFound
*/
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 (is_string($notFound) && ctype_digit($notFound)) {
+ $notFound = (int)$notFound;
+ }
+ if (!(is_int($notFound) && $notFound >= 100 && $notFound <= 999) && !is_callable($notFound)) {
+ throw new \InvalidArgumentException("'notFound' should be valid HTTP status code 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");
+ if (is_string($methodNotAllowed) && ctype_digit($methodNotAllowed)) {
+ $methodNotAllowed = (int)$methodNotAllowed;
+ }
+ if (
+ isset($methodNotAllowed) &&
+ !(is_int($methodNotAllowed) && $methodNotAllowed >= 100 && $methodNotAllowed <= 999) &&
+ !is_callable($methodNotAllowed)
+ ) {
+ throw new \InvalidArgumentException("'methodNotAllowed' should be valid HTTP status code or a callback");
}
$this->routes = $routes;
@@ -60,6 +71,7 @@ class NotFound
{
return $this->routes;
}
+
/**
* Run middleware action
@@ -69,37 +81,34 @@ class NotFound
* @param callback $next
* @return ResponseInterface
*/
- public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next = null)
+ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
{
- if ($next && !is_callable($next)) {
+ if (!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;
+ if (!$this->getRoutes()->hasRoute($request)) {
+ $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);
+ return is_numeric($status) ? $this->simpleResponse($response, $status) : $status($request, $response);
+ }
+
+ return $next($request, $response);
}
/**
* Simple response
*
* @param ResponseInterface $response
- * @param int $code
+ * @param int $code
* @return ResponseInterface
*/
protected function simpleResponse(ResponseInterface $response, $code)
{
- $message = 'Not Found';
-
- $body = $response->getBody();
- $body->rewind();
- $body->write($message);
+ $notFound = $response->withStatus($code);
+ $notFound->getBody()->write('Not found');
- return $response->withStatus($code, $message)->withBody($body);
+ return $notFound;
}
}
diff --git a/src/Router/Route.php b/src/Router/Route.php
index be5a052..d2158ea 100644
--- a/src/Router/Route.php
+++ b/src/Router/Route.php
@@ -10,27 +10,21 @@ class Route extends \stdClass
/**
* Class constructor
*
- * @param array $values
+ * @param array|stdClass $values
*/
- public function __construct(array $values)
- {
- foreach ($values as $key => $value) {
- $this->$key = $value;
- }
- }
-
- /**
- * Factory method
- *
- * @param array|\stdClass $values
- * @return Route
- */
- public static function create($values)
+ public function __construct($values)
{
if ($values instanceof \stdClass) {
$values = get_object_vars($values);
}
- return new static($values);
+ if (!is_array($values)) {
+ $type = (is_object($values) ? get_class($values) . ' ' : '') . gettype($values);
+ throw new \InvalidArgumentException("Route values should be an array, not a $type");
+ }
+
+ foreach ($values as $key => $value) {
+ $this->$key = $value;
+ }
}
}
diff --git a/src/Router/Routes/Glob.php b/src/Router/Routes/Glob.php
index fcece76..a4495ee 100644
--- a/src/Router/Routes/Glob.php
+++ b/src/Router/Routes/Glob.php
@@ -6,6 +6,7 @@ use ArrayObject;
use Jasny\Router\UrlParsing;
use Jasny\Router\Routes;
use Jasny\Router\Route;
+use Jasny\Router\Routes\RouteBinding;
use Psr\Http\Message\ServerRequestInterface;
/**
@@ -14,6 +15,19 @@ use Psr\Http\Message\ServerRequestInterface;
class Glob extends ArrayObject implements Routes
{
use UrlParsing;
+ use RouteBinding;
+
+ /**
+ * Class constructor
+ *
+ * @param Routes[]|array|\Traversable $input
+ * @param int $flags
+ */
+ public function __construct($input = [], $flags = 0)
+ {
+ $routes = $this->createRoutes($input);
+ parent::__construct($routes, $flags);
+ }
/**
* Create a route from an assisiative array or stdClass object
@@ -35,14 +49,7 @@ class Glob extends ArrayObject implements Routes
throw new \InvalidArgumentException("Unable to create a Route from value " . var_export($value, true));
}
- $route = Route::create($value);
-
- if (!isset($route)) {
- throw new \InvalidArgumentException("Unable to create a Route from " . var_export($value, true) . ": "
- . "neither 'controller', 'fn' or 'file' key is defined");
- }
-
- return $route;
+ return new Route($value);
}
/**
@@ -157,200 +164,6 @@ class Glob extends ArrayObject implements Routes
/**
- * Fill out the routes variables based on the url parts.
- *
- * @param array|\stdClass $vars Route variables
- * @param ServerRequestInterface $request
- * @param array $parts URL parts
- * @return array
- */
- protected function bind($vars, ServerRequestInterface $request, array $parts)
- {
- $values = [];
- $type = is_array($vars) && array_keys($vars) === array_keys(array_keys($vars)) ? 'numeric' : 'assoc';
-
- foreach ($vars as $key => $var) {
- if (!isset($var)) continue;
-
- if (is_object($var) && !$var instanceof \stdClass) {
- $part = array($var);
- } elseif (!is_scalar($var)) {
- $part = array($this->bind($var, $request, $parts));
- } elseif ($var[0] === '$') {
- $options = array_map('trim', explode('|', $var));
- $part = $this->bindVar($type, $request, $parts, $options);
- } elseif ($var[0] === '~' && substr($var, -1) === '~') {
- $pieces = array_map('trim', explode('~', substr($var, 1, -1)));
- $bound = array_filter($this->bind($pieces, $request, $parts));
- $part = array(join('', $bound));
- } else {
- $part = array($var);
- }
-
- if ($type === 'assoc') {
- $values[$key] = $part[0];
- } else {
- $values = array_merge($values, $part);
- }
- }
-
- if ($vars instanceof Route) {
- $values = Route::create($values);
- } elseif (is_object($vars) && $type === 'assoc') {
- $values = (object)$values;
- }
-
- return $values;
- }
-
- /**
- * Bind variable
- *
- * @param string $type 'assoc' or 'numeric'
- * @param ServerRequestInterface $request
- * @param array $parts
- * @param array $options
- * @return array
- */
- protected function bindVar($type, ServerRequestInterface $request, array $parts, array $options)
- {
- foreach ($options as $option) {
- $value = null;
-
- $bound =
- $this->bindVarString($option, $value) ||
- $this->bindVarSuperGlobal($option, $request, $value) ||
- $this->bindVarRequestHeader($option, $request, $value) ||
- $this->bindVarMultipleUrlParts($option, $type, $parts, $value) ||
- $this->bindVarSingleUrlPart($option, $parts, $value);
-
- if ($bound && isset($value)) {
- return $value;
- }
- }
-
- return [null];
- }
-
- /**
- * Bind variable when option is a normal string
- *
- * @param string $option
- * @param mixed $value OUTPUT
- * @return boolean
- */
- protected function bindVarString($option, &$value)
- {
- if ($option[0] !== '$') {
- $value = [$option];
- return true;
- }
-
- return false;
- }
-
- /**
- * Bind variable when option is a super global
- *
- * @param string $option
- * @param mixed $value OUTPUT
- * @return boolean
- */
- protected function bindVarSuperGlobal($option, ServerRequestInterface $request, &$value)
- {
- if (preg_match('/^\$_(GET|POST|COOKIE)\[([^\[]*)\]$/i', $option, $matches)) {
- list(, $var, $key) = $matches;
-
- $var = strtolower($var);
- $data = null;
-
- if ($var === 'get') {
- $data = $request->getQueryParams();
- } elseif ($var === 'post') {
- $data = $request->getParsedBody();
- } elseif ($var === 'cookie') {
- $data = $request->getCookieParams();
- }
-
- $value = isset($data[$key]) ? [$data[$key]] : null;
- return true;
- }
-
- return false;
- }
-
- /**
- * Bind variable when option is a request header
- *
- * @param string $option
- * @param ServerRequestInterface $request
- * @param mixed $value OUTPUT
- * @return boolean
- */
- protected function bindVarRequestHeader($option, ServerRequestInterface $request, &$value)
- {
- if (preg_match('/^\$(?:HTTP_)?([A-Z_]+)$/', $option, $matches)) {
- $sentence = preg_replace('/[\W_]+/', ' ', $matches[1]);
- $name = str_replace(' ', '-', ucwords($sentence));
-
- $value = [$request->getHeaderLine($name)];
- return true;
- }
-
- return false;
- }
-
- /**
- * Bind variable when option contains multiple URL parts
- *
- * @param string $option
- * @param string $type 'assoc' or 'numeric'
- * @param array $parts Url parts
- * @param mixed $value OUTPUT
- * @return boolean
- */
- protected function bindVarMultipleUrlParts($option, $type, array $parts, &$value)
- {
- if (substr($option, -3) === '...' && ctype_digit(substr($option, 1, -3))) {
- $i = (int)substr($option, 1, -3);
-
- if ($type === 'assoc') {
- throw new \InvalidArgumentException("Binding multiple parts using '$option' is only allowed in numeric arrays");
- } else {
- $value = array_slice($parts, $i - 1);
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Bind variable when option contains a single URL part
- *
- * @param string $option
- * @param array $parts Url parts
- * @param mixed $value OUTPUT
- * @return boolean
- */
- protected function bindVarSingleUrlPart($option, array $parts, &$value)
- {
- if (ctype_digit(substr($option, 1))) {
- $i = (int)substr($option, 1);
- $part = array_slice($parts, $i - 1, 1);
-
- if (!empty($part)) {
- $value = $part;
- return true;
- }
- }
-
- return false;
- }
-
-
- /**
* Check if a route for the URL exists
*
* @param ServerRequestInterface $request
diff --git a/src/Router/Routes/RouteBinding.php b/src/Router/Routes/RouteBinding.php
new file mode 100644
index 0000000..91563c3
--- /dev/null
+++ b/src/Router/Routes/RouteBinding.php
@@ -0,0 +1,314 @@
+<?php
+
+namespace Jasny\Router\Routes;
+
+use Jasny\Router\Route;
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * Functionality to set route properties based on url parameters
+ */
+trait RouteBinding
+{
+ /**
+ * Fill out the routes variables based on the url parts.
+ *
+ * @param array|\stdClass $vars Route variables
+ * @param ServerRequestInterface $request
+ * @param array $parts URL parts
+ * @return array
+ */
+ protected function bind($vars, ServerRequestInterface $request, array $parts)
+ {
+ $type = is_array($vars) && array_keys($vars) === array_keys(array_keys($vars)) ? 'numeric' : 'assoc';
+
+ $values = $this->bindParts($vars, $type, $request, $parts);
+
+ if ($vars instanceof Route) {
+ $class = get_class($vars);
+ $values = new $class($values);
+ } elseif (is_object($vars) && $type === 'assoc') {
+ $values = (object)$values;
+ }
+
+ return $values;
+ }
+
+
+ /**
+ * Fill out the values based on the url parts.
+ *
+ * @param array|\stdClass $vars Route variables
+ * @param string $type
+ * @param ServerRequestInterface $request
+ * @param array $parts URL parts
+ * @return array
+ */
+ protected function bindParts($vars, $type, ServerRequestInterface $request, array $parts)
+ {
+ $values = [];
+
+ foreach ($vars as $key => $var) {
+ $part = null;
+
+ $bound =
+ $this->bindPartObject($var, $part) ||
+ $this->bindPartArray($var, $request, $parts, $part) ||
+ $this->bindPartVar($var, $type, $request, $parts, $part) ||
+ $this->bindPartConcat($var, $request, $parts, $part) ||
+ $this->bindPartValue($var, $part);
+
+ if (!$bound) continue;
+
+ if ($type === 'assoc') {
+ $values[$key] = $part[0];
+ } else {
+ $values = array_merge($values, $part);
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Bind part if it's an object
+ *
+ * @param mixed $var
+ * @param array $part OUTPUT
+ * @return boolean
+ */
+ protected function bindPartObject($var, &$part)
+ {
+ if (!is_object($var) || $var instanceof \stdClass) {
+ return false;
+ }
+
+ $part = [$var];
+ return true;
+ }
+
+ /**
+ * Bind part if it's an array
+ *
+ * @param mixed $var
+ * @param ServerRequestInterface $request
+ * @param array $parts
+ * @param array $part OUTPUT
+ * @return boolean
+ */
+ protected function bindPartArray($var, ServerRequestInterface $request, array $parts, &$part)
+ {
+ if (!is_array($var) && !$var instanceof \stdClass) {
+ return false;
+ }
+
+ $part = [$this->bind($var, $request, $parts)];
+ return true;
+ }
+
+ /**
+ * Bind part if it's an variable
+ *
+ * @param mixed $var
+ * @param string $type
+ * @param ServerRequestInterface $request
+ * @param array $parts
+ * @param array $part OUTPUT
+ * @return boolean
+ */
+ protected function bindPartVar($var, $type, ServerRequestInterface $request, array $parts, &$part)
+ {
+ if (!is_string($var) || $var[0] !== '$') {
+ return false;
+ }
+
+ $options = array_map('trim', explode('|', $var));
+ $part = $this->bindVar($type, $request, $parts, $options);
+ return true;
+ }
+
+ /**
+ * Bind part if it's an concatenation
+ *
+ * @param mixed $var
+ * @param ServerRequestInterface $request
+ * @param array $parts
+ * @param array $part OUTPUT
+ * @return boolean
+ */
+ protected function bindPartConcat($var, ServerRequestInterface $request, array $parts, &$part)
+ {
+ if (!is_string($var) || $var[0] !== '~' || substr($var, -1) !== '~') {
+ return false;
+ }
+
+ $pieces = array_map('trim', explode('~', substr($var, 1, -1)));
+ $bound = array_filter($this->bind($pieces, $request, $parts));
+ $part = [join('', $bound)];
+
+ return true;
+ }
+
+ /**
+ * Bind part if it's a normal value
+ *
+ * @param mixed $var
+ * @param array $part OUTPUT
+ * @return boolean
+ */
+ protected function bindPartValue($var, &$part)
+ {
+ if (!isset($var)) {
+ return false;
+ }
+
+ $part = [$var];
+ return true;
+ }
+
+ /**
+ * Bind variable
+ *
+ * @param string $type 'assoc' or 'numeric'
+ * @param ServerRequestInterface $request
+ * @param array $parts
+ * @param array $options
+ * @return array
+ */
+ protected function bindVar($type, ServerRequestInterface $request, array $parts, array $options)
+ {
+ foreach ($options as $option) {
+ $value = null;
+
+ $bound =
+ $this->bindVarString($option, $value) ||
+ $this->bindVarSuperGlobal($option, $request, $value) ||
+ $this->bindVarRequestHeader($option, $request, $value) ||
+ $this->bindVarMultipleUrlParts($option, $type, $parts, $value) ||
+ $this->bindVarSingleUrlPart($option, $parts, $value);
+
+ if ($bound && isset($value)) {
+ return $value;
+ }
+ }
+
+ return [null];
+ }
+
+ /**
+ * Bind variable when option is a normal string
+ *
+ * @param string $option
+ * @param mixed $value OUTPUT
+ * @return boolean
+ */
+ protected function bindVarString($option, &$value)
+ {
+ if ($option[0] !== '$') {
+ $value = [$option];
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Bind variable when option is a super global
+ *
+ * @param string $option
+ * @param mixed $value OUTPUT
+ * @return boolean
+ */
+ protected function bindVarSuperGlobal($option, ServerRequestInterface $request, &$value)
+ {
+ if (preg_match('/^\$_(GET|POST|COOKIE)\[([^\[]*)\]$/i', $option, $matches)) {
+ list(, $var, $key) = $matches;
+
+ $var = strtolower($var);
+ $data = null;
+
+ if ($var === 'get') {
+ $data = $request->getQueryParams();
+ } elseif ($var === 'post') {
+ $data = $request->getParsedBody();
+ } elseif ($var === 'cookie') {
+ $data = $request->getCookieParams();
+ }
+
+ $value = isset($data[$key]) ? [$data[$key]] : null;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Bind variable when option is a request header
+ *
+ * @param string $option
+ * @param ServerRequestInterface $request
+ * @param mixed $value OUTPUT
+ * @return boolean
+ */
+ protected function bindVarRequestHeader($option, ServerRequestInterface $request, &$value)
+ {
+ if (preg_match('/^\$(?:HTTP_)?([A-Z_]+)$/', $option, $matches)) {
+ $sentence = preg_replace('/[\W_]+/', ' ', $matches[1]);
+ $name = str_replace(' ', '-', ucwords($sentence));
+
+ $value = [$request->getHeaderLine($name)];
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Bind variable when option contains multiple URL parts
+ *
+ * @param string $option
+ * @param string $type 'assoc' or 'numeric'
+ * @param array $parts Url parts
+ * @param mixed $value OUTPUT
+ * @return boolean
+ */
+ protected function bindVarMultipleUrlParts($option, $type, array $parts, &$value)
+ {
+ if (substr($option, -3) === '...' && ctype_digit(substr($option, 1, -3))) {
+ $i = (int)substr($option, 1, -3);
+
+ if ($type === 'assoc') {
+ throw new \InvalidArgumentException("Binding multiple parts using '$option' is only allowed in numeric arrays");
+ } else {
+ $value = array_slice($parts, $i - 1);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Bind variable when option contains a single URL part
+ *
+ * @param string $option
+ * @param array $parts Url parts
+ * @param mixed $value OUTPUT
+ * @return boolean
+ */
+ protected function bindVarSingleUrlPart($option, array $parts, &$value)
+ {
+ if (ctype_digit(substr($option, 1))) {
+ $i = (int)substr($option, 1);
+ $part = array_slice($parts, $i - 1, 1);
+
+ if (!empty($part)) {
+ $value = $part;
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/Router/Runner.php b/src/Router/Runner.php
index 87f082e..c671fa9 100644
--- a/src/Router/Runner.php
+++ b/src/Router/Runner.php
@@ -4,7 +4,6 @@ namespace Jasny\Router;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
-use Jasny\Router\Route;
/**
* A runner can be invoked in order to run the action specified in a route
@@ -23,20 +22,19 @@ abstract class Runner
/**
* Invoke the action specified in the route and call the next method
*
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @param callback $next Callback for if runner is used as middleware
+ * @param ServerRequestInterface $request
+ * @param ResponseInterface $response
+ * @param callback $next Callback for if runner is used as middleware
* @return ResponseInterface
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next = null)
{
- $response = $this->run($request, $response);
+ $newResponse = $this->run($request, $response);
if (isset($next)) {
- $response = call_user_func($next, $request, $response);
+ $newResponse = call_user_func($next, $request, $newResponse);
}
- return $response;
+ return $newResponse;
}
}
-
diff --git a/src/Router/Runner/Callback.php b/src/Router/Runner/Callback.php
index 7f4c457..8fa8c0e 100644
--- a/src/Router/Runner/Callback.php
+++ b/src/Router/Runner/Callback.php
@@ -29,6 +29,6 @@ class Callback extends Runner
throw new \RuntimeException("'fn' property of route shoud be a callable");
}
- return call_user_func($callback, $request, $response);
+ return $callback($request, $response);
}
}
diff --git a/src/Router/Runner/Controller.php b/src/Router/Runner/Controller.php
index 56cf2b5..caca48f 100644
--- a/src/Router/Runner/Controller.php
+++ b/src/Router/Runner/Controller.php
@@ -7,13 +7,34 @@ use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
- * Description of Controller
- *
- * @author arnold
+ * Run a route using a controller
*/
class Controller extends Runner
{
/**
+ * Get class name from controller name
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function getClass($name)
+ {
+ return strstr($name, '-') ? \Jasny\studlycase($name) : $name;
+ }
+
+ /**
+ * Instantiate a controller object
+ * @codeCoverageIgnore
+ *
+ * @param string $class
+ * @return callable|object
+ */
+ protected function instantiate($class)
+ {
+ return new $class();
+ }
+
+ /**
* Route to a controller
*
* @param ServerRequestInterface $request
@@ -23,18 +44,20 @@ class Controller extends Runner
public function run(ServerRequestInterface $request, ResponseInterface $response)
{
$route = $request->getAttribute('route');
- $class = !empty($route->controller) ? $route->controller : null;
+ $name = !empty($route->controller) ? $route->controller : null;
+ $class = $this->getClass($name);
+
if (!class_exists($class)) {
throw new \RuntimeException("Can not route to controller '$class': class not exists");
}
-
+
if (!method_exists($class, '__invoke')) {
throw new \RuntimeException("Can not route to controller '$class': class does not have '__invoke' method");
}
-
- $controller = new $class($route);
-
+
+ $controller = $this->instantiate($class);
+
return $controller($request, $response);
}
}
diff --git a/src/Router/Runner/PhpScript.php b/src/Router/Runner/PhpScript.php
index d223bd7..51480c5 100644
--- a/src/Router/Runner/PhpScript.php
+++ b/src/Router/Runner/PhpScript.php
@@ -10,7 +10,18 @@ use Psr\Http\Message\ResponseInterface;
* Route to a PHP script
*/
class PhpScript extends Runner
-{
+{
+ /**
+ * Include a file
+ * @param type $file
+ * @param ServerRequestInterface $request
+ * @param ResponseInterface $response
+ */
+ protected function includeScript($file, ServerRequestInterface $request, ResponseInterface $response)
+ {
+ return include $file;
+ }
+
/**
* Route to a file
*
@@ -23,16 +34,16 @@ class PhpScript extends Runner
$route = $request->getAttribute('route');
$file = !empty($route->file) ? ltrim($route->file, '/') : '';
- if (!file_exists($file)) {
- throw new \RuntimeException("Failed to route using '$file': File '$file' doesn't exist.");
- }
-
if ($file[0] === '~' || strpos($file, '..') !== false) {
- throw new \RuntimeException("Won't route using '$file': '~', '..' are not allowed in filename.");
+ throw new \RuntimeException("Won't route to '$file': '~', '..' are not allowed in filename");
}
- $result = include $file;
+ if (!file_exists($file)) {
+ throw new \RuntimeException("Failed to route using '$file': File doesn't exist");
+ }
+
+ $result = $this->includeScript($file, $request, $response);
- return $result === true ? $response : $result;
+ return $result === true || $result === 1 ? $response : $result;
}
}
diff --git a/src/Router/Runner/RunnerFactory.php b/src/Router/RunnerFactory.php
index e32d2cc..08ed7b0 100644
--- a/src/Router/Runner/RunnerFactory.php
+++ b/src/Router/RunnerFactory.php
@@ -1,8 +1,9 @@
<?php
-namespace Jasny\Router\Runner;
+namespace Jasny\Router;
use Jasny\Router\Route;
+use Jasny\Router\Runner;
/**
* Factory of Runner instances
@@ -18,11 +19,11 @@ class RunnerFactory
public function __invoke(Route $route)
{
if (isset($route->controller)) {
- $class = Controller::class;
+ $class = Runner\Controller::class;
} elseif (isset($route->fn)) {
- $class = Callback::class;
+ $class = Runner\Callback::class;
} elseif (isset($route->file)) {
- $class = PhpScript::class;
+ $class = Runner\PhpScript::class;
} else {
throw new \InvalidArgumentException("Route has neither 'controller', 'fn' or 'file' defined");
}
@@ -30,4 +31,3 @@ class RunnerFactory
return new $class();
}
}
-
diff --git a/src/Router/UrlParsing.php b/src/Router/UrlParsing.php
index 5727085..ee89260 100644
--- a/src/Router/UrlParsing.php
+++ b/src/Router/UrlParsing.php
@@ -31,10 +31,6 @@ trait UrlParsing
$url = rtrim($url, '/');
}
- if (substr($url, 0, 2) == '/:') {
- $url = substr($url, 2);
- }
-
return $url;
}
}
diff --git a/tests/Router/Middleware/BasePathTest.php b/tests/Router/Middleware/BasePathTest.php
index eb9a2e8..d615d70 100644
--- a/tests/Router/Middleware/BasePathTest.php
+++ b/tests/Router/Middleware/BasePathTest.php
@@ -1,25 +1,22 @@
<?php
+namespace Jasny\Router\Middleware;
+
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);
- }
+use Jasny\Router\TestHelpers;
+/**
+ * @covers Jasny\Router\Middleware\BasePath
+ */
+class BasePathTest extends \PHPUnit_Framework_TestCase
+{
+ use TestHelpers;
+
/**
* Provide data for testing invalid BasePath creation
*
@@ -39,17 +36,14 @@ class BasePathTest extends PHPUnit_Framework_TestCase
}
/**
- * Test creating BasePath instance
+ * Test creating middleware with invalid parameter
*
- * @dataProvider validConstructProvider
- * @param string $basePath
+ * @dataProvider invalidConstructProvider
+ * @expectedException InvalidArgumentException
*/
- public function testValidConstruct($basePath, $validBasePath)
+ public function testInvalidConstruct($basePath)
{
- $pathHandler = new BasePath($basePath);
-
- $this->assertNotEmpty($pathHandler->getBasePath(), "Empty base path");
- $this->assertEquals($validBasePath, $pathHandler->getBasePath(), "Base path was not set correctly");
+ new BasePath($basePath);
}
/**
@@ -71,41 +65,32 @@ class BasePathTest extends PHPUnit_Framework_TestCase
}
/**
- * Test invoke with invalid 'next' param
+ * Test creating BasePath instance
+ *
+ * @dataProvider validConstructProvider
+ * @param string $basePath
*/
- public function testInvokeInvalidNext()
+ public function testValidConstruct($basePath, $validBasePath)
{
- $middleware = new BasePath('/foo');
- list($request, $response) = $this->getRequests();
-
- $this->expectException(\InvalidArgumentException::class);
+ $pathHandler = new BasePath($basePath);
- $result = $middleware($request, $response, 'not_callable');
+ $this->assertNotEmpty($pathHandler->getBasePath(), "Empty base path");
+ $this->assertEquals($validBasePath, $pathHandler->getBasePath(), "Base path was not set correctly");
}
/**
- * Test case when given request path does not starts with given base path
- *
- * @dataProvider notFoundProvider
- * @param string $basePath
- * @param string $path
+ * Test invoke with invalid 'next' param
+ *
+ * @expectedException InvalidArgumentException
*/
- public function testNotFound($basePath, $path)
+ public function testInvokeInvalidNext()
{
- $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;
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
- return $response;
- });
+ $middleware = new BasePath('/foo');
- $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object");
- $this->assertFalse(isset($response->nextCalled), "'next' was called");
+ $middleware($request, $response, 'not_callable');
}
/**
@@ -128,28 +113,27 @@ class BasePathTest extends PHPUnit_Framework_TestCase
}
/**
- * Test correct case, when path contains base path
- *
- * @dataProvider foundProvider
+ * Test case when given request path does not starts with given base path
+ * @dataProvider notFoundProvider
+ *
* @param string $basePath
* @param string $path
- * @param string $noBasePath
*/
- public function testFound($basePath, $path, $noBasePath)
+ public function testNotFound($basePath, $path)
{
- $middleware = new BasePath($basePath);
- list($request, $response) = $this->getRequests();
-
- $this->expectRequestSetBasePath($request, $basePath, $path, $noBasePath);
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
- $result = $middleware($request, $response, function($request, $response) {
- $response->nextCalled = true;
+ $middleware = new BasePath($basePath);
+
+ $this->expectRequestGetPath($request, $path);
+ $finalResponse = $this->expectNotFound($response);
- return $response;
- });
+ $next = $this->createCallbackMock($this->never());
+
+ $result = $middleware($request, $response, $next);
- $this->assertEquals(get_class($response), get_class($result), "Middleware should return response object");
- $this->assertTrue($response->nextCalled, "'next' was not called");
+ $this->assertSame($finalResponse, $result);
}
/**
@@ -172,19 +156,31 @@ class BasePathTest extends PHPUnit_Framework_TestCase
}
/**
- * Get requests for testing
- *
- * @param string $path
- * @return array
+ * Test correct case, when path contains base path
+ * @dataProvider foundProvider
+ *
+ * @param string $basePath
+ * @param string $path
+ * @param string $noBasePath
*/
- public function getRequests($path = null)
+ public function testFound($basePath, $path, $noBasePath)
{
$request = $this->createMock(ServerRequestInterface::class);
$response = $this->createMock(ResponseInterface::class);
+ $finalRespose = $this->createMock(ResponseInterface::class);
+
+ $middleware = new BasePath($basePath);
+
+ $this->expectRequestSetBasePath($request, $basePath, $path, $noBasePath);
- return [$request, $response];
+ $next = $this->createCallbackMock($this->once(), [$request, $response], $finalRespose);
+
+ $result = $middleware($request, $response, $next);
+
+ $this->assertSame($finalRespose, $result);
}
+
/**
* Expect that request will return a path
*
@@ -221,15 +217,19 @@ class BasePathTest extends PHPUnit_Framework_TestCase
* Expect for not found error
*
* @param ResponseInterface $response
+ * @return ResponseInterface
*/
public function expectNotFound(ResponseInterface $response)
{
+ $finalResponse = $this->createMock(ResponseInterface::class);
+
+ $response->expects($this->once())->method('withStatus')->with(404)->willReturn($finalResponse);
+
$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());
+ $finalResponse->expects($this->once())->method('getBody')->willReturn($stream);
+
+ return $finalResponse;
}
}
diff --git a/tests/Router/Middleware/ErrorHandlerTest.php b/tests/Router/Middleware/ErrorHandlerTest.php
deleted file mode 100644
index c4b6313..0000000
--- a/tests/Router/Middleware/ErrorHandlerTest.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?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/NotFoundTest.php b/tests/Router/Middleware/NotFoundTest.php
index 0c40a68..b8cd657 100644
--- a/tests/Router/Middleware/NotFoundTest.php
+++ b/tests/Router/Middleware/NotFoundTest.php
@@ -5,261 +5,222 @@ use Jasny\Router\Middleware\NotFound;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+/**
+ * @covers Jasny\Router\Middleware\NotFound
+ */
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()
+ public function invalidStatusProvider()
{
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]
+ [0],
+ [true],
+ ['foo bar zoo'],
+ [1000],
+ [['abc']]
];
}
/**
- * Test invoke with invalid 'next' param
+ * @dataProvider invalidStatusProvider
+ * @expectedException InvalidArgumentException
+ *
+ * @param string $status
*/
- 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)
+ public function testConstructInvalidNotFound($status)
{
- 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");
+ new NotFound($this->createMock(Routes::class), $status);
}
/**
- * 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
+ * @expectedException InvalidArgumentException
*/
- public function testInvokeNotAllowed($notFound, $notAllowed, $next)
+ public function testConstructNotFoundNotNull()
{
- 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");
- }
+ new NotFound($this->createMock(Routes::class), null);
}
/**
- * Test __invoke method in case of route not found at all
- *
- * @dataProvider invokeProvider
- * @param callback|int $notFound
- * @param callback|int $notAllowed
- * @param callback $next
+ * @dataProvider invalidStatusProvider
+ * @expectedException InvalidArgumentException
+ *
+ * @param string $status
*/
- public function testInvokeNotFound($notFound, $notAllowed, $next)
+ public function testConstructInvalidMethodNotAllowed($status)
{
- 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");
- }
+ new NotFound($this->createMock(Routes::class), 404, $status);
}
/**
- * Set expectations on finding route
- *
- * @param Routes $routes
- * @param ServerRequestInterface $request
- * @param string $case
+ * @expectedException InvalidArgumentException
*/
- public function expectRoute($routes, $request, $case)
- {
- if ($case === 'found' || $case === 'notFoundOnce') {
- $found = $case === 'found';
+ public function testInvokeInvalidNext()
+ {
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ $middleware = new NotFound($this->createMock(Routes::class));
- $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;
- }));
- }
+ $middleware($request, $response, 'foo bar zoo');
}
+
/**
* 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;
- };
- }
-
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ $mockCallback = function() {
+ return $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock();
+ };
+
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]
+ [$request, $response, 404, 405, $mockCallback()],
+ [$request, $response, 404, null, $mockCallback()],
+ [$request, $response, '200', '402', $mockCallback()],
+ [$request, $response, $mockCallback(), $mockCallback(), $mockCallback()],
+ [$request, $response, $mockCallback(), null, $mockCallback()]
];
}
/**
- * Expect that response is set to simple deny answer
- *
- * @param ResponseInterface $response
- * @param int $statusCode
+ * Test that 'next' callback is invoked when route is found
+ * @dataProvider invokeProvider
+ *
+ * @param ServerRequestInterface|MockObject $request
+ * @param ResponseInterface|MockObject $response
+ * @param callback|MockObject|int $notFound
+ * @param callback|MockObject|int $methodNotAllowed
+ * @param callback|MockObject $next
*/
- public function expectSimpleError(ResponseInterface $response, $statusCode)
+ public function testInvokeFound($request, $response, $notFound, $methodNotAllowed, $next)
{
- $stream = $this->createMock(StreamInterface::class);
- $stream->expects($this->once())->method('rewind');
- $stream->expects($this->once())->method('write')->with($this->equalTo('Not Found'));
+ $finalResponse = $this->createMock(ResponseInterface::class);
+
+ if ($notFound instanceof MockObject) {
+ $notFound->expects($this->never())->method('__invoke');
+ }
+
+ if ($methodNotAllowed instanceof MockObject) {
+ $methodNotAllowed->expects($this->never())->method('__invoke');
+ }
+
+ $next->expects($this->once())->method('__invoke')->with($request, $response)->willReturn($finalResponse);
+
+ $response->expects($this->never())->method('withStatus');
+
+ $routes = $this->createMock(Routes::class);
+ $routes->expects($this->once())->method('hasRoute')->with($request)->willReturn(true);
+
+ $middleware = new NotFound($routes, $notFound, $methodNotAllowed);
+
+ $result = $middleware($request, $response, $next);
- $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());
+ $this->assertSame($finalResponse, $result);
}
/**
- * Expect that there would be no simple error response
- *
- * @param ResponseInterface $response
+ * Test __invoke method in case of route is found with another method
+ * @dataProvider invokeProvider
+ *
+ * @param ServerRequestInterface|MockObject $request
+ * @param ResponseInterface|MockObject $response
+ * @param callback|MockObject|int $notFound
+ * @param callback|MockObject|int $methodNotAllowed
+ * @param callback|MockObject $next
*/
- public function notExpectSimpleError(ResponseInterface $response)
+ public function testInvokeNotFound($request, $response, $notFound, $methodNotAllowed, $next)
{
+ $finalResponse = $this->createMock(ResponseInterface::class);
$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');
- }
+
+ if ($notFound instanceof MockObject) {
+ $notFound->expects($this->once())->method('__invoke')
+ ->with($request, $response)
+ ->willReturn($finalResponse);
+
+ $response->expects($this->never())->method('withStatus');
+ } else {
+ $response->expects($this->once())->method('withStatus')
+ ->with($notFound)
+ ->willReturn($finalResponse);
+
+ $finalResponse->expects($this->once())->method('getBody')->willReturn($stream);
+ $stream->expects($this->once())->method('write')->with('Not found');
+ }
+
+ if ($methodNotAllowed instanceof MockObject) {
+ $methodNotAllowed->expects($this->never())->method('__invoke');
+ }
+
+ $next->expects($this->never())->method('__invoke');
+
+ $routes = $this->createMock(Routes::class);
+
+ $routes->expects($this->exactly(isset($methodNotAllowed) ? 2 : 1))->method('hasRoute')
+ ->withConsecutive([$request], [$request, false])
+ ->willReturn(false);
+
+ $middleware = new NotFound($routes, $notFound, $methodNotAllowed);
- /**
- * Get requests for testing
- *
- * @return array
- */
- public function getRequests()
- {
- $request = $this->createMock(ServerRequestInterface::class);
- $response = $this->createMock(ResponseInterface::class);
+ $result = $middleware($request, $response, $next);
- return [$request, $response];
+ $this->assertSame($finalResponse, $result);
}
/**
- * Get routes array
- *
- * @return Routes
+ * Test __invoke method in case of route is found with another method
+ * @dataProvider invokeProvider
+ *
+ * @param ServerRequestInterface|MockObject $request
+ * @param ResponseInterface|MockObject $response
+ * @param callback|MockObject|int $notFound
+ * @param callback|MockObject|int $methodNotAllowed
+ * @param callback|MockObject $next
*/
- public function getRoutes()
+ public function testInvokeMethodNotAllowed($request, $response, $notFound, $methodNotAllowed, $next)
{
- return $this->getMockBuilder(Routes::class)->disableOriginalConstructor()->getMock();
- }
+ $finalResponse = $this->createMock(ResponseInterface::class);
+ $stream = $this->createMock(StreamInterface::class);
+
+ $expect = $methodNotAllowed ?: $notFound;
+
+ if ($expect !== $notFound && $notFound instanceof MockObject) {
+ $notFound->expects($this->never())->method('__invoke');
+ }
+
+ if ($expect instanceof MockObject) {
+ $expect->expects($this->once())->method('__invoke')
+ ->with($request, $response)
+ ->willReturn($finalResponse);
+
+ $response->expects($this->never())->method('withStatus');
+ } else {
+ $response->expects($this->once())->method('withStatus')
+ ->with($expect)
+ ->willReturn($finalResponse);
+
+ $finalResponse->expects($this->once())->method('getBody')->willReturn($stream);
+ $stream->expects($this->once())->method('write')->with('Not found');
+ }
+
+ $next->expects($this->never())->method('__invoke');
+
+ $routes = $this->createMock(Routes::class);
+
+ $routes->expects($this->exactly(isset($methodNotAllowed) ? 2 : 1))->method('hasRoute')
+ ->withConsecutive([$request], [$request, false])
+ ->will($this->onConsecutiveCalls(false, true));
+
+ $middleware = new NotFound($routes, $notFound, $methodNotAllowed);
- /**
- * Skip the test pass
- */
- public function skipTest()
- {
- return $this->assertTrue(true);
+ $result = $middleware($request, $response, $next);
+
+ $this->assertSame($finalResponse, $result);
}
}
diff --git a/tests/Router/RouteTest.php b/tests/Router/RouteTest.php
new file mode 100644
index 0000000..4a72d27
--- /dev/null
+++ b/tests/Router/RouteTest.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Jasny\Router;
+
+use Jasny\Router\Route;
+
+/**
+ * @covers Jasny\Router\Route
+ */
+class RouteTest extends \PHPUnit_Framework_TestCase
+{
+ public function provider()
+ {
+ return [
+ [['foo' => '$1', 'color' => 'red', 'number' => 42]],
+ [(object)['foo' => '$1', 'color' => 'red', 'number' => 42]]
+ ];
+ }
+
+ /**
+ * @dataProvider provider
+ *
+ * @param array|stdClass $values
+ */
+ public function testConstructionWithObject($values)
+ {
+ $route = new Route($values);
+
+ $this->assertAttributeSame('$1', 'foo', $route);
+ $this->assertAttributeSame('red', 'color', $route);
+ $this->assertAttributeSame(42, 'number', $route);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Route values should be an array, not a string
+ */
+ public function testConstructionInvalidArgument()
+ {
+ new Route('foo');
+ }
+}
diff --git a/tests/Router/Routes/GlobTest.php b/tests/Router/Routes/GlobTest.php
index 3ed4d21..5efa77f 100644
--- a/tests/Router/Routes/GlobTest.php
+++ b/tests/Router/Routes/GlobTest.php
@@ -3,13 +3,17 @@
namespace Jasny\Router\Routes;
use Jasny\Router\Routes\Glob;
+use Jasny\Router\Route;
use Psr\Http\Message\ServerRequestInterface;
use ArrayObject;
use BadMethodCallException;
use InvalidArgumentException;
-use AppendIterator;
+/**
+ * @covers Jasny\Router\Routes\Glob
+ * @covers Jasny\Router\UrlParsing
+ */
class GlobTest extends \PHPUnit_Framework_TestCase
{
/**
@@ -17,63 +21,55 @@ class GlobTest extends \PHPUnit_Framework_TestCase
*/
public function testConstructor()
{
- #Test empty constructor
$glob = new Glob();
$this->assertInstanceOf('ArrayObject', $glob, "Should be an instance of 'ArrayObject'");
$this->assertEquals(0, $glob->count(), "Default count is not empty");
$this->assertEquals(0, $glob->getFlags(), "Default flags are not empty");
$this->assertEquals('ArrayIterator', $glob->getIteratorClass(), "Default iterator class is not correct");
- #Actual check for public values
+ // Actual check for available routes
$count = 0;
foreach ($glob as $value) {
$count++;
break;
}
- $this->assertEquals(0, $count);
+ $this->assertEquals(0, $count);
}
public function testConstructorWithArguments()
{
- #Test with params
$values = [
'/foo/bar' => ['controller' => 'value1'],
'/foo/*' => ['fn' => 'value3'],
'/foo/*/bar' => ['file' => 'value5'],
];
- $glob = new Glob($values, ArrayObject::ARRAY_AS_PROPS, AppendIterator::class);
+ $glob = new Glob($values, ArrayObject::ARRAY_AS_PROPS);
- $this->assertEquals(count($values), $glob->count(), "Routes count is not match");
+ $this->assertCount(3, $glob, "Routes count do not match");
$this->assertEquals(ArrayObject::ARRAY_AS_PROPS, $glob->getFlags(), "Flags are not correct");
- $this->assertEquals(AppendIterator::class, $glob->getIteratorClass(), "Iterator class is not correct");
- foreach ($values as $pattern => $options) {
- $this->assertTrue($glob->offsetExists($pattern), "Key '$pattern' is missing");
+ foreach ($glob as $pattern => $route) {
+ $this->assertInstanceof(Route::class, $route);
+ $this->assertArrayHasKey($pattern, $values);
+ $this->assertArraysEqual($values[$pattern], (array)$route);
}
+
+ return $glob;
}
-
+
/**
- * Test ArrayObject::exchangeArray method
+ * @depends testConstructorWithArguments
*
- * @dataProvider exchangeArrayProvider
+ * @param Glob $original
*/
- public function testExchangeArray($set, $reset)
+ public function testConstructorTraversable(Glob $original)
{
- $glob = new Glob($set);
- $old = $glob->exchangeArray($reset);
-
- $this->assertEquals(count($set), count($old), "Old routes count is not match");
- $this->assertEquals(count($reset), $glob->count(), "Routes count is not match");
-
- foreach ($reset as $pattern => $options) {
- $this->assertTrue($glob->offsetExists($pattern), "Key is missing");
- }
- foreach ($set as $pattern => $options) {
- $this->assertTrue(!empty($old[$pattern]), "Old key is missing");
- $this->assertFalse($glob->offsetExists($pattern), "Key exists, but should not");
- }
+ $glob = new Glob($original);
+
+ $this->assertCount(3, $glob, "Routes count do not match");
+ $this->assertEquals($original->getArrayCopy(), $glob->getArrayCopy());
}
/**
@@ -98,70 +94,93 @@ class GlobTest extends \PHPUnit_Framework_TestCase
}
/**
- * Test ArrayObject::offsetSet method
+ * Test ArrayObject::exchangeArray method
*
- * @dataProvider offsetSetProvider
- * @param string $pattern
- * @param array $options
- * @param string $exception
+ * @dataProvider exchangeArrayProvider
*/
- public function testOffsetSet($pattern, $options, $exception)
+ public function testExchangeArray($set, $reset)
{
- if ($exception) $this->expectException($exception);
-
- $glob = new Glob();
- $glob->offsetSet($pattern, $options);
+ $glob = new Glob($set);
+ $old = $glob->exchangeArray($reset);
- if ($exception) return;
+ $this->assertEquals(count($set), count($old), "Old routes count do not match");
+ $this->assertEquals(count($reset), $glob->count(), "Routes count do not match");
- $this->assertEquals(1, $glob->count(), "Routes count is not match");
- $this->assertTrue($glob->offsetExists($pattern), "Key is missing");
-
- #Verify that value was set correctly
- $value = (array)$glob->offsetGet($pattern);
- $this->assertEmpty(array_diff($options, $value), "Route was not set correct");
+ foreach ($reset as $pattern => $options) {
+ $this->assertTrue($glob->offsetExists($pattern), "Key is missing");
+ }
+ foreach ($set as $pattern => $options) {
+ $this->assertTrue(!empty($old[$pattern]), "Old key is missing");
+ $this->assertFalse($glob->offsetExists($pattern), "Key exists, but should not");
+ }
}
+
/**
* Provide data for testOffsetSet()
*/
public function offsetSetProvider()
{
return [
- ['/foo/*', ['controller' => 'bar'], ''],
- ['/foo/*', ['fn' => 'bar'], ''],
- ['/foo/*', ['file' => 'bar'], ''],
- ['', ['controller' => 'bar'], BadMethodCallException::class],
- ['foo', 'bar', InvalidArgumentException::class],
- ['', '', BadMethodCallException::class]
+ ['/foo/*', ['controller' => 'bar']],
+ ['/foo/*', ['fn' => 'bar']],
+ ['/foo/*', ['file' => 'bar']],
+ ['/foo/*', $this->getMockBuilder(Route::class)->setConstructorArgs([['controller' => 'bar']])->getMock()]
];
}
+
+ /**
+ * Test ArrayObject::offsetSet method
+ * @dataProvider offsetSetProvider
+ *
+ * @param string $pattern
+ * @param array $options
+ */
+ public function testOffsetSet($pattern, $options)
+ {
+ $glob = new Glob();
+ $glob[$pattern] = $options;
+ $this->assertCount(1, $glob);
+ $this->assertTrue(isset($glob[$pattern]));
+
+ $route = $glob[$pattern];
+ $this->assertInstanceOf(Route::class, $route);
+
+ if ($options instanceof Route) {
+ $this->assertSame($options, $route);
+ } else {
+ $this->assertEquals([], array_diff($options, (array)$route));
+ }
+ }
+
/**
- * Test ArrayObject::append method
- *
* @expectedException BadMethodCallException
*/
- public function testAppend()
+ public function testOffsetSetInvalidPattern()
{
$glob = new Glob();
- $glob->append(['controller' => 'bar']);
+ $glob[''] = ['controller' => 'bar'];
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testOffsetSetInvalidRoute()
+ {
+ $glob = new Glob();
+ $glob['/foo'] = 'bar';
}
/**
- * Test matching of url pattern to given uri
+ * Test ArrayObject::append method
*
- * @dataProvider fnMatchProvider
- * @param string $pattern
- * @param string $uri
- * @param boolean $positive
+ * @expectedException BadMethodCallException
*/
- public function testFnMatch($pattern, $uri, $positive)
- {
+ public function testAppend()
+ {
$glob = new Glob();
-
- $this->assertEquals($positive, $glob->fnmatch($pattern, $uri),
- "Pattern and uri should " . ($positive ? "" : "not") . " match");
+ $glob->append(['controller' => 'bar']);
}
/**
@@ -205,12 +224,28 @@ class GlobTest extends \PHPUnit_Framework_TestCase
}
/**
- * Testing getting route and it's existense
+ * Test matching of url pattern to given uri
+ * @dataProvider fnMatchProvider
*
+ * @param string $pattern
+ * @param string $uri
+ * @param boolean $positive
+ */
+ public function testFnMatch($pattern, $uri, $positive)
+ {
+ $glob = new Glob();
+
+ $this->assertEquals($positive, $glob->fnmatch($pattern, $uri),
+ "Pattern and uri should " . ($positive ? "" : "not") . " match");
+ }
+
+ /**
+ * Testing getting route and it's existense
* @dataProvider getHasRouteProvider
- * @param string $uri Uri of ServerRequest
- * @param string $method Query method name
- * @param boolean $positive If the test should be positive or negative
+ *
+ * @param string $uri Uri of ServerRequest
+ * @param string $method Query method name
+ * @param boolean $positive If the test should be positive or negative
*/
public function testGetHasRoute($uri, $method, $positive)
{
@@ -241,7 +276,7 @@ class GlobTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($exist, "Route not exists");
$this->assertTrue((bool)$match, "Found no match of uri with patterns");
- $this->assertEquals($values[$match]['controller'], $route['controller'], "False route obtained");
+ $this->assertEquals($values[$match]['controller'], $route->controller, "False route obtained");
}
/**
@@ -295,152 +330,6 @@ class GlobTest extends \PHPUnit_Framework_TestCase
}
/**
- * Test binding simple string when getting route
- */
- public function testBindVarString()
- {
- $uri = '/foo/bar';
- $values = [$uri => ['controller' => 'value1', 'check' => 'value1']];
-
- $glob = new Glob($values);
- $request = $this->getServerRequest($uri);
- $route = $glob->getRoute($request);
-
- $this->assertEquals($route['check'], $values[$uri]['check'], "Option value is not correct");
- }
-
- /**
- * Test binding single url part to route option
- * @dataProvider bindVarSingleUrlPartProvider
- * @param string $patter
- * @param string $uri
- * @param array $options Route options
- */
- public function testBindVarSingleUrlPart($pattern, $uri, $options)
- {
- $values = [$pattern => $options];
-
- $glob = new Glob($values);
- $request = $this->getServerRequest($uri);
- $route = $glob->getRoute($request);
-
- $this->assertTrue((bool)$route, "Route not found");
-
- if (!empty($options['check'])) {
- $this->assertEquals('test', $route['check'], "Single pocket did not match");
- } elseif(empty($options['check2'])) {
- $this->assertEquals('test1/test2', $route['check1'], "Single compound pocket did not match");
- } else {
- $this->assertEquals('test1', $route['check1'], "First of two pockets did not match");
- $this->assertEquals('test2', $route['check2'], "Second of two pockets did not match");
- }
- }
-
- /**
- * Provide uri's and corresponding patterns for testBindVarSingleUrlPart()
- */
- public function bindVarSingleUrlPartProvider()
- {
- return [
- ['/*', '/test', ['controller' => 'value', 'check' => '$1']],
- ['/foo/*/bar', '/foo/test/bar', ['controller' => 'value', 'check' => '$2']],
- ['/foo/bar/*', '/foo/bar/test', ['controller' => 'value', 'check' => '$3']],
- ['/foo/bar/*/zet/*', '/foo/bar/test1/zet/test2', ['controller' => 'value', 'check1' => '$3', 'check2' => '$5']],
- ['/foo/bar/*/zet/*', '/foo/bar/test1/zet/test2', ['controller' => 'value', 'check1' => '~$3~/~$5~']]
- ];
- }
-
- /**
- * Test binding multyple url parts to route option
- *
- * @dataProvider bindVarMultipleUrlPartsProvider
- * @param string $uri
- * @param array $options Route options
- * @param boolean $positive
- * @param string $exception
- */
- public function testBindVarMultipleUrlParts($uri, $options, $positive, $exception)
- {
- if ($exception) $this->expectException(InvalidArgumentException::class);
-
- $values = [$uri => $options];
- $glob = new Glob($values);
- $request = $this->getServerRequest($uri);
- $route = $glob->getRoute($request);
-
- if ($exception) return;
-
- $values = explode('/', trim($uri, '/'));
- $this->assertTrue((bool)$route, "Route not found");
-
- $positive ?
- $this->assertArraysEqual($values, $route['check'], "Multyple url parts are not picked correctly") :
- $this->assertEmpty($route['check'], "Multyple parts element should be empty");
-
- if (!empty($options['check2'])) {
- array_shift($values);
- $this->assertArraysEqual($values, $route['check2'], "Secondary multyple url parts are not picked correctly");
- }
- }
-
- /**
- * Provide uri's and corresponding patterns for testBindVarMultipleUrlParts()
- */
- public function bindVarMultipleUrlPartsProvider()
- {
- return [
- ['/foo', ['controller' => 'value', 'check' => '$1...'], false, InvalidArgumentException::class],
- ['/', ['controller' => 'value', 'check' => ['$1...']], false, ''],
- ['/foo', ['controller' => 'value', 'check' => ['$1...']], true, ''],
- ['/foo/bar', ['controller' => 'value', 'check' => ['$1...'], 'check2' => ['$2...']], true, '']
- ];
- }
-
- /**
- * Test binding element of superglobal array to route option
- *
- * @dataProvider bindVarSuperGlobalProvider
- * @param string $uri
- * @param array $options
- * @param string $type ('get', 'post', 'cookie')
- */
- public function testBindVarSuperGlobal($uri, $options, $type)
- {
- $test = ['check' => 'test'];
- $glob = new Glob([$uri => $options]);
- $request = $this->getServerRequest($uri, 'GET', [$type => $test]);
- $route = $glob->getRoute($request);
-
- $this->assertEquals($test['check'], $route['check'], "Did not obtaine value for superglobal '$type'");
- }
-
- /**
- * Provide uri's and corresponding patterns for testBindVarMultipleUrlParts()
- */
- public function bindVarSuperGlobalProvider()
- {
- return [
- ['/foo', ['controller' => 'value', 'check' => '$_GET[check]'], 'get'],
- ['/foo', ['controller' => 'value', 'check' => '$_POST[check]'], 'post'],
- ['/foo', ['controller' => 'value', 'check' => '$_COOKIE[check]'], 'cookie']
- ];
- }
-
- /**
- * Test binding element of superglobal array to route option
- */
- public function testBindVarRequestHeader()
- {
- $uri = '/foo/bar';
- $test = 'test_header_value';
- $glob = new Glob([$uri => ['controller' => 'value', 'check' => '$HTTP_REFERER']]);
- $request = $this->getServerRequest($uri, 'GET', [], $test);
- $route = $glob->getRoute($request);
-
- $this->assertEquals($test, $route['check'], "Did not obtaine value for header");
- }
-
- /**
* Get ServerRequestInterface object
*
* @param string $uri
@@ -469,7 +358,7 @@ class GlobTest extends \PHPUnit_Framework_TestCase
*/
public function assertArraysEqual(array $array1, array $array2)
{
- $this->assertEquals(count($array1), count($array2));
- $this->assertEmpty(array_diff($array1, $array2));
+ $this->assertEmpty(array_diff($array2, $array1), 'Missing items');
+ $this->assertEmpty(array_diff($array1, $array2), 'Additional items');
}
}
diff --git a/tests/Router/Routes/RouteBindingTest.php b/tests/Router/Routes/RouteBindingTest.php
new file mode 100644
index 0000000..96536ac
--- /dev/null
+++ b/tests/Router/Routes/RouteBindingTest.php
@@ -0,0 +1,263 @@
+<?php
+
+namespace Jasny\Router\Routes;
+
+use Jasny\Router\Routes\Glob;
+use Jasny\Router\Route;
+use Psr\Http\Message\ServerRequestInterface;
+
+use InvalidArgumentException;
+
+/**
+ * @covers Jasny\Router\Routes\RouteBinding
+ */
+class RouteBindingTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * Test binding simple string when getting route
+ */
+ public function testBindVarString()
+ {
+ $uri = '/foo/bar';
+ $values = [$uri => ['controller' => 'value1', 'check' => 'value1']];
+
+ $glob = new Glob($values);
+ $request = $this->getServerRequest($uri);
+ $route = $glob->getRoute($request);
+
+ $this->assertEquals($values[$uri]['check'], $route->check);
+ }
+
+ /**
+ * Provide uri's and corresponding patterns for testBindVarSingleUrlPart()
+ */
+ public function bindVarSingleUrlPartProvider()
+ {
+ return [
+ ['/*', '/test', ['check' => '$1'], 'test'],
+ ['/', '/', ['check' => '$1|test'], 'test'],
+ ['/foo/*/bar', '/foo/test/bar', ['check' => '$2'], 'test'],
+ ['/foo/bar/*', '/foo/bar/test', ['check' => '$3'], 'test'],
+ ['/foo/bar/*/zet/*', '/foo/bar/test1/zet/test2', ['check' => '$3', 'checkB' => '$5'], 'test1', 'test2'],
+ ['/foo/bar/*/zet/*', '/foo/bar/test1/zet/test2', ['check' => '~$3~/~$5~'], 'test1/test2'],
+ ['/', '/', ['check' => '$foo'], null],
+ ['/', '/', ['check' => 'test', 'checkB' => null], 'test', null]
+ ];
+ }
+
+ /**
+ * Test binding single url part to route option
+ * @dataProvider bindVarSingleUrlPartProvider
+ *
+ * @param string $pattern
+ * @param string $uri
+ * @param array $options Route options
+ * @param string $check Expected value for `check`
+ * @param string $checkB Expected value for `checkB`
+ */
+ public function testBindVarSingleUrlPart($pattern, $uri, $options, $check, $checkB = null)
+ {
+ $values = [$pattern => $options];
+
+ $glob = new Glob($values);
+ $request = $this->getServerRequest($uri);
+ $route = $glob->getRoute($request);
+
+ $this->assertNotNull($route, "Route not found");
+ $this->assertInstanceOf(Route::class, $route);
+
+ $this->assertEquals($check, $route->check);
+
+ if (isset($checkB)) {
+ $this->assertEquals($checkB, $route->checkB);
+ } else {
+ $this->assertObjectNotHasAttribute('checkB', $route);
+ }
+ }
+
+ public function testBindVarWithObject()
+ {
+ $object = new \Exception(); // Could be anything, just not stdClass
+ $glob = new Glob(['/' => ['object' => $object]]);
+
+ $request = $this->getServerRequest('/');
+ $route = $glob->getRoute($request);
+
+ $this->assertNotNull($route, "Route not found");
+ $this->assertInstanceOf(Route::class, $route);
+
+ $this->assertSame($object, $route->object);
+ }
+
+ public function bindVarWithSubProvider()
+ {
+ return [
+ [['group' => ['check' => '$1']], 'array'],
+ [['group' => (object)['check' => '$1']], 'object'],
+ [['group' => ['sub' => (object)['check' => '$1']]], 'array', 'object'],
+ [['group' => (object)['sub' => ['check' => '$1']]], 'object', 'array']
+ ];
+ }
+
+ /**
+ * @dataProvider bindVarWithSubProvider
+ *
+ * @param array $options
+ * @param string $type
+ * @param string $subtype
+ */
+ public function testBindVarWithSub(array $options, $type, $subtype = null)
+ {
+ $glob = new Glob(['/*' => $options]);
+
+ $request = $this->getServerRequest('/test');
+ $route = $glob->getRoute($request);
+
+ $this->assertNotNull($route, "Route not found");
+ $this->assertInstanceOf(Route::class, $route);
+
+ $this->assertInternalType($type, $route->group);
+
+ $group = (array)$route->group;
+
+ if (isset($subtype)) {
+ $this->assertArrayHasKey('sub', $group);
+ $this->assertInternalType($subtype, $group['sub']);
+
+ $group = (array)$group['sub'];
+ }
+
+ $this->assertEquals($group, ['check' => 'test']);
+ }
+
+
+ /**
+ * Provide uri's and corresponding patterns for testBindVarMultipleUrlParts()
+ */
+ public function bindVarMultipleUrlPartsProvider()
+ {
+ return [
+ ['/foo', ['check' => '$1...'], false, InvalidArgumentException::class],
+ ['/', ['check' => ['$1...']], false],
+ ['/foo', ['check' => ['$1...']], true],
+ ['/foo/bar', ['check' => ['$1...'], 'checkB' => ['$2...']],
+ InvalidArgumentException::class]
+ ];
+ }
+
+ /**
+ * Test binding multyple url parts to route option
+ * @dataProvider bindVarMultipleUrlPartsProvider
+ *
+ * @param string $uri
+ * @param array $options Route options
+ * @param boolean $positive
+ * @param string $exception
+ */
+ public function testBindVarMultipleUrlParts($uri, $options, $positive, $exception = false)
+ {
+ if ($exception) {
+ $this->expectException($exception);
+ }
+
+ $glob = new Glob([$uri => $options]);
+ $request = $this->getServerRequest($uri);
+ $route = $glob->getRoute($request);
+
+ if ($exception) return;
+
+ $this->assertNotNull($route, "Route not found");
+ $this->assertInstanceOf(Route::class, $route);
+
+ $values = explode('/', trim($uri, '/'));
+
+ $positive ?
+ $this->assertArraysEqual($values, $route->check, "Multyple url parts are not picked correctly") :
+ $this->assertEmpty($route->check, "Multyple parts element should be empty");
+
+ if (!empty($options->checkB)) {
+ array_shift($values);
+ $this->assertArraysEqual($values, $route->checkB, "Secondary multyple url parts are not picked correctly");
+ }
+ }
+
+ /**
+ * Provide uri's and corresponding patterns for testBindVarMultipleUrlParts()
+ */
+ public function bindVarSuperGlobalProvider()
+ {
+ return [
+ ['/foo', ['check' => '$_GET[check]'], 'get'],
+ ['/foo', ['check' => '$_POST[check]'], 'post'],
+ ['/foo', ['check' => '$_COOKIE[check]'], 'cookie']
+ ];
+ }
+
+ /**
+ * Test binding element of superglobal array to route option
+ * @dataProvider bindVarSuperGlobalProvider
+ *
+ * @param string $uri
+ * @param array $options
+ * @param string $type ('get', 'post', 'cookie')
+ */
+ public function testBindVarSuperGlobal($uri, $options, $type)
+ {
+ $test = ['check' => 'test'];
+ $glob = new Glob([$uri => $options]);
+ $request = $this->getServerRequest($uri, 'GET', [$type => $test]);
+ $route = $glob->getRoute($request);
+
+ $this->assertEquals($test['check'], $route->check, "Did not obtaine value for superglobal '$type'");
+ }
+
+ /**
+ * Test binding element of superglobal array to route option
+ */
+ public function testBindVarRequestHeader()
+ {
+ $uri = '/foo/bar';
+ $test = 'test_header_value';
+
+ $glob = new Glob([$uri => ['check' => '$HTTP_REFERER']]);
+ $request = $this->getServerRequest($uri, 'GET', [], $test);
+
+ $route = $glob->getRoute($request);
+ $this->assertNotNull($route, "Route not found");
+
+ $this->assertEquals($test, $route->check);
+ }
+
+ /**
+ * Get ServerRequestInterface object
+ *
+ * @param string $uri
+ * @param string $method Http query method
+ * @return ServerRequestInterface
+ */
+ public function getServerRequest($uri, $method = 'GET', $globals = [], $header = '')
+ {
+ $request = $this->createMock(ServerRequestInterface::class);
+ $request->method('getUri')->willReturn($uri);
+ $request->method('getMethod')->willReturn($method);
+ $request->method('getQueryParams')->willReturn(isset($globals['get']) ? $globals['get'] : []);
+ $request->method('getParsedBody')->willReturn(isset($globals['post']) ? $globals['post'] : []);
+ $request->method('getCookieParams')->willReturn(isset($globals['cookie']) ? $globals['cookie'] : []);
+ $request->method('getHeaderLine')->willReturn($header);
+
+ return $request;
+ }
+
+ /**
+ * Assert that two 1-dimensional arrays are equal.
+ * Use if array elements are scalar values, or objects with __toString() method
+ *
+ * @param array $array1
+ * @param array $array2
+ */
+ public function assertArraysEqual(array $array1, array $array2)
+ {
+ $this->assertEmpty(array_diff($array2, $array1), 'Missing items');
+ $this->assertEmpty(array_diff($array1, $array2), 'Additional items');
+ }
+}
diff --git a/tests/Router/Runner/CallbackTest.php b/tests/Router/Runner/CallbackTest.php
index 8a31794..39c2798 100644
--- a/tests/Router/Runner/CallbackTest.php
+++ b/tests/Router/Runner/CallbackTest.php
@@ -1,64 +1,73 @@
<?php
+namespace Jasny\Router;
+
use Jasny\Router\Route;
use Jasny\Router\Runner\Callback;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
-class CallbackTest extends PHPUnit_Framework_TestCase
+use Jasny\Router\TestHelpers;
+
+/**
+ * @covers Jasny\Router\Runner\Callback
+ */
+class CallbackTest extends \PHPUnit_Framework_TestCase
{
+ use TestHelpers;
+
/**
* Test creating Callback runner
- *
- * @dataProvider callbackProvider
- * @param Route $route
- * @param boolean $positive
*/
- public function testCallback($route, $positive)
+ public function testCallback()
{
- $runner = new Callback($route);
-
$request = $this->createMock(ServerRequestInterface::class);
$response = $this->createMock(ResponseInterface::class);
- $request->expects($this->once())->method('getAttribute')->with($this->equalTo('route'))->will($this->returnValue($route));
-
- if (!$positive) $this->expectException(\RuntimeException::class);
- $result = $runner->run($request, $response);
-
- if (!$positive) return;
-
- $this->assertEquals($request, $result['request'], "Request object was not passed correctly to result");
- $this->assertEquals($response, $result['response'], "Response object was not passed correctly to result");
+ $finalResponse = $this->createMock(ResponseInterface::class);
+
+ $route = $this->createMock(Route::class);
+ $route->fn = $this->createCallbackMock($this->once(), [$request, $response], $finalResponse);
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = new Callback($route);
+ $result = $runner($request, $response);
+
+ $this->assertSame($finalResponse, $result);
}
-
-
+
/**
- * Provide data fpr testing 'create' method
+ * @expectedException RuntimeException
+ * @expectedExceptionMessage 'fn' property of route shoud be a callable
*/
- public function callbackProvider()
+ public function testNoCallback()
{
- $callback = function($request, $response) {
- return ['request' => $request, 'response' => $response];
- };
-
- return [
- [Route::create(['fn' => $callback, 'value' => 'test']), true],
- [Route::create(['fn' => [$this, 'getCallback'], 'value' => 'test']), true],
- [Route::create(['controller' => 'TestController', 'value' => 'test']), false],
- [Route::create(['file' => 'some_file.php', 'value' => 'test']), false],
- [Route::create(['test' => 'test']), false],
- ];
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ $route = $this->createMock(Route::class);
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = new Callback($route);
+ $runner($request, $response);
}
-
+
/**
- * Testable callback for creating Route
- *
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @return array
+ * @expectedException RuntimeException
+ * @expectedExceptionMessage 'fn' property of route shoud be a callable
*/
- public function getCallback($request, $response)
+ public function testInvalidCallback()
{
- return ['request' => $request, 'response' => $response];
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ $route = $this->createMock(Route::class);
+ $route->fn = 'foo bar zoo';
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = new Callback($route);
+ $runner($request, $response);
}
}
diff --git a/tests/Router/Runner/ControllerTest.php b/tests/Router/Runner/ControllerTest.php
index 433d36d..694fe5b 100644
--- a/tests/Router/Runner/ControllerTest.php
+++ b/tests/Router/Runner/ControllerTest.php
@@ -1,140 +1,80 @@
<?php
+namespace Jasny\Router;
+
use Jasny\Router\Route;
-use Jasny\Router\Runner\Controller;
+use Jasny\Router\Runner;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
-class ControllerTest extends PHPUnit_Framework_TestCase
-{
- /**
- * Tmp scripts
- * @var array
- **/
- public static $files = [];
-
- /**
- * Test creating Controller runner
- *
- * @dataProvider phpScriptProvider
- * @param Route $route
- * @param boolean $positive
- */
- public function testPhpScript($route, $positive)
- {
- $runner = new Controller($route);
+use Jasny\Router\TestHelpers;
+/**
+ * @covers Jasny\Router\Runner\Controller;
+ */
+class ControllerTest extends \PHPUnit_Framework_TestCase
+{
+ use TestHelpers;
+
+ public function testInvoke()
+ {
$request = $this->createMock(ServerRequestInterface::class);
$response = $this->createMock(ResponseInterface::class);
- $request->expects($this->once())->method('getAttribute')->with($this->equalTo('route'))->will($this->returnValue($route));
-
- if (!$positive) $this->expectException(\RuntimeException::class);
- $result = $runner->run($request, $response);
-
- $this->assertEquals($request, $result['request'], "Request object was not passed correctly to result");
- $this->assertEquals($response, $result['response'], "Response object was not passed correctly to result");
+ $finalResponse = $this->createMock(ResponseInterface::class);
+
+ $controller = $this->createCallbackMock($this->once(), [$request, $response], $finalResponse);
+ $class = get_class($controller);
+
+ $route = $this->createMock(Route::class);
+ $route->controller = $class;
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = $this->getMockBuilder(Runner\Controller::class)->setMethods(['instantiate'])->getMock();
+ $runner->expects($this->once())->method('instantiate')->with($class)->willReturn($controller);
+
+ $result = $runner($request, $response);
+
+ $this->assertSame($finalResponse, $result);
}
-
- /**
- * Provide data for testing 'create' method
- */
- public function phpScriptProvider()
- {
- foreach (['noInvoke', 'withInvoke'] as $type) {
- list($class, $path) = static::createTmpScript($type);
- static::$files[$type] = compact('class', 'path');
- }
-
- return [
- [Route::create(['test' => 'test']), false],
- [Route::create(['fn' => 'testFunction', 'value' => 'test']), false],
- [Route::create(['controller' => 'TestController', 'value' => 'test']), false],
- [Route::create(['controller' => '', 'value' => 'test']), false],
- [Route::create(['controller' => static::$files['noInvoke']['class'], 'path' => static::$files['noInvoke']['path']]), false],
- [Route::create(['controller' => static::$files['withInvoke']['class'], 'path' => static::$files['withInvoke']['path']]), true],
- ];
- }
-
- /**
- * Delete tmp test scripts
- */
- public static function tearDownAfterClass()
- {
- foreach (static::$files as $path) {
- unlink($path['path']);
- }
- }
-
+
/**
- * Create single tmp script file for testing
- *
- * @param string $type ('returnTrue', 'returnNotTrue')
- * @return string $path
+ * @expectedException RuntimeException
+ * @expectedExceptionMessage Can not route to controller 'FooBarZoo': class not exists
*/
- public static function createTmpScript($type)
- {
- $dir = rtrim(sys_get_temp_dir(), '/');
-
- do {
- $name = static::getRandomString() . '-test-script.php';
- $path = $dir . '/' . $name;
-
- if (!file_exists($path)) break;
- } while (true);
-
- if ($type === 'noInvoke') {
- $class = 'RunnerTestConrtollerInvalid';
- $content =
-<<<CONTENT
-<?php
-
-class $class {
- public \$route = null;
-
- public function __construct(\$route)
+ public function testInvalidClass()
{
- \$this->route = \$route;
- }
-}
-CONTENT;
- } else {
- $class = 'RunnerTestConrtoller';
- $content =
-<<<CONTENT
-<?php
-
-class $class {
- public \$route = null;
-
- public function __construct(\$route)
- {
- \$this->route = \$route;
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ $route = $this->createMock(Route::class);
+ $route->controller = 'foo-bar-zoo';
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = $this->getMockBuilder(Runner\Controller::class)->setMethods(['instantiate'])->getMock();
+ $runner->expects($this->never())->method('instantiate');
+
+ $runner($request, $response);
}
- public function __invoke(Psr\Http\Message\ServerRequestInterface \$request, Psr\Http\Message\ResponseInterface \$response)
- {
- return ['request' => \$request, 'response' => \$response];
- }
-}
-CONTENT;
- }
-
- $bytes = file_put_contents($path, $content);
- static::assertTrue((int)$bytes > 0);
-
- require_once $path;
-
- return [$class, $path];
- }
-
/**
- * Get random string of given length (no more then length of md5 hash)
- *
- * @param int $length
- * @return string
+ * @expectedException RuntimeException
+ * @expectedExceptionMessage Can not route to controller 'StdClass': class does not have '__invoke' method
*/
- public static function getRandomString($length = 10)
- {
- return substr(md5(microtime(true) * mt_rand()), 0, $length);
+ public function testInvokeNotCallable()
+ {
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ $route = $this->createMock(Route::class);
+ $route->controller = 'std-class';
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = $this->getMockBuilder(Runner\Controller::class)->setMethods(['instantiate'])->getMock();
+ $runner->expects($this->never())->method('instantiate');
+
+ $runner($request, $response);
}
}
diff --git a/tests/Router/Runner/PhpScriptTest.php b/tests/Router/Runner/PhpScriptTest.php
index 2f31f73..658998b 100644
--- a/tests/Router/Runner/PhpScriptTest.php
+++ b/tests/Router/Runner/PhpScriptTest.php
@@ -1,90 +1,128 @@
<?php
+namespace Jasny\Router;
+
use Jasny\Router\Route;
-use Jasny\Router\Runner\PhpScript;
+use Jasny\Router\Runner;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
-class PhpScriptTest extends PHPUnit_Framework_TestCase
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\vfsStreamDirectory;
+
+/**
+ * @covers Jasny\Router\Runner\PhpScript
+ */
+class PhpScriptTest extends \PHPUnit_Framework_TestCase
{
/**
- * Test creating PhpScript runner
- *
- * @dataProvider phpScriptProvider
- * @param Route $route
- * @param boolean $positive
+ * @var vfsStreamDirectory
*/
- public function testPhpScript($route, $positive)
- {
- $runner = new PhpScript($route);
+ protected $root;
+
+ public function setUp()
+ {
+ $this->root = vfsStream::setup('root');
+ $this->root->addChild(vfsStream::newFile('true.php')->setContent('<?php ?>'));
+ $this->root->addChild(vfsStream::newFile('foo.php')->setContent('<?php return "foo"; ?>'));
+ }
+ public function testInvoke()
+ {
$request = $this->createMock(ServerRequestInterface::class);
$response = $this->createMock(ResponseInterface::class);
- $request->expects($this->once())->method('getAttribute')->with($this->equalTo('route'))->will($this->returnValue($route));
-
- if (!$positive) $this->expectException(\RuntimeException::class);
- $result = $runner->run($request, $response);
+ $finalResponse = $this->createMock(ResponseInterface::class);
- if ($route->type === 'returnTrue') {
- $this->assertEquals($response, $result, "Request object was not returned as result");
- } else {
- $this->assertEquals($request, $result['request'], "Request object was not passed correctly to result");
- $this->assertEquals($response, $result['response'], "Response object was not passed correctly to result");
- }
+ $route = $this->createMock(Route::class);
+ $route->file = vfsStream::url('root/foo.php');
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = $this->getMockBuilder(Runner\PhpScript::class)->setMethods(['includeScript'])->getMock();
+ $runner->expects($this->once())->method('includeScript')
+ ->with(vfsStream::url('root/foo.php'), $request, $response)
+ ->willReturn($finalResponse);
- unlink($route->file);
+ $result = $runner($request, $response);
+
+ $this->assertSame($finalResponse, $result);
}
-
- /**
- * Provide data fpr testing 'create' method
- */
+
public function phpScriptProvider()
{
+ $routeTrue = $this->createMock(Route::class);
+ $routeTrue->file = vfsStream::url('root/true.php');
+
+ $routeFoo = $this->createMock(Route::class);
+ $routeFoo->file = vfsStream::url('root/foo.php');
+
return [
- [Route::create(['test' => 'test']), false],
- [Route::create(['fn' => 'testFunction', 'value' => 'test']), false],
- [Route::create(['controller' => 'TestController', 'value' => 'test']), false],
- [Route::create(['file' => '', 'value' => 'test']), false],
- [Route::create(['file' => 'some_file.php', 'value' => 'test']), false],
- [Route::create(['file' => '../' . basename(getcwd()), 'value' => 'test']), false],
- [Route::create(['file' => $this->createTmpScript('returnTrue'), 'type' => 'returnTrue']), true],
- [Route::create(['file' => $this->createTmpScript('returnNotTrue'), 'type' => 'returnNotTrue']), true]
+ [$routeTrue, 1],
+ [$routeFoo, 'foo']
];
}
-
+
/**
- * Create single tmp script file for testing
- *
- * @param string $type ('returnTrue', 'returnNotTrue')
- * @return string $path
+ * @dataProvider phpScriptProvider
+ *
+ * @param Route $route
+ * @param mixed $expected
*/
- public function createTmpScript($type)
+ public function testInvokeIncludeScript($route, $expected)
{
- $dir = rtrim(sys_get_temp_dir(), '/');
-
- do {
- $name = $this->getRandomString() . '-test-script.php';
- $path = $dir . '/' . $name;
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+ $request->expects($this->once())->method('getAttribute')->with($this->equalTo('route'))->will($this->returnValue($route));
- if (!file_exists($path)) break;
- } while (true);
+ $runner = new Runner\PhpScript($route);
+
+ if ($expected === 1) {
+ $expected = $response;
+ }
+
+ $result = $runner->run($request, $response);
+
+ $this->assertSame($expected, $result);
+ }
- $content = $type === 'returnTrue' ? "<?php\n return true;" : "<?php\n return ['request' => \$request, 'response' => \$response];";
- $bytes = file_put_contents($path, $content);
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Failed to route using 'vfs://root/bar.php': File doesn't exist
+ */
+ public function testInvokeWithNonExistingFile()
+ {
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
- $this->assertTrue((int)$bytes > 0);
+ $route = $this->createMock(Route::class);
+ $route->file = vfsStream::url('root/bar.php');
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = $this->getMockBuilder(Runner\PhpScript::class)->setMethods(['includeScript'])->getMock();
+ $runner->expects($this->never())->method('includeScript');
- return $path;
+ $runner($request, $response);
}
/**
- * Get random string of given length (no more then length of md5 hash)
- *
- * @param int $length
- * @return string
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Won't route to 'vfs://root/../bar.php': '~', '..' are not allowed in filename
*/
- public function getRandomString($length = 10)
- {
- return substr(md5(microtime(true) * mt_rand()), 0, $length);
+ public function testInvokeWithIlligalFilename()
+ {
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ $route = $this->createMock(Route::class);
+ $route->file = vfsStream::url('root/../bar.php');
+
+ $request->expects($this->once())->method('getAttribute')->with('route')->willReturn($route);
+
+ $runner = $this->getMockBuilder(Runner\PhpScript::class)->setMethods(['includeScript'])->getMock();
+ $runner->expects($this->never())->method('includeScript');
+
+ $runner($request, $response);
}
+
}
diff --git a/tests/Router/Runner/RunnerFactoryTest.php b/tests/Router/Runner/RunnerFactoryTest.php
deleted file mode 100644
index c18656d..0000000
--- a/tests/Router/Runner/RunnerFactoryTest.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-use Jasny\Router\Route;
-use Jasny\Router\Runner\RunnerFactory;
-use Jasny\Router\Runner\Controller;
-use Jasny\Router\Runner\Callback;
-use Jasny\Router\Runner\PhpScript;
-
-class RunnerFactoryTest extends PHPUnit_Framework_TestCase
-{
- /**
- * Test creating Runner object using factory
- *
- * @dataProvider createProvider
- * @param Route $route
- * @param string $class Runner class to use
- * @param boolean $positive
- */
- public function testCreate($route, $class, $positive)
- {
- if (!$positive) $this->expectException(\InvalidArgumentException::class);
-
- $factory = new RunnerFactory();
- $runner = $factory($route);
-
- $this->assertInstanceOf($class, $runner, "Runner object has invalid class");
- }
-
- /**
- * Provide data fpr testing 'create' method
- */
- public function createProvider()
- {
- return [
- [Route::create(['controller' => 'TestController', 'value' => 'test']), Controller::class, true],
- [Route::create(['fn' => 'testFunction', 'value' => 'test']), Callback::class, true],
- [Route::create(['file' => 'some_file.php', 'value' => 'test']), PhpScript::class, true],
- [Route::create(['test' => 'test']), '', false],
- ];
- }
-}
diff --git a/tests/Router/RunnerFactoryTest.php b/tests/Router/RunnerFactoryTest.php
new file mode 100644
index 0000000..a9fbe92
--- /dev/null
+++ b/tests/Router/RunnerFactoryTest.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Jasny\Router;
+
+use Jasny\Router\RunnerFactory;
+use Jasny\Router\Route;
+use Jasny\Router\Runner;
+
+class RunnerFactoryTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * Provide data fpr testing 'create' method
+ */
+ public function createProvider()
+ {
+ $routeController = $this->createMock(Route::class);
+ $routeController->controller = 'foo-bar';
+
+ $routeCallback = $this->createMock(Route::class);
+ $routeCallback->fn = function() {};
+
+ $routePhpScript = $this->createMock(Route::class);
+ $routePhpScript->file = 'some_file.php';
+
+ return [
+ [$routeController, Runner\Controller::class],
+ [$routeCallback, Runner\Callback::class],
+ [$routePhpScript, Runner\PhpScript::class],
+ ];
+ }
+
+ /**
+ * Test creating Runner object using factory
+ * @dataProvider createProvider
+ *
+ * @param Route $route
+ * @param string $class Runner class to use
+ */
+ public function testCreate($route, $class)
+ {
+ $factory = new RunnerFactory();
+ $runner = $factory($route);
+
+ $this->assertInstanceOf($class, $runner, "Runner object has invalid class");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Route has neither 'controller', 'fn' or 'file' defined
+ */
+ public function testCreatWithInvalideRoute()
+ {
+ $route = $this->createMock(Route::class);
+
+ $factory = new RunnerFactory();
+ $factory($route);
+ }
+}
diff --git a/tests/Router/RunnerTest.php b/tests/Router/RunnerTest.php
index 7db4c02..3e558b4 100644
--- a/tests/Router/RunnerTest.php
+++ b/tests/Router/RunnerTest.php
@@ -1,46 +1,39 @@
<?php
-use Jasny\Router\Route;
use Jasny\Router\Runner;
-use Jasny\Router\Runner\Controller;
-use Jasny\Router\Runner\Callback;
-use Jasny\Router\Runner\PhpScript;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
+use Jasny\Router\TestHelpers;
+
+/**
+ * @covers Jasny\Router\Runner
+ */
class RunnerTest extends PHPUnit_Framework_TestCase
{
+ use TestHelpers;
+
/**
* Test runner __invoke method
*/
public function testInvoke()
{
- $runner = $this->getMockBuilder(Runner::class)->disableOriginalConstructor()->getMockForAbstractClass();
- $queries = [
- 'request' => $this->createMock(ServerRequestInterface::class),
- 'response' => $this->createMock(ResponseInterface::class)
- ];
-
- #Test that 'run' receives correct arguments inside '__invoke'
- $runner->method('run')->will($this->returnCallback(function($arg1, $arg2) {
- return ['request' => $arg1, 'response' => $arg2];
- }));
-
- $result = $runner($queries['request'], $queries['response']);
- $this->assertEquals($result['request'], $queries['request'], "Request was not returned correctly from 'run'");
- $this->assertEquals($result['response'], $queries['response'], "Response was not returned correctly from 'run'");
-
- #The same test with calling 'next' callback
- $result = $runner($queries['request'], $queries['response'], function($request, $prevResponse) use ($queries) {
- $this->assertEquals($request, $queries['request'], "Request is not correct in 'next'");
- $this->assertEquals($prevResponse['request'], $queries['request'], "Prev response was not passed correctly to 'next'");
- $this->assertEquals($prevResponse['response'], $queries['response'], "Prev response was not passed correctly to 'next'");
-
- return $queries + ['next_called' => true];
- });
+ $runner = $this->getMockBuilder(Runner::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $request = $this->createMock(ServerRequestInterface::class);
+
+ $response = $this->createMock(ResponseInterface::class);
+ $runResponse = $this->createMock(ResponseInterface::class);
+ $finalResponse = $this->createMock(ResponseInterface::class);
- $this->assertTrue($result['next_called'], "'Next' callback was not called");
- $this->assertEquals($result['request'], $queries['request'], "Request was not returned correctly from 'run' with 'next'");
- $this->assertEquals($result['response'], $queries['response'], "Request was not returned correctly from 'run' with 'next'");
+ $runner->expects($this->once())->method('run')
+ ->with($request, $response)
+ ->willReturn($runResponse);
+
+ $next = $this->createCallbackMock($this->once(), [$request, $runResponse], $finalResponse);
+
+ $runner($request, $response, $next);
}
}
diff --git a/tests/RouterTest.php b/tests/RouterTest.php
index 46574c4..c036c71 100644
--- a/tests/RouterTest.php
+++ b/tests/RouterTest.php
@@ -1,267 +1,229 @@
<?php
+namespace Jasny\Router;
+
use Jasny\Router;
use Jasny\Router\Route;
-use Jasny\Router\Runner\RunnerFactory;
+use Jasny\Router\Routes;
+use Jasny\Router\RunnerFactory;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
-class RouterTest extends PHPUnit_Framework_TestCase
+use Jasny\Router\TestHelpers;
+
+/**
+ * @covers Jasny\Router
+ */
+class RouterTest extends \PHPUnit_Framework_TestCase
{
+ use TestHelpers;
+
/**
* Test creating Router
*/
- public function testConstruct()
+ public function testGetRoutes()
{
- $routes = [
- '/foo' => ['fn' => 'test_function'],
- '/foo/bar' => ['controller' => 'TestController']
- ];
+ $routes = $this->createMock(Routes::class);
$router = new Router($routes);
- $this->assertEquals($routes, $router->getRoutes(), "Routes were not set correctly");
- }
-
- /**
- * Test that on router 'handle', method '__invoke' is called
- */
- public function testHandle()
- {
- $router = $this->createMock(Router::class, ['__invoke']);
- list($request, $response) = $this->getRequests();
-
- $router->method('__invoke')->will($this->returnCallback(function($arg1, $arg2) {
- return ['request' => $arg1, 'response' => $arg2];
- }));
-
- $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");
+ $this->assertSame($routes, $router->getRoutes(), "Routes were not set correctly");
}
+
/**
- * Test '__invoke' method
+ * Test getting runner factory
*/
- public function testInvoke()
+ public function testGetFactory()
{
- $routes = [
- '/foo/bar' => Route::create(['controller' => 'TestController']),
- '/foo' => Route::create(['fn' => function($arg1, $arg2) {
- return ['request' => $arg1, 'response' => $arg2];
- }])
- ];
-
- list($request, $response) = $this->getRequests();
- $this->expectRequestRoute($request, $routes['/foo']);
-
- $router = new Router($routes);
- $result = $router($request, $response);
+ $router = new Router($this->createMock(Routes::class));
+ $factory = $router->getFactory();
- $this->assertEquals($request, $result['request'], "Request was not processed correctly");
- $this->assertEquals($response, $result['response'], "Response was not processed correctly");
+ $this->assertInstanceOf(RunnerFactory::class, $factory);
}
-
+
/**
- * Test '__invoke' method with 'next' callback
+ * Test setting runner factory
*/
- public function testInvokeNext()
+ public function testSetFactory()
{
- $routes = [
- '/foo/bar' => Route::create(['controller' => 'TestController']),
- '/foo' => Route::create(['fn' => function($request, $response) {
- return $response;
- }])
- ];
-
- list($request, $response) = $this->getRequests();
- $this->expectRequestRoute($request, $routes['/foo']);
-
- $router = new Router($routes);
- $result = $router($request, $response, function($arg1, $arg2) {
- return ['request' => $arg1, 'response' => $arg2];
- });
-
- $this->assertEquals($request, $result['request'], "Request was not processed correctly");
- $this->assertEquals($response, $result['response'], "Response was not processed correctly");
+ $factoryMock = $this->createCallbackMock($this->never());
+
+ $router = new Router($this->createMock(Routes::class));
+
+ $ret = $router->setFactory($factoryMock);
+ $this->assertSame($router, $ret);
+
+ $this->assertSame($factoryMock, $router->getFactory());
}
/**
- * Test case when route is not found
+ * @expectedException \InvalidArgumentException
*/
- public function testNotFound()
+ public function testSetInvalidFactory()
{
- $routes = [
- '/foo/bar' => Route::create(['controller' => 'TestController'])
- ];
-
- list($request, $response) = $this->getRequests();
- $this->expectNotFound($response);
-
- $router = new Router($routes);
- $result = $router($request, $response);
-
- $this->assertEquals(get_class($response), get_class($result), "Returned result is not an instance of 'ServerRequestInterface'");
+ $router = new Router($this->createMock(Routes::class));
+ $router->setFactory('foo bar zoo');
}
+
/**
- * Test adding middleware action
- *
- * @dataProvider addProvider
- * @param mixed $middleware1
- * @param callable $middleware2
- * @param boolean $positive
+ * Test that on router 'handle', method '__invoke' is called
*/
- public function testAdd($middleware1, $middleware2, $positive)
+ public function testHandle()
{
- $router = new Router([]);
- $this->assertEquals(0, count($router->getMiddlewares()), "Middlewares array should be empty");
-
- if (!$positive) $this->expectException(\InvalidArgumentException::class);
-
- $result = $router->add($middleware1);
- $this->assertEquals(1, count($router->getMiddlewares()), "There should be only one item in middlewares array");
- $this->assertEquals($middleware1, reset($router->getMiddlewares()), "Wrong item in middlewares array");
- $this->assertEquals($router, $result, "'Add' should return '\$this'");
+ $request = $this->createMock(ServerRequestInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+ $finalResponse = $this->createMock(ResponseInterface::class);
+
+ $router = $this->getMockBuilder(Router::class)->disableOriginalConstructor()
+ ->setMethods(['__invoke'])->getMock();
+ $router->expects($this->once())->method('__invoke')->with($request, $response)->willReturn($finalResponse);
- if (!$middleware2) return;
+ $result = $router->handle($request, $response);
- $router->add($middleware2);
- $this->assertEquals(2, count($router->getMiddlewares()), "There should be two items in middlewares array");
- foreach ($router->getMiddlewares() as $action) {
- $this->assertTrue($action == $middleware1 || $action == $middleware2, "Wrong item in middlewares array");
- }
+ $this->assertSame($finalResponse, $result);
}
- /**
- * Provide data for testing 'add' method
- */
- public function addProvider()
+ public function nextProvider()
{
return [
- ['wrong_callback', null, false],
- [[$this, 'getMiddlewareCalledFirst'], null, true],
- [[$this, 'getMiddlewareCalledFirst'], [$this, 'getMiddlewareCalledLast'], true]
- ];
- }
-
- /**
- * Test executing router with middlewares chain (test only execution order)
- */
- public function testRunMiddlewares()
- {
- $routes = [
- '/foo' => Route::create(['fn' => function($request, $response) {
- $response->testMiddlewareCalls[] = 'run';
- return $response;
- }])
+ [null],
+ [$this->createCallbackMock($this->any())]
];
-
- list($request, $response) = $this->getRequests();
- $this->expectRequestRoute($request, $routes['/foo']);
-
- $router = new Router($routes);
- $router->add([$this, 'getMiddlewareCalledLast'])->add([$this, 'getMiddlewareCalledFirst']);
-
- $result = $router($request, $response, function($request, $response) {
- $response->testMiddlewareCalls[] = 'outer';
- return $response;
- });
-
- $this->assertEquals(['first','last','run','outer'], $response->testMiddlewareCalls, "Actions were executed in wrong order");
}
/**
- * Test getting and setting runner factory
+ * Test '__invoke' method
+ *
+ * @dataProvider nextProvider
*/
- public function testRunnerFactory()
+ public function testInvoke($next)
{
- $router = new Router([]);
- $factory = $router->getFactory();
-
- $this->assertEquals(RunnerFactory::class, get_class($factory), "By default 'getFactory' should return 'RunnerFactory' instance, not " . get_class($factory));
-
- $self = $router->setFactory(function() {
- return 'test';
- });
- $factory = $router->getFactory();
+ $route = $this->createMock(Route::class);
+
+ $request = $this->createMock(ServerRequestInterface::class);
+ $requestWithRoute = $this->createMock(ServerRequestInterface::class);
+ $request->expects($this->once())->method('withAttribute')->with('route')->willReturn($requestWithRoute);
+
+ $response = $this->createMock(ResponseInterface::class);
+ $finalResponse = $this->createMock(ResponseInterface::class);
+
+ $runner = $this->createCallbackMock($this->once(), [$requestWithRoute, $response, $next], $finalResponse);
+ $factory = $this->createCallbackMock($this->once(), [$route], $runner);
- $this->assertEquals($router, $self, "'setFactory' must return an instance of router");
- $this->assertEquals('test', $factory(), "Factory was not set or got correctly");
+ $routes = $this->createMock(Routes::class);
+ $routes->expects($this->once())->method('getRoute')->with($request)->willReturn($route);
- $this->expectException(\InvalidArgumentException::class);
- $router->setFactory('test');
+ $router = new Router($routes);
+ $router->setFactory($factory);
+
+ $result = $router($request, $response, $next);
+
+ $this->assertSame($finalResponse, $result);
}
/**
- * Get requests for testing
- *
- * @return array
+ * Test case when route is not found
*/
- public function getRequests()
+ public function testNotFound()
{
$request = $this->createMock(ServerRequestInterface::class);
$response = $this->createMock(ResponseInterface::class);
+ $finalResponse = $this->createMock(ResponseInterface::class);
+ $body = $this->createMock(StreamInterface::class);
- $request->method('getUri')->will($this->returnValue('/foo'));
- $request->method('getMethod')->will($this->returnValue('GET'));
+ $response->expects($this->once())->method('withStatus')->with(404)->willReturn($finalResponse);
+ $finalResponse->expects($this->once())->method('getBody')->willReturn($body);
+ $body->expects($this->once())->method('write')->with('Not Found');
+
+ $factory = $this->createCallbackMock($this->never());
- return [$request, $response];
+ $routes = $this->createMock(Routes::class);
+ $routes->expects($this->once())->method('getRoute')->with($request)->willReturn(null);
+
+ $router = new Router($routes);
+ $router->setFactory($factory);
+
+ $result = $router($request, $response);
+
+ $this->assertSame($finalResponse, $result);
}
/**
- * Get middleware action, that should ba called first in middleware chain
- *
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @param callback $next
- * @return ResponseInterface
+ * Test adding middleware action
*/
- public function getMiddlewareCalledFirst(ServerRequestInterface $request, ResponseInterface $response, $next)
+ public function testAdd()
{
- $response->testMiddlewareCalls[] = 'first';
- return $next($request, $response);
+ $middlewareOne = $this->createCallbackMock($this->never());
+ $middlewareTwo = $this->createCallbackMock($this->never());
+
+ $router = new Router($this->createMock(Routes::class));
+
+ $this->assertEquals([], $router->getMiddlewares(), "Middlewares array should be empty");
+
+ $ret = $router->add($middlewareOne);
+ $this->assertSame($router, $ret);
+
+ $router->add($middlewareTwo);
+
+ $this->assertSame([$middlewareOne, $middlewareTwo], $router->getMiddlewares());
}
/**
- * Get middleware action, that should be called last in middleware chain
- *
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @param callback $next
- * @return ResponseInterface
+ * @expectedException \InvalidArgumentException
*/
- public function getMiddlewareCalledLast(ServerRequestInterface $request, ResponseInterface $response, $next)
+ public function testAddInvalidMiddleware()
{
- $response->testMiddlewareCalls[] = 'last';
- return $next($request, $response);
+ $router = new Router($this->createMock(Routes::class));
+ $router->add('foo bar zoo');
}
/**
- * Expect 'not found' response
- *
- * @param ResponseInterface
+ * Test executing router with middlewares chain (test only execution order)
*/
- public function expectNotFound(ResponseInterface $response)
+ public function testRunMiddlewares()
{
- $stream = $this->createMock(StreamInterface::class);
- $stream->expects($this->once())->method('rewind');
- $stream->expects($this->once())->method('write')->with($this->equalTo('Not Found'));
+ $route = $this->createMock(Route::class);
- $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());
- }
+ $request = $this->createMock(ServerRequestInterface::class);
+ $request->expects($this->once())->method('withAttribute')->with('route')->willReturn($request);
+ $requestOne = $this->createMock(ServerRequestInterface::class);
+ $requestTwo = $this->createMock(ServerRequestInterface::class);
+
+ $response = $this->createMock(ResponseInterface::class);
+ $responseOne = $this->createMock(ResponseInterface::class);
+ $responseTwo = $this->createMock(ResponseInterface::class);
+ $finalResponse = $this->createMock(ResponseInterface::class);
+
+ $middlewareOne = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock();
+ $middlewareOne->expects($this->once())->method('__invoke')->id('one')
+ ->with($request, $response, $this->isInstanceOf(Closure::class))
+ ->will($this->returnCallback(function($a, $b, $next) use ($requestOne, $responseOne) {
+ return $next($requestOne, $responseOne);
+ }));
+
+ $middlewareTwo = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock();
+ $middlewareTwo->expects($this->once())->method('__invoke')->id('two')->after('one')
+ ->with($request, $response, $this->isInstanceOf(Closure::class))
+ ->will($this->returnCallback(function($a, $b, $next) use ($requestTwo, $responseTwo) {
+ return $next($requestTwo, $responseTwo);
+ }));
+
+ $runner = $this->createCallbackMock($this->once(), [$requestTwo, $responseTwo], $finalResponse);
+ $factory = $this->createCallbackMock($this->once(), [$route], $runner);
+
+ $routes = $this->createMock(Routes::class);
+ $routes->expects($this->once())->method('getRoute')->with($request)->willReturn($route);
- /**
- * Expect that request will return given route
- *
- * @param ServerRequestInterface $request
- * @param Route $route
- */
- public function expectRequestRoute(ServerRequestInterface $request, $route)
- {
- $request->expects($this->once())->method('getAttribute')->with($this->equalTo('route'))->will($this->returnValue($route));
+ $router = new Router($routes);
+ $router->setFactory($factory);
+
+ $router->add($middlewareOne);
+ $router->add($middlewareTwo);
+
+ $result = $router($request, $response);
+
+ $this->assertSame($finalResponse, $result);
}
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..af5e7b3
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,4 @@
+<?php
+
+require_once('vendor/autoload.php');
+require_once('support/TestHelpers.php');
diff --git a/tests/support/TestHelpers.php b/tests/support/TestHelpers.php
new file mode 100644
index 0000000..e4fdff7
--- /dev/null
+++ b/tests/support/TestHelpers.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Jasny\Router;
+
+use PHPUnit_Framework_MockObject_Matcher_Invocation as Invocation;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+
+/**
+ * Helper methods for PHPUnit tests
+ */
+trait TestHelpers
+{
+ /**
+ * Create mock for next callback
+ *
+ * @param Invocation $matcher
+ * @param array $with With arguments
+ * @param mixed $return
+ * @return MockObject
+ */
+ protected function createCallbackMock(Invocation $matcher, $with = [], $return = null)
+ {
+ $callback = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock();
+ $callback->expects($matcher)->method('__invoke')
+ ->with(...$with)
+ ->willReturn($return);
+
+ return $callback;
+ }
+}