Update core 8.3.0

This commit is contained in:
Rob Davies 2017-04-13 15:53:35 +01:00
parent da7a7918f8
commit cd7a898e66
6144 changed files with 132297 additions and 87747 deletions

View file

@ -10,24 +10,6 @@ use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Access\AccessResult;
/**
* Implements hook_rest_type_uri_alter().
*/
function rest_test_rest_type_uri_alter(&$uri, $context = array()) {
if (!empty($context['rest_test'])) {
$uri = 'rest_test_type';
}
}
/**
* Implements hook_rest_relation_uri_alter().
*/
function rest_test_rest_relation_uri_alter(&$uri, $context = array()) {
if (!empty($context['rest_test'])) {
$uri = 'rest_test_relation';
}
}
/**
* Implements hook_entity_field_access().
*

View file

@ -0,0 +1,9 @@
services:
rest_test.authentication.test_auth:
class: Drupal\rest_test\Authentication\Provider\TestAuth
tags:
- { name: authentication_provider, provider_id: 'rest_test_auth' }
rest_test.authentication.test_auth_global:
class: Drupal\rest_test\Authentication\Provider\TestAuthGlobal
tags:
- { name: authentication_provider, provider_id: 'rest_test_auth_global', global: TRUE }

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\rest_test\Authentication\Provider;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Authentication provider for testing purposes.
*/
class TestAuth implements AuthenticationProviderInterface {
/**
* {@inheritdoc}
*/
public function applies(Request $request) {
return $request->headers->has('REST-test-auth');
}
/**
* {@inheritdoc}
*/
public function authenticate(Request $request) {
return NULL;
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\rest_test\Authentication\Provider;
use Drupal\Core\Authentication\AuthenticationProviderInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Global authentication provider for testing purposes.
*/
class TestAuthGlobal implements AuthenticationProviderInterface {
/**
* {@inheritdoc}
*/
public function applies(Request $request) {
return $request->headers->has('REST-test-auth-global');
}
/**
* {@inheritdoc}
*/
public function authenticate(Request $request) {
return NULL;
}
}

View file

@ -53,10 +53,8 @@ trait CookieResourceTestTrait {
* {@inheritdoc}
*/
protected function initAuthentication() {
// @todo Remove hardcoded use of the 'json' format, and use static::$format
// + static::$mimeType instead in https://www.drupal.org/node/2820888.
$user_login_url = Url::fromRoute('user.login.http')
->setRouteParameter('_format', 'json');
->setRouteParameter('_format', static::$format);
$request_body = [
'name' => $this->account->name->value,
@ -64,7 +62,9 @@ trait CookieResourceTestTrait {
];
$request_options[RequestOptions::BODY] = $this->serializer->encode($request_body, 'json');
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/json';
$request_options[RequestOptions::HEADERS] = [
'Content-Type' => static::$mimeType,
];
$response = $this->request('POST', $user_login_url, $request_options);
// Parse and store the session cookie.
@ -92,7 +92,10 @@ trait CookieResourceTestTrait {
* {@inheritdoc}
*/
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
$this->assertResourceErrorResponse(403, '', $response);
// Requests needing cookie authentication but missing it results in a 403
// response. The cookie authentication mechanism sets no response message.
// @todo https://www.drupal.org/node/2847623
$this->assertResourceErrorResponse(403, FALSE, $response);
}
/**

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Action;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ActionJsonAnonTest extends ActionResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Action;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ActionJsonBasicAuthTest extends ActionResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Action;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ActionJsonCookieTest extends ActionResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,89 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Action;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\system\Entity\Action;
use Drupal\user\RoleInterface;
abstract class ActionResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['user'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'action';
/**
* @var \Drupal\system\ActionConfigEntityInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer actions']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$action = Action::create([
'id' => 'user_add_role_action.' . RoleInterface::ANONYMOUS_ID,
'type' => 'user',
'label' => t('Add the anonymous role to the selected users'),
'configuration' => [
'rid' => RoleInterface::ANONYMOUS_ID,
],
'plugin' => 'user_add_role_action',
]);
$action->save();
return $action;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'configuration' => [
'rid' => 'anonymous',
],
'dependencies' => [
'config' => ['user.role.anonymous'],
'module' => ['user'],
],
'id' => 'user_add_role_action.anonymous',
'label' => 'Add the anonymous role to the selected users',
'langcode' => 'en',
'plugin' => 'user_add_role_action',
'status' => TRUE,
'type' => 'user',
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return [
'user.permissions',
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
}

View file

@ -21,9 +21,4 @@ class BlockJsonAnonTest extends BlockResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
}

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -27,19 +26,9 @@ class BlockJsonBasicAuthTest extends BlockResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View file

@ -21,11 +21,6 @@ class BlockJsonCookieTest extends BlockResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -113,7 +113,7 @@ abstract class BlockResourceTestBase extends EntityResourceTestBase {
*/
protected function getExpectedCacheContexts() {
// @see ::createEntity()
return [];
return ['url.site'];
}
/**
@ -122,9 +122,25 @@ abstract class BlockResourceTestBase extends EntityResourceTestBase {
protected function getExpectedCacheTags() {
// Because the 'user.permissions' cache context is missing, the cache tag
// for the anonymous user role is never added automatically.
return array_filter(parent::getExpectedCacheTags(), function ($tag) {
return array_values(array_filter(parent::getExpectedCacheTags(), function ($tag) {
return $tag !== 'config:user.role.anonymous';
});
}));
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "You are not authorized to view this block entity.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -21,11 +21,6 @@ class CommentJsonAnonTest extends CommentResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -27,19 +26,9 @@ class CommentJsonBasicAuthTest extends CommentResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View file

@ -21,11 +21,6 @@ class CommentJsonCookieTest extends CommentResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -28,6 +28,7 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [
'status',
'pid',
'entity_id',
'uid',
@ -35,7 +36,6 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
'homepage',
'created',
'changed',
'status',
'thread',
'entity_type',
'field_name',
@ -87,10 +87,10 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
$this->addDefaultCommentField('entity_test', 'bar', 'comment');
// Create a "Camelids" test entity that the comment will be assigned to.
$commented_entity = EntityTest::create(array(
$commented_entity = EntityTest::create([
'name' => 'Camelids',
'type' => 'bar',
));
]);
$commented_entity->save();
// Create a "Llama" comment.
@ -144,17 +144,17 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
],
'status' => [
[
'value' => 1,
'value' => TRUE,
],
],
'created' => [
[
'value' => '123456789',
'value' => 123456789,
],
],
'changed' => [
[
'value' => '123456789',
'value' => $this->entity->getChangedTime(),
],
],
'default_langcode' => [
@ -164,7 +164,7 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
],
'uid' => [
[
'target_id' => $author->id(),
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => base_path() . 'user/' . $author->id(),
@ -178,7 +178,7 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
],
'entity_id' => [
[
'target_id' => '1',
'target_id' => 1,
'target_type' => 'entity_test',
'target_uuid' => EntityTest::load(1)->uuid(),
'url' => base_path() . 'entity_test/1',
@ -278,8 +278,10 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
// DX: 422 when missing 'entity_type' field.
$request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['entity_type' => TRUE]), static::$format);
$response = $this->request('POST', $url, $request_options);
// @todo Uncomment, remove next line in https://www.drupal.org/node/2820364.
$this->assertResourceErrorResponse(500, 'A fatal error occurred: Internal Server Error', $response);
// @todo Uncomment, remove next 3 lines in https://www.drupal.org/node/2820364.
$this->assertSame(500, $response->getStatusCode());
$this->assertSame(['application/json'], $response->getHeader('Content-Type'));
$this->assertSame('{"message":"A fatal error occurred: Internal Server Error"}', (string) $response->getBody());
//$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nentity_type: This value should not be null.\n", $response);
// DX: 422 when missing 'entity_id' field.
@ -301,9 +303,29 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
// DX: 422 when missing 'entity_type' field.
$request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['field_name' => TRUE]), static::$format);
$response = $this->request('POST', $url, $request_options);
// @todo Uncomment, remove next line in https://www.drupal.org/node/2820364.
$this->assertResourceErrorResponse(500, 'A fatal error occurred: Field is unknown.', $response);
// @todo Uncomment, remove next 3 lines in https://www.drupal.org/node/2820364.
$this->assertSame(500, $response->getStatusCode());
$this->assertSame(['application/json'], $response->getHeader('Content-Type'));
$this->assertSame('{"message":"A fatal error occurred: Field is unknown."}', (string) $response->getBody());
//$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nfield_name: This value should not be null.\n", $response);
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET';
return "The 'access comments' permission is required and the comment must be published.";
case 'POST';
return "The 'post comments' permission is required.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\CommentType;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class CommentTypeJsonAnonTest extends CommentTypeResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\CommentType;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class CommentTypeJsonBasicAuthTest extends CommentTypeResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\CommentType;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class CommentTypeJsonCookieTest extends CommentTypeResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,77 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\CommentType;
use Drupal\comment\Entity\CommentType;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for CommentType entity.
*/
abstract class CommentTypeResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'comment'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'comment_type';
/**
* The CommentType entity.
*
* @var \Drupal\comment\CommentTypeInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer comment types']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" comment type.
$camelids = CommentType::create([
'id' => 'camelids',
'label' => 'Camelids',
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'target_entity_type_id' => 'node',
]);
$camelids->save();
return $camelids;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'id' => 'camelids',
'label' => 'Camelids',
'langcode' => 'en',
'status' => TRUE,
'target_entity_type_id' => 'node',
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
}

View file

@ -21,9 +21,4 @@ class ConfigTestJsonAnonTest extends ConfigTestResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
}

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -27,19 +26,9 @@ class ConfigTestJsonBasicAuthTest extends ConfigTestResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View file

@ -21,11 +21,6 @@ class ConfigTestJsonCookieTest extends ConfigTestResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigurableLanguage;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ConfigurableLanguageJsonAnonTest extends ConfigurableLanguageResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigurableLanguage;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ConfigurableLanguageJsonBasicAuthTest extends ConfigurableLanguageResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigurableLanguage;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ConfigurableLanguageJsonCookieTest extends ConfigurableLanguageResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,77 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigurableLanguage;
use Drupal\Core\Cache\Cache;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
abstract class ConfigurableLanguageResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'configurable_language';
/**
* @var \Drupal\language\ConfigurableLanguageInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer languages']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$configurable_language = ConfigurableLanguage::create([
'id' => 'll',
'label' => 'Llama Language',
]);
$configurable_language->save();
return $configurable_language;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'direction' => 'ltr',
'id' => 'll',
'label' => 'Llama Language',
'langcode' => 'en',
'locked' => FALSE,
'status' => TRUE,
'uuid' => $this->entity->uuid(),
'weight' => 0,
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return Cache::mergeContexts(parent::getExpectedCacheContexts(), ['languages:language_interface']);
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
}

View file

@ -5,7 +5,6 @@ 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;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
@ -43,10 +42,9 @@ use Psr\Http\Message\ResponseInterface;
* (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.
* that specifies the exact @code $format @endcode, @code $mimeType @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:
@ -111,11 +109,6 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
*/
protected static $secondCreatedEntityId = 3;
/**
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* The main entity used for testing.
*
@ -138,13 +131,13 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
public static $modules = ['rest_test', 'text'];
/**
* {@inheritdoc}
* Provides an entity resource.
*/
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::$entityTypeId, [static::$format], $auth);
$this->provisionResource([static::$format], $auth);
}
/**
@ -153,14 +146,13 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
public function setUp() {
parent::setUp();
// Calculate REST Resource config entity ID.
static::$resourceConfigId = 'entity.' . static::$entityTypeId;
$this->serializer = $this->container->get('serializer');
$this->entityStorage = $this->container->get('entity_type.manager')
->getStorage(static::$entityTypeId);
// Set up a HTTP client that accepts relative URLs.
$this->httpClient = $this->container->get('http_client_factory')
->fromOptions(['base_uri' => $this->baseUrl]);
// Create an entity.
$this->entity = $this->createEntity();
@ -187,18 +179,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// Set a default value on the field.
$this->entity->set('field_rest_test', ['value' => 'All the faith he had had had had no effect on the outcome of his life.']);
// @todo Remove in this if-test in https://www.drupal.org/node/2808335.
if ($this->entity instanceof EntityChangedInterface) {
$changed = $this->entity->getChangedTime();
$this->entity->setChangedTime(42);
$this->entity->save();
$this->entity->setChangedTime($changed);
}
$this->entity->save();
}
// @todo Remove this in https://www.drupal.org/node/2815845.
drupal_flush_all_caches();
}
/**
@ -241,6 +223,43 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
return $this->getNormalizedPostEntity();
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return $this->getExpectedBCUnauthorizedAccessMessage($method);
}
$permission = $this->entity->getEntityType()->getAdminPermission();
if ($permission !== FALSE) {
return "The '{$permission}' permission is required.";
}
$http_method_to_entity_operation = [
'GET' => 'view',
'POST' => 'create',
'PATCH' => 'update',
'DELETE' => 'delete',
];
$operation = $http_method_to_entity_operation[$method];
$message = sprintf('You are not authorized to %s this %s entity', $operation, $this->entity->getEntityTypeId());
if ($this->entity->bundle() !== $this->entity->getEntityTypeId()) {
$message .= ' of bundle ' . $this->entity->bundle();
}
return "$message.";
}
/**
* {@inheritdoc}
*/
protected function getExpectedBcUnauthorizedAccessMessage($method) {
return "The 'restful " . strtolower($method) . " entity:" . $this->entity->getEntityTypeId() . "' permission is required.";
}
/**
* The expected cache tags for the GET/HEAD response of the test entity.
*
@ -255,6 +274,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
if (!static::$auth) {
$expected_cache_tags[] = 'config:user.role.anonymous';
}
$expected_cache_tags[] = 'http_response';
return Cache::mergeTags($expected_cache_tags, $this->entity->getCacheTags());
}
@ -267,6 +287,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
*/
protected function getExpectedCacheContexts() {
return [
'url.site',
'user.permissions',
];
}
@ -301,7 +322,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// response because ?_format query string is present.
$response = $this->request('GET', $url, $request_options);
if ($has_canonical_url) {
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('GET'), $response);
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "GET ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
@ -335,13 +356,28 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$this->assertResponseWhenMissingAuthentication($response);
}
$request_options[RequestOptions::HEADERS]['REST-test-auth'] = '1';
// DX: 403 when attempting to use unallowed authentication provider.
$response = $this->request('GET', $url, $request_options);
$this->assertResourceErrorResponse(403, 'The used authentication method is not allowed on this route.', $response);
unset($request_options[RequestOptions::HEADERS]['REST-test-auth']);
$request_options[RequestOptions::HEADERS]['REST-test-auth-global'] = '1';
// DX: 403 when attempting to use unallowed global authentication provider.
$response = $this->request('GET', $url, $request_options);
$this->assertResourceErrorResponse(403, 'The used authentication method is not allowed on this route.', $response);
unset($request_options[RequestOptions::HEADERS]['REST-test-auth-global']);
$request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('GET'));
// DX: 403 when unauthorized.
$response = $this->request('GET', $url, $request_options);
// @todo Update the message in https://www.drupal.org/node/2808233.
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('GET'), $response);
$this->assertArrayNotHasKey('Link', $response->getHeaders());
$this->setUpAuthorization('GET');
@ -371,15 +407,38 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$this->assertEquals($this->getExpectedCacheTags(), empty($cache_tags_header_value) ? [] : explode(' ', $cache_tags_header_value));
$cache_contexts_header_value = $response->getHeader('X-Drupal-Cache-Contexts')[0];
$this->assertEquals($this->getExpectedCacheContexts(), empty($cache_contexts_header_value) ? [] : explode(' ', $cache_contexts_header_value));
// Comparing the exact serialization is pointless, because the order of
// 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));
// Sort the serialization data first so we can do an identical comparison
// for the keys with the array order the same (it needs to match with
// identical comparison).
$expected = $this->getExpectedNormalizedEntity();
ksort($expected);
$actual = $this->serializer->decode((string) $response->getBody(), static::$format);
ksort($actual);
$this->assertSame($expected, $actual);
// 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);
$this->assertSame($unserialized->uuid(), $this->entity->uuid());
// Finally, assert that the expected 'Link' headers are present.
if ($this->entity->getEntityType()->getLinkTemplates()) {
$this->assertArrayHasKey('Link', $response->getHeaders());
$link_relation_type_manager = $this->container->get('plugin.manager.link_relation_type');
$expected_link_relation_headers = array_map(function ($rel) use ($link_relation_type_manager) {
$definition = $link_relation_type_manager->getDefinition($rel, FALSE);
return (!empty($definition['uri']))
? $definition['uri']
: $rel;
}, array_keys($this->entity->getEntityType()->getLinkTemplates()));
$parse_rel_from_link_header = function ($value) use ($link_relation_type_manager) {
$matches = [];
if (preg_match('/rel="([^"]+)"/', $value, $matches) === 1) {
return $matches[1];
}
return FALSE;
};
$this->assertSame($expected_link_relation_headers, array_map($parse_rel_from_link_header, $response->getHeader('Link')));
}
$get_headers = $response->getHeaders();
// Verify that the GET and HEAD responses are the same. The only difference
@ -394,16 +453,42 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
}
$this->assertSame($get_headers, $head_headers);
// Only run this for fieldable entities. It doesn't make sense for config
// entities as config values are already casted. They also run through the
// ConfigEntityNormalizer, which doesn't deal with fields individually.
if ($this->entity instanceof FieldableEntityInterface) {
$this->config('serialization.settings')->set('bc_primitives_as_strings', TRUE)->save(TRUE);
// Rebuild the container so new config is reflected in the removal of the
// PrimitiveDataNormalizer.
$this->rebuildAll();
$response = $this->request('GET', $url, $request_options);
$this->assertResourceResponse(200, FALSE, $response);
// Again do an identical comparison, but this time transform the expected
// normalized entity's values to strings. This ensures the BC layer for
// bc_primitives_as_strings works as expected.
$expected = $this->getExpectedNormalizedEntity();
// Config entities are not affected.
// @see \Drupal\serialization\Normalizer\ConfigEntityNormalizer::normalize()
$expected = static::castToString($expected);
ksort($expected);
$actual = $this->serializer->decode((string) $response->getBody(), static::$format);
ksort($actual);
$this->assertSame($expected, $actual);
}
// BC: rest_update_8203().
$this->config('rest.settings')->set('bc_entity_resource_permissions', TRUE)->save(TRUE);
// @todo Remove this in https://www.drupal.org/node/2815845.
drupal_flush_all_caches();
$this->refreshTestStateAfterRestConfigChange();
// DX: 403 when unauthorized.
$response = $this->request('GET', $url, $request_options);
// @todo Update the message in https://www.drupal.org/node/2808233.
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('GET'), $response);
$this->grantPermissionsToTestedRole(['restful get entity:' . static::$entityTypeId]);
@ -414,13 +499,39 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$this->assertResourceResponse(200, FALSE, $response);
$this->resourceConfigStorage->load(static::$resourceConfigId)->disable()->save();
$this->refreshTestStateAfterRestConfigChange();
// DX: upon disabling a resource, it's immediately no longer available.
$this->assertResourceNotAvailable($url, $request_options);
$this->resourceConfigStorage->load(static::$resourceConfigId)->enable()->save();
$this->refreshTestStateAfterRestConfigChange();
// DX: upon re-enabling a resource, immediate 200.
$response = $this->request('GET', $url, $request_options);
$this->assertResourceResponse(200, FALSE, $response);
$this->resourceConfigStorage->load(static::$resourceConfigId)->delete();
$this->refreshTestStateAfterRestConfigChange();
// DX: upon deleting a resource, it's immediately no longer available.
$this->assertResourceNotAvailable($url, $request_options);
$this->provisionEntityResource();
$url->setOption('query', ['_format' => 'non_existing_format']);
// DX: 406 when requesting unsupported format.
$response = $this->request('GET', $url, $request_options);
$this->assert406Response($response);
$this->assertNotSame([static::$expectedErrorMimeType], $response->getHeader('Content-Type'));
$this->assertNotSame([static::$mimeType], $response->getHeader('Content-Type'));
$request_options[RequestOptions::HEADERS]['Accept'] = static::$mimeType;
@ -430,7 +541,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// @todo Update in https://www.drupal.org/node/2825347.
$response = $this->request('GET', $url, $request_options);
$this->assert406Response($response);
$this->assertSame([static::$expectedErrorMimeType], $response->getHeader('Content-Type'));
$this->assertSame(['application/json'], $response->getHeader('Content-Type'));
$url = Url::fromRoute('rest.entity.' . static::$entityTypeId . '.GET.' . static::$format);
@ -445,6 +556,30 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$this->assertResourceErrorResponse(404, $message, $response);
}
/**
* Transforms a normalization: casts all non-string types to strings.
*
* @param array $normalization
* A normalization to transform.
*
* @return array
* The transformed normalization.
*/
protected static function castToString(array $normalization) {
foreach ($normalization as $key => $value) {
if (is_bool($value)) {
$normalization[$key] = (string) (int) $value;
}
elseif (is_int($value) || is_float($value)) {
$normalization[$key] = (string) $value;
}
elseif (is_array($value)) {
$normalization[$key] = static::castToString($value);
}
}
return $normalization;
}
/**
* Tests a POST request for an entity, plus edge cases to ensure good DX.
*/
@ -463,8 +598,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$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);
// @todo Change to ['uuid' => UUID] in https://www.drupal.org/node/2820743.
$parseable_invalid_request_body_2 = $this->serializer->encode($this->getNormalizedPostEntity() + ['uuid' => [['value' => $this->randomMachineName(129)]]], static::$format);
$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
@ -476,15 +610,11 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$request_options = [];
// DX: 404 when resource not provisioned, but HTML if canonical route.
// DX: 404 when resource not provisioned. HTML response because missing
// ?_format query string.
$response = $this->request('POST', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "GET ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
}
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
$url->setOption('query', ['_format' => static::$format]);
@ -500,16 +630,12 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$url->setOption('query', []);
// DX: 415 when no Content-Type request header, but HTML if canonical route.
// DX: 415 when no Content-Type request header. HTML response because
// missing ?_format query string.
$response = $this->request('POST', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertContains(htmlspecialchars('No "Content-Type" request header specified'), (string) $response->getBody());
}
else {
$this->assertResourceErrorResponse(415, 'No "Content-Type" request header specified', $response);
}
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertContains(htmlspecialchars('No "Content-Type" request header specified'), (string) $response->getBody());
$url->setOption('query', ['_format' => static::$format]);
@ -533,12 +659,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// DX: 400 when unparseable request body.
$response = $this->request('POST', $url, $request_options);
// @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813853.
// $this->assertResourceErrorResponse(400, 'Syntax error', $response);
$this->assertSame(400, $response->getStatusCode());
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['error' => 'Syntax error'], static::$format), (string) $response->getBody());
$this->assertResourceErrorResponse(400, 'Syntax error', $response);
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body;
@ -557,8 +678,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// DX: 403 when unauthorized.
$response = $this->request('POST', $url, $request_options);
// @todo Update the message in https://www.drupal.org/node/2808233.
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('POST'), $response);
$this->setUpAuthorization('POST');
@ -567,24 +687,19 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// 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 = ucfirst($label_field);
// @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813755.
// $this->assertErrorResponse(422, "Unprocessable Entity: validation failed.\ntitle: <em class=\"placeholder\">Title</em>: this field cannot hold more than 1 values.\n", $response);
$this->assertSame(422, $response->getStatusCode());
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\n$label_field: <em class=\"placeholder\">$label_field_capitalized</em>: this field cannot hold more than 1 values.\n"], static::$format), (string) $response->getBody());
$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: 422 when invalid entity: UUID field too long.
$response = $this->request('POST', $url, $request_options);
// @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813755.
// $this->assertErrorResponse(422, "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n", $response);
$this->assertSame(422, $response->getStatusCode());
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n"], static::$format), (string) $response->getBody());
// @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);
}
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body_3;
@ -592,8 +707,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// DX: 403 when entity contains field without 'edit' access.
$response = $this->request('POST', $url, $request_options);
// @todo Add trailing period in https://www.drupal.org/node/2821013.
$this->assertResourceErrorResponse(403, "Access denied on creating field 'field_rest_test'", $response);
$this->assertResourceErrorResponse(403, "Access denied on creating field 'field_rest_test'.", $response);
$request_options[RequestOptions::BODY] = $parseable_valid_request_body;
@ -622,20 +736,24 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// 201 for well-formed request.
$response = $this->request('POST', $url, $request_options);
$this->assertResourceResponse(201, FALSE, $response);
$this->assertSame([str_replace($this->entity->id(), static::$firstCreatedEntityId, $this->entity->toUrl('canonical')->setAbsolute(TRUE)->toString())], $response->getHeader('Location'));
if ($has_canonical_url) {
$location = $this->entityStorage->load(static::$firstCreatedEntityId)->toUrl('canonical')->setAbsolute(TRUE)->toString();
$this->assertSame([$location], $response->getHeader('Location'));
}
else {
$this->assertSame([], $response->getHeader('Location'));
}
$this->assertFalse($response->hasHeader('X-Drupal-Cache'));
$this->config('rest.settings')->set('bc_entity_resource_permissions', TRUE)->save(TRUE);
$this->refreshTestStateAfterRestConfigChange();
$request_options[RequestOptions::BODY] = $parseable_valid_request_body_2;
// @todo Remove this in https://www.drupal.org/node/2815845.
drupal_flush_all_caches();
// DX: 403 when unauthorized.
$response = $this->request('POST', $url, $request_options);
// @todo Update the message in https://www.drupal.org/node/2808233.
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('POST'), $response);
$this->grantPermissionsToTestedRole(['restful post entity:' . static::$entityTypeId]);
@ -644,7 +762,13 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// 201 for well-formed request.
$response = $this->request('POST', $url, $request_options);
$this->assertResourceResponse(201, FALSE, $response);
$this->assertSame([str_replace($this->entity->id(), static::$secondCreatedEntityId, $this->entity->toUrl('canonical')->setAbsolute(TRUE)->toString())], $response->getHeader('Location'));
if ($has_canonical_url) {
$location = $this->entityStorage->load(static::$secondCreatedEntityId)->toUrl('canonical')->setAbsolute(TRUE)->toString();
$this->assertSame([$location], $response->getHeader('Location'));
}
else {
$this->assertSame([], $response->getHeader('Location'));
}
$this->assertFalse($response->hasHeader('X-Drupal-Cache'));
}
@ -677,23 +801,31 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$request_options = [];
// DX: 405 when resource not provisioned, but HTML if canonical route.
// DX: 404 when resource not provisioned, 405 if canonical route. Plain text
// or HTML response because missing ?_format query string.
$response = $this->request('PATCH', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(405, $response->getStatusCode());
$this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow'));
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
}
$url->setOption('query', ['_format' => static::$format]);
// DX: 405 when resource not provisioned.
// DX: 404 when resource not provisioned, 405 if canonical route.
$response = $this->request('PATCH', $url, $request_options);
$this->assertResourceErrorResponse(405, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response);
if ($has_canonical_url) {
$this->assertResourceErrorResponse(405, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response);
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
}
$this->provisionEntityResource();
@ -701,16 +833,11 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$url->setOption('query', []);
// DX: 415 when no Content-Type request header, but HTML if canonical route.
// DX: 415 when no Content-Type request header.
$response = $this->request('PATCH', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertTrue(FALSE !== strpos((string) $response->getBody(), htmlspecialchars('No "Content-Type" request header specified')));
}
else {
$this->assertResourceErrorResponse(415, 'No "Content-Type" request header specified', $response);
}
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertTrue(FALSE !== strpos((string) $response->getBody(), htmlspecialchars('No "Content-Type" request header specified')));
$url->setOption('query', ['_format' => static::$format]);
@ -734,11 +861,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// DX: 400 when unparseable request body.
$response = $this->request('PATCH', $url, $request_options);
// @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813853.
// $this->assertResourceErrorResponse(400, 'Syntax error', $response);
$this->assertSame(400, $response->getStatusCode());
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['error' => 'Syntax error'], static::$format), (string) $response->getBody());
$this->assertResourceErrorResponse(400, 'Syntax error', $response);
@ -758,8 +881,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// DX: 403 when unauthorized.
$response = $this->request('PATCH', $url, $request_options);
// @todo Update the message in https://www.drupal.org/node/2808233.
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('PATCH'), $response);
$this->setUpAuthorization('PATCH');
@ -768,12 +890,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// 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 = ucfirst($label_field);
// @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813755.
// $this->assertErrorResponse(422, "Unprocessable Entity: validation failed.\ntitle: <em class=\"placeholder\">Title</em>: this field cannot hold more than 1 values.\n", $response);
// $this->assertSame(422, $response->getStatusCode());
// $this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\n$label_field: <em class=\"placeholder\">$label_field_capitalized</em>: this field cannot hold more than 1 values.\n"], static::$format), (string) $response->getBody());
$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;
@ -838,15 +956,13 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$this->config('rest.settings')->set('bc_entity_resource_permissions', TRUE)->save(TRUE);
$this->refreshTestStateAfterRestConfigChange();
$request_options[RequestOptions::BODY] = $parseable_valid_request_body_2;
// @todo Remove this in https://www.drupal.org/node/2815845.
drupal_flush_all_caches();
// DX: 403 when unauthorized.
$response = $this->request('PATCH', $url, $request_options);
// @todo Update the message in https://www.drupal.org/node/2808233.
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('PATCH'), $response);
$this->grantPermissionsToTestedRole(['restful patch entity:' . static::$entityTypeId]);
@ -880,24 +996,32 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$request_options = [];
// DX: 405 when resource not provisioned, but HTML if canonical route.
// DX: 405 when resource not provisioned, but HTML if canonical route. Plain
// text or HTML response because missing ?_format query string.
$response = $this->request('DELETE', $url, $request_options);
if ($has_canonical_url) {
$this->assertSame(405, $response->getStatusCode());
$this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow'));
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
$this->assertSame(404, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
}
$url->setOption('query', ['_format' => static::$format]);
// DX: 405 when resource not provisioned.
// DX: 404 when resource not provisioned, 405 if canonical route.
$response = $this->request('DELETE', $url, $request_options);
$this->assertResourceErrorResponse(405, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response);
if ($has_canonical_url) {
$this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow'));
$this->assertResourceErrorResponse(405, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response);
}
else {
$this->assertResourceErrorResponse(404, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '"', $response);
}
$this->provisionEntityResource();
@ -915,8 +1039,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// DX: 403 when unauthorized.
$response = $this->request('DELETE', $url, $request_options);
// @todo Update the message in https://www.drupal.org/node/2808233.
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('DELETE'), $response);
$this->setUpAuthorization('DELETE');
@ -930,23 +1053,24 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// 204 for well-formed request.
$response = $this->request('DELETE', $url, $request_options);
$this->assertSame(204, $response->getStatusCode());
// @todo Uncomment the following line when https://www.drupal.org/node/2821711 is fixed.
// DELETE responses should not include a Content-Type header. But Apache
// sets it to 'text/html' by default. We also cannot detect the presence of
// Apache either here in the CLI. For now having this documented here is all
// we can do.
// $this->assertSame(FALSE, $response->hasHeader('Content-Type'));
$this->assertSame('', (string) $response->getBody());
$this->assertFalse($response->hasHeader('X-Drupal-Cache'));
$this->config('rest.settings')->set('bc_entity_resource_permissions', TRUE)->save(TRUE);
// @todo Remove this in https://www.drupal.org/node/2815845.
drupal_flush_all_caches();
$this->refreshTestStateAfterRestConfigChange();
$this->entity = $this->createEntity();
$url = $this->getUrl()->setOption('query', $url->getOption('query'));
// DX: 403 when unauthorized.
$response = $this->request('DELETE', $url, $request_options);
// @todo Update the message in https://www.drupal.org/node/2808233.
$this->assertResourceErrorResponse(403, '', $response);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('DELETE'), $response);
$this->grantPermissionsToTestedRole(['restful delete entity:' . static::$entityTypeId]);
@ -982,11 +1106,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// DX: 400 when incorrect entity type bundle is specified.
// @todo Change to 422 in https://www.drupal.org/node/2827084.
$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());
$this->assertResourceErrorResponse(400, '"bad_bundle_name" is not a valid bundle type for denormalization.', $response);
}
@ -997,11 +1117,7 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
// DX: 400 when no entity type bundle is specified.
// @todo Change to 422 in https://www.drupal.org/node/2827084.
$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, 'A string must be provided as a bundle value.', $response);
$this->assertSame(400, $response->getStatusCode());
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['error' => 'A string must be provided as a bundle value.'], static::$format), (string) $response->getBody());
$this->assertResourceErrorResponse(400, sprintf('Could not determine entity type bundle: "%s" field is missing.', $bundle_field_name), $response);
}
}
@ -1090,4 +1206,23 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
}
}
/**
* Asserts that a resource is unavailable: 404, 406 if it has canonical route.
*
* @param \Drupal\Core\Url $url
* URL to request.
* @param array $request_options
* Request options to apply.
*/
protected function assertResourceNotAvailable(Url $url, array $request_options) {
$has_canonical_url = $this->entity->hasLinkTemplate('canonical');
$response = $this->request('GET', $url, $request_options);
if (!$has_canonical_url) {
$this->assertSame(404, $response->getStatusCode());
}
else {
$this->assert406Response($response);
}
}
}

