function ResourceTestBase::testGetIndividual
Same name in other branches
- 9 core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php \Drupal\Tests\jsonapi\Functional\ResourceTestBase::testGetIndividual()
- 8.9.x core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php \Drupal\Tests\jsonapi\Functional\ResourceTestBase::testGetIndividual()
- 11.x core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php \Drupal\Tests\jsonapi\Functional\ResourceTestBase::testGetIndividual()
Tests GETting an individual resource, plus edge cases to ensure good DX.
2 methods override ResourceTestBase::testGetIndividual()
- MessageTest::testGetIndividual in core/
modules/ jsonapi/ tests/ src/ Functional/ MessageTest.php - Tests GETting an individual resource, plus edge cases to ensure good DX.
- NodeTest::testGetIndividual in core/
modules/ jsonapi/ tests/ src/ Functional/ NodeTest.php - Tests GETting an individual resource, plus edge cases to ensure good DX.
File
-
core/
modules/ jsonapi/ tests/ src/ Functional/ ResourceTestBase.php, line 917
Class
- ResourceTestBase
- Subclass this for every JSON:API resource type.
Namespace
Drupal\Tests\jsonapi\FunctionalCode
public function testGetIndividual() : void {
// The URL and Guzzle request options that will be used in this test. The
// request options will be modified/expanded throughout this test:
// - to first test all mistakes a developer might make, and assert that the
// error responses provide a good DX
// - to eventually result in a well-formed request that succeeds.
// @todo Remove line below in favor of commented line in https://www.drupal.org/project/drupal/issues/2878463.
$url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), [
'entity' => $this->entity
->uuid(),
]);
// $url = $this->entity->toUrl('jsonapi');
$request_options = [];
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
// DX: 403 when unauthorized, or 200 if the 'view label' operation is
// supported by the entity type.
$response = $this->request('GET', $url, $request_options);
if (!static::$anonymousUsersCanViewLabels) {
$expected_403_cacheability = $this->getExpectedUnauthorizedAccessCacheability();
$reason = $this->getExpectedUnauthorizedAccessMessage('GET');
$message = trim("The current user is not allowed to GET the selected resource. {$reason}");
// MISS or UNCACHEABLE depends on data. It must not be HIT.
$dynamic_cache_header_value = !empty(array_intersect([
'user',
'session',
], $expected_403_cacheability->getCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
$this->assertResourceErrorResponse(403, $message, $url, $response, '/data', $expected_403_cacheability->getCacheTags(), $expected_403_cacheability->getCacheContexts(), FALSE, $dynamic_cache_header_value);
$this->assertArrayNotHasKey('Link', $response->getHeaders());
}
else {
$expected_document = $this->getExpectedDocument();
$label_field_name = $this->entity
->getEntityType()
->hasKey('label') ? $this->entity
->getEntityType()
->getKey('label') : static::$labelFieldName;
$expected_document['data']['attributes'] = array_intersect_key($expected_document['data']['attributes'], [
$label_field_name => TRUE,
]);
unset($expected_document['data']['relationships']);
// MISS or UNCACHEABLE depends on data. It must not be HIT.
$dynamic_cache_label_only = !empty(array_intersect([
'user',
'session',
], $this->getExpectedCacheContexts([
$label_field_name,
]))) ? 'UNCACHEABLE' : 'MISS';
$this->assertResourceResponse(200, $expected_document, $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts([
$label_field_name,
]), FALSE, $dynamic_cache_label_only);
}
$this->setUpAuthorization('GET');
// Set body despite that being nonsensical: should be ignored.
$request_options[RequestOptions::BODY] = Json::encode($this->getExpectedDocument());
// 400 for GET request with reserved custom query parameter.
$url_reserved_custom_query_parameter = clone $url;
$url_reserved_custom_query_parameter = $url_reserved_custom_query_parameter->setOption('query', [
'foo' => 'bar',
]);
$response = $this->request('GET', $url_reserved_custom_query_parameter, $request_options);
$expected_document = [
'jsonapi' => static::$jsonApiMember,
'errors' => [
[
'title' => 'Bad Request',
'status' => '400',
'detail' => "The following query parameters violate the JSON:API spec: 'foo'.",
'links' => [
'info' => [
'href' => 'http://jsonapi.org/format/#query-parameters',
],
'via' => [
'href' => $url_reserved_custom_query_parameter->toString(),
],
],
],
],
];
$this->assertResourceResponse(400, $expected_document, $response, [
'4xx-response',
'http_response',
], [
'url.query_args',
'url.site',
], FALSE, 'MISS');
// 200 for well-formed HEAD request.
$response = $this->request('HEAD', $url, $request_options);
// MISS or UNCACHEABLE depends on data. It must not be HIT.
$dynamic_cache = !empty(array_intersect([
'user',
'session',
], $this->getExpectedCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
$this->assertResourceResponse(200, NULL, $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts(), FALSE, $dynamic_cache);
$head_headers = $response->getHeaders();
// 200 for well-formed GET request. Page Cache hit because of HEAD request.
// Same for Dynamic Page Cache hit.
$response = $this->request('GET', $url, $request_options);
$this->assertResourceResponse(200, $this->getExpectedDocument(), $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts(), FALSE, $dynamic_cache === 'MISS' ? 'HIT' : 'UNCACHEABLE');
// Assert that Dynamic Page Cache did not store a ResourceResponse object,
// which needs serialization after every cache hit. Instead, it should
// contain a flattened response. Otherwise performance suffers.
// @see \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber::flattenResponse()
$cache_items = $this->container
->get('database')
->select('cache_dynamic_page_cache', 'cdp')
->fields('cdp', [
'data',
])
->condition('cid', '%[route]=jsonapi.%', 'LIKE')
->execute()
->fetchAll();
$this->assertLessThanOrEqual(5, count($cache_items));
$found_cached_200_response = FALSE;
$other_cached_responses_are_4xx = TRUE;
foreach ($cache_items as $cache_item) {
$cached_response = unserialize($cache_item->data);
if (!$cached_response instanceof CacheRedirect) {
if ($cached_response->getStatusCode() === 200) {
$found_cached_200_response = TRUE;
}
elseif (!$cached_response->isClientError()) {
$other_cached_responses_are_4xx = FALSE;
}
$this->assertNotInstanceOf(ResourceResponse::class, $cached_response);
$this->assertInstanceOf(CacheableResponseInterface::class, $cached_response);
}
}
$this->assertSame($dynamic_cache !== 'UNCACHEABLE' || isset($dynamic_cache_label_only) && $dynamic_cache_label_only !== 'UNCACHEABLE', $found_cached_200_response);
$this->assertTrue($other_cached_responses_are_4xx);
// Not only assert the normalization, also assert deserialization of the
// response results in the expected object.
$unserialized = $this->serializer
->deserialize((string) $response->getBody(), JsonApiDocumentTopLevel::class, 'api_json', [
'target_entity' => static::$entityTypeId,
'resource_type' => $this->container
->get('jsonapi.resource_type.repository')
->getByTypeName(static::$resourceTypeName),
]);
$this->assertSame($unserialized->uuid(), $this->entity
->uuid());
$get_headers = $response->getHeaders();
// Verify that the GET and HEAD responses are the same. The only difference
// is that there's no body. For this reason the 'Transfer-Encoding' and
// 'Vary' headers are also added to the list of headers to ignore, as they
// may be added to GET requests, depending on web server configuration. They
// are usually 'Transfer-Encoding: chunked' and 'Vary: Accept-Encoding'.
$ignored_headers = [
'Date',
'Content-Length',
'X-Drupal-Cache',
'X-Drupal-Dynamic-Cache',
'Transfer-Encoding',
'Vary',
];
$header_cleaner = function ($headers) use ($ignored_headers) {
foreach ($headers as $header => $value) {
if (str_starts_with($header, 'X-Drupal-Assertion-') || in_array($header, $ignored_headers)) {
unset($headers[$header]);
}
}
return $headers;
};
$get_headers = $header_cleaner($get_headers);
$head_headers = $header_cleaner($head_headers);
$this->assertSame($get_headers, $head_headers);
// Feature: Sparse fieldsets.
$this->doTestSparseFieldSets($url, $request_options);
// Feature: Included.
$this->doTestIncluded($url, $request_options);
// DX: 404 when GETting non-existing entity.
$random_uuid = \Drupal::service('uuid')->generate();
$url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), [
'entity' => $random_uuid,
]);
$response = $this->request('GET', $url, $request_options);
$message_url = clone $url;
$path = str_replace($random_uuid, '{entity}', $message_url->setAbsolute()
->setOptions([
'base_url' => '',
'query' => [],
])
->toString());
$message = 'The "entity" parameter was not converted for the path "' . $path . '" (route name: "jsonapi.' . static::$resourceTypeName . '.individual")';
$this->assertResourceErrorResponse(404, $message, $url, $response, FALSE, [
'4xx-response',
'http_response',
], [
'url.query_args',
'url.site',
], FALSE, 'UNCACHEABLE');
// DX: when Accept request header is missing, still 404, same response.
unset($request_options[RequestOptions::HEADERS]['Accept']);
$response = $this->request('GET', $url, $request_options);
$this->assertResourceErrorResponse(404, $message, $url, $response, FALSE, [
'4xx-response',
'http_response',
], [
'url.query_args',
'url.site',
], FALSE, 'UNCACHEABLE');
}
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.