You are here

public function ResourceTestBase::testGetIndividual in JSON:API 8

Same name and namespace in other branches
  1. 8.2 tests/src/Functional/ResourceTestBase.php \Drupal\Tests\jsonapi\Functional\ResourceTestBase::testGetIndividual()

Tests GETting an individual resource, plus edge cases to ensure good DX.

6 calls to ResourceTestBase::testGetIndividual()
BlockContentTest::testGetIndividual in tests/src/Functional/BlockContentTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.
BlockContentTest::testPostIndividual in tests/src/Functional/BlockContentTest.php
Tests POSTing an individual resource, plus edge cases to ensure good DX.
EntityFormDisplayTest::testGetIndividual in tests/src/Functional/EntityFormDisplayTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.
EntityViewDisplayTest::testGetIndividual in tests/src/Functional/EntityViewDisplayTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.
FileTest::testGetIndividual in tests/src/Functional/FileTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.

... See full list

7 methods override ResourceTestBase::testGetIndividual()
BlockContentTest::testGetIndividual in tests/src/Functional/BlockContentTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.
EntityFormDisplayTest::testGetIndividual in tests/src/Functional/EntityFormDisplayTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.
EntityViewDisplayTest::testGetIndividual in tests/src/Functional/EntityViewDisplayTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.
FileTest::testGetIndividual in tests/src/Functional/FileTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.
ItemTest::testGetIndividual in tests/src/Functional/ItemTest.php
Tests GETting an individual resource, plus edge cases to ensure good DX.

... See full list

File

tests/src/Functional/ResourceTestBase.php, line 893

Class

ResourceTestBase
Subclass this for every JSON API resource type.

Namespace

Drupal\Tests\jsonapi\Functional

Code

public function testGetIndividual() {

  // 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/jsonapi/issues/2878463.
  $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), [
    static::$entityTypeId => $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.
  $response = $this
    ->request('GET', $url, $request_options);
  $reason = $this
    ->getExpectedUnauthorizedAccessMessage('GET');
  $message = trim("The current user is not allowed to GET the selected resource. {$reason}");
  $this
    ->assertResourceErrorResponse(403, $message, $response, '/data');
  $this
    ->assertArrayNotHasKey('Link', $response
    ->getHeaders());
  $this
    ->setUpAuthorization('GET');

  // Set body despite that being nonsensical: should be ignored.
  $request_options[RequestOptions::BODY] = Json::encode($this
    ->getExpectedDocument());

  // 200 for well-formed HEAD request.
  $response = $this
    ->request('HEAD', $url, $request_options);
  $this
    ->assertResourceResponse(200, NULL, $response, $this
    ->getExpectedCacheTags(), $this
    ->getExpectedCacheContexts(), FALSE, 'MISS');
  $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, 'HIT');

  // 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')
    ->query("SELECT cid, data FROM {cache_dynamic_page_cache} WHERE cid LIKE :pattern", [
    ':pattern' => '%[route]=jsonapi.%',
  ])
    ->fetchAllAssoc('cid');
  $this
    ->assertTrue(count($cache_items) >= 2);
  $found_cache_redirect = FALSE;
  $found_cached_200_response = FALSE;
  $other_cached_responses_are_4xx = TRUE;
  foreach ($cache_items as $cid => $cache_item) {
    $cached_data = unserialize($cache_item->data);
    if (!isset($cached_data['#cache_redirect'])) {
      $cached_response = $cached_data['#response'];
      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);
    }
    else {
      $found_cache_redirect = TRUE;
    }
  }
  $this
    ->assertTrue($found_cache_redirect);
  $this
    ->assertTrue($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 (strpos($header, 'X-Drupal-Assertion-') === 0 || 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);

  // @todo Uncomment this in https://www.drupal.org/project/jsonapi/issues/2929932.
  // @codingStandardsIgnoreStart

  /*
  // BC: serialization_update_8401().
  // Only run this for fieldable entities. It doesn't make sense for config
  // entities as config values always use the raw values (as per the config
  // schema), returned directly from the ConfigEntityNormalizer, which
  // doesn't deal with fields individually.
  if ($this->entity instanceof FieldableEntityInterface) {
    // Test the BC settings for timestamp values.
    $this->config('serialization.settings')->set('bc_timestamp_normalizer_unix', TRUE)->save(TRUE);
    // Rebuild the container so new config is reflected in the addition of the
    // TimestampItemNormalizer.
    $this->rebuildAll();

    $response = $this->request('GET', $url, $request_options);
    $this->assertResourceResponse(200, FALSE, $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts(), static::$auth ? FALSE : 'MISS', 'MISS');

    // This ensures the BC layer for bc_timestamp_normalizer_unix works as
    // expected. This method should be using
    // ::formatExpectedTimestampValue() to generate the timestamp value. This
    // will take into account the above config setting.
    $expected = $this->getExpectedNormalizedEntity();
    // Config entities are not affected.
    // @see \Drupal\serialization\Normalizer\ConfigEntityNormalizer::normalize()
    static::recursiveKsort($expected);
    $actual = Json::decode((string) $response->getBody());
    static::recursiveKsort($actual);
    $this->assertSame($expected, $actual);

    // Reset the config value and rebuild.
    $this->config('serialization.settings')->set('bc_timestamp_normalizer_unix', FALSE)->save(TRUE);
    $this->rebuildAll();
  }
  */

  // @codingStandardsIgnoreEnd
  // 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), [
    static::$entityTypeId => $random_uuid,
  ]);
  $response = $this
    ->request('GET', $url, $request_options);
  $message_url = clone $url;
  $path = str_replace($random_uuid, '{' . static::$entityTypeId . '}', $message_url
    ->setAbsolute()
    ->setOptions([
    'base_url' => '',
    'query' => [],
  ])
    ->toString());
  $message = 'The "' . static::$entityTypeId . '" parameter was not converted for the path "' . $path . '" (route name: "jsonapi.' . static::$resourceTypeName . '.individual")';
  $this
    ->assertResourceErrorResponse(404, $message, $response);

  // DX: when Accept request header is missing, still 404, but HTML response.
  unset($request_options[RequestOptions::HEADERS]['Accept']);
  $response = $this
    ->request('GET', $url, $request_options);
  $this
    ->assertSame(404, $response
    ->getStatusCode());
  $this
    ->assertSame([
    'text/html; charset=UTF-8',
  ], $response
    ->getHeader('Content-Type'));
}