.../Comment/CommentHalJsonAnonTest.php | 2 +-
.../Comment/CommentHalJsonTestBase.php | 2 +-
.../EntityResource/HalEntityNormalizationTrait.php | 19 +++-
.../EntityResource/Node/NodeHalJsonAnonTest.php | 2 +-
.../Vocabulary/VocabularyHalJsonAnonTest.php | 3 +-
.../tests/src/Functional/AnonResourceTestTrait.php | 14 +++
.../src/Functional/BasicAuthResourceTestTrait.php | 9 +-
.../src/Functional/CookieResourceTestTrait.php | 13 ++-
.../EntityResource/Block/BlockResourceTestBase.php | 2 +-
.../EntityResource/Comment/CommentJsonAnonTest.php | 2 +-
.../Comment/CommentResourceTestBase.php | 16 ++-
.../ConfigTest/ConfigTestResourceTestBase.php | 5 +-
.../EntityResource/EntityResourceTestBase.php | 114 +++++++++++++++------
.../EntityTest/EntityTestJsonBasicAuthTest.php | 1 -
.../EntityTest/EntityTestResourceTestBase.php | 8 +-
.../EntityResource/Node/NodeResourceTestBase.php | 4 +-
.../EntityResource/Role/RoleResourceTestBase.php | 2 +-
.../EntityResource/Term/TermResourceTestBase.php | 6 +-
.../EntityResource/User/UserResourceTestBase.php | 6 +-
.../Vocabulary/VocabularyResourceTestBase.php | 2 +-
.../rest/tests/src/Functional/ResourceTestBase.php | 8 +-
21 files changed, 178 insertions(+), 62 deletions(-)
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php
index 56d94e2..ffcefc8 100644
--- a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php
+++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php
@@ -23,7 +23,7 @@ class CommentHalJsonAnonTest extends CommentHalJsonTestBase {
*
* @see ::setUpAuthorization
*/
- protected static $patchProtectedFields = [
+ protected static $patchProtectedFieldNames = [
'changed',
'thread',
'entity_type',
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php
index 9ad4ead..9e42f56 100644
--- a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php
+++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php
@@ -40,7 +40,7 @@
*
* @todo fix in https://www.drupal.org/node/2824271
*/
- protected static $patchProtectedFields = [
+ protected static $patchProtectedFieldNames = [
'created',
'changed',
'status',
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php b/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php
index e3b8d9c..317110b 100644
--- a/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php
+++ b/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php
@@ -7,8 +7,25 @@
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Url;
+/**
+ * Trait for EntityResourceTestBase subclasses testing formats using HAL normalization
+ */
trait HalEntityNormalizationTrait {
+ /**
+ * Applies the HAL entity field normalization to an entity normalization.
+ *
+ * The HAL normalization:
+ * - adds a 'lang' attribute to every translatable field
+ * - omits reference fields, since references are stored in _links & _embedded
+ * - omits empty fields (fields without value)
+ *
+ * @param array $normalization
+ * An entity normalization.
+ *
+ * @return array
+ * The updated entity normalization.
+ */
protected function applyHalFieldNormalization(array $normalization) {
if (!$this->entity instanceof FieldableEntityInterface) {
throw new \LogicException('This trait should only be used for fieldable entity types.');
@@ -58,7 +75,7 @@ protected function applyHalFieldNormalization(array $normalization) {
protected function removeFieldsFromNormalization(array $normalization, $field_names) {
$normalization = parent::removeFieldsFromNormalization($normalization, $field_names);
foreach ($field_names as $field_name) {
- $relation_url = Url::fromUri('base:rest/relation/' . static::$entityType . '/' . $this->entity->bundle() . '/' . $field_name)
+ $relation_url = Url::fromUri('base:rest/relation/' . static::$entityTypeId . '/' . $this->entity->bundle() . '/' . $field_name)
->setAbsolute(TRUE)
->toString();
$normalization['_links'] = array_diff_key($normalization['_links'], [$relation_url => TRUE]);
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php
index e755c91..72d19ae 100644
--- a/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php
+++ b/core/modules/hal/tests/src/Functional/EntityResource/Node/NodeHalJsonAnonTest.php
@@ -38,7 +38,7 @@ class NodeHalJsonAnonTest extends NodeResourceTestBase {
/**
* {@inheritdoc}
*/
- protected static $patchProtectedFields = [
+ protected static $patchProtectedFieldNames = [
'created',
'changed',
'promote',
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Vocabulary/VocabularyHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Vocabulary/VocabularyHalJsonAnonTest.php
index fc9fed7..e4ae869 100644
--- a/core/modules/hal/tests/src/Functional/EntityResource/Vocabulary/VocabularyHalJsonAnonTest.php
+++ b/core/modules/hal/tests/src/Functional/EntityResource/Vocabulary/VocabularyHalJsonAnonTest.php
@@ -33,8 +33,7 @@ class VocabularyHalJsonAnonTest extends VocabularyResourceTestBase {
protected static $expectedErrorMimeType = 'application/json';
/**
- * Disable the GET test coverage due to bug in taxonomy module.
- * @todo Fix in https://www.drupal.org/node/2805281: remove this override.
+ * @todo Remove this override once https://www.drupal.org/node/2805281 is fixed.
*/
public function testGet() {
$this->markTestSkipped();
diff --git a/core/modules/rest/tests/src/Functional/AnonResourceTestTrait.php b/core/modules/rest/tests/src/Functional/AnonResourceTestTrait.php
index 3e1a912..b05ddf2 100644
--- a/core/modules/rest/tests/src/Functional/AnonResourceTestTrait.php
+++ b/core/modules/rest/tests/src/Functional/AnonResourceTestTrait.php
@@ -5,6 +5,20 @@
use Drupal\Core\Url;
use Psr\Http\Message\ResponseInterface;
+/**
+ * Trait for ResourceTestBase subclasses testing $auth=NULL, i.e. authless/anon.
+ *
+ * Characteristics:
+ * - When no authentication provider is being used, there also cannot be any
+ * particular error response for missing authentication, since by definition
+ * there is not any authentication.
+ * - For the same reason, there are no authentication edge cases to test.
+ * - Because no authentication is required, this is vulnerable to CSRF attacks
+ * by design. Hence a REST resource should probably only allow for anonymous
+ * for safe (GET/HEAD) HTTP methods, and only with extreme care should unsafe
+ * (POST/PATCH/DELETE) HTTP methods be allowed for a REST resource that allows
+ * anonymous access.
+ */
trait AnonResourceTestTrait {
/**
diff --git a/core/modules/rest/tests/src/Functional/BasicAuthResourceTestTrait.php b/core/modules/rest/tests/src/Functional/BasicAuthResourceTestTrait.php
index 64c4d7f..6f8c621 100644
--- a/core/modules/rest/tests/src/Functional/BasicAuthResourceTestTrait.php
+++ b/core/modules/rest/tests/src/Functional/BasicAuthResourceTestTrait.php
@@ -6,7 +6,14 @@
use Psr\Http\Message\ResponseInterface;
/**
- * ResourceTestBase::getAuthenticationRequestOptions() for basic_auth.
+ * Trait for ResourceTestBase subclasses testing $auth=basic_auth.
+ *
+ * Characteristics:
+ * - Every request must send an Authorization header.
+ * - When accessing a URI that requires authentication without being
+ * authenticated, a 401 response must be sent.
+ * - Because every request must send an authorization, there is no danger of
+ * CSRF attacks.
*/
trait BasicAuthResourceTestTrait {
diff --git a/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php b/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php
index c085247..e7ffde2 100644
--- a/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php
+++ b/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php
@@ -7,7 +7,18 @@
use Psr\Http\Message\ResponseInterface;
/**
- * ResourceTestBase::getAuthenticationRequestOptions() for cookie.
+ * Trait for ResourceTestBase subclasses testing $auth=cookie.
+ *
+ * Characteristics:
+ * - After performing a valid "log in" request, the server responds with a 2xx
+ * status code and a 'Set-Cookie' response header. This cookie is what
+ * continues to identify the user in subsequent requests.
+ * - When accessing a URI that requires authentication without being
+ * authenticated, a standard 403 response must be sent.
+ * - Because of the reliance on cookies, and the fact that user agents send
+ * cookies with every request, this is vulnerable to CSRF attacks. To mitigate
+ * this, the response for the "log in" request contains a CSRF token that must
+ * be sent with every unsafe (POST/PATCH/DELETE) HTTP request.
*/
trait CookieResourceTestTrait {
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php
index c569c20..d32393e 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php
@@ -15,7 +15,7 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'block';
+ protected static $entityTypeId = 'block';
/**
* @var \Drupal\block\BlockInterface
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentJsonAnonTest.php b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentJsonAnonTest.php
index 10b2e31..6ce580d 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentJsonAnonTest.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentJsonAnonTest.php
@@ -38,7 +38,7 @@ class CommentJsonAnonTest extends CommentResourceTestBase {
*
* @see ::setUpAuthorization
*/
- protected static $patchProtectedFields = [
+ protected static $patchProtectedFieldNames = [
'pid',
'entity_id',
'changed',
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php
index 2ff0ce7..be45da4 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php
@@ -22,12 +22,12 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'comment';
+ protected static $entityTypeId = 'comment';
/**
* {@inheritdoc}
*/
- protected static $patchProtectedFields = [
+ protected static $patchProtectedFieldNames = [
'pid',
'entity_id',
'uid',
@@ -251,6 +251,18 @@ protected function getNormalizedPatchEntity() {
return array_diff_key($this->getNormalizedPostEntity(), ['entity_type' => TRUE, 'entity_id' => TRUE, 'field_name' => TRUE]);
}
+ /**
+ * Tests POSTing a comment without critical base fields.
+ *
+ * testPost() is testing with the most minimal normalization possible: the one
+ * returned by ::getNormalizedPostEntity().
+ *
+ * But Comment entities have some very special edge cases:
+ * - base fields that are not marked as required in \Drupal\comment\Entity\Comment::baseFieldDefinitions()
+ * yet in fact are required.
+ * - base fields that are marked as required, but yet can still result in
+ * validation errors other than "missing required field".
+ */
public function testPostDxWithoutCriticalBaseFields() {
$this->initAuthentication();
$this->provisionEntityResource();
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 50c6ec9..600a254 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php
@@ -2,6 +2,7 @@
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
+use Drupal\config_test\Entity\ConfigTest;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
abstract class ConfigTestResourceTestBase extends EntityResourceTestBase {
@@ -14,7 +15,7 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'config_test';
+ protected static $entityTypeId = 'config_test';
/**
* @var \Drupal\config_test\ConfigTestInterface
@@ -32,7 +33,7 @@ protected function setUpAuthorization($method) {
* {@inheritdoc}
*/
protected function createEntity() {
- $config_test = entity_create('config_test', [
+ $config_test = ConfigTest::create([
'id' => 'llama',
'label' => 'Llama',
]);
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
index 1906020..f091331 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -23,6 +23,43 @@
*
* Subclass this for every entity type. Also respect instructions in
* \Drupal\rest\Tests\ResourceTestBase.
+ *
+ * For example, for the node test coverage, there is the (abstract)
+ * \Drupal\Tests\rest\Functional\EntityResource\Node\NodeResourceTestBase, which
+ * is then again subclassed for every authentication provider:
+ * - \Drupal\Tests\rest\Functional\EntityResource\Node\NodeJsonAnonTest
+ * - \Drupal\Tests\rest\Functional\EntityResource\Node\NodeJsonBasicAuthTest
+ * - \Drupal\Tests\rest\Functional\EntityResource\Node\NodeJsonCookieTest
+ * But the HAL module also adds a new format ('hal_json'), so that format also
+ * needs test coverage (for its own peculiarities in normalization & encoding):
+ * - \Drupal\Tests\hal\Functional\EntityResource\Node\NodeHalJsonAnonTest
+ * - \Drupal\Tests\hal\Functional\EntityResource\Node\NodeHalJsonBasicAuthTest
+ * - \Drupal\Tests\hal\Functional\EntityResource\Node\NodeHalJsonCookieTest
+ *
+ * In other words: for every entity type there should be:
+ * 1. an abstract subclass that includes the entity type-specific authorization
+ * (permissions or perhaps custom access control handling, such as node
+ * grants), plus
+ * 2. a concrete subclass extending the abstract entity type-specific subclass
+ * that specifies the exact @code $format @endcode, @code $mimeType @endcode,
+ * @code $expectedErrorMimeType @endcode and @code $auth @endcode for this
+ * concrete test. Usually that's all that's necessary: most concrete
+ * subclasses will be very thin.
+ *
+ * For every of these concrete subclasses, a comprehensive test scenario will
+ * run per HTTP method:
+ * - ::testGet()
+ * - ::testPost()
+ * - ::testPatch()
+ * - ::testDelete()
+ *
+ * If there is an entity type-specific edge case scenario to test, then add that
+ * to the entity type-specific abstract subclass. Example:
+ * \Drupal\Tests\rest\Functional\EntityResource\Comment\CommentResourceTestBase::testPostDxWithoutCriticalBaseFields
+ *
+ * If there is an entity type-specific format-specific edge case to test, then
+ * add that to a concrete subclass. Example:
+ * \Drupal\Tests\hal\Functional\EntityResource\Comment\CommentHalJsonTestBase::$patchProtectedFieldNames
*/
abstract class EntityResourceTestBase extends ResourceTestBase {
@@ -31,14 +68,14 @@
*
* @var string
*/
- protected static $entityType = NULL;
+ protected static $entityTypeId = NULL;
/**
* The fields that are protected against modification during PATCH requests.
*
* @var string[]
*/
- protected static $patchProtectedFields;
+ protected static $patchProtectedFieldNames;
/**
* Optionally specify which field is the 'label' field. Some entities specify
@@ -48,7 +85,7 @@
*
* @var string|null
*/
- protected static $labelField = NULL;
+ protected static $labelFieldName = NULL;
/**
* The entity ID for the first created entity in testPost().
@@ -105,7 +142,7 @@ protected function provisionEntityResource() {
// It's possible to not have any authentication providers enabled, when
// testing public (anonymous) usage of a REST resource.
$auth = isset(static::$auth) ? [static::$auth] : [];
- $this->provisionResource('entity.' . static::$entityType, [static::$format], $auth);
+ $this->provisionResource('entity.' . static::$entityTypeId, [static::$format], $auth);
}
/**
@@ -116,7 +153,7 @@ public function setUp() {
$this->serializer = $this->container->get('serializer');
$this->entityStorage = $this->container->get('entity_type.manager')
- ->getStorage(static::$entityType);
+ ->getStorage(static::$entityTypeId);
// Set up a HTTP client that accepts relative URLs.
$this->httpClient = $this->container->get('http_client_factory')
@@ -128,14 +165,14 @@ public function setUp() {
if ($this->entity instanceof FieldableEntityInterface) {
// Add access-protected field.
FieldStorageConfig::create([
- 'entity_type' => static::$entityType,
+ 'entity_type' => static::$entityTypeId,
'field_name' => 'field_rest_test',
'type' => 'text',
])
->setCardinality(1)
->save();
FieldConfig::create([
- 'entity_type' => static::$entityType,
+ 'entity_type' => static::$entityTypeId,
'field_name' => 'field_rest_test',
'bundle' => $this->entity->bundle(),
])
@@ -202,6 +239,9 @@ protected function getNormalizedPatchEntity() {
return $this->getNormalizedPostEntity();
}
+ /**
+ * Tests GETting an entity, plus edge cases to ensure good DX.
+ */
public function testGet() {
$this->initAuthentication();
$has_canonical_url = $this->entity->hasLinkTemplate('canonical');
@@ -325,7 +365,7 @@ public function testGet() {
$this->assertResourceErrorResponse(403, '', $response);
- $this->grantPermissionsToTestedRole(['restful get entity:' . static::$entityType]);
+ $this->grantPermissionsToTestedRole(['restful get entity:' . static::$entityTypeId]);
// 200 for well-formed request.
@@ -352,29 +392,21 @@ public function testGet() {
$this->assertSame([static::$expectedErrorMimeType], $response->getHeader('Content-Type'));
- $url = Url::fromRoute('rest.entity.' . static::$entityType . '.GET.' . static::$format);
- $url->setRouteParameter(static::$entityType, 987654321);
+ $url = Url::fromRoute('rest.entity.' . static::$entityTypeId . '.GET.' . static::$format);
+ $url->setRouteParameter(static::$entityTypeId, 987654321);
$url->setOption('query', ['_format' => static::$format]);
// DX: 404 when GETting non-existing entity.
$response = $this->request('GET', $url, $request_options);
- $path = str_replace('987654321', '{' . static::$entityType . '}', $url->setAbsolute()->setOptions(['base_url' => '', 'query' => []])->toString());
- $message = 'The "' . static::$entityType . '" parameter was not converted for the path "' . $path . '" (route name: "rest.entity.' . static::$entityType . '.GET.' . static::$format . '")';
+ $path = str_replace('987654321', '{' . static::$entityTypeId . '}', $url->setAbsolute()->setOptions(['base_url' => '', 'query' => []])->toString());
+ $message = 'The "' . static::$entityTypeId . '" parameter was not converted for the path "' . $path . '" (route name: "rest.entity.' . static::$entityTypeId . '.GET.' . static::$format . '")';
$this->assertResourceErrorResponse(404, $message, $response);
}
/**
- * Gets an entity resource's GET/PATCH/DELETE URL.
- *
- * @return \Drupal\Core\Url
- * The URL to GET/PATCH/DELETE.
+ * Tests POSTing an entity, plus edge cases to ensure good DX.
*/
- protected function getUrl() {
- $has_canonical_url = $this->entity->hasLinkTemplate('canonical');
- return $has_canonical_url ? $this->entity->toUrl() : Url::fromUri('base:entity/' . static::$entityType . '/' . $this->entity->id());
- }
-
public function testPost() {
// @todo Remove this in https://www.drupal.org/node/2300677.
if ($this->entity instanceof ConfigEntityInterface) {
@@ -494,7 +526,7 @@ 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::$labelField;
+ $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
$label_field_capitalized = ucfirst($label_field);
// @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813755 lands.
// $this->assertErrorResponse(422, "Unprocessable Entity: validation failed.\ntitle: Title: this field cannot hold more than 1 values.\n", $response);
@@ -548,7 +580,7 @@ public function testPost() {
$this->assertResourceErrorResponse(403, '', $response);
- $this->grantPermissionsToTestedRole(['restful post entity:' . static::$entityType]);
+ $this->grantPermissionsToTestedRole(['restful post entity:' . static::$entityTypeId]);
// 201 for well-formed request.
@@ -557,6 +589,9 @@ public function testPost() {
$this->assertSame([str_replace($this->entity->id(), static::$secondCreatedEntityId, $this->entity->toUrl('canonical')->setAbsolute(TRUE)->toString())], $response->getHeader('Location'));
}
+ /**
+ * Tests PATCHing 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) {
@@ -674,7 +709,7 @@ 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::$labelField;
+ $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
$label_field_capitalized = ucfirst($label_field);
// @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813755 lands.
// $this->assertErrorResponse(422, "Unprocessable Entity: validation failed.\ntitle: Title: this field cannot hold more than 1 values.\n", $response);
@@ -697,15 +732,15 @@ public function testPatch() {
// the normalization, send another request, assert the next PATCH-protected
// field error message. And so on.
$max_normalization = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
- for ($i = 0; $i < count(static::$patchProtectedFields); $i++) {
- $max_normalization = $this->removeFieldsFromNormalization($max_normalization, array_slice(static::$patchProtectedFields, 0, $i));
+ for ($i = 0; $i < count(static::$patchProtectedFieldNames); $i++) {
+ $max_normalization = $this->removeFieldsFromNormalization($max_normalization, array_slice(static::$patchProtectedFieldNames, 0, $i));
$request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format);
$response = $this->request('PATCH', $url, $request_options);
- $this->assertResourceErrorResponse(403, "Access denied on updating field '" . static::$patchProtectedFields[$i] . "'.", $response);
+ $this->assertResourceErrorResponse(403, "Access denied on updating field '" . static::$patchProtectedFieldNames[$i] . "'.", $response);
}
// 200 for well-formed request that sends the maximum number of fields.
- $max_normalization = $this->removeFieldsFromNormalization($max_normalization, static::$patchProtectedFields);
+ $max_normalization = $this->removeFieldsFromNormalization($max_normalization, static::$patchProtectedFieldNames);
$request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format);
$response = $this->request('PATCH', $url, $request_options);
$this->assertResourceResponse(200, FALSE, $response);
@@ -739,7 +774,7 @@ public function testPatch() {
$this->assertResourceErrorResponse(403, '', $response);
- $this->grantPermissionsToTestedRole(['restful patch entity:' . static::$entityType]);
+ $this->grantPermissionsToTestedRole(['restful patch entity:' . static::$entityTypeId]);
// 200 for well-formed request.
@@ -747,6 +782,9 @@ public function testPatch() {
$this->assertResourceResponse(200, FALSE, $response);
}
+ /**
+ * Tests DELETEing 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) {
@@ -834,7 +872,7 @@ public function testDelete() {
$this->assertResourceErrorResponse(403, '', $response);
- $this->grantPermissionsToTestedRole(['restful delete entity:' . static::$entityType]);
+ $this->grantPermissionsToTestedRole(['restful delete entity:' . static::$entityTypeId]);
// 204 for well-formed request.
@@ -845,6 +883,18 @@ public function testDelete() {
$this->assertSame('', $response->getBody()->getContents());
}
+
+ /**
+ * Gets an entity resource's GET/PATCH/DELETE URL.
+ *
+ * @return \Drupal\Core\Url
+ * The URL to GET/PATCH/DELETE.
+ */
+ protected function getUrl() {
+ $has_canonical_url = $this->entity->hasLinkTemplate('canonical');
+ return $has_canonical_url ? $this->entity->toUrl() : Url::fromUri('base:entity/' . static::$entityTypeId . '/' . $this->entity->id());
+ }
+
/**
* Gets an entity resource's POST URL.
*
@@ -853,7 +903,7 @@ public function testDelete() {
*/
protected function getPostUrl() {
$has_canonical_url = $this->entity->hasLinkTemplate('https://www.drupal.org/link-relations/create');
- return $has_canonical_url ? $this->entity->toUrl() : Url::fromUri('base:entity/' . static::$entityType);
+ return $has_canonical_url ? $this->entity->toUrl() : Url::fromUri('base:entity/' . static::$entityTypeId);
}
/**
@@ -867,7 +917,7 @@ protected function getPostUrl() {
*/
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::$labelField;
+ $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
$normalization[$label_field][1]['value'] = 'Second Title';
return $normalization;
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonBasicAuthTest.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonBasicAuthTest.php
index 4625ed3..be75784 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonBasicAuthTest.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestJsonBasicAuthTest.php
@@ -42,5 +42,4 @@ class EntityTestJsonBasicAuthTest extends EntityTestResourceTestBase {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
-
}
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php
index 437d9eb..da86d00 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php
@@ -16,12 +16,12 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'entity_test';
+ protected static $entityTypeId = 'entity_test';
/**
* {@inheritdoc}
*/
- protected static $patchProtectedFields = [];
+ protected static $patchProtectedFieldNames = [];
/**
* @var \Drupal\entity_test\Entity\EntityTest
@@ -50,10 +50,10 @@ protected function setUpAuthorization($method) {
* {@inheritdoc}
*/
protected function createEntity() {
- $entity_test = EntityTest::create(array(
+ $entity_test = EntityTest::create([
'name' => 'Llama',
'type' => 'entity_test',
- ));
+ ]);
$entity_test->setOwnerId(0);
$entity_test->save();
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
index bb8b495..d7651c4 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
@@ -17,12 +17,12 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'node';
+ protected static $entityTypeId = 'node';
/**
* {@inheritdoc}
*/
- protected static $patchProtectedFields = [
+ protected static $patchProtectedFieldNames = [
'uid',
'created',
'changed',
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleResourceTestBase.php
index 4cc247c..5ea3154 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleResourceTestBase.php
@@ -16,7 +16,7 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'user_role';
+ protected static $entityTypeId = 'user_role';
/**
* @var \Drupal\user\RoleInterface
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
index d8bc693..b6dce4f 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
@@ -16,12 +16,12 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'taxonomy_term';
+ protected static $entityTypeId = 'taxonomy_term';
/**
* {@inheritdoc}
*/
- protected static $patchProtectedFields = [
+ protected static $patchProtectedFieldNames = [
'changed',
];
@@ -41,7 +41,7 @@ protected function setUpAuthorization($method) {
case 'POST':
case 'PATCH':
case 'DELETE':
- // @todo Create issue similar to https://www.drupal.org/node/2808217.
+ // @todo Update once https://www.drupal.org/node/2824408 lands.
$this->grantPermissionsToTestedRole(['administer taxonomy']);
break;
}
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php
index 5630fd5..195e1ad 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php
@@ -17,12 +17,12 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'user';
+ protected static $entityTypeId = 'user';
/**
* {@inheritdoc}
*/
- protected static $patchProtectedFields = [
+ protected static $patchProtectedFieldNames = [
'changed',
];
@@ -34,7 +34,7 @@
/**
* {@inheritdoc}
*/
- protected static $labelField = 'name';
+ protected static $labelFieldName = 'name';
/**
* {@inheritdoc}
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php
index e812fbb..97dcb79 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php
@@ -18,7 +18,7 @@
/**
* {@inheritdoc}
*/
- protected static $entityType = 'taxonomy_vocabulary';
+ protected static $entityTypeId = 'taxonomy_vocabulary';
/**
* @var \Drupal\taxonomy\VocabularyInterface
diff --git a/core/modules/rest/tests/src/Functional/ResourceTestBase.php b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
index 549ebad..191b9e2 100644
--- a/core/modules/rest/tests/src/Functional/ResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
@@ -13,7 +13,13 @@
use Psr\Http\Message\ResponseInterface;
/**
- * Subclass this for every REST resource, every format and every auth mechanism.
+ * Subclass this for every REST resource, every format and every auth provider.
+ *
+ * For more guidance see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase
+ * which has recommendations for testing the \Drupal\rest\Plugin\rest\resource\EntityResource
+ * REST resource for every format and every auth provider. It's a special case
+ * (because that single REST resource generates supports not just one thing, but
+ * many things — multiple entity types), but the same principles apply.
*/
abstract class ResourceTestBase extends BrowserTestBase {