diff --git a/composer.json b/composer.json index 970aa03..6978862 100644 --- a/composer.json +++ b/composer.json @@ -2,9 +2,5 @@ "name": "drupal/jsonapi", "description": "Provides a JSON API standards-compliant API for accessing and manipulating Drupal content and configuration entities.", "type": "drupal-module", - "license": "GPL-2.0+", - "require-dev": { - "justinrainbow/json-schema": "^5.2", - "drupal/schemata": "1.x-dev#8325d172e1d6880aa24073f8f751ef089282cf9a" - } + "license": "GPL-2.0+" } diff --git a/jsonapi.info.yml b/jsonapi.info.yml index f2135bc..a333090 100644 --- a/jsonapi.info.yml +++ b/jsonapi.info.yml @@ -4,7 +4,4 @@ description: Provides a JSON:API standards-compliant API for accessing and manip core: 8.x package: Web services dependencies: - - drupal:system (>=8.5.4) - drupal:serialization -test_dependencies: - - schemata:schemata_json_schema diff --git a/jsonapi.module b/jsonapi.module index a092541..f1256bf 100644 --- a/jsonapi.module +++ b/jsonapi.module @@ -98,22 +98,6 @@ function jsonapi_help($route_name, RouteMatchInterface $route_match) { return NULL; } -/** - * Implements hook_entity_bundle_field_info(). - */ -function jsonapi_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) { - $fields = []; - - if (floatval(\Drupal::VERSION) < 8.6 && $entity_type->id() == 'taxonomy_term') { - // Only terms in the same bundle can be a parent. - $fields['parent'] = clone $base_field_definitions['parent']; - $fields['parent']->setSetting('handler_settings', ['target_bundles' => [$bundle]]); - return $fields; - } - - return $fields; -} - /** * Implements hook_entity_bundle_create(). */ diff --git a/jsonapi.services.yml b/jsonapi.services.yml index 44309dc..8f8ac08 100644 --- a/jsonapi.services.yml +++ b/jsonapi.services.yml @@ -139,10 +139,6 @@ services: - { name: event_subscriber } arguments: ['@jsonapi.serializer_do_not_use_removal_imminent', '%serializer.formats%'] - logger.channel.jsonapi: - parent: logger.channel_base - arguments: ['jsonapi'] - # Cache. cache.jsonapi_resource_types: class: Drupal\Core\Cache\MemoryCache\MemoryCache @@ -205,14 +201,6 @@ services: arguments: ['@jsonapi.serializer_do_not_use_removal_imminent'] tags: - { name: event_subscriber } - jsonapi.resource_response_validator.subscriber: - class: Drupal\jsonapi\EventSubscriber\ResourceResponseValidator - arguments: ['@jsonapi.serializer_do_not_use_removal_imminent', '@logger.channel.jsonapi', '@module_handler', '@app.root'] - calls: - - [setValidator, []] - - [setSchemaFactory, ['@?schemata.schema_factory']] # This is only injected when the service is available. - tags: - - { name: event_subscriber, priority: 1000 } # Revision management. jsonapi.version_negotiator: diff --git a/schema.json b/schema.json deleted file mode 100644 index 902a39d..0000000 --- a/schema.json +++ /dev/null @@ -1,375 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "JSON API Schema", - "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", - "oneOf": [ - { - "$ref": "#/definitions/success" - }, - { - "$ref": "#/definitions/failure" - }, - { - "$ref": "#/definitions/info" - } - ], - - "definitions": { - "success": { - "type": "object", - "required": [ - "data" - ], - "properties": { - "data": { - "$ref": "#/definitions/data" - }, - "included": { - "description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".", - "type": "array", - "items": { - "$ref": "#/definitions/resource" - }, - "uniqueItems": true - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "links": { - "description": "Link members related to the primary data.", - "allOf": [ - { - "$ref": "#/definitions/links" - }, - { - "$ref": "#/definitions/pagination" - } - ] - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - } - }, - "additionalProperties": false - }, - "failure": { - "type": "object", - "required": [ - "errors" - ], - "properties": { - "errors": { - "type": "array", - "items": { - "$ref": "#/definitions/error" - }, - "uniqueItems": true - }, - "meta": { - "$ref": "#/definitions/meta" - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - } - }, - "additionalProperties": false - }, - "info": { - "type": "object", - "required": [ - "meta" - ], - "properties": { - "meta": { - "$ref": "#/definitions/meta" - }, - "links": { - "$ref": "#/definitions/links" - }, - "jsonapi": { - "$ref": "#/definitions/jsonapi" - } - }, - "additionalProperties": false - }, - - "meta": { - "description": "Non-standard meta-information that can not be represented as an attribute or relationship.", - "type": "object", - "additionalProperties": true - }, - "data": { - "description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.", - "oneOf": [ - { - "$ref": "#/definitions/resource" - }, - { - "description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.", - "type": "array", - "items": { - "$ref": "#/definitions/resource" - }, - "uniqueItems": true - }, - { - "description": "null if the request is one that might correspond to a single resource, but doesn't currently.", - "type": "null" - } - ] - }, - "resource": { - "description": "\"Resource objects\" appear in a JSON API document to represent resources.", - "type": "object", - "required": [ - "type", - "id" - ], - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "attributes": { - "$ref": "#/definitions/attributes" - }, - "relationships": { - "$ref": "#/definitions/relationships" - }, - "links": { - "$ref": "#/definitions/links" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - - "links": { - "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.", - "type": "object", - "properties": { - "self": { - "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.", - "type": "string", - "format": "uri" - }, - "related": { - "$ref": "#/definitions/link" - } - }, - "additionalProperties": true - }, - "link": { - "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.", - "oneOf": [ - { - "description": "A string containing the link's URL.", - "type": "string", - "format": "uri" - }, - { - "type": "object", - "required": [ - "href" - ], - "properties": { - "href": { - "description": "A string containing the link's URL.", - "type": "string", - "format": "uri" - }, - "meta": { - "$ref": "#/definitions/meta" - } - } - } - ] - }, - - "attributes": { - "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.", - "type": "object", - "patternProperties": { - "^(?!relationships$|links$)\\w[-\\w_]*$": { - "description": "Attributes may contain any valid JSON value." - } - }, - "additionalProperties": false - }, - - "relationships": { - "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.", - "type": "object", - "patternProperties": { - "^\\w[-\\w_]*$": { - "properties": { - "links": { - "$ref": "#/definitions/links" - }, - "data": { - "description": "Member, whose value represents \"resource linkage\".", - "oneOf": [ - { - "$ref": "#/definitions/relationshipToOne" - }, - { - "$ref": "#/definitions/relationshipToMany" - } - ] - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "anyOf": [ - {"required": ["data"]}, - {"required": ["meta"]}, - {"required": ["links"]} - ], - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "relationshipToOne": { - "description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.", - "anyOf": [ - { - "$ref": "#/definitions/empty" - }, - { - "$ref": "#/definitions/linkage" - } - ] - }, - "relationshipToMany": { - "description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.", - "type": "array", - "items": { - "$ref": "#/definitions/linkage" - }, - "uniqueItems": true - }, - "empty": { - "description": "Describes an empty to-one relationship.", - "type": "null" - }, - "linkage": { - "description": "The \"type\" and \"id\" to non-empty members.", - "type": "object", - "required": [ - "type", - "id" - ], - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - "pagination": { - "type": "object", - "properties": { - "first": { - "description": "The first page of data", - "oneOf": [ - { "type": "string", "format": "uri" }, - { "type": "null" } - ] - }, - "last": { - "description": "The last page of data", - "oneOf": [ - { "type": "string", "format": "uri" }, - { "type": "null" } - ] - }, - "prev": { - "description": "The previous page of data", - "oneOf": [ - { "type": "string", "format": "uri" }, - { "type": "null" } - ] - }, - "next": { - "description": "The next page of data", - "oneOf": [ - { "type": "string", "format": "uri" }, - { "type": "null" } - ] - } - } - }, - - "jsonapi": { - "description": "An object describing the server's implementation", - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - }, - - "error": { - "type": "object", - "properties": { - "id": { - "description": "A unique identifier for this particular occurrence of the problem.", - "type": "string" - }, - "links": { - "$ref": "#/definitions/links" - }, - "status": { - "description": "The HTTP status code applicable to this problem, expressed as a string value.", - "type": "string" - }, - "code": { - "description": "An application-specific error code, expressed as a string value.", - "type": "string" - }, - "title": { - "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.", - "type": "string" - }, - "detail": { - "description": "A human-readable explanation specific to this occurrence of the problem.", - "type": "string" - }, - "source": { - "type": "object", - "properties": { - "pointer": { - "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].", - "type": "string" - }, - "parameter": { - "description": "A string indicating which query parameter caused the error.", - "type": "string" - } - } - }, - "meta": { - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false - } - } -} diff --git a/src/Access/TemporaryQueryGuard.php b/src/Access/TemporaryQueryGuard.php index 618e48e..8150350 100644 --- a/src/Access/TemporaryQueryGuard.php +++ b/src/Access/TemporaryQueryGuard.php @@ -374,15 +374,6 @@ class TemporaryQueryGuard { $cacheability->addCacheTags($entity_type->getListCacheTags()); } } - // @todo Remove when Drupal 8.5 support is dropped (terms are publishable in >=8.6). - if (floatval(\Drupal::VERSION) < 8.6 && $entity_type->id() === 'taxonomy_term') { - $access_result = $access_results[JSONAPI_FILTER_AMONG_PUBLISHED]; - $cacheability->addCacheableDependency($access_result); - if ($access_result->isAllowed()) { - $conditions[] = new EntityCondition('tid', 0, '>'); - $cacheability->addCacheTags($entity_type->getListCacheTags()); - } - } // The "enabled" subset. // @todo Remove ternary when the 'status' key is added to the User entity type. @@ -399,10 +390,6 @@ class TemporaryQueryGuard { // The "owner" subset. // @todo Remove ternary when the 'uid' key is added to the User entity type. $owner_field_name = $entity_type->id() === 'user' ? 'uid' : $entity_type->getKey('owner'); - // @todo Remove when Drupal 8.5 and 8.6 support is dropped. - if (floatval(\Drupal::VERSION) < 8.7) { - $owner_field_name = $entity_type->id() === 'user' ? $owner_field_name : $entity_type->getKey('uid'); - } if ($owner_field_name) { $access_result = $access_results[JSONAPI_FILTER_AMONG_OWN]; $cacheability->addCacheableDependency($access_result); diff --git a/src/BackwardCompatibility/tests/Traits/EntityReferenceTestTrait.php b/src/BackwardCompatibility/tests/Traits/EntityReferenceTestTrait.php deleted file mode 100644 index 2c8a5e3..0000000 --- a/src/BackwardCompatibility/tests/Traits/EntityReferenceTestTrait.php +++ /dev/null @@ -1,67 +0,0 @@ -=8.6 - */ -trait EntityReferenceTestTrait { - - /** - * Creates an entity reference field storage on the specified bundle. - * - * @param string $entity_type - * The type of entity the field will be attached to. - * @param string $bundle - * The bundle name of the entity the field will be attached to. - * @param string $field_name - * The name of the field; if it already exists, a new instance of the - * existing field will be created. - * @param string $field_label - * The label of the field. - * @param string $target_entity_type - * The type of the referenced entity. - * @param string $selection_handler - * The selection handler used by this field. - * @param array $selection_handler_settings - * An array of settings supported by the selection handler specified above. - * (e.g. 'target_bundles', 'sort', 'auto_create', etc). - * @param int $cardinality - * The cardinality of the field. - * - * @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase::buildConfigurationForm() - */ - protected function createEntityReferenceField($entity_type, $bundle, $field_name, $field_label, $target_entity_type, $selection_handler = 'default', array $selection_handler_settings = [], $cardinality = 1) { - // Look for or add the specified field to the requested entity bundle. - if (!FieldStorageConfig::loadByName($entity_type, $field_name)) { - FieldStorageConfig::create([ - 'field_name' => $field_name, - 'type' => 'entity_reference', - 'entity_type' => $entity_type, - 'cardinality' => $cardinality, - 'settings' => [ - 'target_type' => $target_entity_type, - ], - ])->save(); - } - if (!FieldConfig::loadByName($entity_type, $bundle, $field_name)) { - FieldConfig::create([ - 'field_name' => $field_name, - 'entity_type' => $entity_type, - 'bundle' => $bundle, - 'label' => $field_label, - 'settings' => [ - 'handler' => $selection_handler, - 'handler_settings' => $selection_handler_settings, - ], - ])->save(); - } - } - -} diff --git a/src/EventSubscriber/ResourceResponseValidator.php b/src/EventSubscriber/ResourceResponseValidator.php deleted file mode 100644 index 6823498..0000000 --- a/src/EventSubscriber/ResourceResponseValidator.php +++ /dev/null @@ -1,258 +0,0 @@ -serializer = $serializer; - $this->logger = $logger; - $this->moduleHandler = $module_handler; - $this->appRoot = $app_root; - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - $events[KernelEvents::RESPONSE][] = ['onResponse']; - return $events; - } - - /** - * Sets the validator service if available. - */ - public function setValidator(Validator $validator = NULL) { - if ($validator) { - $this->validator = $validator; - } - elseif (class_exists(Validator::class)) { - $this->validator = new Validator(); - } - } - - /** - * Injects the schema factory. - * - * @param \Drupal\schemata\SchemaFactory $schema_factory - * The schema factory service. - */ - public function setSchemaFactory(SchemaFactory $schema_factory) { - $this->schemaFactory = $schema_factory; - } - - /** - * Validates JSON:API responses. - * - * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event - * The event to process. - */ - public function onResponse(FilterResponseEvent $event) { - $response = $event->getResponse(); - if (!$response instanceof ResourceResponse) { - return; - } - - $this->doValidateResponse($response, $event->getRequest()); - } - - /** - * Wraps validation in an assert to prevent execution in production. - * - * @see self::validateResponse - */ - public function doValidateResponse(Response $response, Request $request) { - if (PHP_MAJOR_VERSION >= 7 || assert_options(ASSERT_ACTIVE)) { - assert($this->validateResponse($response, $request), 'A JSON:API response failed validation (see the logs for details). Please report this in the issue queue on drupal.org'); - } - } - - /** - * Validates a response against the JSON:API specification. - * - * @param \Symfony\Component\HttpFoundation\Response $response - * The response to validate. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request containing info about what to validate. - * - * @return bool - * FALSE if the response failed validation, otherwise TRUE. - */ - protected function validateResponse(Response $response, Request $request) { - // If the validator isn't set, then the validation library is not installed. - if (!$this->validator) { - return TRUE; - } - - // Do not use Json::decode here since it coerces the response into an - // associative array, which creates validation errors. - $response_data = json_decode($response->getContent()); - if (empty($response_data)) { - return TRUE; - } - - $schema_ref = sprintf( - 'file://%s/schema.json', - implode('/', [ - $this->appRoot, - $this->moduleHandler->getModule('jsonapi')->getPath(), - ]) - ); - $generic_jsonapi_schema = (object) ['$ref' => $schema_ref]; - $is_valid = $this->validateSchema($generic_jsonapi_schema, $response_data); - if (!$is_valid) { - return FALSE; - } - - // This will be set if the schemata module is present. - if (!$this->schemaFactory) { - // Fall back the valid generic result since schemata is absent. - return TRUE; - } - - // Get the schema for the current resource. For that we will need to - // introspect the request to find the entity type and bundle matched by the - // router. - $resource_type = $request->get(Routes::RESOURCE_TYPE_KEY); - $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME); - - // We shouldn't validate related/relationships. - $is_related = strpos($route_name, '.related') !== FALSE; - $is_relationship = strpos($route_name, '.relationship') !== FALSE; - if ($is_related || $is_relationship) { - // Fall back the valid generic result since schemata is absent. - return TRUE; - } - - $entity_type_id = $resource_type->getEntityTypeId(); - $bundle = $resource_type->getBundle(); - $output_format = 'schema_json'; - $described_format = 'api_json'; - - $schema_object = $this->schemaFactory->create($entity_type_id, $bundle); - $format = $output_format . ':' . $described_format; - $output = $this->serializer->serialize($schema_object, $format); - $specific_schema = Json::decode($output); - if (!$specific_schema) { - return $is_valid; - } - - // We need to individually validate each collection resource object. - $is_collection = strpos($route_name, '.collection') !== FALSE; - - // Iterate over each resource object and check the schema. - return array_reduce( - $is_collection ? $response_data->data : [$response_data->data], - function ($valid, $resource_object) use ($specific_schema) { - // Validating the schema first ensures that every object is processed. - return $this->validateSchema($specific_schema, $resource_object) && $valid; - }, - TRUE - ); - } - - /** - * Validates a string against a JSON Schema. It logs any possible errors. - * - * @param object $schema - * The JSON Schema object. - * @param string $response_data - * The JSON string to validate. - * - * @return bool - * TRUE if the string is a valid instance of the schema. FALSE otherwise. - */ - protected function validateSchema($schema, $response_data) { - $this->validator->check($response_data, $schema); - $is_valid = $this->validator->isValid(); - if (!$is_valid) { - $this->logger->debug("Response failed validation.\nResponse:\n@data\n\nErrors:\n@errors", [ - '@data' => Json::encode($response_data), - '@errors' => Json::encode($this->validator->getErrors()), - ]); - } - return $is_valid; - } - -} diff --git a/src/JsonapiServiceProvider.php b/src/JsonapiServiceProvider.php index a6d0b39..fe43787 100644 --- a/src/JsonapiServiceProvider.php +++ b/src/JsonapiServiceProvider.php @@ -8,7 +8,6 @@ use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\jsonapi\DependencyInjection\Compiler\RegisterSerializationClassesCompilerPass; use Drupal\jsonapi\DependencyInjection\Compiler\RemoveJsonapiFormatCompilerPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; -use Symfony\Component\DependencyInjection\Reference; /** * Adds 'api_json' as known format and prevents its use in the REST module. @@ -21,17 +20,6 @@ class JsonapiServiceProvider implements ServiceModifierInterface, ServiceProvide * {@inheritdoc} */ public function alter(ContainerBuilder $container) { - // @todo Remove when we stop supporting Drupal 8.5. - if (floatval(\Drupal::VERSION) < 8.6) { - // Swap the cache service back. - $definition = $container->getDefinition('jsonapi.resource_type.repository'); - $definition->setArgument(3, new Reference('cache.static')); - $container->setDefinition('jsonapi.resource_type.repository', $definition); - - // Drop the new service definition. - $container->removeDefinition('cache.jsonapi_resource_types'); - } - if ($container->has('http_middleware.negotiation') && is_a($container->getDefinition('http_middleware.negotiation') ->getClass(), '\Drupal\Core\StackMiddleware\NegotiationMiddleware', TRUE) ) { diff --git a/tests/src/Functional/BlockContentTest.php b/tests/src/Functional/BlockContentTest.php index 9cd9ff0..3168383 100644 --- a/tests/src/Functional/BlockContentTest.php +++ b/tests/src/Functional/BlockContentTest.php @@ -86,7 +86,7 @@ class BlockContentTest extends ResourceTestBase { */ protected function getExpectedDocument() { $self_url = Url::fromUri('base:/jsonapi/block_content/basic/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl(); - $expected_document = [ + return [ 'jsonapi' => [ 'meta' => [ 'links' => [ @@ -121,6 +121,7 @@ class BlockContentTest extends ResourceTestBase { 'default_langcode' => TRUE, 'drupal_internal__id' => 1, 'drupal_internal__revision_id' => 1, + 'reusable' => TRUE, ], 'relationships' => [ 'block_content_type' => [ @@ -143,10 +144,6 @@ class BlockContentTest extends ResourceTestBase { ], ], ]; - if (floatval(\Drupal::VERSION) >= 8.6) { - $expected_document['data']['attributes']['reusable'] = TRUE; - } - return $expected_document; } /** diff --git a/tests/src/Functional/BlockTest.php b/tests/src/Functional/BlockTest.php index b88ef23..da080af 100644 --- a/tests/src/Functional/BlockTest.php +++ b/tests/src/Functional/BlockTest.php @@ -156,7 +156,7 @@ class BlockTest extends ResourceTestBase { protected function getExpectedUnauthorizedAccessMessage($method) { switch ($method) { case 'GET': - return floatval(\Drupal::VERSION >= 8.7) ? "The block visibility condition 'user_role' denied access." : ''; + return "The block visibility condition 'user_role' denied access."; default: return parent::getExpectedUnauthorizedAccessMessage($method); diff --git a/tests/src/Functional/CommentTest.php b/tests/src/Functional/CommentTest.php index 626237d..7b551e7 100644 --- a/tests/src/Functional/CommentTest.php +++ b/tests/src/Functional/CommentTest.php @@ -275,10 +275,7 @@ class CommentTest extends ResourceTestBase { return "The 'post comments' permission is required."; case 'PATCH': - // @todo Make this unconditional when JSON:API requires Drupal 8.6 or newer. - if (floatval(\Drupal::VERSION) >= 8.6) { - return "The 'edit own comments' permission is required, the user must be the comment author, and the comment must be published."; - } + return "The 'edit own comments' permission is required, the user must be the comment author, and the comment must be published."; default: return parent::getExpectedUnauthorizedAccessMessage($method); @@ -315,12 +312,7 @@ class CommentTest extends ResourceTestBase { // DX: 422 when missing 'entity_type' field. $request_options[RequestOptions::BODY] = Json::encode($remove_field($this->getPostDocument(), 'attributes', 'entity_type')); $response = $this->request('POST', $url, $request_options); - if (floatval(\Drupal::VERSION) >= 8.7) { - $this->assertResourceErrorResponse(422, 'entity_type: This value should not be null.', NULL, $response, '/data/attributes/entity_type'); - } - else { - $this->assertResourceErrorResponse(500, 'The "" entity type does not exist.', $url, $response, FALSE); - } + $this->assertResourceErrorResponse(422, 'entity_type: This value should not be null.', NULL, $response, '/data/attributes/entity_type'); // DX: 422 when missing 'entity_id' field. $request_options[RequestOptions::BODY] = Json::encode($remove_field($this->getPostDocument(), 'relationships', 'entity_id')); @@ -336,12 +328,7 @@ class CommentTest extends ResourceTestBase { // DX: 422 when missing 'field_name' field. $request_options[RequestOptions::BODY] = Json::encode($remove_field($this->getPostDocument(), 'attributes', 'field_name')); $response = $this->request('POST', $url, $request_options); - if (floatval(\Drupal::VERSION) >= 8.7) { - $this->assertResourceErrorResponse(422, 'field_name: This value should not be null.', NULL, $response, '/data/attributes/field_name'); - } - else { - $this->assertSame(500, $response->getStatusCode()); - } + $this->assertResourceErrorResponse(422, 'field_name: This value should not be null.', NULL, $response, '/data/attributes/field_name'); } /** diff --git a/tests/src/Functional/ConfigTestTest.php b/tests/src/Functional/ConfigTestTest.php index df8da49..e6d873d 100644 --- a/tests/src/Functional/ConfigTestTest.php +++ b/tests/src/Functional/ConfigTestTest.php @@ -47,7 +47,7 @@ class ConfigTestTest extends ResourceTestBase { protected function getExpectedUnauthorizedAccessMessage($method) { switch ($method) { case 'GET': - return floatval(\Drupal::VERSION >= 8.7) ? "The 'view config_test' permission is required." : ''; + return "The 'view config_test' permission is required."; default: return parent::getExpectedUnauthorizedAccessMessage($method); diff --git a/tests/src/Functional/EntityTestMapFieldTest.php b/tests/src/Functional/EntityTestMapFieldTest.php index 38326eb..b78f96c 100644 --- a/tests/src/Functional/EntityTestMapFieldTest.php +++ b/tests/src/Functional/EntityTestMapFieldTest.php @@ -10,9 +10,6 @@ use Drupal\user\Entity\User; * JSON:API integration test for the "EntityTestMapField" content entity type. * * @group jsonapi - * @requires function Drupal\entity_test\Entity\EntityTestMapField::baseFieldDefinitions - * - * @todo Remove the "@requires" annotation when JSON:API requires Drupal >=8.6 */ class EntityTestMapFieldTest extends ResourceTestBase { diff --git a/tests/src/Functional/EntityViewDisplayTest.php b/tests/src/Functional/EntityViewDisplayTest.php index cd5fa95..1b154cc 100644 --- a/tests/src/Functional/EntityViewDisplayTest.php +++ b/tests/src/Functional/EntityViewDisplayTest.php @@ -70,7 +70,7 @@ class EntityViewDisplayTest extends ResourceTestBase { */ protected function getExpectedDocument() { $self_url = Url::fromUri('base:/jsonapi/entity_view_display/entity_view_display/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl(); - $document = [ + return [ 'jsonapi' => [ 'meta' => [ 'links' => [ @@ -94,6 +94,8 @@ class EntityViewDisplayTest extends ResourceTestBase { 'links' => [ 'region' => 'content', 'weight' => 100, + 'settings' => [], + 'third_party_settings' => [], ], ], 'dependencies' => [ @@ -113,11 +115,6 @@ class EntityViewDisplayTest extends ResourceTestBase { ], ], ]; - if (floatval(\Drupal::VERSION) >= 8.6) { - $document['data']['attributes']['content']['links']['settings'] = []; - $document['data']['attributes']['content']['links']['third_party_settings'] = []; - } - return $document; } /** diff --git a/tests/src/Functional/FileTest.php b/tests/src/Functional/FileTest.php index 342cf78..ff67417 100644 --- a/tests/src/Functional/FileTest.php +++ b/tests/src/Functional/FileTest.php @@ -203,8 +203,7 @@ class FileTest extends ResourceTestBase { if ($method === 'GET') { return "The 'access content' permission is required."; } - // @todo Make this unconditional when JSON:API requires Drupal 8.6 or newer. - if (floatval(\Drupal::VERSION) >= 8.6 && ($method === 'PATCH' || $method === 'DELETE')) { + if ($method === 'PATCH' || $method === 'DELETE') { return "Only the file owner can update or delete the file entity."; } return parent::getExpectedUnauthorizedAccessMessage($method); diff --git a/tests/src/Functional/ImageStyleTest.php b/tests/src/Functional/ImageStyleTest.php index f6d7a36..55193d8 100644 --- a/tests/src/Functional/ImageStyleTest.php +++ b/tests/src/Functional/ImageStyleTest.php @@ -79,7 +79,7 @@ class ImageStyleTest extends ResourceTestBase { */ protected function getExpectedDocument() { $self_url = Url::fromUri('base:/jsonapi/image_style/image_style/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl(); - $doc = [ + return [ 'jsonapi' => [ 'meta' => [ 'links' => [ @@ -118,10 +118,6 @@ class ImageStyleTest extends ResourceTestBase { ], ], ]; - if (floatval(\Drupal::VERSION) < 8.6) { - unset($doc['data']['attributes']['effects'][$this->effectUuid]['data']['anchor']); - } - return $doc; } /** diff --git a/tests/src/Functional/InternalEntitiesTest.php b/tests/src/Functional/InternalEntitiesTest.php index 1a0a85b..e170222 100644 --- a/tests/src/Functional/InternalEntitiesTest.php +++ b/tests/src/Functional/InternalEntitiesTest.php @@ -7,8 +7,8 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\entity_test\Entity\EntityTestBundle; use Drupal\entity_test\Entity\EntityTestNoLabel; use Drupal\entity_test\Entity\EntityTestWithBundle; -use Drupal\jsonapi\BackwardCompatibility\tests\Traits\EntityReferenceTestTrait; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\field\Traits\EntityReferenceTestTrait; /** * Makes assertions about the JSON:API behavior for internal entities. diff --git a/tests/src/Functional/JsonApiFunctionalTestBase.php b/tests/src/Functional/JsonApiFunctionalTestBase.php index dbad32e..87ab13e 100644 --- a/tests/src/Functional/JsonApiFunctionalTestBase.php +++ b/tests/src/Functional/JsonApiFunctionalTestBase.php @@ -7,10 +7,10 @@ use Drupal\Core\Url; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\file\Entity\File; -use Drupal\jsonapi\BackwardCompatibility\tests\Traits\EntityReferenceTestTrait; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\field\Traits\EntityReferenceTestTrait; use Drupal\Tests\image\Kernel\ImageFieldCreationTrait; use Drupal\user\Entity\Role; use Drupal\user\RoleInterface; diff --git a/tests/src/Functional/MediaTest.php b/tests/src/Functional/MediaTest.php index 864b56b..30b4bb4 100644 --- a/tests/src/Functional/MediaTest.php +++ b/tests/src/Functional/MediaTest.php @@ -145,7 +145,7 @@ class MediaTest extends ResourceTestBase { $thumbnail = File::load(3); $author = User::load($this->entity->getOwnerId()); $self_url = Url::fromUri('base:/jsonapi/media/camelids/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl(); - $data = [ + return [ 'jsonapi' => [ 'meta' => [ 'links' => [ @@ -243,12 +243,6 @@ class MediaTest extends ResourceTestBase { ], ], ]; - // @todo Make this unconditional when JSON:API requires Drupal 8.6 or newer. - if (floatval(\Drupal::VERSION) < 8.6) { - $data['data']['relationships']['thumbnail']['data']['meta']['alt'] = 'Thumbnail'; - $data['data']['relationships']['thumbnail']['data']['meta']['title'] = 'Llama'; - } - return $data; } /** @@ -290,16 +284,10 @@ class MediaTest extends ResourceTestBase { return "The following permissions are required: 'administer media' OR 'create media' OR 'create camelids media'."; case 'PATCH': - // @todo Make this unconditional when JSON:API requires Drupal 8.6 or newer. - if (floatval(\Drupal::VERSION) >= 8.6) { - return "The following permissions are required: 'update any media' OR 'update own media' OR 'camelids: edit any media' OR 'camelids: edit own media'."; - } + return "The following permissions are required: 'update any media' OR 'update own media' OR 'camelids: edit any media' OR 'camelids: edit own media'."; case 'DELETE': - // @todo Make this unconditional when JSON:API requires Drupal 8.6 or newer. - if (floatval(\Drupal::VERSION) >= 8.6) { - return "The following permissions are required: 'delete any media' OR 'delete own media' OR 'camelids: delete any media' OR 'camelids: delete own media'."; - } + return "The following permissions are required: 'delete any media' OR 'delete own media' OR 'camelids: delete any media' OR 'camelids: delete own media'."; default: return ''; @@ -348,11 +336,6 @@ class MediaTest extends ResourceTestBase { 'height' => 180, 'title' => NULL, ]; - // @todo Make this unconditional when JSON:API requires Drupal 8.6 or newer. - if (floatval(\Drupal::VERSION) < 8.6) { - $data['meta']['alt'] = 'Thumbnail'; - $data['meta']['title'] = 'Llama'; - } return $data; case 'field_media_file': diff --git a/tests/src/Functional/MenuLinkContentTest.php b/tests/src/Functional/MenuLinkContentTest.php index c60d162..2e6b04c 100644 --- a/tests/src/Functional/MenuLinkContentTest.php +++ b/tests/src/Functional/MenuLinkContentTest.php @@ -139,7 +139,7 @@ class MenuLinkContentTest extends ResourceTestBase { protected function getExpectedUnauthorizedAccessMessage($method) { switch ($method) { case 'DELETE': - return floatval(\Drupal::VERSION >= 8.7) ? "The 'administer menu' permission is required." : ''; + return "The 'administer menu' permission is required."; default: return parent::getExpectedUnauthorizedAccessMessage($method); diff --git a/tests/src/Functional/ResourceTestBase.php b/tests/src/Functional/ResourceTestBase.php index 82bb55b..50655aa 100644 --- a/tests/src/Functional/ResourceTestBase.php +++ b/tests/src/Functional/ResourceTestBase.php @@ -1767,10 +1767,6 @@ abstract class ResourceTestBase extends BrowserTestBase { $detail = 'The current user is not allowed to view this relationship.'; if (!$entity->access('view') && $entity->access('view label') && $access instanceof AccessResultReasonInterface && empty($access->getReason())) { $access->setReason("The user only has authorization for the 'view label' operation."); - // @todo Remove when we stop supporting Drupal 8.5. - if (floatval(\Drupal::VERSION) < 8.6 && $relationship_field_name === 'roles' && static::$entityTypeId === 'user' && $access->getCacheContexts() === ['user.permissions']) { - $access = $access->setReason(''); - } } $via_link = Url::fromRoute( sprintf('jsonapi.%s.%s.related', $base_resource_identifier['type'], $relationship_field_name), @@ -2163,9 +2159,6 @@ abstract class ResourceTestBase extends BrowserTestBase { ], ], ]; - if (floatval(\Drupal::VERSION) < 8.6) { - $expected_document['errors'][0]['detail'] = "The current user is not allowed to PATCH the selected field ($id_field_name). The entity ID cannot be changed"; - } $this->assertResourceResponse(403, $expected_document, $response); /* $this->assertResourceErrorResponse(403, "The current user is not allowed to PATCH the selected field ($id_field_name). The entity ID cannot be changed", $response, "/data/attributes/$id_field_name"); */ diff --git a/tests/src/Functional/ShortcutSetTest.php b/tests/src/Functional/ShortcutSetTest.php index 2eea948..0ab41a3 100644 --- a/tests/src/Functional/ShortcutSetTest.php +++ b/tests/src/Functional/ShortcutSetTest.php @@ -60,7 +60,7 @@ class ShortcutSetTest extends ResourceTestBase { protected function getExpectedUnauthorizedAccessMessage($method) { switch ($method) { case 'GET': - return floatval(\Drupal::VERSION >= 8.7) ? "The 'access shortcuts' permission is required." : ''; + return "The 'access shortcuts' permission is required."; default: return parent::getExpectedUnauthorizedAccessMessage($method); diff --git a/tests/src/Functional/TermTest.php b/tests/src/Functional/TermTest.php index bffa1f6..cce7732 100644 --- a/tests/src/Functional/TermTest.php +++ b/tests/src/Functional/TermTest.php @@ -210,18 +210,7 @@ class TermTest extends ResourceTestBase { break; } - // @todo Remove this when JSON:API requires Drupal 8.6 or newer. - if (floatval(\Drupal::VERSION) < 8.6) { - $expected_parent_normalization = [ - 'data' => [], - 'links' => [ - 'related' => ['href' => $self_url . '/parent'], - 'self' => ['href' => $self_url . '/relationships/parent'], - ], - ]; - } - - $expected_document = [ + return [ 'jsonapi' => [ 'meta' => [ 'links' => [ @@ -256,6 +245,7 @@ class TermTest extends ResourceTestBase { ], 'weight' => 0, 'drupal_internal__tid' => 1, + 'status' => TRUE, ], 'relationships' => [ 'parent' => $expected_parent_normalization, @@ -272,12 +262,6 @@ class TermTest extends ResourceTestBase { ], ], ]; - - if (floatval(\Drupal::VERSION) >= 8.6) { - $expected_document['data']['attributes']['status'] = TRUE; - } - - return $expected_document; } /** @@ -285,7 +269,7 @@ class TermTest extends ResourceTestBase { */ protected function getExpectedGetRelationshipDocumentData($relationship_field_name, EntityInterface $entity = NULL) { $data = parent::getExpectedGetRelationshipDocumentData($relationship_field_name, $entity); - if ($relationship_field_name === 'parent' && floatval(\Drupal::VERSION) >= 8.6) { + if ($relationship_field_name === 'parent') { $data = [ 0 => [ 'id' => 'virtual', @@ -311,7 +295,7 @@ class TermTest extends ResourceTestBase { */ protected function getExpectedRelatedResponses(array $relationship_field_names, array $request_options, EntityInterface $entity = NULL) { $responses = parent::getExpectedRelatedResponses($relationship_field_names, $request_options, $entity); - if ($responses['parent']->getStatusCode() === 404 && floatval(\Drupal::VERSION) >= 8.6) { + if ($responses['parent']->getStatusCode() === 404) { $responses['parent'] = new ResourceResponse([ 'data' => [], 'jsonapi' => [ @@ -356,9 +340,7 @@ class TermTest extends ResourceTestBase { protected function getExpectedUnauthorizedAccessMessage($method) { switch ($method) { case 'GET': - return floatval(\Drupal::VERSION) >= 8.6 - ? "The 'access content' permission is required and the taxonomy term must be published." - : "The 'access content' permission is required."; + return "The 'access content' permission is required and the taxonomy term must be published."; case 'POST': return "The following permissions are required: 'create terms in camelids' OR 'administer taxonomy'."; @@ -379,9 +361,7 @@ class TermTest extends ResourceTestBase { */ protected function getExpectedUnauthorizedAccessCacheability() { $cacheability = parent::getExpectedUnauthorizedAccessCacheability(); - if (floatval(\Drupal::VERSION) >= 8.6) { - $cacheability->addCacheableDependency($this->entity); - } + $cacheability->addCacheableDependency($this->entity); return $cacheability; } @@ -452,11 +432,6 @@ class TermTest extends ResourceTestBase { * @dataProvider providerTestGetIndividualTermWithParent */ public function testGetIndividualTermWithParent(array $parent_term_ids) { - if (floatval(\Drupal::VERSION) < 8.6) { - $this->markTestSkipped('The "parent" field on terms is only available for normalization in Drupal 8.6 and later.'); - return; - } - // Create all possible parent terms. Term::create(['vid' => Vocabulary::load('camelids')->id()]) ->setName('Lamoids') @@ -503,12 +478,7 @@ class TermTest extends ResourceTestBase { * {@inheritdoc} */ public function testCollectionFilterAccess() { - if (floatval(\Drupal::VERSION) >= 8.6) { - $this->doTestCollectionFilterAccessForPublishableEntities('name', 'access content', 'administer taxonomy'); - } - else { - $this->doTestCollectionFilterAccessBasedOnPermissions('name', 'access content'); - } + $this->doTestCollectionFilterAccessBasedOnPermissions('name', 'access content'); } } diff --git a/tests/src/Functional/UserTest.php b/tests/src/Functional/UserTest.php index 131e38e..d135628 100644 --- a/tests/src/Functional/UserTest.php +++ b/tests/src/Functional/UserTest.php @@ -172,10 +172,10 @@ class UserTest extends ResourceTestBase { return "The 'access user profiles' permission is required and the user must be active."; case 'PATCH': - return floatval(\Drupal::VERSION >= 8.7) ? "Users can only update their own account, unless they have the 'administer users' permission." : ''; + return "Users can only update their own account, unless they have the 'administer users' permission."; case 'DELETE': - return floatval(\Drupal::VERSION >= 8.7) ? "The 'cancel account' permission is required." : ''; + return "The 'cancel account' permission is required."; default: return parent::getExpectedUnauthorizedAccessMessage($method); diff --git a/tests/src/Functional/VocabularyTest.php b/tests/src/Functional/VocabularyTest.php index 4ce1fe3..61ecdc8 100644 --- a/tests/src/Functional/VocabularyTest.php +++ b/tests/src/Functional/VocabularyTest.php @@ -59,7 +59,7 @@ class VocabularyTest extends ResourceTestBase { */ protected function getExpectedDocument() { $self_url = Url::fromUri('base:/jsonapi/taxonomy_vocabulary/taxonomy_vocabulary/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl(); - $expected_document = [ + return [ 'jsonapi' => [ 'meta' => [ 'links' => [ @@ -88,10 +88,6 @@ class VocabularyTest extends ResourceTestBase { ], ], ]; - if (floatval(\Drupal::VERSION) < 8.7) { - $expected_document['data']['attributes']['hierarchy'] = 0; - } - return $expected_document; } /** diff --git a/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php index 79703af..5e13e53 100644 --- a/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php +++ b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php @@ -289,7 +289,7 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { $this->assertSame($this->term1->uuid(), $normalized['included'][1]['id']); $this->assertSame('taxonomy_term--tags', $normalized['included'][1]['type']); $this->assertSame($this->term1->label(), $normalized['included'][1]['attributes']['name']); - $this->assertCount(floatval(\Drupal::VERSION) >= 8.6 ? 8 : 7, $normalized['included'][1]['attributes']); + $this->assertCount(8, $normalized['included'][1]['attributes']); $this->assertTrue(!isset($normalized['included'][1]['attributes']['created'])); // Make sure that the cache tags for the includes and the requested entities // are bubbling as expected. @@ -379,7 +379,7 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { $this->assertTrue(empty($normalized['meta']['omitted'])); $this->assertEquals($this->user->uuid(), $normalized['included'][0]['id']); $this->assertCount(1, $normalized['included'][0]['attributes']); - $this->assertCount(floatval(\Drupal::VERSION) >= 8.6 ? 8 : 7, $normalized['included'][1]['attributes']); + $this->assertCount(8, $normalized['included'][1]['attributes']); // Make sure that the cache tags for the includes and the requested entities // are bubbling as expected. $this->assertArraySubset( diff --git a/tests/src/Traits/CommonCollectionFilterAccessTestPatternsTrait.php b/tests/src/Traits/CommonCollectionFilterAccessTestPatternsTrait.php index c815b01..8ac8a39 100644 --- a/tests/src/Traits/CommonCollectionFilterAccessTestPatternsTrait.php +++ b/tests/src/Traits/CommonCollectionFilterAccessTestPatternsTrait.php @@ -7,7 +7,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Entity\EntityPublishedInterface; use Drupal\Core\Url; use Drupal\entity_test\Entity\EntityTest; -use Drupal\jsonapi\BackwardCompatibility\tests\Traits\EntityReferenceTestTrait; +use Drupal\Tests\field\Traits\EntityReferenceTestTrait; use Drupal\Tests\jsonapi\Functional\ResourceTestBase; use GuzzleHttp\RequestOptions; diff --git a/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php b/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php deleted file mode 100644 index e3dfba6..0000000 --- a/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php +++ /dev/null @@ -1,319 +0,0 @@ -fail('The JSON Schema validator is missing. You can install it with `composer require justinrainbow/json-schema`.'); - } - - $module_handler = $this->prophesize(ModuleHandlerInterface::class); - $module = $this->prophesize(Extension::class); - $module_path = dirname(dirname(dirname(dirname(__DIR__)))); - $module->getPath()->willReturn($module_path); - $module_handler->getModule('jsonapi')->willReturn($module->reveal()); - $encoders = [new JsonEncoder()]; - if (class_exists(JsonSchemaEncoder::class)) { - $encoders[] = new JsonSchemaEncoder(); - } - $subscriber = new ResourceResponseValidator( - new Serializer([], $encoders), - $this->prophesize(LoggerInterface::class)->reveal(), - $module_handler->reveal(), - '' - ); - $subscriber->setValidator(); - $this->subscriber = $subscriber; - } - - /** - * @covers ::doValidateResponse - */ - public function testDoValidateResponse() { - $request = $this->createRequest( - 'jsonapi.node--article.individual', - new ResourceType('node', 'article', NULL) - ); - - $response = $this->createResponse('{"data":null}'); - - // Capture the default assert settings. - $zend_assertions_default = ini_get('zend.assertions'); - $assert_active_default = assert_options(ASSERT_ACTIVE); - - // The validator *should* be called when asserts are active. - $validator = $this->prophesize(Validator::class); - $validator->check(Argument::any(), Argument::any())->shouldBeCalled('Validation should be run when asserts are active.'); - $validator->isValid()->willReturn(TRUE); - $this->subscriber->setValidator($validator->reveal()); - - // Ensure asset is active. - ini_set('zend.assertions', 1); - assert_options(ASSERT_ACTIVE, 1); - $this->subscriber->doValidateResponse($response, $request); - - // The validator should *not* be called when asserts are inactive. - $validator = $this->prophesize(Validator::class); - $validator->check(Argument::any(), Argument::any())->shouldNotBeCalled('Validation should not be run when asserts are not active.'); - $this->subscriber->setValidator($validator->reveal()); - - // Ensure asset is inactive. - ini_set('zend.assertions', 0); - assert_options(ASSERT_ACTIVE, 0); - $this->subscriber->doValidateResponse($response, $request); - - // Reset the original assert values. - ini_set('zend.assertions', $zend_assertions_default); - assert_options(ASSERT_ACTIVE, $assert_active_default); - } - - /** - * @covers ::onResponse - * @requires function Drupal\schemata\SchemaFactory::__construct - */ - public function testValidateResponseSchemata() { - $request = $this->createRequest( - 'jsonapi.node--article.individual', - new ResourceType('node', 'article', NULL) - ); - - $response = $this->createResponse('{"data":null}'); - - // The validator should be called *once* if schemata is *not* installed. - $validator = $this->prophesize(Validator::class); - $validator->check(Argument::any(), Argument::any())->shouldBeCalledTimes(1); - $validator->isValid()->willReturn(TRUE); - $this->subscriber->setValidator($validator->reveal()); - - // Run validations. - $this->subscriber->doValidateResponse($response, $request); - - // The validator should be called *twice* if schemata is installed. - $validator = $this->prophesize(Validator::class); - $validator->check(Argument::any(), Argument::any())->shouldBeCalledTimes(2); - $validator->isValid()->willReturn(TRUE); - $this->subscriber->setValidator($validator->reveal()); - - // Make the schemata factory available. - $schema_factory = $this->prophesize(SchemaFactory::class); - $schema_factory->create('node', 'article')->willReturn('{}'); - $this->subscriber->setSchemaFactory($schema_factory->reveal()); - - // Run validations. - $this->subscriber->doValidateResponse($response, $request); - - // The validator resource specific schema should *not* be validated on - // 'related' routes. - $request = $this->createRequest( - 'jsonapi.node--article.related', - new ResourceType('node', 'article', NULL) - ); - - // Since only the generic schema should be validated, the validator should - // only be called once. - $validator = $this->prophesize(Validator::class); - $validator->check(Argument::any(), Argument::any())->shouldBeCalledTimes(1); - $validator->isValid()->willReturn(TRUE); - $this->subscriber->setValidator($validator->reveal()); - - // Run validations. - $this->subscriber->doValidateResponse($response, $request); - - // The validator resource specific schema should *not* be validated on - // 'relationship' routes. - $request = $this->createRequest( - 'jsonapi.node--article.relationship', - new ResourceType('node', 'article', NULL) - ); - - // Since only the generic schema should be validated, the validator should - // only be called once. - $validator = $this->prophesize(Validator::class); - $validator->check(Argument::any(), Argument::any())->shouldBeCalledTimes(1); - $validator->isValid()->willReturn(TRUE); - $this->subscriber->setValidator($validator->reveal()); - - // Run validations. - $this->subscriber->doValidateResponse($response, $request); - } - - /** - * @covers ::validateResponse - * @dataProvider validateResponseProvider - */ - public function testValidateResponse($request, $response, $expected, $description) { - // Expose protected ResourceResponseSubscriber::validateResponse() method. - $object = new \ReflectionObject($this->subscriber); - $method = $object->getMethod('validateResponse'); - $method->setAccessible(TRUE); - - $this->assertSame($expected, $method->invoke($this->subscriber, $response, $request), $description); - } - - /** - * Provides test cases for testValidateResponse. - * - * @return array - * An array of test cases. - */ - public function validateResponseProvider() { - $defaults = [ - 'route_name' => 'jsonapi.node--article.individual', - 'resource_type' => new ResourceType('node', 'article', NULL), - ]; - - $test_data = [ - // Test validation success. - [ - 'json' => <<<'EOD' -{ - "data": { - "type": "node--article", - "id": "4f342419-e668-4b76-9f87-7ce20c436169", - "attributes": { - "nid": "1", - "uuid": "4f342419-e668-4b76-9f87-7ce20c436169" - } - } -} -EOD - , - 'expected' => TRUE, - 'description' => 'Response validation flagged a valid response.', - ], - // Test validation failure: no "type" in "data". - [ - 'json' => <<<'EOD' -{ - "data": { - "id": "4f342419-e668-4b76-9f87-7ce20c436169", - "attributes": { - "nid": "1", - "uuid": "4f342419-e668-4b76-9f87-7ce20c436169" - } - } -} -EOD - , - 'expected' => FALSE, - 'description' => 'Response validation failed to flag an invalid response.', - ], - // Test validation failure: "errors" at the root level. - [ - 'json' => <<<'EOD' -{ - "data": { - "type": "node--article", - "id": "4f342419-e668-4b76-9f87-7ce20c436169", - "attributes": { - "nid": "1", - "uuid": "4f342419-e668-4b76-9f87-7ce20c436169" - } - }, - "errors": [{}] -} -EOD - , - 'expected' => FALSE, - 'description' => 'Response validation failed to flag an invalid response.', - ], - // Test validation of an empty response passes. - [ - 'json' => NULL, - 'expected' => TRUE, - 'description' => 'Response validation flagged a valid empty response.', - ], - // Test validation fails on empty object. - [ - 'json' => '{}', - 'expected' => FALSE, - 'description' => 'Response validation flags empty array as invalid.', - ], - ]; - - $test_cases = array_map(function ($input) use ($defaults) { - list($json, $expected, $description, $route_name, $resource_type) = array_values($input + $defaults); - return [ - $this->createRequest($route_name, $resource_type), - $this->createResponse($json), - $expected, - $description, - ]; - }, $test_data); - - return $test_cases; - } - - /** - * Helper method to create a request object. - * - * @param string $route_name - * The route name with which to construct a request. - * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type - * The resource type for the requested route. - * - * @return \Symfony\Component\HttpFoundation\Request - * The mock request object. - */ - protected function createRequest($route_name, ResourceType $resource_type) { - $request = new Request(); - $request->attributes->set(RouteObjectInterface::ROUTE_NAME, $route_name); - $request->attributes->set(Routes::RESOURCE_TYPE_KEY, $resource_type); - return $request; - } - - /** - * Helper method to create a resource response from arbitrary JSON. - * - * @param string|null $json - * The JSON with which to create a mock response. - * - * @return \Drupal\rest\ResourceResponse - * The mock response object. - */ - protected function createResponse($json = NULL) { - $response = new ResourceResponse(); - if ($json) { - $response->setContent($json); - } - return $response; - } - -}