From 0ecaf94ad3edb883afcf9f754bac494c0ad1e95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BCrk?= <stefan@buerk.tech> Date: Thu, 3 Mar 2022 00:55:04 +0100 Subject: [PATCH] [TASK] Add request tests for DELETE,PATCH,PUT AND POST with data The testing-framework with FE requests using sub requests is now able to handle DELETE,PATCH,PUT and POST requests. The situation in testing-framework is not the final solution, though. The patch adds a test to verify if testing-framework request details are properly received in the FE application. This not only adds a use case to the core for these kind of requests, but also ensures that we don't break this detail again when testing-framework internal handling is further refactored. Also needs a testing-framework bugfix in v11: > composer req --dev typo3/testing-framework:^6.16.2 Resolves: #97084 Releases: main, 11.5 Change-Id: I8268625d4b439f1657168d6b9c9a3878b36477bd Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73768 Tested-by: core-ci <typo3@b13.com> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> --- composer.json | 5 +- composer.lock | 14 +- .../Classes/Middleware/RequestMirror.php | 50 ++++ .../Configuration/RequestMiddlewares.php | 16 ++ .../test_request_mirror/ext_emconf.php | 22 ++ .../InternalRequestDataMappingTest.php | 247 ++++++++++++++++++ 6 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Classes/Middleware/RequestMirror.php create mode 100644 typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Configuration/RequestMiddlewares.php create mode 100644 typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/ext_emconf.php create mode 100644 typo3/sysext/frontend/Tests/Functional/Request/InternalRequestDataMappingTest.php diff --git a/composer.json b/composer.json index fe15a6daac70..b0be852da770 100644 --- a/composer.json +++ b/composer.json @@ -121,7 +121,7 @@ "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5.10", "typo3/cms-styleguide": "~11.5.4", - "typo3/testing-framework": "^6.16.1" + "typo3/testing-framework": "^6.16.2" }, "suggest": { "ext-gd": "GDlib/Freetype is required for building images with text (GIFBUILDER) and can also be used to scale images", @@ -290,7 +290,8 @@ "typo3/sysext/extbase/Tests/UnitDeprecated/Object/Container/Fixtures/", "typo3/sysext/extbase/Tests/Functional/Fixtures/", "typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/", - "typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/fluid_test/Classes/" + "typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/fluid_test/Classes/", + "typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Classes/" ], "files": [ "typo3/sysext/extbase/Tests/Fixture/TxClassWithGettersAndSetters.php" diff --git a/composer.lock b/composer.lock index f7d360962a38..ec1cb29d48fe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ffb1f9b636cecefa553166f2b6d7f5a5", + "content-hash": "e7e9c0414caf6cf8ccbfae3557b28b3c", "packages": [ { "name": "bacon/bacon-qr-code", @@ -9008,16 +9008,16 @@ }, { "name": "typo3/testing-framework", - "version": "6.16.1", + "version": "6.16.2", "source": { "type": "git", "url": "https://github.com/TYPO3/testing-framework.git", - "reference": "774d2123c907261b79f554c98f25f93ed2b0ee8c" + "reference": "54ac7b82c48bc70a9dbb43fbc3f46a59ea28583a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/774d2123c907261b79f554c98f25f93ed2b0ee8c", - "reference": "774d2123c907261b79f554c98f25f93ed2b0ee8c", + "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/54ac7b82c48bc70a9dbb43fbc3f46a59ea28583a", + "reference": "54ac7b82c48bc70a9dbb43fbc3f46a59ea28583a", "shasum": "" }, "require": { @@ -9076,9 +9076,9 @@ "support": { "general": "https://typo3.org/support/", "issues": "https://github.com/TYPO3/testing-framework/issues", - "source": "https://github.com/TYPO3/testing-framework/tree/6.16.1" + "source": "https://github.com/TYPO3/testing-framework/tree/6.16.2" }, - "time": "2022-03-04T16:41:08+00:00" + "time": "2022-03-04T20:20:41+00:00" } ], "aliases": [], diff --git a/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Classes/Middleware/RequestMirror.php b/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Classes/Middleware/RequestMirror.php new file mode 100644 index 000000000000..a1120402ebda --- /dev/null +++ b/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Classes/Middleware/RequestMirror.php @@ -0,0 +1,50 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\RequestMirror\Middleware; + +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class RequestMirror implements MiddlewareInterface +{ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if ($request->getUri()->getPath() !== '/request-mirror') { + return $handler->handle($request); + } + return GeneralUtility::makeInstance(ResponseFactoryInterface::class) + ->createResponse(200, '') + ->withHeader('Content-type', 'application/json') + ->withBody(GeneralUtility::makeInstance(StreamFactoryInterface::class)->createStream(\json_encode( + [ + 'uri' => $request->getUri(), + 'method' => $request->getMethod(), + 'headers' => $request->getHeaders(), + 'queryParams' => $request->getQueryParams(), + 'parsedBody' => $request->getParsedBody(), + 'body' => (string)$request->getBody(), + ], + JSON_THROW_ON_ERROR + ))); + } +} diff --git a/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Configuration/RequestMiddlewares.php b/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Configuration/RequestMiddlewares.php new file mode 100644 index 000000000000..49b801a49722 --- /dev/null +++ b/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/Configuration/RequestMiddlewares.php @@ -0,0 +1,16 @@ +<?php + +return [ + 'frontend' => [ + 'typo3/request-mirror' => [ + 'target' => \TYPO3\RequestMirror\Middleware\RequestMirror::class, + 'after' => [ + 'typo3/cms-core/normalized-params-attribute', + ], + 'before' => [ + 'typo3/cms-frontend/site', + 'typo3/cms-frontend/eid', + ], + ], + ], +]; diff --git a/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/ext_emconf.php b/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/ext_emconf.php new file mode 100644 index 000000000000..c5215fb1d2f2 --- /dev/null +++ b/typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror/ext_emconf.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +$EM_CONF[$_EXTKEY] = [ + 'title' => 'RequestMirror', + 'description' => 'RequestMirror', + 'category' => 'example', + 'version' => '11.0.0', + 'state' => 'beta', + 'author' => 'Stefan Bürk', + 'author_email' => 'stefan@buerk.tech', + 'author_company' => '', + 'constraints' => [ + 'depends' => [ + 'typo3' => '11.0.0-11.99.99', + 'frontend' => '11.0.0-11.99.99', + ], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/typo3/sysext/frontend/Tests/Functional/Request/InternalRequestDataMappingTest.php b/typo3/sysext/frontend/Tests/Functional/Request/InternalRequestDataMappingTest.php new file mode 100644 index 000000000000..28711fb2e56f --- /dev/null +++ b/typo3/sysext/frontend/Tests/Functional/Request/InternalRequestDataMappingTest.php @@ -0,0 +1,247 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Frontend\Tests\Functional\Request; + +use Psr\Http\Message\StreamFactoryInterface; +use TYPO3\CMS\Core\Http\StreamFactory; +use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class InternalRequestDataMappingTest extends FunctionalTestCase +{ + protected $testExtensionsToLoad = [ + 'typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_request_mirror', + ]; + + public function ensureRequestMappingWorksDataProvider(): \Generator + { + yield 'POST parsedBody(_POST) as parsedBody' => [ + 'uri' => 'https://acme.com/request-mirror', + 'method' => 'POST', + 'parsedBody' => ['param1' => 'value1'], + 'headers' => [ + 'Content-type' => 'application/x-www-form-urlencoded', + ], + 'body' => null, + 'expectedJsonKeyValues' => [ + 'method' => 'POST', + 'parsedBody' => ['param1' => 'value1'], + 'queryParams' => [], + 'body' => '', + 'headers' => [ + 'Content-type' => [ + 'application/x-www-form-urlencoded', + ], + 'host' => [ + 'acme.com', + ], + ], + ], + ]; + yield 'PATCH body as parsedBody' => [ + 'uri' => 'https://acme.com/request-mirror', + 'method' => 'PATCH', + 'parsedBody' => null, + 'headers' => [ + 'Content-type' => 'application/x-www-form-urlencoded', + ], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'expectedJsonKeyValues' => [ + 'method' => 'PATCH', + 'parsedBody' => ['param1' => 'value1'], + 'queryParams' => [], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'headers' => [ + 'Content-type' => [ + 'application/x-www-form-urlencoded', + ], + 'host' => [ + 'acme.com', + ], + ], + ], + ]; + yield 'PUT body as parsedBody' => [ + 'uri' => 'https://acme.com/request-mirror', + 'method' => 'PUT', + 'parsedBody' => null, + 'headers' => [ + 'Content-type' => 'application/x-www-form-urlencoded', + ], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'expectedJsonKeyValues' => [ + 'method' => 'PUT', + 'parsedBody' => ['param1' => 'value1'], + 'queryParams' => [], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'headers' => [ + 'Content-type' => [ + 'application/x-www-form-urlencoded', + ], + 'host' => [ + 'acme.com', + ], + ], + ], + ]; + yield 'DELETE body as parsedBody' => [ + 'uri' => 'https://acme.com/request-mirror', + 'method' => 'DELETE', + 'parsedBody' => null, + 'headers' => [ + 'Content-type' => 'application/x-www-form-urlencoded', + ], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'expectedJsonKeyValues' => [ + 'method' => 'DELETE', + 'parsedBody' => ['param1' => 'value1'], + 'queryParams' => [], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'headers' => [ + 'Content-type' => [ + 'application/x-www-form-urlencoded', + ], + 'host' => [ + 'acme.com', + ], + ], + ], + ]; + yield 'POST parsedBody(_POST) as parsedBody and queryParams' => [ + 'uri' => 'https://acme.com/request-mirror?queryParam1=queryValue1', + 'method' => 'POST', + 'parsedBody' => ['param1' => 'value1'], + 'headers' => [ + 'Content-type' => 'application/x-www-form-urlencoded', + ], + 'body' => null, + 'expectedJsonKeyValues' => [ + 'method' => 'POST', + 'parsedBody' => ['param1' => 'value1'], + 'queryParams' => ['queryParam1' => 'queryValue1'], + 'body' => '', + 'headers' => [ + 'Content-type' => [ + 'application/x-www-form-urlencoded', + ], + 'host' => [ + 'acme.com', + ], + ], + ], + ]; + yield 'PATCH body as parsedBody and queryParams' => [ + 'uri' => 'https://acme.com/request-mirror?queryParam1=queryValue1', + 'method' => 'PATCH', + 'parsedBody' => null, + 'headers' => [ + 'Content-type' => 'application/x-www-form-urlencoded', + ], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'expectedJsonKeyValues' => [ + 'method' => 'PATCH', + 'parsedBody' => ['param1' => 'value1'], + 'queryParams' => ['queryParam1' => 'queryValue1'], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'headers' => [ + 'Content-type' => [ + 'application/x-www-form-urlencoded', + ], + 'host' => [ + 'acme.com', + ], + ], + ], + ]; + yield 'PUT body as parsedBody and queryParams' => [ + 'uri' => 'https://acme.com/request-mirror?queryParam1=queryValue1', + 'method' => 'PUT', + 'parsedBody' => null, + 'headers' => [ + 'Content-type' => 'application/x-www-form-urlencoded', + ], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'expectedJsonKeyValues' => [ + 'method' => 'PUT', + 'parsedBody' => ['param1' => 'value1'], + 'queryParams' => ['queryParam1' => 'queryValue1'], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'headers' => [ + 'Content-type' => [ + 'application/x-www-form-urlencoded', + ], + 'host' => [ + 'acme.com', + ], + ], + ], + ]; + yield 'DELETE body as parsedBody and queryParams' => [ + 'uri' => 'https://acme.com/request-mirror?queryParam1=queryValue1', + 'method' => 'DELETE', + 'parsedBody' => null, + 'headers' => [ + 'Content-type' => 'application/x-www-form-urlencoded', + ], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'expectedJsonKeyValues' => [ + 'method' => 'DELETE', + 'parsedBody' => ['param1' => 'value1'], + 'queryParams' => ['queryParam1' => 'queryValue1'], + 'body' => \GuzzleHttp\Psr7\Query::build(['param1' => 'value1']), + 'headers' => [ + 'Content-type' => [ + 'application/x-www-form-urlencoded', + ], + 'host' => [ + 'acme.com', + ], + ], + ], + ]; + } + + /** + * Verify testing-framework request details are properly received + * in the application by adding an extension with a middleware. + * + * @test + * @dataProvider ensureRequestMappingWorksDataProvider + */ + public function ensureRequestMappingWorks(string $uri, string $method, ?array $parsedBody, array $headers, ?string $body, array $expectedJsonKeyValues): void + { + $request = (new InternalRequest($uri)) + ->withMethod($method) + ->withParsedBody($parsedBody); + foreach ($headers as $headerName => $headerValue) { + $request = $request->withAddedHeader($headerName, $headerValue); + } + if ($body) { + /** @var StreamFactory $streamFactory */ + $streamFactory = $this->get(StreamFactoryInterface::class); + $request = $request->withBody($streamFactory->createStream($body)); + } + + $response = $this->executeFrontendSubRequest($request); + self::assertSame(200, $response->getStatusCode()); + $json = json_decode((string)$response->getBody(), true); + foreach ($expectedJsonKeyValues as $expectedKey => $expectedValue) { + self::assertSame($expectedValue, $json[$expectedKey] ?? null); + } + } +} -- GitLab