diff --git a/core/lib/Drupal/Core/Entity/ContentEntityType.php b/core/lib/Drupal/Core/Entity/ContentEntityType.php index 0e26c3bb51..c4033b5dbb 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityType.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityType.php @@ -17,6 +17,11 @@ class ContentEntityType extends EntityType implements ContentEntityTypeInterface /** * {@inheritdoc} */ + protected $supports_validation = TRUE; + + /** + * {@inheritdoc} + */ public function __construct($definition) { parent::__construct($definition); $this->handlers += [ diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php index b39fb3fa46..97cdda785f 100644 --- a/core/lib/Drupal/Core/Entity/EntityType.php +++ b/core/lib/Drupal/Core/Entity/EntityType.php @@ -278,6 +278,13 @@ class EntityType extends PluginDefinition implements EntityTypeInterface { protected $additional = []; /** + * Entity type supports validation. + * + * @var bool + */ + protected $supports_validation = FALSE; + + /** * Constructs a new EntityType. * * @param array $definition diff --git a/core/modules/config/src/Tests/ConfigEntityListTest.php b/core/modules/config/src/Tests/ConfigEntityListTest.php index e9950ea424..cb0c730b33 100644 --- a/core/modules/config/src/Tests/ConfigEntityListTest.php +++ b/core/modules/config/src/Tests/ConfigEntityListTest.php @@ -29,6 +29,8 @@ protected function setUp() { // test. \Drupal::entityManager()->getStorage('config_test')->load('override')->delete(); $this->drupalPlaceBlock('local_actions_block'); + + $this->drupalLogin($this->createUser(['view config_test', 'administer config_test'])); } /** @@ -149,7 +151,7 @@ public function testList() { */ public function testListUI() { // Log in as an administrative user to access the full menu trail. - $this->drupalLogin($this->drupalCreateUser(['access administration pages', 'administer site configuration'])); + $this->drupalLogin($this->drupalCreateUser(['access administration pages', 'administer site configuration', 'administer config_test'])); // Get the list callback page. $this->drupalGet('admin/structure/config_test'); diff --git a/core/modules/config/src/Tests/ConfigEntityTest.php b/core/modules/config/src/Tests/ConfigEntityTest.php index c0dc97bc7b..78cd31e705 100644 --- a/core/modules/config/src/Tests/ConfigEntityTest.php +++ b/core/modules/config/src/Tests/ConfigEntityTest.php @@ -231,7 +231,7 @@ public function testCRUD() { * Tests CRUD operations through the UI. */ public function testCRUDUI() { - $this->drupalLogin($this->drupalCreateUser(['administer site configuration'])); + $this->drupalLogin($this->drupalCreateUser(['administer site configuration', 'administer config_test'])); $id = strtolower($this->randomMachineName()); $label1 = $this->randomMachineName(); diff --git a/core/modules/config/tests/config_test/config_test.permissions.yml b/core/modules/config/tests/config_test/config_test.permissions.yml new file mode 100644 index 0000000000..eaf1be61b7 --- /dev/null +++ b/core/modules/config/tests/config_test/config_test.permissions.yml @@ -0,0 +1,4 @@ +view config_test: + title: 'View ConfigTest entities' +administer config_test: + title: 'Administer ConfigTest entities' diff --git a/core/modules/config/tests/config_test/src/ConfigTestAccessControlHandler.php b/core/modules/config/tests/config_test/src/ConfigTestAccessControlHandler.php index 88896f0b96..0aeb6e0d63 100644 --- a/core/modules/config/tests/config_test/src/ConfigTestAccessControlHandler.php +++ b/core/modules/config/tests/config_test/src/ConfigTestAccessControlHandler.php @@ -18,14 +18,17 @@ class ConfigTestAccessControlHandler extends EntityAccessControlHandler { * {@inheritdoc} */ public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { - return AccessResult::allowed(); + if ($operation === 'view') { + return AccessResult::allowedIfHasPermission($account, 'view config_test'); + } + return AccessResult::allowedIfHasPermission($account, 'administer config_test'); } /** * {@inheritdoc} */ protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { - return AccessResult::allowed(); + return AccessResult::allowedIfHasPermission($account, 'administer config_test'); } } diff --git a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php index 1bd1d8dee9..4f84ecfe1f 100644 --- a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php @@ -22,6 +22,7 @@ * }, * "access" = "Drupal\config_test\ConfigTestAccessControlHandler" * }, + * supports_validation = TRUE, * config_prefix = "dynamic", * entity_keys = { * "id" = "id", diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php index 5b1c250679..888d2ba645 100644 --- a/core/modules/content_moderation/src/Entity/ContentModerationState.php +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php @@ -31,6 +31,7 @@ * data_table = "content_moderation_state_field_data", * revision_data_table = "content_moderation_state_field_revision", * translatable = TRUE, + * supports_validation = FALSE, * entity_keys = { * "id" = "id", * "revision" = "revision_id", diff --git a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php index 56484705e1..14b2cae406 100644 --- a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php +++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php @@ -54,7 +54,7 @@ protected function setUp() { parent::setUp(); // Create a test user. - $web_user = $this->drupalCreateUser(['administer entity_test content', 'administer entity_test fields', 'view test entity']); + $web_user = $this->drupalCreateUser(['administer entity_test content', 'administer entity_test fields', 'view test entity', 'view config_test', 'administer config_test']); $this->drupalLogin($web_user); } diff --git a/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php index a74e8b2a61..8940c910b6 100644 --- a/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php +++ b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php @@ -2,8 +2,10 @@ namespace Drupal\rest\Plugin\Deriver; +use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; +use Drupal\rest\Plugin\rest\resource\ConfigEntityResource; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -88,6 +90,10 @@ public function getDerivativeDefinitions($base_plugin_definition) { } } + if ($entity_type instanceof ConfigEntityTypeInterface) { + $this->derivatives[$entity_type_id]['class'] = ConfigEntityResource::class; + } + $this->derivatives[$entity_type_id] += $base_plugin_definition; } } diff --git a/core/modules/rest/src/Plugin/rest/resource/ConfigEntityResource.php b/core/modules/rest/src/Plugin/rest/resource/ConfigEntityResource.php new file mode 100644 index 0000000000..91d4e89b21 --- /dev/null +++ b/core/modules/rest/src/Plugin/rest/resource/ConfigEntityResource.php @@ -0,0 +1,25 @@ +createFromNameAndData($entity->getConfigDependencyName(), $entity->toArray()); + $violations = $typed_config->validate(); + + $this->processViolations($violations); + } + +} diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 5d9849ded4..e0c40a93bb 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -264,36 +264,38 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity // Overwrite the received properties. $entity_keys = $entity->getEntityType()->getKeys(); - foreach ($entity->_restSubmittedFields as $field_name) { - $field = $entity->get($field_name); - - // Entity key fields need special treatment: together they uniquely - // identify the entity. Therefore it does not make sense to modify any of - // them. However, rather than throwing an error, we just ignore them as - // long as their specified values match their current values. - if (in_array($field_name, $entity_keys, TRUE)) { - // @todo Work around the wrong assumption that entity keys need special - // treatment, when only read-only fields need it. - // This will be fixed in https://www.drupal.org/node/2824851. - if ($entity->getEntityTypeId() == 'comment' && $field_name == 'status' && !$original_entity->get($field_name)->access('edit')) { - throw new AccessDeniedHttpException("Access denied on updating field '$field_name'."); + if ($entity instanceof FieldableEntityInterface) { + foreach ($entity->_restSubmittedFields as $field_name) { + $field = $entity->get($field_name); + + // Entity key fields need special treatment: together they uniquely + // identify the entity. Therefore it does not make sense to modify any of + // them. However, rather than throwing an error, we just ignore them as + // long as their specified values match their current values. + if (in_array($field_name, $entity_keys, TRUE)) { + // @todo Work around the wrong assumption that entity keys need special + // treatment, when only read-only fields need it. + // This will be fixed in https://www.drupal.org/node/2824851. + if ($entity->getEntityTypeId() == 'comment' && $field_name == 'status' && !$original_entity->get($field_name)->access('edit')) { + throw new AccessDeniedHttpException("Access denied on updating field '$field_name'."); + } + + // Unchanged values for entity keys don't need access checking. + if ($this->getCastedValueFromFieldItemList($original_entity->get($field_name)) === $this->getCastedValueFromFieldItemList($entity->get($field_name))) { + continue; + } + // It is not possible to set the language to NULL as it is automatically + // re-initialized. As it must not be empty, skip it if it is. + elseif (isset($entity_keys['langcode']) && $field_name === $entity_keys['langcode'] && $field->isEmpty()) { + continue; + } } - // Unchanged values for entity keys don't need access checking. - if ($this->getCastedValueFromFieldItemList($original_entity->get($field_name)) === $this->getCastedValueFromFieldItemList($entity->get($field_name))) { - continue; - } - // It is not possible to set the language to NULL as it is automatically - // re-initialized. As it must not be empty, skip it if it is. - elseif (isset($entity_keys['langcode']) && $field_name === $entity_keys['langcode'] && $field->isEmpty()) { - continue; + if (!$original_entity->get($field_name)->access('edit')) { + throw new AccessDeniedHttpException("Access denied on updating field '$field_name'."); } + $original_entity->set($field_name, $field->getValue()); } - - if (!$original_entity->get($field_name)->access('edit')) { - throw new AccessDeniedHttpException("Access denied on updating field '$field_name'."); - } - $original_entity->set($field_name, $field->getValue()); } // Validate the received data before saving. @@ -392,9 +394,8 @@ protected function getBaseRoute($canonical_path, $method) { */ public function availableMethods() { $methods = parent::availableMethods(); - if ($this->isConfigEntityResource()) { - // Currently only GET is supported for Config Entities. - // @todo Remove when supported https://www.drupal.org/node/2300677 + // Without validation, it's impossible to support creation or modification. + if (!$this->entityType->get('supports_validation')) { $unsupported_methods = ['POST', 'PUT', 'DELETE', 'PATCH']; $methods = array_diff($methods, $unsupported_methods); } @@ -402,16 +403,6 @@ public function availableMethods() { } /** - * Checks if this resource is for a Config Entity. - * - * @return bool - * TRUE if the entity is a Config Entity, FALSE otherwise. - */ - protected function isConfigEntityResource() { - return $this->entityType instanceof ConfigEntityType; - } - - /** * {@inheritdoc} */ public function calculateDependencies() { diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResourceAccessTrait.php b/core/modules/rest/src/Plugin/rest/resource/EntityResourceAccessTrait.php index 7bf8e824e1..4480a85bcb 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResourceAccessTrait.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResourceAccessTrait.php @@ -3,6 +3,7 @@ namespace Drupal\rest\Plugin\rest\resource; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\FieldableEntityInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** @@ -22,6 +23,10 @@ * field. */ protected function checkEditFieldAccess(EntityInterface $entity) { + if (!$entity instanceof FieldableEntityInterface) { + return; + } + // Only check 'edit' permissions for fields that were actually submitted by // the user. Field access makes no difference between 'create' and 'update', // so the 'edit' operation is used here. diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php b/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php index 09b4b64bae..de1bb77f03 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php @@ -33,6 +33,19 @@ protected function validate(EntityInterface $entity) { // changes. $violations->filterByFieldAccess(); + $this->processViolations($violations); + } + + /** + * Processes violations and creates a helpful exception message. + * + * @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations + * The entity constraint violations to process. + * + * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException + * Throws a HTTP exception when the validation fails. + */ + protected function processViolations($violations) { if ($violations->count() > 0) { $message = "Unprocessable Entity: validation failed.\n"; foreach ($violations as $violation) { diff --git a/core/modules/rest/tests/modules/config_test_rest/config_test_rest.module b/core/modules/rest/tests/modules/config_test_rest/config_test_rest.module index fcd9979a11..0cffb9c1f4 100644 --- a/core/modules/rest/tests/modules/config_test_rest/config_test_rest.module +++ b/core/modules/rest/tests/modules/config_test_rest/config_test_rest.module @@ -26,5 +26,8 @@ function config_test_rest_config_test_access(EntityInterface $entity, $operation // Add permission, so that EntityResourceTestBase's scenarios can test access // being denied. By default, all access is always allowed for the config_test // config entity. - return AccessResult::forbiddenIf(!$account->hasPermission('view config_test'))->cachePerPermissions(); + if ($operation === 'view') { + return AccessResult::forbiddenIf(!$account->hasPermission('view config_test'))->cachePerPermissions(); + } + return AccessResult::neutral(); } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php index 9fe073b097..b89a2718dc 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php @@ -23,10 +23,31 @@ protected $entity; /** + * Counter used internally to have deterministic IDs. + * + * @var int + */ + protected $counter = 0; + + /** + * {@inheritdoc} + */ + protected static $firstCreatedEntityId = 'llama'; + + /** * {@inheritdoc} */ protected function setUpAuthorization($method) { - $this->grantPermissionsToTestedRole(['view config_test']); + switch ($method) { + case 'GET': + $this->grantPermissionsToTestedRole(['view config_test']); + break; + case 'POST': + case 'PATCH': + case 'DELETE': + $this->grantPermissionsToTestedRole(['administer config_test']); + break; + } } /** @@ -67,7 +88,33 @@ protected function getExpectedNormalizedEntity() { * {@inheritdoc} */ protected function getNormalizedPostEntity() { - // @todo Update in https://www.drupal.org/node/2300677. + $this->counter++; + return [ + 'id' => 'llama' . (string) $this->counter, + 'label' => 'Llamam', + ]; + } + + /** + * {@inheritdoc} + */ + protected function makeNormalizationInvalid(array $normalization) { + $normalization['label'] = ['foo', 'bar']; + return $normalization; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedUnauthorizedAccessMessage($method) { + if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) { + return parent::getExpectedUnauthorizedAccessMessage($method); + } + + if ($method === 'GET') { + return 'You are not authorized to view this config_test entity.'; + } + return "The 'administer config_test' permission is required."; } } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 2de3852080..6345b0b185 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -77,7 +77,7 @@ * * @var string[] */ - protected static $patchProtectedFieldNames; + protected static $patchProtectedFieldNames = []; /** * Optionally specify which field is the 'label' field. Some entities specify @@ -691,9 +691,8 @@ protected static function recursiveKSort(array &$array) { * Tests a POST request for an entity, plus edge cases to ensure good DX. */ public function testPost() { - // @todo Remove this in https://www.drupal.org/node/2300677. - if ($this->entity instanceof ConfigEntityInterface) { - $this->assertTrue(TRUE, 'POSTing config entities is not yet supported.'); + if (!$this->entity->getEntityType()->get('supports_validation')) { + $this->assertTrue(TRUE, "This entity type doesn't support POSTing."); return; } @@ -705,7 +704,14 @@ public function testPost() { $parseable_valid_request_body = $this->serializer->encode($this->getNormalizedPostEntity(), static::$format); $parseable_valid_request_body_2 = $this->serializer->encode($this->getNormalizedPostEntity(), static::$format); $parseable_invalid_request_body = $this->serializer->encode($this->makeNormalizationInvalid($this->getNormalizedPostEntity()), static::$format); - $parseable_invalid_request_body_2 = $this->serializer->encode($this->getNormalizedPostEntity() + ['uuid' => [$this->randomMachineName(129)]], static::$format); + // The normalized structure is different for fieldable and non-fieldable + // entities. + if ($this->entity instanceof FieldableEntityInterface) { + $parseable_invalid_request_body_2 = $this->serializer->encode($this->getNormalizedPostEntity() + ['uuid' => [$this->randomMachineName(129)]], static::$format); + } + else { + $parseable_invalid_request_body_2 = $this->serializer->encode($this->getNormalizedPostEntity() + ['uuid' => $this->randomMachineName(129)], static::$format); + } $parseable_invalid_request_body_3 = $this->serializer->encode($this->getNormalizedPostEntity() + ['field_rest_test' => [['value' => $this->randomString()]]], static::$format); // The URL and Guzzle request options that will be used in this test. The @@ -793,9 +799,14 @@ public function testPost() { // DX: 422 when invalid entity: multiple values sent for single-value field. $response = $this->request('POST', $url, $request_options); - $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; - $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); - $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); + if ($this->entity instanceof ConfigEntityInterface) { + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nlabel: This value should be of the correct primitive type.\n", $response); + } + else { + $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; + $label_field_capitalized = $this->entity instanceof FieldableEntityInterface ? $this->entity->getFieldDefinition($label_field)->getLabel() : 'label'; + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); + } $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2; @@ -805,16 +816,23 @@ public function testPost() { // @todo Fix this in https://www.drupal.org/node/2149851. if ($this->entity->getEntityType()->hasKey('uuid')) { $response = $this->request('POST', $url, $request_options); - $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nuuid.0.value: UUID: may not be longer than 128 characters.\n", $response); + if ($this->entity instanceof FieldableEntityInterface) { + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nuuid.0.value: UUID: may not be longer than 128 characters.\n", $response); + } + else { + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nuuid: This is not a valid UUID.\n", $response); + } } - $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_3; + if ($this->entity instanceof FieldableEntityInterface) { + $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_3; - // DX: 403 when entity contains field without 'edit' access. - $response = $this->request('POST', $url, $request_options); - $this->assertResourceErrorResponse(403, "Access denied on creating field 'field_rest_test'.", $response); + // DX: 403 when entity contains field without 'edit' access. + $response = $this->request('POST', $url, $request_options); + $this->assertResourceErrorResponse(403, "Access denied on creating field 'field_rest_test'.", $response); + } $request_options[RequestOptions::BODY] = $parseable_valid_request_body; @@ -912,9 +930,8 @@ public function testPost() { * Tests a PATCH request for an entity, plus edge cases to ensure good DX. */ public function testPatch() { - // @todo Remove this in https://www.drupal.org/node/2300677. - if ($this->entity instanceof ConfigEntityInterface) { - $this->assertTrue(TRUE, 'PATCHing config entities is not yet supported.'); + if (!$this->entity->getEntityType()->get('supports_validation')) { + $this->assertTrue(TRUE, "This entity type doesn't support PATCHing."); return; } @@ -1025,18 +1042,22 @@ public function testPatch() { // DX: 422 when invalid entity: multiple values sent for single-value field. - $response = $this->request('PATCH', $url, $request_options); - $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; - $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); - $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); + if ($this->entity instanceof FieldableEntityInterface) { + $response = $this->request('PATCH', $url, $request_options); + $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; + $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); + } $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2; // DX: 403 when entity contains field without 'edit' access. - $response = $this->request('PATCH', $url, $request_options); - $this->assertResourceErrorResponse(403, "Access denied on updating field 'field_rest_test'.", $response); + if ($this->entity instanceof FieldableEntityInterface) { + $response = $this->request('PATCH', $url, $request_options); + $this->assertResourceErrorResponse(403, "Access denied on updating field 'field_rest_test'.", $response); + } // DX: 403 when sending PATCH request with read-only fields. @@ -1083,7 +1104,8 @@ public function testPatch() { $response = $this->request('PATCH', $url, $request_options); $this->assertResourceResponse(200, FALSE, $response); $this->assertFalse($response->hasHeader('X-Drupal-Cache')); - // Assert that the entity was indeed updated, and that the response body + if ($this->entity instanceof FieldableEntityInterface) { + // Assert that the entity was indeed updated, and that the response body // contains the serialized updated entity. $updated_entity = $this->entityStorage->loadUnchanged($this->entity->id()); $updated_entity_normalization = $this->serializer->normalize($updated_entity, static::$format, ['account' => $this->account]); @@ -1099,9 +1121,10 @@ public function testPatch() { } } // Ensure that fields do not get deleted if they're not present in the PATCH - // request. Test this using the configurable field that we added, but which - // is not sent in the PATCH request. - $this->assertSame('All the faith he had had had had no effect on the outcome of his life.', $updated_entity->get('field_rest_test')->value); + // request. Test this using the configurable field that we added, but which + // is not sent in the PATCH request. + $this->assertSame('All the faith he had had had had no effect on the outcome of his life.', $updated_entity->get('field_rest_test')->value); + } $this->config('rest.settings')->set('bc_entity_resource_permissions', TRUE)->save(TRUE); @@ -1127,9 +1150,8 @@ public function testPatch() { * Tests a DELETE request for an entity, plus edge cases to ensure good DX. */ public function testDelete() { - // @todo Remove this in https://www.drupal.org/node/2300677. - if ($this->entity instanceof ConfigEntityInterface) { - $this->assertTrue(TRUE, 'DELETEing config entities is not yet supported.'); + if (!$this->entity->getEntityType()->get('supports_validation')) { + $this->assertTrue(TRUE, "This entity type doesn't support DELETEing."); return; } @@ -1303,7 +1325,15 @@ protected function getEntityResourcePostUrl() { protected function makeNormalizationInvalid(array $normalization) { // Add a second label to this entity to make it invalid. $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; - $normalization[$label_field][1]['value'] = 'Second Title'; + if ($this->entity instanceof FieldableEntityInterface) { + $normalization[$label_field][1]['value'] = 'Second Title'; + } + else { + $normalization[$label_field] = [ + $normalization[$label_field], + 'Second title', + ]; + } return $normalization; }