Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,553 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\Core\Config\Entity\ConfigEntityType;
use Drupal\node\NodeInterface;
use Drupal\rest\RestResourceConfigInterface;
use Drupal\simpletest\WebTestBase;
/**
* Test helper class that provides a REST client method to send HTTP requests.
*
* @deprecated in Drupal 8.3.x-dev and will be removed before Drupal 9.0.0. Use \Drupal\Tests\rest\Functional\ResourceTestBase and \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase instead. Only retained for contributed module tests that may be using this base class.
*/
abstract class RESTTestBase extends WebTestBase {
/**
* The REST resource config storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $resourceConfigStorage;
/**
* The default serialization format to use for testing REST operations.
*
* @var string
*/
protected $defaultFormat;
/**
* The default MIME type to use for testing REST operations.
*
* @var string
*/
protected $defaultMimeType;
/**
* The entity type to use for testing.
*
* @var string
*/
protected $testEntityType = 'entity_test';
/**
* The default authentication provider to use for testing REST operations.
*
* @var array
*/
protected $defaultAuth;
/**
* The raw response body from http request operations.
*
* @var array
*/
protected $responseBody;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('rest', 'entity_test');
protected function setUp() {
parent::setUp();
$this->defaultFormat = 'hal_json';
$this->defaultMimeType = 'application/hal+json';
$this->defaultAuth = array('cookie');
$this->resourceConfigStorage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
// Create a test content type for node testing.
if (in_array('node', static::$modules)) {
$this->drupalCreateContentType(array('name' => 'resttest', 'type' => 'resttest'));
}
}
/**
* Helper function to issue a HTTP request with simpletest's cURL.
*
* @param string|\Drupal\Core\Url $url
* A Url object or system path.
* @param string $method
* HTTP method, one of GET, POST, PUT or DELETE.
* @param string $body
* The body for POST and PUT.
* @param string $mime_type
* The MIME type of the transmitted content.
* @param bool $csrf_token
* If NULL, a CSRF token will be retrieved and used. If FALSE, omit the
* X-CSRF-Token request header (to simulate developer error). Otherwise, the
* passed in value will be used as the value for the X-CSRF-Token request
* header (to simulate developer error, by sending an invalid CSRF token).
*
* @return string
* The content returned from the request.
*/
protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL, $csrf_token = NULL) {
if (!isset($mime_type)) {
$mime_type = $this->defaultMimeType;
}
if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) {
// GET the CSRF token first for writing requests.
$requested_token = $this->drupalGet('session/token');
}
$url = $this->buildUrl($url);
$curl_options = array();
switch ($method) {
case 'GET':
// Set query if there are additional GET parameters.
$curl_options = array(
CURLOPT_HTTPGET => TRUE,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
);
break;
case 'HEAD':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_CUSTOMREQUEST => 'HEAD',
CURLOPT_URL => $url,
CURLOPT_NOBODY => TRUE,
CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
);
break;
case 'POST':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
'Content-Type: ' . $mime_type,
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
) : array(
'Content-Type: ' . $mime_type,
),
);
break;
case 'PUT':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
'Content-Type: ' . $mime_type,
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
) : array(
'Content-Type: ' . $mime_type,
),
);
break;
case 'PATCH':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_POSTFIELDS => $body,
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
'Content-Type: ' . $mime_type,
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
) : array(
'Content-Type: ' . $mime_type,
),
);
break;
case 'DELETE':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_URL => $url,
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array(
'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token),
) : array(),
);
break;
}
if ($mime_type === 'none') {
unset($curl_options[CURLOPT_HTTPHEADER]['Content-Type']);
}
$this->responseBody = $this->curlExec($curl_options);
// Ensure that any changes to variables in the other thread are picked up.
$this->refreshVariables();
$headers = $this->drupalGetHeaders();
$this->verbose($method . ' request to: ' . $url .
'<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
(isset($curl_options[CURLOPT_HTTPHEADER]) ? '<hr />Request headers: ' . nl2br(print_r($curl_options[CURLOPT_HTTPHEADER], TRUE)) : '' ) .
(isset($curl_options[CURLOPT_POSTFIELDS]) ? '<hr />Request body: ' . nl2br(print_r($curl_options[CURLOPT_POSTFIELDS], TRUE)) : '' ) .
'<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
'<hr />Response body: ' . $this->responseBody);
return $this->responseBody;
}
/**
* Creates entity objects based on their types.
*
* @param string $entity_type
* The type of the entity that should be created.
*
* @return \Drupal\Core\Entity\EntityInterface
* The new entity object.
*/
protected function entityCreate($entity_type) {
return $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create($this->entityValues($entity_type));
}
/**
* Provides an array of suitable property values for an entity type.
*
* Required properties differ from entity type to entity type, so we keep a
* minimum mapping here.
*
* @param string $entity_type_id
* The ID of the type of entity that should be created.
*
* @return array
* An array of values keyed by property name.
*/
protected function entityValues($entity_type_id) {
switch ($entity_type_id) {
case 'entity_test':
return array(
'name' => $this->randomMachineName(),
'user_id' => 1,
'field_test_text' => array(0 => array(
'value' => $this->randomString(),
'format' => 'plain_text',
)),
);
case 'config_test':
return [
'id' => $this->randomMachineName(),
'label' => 'Test label',
];
case 'node':
return array('title' => $this->randomString(), 'type' => 'resttest');
case 'node_type':
return array(
'type' => 'article',
'name' => $this->randomMachineName(),
);
case 'user':
return array('name' => $this->randomMachineName());
case 'comment':
return [
'subject' => $this->randomMachineName(),
'entity_type' => 'node',
'comment_type' => 'comment',
'comment_body' => $this->randomString(),
'entity_id' => 'invalid',
'field_name' => 'comment',
];
case 'taxonomy_vocabulary':
return [
'vid' => 'tags',
'name' => $this->randomMachineName(),
];
default:
if ($this->isConfigEntity($entity_type_id)) {
return $this->configEntityValues($entity_type_id);
}
return array();
}
}
/**
* Enables the REST service interface for a specific entity type.
*
* @param string|false $resource_type
* The resource type that should get REST API enabled or FALSE to disable all
* resource types.
* @param string $method
* The HTTP method to enable, e.g. GET, POST etc.
* @param string|array $format
* (Optional) The serialization format, e.g. hal_json, or a list of formats.
* @param array $auth
* (Optional) The list of valid authentication methods.
*/
protected function enableService($resource_type, $method = 'GET', $format = NULL, array $auth = []) {
if ($resource_type) {
// Enable REST API for this entity type.
$resource_config_id = str_replace(':', '.', $resource_type);
// get entity by id
/** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
$resource_config = $this->resourceConfigStorage->load($resource_config_id);
if (!$resource_config) {
$resource_config = $this->resourceConfigStorage->create([
'id' => $resource_config_id,
'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
'configuration' => []
]);
}
$configuration = $resource_config->get('configuration');
if (is_array($format)) {
for ($i = 0; $i < count($format); $i++) {
$configuration[$method]['supported_formats'][] = $format[$i];
}
}
else {
if ($format == NULL) {
$format = $this->defaultFormat;
}
$configuration[$method]['supported_formats'][] = $format;
}
if (!is_array($auth) || empty($auth)) {
$auth = $this->defaultAuth;
}
foreach ($auth as $auth_provider) {
$configuration[$method]['supported_auth'][] = $auth_provider;
}
$resource_config->set('configuration', $configuration);
$resource_config->save();
}
else {
foreach ($this->resourceConfigStorage->loadMultiple() as $resource_config) {
$resource_config->delete();
}
}
$this->rebuildCache();
}
/**
* Rebuilds routing caches.
*/
protected function rebuildCache() {
// Rebuild routing cache, so that the REST API paths are available.
$this->container->get('router.builder')->rebuild();
}
/**
* {@inheritdoc}
*
* This method is overridden to deal with a cURL quirk: the usage of
* CURLOPT_CUSTOMREQUEST cannot be unset on the cURL handle, so we need to
* override it every time it is omitted.
*/
protected function curlExec($curl_options, $redirect = FALSE) {
if (!isset($curl_options[CURLOPT_CUSTOMREQUEST])) {
if (!empty($curl_options[CURLOPT_HTTPGET])) {
$curl_options[CURLOPT_CUSTOMREQUEST] = 'GET';
}
if (!empty($curl_options[CURLOPT_POST])) {
$curl_options[CURLOPT_CUSTOMREQUEST] = 'POST';
}
}
return parent::curlExec($curl_options, $redirect);
}
/**
* Provides the necessary user permissions for entity operations.
*
* @param string $entity_type_id
* The entity type.
* @param string $operation
* The operation, one of 'view', 'create', 'update' or 'delete'.
*
* @return array
* The set of user permission strings.
*/
protected function entityPermissions($entity_type_id, $operation) {
switch ($entity_type_id) {
case 'entity_test':
switch ($operation) {
case 'view':
return array('view test entity');
case 'create':
case 'update':
case 'delete':
return array('administer entity_test content');
}
case 'node':
switch ($operation) {
case 'view':
return array('access content');
case 'create':
return array('create resttest content');
case 'update':
return array('edit any resttest content');
case 'delete':
return array('delete any resttest content');
}
case 'comment':
switch ($operation) {
case 'view':
return ['access comments'];
case 'create':
return ['post comments', 'skip comment approval'];
case 'update':
return ['edit own comments'];
case 'delete':
return ['administer comments'];
}
break;
case 'user':
switch ($operation) {
case 'view':
return ['access user profiles'];
default:
return ['administer users'];
}
default:
if ($this->isConfigEntity($entity_type_id)) {
$entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
if ($admin_permission = $entity_type->getAdminPermission()) {
return [$admin_permission];
}
}
}
return [];
}
/**
* Loads an entity based on the location URL returned in the location header.
*
* @param string $location_url
* The URL returned in the Location header.
*
* @return \Drupal\Core\Entity\Entity|false
* The entity or FALSE if there is no matching entity.
*/
protected function loadEntityFromLocationHeader($location_url) {
$url_parts = explode('/', $location_url);
$id = end($url_parts);
return $this->container->get('entity_type.manager')
->getStorage($this->testEntityType)->load($id);
}
/**
* Remove node fields that can only be written by an admin user.
*
* @param \Drupal\node\NodeInterface $node
* The node to remove fields where non-administrative users cannot write.
*
* @return \Drupal\node\NodeInterface
* The node with removed fields.
*/
protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
$node->set('status', NULL);
$node->set('created', NULL);
$node->set('changed', NULL);
$node->set('promote', NULL);
$node->set('sticky', NULL);
$node->set('revision_timestamp', NULL);
$node->set('revision_log', NULL);
$node->set('uid', NULL);
return $node;
}
/**
* Check to see if the HTTP request response body is identical to the expected
* value.
*
* @param $expected
* The first value to check.
* @param $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return bool
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertResponseBody($expected, $message = '', $group = 'REST Response') {
return $this->assertIdentical($expected, $this->responseBody, $message ? $message : strtr('Response body @expected (expected) is equal to @response (actual).', array('@expected' => var_export($expected, TRUE), '@response' => var_export($this->responseBody, TRUE))), $group);
}
/**
* Checks if an entity type id is for a Config Entity.
*
* @param string $entity_type_id
* The entity type ID to check.
*
* @return bool
* TRUE if the entity is a Config Entity, FALSE otherwise.
*/
protected function isConfigEntity($entity_type_id) {
return \Drupal::entityTypeManager()->getDefinition($entity_type_id) instanceof ConfigEntityType;
}
/**
* Provides an array of suitable property values for a config entity type.
*
* Config entities have some common keys that need to be created. Required
* properties differ among config entity types, so we keep a minimum mapping
* here.
*
* @param string $entity_type_id
* The ID of the type of entity that should be created.
*
* @return array
* An array of values keyed by property name.
*/
protected function configEntityValues($entity_type_id) {
$entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
$keys = $entity_type->getKeys();
$values = [];
// Fill out known key values that are shared across entity types.
foreach ($keys as $key) {
if ($key === 'id' || $key === 'label') {
$values[$key] = $this->randomMachineName();
}
}
// Add extra values for particular entity types.
switch ($entity_type_id) {
case 'block':
$values['plugin'] = 'system_powered_by_block';
break;
}
return $values;
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\Core\Session\AccountInterface;
use Drupal\rest\RestResourceConfigInterface;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the structure of a REST resource.
*
* @group rest
*/
class ResourceTest extends RESTTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('hal', 'rest', 'entity_test', 'rest_test');
/**
* The entity.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create an entity programmatic.
$this->entity = $this->entityCreate('entity_test');
$this->entity->save();
Role::load(AccountInterface::ANONYMOUS_ROLE)
->grantPermission('view test entity')
->save();
}
/**
* Tests that a resource without formats cannot be enabled.
*/
public function testFormats() {
$this->resourceConfigStorage->create([
'id' => 'entity.entity_test',
'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
'configuration' => [
'GET' => [
'supported_auth' => [
'basic_auth',
],
],
],
])->save();
// Verify that accessing the resource returns 406.
$response = $this->httpRequest($this->entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
// \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
// non-REST route a match, but a lower quality one: no format restrictions
// means there's always a match and hence when there is no matching REST
// route, the non-REST route is used, but can't render into
// application/hal+json, so it returns a 406.
$this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.');
$this->curlClose();
}
/**
* Tests that a resource without authentication cannot be enabled.
*/
public function testAuthentication() {
$this->resourceConfigStorage->create([
'id' => 'entity.entity_test',
'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
'configuration' => [
'GET' => [
'supported_formats' => [
'hal_json',
],
],
],
])->save();
// Verify that accessing the resource returns 401.
$response = $this->httpRequest($this->entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
// \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
// non-REST route a match, but a lower quality one: no format restrictions
// means there's always a match and hence when there is no matching REST
// route, the non-REST route is used, but can't render into
// application/hal+json, so it returns a 406.
$this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.');
$this->curlClose();
}
/**
* Tests that serialization_class is optional.
*/
public function testSerializationClassIsOptional() {
$this->enableService('serialization_test', 'POST', 'json');
Role::load(RoleInterface::ANONYMOUS_ID)
->grantPermission('restful post serialization_test')
->save();
$serialized = $this->container->get('serializer')->serialize(['foo', 'bar'], 'json');
$this->httpRequest('serialization_test', 'POST', $serialized, 'application/json');
$this->assertResponse(200);
$this->assertResponseBody('["foo","bar"]');
}
/**
* Tests that resource URI paths are formatted properly.
*/
public function testUriPaths() {
$this->enableService('entity:entity_test');
/** @var \Drupal\rest\Plugin\Type\ResourcePluginManager $manager */
$manager = \Drupal::service('plugin.manager.rest');
foreach ($manager->getDefinitions() as $resource => $definition) {
foreach ($definition['uri_paths'] as $key => $uri_path) {
$this->assertFalse(strpos($uri_path, '//'), 'The resource URI path does not have duplicate slashes.');
}
}
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Drupal\rest\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests that existing sites continue to use permissions for EntityResource.
*
* @see https://www.drupal.org/node/2664780
*
* @group rest
*/
class EntityResourcePermissionsUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['rest', 'serialization'];
/**
* {@inheritdoc}
*/
public function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php',
];
}
/**
* Tests rest_update_8203().
*/
public function testBcEntityResourcePermissionSettingAdded() {
$permission_handler = $this->container->get('user.permissions');
$is_rest_resource_permission = function ($permission) {
return $permission['provider'] === 'rest' && (string) $permission['title'] !== 'Administer REST resource configuration';
};
// Make sure we have the expected values before the update.
$rest_settings = $this->config('rest.settings');
$this->assertFalse(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData()));
$this->assertEqual([], array_filter($permission_handler->getPermissions(), $is_rest_resource_permission));
$this->runUpdates();
// Make sure we have the expected values after the update.
$rest_settings = $this->config('rest.settings');
$this->assertTrue(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData()));
$this->assertTrue($rest_settings->get('bc_entity_resource_permissions'));
$rest_permissions = array_keys(array_filter($permission_handler->getPermissions(), $is_rest_resource_permission));
$this->assertEqual(['restful delete entity:node', 'restful get entity:node', 'restful patch entity:node', 'restful post entity:node'], $rest_permissions);
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\rest\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests method-granularity REST config is simplified to resource-granularity.
*
* @see https://www.drupal.org/node/2721595
* @see rest_post_update_resource_granularity()
*
* @group rest
*/
class ResourceGranularityUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['rest', 'serialization'];
/**
* {@inheritdoc}
*/
public function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php',
];
}
/**
* Tests rest_post_update_simplify_resource_granularity().
*/
public function testMethodGranularityConvertedToResourceGranularity() {
/** @var \Drupal\Core\Entity\EntityStorageInterface $resource_config_storage */
$resource_config_storage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
// Make sure we have the expected values before the update.
$resource_config_entities = $resource_config_storage->loadMultiple();
$this->assertIdentical(['entity.comment', 'entity.node', 'entity.user'], array_keys($resource_config_entities));
$this->assertIdentical('method', $resource_config_entities['entity.node']->get('granularity'));
$this->assertIdentical('method', $resource_config_entities['entity.comment']->get('granularity'));
$this->assertIdentical('method', $resource_config_entities['entity.user']->get('granularity'));
// Read the existing 'entity:comment' and 'entity:user' resource
// configuration so we can verify it after the update.
$comment_resource_configuration = $resource_config_entities['entity.comment']->get('configuration');
$user_resource_configuration = $resource_config_entities['entity.user']->get('configuration');
$this->runUpdates();
// Make sure we have the expected values after the update.
$resource_config_entities = $resource_config_storage->loadMultiple();
$this->assertIdentical(['entity.comment', 'entity.node', 'entity.user'], array_keys($resource_config_entities));
// 'entity:node' should be updated.
$this->assertIdentical('resource', $resource_config_entities['entity.node']->get('granularity'));
$this->assertidentical($resource_config_entities['entity.node']->get('configuration'), [
'methods' => ['GET', 'POST', 'PATCH', 'DELETE'],
'formats' => ['hal_json'],
'authentication' => ['basic_auth'],
]);
// 'entity:comment' should be unchanged.
$this->assertIdentical('method', $resource_config_entities['entity.comment']->get('granularity'));
$this->assertIdentical($comment_resource_configuration, $resource_config_entities['entity.comment']->get('configuration'));
// 'entity:user' should be unchanged.
$this->assertIdentical('method', $resource_config_entities['entity.user']->get('granularity'));
$this->assertIdentical($user_resource_configuration, $resource_config_entities['entity.user']->get('configuration'));
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace Drupal\rest\Tests\Update;
use Drupal\rest\RestResourceConfigInterface;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests that rest.settings is converted to rest_resource_config entities.
*
* @see https://www.drupal.org/node/2308745
* @see rest_update_8201()
* @see rest_post_update_create_rest_resource_config_entities()
*
* @group rest
*/
class RestConfigurationEntitiesUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['rest', 'serialization'];
/**
* {@inheritdoc}
*/
public function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_update_8201.php',
];
}
/**
* Tests rest_update_8201().
*/
public function testResourcesConvertedToConfigEntities() {
/** @var \Drupal\Core\Entity\EntityStorageInterface $resource_config_storage */
$resource_config_storage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
// Make sure we have the expected values before the update.
$rest_settings = $this->config('rest.settings');
$this->assertTrue(array_key_exists('resources', $rest_settings->getRawData()));
$this->assertTrue(array_key_exists('entity:node', $rest_settings->getRawData()['resources']));
$resource_config_entities = $resource_config_storage->loadMultiple();
$this->assertIdentical([], array_keys($resource_config_entities));
$this->runUpdates();
// Make sure we have the expected values after the update.
$rest_settings = $this->config('rest.settings');
$this->assertFalse(array_key_exists('resources', $rest_settings->getRawData()));
$resource_config_entities = $resource_config_storage->loadMultiple();
$this->assertIdentical(['entity.node'], array_keys($resource_config_entities));
$node_resource_config_entity = $resource_config_entities['entity.node'];
$this->assertIdentical(RestResourceConfigInterface::RESOURCE_GRANULARITY, $node_resource_config_entity->get('granularity'));
$this->assertIdentical([
'methods' => ['GET'],
'formats' => ['json'],
'authentication' => ['basic_auth'],
], $node_resource_config_entity->get('configuration'));
$this->assertIdentical(['module' => ['basic_auth', 'node', 'serialization']], $node_resource_config_entity->getDependencies());
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\rest\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Ensures that update hook is run properly for REST Export config.
*
* @group Update
*/
class RestExportAuthUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../tests/fixtures/update/rest-export-with-authentication.php',
];
}
/**
* Ensures that update hook is run for rest module.
*/
public function testUpdate() {
$this->runUpdates();
// Get particular view.
$view = \Drupal::entityTypeManager()->getStorage('view')->load('rest_export_with_authorization');
$displays = $view->get('display');
$this->assertIdentical($displays['rest_export_1']['display_options']['auth']['basic_auth'], 'basic_auth', 'Basic authentication is set as authentication method.');
}
}