View file

@ -21,9 +21,4 @@ class EntityTestJsonAnonTest extends EntityTestResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
}

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -27,19 +26,9 @@ class EntityTestJsonBasicAuthTest extends EntityTestResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View file

@ -21,11 +21,6 @@ class EntityTestJsonCookieTest extends EntityTestResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -73,7 +73,7 @@ abstract class EntityTestResourceTestBase extends EntityResourceTestBase {
],
'id' => [
[
'value' => '1',
'value' => 1,
],
],
'langcode' => [
@ -93,12 +93,12 @@ abstract class EntityTestResourceTestBase extends EntityResourceTestBase {
],
'created' => [
[
'value' => $this->entity->get('created')->value,
'value' => (int) $this->entity->get('created')->value,
]
],
'user_id' => [
[
'target_id' => $author->id(),
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => $author->toUrl()->toString(),
@ -124,4 +124,22 @@ abstract class EntityTestResourceTestBase extends EntityResourceTestBase {
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'view test entity' permission is required.";
case 'POST':
return "The following permissions are required: 'administer entity_test content' OR 'administer entity_test_with_bundle content' OR 'create entity_test entity_test_with_bundle entities'.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class EntityTestLabelJsonAnonTest extends EntityTestLabelResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class EntityTestLabelJsonBasicAuthTest extends EntityTestLabelResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class EntityTestLabelJsonCookieTest extends EntityTestLabelResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,156 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTestLabel;
use Drupal\entity_test\Entity\EntityTestLabel;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\user\Entity\User;
abstract class EntityTestLabelResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['entity_test'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'entity_test_label';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [];
/**
* @var \Drupal\entity_test\Entity\EntityTestLabel
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['view test entity']);
break;
case 'POST':
$this->grantPermissionsToTestedRole([
'administer entity_test content',
'administer entity_test_with_bundle content',
'create entity_test entity_test_with_bundle entities',
]);
break;
case 'PATCH':
case 'DELETE':
$this->grantPermissionsToTestedRole(['administer entity_test content']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$entity_test_label = EntityTestLabel::create([
'name' => 'label_llama',
]);
$entity_test_label->setOwnerId(0);
$entity_test_label->save();
return $entity_test_label;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
$author = User::load(0);
$normalization = [
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
'id' => [
[
'value' => (int) $this->entity->id(),
],
],
'langcode' => [
[
'value' => 'en',
],
],
'type' => [
[
'value' => 'entity_test_label',
],
],
'name' => [
[
'value' => 'label_llama',
],
],
'created' => [
[
'value' => (int) $this->entity->get('created')->value,
],
],
'user_id' => [
[
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => $author->toUrl()->toString(),
],
],
];
return $normalization;
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'type' => 'entity_test_label',
'name' => [
[
'value' => 'label_llama',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return ['user.permissions'];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'view test entity' permission is required.";
case 'POST':
return "The following permissions are required: 'administer entity_test content' OR 'administer entity_test_with_bundle content' OR 'create entity_test_label entity_test_with_bundle entities'.";
case 'PATCH':
case 'DELETE':
return "The 'administer entity_test content' permission is required.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\FilterFormat;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class FilterFormatJsonAnonTest extends FilterFormatResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\FilterFormat;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class FilterFormatJsonBasicAuthTest extends FilterFormatResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\FilterFormat;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class FilterFormatJsonCookieTest extends FilterFormatResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,88 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\FilterFormat;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
abstract class FilterFormatResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'filter_format';
/**
* @var \Drupal\filter\FilterFormatInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer filters']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$pablo_format = FilterFormat::create([
'name' => 'Pablo Piccasso',
'format' => 'pablo',
'langcode' => 'es',
'filters' => [
'filter_html' => [
'status' => TRUE,
'settings' => [
'allowed_html' => '<p> <a> <b> <lo>',
],
],
],
]);
$pablo_format->save();
return $pablo_format;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'filters' => [
'filter_html' => [
'id' => 'filter_html',
'provider' => 'filter',
'status' => TRUE,
'weight' => -10,
'settings' => [
'allowed_html' => '<p> <a> <b> <lo>',
'filter_html_help' => TRUE,
'filter_html_nofollow' => FALSE,
],
],
],
'format' => 'pablo',
'langcode' => 'es',
'name' => 'Pablo Piccasso',
'status' => TRUE,
'uuid' => $this->entity->uuid(),
'weight' => 0,
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ImageStyle;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ImageStyleJsonAnonTest extends ImageStyleResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ImageStyle;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ImageStyleJsonBasicAuthTest extends ImageStyleResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ImageStyle;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ImageStyleJsonCookieTest extends ImageStyleResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,113 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\ImageStyle;
use Drupal\image\Entity\ImageStyle;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for ImageStyle entity.
*/
abstract class ImageStyleResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['image'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'image_style';
/**
* The ImageStyle entity.
*
* @var \Drupal\image\ImageStyleInterface
*/
protected $entity;
/**
* The effect UUID.
*
* @var string
*/
protected $effectUuid;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer image styles']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" image style.
$camelids = ImageStyle::create([
'name' => 'camelids',
'label' => 'Camelids',
]);
// Add an image effect.
$effect = [
'id' => 'image_scale_and_crop',
'data' => [
'width' => 120,
'height' => 121,
],
'weight' => 0,
];
$this->effectUuid = $camelids->addImageEffect($effect);
$camelids->save();
return $camelids;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'effects' => [
$this->effectUuid => [
'uuid' => $this->effectUuid,
'id' => 'image_scale_and_crop',
'weight' => 0,
'data' => [
'width' => 120,
'height' => 121,
],
],
],
'label' => 'Camelids',
'langcode' => 'en',
'name' => 'camelids',
'status' => TRUE,
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
return "The 'administer image styles' permission is required.";
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Item;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ItemJsonAnonTest extends ItemResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Item;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ItemJsonBasicAuthTest extends ItemResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Item;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ItemJsonCookieTest extends ItemResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,177 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Item;
use Drupal\aggregator\Entity\Feed;
use Drupal\aggregator\Entity\Item;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for Item entity.
*/
abstract class ItemResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['aggregator'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'aggregator_item';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [];
/**
* The Item entity.
*
* @var \Drupal\aggregator\ItemInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['access news feeds']);
break;
case 'POST':
case 'PATCH':
case 'DELETE':
$this->grantPermissionsToTestedRole(['administer news feeds']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" feed.
$feed = Feed::create([
'title' => 'Camelids',
'url' => 'https://groups.drupal.org/not_used/167169',
'refresh' => 900,
'checked' => 1389919932,
'description' => 'Drupal Core Group feed',
]);
$feed->save();
// Create a "Llama" item.
$item = Item::create();
$item->setTitle('Llama')
->setFeedId($feed->id())
->setLink('https://www.drupal.org/')
->setPostedTime(123456789)
->save();
return $item;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
$feed = Feed::load($this->entity->getFeedId());
return [
'iid' => [
[
'value' => 1,
],
],
'langcode' => [
[
'value' => 'en',
],
],
'fid' => [
[
'target_id' => 1,
'target_type' => 'aggregator_feed',
'target_uuid' => $feed->uuid(),
'url' => base_path() . 'aggregator/sources/1',
],
],
'title' => [
[
'value' => 'Llama',
],
],
'link' => [
[
'value' => 'https://www.drupal.org/',
],
],
'author' => [],
'description' => [],
'timestamp' => [
[
'value' => 123456789,
],
],
'guid' => [],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'fid' => [
[
'target_id' => 1,
],
],
'title' => [
[
'value' => 'Llama',
],
],
'link' => [
[
'value' => 'https://www.drupal.org/',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
// @see ::createEntity()
return ['user.permissions'];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'access news feeds' permission is required.";
case 'POST':
case 'PATCH':
case 'DELETE':
return "The 'administer news feeds' permission is required.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\MenuLinkContent;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class MenuLinkContentJsonAnonTest extends MenuLinkContentResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\MenuLinkContent;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class MenuLinkContentJsonBasicAuthTest extends MenuLinkContentResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\MenuLinkContent;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class MenuLinkContentJsonCookieTest extends MenuLinkContentResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,193 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\MenuLinkContent;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for MenuLinkContent entity.
*/
abstract class MenuLinkContentResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_link_content'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'menu_link_content';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [
'changed',
];
/**
* The MenuLinkContent entity.
*
* @var \Drupal\menu_link_content\MenuLinkContentInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
case 'POST':
case 'PATCH':
case 'DELETE':
$this->grantPermissionsToTestedRole(['administer menu']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$menu_link = MenuLinkContent::create([
'id' => 'llama',
'title' => 'Llama Gabilondo',
'description' => 'Llama Gabilondo',
'link' => 'https://nl.wikipedia.org/wiki/Llama',
'weight' => 0,
'menu_name' => 'main',
]);
$menu_link->save();
return $menu_link;
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'title' => [
[
'value' => 'Dramallama',
],
],
'link' => [
[
'uri' => 'http://www.urbandictionary.com/define.php?term=drama%20llama',
],
],
'bundle' => [
[
'value' => 'menu_link_content',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
'id' => [
[
'value' => 1,
],
],
'title' => [
[
'value' => 'Llama Gabilondo',
],
],
'link' => [
[
'uri' => 'https://nl.wikipedia.org/wiki/Llama',
'title' => NULL,
'options' => [],
],
],
'weight' => [
[
'value' => 0,
],
],
'menu_name' => [
[
'value' => 'main',
],
],
'langcode' => [
[
'value' => 'en',
],
],
'bundle' => [
[
'value' => 'menu_link_content',
],
],
'description' => [
[
'value' => 'Llama Gabilondo',
],
],
'external' => [
[
'value' => FALSE,
],
],
'rediscover' => [
[
'value' => FALSE,
],
],
'expanded' => [
[
'value' => FALSE,
],
],
'enabled' => [
[
'value' => TRUE,
],
],
'changed' => [
[
'value' => $this->entity->getChangedTime(),
],
],
'default_langcode' => [
[
'value' => TRUE,
],
],
'parent' => [],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'DELETE':
return "You are not authorized to delete this menu_link_content entity.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -21,9 +21,4 @@ class NodeJsonAnonTest extends NodeResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
}

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -27,19 +26,9 @@ class NodeJsonBasicAuthTest extends NodeResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View file

@ -21,11 +21,6 @@ class NodeJsonCookieTest extends NodeResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -116,32 +116,32 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
],
'status' => [
[
'value' => 1,
'value' => TRUE,
],
],
'created' => [
[
'value' => '123456789',
'value' => 123456789,
],
],
'changed' => [
[
'value' => '123456789',
'value' => $this->entity->getChangedTime(),
],
],
'promote' => [
[
'value' => 1,
'value' => TRUE,
],
],
'sticky' => [
[
'value' => '0',
'value' => FALSE,
],
],
'revision_timestamp' => [
[
'value' => '123456789',
'value' => 123456789,
],
],
'revision_translation_affected' => [
@ -156,7 +156,7 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
],
'uid' => [
[
'target_id' => $author->id(),
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => base_path() . 'user/' . $author->id(),
@ -164,14 +164,13 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
],
'revision_uid' => [
[
'target_id' => $author->id(),
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => base_path() . 'user/' . $author->id(),
],
],
'revision_log' => [
],
'revision_log' => [],
];
}
@ -193,4 +192,18 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
if ($method === 'GET' || $method == 'PATCH' || $method == 'DELETE') {
return "The 'access content' permission is required.";
}
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\NodeType;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class NodeTypeJsonAnonTest extends NodeTypeResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\NodeType;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class NodeTypeJsonBasicAuthTest extends NodeTypeResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\NodeType;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class NodeTypeJsonCookieTest extends NodeTypeResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\NodeType;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for NodeType entity.
*/
abstract class NodeTypeResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'node_type';
/**
* The NodeType entity.
*
* @var \Drupal\node\NodeTypeInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer content types', 'access content']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" node type.
$camelids = NodeType::create([
'name' => 'Camelids',
'type' => 'camelids',
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
]);
$camelids->save();
return $camelids;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'display_submitted' => TRUE,
'help' => NULL,
'langcode' => 'en',
'name' => 'Camelids',
'new_revision' => TRUE,
'preview_mode' => 1,
'status' => TRUE,
'type' => 'camelids',
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
return "The 'access content' permission is required.";
}
}

View file

@ -21,9 +21,4 @@ class RoleJsonAnonTest extends RoleResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
}

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Psr\Http\Message\ResponseInterface;
/**
* @group rest
@ -27,22 +26,9 @@ class RoleJsonBasicAuthTest extends RoleResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
/**
* {@inheritdoc}
*/
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
$this->assertSame(401, $response->getStatusCode());
$this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string) $response->getBody());
}
}

View file

@ -21,11 +21,6 @@ class RoleJsonCookieTest extends RoleResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\SearchPage;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class SearchPageJsonAnonTest extends SearchPageResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\SearchPage;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class SearchPageJsonBasicAuthTest extends SearchPageResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\SearchPage;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class SearchPageJsonCookieTest extends SearchPageResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,102 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\SearchPage;
use Drupal\search\Entity\SearchPage;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
abstract class SearchPageResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'search'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'search_page';
/**
* @var \Drupal\search\SearchPageInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['access content']);
break;
case 'POST':
case 'PATCH':
case 'DELETE':
$this->grantPermissionsToTestedRole(['administer search']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$search_page = SearchPage::create([
'id' => 'hinode_search',
'plugin' => 'node_search',
'label' => 'Search of magnetic activity of the Sun',
'path' => 'sun',
]);
$search_page->save();
return $search_page;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'configuration' => [
'rankings' => [],
],
'dependencies' => [
'module' => ['node'],
],
'id' => 'hinode_search',
'label' => 'Search of magnetic activity of the Sun',
'langcode' => 'en',
'path' => 'sun',
'plugin' => 'node_search',
'status' => TRUE,
'uuid' => $this->entity->uuid(),
'weight' => 0,
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'access content' permission is required.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Shortcut;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ShortcutJsonAnonTest extends ShortcutResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Shortcut;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ShortcutJsonBasicAuthTest extends ShortcutResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Shortcut;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ShortcutJsonCookieTest extends ShortcutResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,159 @@
<?php
namespace Drupal\Tests\rest\Functional\EntityResource\Shortcut;
use Drupal\shortcut\Entity\Shortcut;
use Drupal\shortcut\Entity\ShortcutSet;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for Shortcut entity.
*/
abstract class ShortcutResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['comment', 'shortcut'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'shortcut';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [];
/**
* The Shortcut entity.
*
* @var \Drupal\shortcut\ShortcutInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
case 'POST':
case 'PATCH':
case 'DELETE':
$this->grantPermissionsToTestedRole(['access shortcuts', 'customize shortcut links']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create shortcut.
$shortcut = Shortcut::create([
'shortcut_set' => 'default',
'title' => t('Comments'),
'weight' => -20,
'link' => [
'uri' => 'internal:/admin/content/comment',
],
]);
$shortcut->save();
return $shortcut;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
'id' => [
[
'value' => (int) $this->entity->id(),
],
],
'title' => [
[
'value' => 'Comments',
],
],
'shortcut_set' => [
[
'target_id' => 'default',
'target_type' => 'shortcut_set',
'target_uuid' => ShortcutSet::load('default')->uuid(),
],
],
'link' => [
[
'uri' => 'internal:/admin/content/comment',
'title' => NULL,
'options' => [],
],
],
'weight' => [
[
'value' => -20,
],
],
'langcode' => [
[
'value' => 'en',
],
],
'default_langcode' => [
[
'value' => TRUE,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'title' => [
[
'value' => 'Comments',
],
],
'link' => [
[
'uri' => 'internal:/',
],
],
'shortcut_set' => 'default',
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
case 'POST':
case 'PATCH':
case 'DELETE':
return "The shortcut set must be the currently displayed set for the user and the user must have 'access shortcuts' AND 'customize shortcut links' permissions.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -21,9 +21,4 @@ class TermJsonAnonTest extends TermResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
}

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -27,19 +26,9 @@ class TermJsonBasicAuthTest extends TermResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View file

@ -21,11 +21,6 @@ class TermJsonCookieTest extends TermResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -108,7 +108,7 @@ abstract class TermResourceTestBase extends EntityResourceTestBase {
],
'changed' => [
[
'value' => '123456789',
'value' => $this->entity->getChangedTime(),
],
],
'default_langcode' => [
@ -137,4 +137,26 @@ abstract class TermResourceTestBase extends EntityResourceTestBase {
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'access content' permission is required.";
case 'POST':
return "The 'administer taxonomy' permission is required.";
case 'PATCH':
return "The following permissions are required: 'edit terms in camelids' OR 'administer taxonomy'.";
case 'DELETE':
return "The following permissions are required: 'delete terms in camelids' OR 'administer taxonomy'.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -21,9 +21,4 @@ class UserJsonAnonTest extends UserResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
}

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\User;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -27,19 +26,9 @@ class UserJsonBasicAuthTest extends UserResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View file

@ -21,11 +21,6 @@ class UserJsonCookieTest extends UserResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -82,7 +82,7 @@ abstract class UserResourceTestBase extends EntityResourceTestBase {
protected function getExpectedNormalizedEntity() {
return [
'uid' => [
['value' => '3'],
['value' => 3],
],
'uuid' => [
['value' => $this->entity->uuid()],
@ -99,12 +99,12 @@ abstract class UserResourceTestBase extends EntityResourceTestBase {
],
'created' => [
[
'value' => '123456789',
'value' => 123456789,
],
],
'changed' => [
[
'value' => '123456789',
'value' => $this->entity->getChangedTime(),
],
],
'default_langcode' => [
@ -163,11 +163,7 @@ abstract class UserResourceTestBase extends EntityResourceTestBase {
// DX: 422 when changing email without providing the password.
$response = $this->request('PATCH', $url, $request_options);
// @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813755 lands.
// $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n", $response);
$this->assertSame(422, $response->getStatusCode());
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n"], static::$format), (string) $response->getBody());
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the Email.\n", $response);
$normalization['pass'] = [['existing' => 'wrong']];
@ -175,11 +171,7 @@ abstract class UserResourceTestBase extends EntityResourceTestBase {
// DX: 422 when changing email while providing a wrong password.
$response = $this->request('PATCH', $url, $request_options);
// @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813755 lands.
// $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n", $response);
$this->assertSame(422, $response->getStatusCode());
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n"], static::$format), (string) $response->getBody());
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the Email.\n", $response);
$normalization['pass'] = [['existing' => $this->account->passRaw]];
@ -200,11 +192,7 @@ abstract class UserResourceTestBase extends EntityResourceTestBase {
// DX: 422 when changing password without providing the current password.
$response = $this->request('PATCH', $url, $request_options);
// @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813755 lands.
// $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n", $response);
$this->assertSame(422, $response->getStatusCode());
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n"], static::$format), (string) $response->getBody());
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the Password.\n", $response);
$normalization['pass'][0]['existing'] = $this->account->pass_raw;
@ -229,4 +217,24 @@ abstract class UserResourceTestBase extends EntityResourceTestBase {
$this->assertSame(200, $response->getStatusCode());
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'access user profiles' permission is required and the user must be active.";
case 'PATCH':
return "You are not authorized to update this user entity.";
case 'DELETE':
return 'You are not authorized to delete this user entity.';
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
}

View file

@ -21,11 +21,6 @@ class VocabularyJsonAnonTest extends VocabularyResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
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.

View file

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Vocabulary;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -27,19 +26,9 @@ class VocabularyJsonBasicAuthTest extends VocabularyResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View file

@ -21,11 +21,6 @@ class VocabularyJsonCookieTest extends VocabularyResourceTestBase {
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* {@inheritdoc}
*/

View file

@ -1,25 +0,0 @@
<?php
namespace Drupal\Tests\rest\Functional;
use Psr\Http\Message\ResponseInterface;
trait JsonBasicAuthWorkaroundFor2805281Trait {
/**
* {@inheritdoc}
*
* Note that strange 'A fatal error occurred: ' prefix, that should not exist.
*
* @todo Fix in https://www.drupal.org/node/2805281: remove this trait.
*/
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
$this->assertSame(401, $response->getStatusCode());
$this->assertSame([static::$expectedErrorMimeType], $response->getHeader('Content-Type'));
// 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());
}
}

View file

@ -2,6 +2,7 @@
namespace Drupal\Tests\rest\Functional;
use Behat\Mink\Driver\BrowserKitDriver;
use Drupal\Core\Url;
use Drupal\rest\RestResourceConfigInterface;
use Drupal\Tests\BrowserTestBase;
@ -46,17 +47,6 @@ abstract class ResourceTestBase extends BrowserTestBase {
*/
protected static $mimeType = 'application/json';
/**
* The expected MIME type in case of 4xx error responses.
*
* (Can be different, when $mimeType for example encodes a particular
* normalization, such as 'application/hal+json': its error response MIME
* type is 'application/json'.)
*
* @var string
*/
protected static $expectedErrorMimeType = 'application/json';
/**
* The authentication mechanism to use in this test.
*
@ -66,6 +56,15 @@ abstract class ResourceTestBase extends BrowserTestBase {
*/
protected static $auth = FALSE;
/**
* The REST Resource Config entity ID under test (i.e. a resource type).
*
* The REST Resource plugin ID can be calculated from this.
*
* @var string
*/
protected static $resourceConfigId = NULL;
/**
* The account to use for authentication, if any.
*
@ -94,6 +93,11 @@ abstract class ResourceTestBase extends BrowserTestBase {
*/
public static $modules = ['rest'];
/**
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* {@inheritdoc}
*/
@ -130,30 +134,51 @@ abstract class ResourceTestBase extends BrowserTestBase {
// Ensure there's a clean slate: delete all REST resource config entities.
$this->resourceConfigStorage->delete($this->resourceConfigStorage->loadMultiple());
$this->refreshTestStateAfterRestConfigChange();
// Set up a HTTP client that accepts relative URLs.
$this->httpClient = $this->container->get('http_client_factory')
->fromOptions(['base_uri' => $this->baseUrl]);
}
/**
* Provisions a REST resource.
* Provisions the REST resource under test.
*
* @param string $resource_type
* The resource type (REST resource plugin ID).
* @param string[] $formats
* The allowed formats for this resource.
* @param string[] $authentication
* The allowed authentication providers for this resource.
*/
protected function provisionResource($resource_type, $formats = [], $authentication = []) {
protected function provisionResource($formats = [], $authentication = []) {
$this->resourceConfigStorage->create([
'id' => $resource_type,
'id' => static::$resourceConfigId,
'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
'configuration' => [
'methods' => ['GET', 'POST', 'PATCH', 'DELETE'],
'formats' => $formats,
'authentication' => $authentication,
]
],
'status' => TRUE,
])->save();
// @todo Remove this in https://www.drupal.org/node/2815845.
drupal_flush_all_caches();
$this->refreshTestStateAfterRestConfigChange();
}
/**
* Refreshes the state of the tester to be in sync with the testee.
*
* Should be called after every change made to:
* - RestResourceConfig entities
* - the 'rest.settings' simple configuration
*/
protected function refreshTestStateAfterRestConfigChange() {
// Ensure that the cache tags invalidator has its internal values reset.
// Otherwise the http_response cache tag invalidation won't work.
$this->refreshVariables();
// Tests using this base class may trigger route rebuilds due to changes to
// RestResourceConfig entities or 'rest.settings'. Ensure the test generates
// routes using an up-to-date router.
\Drupal::service('router.builder')->rebuildIfNeeded();
}
/**
@ -214,6 +239,29 @@ abstract class ResourceTestBase extends BrowserTestBase {
*/
abstract protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options);
/**
* Return the expected error message.
*
* @param string $method
* The HTTP method (GET, POST, PATCH, DELETE).
*
* @return string
* The error string.
*/
abstract protected function getExpectedUnauthorizedAccessMessage($method);
/**
* Return the default expected error message if the
* bc_entity_resource_permissions is true.
*
* @param string $method
* The HTTP method (GET, POST, PATCH, DELETE).
*
* @return string
* The error string.
*/
abstract protected function getExpectedBcUnauthorizedAccessMessage($method);
/**
* Initializes authentication.
*
@ -295,16 +343,13 @@ abstract class ResourceTestBase extends BrowserTestBase {
*/
protected function request($method, Url $url, array $request_options) {
$request_options[RequestOptions::HTTP_ERRORS] = FALSE;
$request_options = $this->decorateWithXdebugCookie($request_options);
return $this->httpClient->request($method, $url->toString(), $request_options);
}
/**
* Asserts that a resource response has the given status code and body.
*
* (Also asserts that the expected error MIME type is present, but this is
* defined globally for the test via static::$expectedErrorMimeType, because
* all error responses should use the same MIME type.)
*
* @param int $expected_status_code
* The expected response status.
* @param string|false $expected_body
@ -318,7 +363,7 @@ abstract class ResourceTestBase extends BrowserTestBase {
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
}
else {
$this->assertSame([static::$expectedErrorMimeType], $response->getHeader('Content-Type'));
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
}
if ($expected_body !== FALSE) {
$this->assertSame($expected_body, (string) $response->getBody());
@ -328,10 +373,6 @@ abstract class ResourceTestBase extends BrowserTestBase {
/**
* Asserts that a resource error response has the given message.
*
* (Also asserts that the expected error MIME type is present, but this is
* defined globally for the test via static::$expectedErrorMimeType, because
* all error responses should use the same MIME type.)
*
* @param int $expected_status_code
* The expected response status.
* @param string $expected_message
@ -340,10 +381,34 @@ abstract class ResourceTestBase extends BrowserTestBase {
* The error response to assert.
*/
protected function assertResourceErrorResponse($expected_status_code, $expected_message, ResponseInterface $response) {
// @todo Fix this in https://www.drupal.org/node/2813755.
$encode_options = ['json_encode_options' => JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT];
$expected_body = $this->serializer->encode(['message' => $expected_message], static::$format, $encode_options);
$expected_body = ($expected_message !== FALSE) ? $this->serializer->encode(['message' => $expected_message], static::$format) : FALSE;
$this->assertResourceResponse($expected_status_code, $expected_body, $response);
}
/**
* Adds the Xdebug cookie to the request options.
*
* @param array $request_options
* The request options.
*
* @return array
* Request options updated with the Xdebug cookie if present.
*/
protected function decorateWithXdebugCookie(array $request_options) {
$session = $this->getSession();
$driver = $session->getDriver();
if ($driver instanceof BrowserKitDriver) {
$client = $driver->getClient();
foreach ($client->getCookieJar()->all() as $cookie) {
if (isset($request_options[RequestOptions::HEADERS]['Cookie'])) {
$request_options[RequestOptions::HEADERS]['Cookie'] .= '; ' . $cookie->getName() . '=' . $cookie->getValue();
}
else {
$request_options[RequestOptions::HEADERS]['Cookie'] = $cookie->getName() . '=' . $cookie->getValue();
}
}
}
return $request_options;
}
}

View file

@ -2,7 +2,6 @@
namespace Drupal\Tests\rest\Kernel;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\KernelTests\KernelTestBase;
@ -10,8 +9,6 @@ use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\RequestHandler;
use Drupal\rest\ResourceResponse;
use Drupal\rest\RestResourceConfigInterface;
use Prophecy\Argument;
use Prophecy\Prophecy\MethodProphecy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
@ -48,11 +45,9 @@ class RequestHandlerTest extends KernelTestBase {
}
/**
* Assert some basic handler method logic.
*
* @covers ::handle
*/
public function testBaseHandler() {
public function testHandle() {
$request = new Request();
$route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => 'restplugin'], ['_format' => 'json']));
@ -91,249 +86,6 @@ class RequestHandlerTest extends KernelTestBase {
$this->assertEquals($response, $handler_response);
}
/**
* Test that given structured data, the request handler will serialize it.
*
* @dataProvider providerTestSerialization
* @covers ::handle
*/
public function testSerialization($data, $expected_response = FALSE) {
$request = new Request();
$route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => 'restplugin'], ['_format' => 'json']));
$resource = $this->prophesize(StubRequestHandlerResourcePlugin::class);
// Mock the configuration.
$config = $this->prophesize(RestResourceConfigInterface::class);
$config->getResourcePlugin()->willReturn($resource->reveal());
$config->getCacheContexts()->willReturn([]);
$config->getCacheTags()->willReturn([]);
$config->getCacheMaxAge()->willReturn(12);
$this->entityStorage->load('restplugin')->willReturn($config->reveal());
$response = new ResourceResponse($data);
$resource->get(NULL, $request)
->willReturn($response);
$handler_response = $this->requestHandler->handle($route_match, $request);
// Content is a serialized version of the data we provided.
$this->assertEquals($expected_response !== FALSE ? $expected_response : json_encode($data), $handler_response->getContent());
}
public function providerTestSerialization() {
return [
// The default data for \Drupal\rest\ResourceResponse.
[NULL, ''],
[''],
['string'],
['Complex \ string $%^&@ with unicode ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ'],
[[]],
[['test']],
[['test' => 'foobar']],
[TRUE],
[FALSE],
// @todo Not supported. https://www.drupal.org/node/2427811
// [new \stdClass()],
// [(object) ['test' => 'foobar']],
];
}
/**
* @covers ::getResponseFormat
*
* Note this does *not* need to test formats being requested that are not
* accepted by the server, because the routing system would have already
* prevented those from reaching RequestHandler.
*
* @param string[] $methods
* The HTTP methods to test.
* @param string[] $supported_formats
* The supported formats for the REST route to be tested.
* @param string|false $request_format
* The value for the ?_format URL query argument, if any.
* @param string[] $request_headers
* The request headers to send, if any.
* @param string|null $request_body
* The request body to send, if any.
* @param string|null $expected_response_content_type
* The expected MIME type of the response, if any.
* @param string $expected_response_content
* The expected content of the response.
*
* @dataProvider providerTestResponseFormat
*/
public function testResponseFormat($methods, array $supported_formats, $request_format, array $request_headers, $request_body, $expected_response_content_type, $expected_response_content) {
$rest_config_name = $this->randomMachineName();
$parameters = [];
if ($request_format !== FALSE) {
$parameters['_format'] = $request_format;
}
foreach ($request_headers as $key => $value) {
unset($request_headers[$key]);
$key = strtoupper(str_replace('-', '_', $key));
$request_headers[$key] = $value;
}
foreach ($methods as $method) {
$request = Request::create('/rest/test', $method, $parameters, [], [], $request_headers, $request_body);
$route_requirement_key_format = $request->isMethodSafe() ? '_format' : '_content_type_format';
$route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => $rest_config_name], [$route_requirement_key_format => implode('|', $supported_formats)]));
$resource = $this->prophesize(StubRequestHandlerResourcePlugin::class);
// Mock the configuration.
$config = $this->prophesize(RestResourceConfigInterface::class);
$config->getFormats($method)->willReturn($supported_formats);
$config->getResourcePlugin()->willReturn($resource->reveal());
$config->getCacheContexts()->willReturn([]);
$config->getCacheTags()->willReturn([]);
$config->getCacheMaxAge()->willReturn(12);
$this->entityStorage->load($rest_config_name)->willReturn($config->reveal());
// Mock the resource plugin.
$response = new ResourceResponse($method !== 'DELETE' ? ['REST' => 'Drupal'] : NULL);
$resource->getPluginDefinition()->willReturn([]);
$method_prophecy = new MethodProphecy($resource, strtolower($method), [Argument::any(), $request]);
$method_prophecy->willReturn($response);
$resource->addMethodProphecy($method_prophecy);
// Test the request handler.
$handler_response = $this->requestHandler->handle($route_match, $request);
$this->assertSame($expected_response_content_type, $handler_response->headers->get('Content-Type'));
$this->assertEquals($expected_response_content, $handler_response->getContent());
}
}
/**
* @return array
* 0. methods to test
* 1. supported formats for route requirements
* 2. request format
* 3. request headers
* 4. request body
* 5. expected response content type
* 6. expected response body
*/
public function providerTestResponseFormat() {
$json_encoded = Json::encode(['REST' => 'Drupal']);
$xml_encoded = "<?xml version=\"1.0\"?>\n<response><REST>Drupal</REST></response>\n";
$safe_method_test_cases = [
'safe methods: client requested format (JSON)' => [
// @todo add 'HEAD' in https://www.drupal.org/node/2752325
['GET'],
['xml', 'json'],
'json',
[],
NULL,
'application/json',
$json_encoded,
],
'safe methods: client requested format (XML)' => [
// @todo add 'HEAD' in https://www.drupal.org/node/2752325
['GET'],
['xml', 'json'],
'xml',
[],
NULL,
'text/xml',
$xml_encoded,
],
'safe methods: client requested no format: response should use the first configured format (JSON)' => [
// @todo add 'HEAD' in https://www.drupal.org/node/2752325
['GET'],
['json', 'xml'],
FALSE,
[],
NULL,
'application/json',
$json_encoded,
],
'safe methods: client requested no format: response should use the first configured format (XML)' => [
// @todo add 'HEAD' in https://www.drupal.org/node/2752325
['GET'],
['xml', 'json'],
FALSE,
[],
NULL,
'text/xml',
$xml_encoded,
],
];
$unsafe_method_bodied_test_cases = [
'unsafe methods with response (POST, PATCH): client requested no format, response should use request body format (JSON)' => [
['POST', 'PATCH'],
['xml', 'json'],
FALSE,
['Content-Type' => 'application/json'],
$json_encoded,
'application/json',
$json_encoded,
],
'unsafe methods with response (POST, PATCH): client requested no format, response should use request body format (XML)' => [
['POST', 'PATCH'],
['xml', 'json'],
FALSE,
['Content-Type' => 'text/xml'],
$xml_encoded,
'text/xml',
$xml_encoded,
],
'unsafe methods with response (POST, PATCH): client requested format other than request body format (JSON): response format should use requested format (XML)' => [
['POST', 'PATCH'],
['xml', 'json'],
'xml',
['Content-Type' => 'application/json'],
$json_encoded,
'text/xml',
$xml_encoded,
],
'unsafe methods with response (POST, PATCH): client requested format other than request body format (XML), but is allowed for the request body (JSON)' => [
['POST', 'PATCH'],
['xml', 'json'],
'json',
['Content-Type' => 'text/xml'],
$xml_encoded,
'application/json',
$json_encoded,
],
];
$unsafe_method_bodyless_test_cases = [
'unsafe methods with response bodies (DELETE): client requested no format, response should have no format' => [
['DELETE'],
['xml', 'json'],
FALSE,
['Content-Type' => 'application/json'],
$json_encoded,
NULL,
'',
],
'unsafe methods with response bodies (DELETE): client requested format (XML), response should have no format' => [
['DELETE'],
['xml', 'json'],
'xml',
['Content-Type' => 'application/json'],
$json_encoded,
NULL,
'',
],
'unsafe methods with response bodies (DELETE): client requested format (JSON), response should have no format' => [
['DELETE'],
['xml', 'json'],
'json',
['Content-Type' => 'application/json'],
$json_encoded,
NULL,
'',
],
];
return $safe_method_test_cases + $unsafe_method_bodied_test_cases + $unsafe_method_bodyless_test_cases;
}
}
/**
@ -341,9 +93,9 @@ class RequestHandlerTest extends KernelTestBase {
*/
class StubRequestHandlerResourcePlugin extends ResourceBase {
function get() {}
function post() {}
function patch() {}
function delete() {}
public function get() {}
public function post() {}
public function patch() {}
public function delete() {}
}

