.../Comment/CommentHalJsonAnonTest.php | 3 +- .../Comment/CommentHalJsonTestBase.php | 2 + .../EntityResource/HalEntityNormalizationTrait.php | 2 +- .../HalJsonBasicAuthWorkaroundFor2805281Trait.php | 2 +- .../src/Functional/CookieResourceTestTrait.php | 10 +++-- .../EntityResource/EntityResourceTestBase.php | 50 ++++++++++++---------- .../EntityResource/Role/RoleJsonBasicAuthTest.php | 2 +- .../JsonBasicAuthWorkaroundFor2805281Trait.php | 2 +- 8 files changed, 42 insertions(+), 31 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 ffcefc8..9313640 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonAnonTest.php @@ -19,7 +19,8 @@ class CommentHalJsonAnonTest extends CommentHalJsonTestBase { * @see \Drupal\comment\CommentAccessControlHandler::checkAccess * * Therefore we grant them the 'administer comments' permission for the - * purpose of this test. + * purpose of this test. Then they are able to edit their own comments, but + * some fields are still not editable, even with that permission. * * @see ::setUpAuthorization */ 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 1d07465..5328d11 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php @@ -60,6 +60,8 @@ protected function getExpectedNormalizedEntity() { $normalization = $this->applyHalFieldNormalization($default_normalization); + // Because \Drupal\comment\Entity\Comment::getOwner() generates an in-memory + // User entity without a UUID, we cannot use it. $author = User::load($this->entity->getOwnerId()); $commented_entity = EntityTest::load(1); return $normalization + [ diff --git a/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php b/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php index 0b3bb52..16cca6e 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/HalEntityNormalizationTrait.php @@ -46,7 +46,7 @@ protected function applyHalFieldNormalization(array $normalization) { // bundle field. $bundle_key = $this->entity->getEntityType()->getKey('bundle'); $reference_fields = array_keys(array_filter($this->entity->getFields(), function (FieldItemListInterface $field) use ($bundle_key) { - return ($field->getName() === $bundle_key) ? FALSE : $field instanceof EntityReferenceFieldItemListInterface; + return $field instanceof EntityReferenceFieldItemListInterface && $field->getName() !== $bundle_key; })); foreach ($reference_fields as $field_name) { unset($normalization[$field_name]); diff --git a/core/modules/hal/tests/src/Functional/HalJsonBasicAuthWorkaroundFor2805281Trait.php b/core/modules/hal/tests/src/Functional/HalJsonBasicAuthWorkaroundFor2805281Trait.php index 50bfdb6..5661d33 100644 --- a/core/modules/hal/tests/src/Functional/HalJsonBasicAuthWorkaroundFor2805281Trait.php +++ b/core/modules/hal/tests/src/Functional/HalJsonBasicAuthWorkaroundFor2805281Trait.php @@ -20,6 +20,6 @@ protected function assertResponseWhenMissingAuthentication(ResponseInterface $re // @todo this works fine locally, but on testbot it comes back with // 'text/plain; charset=UTF-8'. WTF. // $this->assertSame(['application/hal+json'], $response->getHeader('Content-Type')); - $this->assertSame('No authentication credentials provided.', (string)$response->getBody()); + $this->assertSame('No authentication credentials provided.', (string) $response->getBody()); } } diff --git a/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php b/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php index 1d39407..c231ffb 100644 --- a/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php +++ b/core/modules/rest/tests/src/Functional/CookieResourceTestTrait.php @@ -81,7 +81,8 @@ protected function initAuthentication() { */ protected function getAuthenticationRequestOptions($method) { $request_options[RequestOptions::HEADERS]['Cookie'] = $this->sessionCookie; - if (!in_array($method, ['HEAD', 'GET'])) { + // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html + if (!in_array($method, ['HEAD', 'GET', 'OPTIONS', 'TRACE'])) { $request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken; } return $request_options; @@ -98,9 +99,10 @@ protected function assertResponseWhenMissingAuthentication(ResponseInterface $re * {@inheritdoc} */ protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) { - // X-CSRF-Token request header is unnecessary for safe HTTP methods. No need - // for additional assertions. - if (in_array($method, ['HEAD', 'GET'])) { + // X-CSRF-Token request header is unnecessary for safe and side effect-free + // HTTP methods. No need for additional assertions. + // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html + if (in_array($method, ['HEAD', 'GET', 'OPTIONS', 'TRACE'])) { return; } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 15690d6..41625fe 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\rest\Functional\EntityResource; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\EntityChangedInterface; @@ -286,7 +287,8 @@ public function testGet() { $request_options = []; - // DX: 404 when resource not provisioned, 403 if canonical route. + // DX: 404 when resource not provisioned, 403 if canonical route. HTML + // response because missing ?_format query string. $response = $this->request('GET', $url, $request_options); $this->assertSame($has_canonical_url ? 403 : 404, $response->getStatusCode()); $this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type')); @@ -295,7 +297,8 @@ public function testGet() { $url->setOption('query', ['_format' => static::$format]); - // DX: 404 when resource not provisioned, 403 if canonical route. + // DX: 404 when resource not provisioned, 403 if canonical route. Non-HTML + // response because ?_format query string is present. $response = $this->request('GET', $url, $request_options); if ($has_canonical_url) { $this->assertResourceErrorResponse(403, '', $response); @@ -332,7 +335,7 @@ public function testGet() { $this->assertResponseWhenMissingAuthentication($response); } - $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('GET')); + $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('GET')); // DX: 403 when unauthorized. @@ -372,15 +375,15 @@ public function testGet() { // fields does not matter (at least not yet). That's why we only compare the // normalized entity with the decoded response: it's comparing PHP arrays // instead of strings. - $this->assertEquals($this->getExpectedNormalizedEntity(), $this->serializer->decode((string)$response->getBody(), static::$format)); + $this->assertEquals($this->getExpectedNormalizedEntity(), $this->serializer->decode((string) $response->getBody(), static::$format)); // Not only assert the normalization, also assert deserialization of the // response results in the expected object. - $unserialized = $this->serializer->deserialize((string)$response->getBody(), get_class($this->entity), static::$format); + $unserialized = $this->serializer->deserialize((string) $response->getBody(), get_class($this->entity), static::$format); $this->assertSame($unserialized->uuid(), $this->entity->uuid()); $get_headers = $response->getHeaders(); - // Verify that the GET and HEAD responses are the same, that the only - // difference is that there's no body. + // Verify that the GET and HEAD responses are the same. The only difference + // is that there's no body. $ignored_headers = ['Date', 'Content-Length', 'X-Drupal-Cache', 'X-Drupal-Dynamic-Cache']; foreach ($ignored_headers as $ignored_header) { unset($head_headers[$ignored_header]); @@ -499,7 +502,7 @@ public function testPost() { if ($has_canonical_url) { $this->assertSame(415, $response->getStatusCode()); $this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type')); - $this->assertTrue(FALSE !== strpos($response->getBody()->getContents(), htmlspecialchars('No "Content-Type" request header specified'))); + $this->assertContains(htmlspecialchars('No "Content-Type" request header specified'), $response->getBody()->getContents()); } else { $this->assertResourceErrorResponse(415, 'No "Content-Type" request header specified', $response); @@ -546,7 +549,7 @@ public function testPost() { } - $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('POST')); + $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('POST')); // DX: 403 when unauthorized. @@ -747,7 +750,7 @@ public function testPatch() { } - $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH')); + $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('PATCH')); // DX: 403 when unauthorized. @@ -904,7 +907,7 @@ public function testDelete() { } - $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH')); + $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('PATCH')); // DX: 403 when unauthorized. @@ -962,22 +965,25 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques // \Drupal\serialization\Normalizer\EntityNormalizer::denormalize(): entity // types with bundles MUST send their bundle field to be denormalizable. $entity_type = $this->entity->getEntityType(); - if ($entity_type->hasKey('bundle') && $entity_type->getBundleEntityType()) { + if ($entity_type->hasKey('bundle')) { $bundle_field_name = $this->entity->getEntityType()->getKey('bundle'); $normalization = $this->getNormalizedPostEntity(); - - $normalization[$bundle_field_name] = 'bad_bundle_name'; - $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format); + // The bundle type itself can be validated only if there's a bundle entity + // type. + if ($entity_type->getBundleEntityType()) { + $normalization[$bundle_field_name] = 'bad_bundle_name'; + $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format); - // DX: 400 when incorrect entity type bundle is specified. - $response = $this->request($method, $url, $request_options); - // @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813853 lands. -// $this->assertResourceErrorResponse(400, '"bad_bundle_name" is not a valid bundle type for denormalization.', $response); - $this->assertSame(400, $response->getStatusCode()); - $this->assertSame([static::$mimeType], $response->getHeader('Content-Type')); - $this->assertSame($this->serializer->encode(['error' => '"bad_bundle_name" is not a valid bundle type for denormalization.'], static::$format), (string) $response->getBody()); + // DX: 400 when incorrect entity type bundle is specified. + $response = $this->request($method, $url, $request_options); + // @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813853 lands. + // $this->assertResourceErrorResponse(400, '"bad_bundle_name" is not a valid bundle type for denormalization.', $response); + $this->assertSame(400, $response->getStatusCode()); + $this->assertSame([static::$mimeType], $response->getHeader('Content-Type')); + $this->assertSame($this->serializer->encode(['error' => '"bad_bundle_name" is not a valid bundle type for denormalization.'], static::$format), (string) $response->getBody()); + } unset($normalization[$bundle_field_name]); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleJsonBasicAuthTest.php b/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleJsonBasicAuthTest.php index b03ab5c..75fcd08 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleJsonBasicAuthTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleJsonBasicAuthTest.php @@ -42,7 +42,7 @@ class RoleJsonBasicAuthTest extends RoleResourceTestBase { */ protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) { $this->assertSame(401, $response->getStatusCode()); - $this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string)$response->getBody()); + $this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string) $response->getBody()); } } diff --git a/core/modules/rest/tests/src/Functional/JsonBasicAuthWorkaroundFor2805281Trait.php b/core/modules/rest/tests/src/Functional/JsonBasicAuthWorkaroundFor2805281Trait.php index c392f0c..495bf5a 100644 --- a/core/modules/rest/tests/src/Functional/JsonBasicAuthWorkaroundFor2805281Trait.php +++ b/core/modules/rest/tests/src/Functional/JsonBasicAuthWorkaroundFor2805281Trait.php @@ -19,7 +19,7 @@ protected function assertResponseWhenMissingAuthentication(ResponseInterface $re // Note that strange 'A fatal error occurred: ' prefix, that should not // exist. // @todo Fix in https://www.drupal.org/node/2805281. - $this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string)$response->getBody()); + $this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string) $response->getBody()); } }