Commit c5c09ac1 authored by Emiliano Balbuena's avatar Emiliano Balbuena

(wip)(test): Router spec tests

1 merge request!342WIP: (feat): Modernize Router (&75)
Pipeline #101056855 passed with stages
in 6 minutes and 58 seconds
......@@ -70,7 +70,7 @@ class Router
->withUri(
(new Uri($uri))
->withHost($host)
); // TODO: Ensure it works with reverse proxy
);
$response = $this->dispatcher
->pipe(new Kernel\ContentNegotiationMiddleware())
......
......@@ -20,6 +20,15 @@ class AdminMiddleware implements MiddlewareInterface
/** @var string */
protected $attributeName = '_user';
/** @var callable */
private $xsrfValidateRequest;
public function __construct(
$xsrfValidateRequest = null
) {
$this->xsrfValidateRequest = $xsrfValidateRequest ?: [XSRF::class, 'validateRequest'];
}
/**
* @param string $attributeName
* @return AdminMiddleware
......@@ -46,7 +55,7 @@ class AdminMiddleware implements MiddlewareInterface
{
if (
!$request->getAttribute($this->attributeName) ||
!XSRF::validateRequest()
!call_user_func($this->xsrfValidateRequest)
) {
throw new UnauthorizedException();
}
......
......@@ -18,6 +18,15 @@ class LoggedInMiddleware implements MiddlewareInterface
/** @var string */
protected $attributeName = '_user';
/** @var callable */
private $xsrfValidateRequest;
public function __construct(
$xsrfValidateRequest = null
) {
$this->xsrfValidateRequest = $xsrfValidateRequest ?: [XSRF::class, 'validateRequest'];
}
/**
* @param string $attributeName
* @return LoggedInMiddleware
......@@ -43,7 +52,7 @@ class LoggedInMiddleware implements MiddlewareInterface
{
if (
!$request->getAttribute($this->attributeName) ||
!XSRF::validateRequest()
!call_user_func($this->xsrfValidateRequest)
) {
throw new UnauthorizedException();
}
......
......@@ -17,6 +17,25 @@ use Zend\Diactoros\Stream;
class Fallback
{
/** @var string[] */
const ALLOWED = [
'/api/v1/',
'/api/v2/',
'/emails/',
'/fs/v1',
'/oauth2/',
'/checkout',
'/deeplinks',
'/icon',
'/sitemap',
'/sitemaps',
'/thumbProxy',
'/archive',
'/wall',
'/not-supported',
'/apple-app-site-association',
];
/** @var Config */
protected $config;
......@@ -36,28 +55,10 @@ class Fallback
*/
public function shouldRoute(string $route): bool
{
$allowed = [
'/api/v1/',
'/api/v2/',
'/emails/',
'/fs/v1',
'/oauth2/',
'/checkout',
'/deeplinks',
'/icon',
'/sitemap',
'/sitemaps',
'/thumbProxy',
'/archive',
'/wall',
'/not-supported',
'/apple-app-site-association',
];
$route = sprintf("/%s", ltrim($route, '/'));
$shouldFallback = false;
foreach ($allowed as $allowedRoute) {
foreach (static::ALLOWED as $allowedRoute) {
if (stripos($route, $allowedRoute) === 0) {
$shouldFallback = true;
break;
......
......@@ -91,6 +91,7 @@ class Route
}
$route = sprintf("/%s/%s", trim($this->getPrefix(), '/'), trim($route, '/'));
$route = trim($route, '/');
foreach ($methods as $method) {
$this->registry->register($method, $route, $binding, $this->middleware);
......
......@@ -2,14 +2,116 @@
namespace Spec\Minds\Core\Router\Middleware;
use Minds\Core\Router\Exceptions\ForbiddenException;
use Minds\Core\Router\Exceptions\UnauthorizedException;
use Minds\Core\Router\Middleware\AdminMiddleware;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class AdminMiddlewareSpec extends ObjectBehavior
{
public function let()
{
$xsrfValidateRequest = function () {
/** XSRF::validateRequest() */
return true;
};
$this->beConstructedWith($xsrfValidateRequest);
}
public function it_is_initializable()
{
$this->shouldHaveType(AdminMiddleware::class);
}
public function it_should_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
User $user
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$user->isAdmin()
->shouldBeCalled()
->willReturn(true);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->setAttributeName('_phpspec_user')
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_throw_unauthorized_if_no_user_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn(null);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(UnauthorizedException::class)
->duringProcess($request, $handler);
}
public function it_should_throw_unauthorized_if_xsrf_check_fail_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
User $user
) {
$this->beConstructedWith(function () {
/** XSRF::validateRequest() */
return false;
});
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(UnauthorizedException::class)
->duringProcess($request, $handler);
}
public function it_should_throw_forbidden_if_not_an_admin_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
User $user
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$user->isAdmin()
->shouldBeCalled()
->willReturn(false);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(ForbiddenException::class)
->duringProcess($request, $handler);
}
}
......@@ -5,6 +5,9 @@ namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\ContentNegotiationMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ContentNegotiationMiddlewareSpec extends ObjectBehavior
{
......@@ -12,4 +15,72 @@ class ContentNegotiationMiddlewareSpec extends ObjectBehavior
{
$this->shouldHaveType(ContentNegotiationMiddleware::class);
}
public function it_should_process_and_mark_as_json(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getHeader('Accept')
->shouldBeCalled()
->willReturn(['text/json; utf=8,application/json']);
$request
->withAttribute('accept', 'json')
->shouldBeCalled()
->willReturn($request);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_process_and_mark_as_html(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getHeader('Accept')
->shouldBeCalled()
->willReturn(['text/html; utf=8', 'application/xhtml+xml']);
$request
->withAttribute('accept', 'html')
->shouldBeCalled()
->willReturn($request);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_process_and_leave_unmarked(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getHeader('Accept')
->shouldBeCalled()
->willReturn(['image/png']);
$request
->withAttribute('accept', Argument::cetera())
->shouldNotBeCalled();
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
}
......@@ -5,6 +5,10 @@ namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\CorsMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\EmptyResponse;
class CorsMiddlewareSpec extends ObjectBehavior
{
......@@ -12,4 +16,39 @@ class CorsMiddlewareSpec extends ObjectBehavior
{
$this->shouldHaveType(CorsMiddleware::class);
}
public function it_should_process_and_return_empty_response_if_options(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getMethod()
->shouldBeCalled()
->willReturn('OPTIONS');
$request->getHeaderLine('Origin')
->shouldBeCalled()
->willReturn('https://phpspec.test');
$this
->process($request, $handler)
->shouldBeAnInstanceOf(EmptyResponse::class);
}
public function it_should_process_and_passthru_if_not_options(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$request->getMethod()
->shouldBeCalled()
->willReturn('GET');
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->process($request, $handler)
->shouldReturn($response);
}
}
......@@ -5,6 +5,10 @@ namespace Spec\Minds\Core\Router\Middleware\Kernel;
use Minds\Core\Router\Middleware\Kernel\EmptyResponseMiddleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
class EmptyResponseMiddlewareSpec extends ObjectBehavior
{
......@@ -12,4 +16,30 @@ class EmptyResponseMiddlewareSpec extends ObjectBehavior
{
$this->shouldHaveType(EmptyResponseMiddleware::class);
}
public function it_should_process_and_return_an_html_response(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getAttribute('accept')
->shouldBeCalled()
->willReturn('html');
$this
->process($request, $handler)
->shouldBeAnInstanceOf(HtmlResponse::class);
}
public function it_should_process_and_return_a_json_response(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getAttribute('accept')
->shouldBeCalled()
->willReturn('json');
$this
->process($request, $handler)
->shouldBeAnInstanceOf(JsonResponse::class);
}
}
......@@ -2,14 +2,90 @@
namespace Spec\Minds\Core\Router\Middleware;
use Minds\Core\Router\Exceptions\UnauthorizedException;
use Minds\Core\Router\Middleware\LoggedInMiddleware;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class LoggedInMiddlewareSpec extends ObjectBehavior
{
public function let()
{
$xsrfValidateRequest = function () {
/** XSRF::validateRequest() */
return true;
};
$this->beConstructedWith($xsrfValidateRequest);
}
public function it_is_initializable()
{
$this->shouldHaveType(LoggedInMiddleware::class);
}
public function it_should_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response,
User $user
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$handler->handle($request)
->shouldBeCalled()
->willReturn($response);
$this
->setAttributeName('_phpspec_user')
->process($request, $handler)
->shouldReturn($response);
}
public function it_should_throw_unauthorized_if_no_user_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn(null);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(UnauthorizedException::class)
->duringProcess($request, $handler);
}
public function it_should_throw_unauthorized_if_xsrf_check_fail_during_process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
User $user
) {
$this->beConstructedWith(function () {
/** XSRF::validateRequest() */
return false;
});
$request->getAttribute('_phpspec_user')
->shouldBeCalled()
->willReturn($user);
$handler->handle($request)
->shouldNotBeCalled();
$this
->setAttributeName('_phpspec_user')
->shouldThrow(UnauthorizedException::class)
->duringProcess($request, $handler);
}
}
......@@ -2,14 +2,54 @@
namespace Spec\Minds\Core\Router\PrePsr7;
use Minds\Core\Config;
use Minds\Core\Router\PrePsr7\Fallback;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class FallbackSpec extends ObjectBehavior
{
/** @var Config */
protected $config;
public function let(
Config $config
) {
$this->config = $config;
$this->beConstructedWith($config);
}
public function it_is_initializable()
{
$this->shouldHaveType(Fallback::class);
}
public function it_should_check_if_should_route()
{
$this
->callOnWrappedObject('shouldRoute', [
Fallback::ALLOWED[0]
])
->shouldReturn(true);
$this
->callOnWrappedObject('shouldRoute', [
Fallback::ALLOWED[count(Fallback::ALLOWED) - 1]
])
->shouldReturn(true);
$this
->callOnWrappedObject('shouldRoute', [
'~**INVALID FALLBACK ROUTE**~?'
])
->shouldReturn(false);
}
/**
* The rest of this class is untestable due to the need of
* instantiating PrePsr7\Router class on-the-fly.
*
* That's all, folks!
*/
}
......@@ -2,14 +2,129 @@
namespace Spec\Minds\Core\Router;
use Closure;
use Exception;
use Minds\Core\Di\Ref;
use Minds\Core\Router\Registry;
use Minds\Core\Router\Route;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Server\MiddlewareInterface;
class RouteSpec extends ObjectBehavior
{
/** @var Registry */
protected $registry;
public function let(
Registry $registry
) {
$this->registry = $registry;
$this->beConstructedWith($registry);
}
public function it_is_initializable()
{
$this->shouldHaveType(Route::class);
}
public function it_should_register(
Ref $ref
) {
$method = Route::ALLOWED_METHODS[0];
$this->registry->register($method, 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register([$method], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_with_prefix_and_middleware(
Ref $ref,
MiddlewareInterface $middleware
) {
$method = Route::ALLOWED_METHODS[0];
$this->registry->register($method, 'prefix/0/phpspec/test/2', $ref, [
$middleware
])
->shouldBeCalled();
$this
->withPrefix('/prefix/0/')
->withMiddleware([$middleware])
->register([$method], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_throw_if_no_known_method_during_register(
Ref $ref
) {
$method = '~**INVALID HTTP METHOD**~';
$this->registry->register($method, Argument::cetera())
->shouldNotBeCalled();
$this
->shouldThrow(new Exception('Invalid method'))
->duringRegister([$method], '/phpspec/test/2/', $ref);
}
public function it_should_register_all(
Ref $ref
) {
$this->registry->register(Argument::type('string'), 'phpspec/test/2', $ref, [])
->shouldBeCalledTimes(count(Route::ALLOWED_METHODS));
$this
->all('/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_get(
Ref $ref
) {
$this->registry->register('get', 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register(['get'], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_post(
Ref $ref
) {
$this->registry->register('post', 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register(['post'], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_put(
Ref $ref
) {
$this->registry->register('put', 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register(['put'], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
public function it_should_register_delete(
Ref $ref
) {
$this->registry->register('delete', 'phpspec/test/2', $ref, [])
->shouldBeCalled();
$this
->register(['delete'], '/phpspec/test/2/', $ref)
->shouldReturn(true);
}
}
......@@ -2,12 +2,36 @@
namespace Spec\Minds\Core;
use Minds\Core\Features\Manager as Features;
use Minds\Core\Router;
use Minds\Core\Router\Dispatcher;
use Minds\Core\Router\PrePsr7\Fallback;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class RouterSpec extends ObjectBehavior
{
/** @var Dispatcher */
protected $dispatcher;
/** @var Features */
protected $features;
/** @var Fallback */
protected $fallback;
public function let(
Dispatcher $dispatcher,
Features $features,
Fallback $fallback
) {
$this->dispatcher = $dispatcher;
$this->features = $features;
$this->fallback = $fallback;
$this->beConstructedWith($dispatcher, $features, $fallback);
}
public function it_is_initializable()
{
$this->shouldHaveType(Router::class);
......
Please register or to comment