From 953cae0de1f0e1f23457c7e9e8385b77c4ef0de6 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Wed, 12 Jun 2019 22:57:38 +0200 Subject: [PATCH] Refactor tests to shorten HTTP requests Multiple tests now provide JSON request bodies, and others copy cookies from previous responses, so let's provide convenient helpers for these. --- framework/core/tests/integration/TestCase.php | 70 ++++++++ .../csrf_protection/RequireCsrfTokenTest.php | 161 +++++++----------- 2 files changed, 128 insertions(+), 103 deletions(-) diff --git a/framework/core/tests/integration/TestCase.php b/framework/core/tests/integration/TestCase.php index 2d295d11d..9fa641df0 100644 --- a/framework/core/tests/integration/TestCase.php +++ b/framework/core/tests/integration/TestCase.php @@ -11,8 +11,13 @@ namespace Flarum\Tests\integration; +use Dflydev\FigCookies\SetCookie; use Flarum\Foundation\InstalledSite; use Illuminate\Database\ConnectionInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Zend\Diactoros\CallbackStream; +use Zend\Diactoros\ServerRequest; abstract class TestCase extends \PHPUnit\Framework\TestCase { @@ -88,4 +93,69 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase // And finally, turn on foreign key checks again. $this->database()->getSchemaBuilder()->enableForeignKeyConstraints(); } + + /** + * Send a full HTTP request through Flarum's middleware stack. + */ + protected function send(ServerRequestInterface $request): ResponseInterface + { + return $this->server->handle($request); + } + + /** + * Build a HTTP request that can be passed through middleware. + * + * This method simplifies building HTTP request for use in our HTTP-level + * integration tests. It provides options for all features repeatedly being + * used in those tests. + * + * @param string $method + * @param string $path + * @param array $options + * An array of optional request properties. + * Currently supported: + * - "json" should point to a JSON-serializable object that will be + * serialized and used as request body. The corresponding Content-Type + * header will be set automatically. + * - "cookiesFrom" should hold a response object from a previous HTTP + * interaction. All cookies returned from the server in that response + * (via the "Set-Cookie" header) will be copied to the cookie params of + * the new request. + * @return ServerRequestInterface + */ + protected function request(string $method, string $path, array $options = []): ServerRequestInterface + { + $request = new ServerRequest([], [], $path, $method); + + // Do we want a JSON request body? + if (isset($options['json'])) { + $request = $request + ->withHeader('Content-Type', 'application/json') + ->withBody( + new CallbackStream(function () use ($options) { + return json_encode($options['json']); + }) + ); + } + + // Let's copy the cookies from a previous response + if (isset($options['cookiesFrom'])) { + /** @var ResponseInterface $previousResponse */ + $previousResponse = $options['cookiesFrom']; + + $cookies = array_reduce( + $previousResponse->getHeader('Set-Cookie'), + function ($memo, $setCookieString) { + $setCookie = SetCookie::fromSetCookieString($setCookieString); + $memo[$setCookie->getName()] = $setCookie->getValue(); + return $memo; + }, + [] + ); + + $request = $request->withCookieParams($cookies); + } + + return $request; + } } diff --git a/framework/core/tests/integration/api/csrf_protection/RequireCsrfTokenTest.php b/framework/core/tests/integration/api/csrf_protection/RequireCsrfTokenTest.php index e0cc6755d..a3cbf4dd6 100644 --- a/framework/core/tests/integration/api/csrf_protection/RequireCsrfTokenTest.php +++ b/framework/core/tests/integration/api/csrf_protection/RequireCsrfTokenTest.php @@ -11,12 +11,9 @@ namespace Flarum\Tests\integration\api\csrf_protection; -use Dflydev\FigCookies\SetCookie; use Flarum\Foundation\Application; use Flarum\Tests\integration\RetrievesAuthorizedUsers; use Flarum\Tests\integration\TestCase; -use Zend\Diactoros\CallbackStream; -use Zend\Diactoros\ServerRequest; class RequireCsrfTokenTest extends TestCase { @@ -54,31 +51,23 @@ class RequireCsrfTokenTest extends TestCase */ public function error_when_doing_cookie_auth_without_csrf_token() { - $auth = $this->server->handle( - (new ServerRequest([], [], '/login', 'POST')) - ->withBody(new CallbackStream(function () { - return '{"identification": "admin", "password": "password"}'; - })) - ->withHeader('Content-Type', 'application/json') + $auth = $this->send( + $this->request( + 'POST', '/login', + [ + 'json' => ['identification' => 'admin', 'password' => 'password'], + ] + ) ); - $cookies = array_reduce( - $auth->getHeader('Set-Cookie'), - function ($memo, $setCookieString) { - $setCookie = SetCookie::fromSetCookieString($setCookieString); - $memo[$setCookie->getName()] = $setCookie->getValue(); - return $memo; - }, - [] - ); - - $response = $this->server->handle( - (new ServerRequest([], [], '/api/settings', 'POST')) - ->withBody(new CallbackStream(function () { - return '{"mail_driver": "log"}'; - })) - ->withCookieParams($cookies) - ->withHeader('Content-Type', 'application/json') + $response = $this->send( + $this->request( + 'POST', '/api/settings', + [ + 'cookiesFrom' => $auth, + 'json' => ['mail_driver' => 'log'], + ] + ) ); // Response should be "HTTP 400 Bad Request" @@ -99,50 +88,32 @@ class RequireCsrfTokenTest extends TestCase */ public function cookie_auth_succeeds_with_csrf_token_in_header() { - $initial = $this->server->handle( - (new ServerRequest([], [], '/', 'GET')) + $initial = $this->send( + $this->request('GET', '/') ); $token = $initial->getHeaderLine('X-CSRF-Token'); - $cookies = array_reduce( - $initial->getHeader('Set-Cookie'), - function ($memo, $setCookieString) { - $setCookie = SetCookie::fromSetCookieString($setCookieString); - $memo[$setCookie->getName()] = $setCookie->getValue(); - return $memo; - }, - [] - ); - $auth = $this->server->handle( - (new ServerRequest([], [], '/login', 'POST')) - ->withBody(new CallbackStream(function () { - return '{"identification": "admin", "password": "password"}'; - })) - ->withCookieParams($cookies) - ->withHeader('Content-Type', 'application/json') - ->withHeader('X-CSRF-Token', $token) + $auth = $this->send( + $this->request( + 'POST', '/login', + [ + 'cookiesFrom' => $initial, + 'json' => ['identification' => 'admin', 'password' => 'password'], + ] + )->withHeader('X-CSRF-Token', $token) ); $token = $auth->getHeaderLine('X-CSRF-Token'); - $cookies = array_reduce( - $auth->getHeader('Set-Cookie'), - function ($memo, $setCookieString) { - $setCookie = SetCookie::fromSetCookieString($setCookieString); - $memo[$setCookie->getName()] = $setCookie->getValue(); - return $memo; - }, - [] - ); - $response = $this->server->handle( - (new ServerRequest([], [], '/api/settings', 'POST')) - ->withBody(new CallbackStream(function () { - return '{"mail_driver": "log"}'; - })) - ->withCookieParams($cookies) - ->withHeader('Content-Type', 'application/json') - ->withHeader('X-CSRF-Token', $token) + $response = $this->send( + $this->request( + 'POST', '/api/settings', + [ + 'cookiesFrom' => $auth, + 'json' => ['mail_driver' => 'log'], + ] + )->withHeader('X-CSRF-Token', $token) ); // Successful response? @@ -160,48 +131,32 @@ class RequireCsrfTokenTest extends TestCase */ public function cookie_auth_succeeds_with_csrf_token_in_body() { - $initial = $this->server->handle( - (new ServerRequest([], [], '/', 'GET')) + $initial = $this->send( + $this->request('GET', '/') ); $token = $initial->getHeaderLine('X-CSRF-Token'); - $cookies = array_reduce( - $initial->getHeader('Set-Cookie'), - function ($memo, $setCookieString) { - $setCookie = SetCookie::fromSetCookieString($setCookieString); - $memo[$setCookie->getName()] = $setCookie->getValue(); - return $memo; - }, - [] - ); - $auth = $this->server->handle( - (new ServerRequest([], [], '/login', 'POST')) - ->withBody(new CallbackStream(function () use ($token) { - return '{"identification": "admin", "password": "password", "csrfToken": "'.$token.'"}'; - })) - ->withCookieParams($cookies) - ->withHeader('Content-Type', 'application/json') + $auth = $this->send( + $this->request( + 'POST', '/login', + [ + 'cookiesFrom' => $initial, + 'json' => ['identification' => 'admin', 'password' => 'password', 'csrfToken' => $token], + ] + ) ); $token = $auth->getHeaderLine('X-CSRF-Token'); - $cookies = array_reduce( - $auth->getHeader('Set-Cookie'), - function ($memo, $setCookieString) { - $setCookie = SetCookie::fromSetCookieString($setCookieString); - $memo[$setCookie->getName()] = $setCookie->getValue(); - return $memo; - }, - [] - ); - $response = $this->server->handle( - (new ServerRequest([], [], '/api/settings', 'POST')) - ->withBody(new CallbackStream(function () use ($token) { - return '{"mail_driver": "log", "csrfToken": "'.$token.'"}'; - })) - ->withCookieParams($cookies) - ->withHeader('Content-Type', 'application/json') + $response = $this->send( + $this->request( + 'POST', '/api/settings', + [ + 'cookiesFrom' => $auth, + 'json' => ['mail_driver' => 'log', 'csrfToken' => $token], + ] + ) ); // Successful response? @@ -219,13 +174,13 @@ class RequireCsrfTokenTest extends TestCase */ public function master_api_token_does_not_need_csrf_token() { - $response = $this->server->handle( - (new ServerRequest([], [], '/api/settings', 'POST')) - ->withBody(new CallbackStream(function () { - return '{"mail_driver": "log"}'; - })) - ->withHeader('Authorization', 'Token superadmin') - ->withHeader('Content-Type', 'application/json') + $response = $this->send( + $this->request( + 'POST', '/api/settings', + [ + 'json' => ['mail_driver' => 'log'], + ] + )->withHeader('Authorization', 'Token superadmin') ); // Successful response?