View file

@ -1,85 +0,0 @@
<?php
namespace Drupal\Tests\rest\Kernel;
use Drupal\Core\Url;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that REST type and relation link managers work as expected
* @group rest
*/
class RestLinkManagerTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['serialization', 'rest', 'rest_test', 'system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
\Drupal::service('router.builder')->rebuild();
}
/**
* Tests that type hooks work as expected.
*/
public function testRestLinkManagers() {
\Drupal::moduleHandler()->invoke('rest', 'install');
/* @var \Drupal\rest\LinkManager\TypeLinkManagerInterface $type_manager */
$type_manager = \Drupal::service('rest.link_manager.type');
$base = Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString();
$link = $type_manager->getTypeUri('node', 'page');
$this->assertEqual($link, $base . 'rest/type/node/page');
// Now with optional context.
$link = $type_manager->getTypeUri('node', 'page', ['rest_test' => TRUE]);
$this->assertEqual($link, 'rest_test_type');
/* @var \Drupal\rest\LinkManager\RelationLinkManagerInterface $relation_manager */
$relation_manager = \Drupal::service('rest.link_manager.relation');
$link = $relation_manager->getRelationUri('node', 'page', 'field_ref');
$this->assertEqual($link, $base . 'rest/relation/node/page/field_ref');
// Now with optional context.
$link = $relation_manager->getRelationUri('node', 'page', 'foobar', ['rest_test' => TRUE]);
$this->assertEqual($link, 'rest_test_relation');
}
/**
* Tests that type hooks work as expected even without install hook.
*/
public function testRestLinkManagersNoInstallHook() {
/* @var \Drupal\rest\LinkManager\TypeLinkManagerInterface $type_manager */
$type_manager = \Drupal::service('rest.link_manager.type');
$base = Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString();
$link = $type_manager->getTypeUri('node', 'page');
$this->assertEqual($link, $base . 'rest/type/node/page');
// Now with optional context.
$link = $type_manager->getTypeUri('node', 'page', ['rest_test' => TRUE]);
$this->assertEqual($link, 'rest_test_type');
/* @var \Drupal\rest\LinkManager\RelationLinkManagerInterface $relation_manager */
$relation_manager = \Drupal::service('rest.link_manager.relation');
$link = $relation_manager->getRelationUri('node', 'page', 'field_ref');
$this->assertEqual($link, $base . 'rest/relation/node/page/field_ref');
// Now with optional context.
$link = $relation_manager->getRelationUri('node', 'page', 'foobar', ['rest_test' => TRUE]);
$this->assertEqual($link, 'rest_test_relation');
}
/**
* Tests \Drupal\rest\LinkManager\LinkManager::setLinkDomain().
*/
public function testRestLinkManagersSetLinkDomain() {
/* @var \Drupal\rest\LinkManager\LinkManager $link_manager */
$link_manager = \Drupal::service('rest.link_manager');
$link_manager->setLinkDomain('http://example.com/');
$link = $link_manager->getTypeUri('node', 'page');
$this->assertEqual($link, 'http://example.com/rest/type/node/page');
$link = $link_manager->getRelationUri('node', 'page', 'field_ref');
$this->assertEqual($link, 'http://example.com/rest/relation/node/page/field_ref');
}
}

