function ResourceTestBase::testCollection

Same name and namespace in other branches
  1. 9 core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php \Drupal\Tests\jsonapi\Functional\ResourceTestBase::testCollection()
  2. 8.9.x core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php \Drupal\Tests\jsonapi\Functional\ResourceTestBase::testCollection()
  3. 10 core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php \Drupal\Tests\jsonapi\Functional\ResourceTestBase::testCollection()

Tests GETting a collection of resources.

2 methods override ResourceTestBase::testCollection()
FileUploadTest::testCollection in core/modules/jsonapi/tests/src/Functional/FileUploadTest.php
Tests GETting a collection of resources.
MessageTest::testCollection in core/modules/jsonapi/tests/src/Functional/MessageTest.php
Tests GETting a collection of resources.

File

core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php, line 1091

Class

ResourceTestBase
Subclass this for every JSON:API resource type.

Namespace

Drupal\Tests\jsonapi\Functional

Code

public function testCollection() : void {
    $entity_collection = $this->getData();
    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());
    // This asserts that collections will work without a sort, added by default
    // below, without actually asserting the content of the response.
    $expected_response = $this->getExpectedCollectionResponse($entity_collection, $collection_url->toString(), $request_options);
    $expected_cacheability = $expected_response->getCacheableMetadata();
    $response = $this->request('HEAD', $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, NULL, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
    // Different databases have different sort orders, so a sort is required so
    // test expectations do not need to vary per database.
    $default_sort = [
        'sort' => 'drupal_internal__' . $this->entity
            ->getEntityType()
            ->getKey('id'),
    ];
    $collection_url->setOption('query', $default_sort);
    // 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();
    // 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';
    $expected_document = $expected_response->getResponseData();
    $response = $this->request('GET', $collection_url, $request_options);
    $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();
    // 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';
    $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.status',
                    'value' => 'does_not@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.";
        $expected_cache_tags = [
            '4xx-response',
            'http_response',
        ];
        $expected_cache_contexts = [
            'url.query_args:filter',
            'url.query_args:sort',
            'url.site',
            'user.permissions',
        ];
        $this->assertResourceErrorResponse(403, $expected_error_message, $unauthorized_filter_url, $response, FALSE, $expected_cache_tags, $expected_cache_contexts, FALSE, 'MISS');
        $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 `status` field, given in the path `field_jsonapi_test_entity_ref.entity:user.status`.";
        $this->assertResourceErrorResponse(403, $expected_error_message, $unauthorized_filter_url, $response, FALSE, $expected_cache_tags, $expected_cache_contexts, FALSE, 'MISS');
    }
    // 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' => 'id',
                    'value' => $removed->uuid(),
                ],
            ],
        ],
    ];
    $filtered_collection_url->setOption('query', $entity_collection_filter + $default_sort);
    $expected_response = $this->getExpectedCollectionResponse($filtered_entity_collection, $filtered_collection_url->toString(), $request_options, NULL, 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', $entity_collection_filter + $include + $default_sort);
    $expected_response = $this->getExpectedCollectionResponse($filtered_entity_collection, $filtered_collection_include_url->toString(), $request_options, $relationship_field_names, TRUE);
    $expected_cacheability = $expected_response->getCacheableMetadata();
    $expected_cacheability->setCacheTags(array_values(array_diff($expected_cacheability->getCacheTags(), [
        '4xx-response',
    ])));
    $expected_document = $expected_response->getResponseData();
    $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);
    // If the response should vary by a user's authorizations, grant permissions
    // for the included resources and execute another request.
    $permission_related_cache_contexts = [
        'user',
        'user.permissions',
        'user.roles',
    ];
    if (!empty($relationship_field_names) && !empty(array_intersect($expected_cacheability->getCacheContexts(), $permission_related_cache_contexts))) {
        $applicable_permissions = array_intersect_key(static::getIncludePermissions(), array_flip($relationship_field_names));
        $flattened_permissions = array_unique(array_reduce($applicable_permissions, 'array_merge', []));
        $this->grantPermissionsToTestedRole($flattened_permissions);
        $expected_response = $this->getExpectedCollectionResponse($filtered_entity_collection, $filtered_collection_include_url->toString(), $request_options, $relationship_field_names, TRUE);
        $expected_cacheability = $expected_response->getCacheableMetadata();
        $expected_document = $expected_response->getResponseData();
        $response = $this->request('GET', $filtered_collection_include_url, $request_options);
        $requires_include_only_permissions = !empty($flattened_permissions);
        $uncacheable = $expected_cacheability->getCacheMaxAge() === 0 || !empty(array_intersect([
            'user',
            'session',
        ], $expected_cacheability->getCacheContexts()));
        $dynamic_cache = !$uncacheable ? $requires_include_only_permissions ? 'MISS' : 'HIT' : 'UNCACHEABLE';
        $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->uuid(), $a->uuid());
    });
    $sorted_collection_include_url = clone $collection_url;
    $sorted_collection_include_url->setOption('query', $include + [
        'sort' => "-id",
    ]);
    $expected_response = $this->getExpectedCollectionResponse($sorted_entity_collection, $sorted_collection_include_url->toString(), $request_options, $relationship_field_names);
    $expected_cacheability = $expected_response->getCacheableMetadata();
    $expected_cacheability->setCacheTags(array_values(array_diff($expected_cacheability->getCacheTags(), [
        '4xx-response',
    ])));
    $expected_document = $expected_response->getResponseData();
    $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);
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.