.../EventSubscriber/ResourceResponseSubscriber.php | 3 +-
.../rest/tests/src/Kernel/RequestHandlerTest.php | 247 +--------------
.../ResourceResponseSubscriberTest.php | 334 +++++++++++++++++++++
3 files changed, 336 insertions(+), 248 deletions(-)
diff --git a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
index ba1e3eb..ce77e01 100644
--- a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
+++ b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php
@@ -92,7 +92,7 @@ public function onRespond(FilterResponseEvent $event) {
* @return string
* The response format.
*/
- protected function getResponseFormat(RouteMatchInterface $route_match, Request $request) {
+ public function getResponseFormat(RouteMatchInterface $route_match, Request $request) {
$route = $route_match->getRouteObject();
$acceptable_request_formats = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : [];
$acceptable_content_type_formats = $route->hasRequirement('_content_type_format') ? explode('|', $route->getRequirement('_content_type_format')) : [];
@@ -188,7 +188,6 @@ protected function renderResponseBody(Request $request, ResourceResponseInterfac
* The flattened response.
*/
protected function flattenResponse(ResourceResponseInterface $response) {
- assert('!empty($response->getContent())');
$final_response = ($response instanceof CacheableResponseInterface) ? new CacheableResponse() : new Response();
$final_response->setContent($response->getContent());
$final_response->setStatusCode($response->getStatusCode());
diff --git a/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
index 905b449..336e483 100644
--- a/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
+++ b/core/modules/rest/tests/src/Kernel/RequestHandlerTest.php
@@ -48,11 +48,9 @@ public function setUp() {
}
/**
- * Assert some basic handler method logic.
- *
* @covers ::handle
*/
- public function testBaseHandler() {
+ public function testHandle() {
$request = new Request();
$route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => 'restplugin'], ['_format' => 'json']));
@@ -91,249 +89,6 @@ public function testBaseHandler() {
$this->assertEquals($response, $handler_response);
}
- /**
- * Test that given structured data, the request handler will serialize it.
- *
- * @dataProvider providerTestSerialization
- * @covers ::handle
- */
- public function testSerialization($data, $expected_response = FALSE) {
- $request = new Request();
- $route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => 'restplugin'], ['_format' => 'json']));
-
- $resource = $this->prophesize(StubRequestHandlerResourcePlugin::class);
-
- // Mock the configuration.
- $config = $this->prophesize(RestResourceConfigInterface::class);
- $config->getResourcePlugin()->willReturn($resource->reveal());
- $config->getCacheContexts()->willReturn([]);
- $config->getCacheTags()->willReturn([]);
- $config->getCacheMaxAge()->willReturn(12);
- $this->entityStorage->load('restplugin')->willReturn($config->reveal());
-
- $response = new ResourceResponse($data);
- $resource->get(NULL, $request)
- ->willReturn($response);
- $handler_response = $this->requestHandler->handle($route_match, $request);
- // Content is a serialized version of the data we provided.
- $this->assertEquals($expected_response !== FALSE ? $expected_response : json_encode($data), $handler_response->getContent());
- }
-
- public function providerTestSerialization() {
- return [
- // The default data for \Drupal\rest\ResourceResponse.
- [NULL, ''],
- [''],
- ['string'],
- ['Complex \ string $%^&@ with unicode ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ'],
- [[]],
- [['test']],
- [['test' => 'foobar']],
- [TRUE],
- [FALSE],
- // @todo Not supported. https://www.drupal.org/node/2427811
- // [new \stdClass()],
- // [(object) ['test' => 'foobar']],
- ];
- }
-
- /**
- * @covers ::getResponseFormat
- *
- * Note this does *not* need to test formats being requested that are not
- * accepted by the server, because the routing system would have already
- * prevented those from reaching RequestHandler.
- *
- * @param string[] $methods
- * The HTTP methods to test.
- * @param string[] $supported_formats
- * The supported formats for the REST route to be tested.
- * @param string|false $request_format
- * The value for the ?_format URL query argument, if any.
- * @param string[] $request_headers
- * The request headers to send, if any.
- * @param string|null $request_body
- * The request body to send, if any.
- * @param string|null $expected_response_content_type
- * The expected MIME type of the response, if any.
- * @param string $expected_response_content
- * The expected content of the response.
- *
- * @dataProvider providerTestResponseFormat
- */
- public function testResponseFormat($methods, array $supported_formats, $request_format, array $request_headers, $request_body, $expected_response_content_type, $expected_response_content) {
- $rest_config_name = $this->randomMachineName();
-
- $parameters = [];
- if ($request_format !== FALSE) {
- $parameters['_format'] = $request_format;
- }
-
- foreach ($request_headers as $key => $value) {
- unset($request_headers[$key]);
- $key = strtoupper(str_replace('-', '_', $key));
- $request_headers[$key] = $value;
- }
-
- foreach ($methods as $method) {
- $request = Request::create('/rest/test', $method, $parameters, [], [], $request_headers, $request_body);
- $route_requirement_key_format = $request->isMethodSafe() ? '_format' : '_content_type_format';
- $route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => $rest_config_name], [$route_requirement_key_format => implode('|', $supported_formats)]));
-
- $resource = $this->prophesize(StubRequestHandlerResourcePlugin::class);
-
- // Mock the configuration.
- $config = $this->prophesize(RestResourceConfigInterface::class);
- $config->getFormats($method)->willReturn($supported_formats);
- $config->getResourcePlugin()->willReturn($resource->reveal());
- $config->getCacheContexts()->willReturn([]);
- $config->getCacheTags()->willReturn([]);
- $config->getCacheMaxAge()->willReturn(12);
- $this->entityStorage->load($rest_config_name)->willReturn($config->reveal());
-
- // Mock the resource plugin.
- $response = new ResourceResponse($method !== 'DELETE' ? ['REST' => 'Drupal'] : NULL);
- $resource->getPluginDefinition()->willReturn([]);
- $method_prophecy = new MethodProphecy($resource, strtolower($method), [Argument::any(), $request]);
- $method_prophecy->willReturn($response);
- $resource->addMethodProphecy($method_prophecy);
-
- // Test the request handler.
- $handler_response = $this->requestHandler->handle($route_match, $request);
- $this->assertSame($expected_response_content_type, $handler_response->headers->get('Content-Type'));
- $this->assertEquals($expected_response_content, $handler_response->getContent());
- }
- }
-
- /**
- * @return array
- * 0. methods to test
- * 1. supported formats for route requirements
- * 2. request format
- * 3. request headers
- * 4. request body
- * 5. expected response content type
- * 6. expected response body
- */
- public function providerTestResponseFormat() {
- $json_encoded = Json::encode(['REST' => 'Drupal']);
- $xml_encoded = "\nDrupal\n";
-
- $safe_method_test_cases = [
- 'safe methods: client requested format (JSON)' => [
- // @todo add 'HEAD' in https://www.drupal.org/node/2752325
- ['GET'],
- ['xml', 'json'],
- 'json',
- [],
- NULL,
- 'application/json',
- $json_encoded,
- ],
- 'safe methods: client requested format (XML)' => [
- // @todo add 'HEAD' in https://www.drupal.org/node/2752325
- ['GET'],
- ['xml', 'json'],
- 'xml',
- [],
- NULL,
- 'text/xml',
- $xml_encoded,
- ],
- 'safe methods: client requested no format: response should use the first configured format (JSON)' => [
- // @todo add 'HEAD' in https://www.drupal.org/node/2752325
- ['GET'],
- ['json', 'xml'],
- FALSE,
- [],
- NULL,
- 'application/json',
- $json_encoded,
- ],
- 'safe methods: client requested no format: response should use the first configured format (XML)' => [
- // @todo add 'HEAD' in https://www.drupal.org/node/2752325
- ['GET'],
- ['xml', 'json'],
- FALSE,
- [],
- NULL,
- 'text/xml',
- $xml_encoded,
- ],
- ];
-
- $unsafe_method_bodied_test_cases = [
- 'unsafe methods with response (POST, PATCH): client requested no format, response should use request body format (JSON)' => [
- ['POST', 'PATCH'],
- ['xml', 'json'],
- FALSE,
- ['Content-Type' => 'application/json'],
- $json_encoded,
- 'application/json',
- $json_encoded,
- ],
- 'unsafe methods with response (POST, PATCH): client requested no format, response should use request body format (XML)' => [
- ['POST', 'PATCH'],
- ['xml', 'json'],
- FALSE,
- ['Content-Type' => 'text/xml'],
- $xml_encoded,
- 'text/xml',
- $xml_encoded,
- ],
- 'unsafe methods with response (POST, PATCH): client requested format other than request body format (JSON): response format should use requested format (XML)' => [
- ['POST', 'PATCH'],
- ['xml', 'json'],
- 'xml',
- ['Content-Type' => 'application/json'],
- $json_encoded,
- 'text/xml',
- $xml_encoded,
- ],
- 'unsafe methods with response (POST, PATCH): client requested format other than request body format (XML), but is allowed for the request body (JSON)' => [
- ['POST', 'PATCH'],
- ['xml', 'json'],
- 'json',
- ['Content-Type' => 'text/xml'],
- $xml_encoded,
- 'application/json',
- $json_encoded,
- ],
- ];
-
- $unsafe_method_bodyless_test_cases = [
- 'unsafe methods with response bodies (DELETE): client requested no format, response should have no format' => [
- ['DELETE'],
- ['xml', 'json'],
- FALSE,
- ['Content-Type' => 'application/json'],
- $json_encoded,
- NULL,
- '',
- ],
- 'unsafe methods with response bodies (DELETE): client requested format (XML), response should have no format' => [
- ['DELETE'],
- ['xml', 'json'],
- 'xml',
- ['Content-Type' => 'application/json'],
- $json_encoded,
- NULL,
- '',
- ],
- 'unsafe methods with response bodies (DELETE): client requested format (JSON), response should have no format' => [
- ['DELETE'],
- ['xml', 'json'],
- 'json',
- ['Content-Type' => 'application/json'],
- $json_encoded,
- NULL,
- '',
- ],
- ];
-
- return $safe_method_test_cases + $unsafe_method_bodied_test_cases + $unsafe_method_bodyless_test_cases;
- }
-
}
/**
diff --git a/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php b/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php
new file mode 100644
index 0000000..bb138fe
--- /dev/null
+++ b/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php
@@ -0,0 +1,334 @@
+ 'restplugin'], ['_format' => 'json']));
+
+ $handler_response = new ResourceResponse($data);
+ $resource_response_subscriber = $this->getFunctioningResourceResponseSubscriber($route_match);
+ $event = new FilterResponseEvent(
+ $this->prophesize(HttpKernelInterface::class)->reveal(),
+ $request,
+ HttpKernelInterface::MASTER_REQUEST,
+ $handler_response
+ );
+ $resource_response_subscriber->onRespond($event);
+
+ // Content is a serialized version of the data we provided.
+ $this->assertEquals($expected_response !== FALSE ? $expected_response : json_encode($data), $event->getResponse()->getContent());
+ }
+
+ public function providerTestSerialization() {
+ return [
+ // The default data for \Drupal\rest\ResourceResponse.
+ [NULL, ''],
+ [''],
+ ['string'],
+ ['Complex \ string $%^&@ with unicode ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ'],
+ [[]],
+ [['test']],
+ [['test' => 'foobar']],
+ [TRUE],
+ [FALSE],
+ // @todo Not supported. https://www.drupal.org/node/2427811
+ // [new \stdClass()],
+ // [(object) ['test' => 'foobar']],
+ ];
+ }
+
+ /**
+ * @covers ::getResponseFormat
+ *
+ * Note this does *not* need to test formats being requested that are not
+ * accepted by the server, because the routing system would have already
+ * prevented those from reaching the controller.
+ *
+ * @dataProvider providerTestResponseFormat
+ */
+ public function testResponseFormat($methods, array $supported_formats, $request_format, array $request_headers, $request_body, $expected_response_format, $expected_response_content_type, $expected_response_content) {
+ $parameters = [];
+ if ($request_format !== FALSE) {
+ $parameters['_format'] = $request_format;
+ }
+
+ foreach ($request_headers as $key => $value) {
+ unset($request_headers[$key]);
+ $key = strtoupper(str_replace('-', '_', $key));
+ $request_headers[$key] = $value;
+ }
+
+ foreach ($methods as $method) {
+ $request = Request::create('/rest/test', $method, $parameters, [], [], $request_headers, $request_body);
+ $route_requirement_key_format = $request->isMethodSafe() ? '_format' : '_content_type_format';
+ $route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => $this->randomMachineName()], [$route_requirement_key_format => implode('|', $supported_formats)]));
+
+ $resource_response_subscriber = new ResourceResponseSubscriber(
+ $this->prophesize(SerializerInterface::class)->reveal(),
+ $this->prophesize(RendererInterface::class)->reveal(),
+ $route_match
+ );
+
+ $this->assertSame($expected_response_format, $resource_response_subscriber->getResponseFormat($route_match, $request));
+ }
+ }
+
+ /**
+ * @covers ::onRespond
+ * @covers ::getResponseFormat
+ * @covers ::renderResponseBody
+ * @covers ::flattenResponse
+ *
+ * @dataProvider providerTestResponseFormat
+ */
+ public function testOnRespond($methods, array $supported_formats, $request_format, array $request_headers, $request_body, $expected_response_format, $expected_response_content_type, $expected_response_content) {
+ $rest_config_name = $this->randomMachineName();
+
+ $parameters = [];
+ if ($request_format !== FALSE) {
+ $parameters['_format'] = $request_format;
+ }
+
+ foreach ($request_headers as $key => $value) {
+ unset($request_headers[$key]);
+ $key = strtoupper(str_replace('-', '_', $key));
+ $request_headers[$key] = $value;
+ }
+
+ foreach ($methods as $method) {
+ $request = Request::create('/rest/test', $method, $parameters, [], [], $request_headers, $request_body);
+ $route_requirement_key_format = $request->isMethodSafe() ? '_format' : '_content_type_format';
+ $route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => $rest_config_name], [$route_requirement_key_format => implode('|', $supported_formats)]));
+
+
+ // The RequestHandler must return a ResourceResponseInterface object.
+ $handler_response = new ResourceResponse($method !== 'DELETE' ? ['REST' => 'Drupal'] : NULL);
+ $this->assertInstanceOf(ResourceResponseInterface::class, $handler_response);
+ $this->assertInstanceOf(CacheableResponseInterface::class, $handler_response);
+
+ // The ResourceResponseSubscriber must then generate a response body and
+ // transform it to a plain (Cacheable)Response object.
+ $resource_response_subscriber = $this->getFunctioningResourceResponseSubscriber($route_match);
+ $event = new FilterResponseEvent(
+ $this->prophesize(HttpKernelInterface::class)->reveal(),
+ $request,
+ HttpKernelInterface::MASTER_REQUEST,
+ $handler_response
+ );
+ $resource_response_subscriber->onRespond($event);
+ $final_response = $event->getResponse();
+ $this->assertNotInstanceOf(ResourceResponseInterface::class, $final_response);
+ $this->assertInstanceOf(CacheableResponseInterface::class, $handler_response);
+ $this->assertSame($expected_response_content_type, $handler_response->headers->get('Content-Type'));
+ $this->assertEquals($expected_response_content, $handler_response->getContent());
+ }
+ }
+
+ /**
+ * @return \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
+ */
+ protected function getFunctioningResourceResponseSubscriber(RouteMatchInterface $route_match) {
+ // Create a dummy of the renderer service.
+ $renderer = $this->prophesize(RendererInterface::class);
+ $renderer->executeInRenderContext(Argument::type(RenderContext::class), Argument::type('callable'))
+ ->will(function ($args) {
+ $callable = $args[1];
+ return $callable();
+ });
+
+ // Instantiate the ResourceResponseSubscriber we will test.
+ $resource_response_subscriber = new ResourceResponseSubscriber(
+ new Serializer([], [new JsonEncoder(), new XmlEncoder()]),
+ $renderer->reveal(),
+ $route_match
+ );
+
+ return $resource_response_subscriber;
+ }
+
+ /**
+ * @return array
+ * 0. methods to test
+ * 1. supported formats for route requirements
+ * 2. request format
+ * 3. request headers
+ * 4. request body
+ * 5. expected response format
+ * 6. expected response content type
+ * 7. expected response body
+ */
+ public function providerTestResponseFormat() {
+ $json_encoded = Json::encode(['REST' => 'Drupal']);
+ $xml_encoded = "\nDrupal\n";
+
+ $safe_method_test_cases = [
+ 'safe methods: client requested format (JSON)' => [
+ // @todo add 'HEAD' in https://www.drupal.org/node/2752325
+ ['GET'],
+ ['xml', 'json'],
+ 'json',
+ [],
+ NULL,
+ 'json',
+ 'application/json',
+ $json_encoded,
+ ],
+ 'safe methods: client requested format (XML)' => [
+ // @todo add 'HEAD' in https://www.drupal.org/node/2752325
+ ['GET'],
+ ['xml', 'json'],
+ 'xml',
+ [],
+ NULL,
+ 'xml',
+ 'text/xml',
+ $xml_encoded,
+ ],
+ 'safe methods: client requested no format: response should use the first configured format (JSON)' => [
+ // @todo add 'HEAD' in https://www.drupal.org/node/2752325
+ ['GET'],
+ ['json', 'xml'],
+ FALSE,
+ [],
+ NULL,
+ 'json',
+ 'application/json',
+ $json_encoded,
+ ],
+ 'safe methods: client requested no format: response should use the first configured format (XML)' => [
+ // @todo add 'HEAD' in https://www.drupal.org/node/2752325
+ ['GET'],
+ ['xml', 'json'],
+ FALSE,
+ [],
+ NULL,
+ 'xml',
+ 'text/xml',
+ $xml_encoded,
+ ],
+ ];
+
+ $unsafe_method_bodied_test_cases = [
+ 'unsafe methods with response (POST, PATCH): client requested no format, response should use request body format (JSON)' => [
+ ['POST', 'PATCH'],
+ ['xml', 'json'],
+ FALSE,
+ ['Content-Type' => 'application/json'],
+ $json_encoded,
+ 'json',
+ 'application/json',
+ $json_encoded,
+ ],
+ 'unsafe methods with response (POST, PATCH): client requested no format, response should use request body format (XML)' => [
+ ['POST', 'PATCH'],
+ ['xml', 'json'],
+ FALSE,
+ ['Content-Type' => 'text/xml'],
+ $xml_encoded,
+ 'xml',
+ 'text/xml',
+ $xml_encoded,
+ ],
+ 'unsafe methods with response (POST, PATCH): client requested format other than request body format (JSON): response format should use requested format (XML)' => [
+ ['POST', 'PATCH'],
+ ['xml', 'json'],
+ 'xml',
+ ['Content-Type' => 'application/json'],
+ $json_encoded,
+ 'xml',
+ 'text/xml',
+ $xml_encoded,
+ ],
+ 'unsafe methods with response (POST, PATCH): client requested format other than request body format (XML), but is allowed for the request body (JSON)' => [
+ ['POST', 'PATCH'],
+ ['xml', 'json'],
+ 'json',
+ ['Content-Type' => 'text/xml'],
+ $xml_encoded,
+ 'json',
+ 'application/json',
+ $json_encoded,
+ ],
+ ];
+
+ $unsafe_method_bodyless_test_cases = [
+ 'unsafe methods with response bodies (DELETE): client requested no format, response should have no format' => [
+ ['DELETE'],
+ ['xml', 'json'],
+ FALSE,
+ ['Content-Type' => 'application/json'],
+ NULL,
+ 'xml',
+ NULL,
+ '',
+ ],
+ 'unsafe methods with response bodies (DELETE): client requested format (XML), response should have no format' => [
+ ['DELETE'],
+ ['xml', 'json'],
+ 'xml',
+ ['Content-Type' => 'application/json'],
+ NULL,
+ 'xml',
+ NULL,
+ '',
+ ],
+ 'unsafe methods with response bodies (DELETE): client requested format (JSON), response should have no format' => [
+ ['DELETE'],
+ ['xml', 'json'],
+ 'json',
+ ['Content-Type' => 'application/json'],
+ NULL,
+ 'json',
+ NULL,
+ '',
+ ],
+ ];
+
+ return $safe_method_test_cases + $unsafe_method_bodied_test_cases + $unsafe_method_bodyless_test_cases;
+ }
+
+}
+
+/**
+ * Stub class where we can prophesize methods.
+ */
+class StubRequestHandlerResourcePlugin extends ResourceBase {
+
+ function get() {}
+ function post() {}
+ function patch() {}
+ function delete() {}
+
+}