View file

@ -0,0 +1,88 @@
<?php
namespace Drupal\rest\Tests\Views;
use Drupal\node\Entity\Node;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
/**
* Tests the display of an excluded field that is used as a token.
*
* @group rest
* @see \Drupal\rest\Plugin\views\display\RestExport
* @see \Drupal\rest\Plugin\views\row\DataFieldRow
*/
class ExcludedFieldTokenTest extends ViewTestBase {
/**
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* The views that are used by this test.
*
* @var array
*/
public static $testViews = ['test_excluded_field_token_display'];
/**
* The modules that need to be installed for this test.
*
* @var array
*/
public static $modules = [
'entity_test',
'rest_test_views',
'node',
'field',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), ['rest_test_views']);
// Create some test content.
for ($i = 1; $i <= 10; $i++) {
Node::create([
'type' => 'article',
'title' => 'Article test ' . $i,
])->save();
}
$this->enableViewsTestModule();
$this->view = Views::getView('test_excluded_field_token_display');
$this->view->setDisplay('rest_export_1');
}
/**
* Tests the display of an excluded title field when used as a token.
*/
public function testExcludedTitleTokenDisplay() {
$actual_json = $this->drupalGetWithFormat($this->view->getPath(), 'json');
$this->assertResponse(200);
$expected = [
['nothing' => 'Article test 10'],
['nothing' => 'Article test 9'],
['nothing' => 'Article test 8'],
['nothing' => 'Article test 7'],
['nothing' => 'Article test 6'],
['nothing' => 'Article test 5'],
['nothing' => 'Article test 4'],
['nothing' => 'Article test 3'],
['nothing' => 'Article test 2'],
['nothing' => 'Article test 1'],
];
$this->assertIdentical($actual_json, json_encode($expected));
}
}