View file

@ -39,18 +39,18 @@ class CollectRoutesTest extends UnitTestCase {
->disableOriginalConstructor()
->getMock();
$this->view = $this->getMock('\Drupal\views\Entity\View', array('initHandlers'), array(
array('id' => 'test_view'),
$this->view = $this->getMock('\Drupal\views\Entity\View', ['initHandlers'], [
['id' => 'test_view'],
'view',
));
]);
$view_executable = $this->getMock('\Drupal\views\ViewExecutable', array('initHandlers', 'getTitle'), array(), '', FALSE);
$view_executable = $this->getMock('\Drupal\views\ViewExecutable', ['initHandlers', 'getTitle'], [], '', FALSE);
$view_executable->expects($this->any())
->method('getTitle')
->willReturn('View title');
$view_executable->storage = $this->view;
$view_executable->argument = array();
$view_executable->argument = [];
$display_manager = $this->getMockBuilder('\Drupal\views\Plugin\ViewsPluginManager')
->disableOriginalConstructor()
@ -86,21 +86,21 @@ class CollectRoutesTest extends UnitTestCase {
\Drupal::setContainer($container);
$this->restExport = RestExport::create($container, array(), "test_routes", array());
$this->restExport = RestExport::create($container, [], "test_routes", []);
$this->restExport->view = $view_executable;
// Initialize a display.
$this->restExport->display = array('id' => 'page_1');
$this->restExport->display = ['id' => 'page_1'];
// Set the style option.
$this->restExport->setOption('style', array('type' => 'serializer'));
$this->restExport->setOption('style', ['type' => 'serializer']);
// Set the auth option.
$this->restExport->setOption('auth', ['basic_auth']);
$display_manager->expects($this->once())
->method('getDefinition')
->will($this->returnValue(array('id' => 'test', 'provider' => 'test')));
->will($this->returnValue(['id' => 'test', 'provider' => 'test']));
$none = $this->getMockBuilder('\Drupal\views\Plugin\views\access\None')
->disableOriginalConstructor()
@ -110,11 +110,11 @@ class CollectRoutesTest extends UnitTestCase {
->method('createInstance')
->will($this->returnValue($none));
$style_plugin = $this->getMock('\Drupal\rest\Plugin\views\style\Serializer', array('getFormats', 'init'), array(), '', FALSE);
$style_plugin = $this->getMock('\Drupal\rest\Plugin\views\style\Serializer', ['getFormats', 'init'], [], '', FALSE);
$style_plugin->expects($this->once())
->method('getFormats')
->will($this->returnValue(array('json')));
->will($this->returnValue(['json']));
$style_plugin->expects($this->once())
->method('init')

View file

@ -0,0 +1,72 @@
<?php
namespace Drupal\Tests\rest\Unit;
use Drupal\Core\Entity\EntityConstraintViolationList;
use Drupal\node\Entity\Node;
use Drupal\Tests\UnitTestCase;
use Drupal\user\Entity\User;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* @group rest
* @coversDefaultClass \Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait
*/
class EntityResourceValidationTraitTest extends UnitTestCase {
/**
* @covers ::validate
*/
public function testValidate() {
$trait = $this->getMockForTrait('Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait');
$method = new \ReflectionMethod($trait, 'validate');
$method->setAccessible(TRUE);
$violations = $this->prophesize(EntityConstraintViolationList::class);
$violations->filterByFieldAccess()->shouldBeCalled()->willReturn([]);
$violations->count()->shouldBeCalled()->willReturn(0);
$entity = $this->prophesize(Node::class);
$entity->validate()->shouldBeCalled()->willReturn($violations->reveal());
$method->invoke($trait, $entity->reveal());
}
/**
* @covers ::validate
*/
public function testFailedValidate() {
$violation1 = $this->prophesize(ConstraintViolationInterface::class);
$violation1->getPropertyPath()->willReturn('property_path');
$violation1->getMessage()->willReturn('message');
$violation2 = $this->prophesize(ConstraintViolationInterface::class);
$violation2->getPropertyPath()->willReturn('property_path');
$violation2->getMessage()->willReturn('message');
$entity = $this->prophesize(User::class);
$violations = $this->getMockBuilder(EntityConstraintViolationList::class)
->setConstructorArgs([$entity->reveal(), [$violation1->reveal(), $violation2->reveal()]])
->setMethods(['filterByFieldAccess'])
->getMock();
$violations->expects($this->once())
->method('filterByFieldAccess')
->will($this->returnValue([]));
$entity->validate()->willReturn($violations);
$trait = $this->getMockForTrait('Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait');
$method = new \ReflectionMethod($trait, 'validate');
$method->setAccessible(TRUE);
$this->setExpectedException(UnprocessableEntityHttpException::class);
$method->invoke($trait, $entity->reveal());
}
}

View file

@ -0,0 +1,371 @@
<?php
namespace Drupal\Tests\rest\Unit\EventSubscriber;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\rest\EventSubscriber\ResourceResponseSubscriber;
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\ResourceResponse;
use Drupal\rest\ResourceResponseInterface;
use Drupal\serialization\Encoder\JsonEncoder;
use Drupal\serialization\Encoder\XmlEncoder;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface;
/**
* @coversDefaultClass \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
* @group rest
*/
class ResourceResponseSubscriberTest extends UnitTestCase {
/**
* @covers ::onResponse
* @dataProvider providerTestSerialization
*/
public function testSerialization($data, $expected_response = FALSE) {
$request = new Request();
$route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => 'restplugin'], ['_format' => 'json']));
$handler_response = new ResourceResponse($data);
$resource_response_subscriber = $this->getFunctioningResourceResponseSubscriber($route_match);
$event = new FilterResponseEvent(
$this->prophesize(HttpKernelInterface::class)->reveal(),
$request,
HttpKernelInterface::MASTER_REQUEST,
$handler_response
);
$resource_response_subscriber->onResponse($event);
// Content is a serialized version of the data we provided.
$this->assertEquals($expected_response !== FALSE ? $expected_response : Json::encode($data), $event->getResponse()->getContent());
}
public function providerTestSerialization() {
return [
// The default data for \Drupal\rest\ResourceResponse.
'default' => [NULL, ''],
'empty string' => [''],
'simple string' => ['string'],
'complex string' => ['Complex \ string $%^&@ with unicode ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ'],
'empty array' => [[]],
'numeric array' => [['test']],
'associative array' => [['test' => 'foobar']],
'boolean true' => [TRUE],
'boolean false' => [FALSE],
// @todo Not supported. https://www.drupal.org/node/2427811
// [new \stdClass()],
// [(object) ['test' => 'foobar']],
];
}
/**
* @covers ::getResponseFormat
*
* Note this does *not* need to test formats being requested that are not
* accepted by the server, because the routing system would have already
* prevented those from reaching the controller.
*
* @dataProvider providerTestResponseFormat
*/
public function testResponseFormat($methods, array $supported_formats, $request_format, array $request_headers, $request_body, $expected_response_format, $expected_response_content_type, $expected_response_content) {
$parameters = [];
if ($request_format !== FALSE) {
$parameters['_format'] = $request_format;
}
foreach ($request_headers as $key => $value) {
unset($request_headers[$key]);
$key = strtoupper(str_replace('-', '_', $key));
$request_headers[$key] = $value;
}
foreach ($methods as $method) {
$request = Request::create('/rest/test', $method, $parameters, [], [], $request_headers, $request_body);
$route_requirement_key_format = $request->isMethodSafe() ? '_format' : '_content_type_format';
$route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => $this->randomMachineName()], [$route_requirement_key_format => implode('|', $supported_formats)]));
$resource_response_subscriber = new ResourceResponseSubscriber(
$this->prophesize(SerializerInterface::class)->reveal(),
$this->prophesize(RendererInterface::class)->reveal(),
$route_match
);
$this->assertSame($expected_response_format, $resource_response_subscriber->getResponseFormat($route_match, $request));
}
}
/**
* @covers ::onResponse
* @covers ::getResponseFormat
* @covers ::renderResponseBody
* @covers ::flattenResponse
*
* @dataProvider providerTestResponseFormat
*/
public function testOnResponseWithCacheableResponse($methods, array $supported_formats, $request_format, array $request_headers, $request_body, $expected_response_format, $expected_response_content_type, $expected_response_content) {
$rest_config_name = $this->randomMachineName();
$parameters = [];
if ($request_format !== FALSE) {
$parameters['_format'] = $request_format;
}
foreach ($request_headers as $key => $value) {
unset($request_headers[$key]);
$key = strtoupper(str_replace('-', '_', $key));
$request_headers[$key] = $value;
}
foreach ($methods as $method) {
$request = Request::create('/rest/test', $method, $parameters, [], [], $request_headers, $request_body);
$route_requirement_key_format = $request->isMethodSafe() ? '_format' : '_content_type_format';
$route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => $rest_config_name], [$route_requirement_key_format => implode('|', $supported_formats)]));
// The RequestHandler must return a ResourceResponseInterface object.
$handler_response = new ResourceResponse($method !== 'DELETE' ? ['REST' => 'Drupal'] : NULL);
$this->assertInstanceOf(ResourceResponseInterface::class, $handler_response);
$this->assertInstanceOf(CacheableResponseInterface::class, $handler_response);
// The ResourceResponseSubscriber must then generate a response body and
// transform it to a plain CacheableResponse object.
$resource_response_subscriber = $this->getFunctioningResourceResponseSubscriber($route_match);
$event = new FilterResponseEvent(
$this->prophesize(HttpKernelInterface::class)->reveal(),
$request,
HttpKernelInterface::MASTER_REQUEST,
$handler_response
);
$resource_response_subscriber->onResponse($event);
$final_response = $event->getResponse();
$this->assertNotInstanceOf(ResourceResponseInterface::class, $final_response);
$this->assertInstanceOf(CacheableResponseInterface::class, $final_response);
$this->assertSame($expected_response_content_type, $final_response->headers->get('Content-Type'));
$this->assertEquals($expected_response_content, $final_response->getContent());
}
}
/**
* @covers ::onResponse
* @covers ::getResponseFormat
* @covers ::renderResponseBody
* @covers ::flattenResponse
*
* @dataProvider providerTestResponseFormat
*/
public function testOnResponseWithUncacheableResponse($methods, array $supported_formats, $request_format, array $request_headers, $request_body, $expected_response_format, $expected_response_content_type, $expected_response_content) {
$rest_config_name = $this->randomMachineName();
$parameters = [];
if ($request_format !== FALSE) {
$parameters['_format'] = $request_format;
}
foreach ($request_headers as $key => $value) {
unset($request_headers[$key]);
$key = strtoupper(str_replace('-', '_', $key));
$request_headers[$key] = $value;
}
foreach ($methods as $method) {
$request = Request::create('/rest/test', $method, $parameters, [], [], $request_headers, $request_body);
$route_requirement_key_format = $request->isMethodSafe() ? '_format' : '_content_type_format';
$route_match = new RouteMatch('test', new Route('/rest/test', ['_rest_resource_config' => $rest_config_name], [$route_requirement_key_format => implode('|', $supported_formats)]));
// The RequestHandler must return a ResourceResponseInterface object.
$handler_response = new ModifiedResourceResponse($method !== 'DELETE' ? ['REST' => 'Drupal'] : NULL);
$this->assertInstanceOf(ResourceResponseInterface::class, $handler_response);
$this->assertNotInstanceOf(CacheableResponseInterface::class, $handler_response);
// The ResourceResponseSubscriber must then generate a response body and
// transform it to a plain Response object.
$resource_response_subscriber = $this->getFunctioningResourceResponseSubscriber($route_match);
$event = new FilterResponseEvent(
$this->prophesize(HttpKernelInterface::class)->reveal(),
$request,
HttpKernelInterface::MASTER_REQUEST,
$handler_response
);
$resource_response_subscriber->onResponse($event);
$final_response = $event->getResponse();
$this->assertNotInstanceOf(ResourceResponseInterface::class, $final_response);
$this->assertNotInstanceOf(CacheableResponseInterface::class, $final_response);
$this->assertSame($expected_response_content_type, $final_response->headers->get('Content-Type'));
$this->assertEquals($expected_response_content, $final_response->getContent());
}
}
/**
* @return array
* 0. methods to test
* 1. supported formats for route requirements
* 2. request format
* 3. request headers
* 4. request body
* 5. expected response format
* 6. expected response content type
* 7. expected response body
*/
public function providerTestResponseFormat() {
$json_encoded = Json::encode(['REST' => 'Drupal']);
$xml_encoded = "<?xml version=\"1.0\"?>\n<response><REST>Drupal</REST></response>\n";
$safe_method_test_cases = [
'safe methods: client requested format (JSON)' => [
// @todo add 'HEAD' in https://www.drupal.org/node/2752325
['GET'],
['xml', 'json'],
'json',
[],
NULL,
'json',
'application/json',
$json_encoded,
],
'safe methods: client requested format (XML)' => [
// @todo add 'HEAD' in https://www.drupal.org/node/2752325
['GET'],
['xml', 'json'],
'xml',
[],
NULL,
'xml',
'text/xml',
$xml_encoded,
],
'safe methods: client requested no format: response should use the first configured format (JSON)' => [
// @todo add 'HEAD' in https://www.drupal.org/node/2752325
['GET'],
['json', 'xml'],
FALSE,
[],
NULL,
'json',
'application/json',
$json_encoded,
],
'safe methods: client requested no format: response should use the first configured format (XML)' => [
// @todo add 'HEAD' in https://www.drupal.org/node/2752325
['GET'],
['xml', 'json'],
FALSE,
[],
NULL,
'xml',
'text/xml',
$xml_encoded,
],
];
$unsafe_method_bodied_test_cases = [
'unsafe methods with response (POST, PATCH): client requested no format, response should use request body format (JSON)' => [
['POST', 'PATCH'],
['xml', 'json'],
FALSE,
['Content-Type' => 'application/json'],
$json_encoded,
'json',
'application/json',
$json_encoded,
],
'unsafe methods with response (POST, PATCH): client requested no format, response should use request body format (XML)' => [
['POST', 'PATCH'],
['xml', 'json'],
FALSE,
['Content-Type' => 'text/xml'],
$xml_encoded,
'xml',
'text/xml',
$xml_encoded,
],
'unsafe methods with response (POST, PATCH): client requested format other than request body format (JSON): response format should use requested format (XML)' => [
['POST', 'PATCH'],
['xml', 'json'],
'xml',
['Content-Type' => 'application/json'],
$json_encoded,
'xml',
'text/xml',
$xml_encoded,
],
'unsafe methods with response (POST, PATCH): client requested format other than request body format (XML), but is allowed for the request body (JSON)' => [
['POST', 'PATCH'],
['xml', 'json'],
'json',
['Content-Type' => 'text/xml'],
$xml_encoded,
'json',
'application/json',
$json_encoded,
],
];
$unsafe_method_bodyless_test_cases = [
'unsafe methods with response bodies (DELETE): client requested no format, response should have no format' => [
['DELETE'],
['xml', 'json'],
FALSE,
['Content-Type' => 'application/json'],
NULL,
'xml',
NULL,
'',
],
'unsafe methods with response bodies (DELETE): client requested format (XML), response should have no format' => [
['DELETE'],
['xml', 'json'],
'xml',
['Content-Type' => 'application/json'],
NULL,
'xml',
NULL,
'',
],
'unsafe methods with response bodies (DELETE): client requested format (JSON), response should have no format' => [
['DELETE'],
['xml', 'json'],
'json',
['Content-Type' => 'application/json'],
NULL,
'json',
NULL,
'',
],
];
return $safe_method_test_cases + $unsafe_method_bodied_test_cases + $unsafe_method_bodyless_test_cases;
}
/**
* @return \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
*/
protected function getFunctioningResourceResponseSubscriber(RouteMatchInterface $route_match) {
// Create a dummy of the renderer service.
$renderer = $this->prophesize(RendererInterface::class);
$renderer->executeInRenderContext(Argument::type(RenderContext::class), Argument::type('callable'))
->will(function ($args) {
$callable = $args[1];
return $callable();
});
// Instantiate the ResourceResponseSubscriber we will test.
$resource_response_subscriber = new ResourceResponseSubscriber(
new Serializer([], [new JsonEncoder(), new XmlEncoder()]),
$renderer->reveal(),
$route_match
);
return $resource_response_subscriber;
}
}