You are here

public function ResourceTestBase::testCollection in JSON:API 8

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

Tests GETting a collection of resources.

1 call to ResourceTestBase::testCollection()
EntityViewDisplayTest::testCollection in tests/src/Functional/EntityViewDisplayTest.php
Tests GETting a collection of resources.
3 methods override ResourceTestBase::testCollection()
EntityViewDisplayTest::testCollection in tests/src/Functional/EntityViewDisplayTest.php
Tests GETting a collection of resources.
ItemTest::testCollection in tests/src/Functional/ItemTest.php
Tests GETting a collection of resources.
MessageTest::testCollection in tests/src/Functional/MessageTest.php
Tests GETting a collection of resources.

File

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

Class

ResourceTestBase
Subclass this for every JSON API resource type.

Namespace

Drupal\Tests\jsonapi\Functional

Code

public function testCollection() {
  $entity_collection = $this
    ->getEntityCollection();
  assert(count($entity_collection) > 1, 'A collection must have more that one entity in it.');
  $collection_url = Url::fromRoute(sprintf('jsonapi.%s.collection', static::$resourceTypeName))
    ->setAbsolute(TRUE);
  $request_options = [];
  $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
  $request_options = NestedArray::mergeDeep($request_options, $this
    ->getAuthenticationRequestOptions());

  // 200 for collections, even when all entities are inaccessible. Access is
  // on a per-entity basis, which is handled by
  // self::getExpectedCollectionResponse().
  $expected_response = $this
    ->getExpectedCollectionResponse($entity_collection, $collection_url
    ->toString(), $request_options);
  $expected_cacheability = $expected_response
    ->getCacheableMetadata();
  $expected_document = $expected_response
    ->getResponseData();
  $response = $this
    ->request('GET', $collection_url, $request_options);

  // MISS or UNCACHEABLE depends on the collection data. It must not be HIT.
  $dynamic_cache = $expected_cacheability
    ->getCacheMaxAge() === 0 ? 'UNCACHEABLE' : 'MISS';
  $this
    ->assertResourceResponse(200, $expected_document, $response, $expected_cacheability
    ->getCacheTags(), $expected_cacheability
    ->getCacheContexts(), FALSE, $dynamic_cache);
  $this
    ->setUpAuthorization('GET');

  // 200 for well-formed HEAD request.
  $expected_response = $this
    ->getExpectedCollectionResponse($entity_collection, $collection_url
    ->toString(), $request_options);
  $expected_cacheability = $expected_response
    ->getCacheableMetadata();
  $response = $this
    ->request('HEAD', $collection_url, $request_options);
  $this
    ->assertResourceResponse(200, NULL, $response, $expected_cacheability
    ->getCacheTags(), $expected_cacheability
    ->getCacheContexts(), FALSE, $dynamic_cache);

  // 200 for well-formed GET request.
  $expected_response = $this
    ->getExpectedCollectionResponse($entity_collection, $collection_url
    ->toString(), $request_options);
  $expected_cacheability = $expected_response
    ->getCacheableMetadata();
  $expected_document = $expected_response
    ->getResponseData();
  $response = $this
    ->request('GET', $collection_url, $request_options);

  // Dynamic Page Cache HIT unless the HEAD request was UNCACHEABLE.
  $dynamic_cache = $dynamic_cache === 'UNCACHEABLE' ? 'UNCACHEABLE' : 'HIT';
  $this
    ->assertResourceResponse(200, $expected_document, $response, $expected_cacheability
    ->getCacheTags(), $expected_cacheability
    ->getCacheContexts(), FALSE, $dynamic_cache);
  if ($this->entity instanceof FieldableEntityInterface) {

    // 403 for filtering on an unauthorized field on the base resource type.
    $unauthorized_filter_url = clone $collection_url;
    $unauthorized_filter_url
      ->setOption('query', [
      'filter' => [
        'related_author_id' => [
          'operator' => '<>',
          'path' => 'field_jsonapi_test_entity_ref.mail',
          'value' => 'doesnt@matter.com',
        ],
      ],
    ]);
    $response = $this
      ->request('GET', $unauthorized_filter_url, $request_options);
    $expected_error_message = "The current user is not authorized to filter by the `field_jsonapi_test_entity_ref` field, given in the path `field_jsonapi_test_entity_ref`. The 'field_jsonapi_test_entity_ref view access' permission is required.";
    $this
      ->assertResourceErrorResponse(403, $expected_error_message, $response);
    $this
      ->grantPermissionsToTestedRole([
      'field_jsonapi_test_entity_ref view access',
    ]);

    // 403 for filtering on an unauthorized field on a related resource type.
    $response = $this
      ->request('GET', $unauthorized_filter_url, $request_options);
    $expected_error_message = "The current user is not authorized to filter by the `mail` field, given in the path `field_jsonapi_test_entity_ref.entity:user.mail`.";
    $this
      ->assertResourceErrorResponse(403, $expected_error_message, $response);
  }

  // Remove an entity from the collection, then filter it out.
  $filtered_entity_collection = $entity_collection;
  $removed = array_shift($filtered_entity_collection);
  $filtered_collection_url = clone $collection_url;
  $entity_collection_filter = [
    'filter' => [
      'ids' => [
        'condition' => [
          'operator' => '<>',
          'path' => $removed
            ->getEntityType()
            ->getKey('id'),
          'value' => $removed
            ->id(),
        ],
      ],
    ],
  ];
  $filtered_collection_url
    ->setOption('query', $entity_collection_filter);
  $expected_response = $this
    ->getExpectedCollectionResponse($filtered_entity_collection, $filtered_collection_url
    ->toString(), $request_options, TRUE);
  $expected_cacheability = $expected_response
    ->getCacheableMetadata();
  $expected_document = $expected_response
    ->getResponseData();
  $response = $this
    ->request('GET', $filtered_collection_url, $request_options);

  // MISS or UNCACHEABLE depends on the collection data. It must not be HIT.
  $dynamic_cache = $expected_cacheability
    ->getCacheMaxAge() === 0 || !empty(array_intersect([
    'user',
    'session',
  ], $expected_cacheability
    ->getCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
  $this
    ->assertResourceResponse(200, $expected_document, $response, $expected_cacheability
    ->getCacheTags(), $expected_cacheability
    ->getCacheContexts(), FALSE, $dynamic_cache);

  // Filtered collection with includes.
  $relationship_field_names = array_reduce($filtered_entity_collection, function ($relationship_field_names, $entity) {
    return array_unique(array_merge($relationship_field_names, $this
      ->getRelationshipFieldNames($entity)));
  }, []);
  $include = [
    'include' => implode(',', $relationship_field_names),
  ];
  $filtered_collection_include_url = clone $collection_url;
  $filtered_collection_include_url
    ->setOption('query', array_merge($entity_collection_filter, $include));
  $expected_response = $this
    ->getExpectedCollectionResponse($filtered_entity_collection, $filtered_collection_include_url
    ->toString(), $request_options, TRUE);
  $related_responses = array_reduce($filtered_entity_collection, function ($related_responses, $entity) use ($relationship_field_names, $request_options) {
    return array_merge($related_responses, array_values($this
      ->getExpectedRelatedResponses($relationship_field_names, $request_options, $entity)));
  }, []);
  $expected_response = static::decorateExpectedResponseForIncludedFields($expected_response, $related_responses);
  $expected_cacheability = $expected_response
    ->getCacheableMetadata();
  $expected_document = $expected_response
    ->getResponseData();

  // @todo remove this loop in https://www.drupal.org/project/jsonapi/issues/2853066.
  if (!empty($expected_document['meta']['errors'])) {
    foreach ($expected_document['meta']['errors'] as $index => $error) {
      $expected_document['meta']['errors'][$index]['source']['pointer'] = '/data';
    }
  }
  $response = $this
    ->request('GET', $filtered_collection_include_url, $request_options);

  // MISS or UNCACHEABLE depends on the included data. It must not be HIT.
  $dynamic_cache = $expected_cacheability
    ->getCacheMaxAge() === 0 || !empty(array_intersect([
    'user',
    'session',
  ], $expected_cacheability
    ->getCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
  $this
    ->assertResourceResponse(200, $expected_document, $response, $expected_cacheability
    ->getCacheTags(), $expected_cacheability
    ->getCacheContexts(), FALSE, $dynamic_cache);

  // Sorted collection with includes.
  $sorted_entity_collection = $entity_collection;
  uasort($sorted_entity_collection, function (EntityInterface $a, EntityInterface $b) {

    // Sort by ID in reverse order.
    return strcmp($b
      ->id(), $a
      ->id());
  });
  $id_key = reset($entity_collection)
    ->getEntityType()
    ->getKey('id');
  if (!$id_key) {

    // Can't sort without an ID.
    return;
  }
  $sorted_collection_include_url = clone $collection_url;
  $sorted_collection_include_url
    ->setOption('query', array_merge($include, [
    'sort' => "-{$id_key}",
  ]));
  $expected_response = $this
    ->getExpectedCollectionResponse($sorted_entity_collection, $sorted_collection_include_url
    ->toString(), $request_options);
  $related_responses = array_reduce($sorted_entity_collection, function ($related_responses, $entity) use ($relationship_field_names, $request_options) {
    return array_merge($related_responses, array_values($this
      ->getExpectedRelatedResponses($relationship_field_names, $request_options, $entity)));
  }, []);
  $expected_response = static::decorateExpectedResponseForIncludedFields($expected_response, $related_responses);
  $expected_cacheability = $expected_response
    ->getCacheableMetadata();
  $expected_document = $expected_response
    ->getResponseData();

  // @todo remove this loop in https://www.drupal.org/project/jsonapi/issues/2853066.
  if (!empty($expected_document['meta']['errors'])) {
    foreach ($expected_document['meta']['errors'] as $index => $error) {
      $expected_document['meta']['errors'][$index]['source']['pointer'] = '/data';
    }
  }
  $response = $this
    ->request('GET', $sorted_collection_include_url, $request_options);

  // MISS or UNCACHEABLE depends on the included data. It must not be HIT.
  $dynamic_cache = $expected_cacheability
    ->getCacheMaxAge() === 0 ? 'UNCACHEABLE' : 'MISS';
  $this
    ->assertResourceResponse(200, $expected_document, $response, $expected_cacheability
    ->getCacheTags(), $expected_cacheability
    ->getCacheContexts(), FALSE, $dynamic_cache);
}