View file

@ -0,0 +1,838 @@
<?php
namespace Drupal\rest\Tests\Views;
use Drupal\Core\Cache\Cache;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\views\Entity\View;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Views;
use Drupal\views\Tests\Plugin\PluginTestBase;
use Drupal\views\Tests\ViewTestData;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests the serializer style plugin.
*
* @group rest
* @see \Drupal\rest\Plugin\views\display\RestExport
* @see \Drupal\rest\Plugin\views\style\Serializer
* @see \Drupal\rest\Plugin\views\row\DataEntityRow
* @see \Drupal\rest\Plugin\views\row\DataFieldRow
*/
class StyleSerializerTest extends PluginTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* {@inheritdoc}
*/
protected $dumpHeaders = TRUE;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field', 'language', 'basic_auth');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_serializer_display_field', 'test_serializer_display_entity', 'test_serializer_display_entity_translated', 'test_serializer_node_display_field', 'test_serializer_node_exposed_filter');
/**
* A user with administrative privileges to look at test entity and configure views.
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), array('rest_test_views'));
$this->adminUser = $this->drupalCreateUser(array('administer views', 'administer entity_test content', 'access user profiles', 'view test entity'));
// Save some entity_test entities.
for ($i = 1; $i <= 10; $i++) {
EntityTest::create(array('name' => 'test_' . $i, 'user_id' => $this->adminUser->id()))->save();
}
$this->enableViewsTestModule();
}
/**
* Checks that the auth options restricts access to a REST views display.
*/
public function testRestViewsAuthentication() {
// Assume the view is hidden behind a permission.
$this->drupalGetWithFormat('test/serialize/auth_with_perm', 'json');
$this->assertResponse(401);
// Not even logging in would make it possible to see the view, because then
// we are denied based on authentication method (cookie).
$this->drupalLogin($this->adminUser);
$this->drupalGetWithFormat('test/serialize/auth_with_perm', 'json');
$this->assertResponse(403);
$this->drupalLogout();
// But if we use the basic auth authentication strategy, we should be able
// to see the page.
$url = $this->buildUrl('test/serialize/auth_with_perm');
$response = \Drupal::httpClient()->get($url, [
'auth' => [$this->adminUser->getUsername(), $this->adminUser->pass_raw],
]);
// Ensure that any changes to variables in the other thread are picked up.
$this->refreshVariables();
$headers = $response->getHeaders();
$this->verbose('GET request to: ' . $url .
'<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
'<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
'<hr />Response body: ' . (string) $response->getBody());
$this->assertResponse(200);
}
/**
* Checks the behavior of the Serializer callback paths and row plugins.
*/
public function testSerializerResponses() {
// Test the serialize callback.
$view = Views::getView('test_serializer_display_field');
$view->initDisplay();
$this->executeView($view);
$actual_json = $this->drupalGetWithFormat('test/serialize/field', 'json');
$this->assertResponse(200);
$this->assertCacheTags($view->getCacheTags());
$this->assertCacheContexts(['languages:language_interface', 'theme', 'request_format']);
// @todo Due to https://www.drupal.org/node/2352009 we can't yet test the
// propagation of cache max-age.
// Test the http Content-type.
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
$expected = array();
foreach ($view->result as $row) {
$expected_row = array();
foreach ($view->field as $id => $field) {
$expected_row[$id] = $field->render($row);
}
$expected[] = $expected_row;
}
$this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.');
// Test that the rendered output and the preview output are the same.
$view->destroy();
$view->setDisplay('rest_export_1');
// Mock the request content type by setting it on the display handler.
$view->display_handler->setContentType('json');
$output = $view->preview();
$this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.');
// Test a 403 callback.
$this->drupalGet('test/serialize/denied');
$this->assertResponse(403);
// Test the entity rows.
$view = Views::getView('test_serializer_display_entity');
$view->initDisplay();
$this->executeView($view);
// Get the serializer service.
$serializer = $this->container->get('serializer');
$entities = array();
foreach ($view->result as $row) {
$entities[] = $row->_entity;
}
$expected = $serializer->serialize($entities, 'json');
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
$this->assertResponse(200);
$this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
$expected_cache_tags = $view->getCacheTags();
$expected_cache_tags[] = 'entity_test_list';
/** @var \Drupal\Core\Entity\EntityInterface $entity */
foreach ($entities as $entity) {
$expected_cache_tags = Cache::mergeTags($expected_cache_tags, $entity->getCacheTags());
}
$this->assertCacheTags($expected_cache_tags);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
$expected = $serializer->serialize($entities, 'hal_json');
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'hal_json');
$this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
$this->assertCacheTags($expected_cache_tags);
// Change the default format to xml.
$view->setDisplay('rest_export_1');
$view->getDisplay()->setOption('style', array(
'type' => 'serializer',
'options' => array(
'uses_fields' => FALSE,
'formats' => array(
'xml' => 'xml',
),
),
));
$view->save();
$expected = $serializer->serialize($entities, 'xml');
$actual_xml = $this->drupalGet('test/serialize/entity');
$this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
$this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
// Allow multiple formats.
$view->setDisplay('rest_export_1');
$view->getDisplay()->setOption('style', array(
'type' => 'serializer',
'options' => array(
'uses_fields' => FALSE,
'formats' => array(
'xml' => 'xml',
'json' => 'json',
),
),
));
$view->save();
$expected = $serializer->serialize($entities, 'json');
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
$this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
$expected = $serializer->serialize($entities, 'xml');
$actual_xml = $this->drupalGetWithFormat('test/serialize/entity', 'xml');
$this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
}
/**
* Verifies site maintenance mode functionality.
*/
protected function testSiteMaintenance() {
$view = Views::getView('test_serializer_display_field');
$view->initDisplay();
$this->executeView($view);
// Set the site to maintenance mode.
$this->container->get('state')->set('system.maintenance_mode', TRUE);
$this->drupalGetWithFormat('test/serialize/entity', 'json');
// Verify that the endpoint is unavailable for anonymous users.
$this->assertResponse(503);
}
/**
* Sets up a request on the request stack with a specified format.
*
* @param string $format
* The new request format.
*/
protected function addRequestWithFormat($format) {
$request = \Drupal::request();
$request = clone $request;
$request->setRequestFormat($format);
\Drupal::requestStack()->push($request);
}
/**
* Tests REST export with views render caching enabled.
*/
public function testRestRenderCaching() {
$this->drupalLogin($this->adminUser);
/** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
$render_cache = \Drupal::service('render_cache');
// Enable render caching for the views.
/** @var \Drupal\views\ViewEntityInterface $storage */
$storage = View::load('test_serializer_display_entity');
$options = &$storage->getDisplay('default');
$options['display_options']['cache'] = [
'type' => 'tag',
];
$storage->save();
$original = DisplayPluginBase::buildBasicRenderable('test_serializer_display_entity', 'rest_export_1');
// Ensure that there is no corresponding render cache item yet.
$original['#cache'] += ['contexts' => []];
$original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
$cache_tags = [
'config:views.view.test_serializer_display_entity',
'entity_test:1',
'entity_test:10',
'entity_test:2',
'entity_test:3',
'entity_test:4',
'entity_test:5',
'entity_test:6',
'entity_test:7',
'entity_test:8',
'entity_test:9',
'entity_test_list'
];
$cache_contexts = [
'entity_test_view_grants',
'languages:language_interface',
'theme',
'request_format',
];
$this->assertFalse($render_cache->get($original));
// Request the page, once in XML and once in JSON to ensure that the caching
// varies by it.
$result1 = $this->drupalGetJSON('test/serialize/entity');
$this->addRequestWithFormat('json');
$this->assertHeader('content-type', 'application/json');
$this->assertCacheContexts($cache_contexts);
$this->assertCacheTags($cache_tags);
$this->assertTrue($render_cache->get($original));
$result_xml = $this->drupalGetWithFormat('test/serialize/entity', 'xml');
$this->addRequestWithFormat('xml');
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
$this->assertCacheContexts($cache_contexts);
$this->assertCacheTags($cache_tags);
$this->assertTrue($render_cache->get($original));
// Ensure that the XML output is different from the JSON one.
$this->assertNotEqual($result1, $result_xml);
// Ensure that the cached page works.
$result2 = $this->drupalGetJSON('test/serialize/entity');
$this->addRequestWithFormat('json');
$this->assertHeader('content-type', 'application/json');
$this->assertEqual($result2, $result1);
$this->assertCacheContexts($cache_contexts);
$this->assertCacheTags($cache_tags);
$this->assertTrue($render_cache->get($original));
// Create a new entity and ensure that the cache tags are taken over.
EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save();
$result3 = $this->drupalGetJSON('test/serialize/entity');
$this->addRequestWithFormat('json');
$this->assertHeader('content-type', 'application/json');
$this->assertNotEqual($result3, $result2);
// Add the new entity cache tag and remove the first one, because we just
// show 10 items in total.
$cache_tags[] = 'entity_test:11';
unset($cache_tags[array_search('entity_test:1', $cache_tags)]);
$this->assertCacheContexts($cache_contexts);
$this->assertCacheTags($cache_tags);
$this->assertTrue($render_cache->get($original));
}
/**
* Tests the response format configuration.
*/
public function testResponseFormatConfiguration() {
$this->drupalLogin($this->adminUser);
$style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options';
// Select only 'xml' as an accepted format.
$this->drupalPostForm($style_options, array('style_options[formats][xml]' => 'xml'), t('Apply'));
$this->drupalPostForm(NULL, array(), t('Save'));
// Should return a 406.
$this->drupalGetWithFormat('test/serialize/field', 'json');
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(406, 'A 406 response was returned when JSON was requested.');
// Should return a 200.
$this->drupalGetWithFormat('test/serialize/field', 'xml');
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
$this->assertResponse(200, 'A 200 response was returned when XML was requested.');
// Add 'json' as an accepted format, so we have multiple.
$this->drupalPostForm($style_options, array('style_options[formats][json]' => 'json'), t('Apply'));
$this->drupalPostForm(NULL, array(), t('Save'));
// Should return a 200.
// @todo This should be fixed when we have better content negotiation.
$this->drupalGet('test/serialize/field');
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when any format was requested.');
// Should return a 200. Emulates a sample Firefox header.
$this->drupalGet('test/serialize/field', array(), array('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'));
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when a browser accept header was requested.');
// Should return a 200.
$this->drupalGetWithFormat('test/serialize/field', 'json');
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
// Should return a 200.
$this->drupalGetWithFormat('test/serialize/field', 'xml');
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
$this->assertResponse(200, 'A 200 response was returned when XML was requested');
$headers = $this->drupalGetHeaders();
$this->assertTrue(strpos($headers['content-type'], 'text/xml') !== FALSE, 'The header Content-type is correct.');
// Should return a 406.
$this->drupalGetWithFormat('test/serialize/field', 'html');
// We want to show the first format by default, see
// \Drupal\rest\Plugin\views\style\Serializer::render.
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
// Now configure now format, so all of them should be allowed.
$this->drupalPostForm($style_options, array('style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'), t('Apply'));
// Should return a 200.
$this->drupalGetWithFormat('test/serialize/field', 'json');
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
// Should return a 200.
$this->drupalGetWithFormat('test/serialize/field', 'xml');
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
$this->assertResponse(200, 'A 200 response was returned when XML was requested');
// Should return a 200.
$this->drupalGetWithFormat('test/serialize/field', 'html');
// We want to show the first format by default, see
// \Drupal\rest\Plugin\views\style\Serializer::render.
$this->assertHeader('content-type', 'application/json');
$this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
}
/**
* Test the field ID alias functionality of the DataFieldRow plugin.
*/
public function testUIFieldAlias() {
$this->drupalLogin($this->adminUser);
// Test the UI settings for adding field ID aliases.
$this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
$row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
$this->assertLinkByHref($row_options);
// Test an empty string for an alias, this should not be used. This also
// tests that the form can be submitted with no aliases.
$this->drupalPostForm($row_options, array('row_options[field_options][name][alias]' => ''), t('Apply'));
$this->drupalPostForm(NULL, array(), t('Save'));
$view = Views::getView('test_serializer_display_field');
$view->setDisplay('rest_export_1');
$this->executeView($view);
$expected = array();
foreach ($view->result as $row) {
$expected_row = array();
foreach ($view->field as $id => $field) {
$expected_row[$id] = $field->render($row);
}
$expected[] = $expected_row;
}
$this->assertIdentical($this->drupalGetJSON('test/serialize/field'), $this->castSafeStrings($expected));
// Test a random aliases for fields, they should be replaced.
$alias_map = array(
'name' => $this->randomMachineName(),
// Use # to produce an invalid character for the validation.
'nothing' => '#' . $this->randomMachineName(),
'created' => 'created',
);
$edit = array('row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']);
$this->drupalPostForm($row_options, $edit, t('Apply'));
$this->assertText(t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
// Change the map alias value to a valid one.
$alias_map['nothing'] = $this->randomMachineName();
$edit = array('row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']);
$this->drupalPostForm($row_options, $edit, t('Apply'));
$this->drupalPostForm(NULL, array(), t('Save'));
$view = Views::getView('test_serializer_display_field');
$view->setDisplay('rest_export_1');
$this->executeView($view);
$expected = array();
foreach ($view->result as $row) {
$expected_row = array();
foreach ($view->field as $id => $field) {
$expected_row[$alias_map[$id]] = $field->render($row);
}
$expected[] = $expected_row;
}
$this->assertIdentical($this->drupalGetJSON('test/serialize/field'), $this->castSafeStrings($expected));
}
/**
* Tests the raw output options for row field rendering.
*/
public function testFieldRawOutput() {
$this->drupalLogin($this->adminUser);
// Test the UI settings for adding field ID aliases.
$this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
$row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
$this->assertLinkByHref($row_options);
// Test an empty string for an alias, this should not be used. This also
// tests that the form can be submitted with no aliases.
$values = array(
'row_options[field_options][created][raw_output]' => '1',
'row_options[field_options][name][raw_output]' => '1',
);
$this->drupalPostForm($row_options, $values, t('Apply'));
$this->drupalPostForm(NULL, array(), t('Save'));
$view = Views::getView('test_serializer_display_field');
$view->setDisplay('rest_export_1');
$this->executeView($view);
$storage = $this->container->get('entity_type.manager')->getStorage('entity_test');
// Update the name for each to include a script tag.
foreach ($storage->loadMultiple() as $entity_test) {
$name = $entity_test->name->value;
$entity_test->set('name', "<script>$name</script>");
$entity_test->save();
}
// Just test the raw 'created' value against each row.
foreach ($this->drupalGetJSON('test/serialize/field') as $index => $values) {
$this->assertIdentical($values['created'], $view->result[$index]->views_test_data_created, 'Expected raw created value found.');
$this->assertIdentical($values['name'], $view->result[$index]->views_test_data_name, 'Expected raw name value found.');
}
// Test result with an excluded field.
$view->setDisplay('rest_export_1');
$view->displayHandlers->get('rest_export_1')->overrideOption('fields', [
'name' => [
'id' => 'name',
'table' => 'views_test_data',
'field' => 'name',
'relationship' => 'none',
],
'created' => [
'id' => 'created',
'exclude' => TRUE,
'table' => 'views_test_data',
'field' => 'created',
'relationship' => 'none',
],
]);
$view->save();
$this->executeView($view);
foreach ($this->drupalGetJSON('test/serialize/field') as $index => $values) {
$this->assertTrue(!isset($values['created']), 'Excluded value not found.');
}
// Test that the excluded field is not shown in the row options.
$this->drupalGet('admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options');
$this->assertNoText('created');
}
/**
* Tests the live preview output for json output.
*/
public function testLivePreview() {
// We set up a request so it looks like an request in the live preview.
$request = new Request();
$request->query->add([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']);
/** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
$request_stack = \Drupal::service('request_stack');
$request_stack->push($request);
$view = Views::getView('test_serializer_display_entity');
$view->setDisplay('rest_export_1');
$this->executeView($view);
// Get the serializer service.
$serializer = $this->container->get('serializer');
$entities = array();
foreach ($view->result as $row) {
$entities[] = $row->_entity;
}
$expected = $serializer->serialize($entities, 'json');
$view->live_preview = TRUE;
$build = $view->preview();
$rendered_json = $build['#plain_text'];
$this->assertTrue(!isset($build['#markup']) && $rendered_json == $expected, 'Ensure the previewed json is escaped.');
$view->destroy();
$expected = $serializer->serialize($entities, 'xml');
// Change the request format to xml.
$view->setDisplay('rest_export_1');
$view->getDisplay()->setOption('style', array(
'type' => 'serializer',
'options' => array(
'uses_fields' => FALSE,
'formats' => array(
'xml' => 'xml',
),
),
));
$this->executeView($view);
$build = $view->preview();
$rendered_xml = $build['#plain_text'];
$this->assertEqual($rendered_xml, $expected, 'Ensure we preview xml when we change the request format.');
}
/**
* Tests the views interface for REST export displays.
*/
public function testSerializerViewsUI() {
$this->drupalLogin($this->adminUser);
// Click the "Update preview button".
$this->drupalPostForm('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1', $edit = array(), t('Update preview'));
$this->assertResponse(200);
// Check if we receive the expected result.
$result = $this->xpath('//div[@id="views-live-preview"]/pre');
$this->assertIdentical($this->drupalGet('test/serialize/field'), (string) $result[0], 'The expected JSON preview output was found.');
}
/**
* Tests the field row style using fieldapi fields.
*/
public function testFieldapiField() {
$this->drupalCreateContentType(array('type' => 'page'));
$node = $this->drupalCreateNode();
$result = $this->drupalGetJSON('test/serialize/node-field');
$this->assertEqual($result[0]['nid'], $node->id());
$this->assertEqual($result[0]['body'], $node->body->processed);
// Make sure that serialized fields are not exposed to XSS.
$node = $this->drupalCreateNode();
$node->body = [
'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
'format' => filter_default_format(),
];
$node->save();
$result = $this->drupalGetJSON('test/serialize/node-field');
$this->assertEqual($result[1]['nid'], $node->id());
$this->assertTrue(strpos($this->getRawContent(), "<script") === FALSE, "No script tag is present in the raw page contents.");
$this->drupalLogin($this->adminUser);
// Add an alias and make the output raw.
$row_options = 'admin/structure/views/nojs/display/test_serializer_node_display_field/rest_export_1/row_options';
// Test an empty string for an alias, this should not be used. This also
// tests that the form can be submitted with no aliases.
$this->drupalPostForm($row_options, ['row_options[field_options][title][raw_output]' => '1'], t('Apply'));
$this->drupalPostForm(NULL, [], t('Save'));
$view = Views::getView('test_serializer_node_display_field');
$view->setDisplay('rest_export_1');
$this->executeView($view);
// Test the raw 'created' value against each row.
foreach ($this->drupalGetJSON('test/serialize/node-field') as $index => $values) {
$this->assertIdentical($values['title'], $view->result[$index]->_entity->title->value, 'Expected raw title value found.');
}
// Test that multiple raw body fields are shown.
// Make the body field unlimited cardinatlity.
$storage_definition = $node->getFieldDefinition('body')->getFieldStorageDefinition();
$storage_definition->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$storage_definition->save();
$this->drupalPostForm($row_options, ['row_options[field_options][body][raw_output]' => '1'], t('Apply'));
$this->drupalPostForm(NULL, [], t('Save'));
$node = $this->drupalCreateNode();
$body = [
'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
'format' => filter_default_format(),
];
// Add two body items.
$node->body = [$body, $body];
$node->save();
$view = Views::getView('test_serializer_node_display_field');
$view->setDisplay('rest_export_1');
$this->executeView($view);
$result = $this->drupalGetJSON('test/serialize/node-field');
$this->assertEqual(count($result[2]['body']), $node->body->count(), 'Expected count of values');
$this->assertEqual($result[2]['body'], array_map(function($item) { return $item['value']; }, $node->body->getValue()), 'Expected raw body values found.');
}
/**
* Tests the "Grouped rows" functionality.
*/
public function testGroupRows() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$this->drupalCreateContentType(['type' => 'page']);
// Create a text field with cardinality set to unlimited.
$field_name = 'field_group_rows';
$field_storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'string',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
]);
$field_storage->save();
// Create an instance of the text field on the content type.
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'page',
]);
$field->save();
$grouped_field_values = ['a', 'b', 'c'];
$edit = [
'title' => $this->randomMachineName(),
$field_name => $grouped_field_values,
];
$this->drupalCreateNode($edit);
$view = Views::getView('test_serializer_node_display_field');
$view->setDisplay('rest_export_1');
// Override the view's fields to include the field_group_rows field, set the
// group_rows setting to true.
$fields = [
$field_name => [
'id' => $field_name,
'table' => 'node__' . $field_name,
'field' => $field_name,
'type' => 'string',
'group_rows' => TRUE,
],
];
$view->displayHandlers->get('default')->overrideOption('fields', $fields);
$build = $view->preview();
// Get the serializer service.
$serializer = $this->container->get('serializer');
// Check if the field_group_rows field is grouped.
$expected = [];
$expected[] = [$field_name => implode(', ', $grouped_field_values)];
$this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
// Set the group rows setting to false.
$view = Views::getView('test_serializer_node_display_field');
$view->setDisplay('rest_export_1');
$fields[$field_name]['group_rows'] = FALSE;
$view->displayHandlers->get('default')->overrideOption('fields', $fields);
$build = $view->preview();
// Check if the field_group_rows field is ungrouped and displayed per row.
$expected = [];
foreach ($grouped_field_values as $grouped_field_value) {
$expected[] = [$field_name => $grouped_field_value];
}
$this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
}
/**
* Tests the exposed filter works.
*
* There is an exposed filter on the title field which takes a title query
* parameter. This is set to filter nodes by those whose title starts with
* the value provided.
*/
public function testRestViewExposedFilter() {
$this->drupalCreateContentType(array('type' => 'page'));
$node0 = $this->drupalCreateNode(array('title' => 'Node 1'));
$node1 = $this->drupalCreateNode(array('title' => 'Node 11'));
$node2 = $this->drupalCreateNode(array('title' => 'Node 111'));
// Test that no filter brings back all three nodes.
$result = $this->drupalGetJSON('test/serialize/node-exposed-filter');
$expected = array(
0 => array(
'nid' => $node0->id(),
'body' => $node0->body->processed,
),
1 => array(
'nid' => $node1->id(),
'body' => $node1->body->processed,
),
2 => array(
'nid' => $node2->id(),
'body' => $node2->body->processed,
),
);
$this->assertEqual($result, $expected, 'Querying a view with no exposed filter returns all nodes.');
// Test that title starts with 'Node 11' query finds 2 of the 3 nodes.
$result = $this->drupalGetJSON('test/serialize/node-exposed-filter', ['query' => ['title' => 'Node 11']]);
$expected = array(
0 => array(
'nid' => $node1->id(),
'body' => $node1->body->processed,
),
1 => array(
'nid' => $node2->id(),
'body' => $node2->body->processed,
),
);
$cache_contexts = [
'languages:language_content',
'languages:language_interface',
'theme',
'request_format',
'user.node_grants:view',
'url',
];
$this->assertEqual($result, $expected, 'Querying a view with a starts with exposed filter on the title returns nodes whose title starts with value provided.');
$this->assertCacheContexts($cache_contexts);
}
/**
* Test multilingual entity rows.
*/
public function testMulEntityRows() {
// Create some languages.
ConfigurableLanguage::createFromLangcode('l1')->save();
ConfigurableLanguage::createFromLangcode('l2')->save();
// Create an entity with no translations.
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_mul');
$storage->create(['langcode' => 'l1', 'name' => 'mul-none'])->save();
// Create some entities with translations.
$entity = $storage->create(['langcode' => 'l1', 'name' => 'mul-l1-orig']);
$entity->save();
$entity->addTranslation('l2', ['name' => 'mul-l1-l2'])->save();
$entity = $storage->create(['langcode' => 'l2', 'name' => 'mul-l2-orig']);
$entity->save();
$entity->addTranslation('l1', ['name' => 'mul-l2-l1'])->save();
// Get the names of the output.
$json = $this->drupalGetWithFormat('test/serialize/translated_entity', 'json');
$decoded = $this->container->get('serializer')->decode($json, 'hal_json');
$names = [];
foreach ($decoded as $item) {
$names[] = $item['name'][0]['value'];
}
sort($names);
// Check that the names are correct.
$expected = ['mul-l1-l2', 'mul-l1-orig', 'mul-l2-l1', 'mul-l2-orig', 'mul-none'];
$this->assertIdentical($names, $expected, 'The translated content was found in the JSON.');
}
}