updated core

This commit is contained in:
Bachir Soussi Chiadmi
2018-01-24 13:36:32 +01:00
parent cd7dea45a9
commit f9374cf96d
1997 changed files with 5175 additions and 127715 deletions
@@ -1,88 +0,0 @@
<?php
namespace Drupal\rest\Access;
use Drupal\Core\Access\AccessCheckInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\SessionConfigurationInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* Access protection against CSRF attacks.
*/
class CSRFAccessCheck implements AccessCheckInterface {
/**
* The session configuration.
*
* @var \Drupal\Core\Session\SessionConfigurationInterface
*/
protected $sessionConfiguration;
/**
* Constructs a new rest CSRF access check.
*
* @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
* The session configuration.
*/
public function __construct(SessionConfigurationInterface $session_configuration) {
$this->sessionConfiguration = $session_configuration;
}
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
$requirements = $route->getRequirements();
if (array_key_exists('_access_rest_csrf', $requirements)) {
if (isset($requirements['_method'])) {
// There could be more than one method requirement separated with '|'.
$methods = explode('|', $requirements['_method']);
// CSRF protection only applies to write operations, so we can filter
// out any routes that require reading methods only.
$write_methods = array_diff($methods, array('GET', 'HEAD', 'OPTIONS', 'TRACE'));
if (empty($write_methods)) {
return FALSE;
}
}
// No method requirement given, so we run this access check to be on the
// safe side.
return TRUE;
}
}
/**
* Checks access.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Request $request, AccountInterface $account) {
$method = $request->getMethod();
// This check only applies if
// 1. this is a write operation
// 2. the user was successfully authenticated and
// 3. the request comes with a session cookie.
if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))
&& $account->isAuthenticated()
&& $this->sessionConfiguration->hasSession($request)
) {
$csrf_token = $request->headers->get('X-CSRF-Token');
if (!\Drupal::csrfToken()->validate($csrf_token, 'rest')) {
return AccessResult::forbidden()->setCacheMaxAge(0);
}
}
// Let other access checkers decide if the request is legit.
return AccessResult::allowed()->setCacheMaxAge(0);
}
}
@@ -43,7 +43,7 @@ class ResourcePluginManager extends DefaultPluginManager {
*
* @see https://www.drupal.org/node/2874934
*/
public function getInstance(array $options){
public function getInstance(array $options) {
if (isset($options['id'])) {
return $this->createInstance($options['id']);
}
-1
View File
@@ -74,7 +74,6 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
$method = strtolower($route_match->getRouteObject()->getMethods()[0]);
assert(count($route_match->getRouteObject()->getMethods()) === 1);
$resource_config_id = $route_match->getRouteObject()->getDefault('_rest_resource_config');
/** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
$resource_config = $this->resourceStorage->load($resource_config_id);
-104
View File
@@ -1,104 +0,0 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\Core\Url;
/**
* Tests authentication provider restrictions.
*
* @group rest
*/
class AuthTest extends RESTTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('basic_auth', 'hal', 'rest', 'entity_test', 'comment');
/**
* Tests reading from an authenticated resource.
*/
public function testRead() {
$entity_type = 'entity_test';
// Enable a test resource through GET method and basic HTTP authentication.
$this->enableService('entity:' . $entity_type, 'GET', NULL, array('basic_auth'));
// Create an entity programmatically.
$entity = $this->entityCreate($entity_type);
$entity->save();
// Try to read the resource as an anonymous user, which should not work.
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$this->assertResponse('401', 'HTTP response code is 401 when the request is not authenticated and the user is anonymous.');
$this->assertRaw(json_encode(['message' => 'A fatal error occurred: No authentication credentials provided.']));
// Ensure that cURL settings/headers aren't carried over to next request.
unset($this->curlHandle);
// Create a user account that has the required permissions to read
// resources via the REST API, but the request is authenticated
// with session cookies.
$permissions = $this->entityPermissions($entity_type, 'view');
$permissions[] = 'restful get entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
// Try to read the resource with session cookie authentication, which is
// not enabled and should not work.
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$this->assertResponse('403', 'HTTP response code is 403 when the request was authenticated by the wrong authentication provider.');
// Ensure that cURL settings/headers aren't carried over to next request.
unset($this->curlHandle);
// Now read it with the Basic authentication which is enabled and should
// work.
$this->basicAuthGet($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), $account->getUsername(), $account->pass_raw);
$this->assertResponse('200', 'HTTP response code is 200 for successfully authenticated requests.');
$this->curlClose();
}
/**
* Performs a HTTP request with Basic authentication.
*
* We do not use \Drupal\simpletest\WebTestBase::drupalGet because we need to
* set curl settings for basic authentication.
*
* @param \Drupal\Core\Url $url
* A Url object.
* @param string $username
* The user name to authenticate with.
* @param string $password
* The password.
* @param string $mime_type
* The MIME type for the Accept header.
*
* @return string
* Curl output.
*/
protected function basicAuthGet(Url $url, $username, $password, $mime_type = NULL) {
if (!isset($mime_type)) {
$mime_type = $this->defaultMimeType;
}
$out = $this->curlExec(
array(
CURLOPT_HTTPGET => TRUE,
CURLOPT_URL => $url->setAbsolute()->toString(),
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_USERPWD => $username . ':' . $password,
CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
)
);
$this->verbose('GET request to: ' . $url->toString() .
'<hr />' . $out);
return $out;
}
}
-465
View File
@@ -1,465 +0,0 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\node\Entity\Node;
use Drupal\user\Entity\User;
use Drupal\comment\Entity\Comment;
/**
* Tests the creation of resources.
*
* @group rest
*/
class CreateTest extends RESTTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('hal', 'rest', 'entity_test', 'comment');
/**
* The 'serializer' service.
*
* @var \Symfony\Component\Serializer\Serializer
*/
protected $serializer;
protected function setUp() {
parent::setUp();
$this->addDefaultCommentField('node', 'resttest');
// Get the 'serializer' service.
$this->serializer = $this->container->get('serializer');
}
/**
* Try to create a resource which is not REST API enabled.
*/
public function testCreateResourceRestApiNotEnabled() {
$entity_type = 'entity_test';
// Enables the REST service for a specific entity type.
$this->enableService('entity:' . $entity_type, 'POST');
// Get the necessary user permissions to create the current entity type.
$permissions = $this->entityPermissions($entity_type, 'create');
// POST method must be allowed for the current entity type.
$permissions[] = 'restful post entity:' . $entity_type;
// Create the user.
$account = $this->drupalCreateUser($permissions);
// Populate some entity properties before create the entity.
$entity_values = $this->entityValues($entity_type);
$entity = EntityTest::create($entity_values);
// Serialize the entity before the POST request.
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
// Disable all resource types.
$this->enableService(FALSE);
$this->drupalLogin($account);
// POST request to create the current entity. GET request for CSRF token
// is included into the httpRequest() method.
$this->httpRequest('entity/entity_test', 'POST', $serialized, $this->defaultMimeType);
// The resource is not enabled. So, we receive a 'not found' response.
$this->assertResponse(404);
$this->assertFalse(EntityTest::loadMultiple(), 'No entity has been created in the database.');
}
/**
* Ensure that an entity cannot be created without the restful permission.
*/
public function testCreateWithoutPermission() {
$entity_type = 'entity_test';
// Enables the REST service for 'entity_test' entity type.
$this->enableService('entity:' . $entity_type, 'POST');
$permissions = $this->entityPermissions($entity_type, 'create');
// Create a user without the 'restful post entity:entity_test permission.
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
// Populate some entity properties before create the entity.
$entity_values = $this->entityValues($entity_type);
$entity = EntityTest::create($entity_values);
// Serialize the entity before the POST request.
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
// Create the entity over the REST API.
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
$this->assertResponse(403);
$this->assertFalse(EntityTest::loadMultiple(), 'No entity has been created in the database.');
}
/**
* Tests valid and invalid create requests for 'entity_test' entity type.
*/
public function testCreateEntityTest() {
$entity_type = 'entity_test';
// Enables the REST service for 'entity_test' entity type.
$this->enableService('entity:' . $entity_type, 'POST');
// Create two accounts with the required permissions to create resources.
// The second one has administrative permissions.
$accounts = $this->createAccountPerEntity($entity_type);
// Verify create requests per user.
foreach ($accounts as $key => $account) {
$this->drupalLogin($account);
// Populate some entity properties before create the entity.
$entity_values = $this->entityValues($entity_type);
$entity = EntityTest::create($entity_values);
// Serialize the entity before the POST request.
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
// Create the entity over the REST API.
$this->assertCreateEntityOverRestApi($entity_type, $serialized);
// Get the entity ID from the location header and try to read it from the
// database.
$this->assertReadEntityIdFromHeaderAndDb($entity_type, $entity, $entity_values);
// Try to create an entity with an access protected field.
// @see entity_test_entity_field_access()
$normalized = $this->serializer->normalize($entity, $this->defaultFormat, ['account' => $account]);
$normalized['field_test_text'][0]['value'] = 'no access value';
$this->httpRequest('entity/' . $entity_type, 'POST', $this->serializer->serialize($normalized, $this->defaultFormat, ['account' => $account]), $this->defaultMimeType);
$this->assertResponse(403);
$this->assertFalse(EntityTest::loadMultiple(), 'No entity has been created in the database.');
// Try to create a field with a text format this user has no access to.
$entity->field_test_text->value = $entity_values['field_test_text'][0]['value'];
$entity->field_test_text->format = 'full_html';
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
// The value selected is not a valid choice because the format must be
// 'plain_txt'.
$this->assertResponse(422);
$this->assertFalse(EntityTest::loadMultiple(), 'No entity has been created in the database.');
// Restore the valid test value.
$entity->field_test_text->format = 'plain_text';
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
// Try to send invalid data that cannot be correctly deserialized.
$this->assertCreateEntityInvalidData($entity_type);
// Try to send no data at all, which does not make sense on POST requests.
$this->assertCreateEntityNoData($entity_type);
// Try to send invalid data to trigger the entity validation constraints.
// Send a UUID that is too long.
$this->assertCreateEntityInvalidSerialized($entity, $entity_type);
// Try to create an entity without proper permissions.
$this->assertCreateEntityWithoutProperPermissions($entity_type, $serialized, ['account' => $account]);
}
}
/**
* Tests several valid and invalid create requests for 'node' entity type.
*/
public function testCreateNode() {
$entity_type = 'node';
// Enables the REST service for 'node' entity type.
$this->enableService('entity:' . $entity_type, 'POST');
// Create two accounts that have the required permissions to create
// resources. The second one has administrative permissions.
$accounts = $this->createAccountPerEntity($entity_type);
// Verify create requests per user.
foreach ($accounts as $key => $account) {
$this->drupalLogin($account);
// Populate some entity properties before create the entity.
$entity_values = $this->entityValues($entity_type);
$entity = Node::create($entity_values);
// Verify that user cannot create content when trying to write to fields
// where it is not possible.
if (!$account->hasPermission('administer nodes')) {
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
$this->assertResponse(403);
// Remove fields where non-administrative users cannot write.
$entity = $this->removeNodeFieldsForNonAdminUsers($entity);
}
else {
// Changed and revision_timestamp fields can never be added.
$entity->set('changed', NULL);
$entity->set('revision_timestamp', NULL);
}
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
// Create the entity over the REST API.
$this->assertCreateEntityOverRestApi($entity_type, $serialized);
// Get the new entity ID from the location header and try to read it from
// the database.
$this->assertReadEntityIdFromHeaderAndDb($entity_type, $entity, $entity_values);
// Try to send invalid data that cannot be correctly deserialized.
$this->assertCreateEntityInvalidData($entity_type);
// Try to send no data at all, which does not make sense on POST requests.
$this->assertCreateEntityNoData($entity_type);
// Try to send invalid data to trigger the entity validation constraints. Send a UUID that is too long.
$this->assertCreateEntityInvalidSerialized($entity, $entity_type);
// Try to create an entity without proper permissions.
$this->assertCreateEntityWithoutProperPermissions($entity_type, $serialized);
}
}
/**
* Test comment creation.
*/
protected function testCreateComment() {
$node = Node::create([
'type' => 'resttest',
'title' => 'some node',
]);
$node->save();
$entity_type = 'comment';
// Enable the REST service for 'comment' entity type.
$this->enableService('entity:' . $entity_type, 'POST');
// Create two accounts that have the required permissions to create
// resources, The second one has administrative permissions.
$accounts = $this->createAccountPerEntity($entity_type);
$account = end($accounts);
$this->drupalLogin($account);
$entity_values = $this->entityValues($entity_type);
$entity_values['entity_id'] = $node->id();
$entity = Comment::create($entity_values);
// Changed field can never be added.
unset($entity->changed);
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
// Create the entity over the REST API.
$this->assertCreateEntityOverRestApi($entity_type, $serialized);
// Get the new entity ID from the location header and try to read it from
// the database.
$this->assertReadEntityIdFromHeaderAndDb($entity_type, $entity, $entity_values);
// Try to send invalid data that cannot be correctly deserialized.
$this->assertCreateEntityInvalidData($entity_type);
// Try to send no data at all, which does not make sense on POST requests.
$this->assertCreateEntityNoData($entity_type);
// Try to send invalid data to trigger the entity validation constraints.
// Send a UUID that is too long.
$this->assertCreateEntityInvalidSerialized($entity, $entity_type);
}
/**
* Tests several valid and invalid create requests for 'user' entity type.
*/
public function testCreateUser() {
$entity_type = 'user';
// Enables the REST service for 'user' entity type.
$this->enableService('entity:' . $entity_type, 'POST');
// Create two accounts that have the required permissions to create
// resources. The second one has administrative permissions.
$accounts = $this->createAccountPerEntity($entity_type);
foreach ($accounts as $key => $account) {
$this->drupalLogin($account);
$entity_values = $this->entityValues($entity_type);
$entity = User::create($entity_values);
// Verify that only administrative users can create users.
if (!$account->hasPermission('administer users')) {
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
$this->assertResponse(403);
continue;
}
// Changed field can never be added.
$entity->set('changed', NULL);
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
// Create the entity over the REST API.
$this->assertCreateEntityOverRestApi($entity_type, $serialized);
// Get the new entity ID from the location header and try to read it from
// the database.
$this->assertReadEntityIdFromHeaderAndDb($entity_type, $entity, $entity_values);
// Try to send invalid data that cannot be correctly deserialized.
$this->assertCreateEntityInvalidData($entity_type);
// Try to send no data at all, which does not make sense on POST requests.
$this->assertCreateEntityNoData($entity_type);
// Try to send invalid data to trigger the entity validation constraints.
// Send a UUID that is too long.
$this->assertCreateEntityInvalidSerialized($entity, $entity_type);
}
}
/**
* Creates user accounts that have the required permissions to create
* resources via the REST API. The second one has administrative permissions.
*
* @param string $entity_type
* Entity type needed to apply user permissions.
* @return array
* An array that contains user accounts.
*/
public function createAccountPerEntity($entity_type) {
$accounts = array();
// Get the necessary user permissions for the current $entity_type creation.
$permissions = $this->entityPermissions($entity_type, 'create');
// POST method must be allowed for the current entity type.
$permissions[] = 'restful post entity:' . $entity_type;
// Create user without administrative permissions.
$accounts[] = $this->drupalCreateUser($permissions);
// Add administrative permissions for nodes and users.
$permissions[] = 'administer nodes';
$permissions[] = 'administer users';
$permissions[] = 'administer comments';
// Create an administrative user.
$accounts[] = $this->drupalCreateUser($permissions);
return $accounts;
}
/**
* Creates the entity over the REST API.
*
* @param string $entity_type
* The type of the entity that should be created.
* @param string $serialized
* The body for the POST request.
*/
public function assertCreateEntityOverRestApi($entity_type, $serialized = NULL) {
// Note: this will fail with PHP 5.6 when always_populate_raw_post_data is
// set to something other than -1. See https://www.drupal.org/node/2456025.
$response = $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
$this->assertResponse(201);
// Make sure that the response includes an entity in the body and check the
// UUID as an example.
$request = Json::decode($serialized);
$response = Json::decode($response);
$this->assertEqual($request['uuid'][0]['value'], $response['uuid'][0]['value'], 'Got new entity created as response after successful POST over Rest API');
}
/**
* Gets the new entity ID from the location header and tries to read it from
* the database.
*
* @param string $entity_type
* Entity type we need to load the entity from DB.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity we want to check that was inserted correctly.
* @param array $entity_values
* The values of $entity.
*/
public function assertReadEntityIdFromHeaderAndDb($entity_type, EntityInterface $entity, array $entity_values = array()) {
// Get the location from the HTTP response header.
$location_url = $this->drupalGetHeader('location');
$url_parts = explode('/', $location_url);
$id = end($url_parts);
// Get the entity using the ID found.
$loaded_entity = \Drupal::entityManager()->getStorage($entity_type)->load($id);
$this->assertNotIdentical(FALSE, $loaded_entity, 'The new ' . $entity_type . ' was found in the database.');
$this->assertEqual($entity->uuid(), $loaded_entity->uuid(), 'UUID of created entity is correct.');
// Verify that the field values sent and received from DB are the same.
foreach ($entity_values as $property => $value) {
$actual_value = $loaded_entity->get($property)->value;
$send_value = $entity->get($property)->value;
$this->assertEqual($send_value, $actual_value, 'Created property ' . $property . ' expected: ' . $send_value . ', actual: ' . $actual_value);
}
// Delete the entity loaded from DB.
$loaded_entity->delete();
}
/**
* Try to send invalid data that cannot be correctly deserialized.
*
* @param string $entity_type
* The type of the entity that should be created.
*/
public function assertCreateEntityInvalidData($entity_type) {
$this->httpRequest('entity/' . $entity_type, 'POST', 'kaboom!', $this->defaultMimeType);
$this->assertResponse(400);
}
/**
* Try to send no data at all, which does not make sense on POST requests.
*
* @param string $entity_type
* The type of the entity that should be created.
*/
public function assertCreateEntityNoData($entity_type) {
$this->httpRequest('entity/' . $entity_type, 'POST', NULL, $this->defaultMimeType);
$this->assertResponse(400);
}
/**
* Send an invalid UUID to trigger the entity validation.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity we want to check that was inserted correctly.
* @param string $entity_type
* The type of the entity that should be created.
* @param array $context
* Options normalizers/encoders have access to.
*/
public function assertCreateEntityInvalidSerialized(EntityInterface $entity, $entity_type, array $context = array()) {
// Add a UUID that is too long.
$entity->set('uuid', $this->randomMachineName(129));
$invalid_serialized = $this->serializer->serialize($entity, $this->defaultFormat, $context);
$response = $this->httpRequest('entity/' . $entity_type, 'POST', $invalid_serialized, $this->defaultMimeType);
// Unprocessable Entity as response.
$this->assertResponse(422);
// Verify that the text of the response is correct.
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
}
/**
* Try to create an entity without proper permissions.
*
* @param string $entity_type
* The type of the entity that should be created.
* @param string $serialized
* The body for the POST request.
*/
public function assertCreateEntityWithoutProperPermissions($entity_type, $serialized = NULL) {
$this->drupalLogout();
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
// Forbidden Error as response.
$this->assertResponse(403);
$this->assertFalse(\Drupal::entityManager()->getStorage($entity_type)->loadMultiple(), 'No entity has been created in the database.');
}
}
-118
View File
@@ -1,118 +0,0 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\Core\Url;
/**
* Tests the CSRF protection.
*
* @group rest
*/
class CsrfTest extends RESTTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('hal', 'rest', 'entity_test', 'basic_auth');
/**
* A testing user account.
*
* @var \Drupal\user\Entity\User
*/
protected $account;
/**
* The serialized entity.
*
* @var string
*/
protected $serialized;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->enableService('entity:' . $this->testEntityType, 'POST', 'hal_json', array('basic_auth', 'cookie'));
// Create a user account that has the required permissions to create
// resources via the REST API.
$permissions = $this->entityPermissions($this->testEntityType, 'create');
$permissions[] = 'restful post entity:' . $this->testEntityType;
$this->account = $this->drupalCreateUser($permissions);
// Serialize an entity to a string to use in the content body of the POST
// request.
$serializer = $this->container->get('serializer');
$entity_values = $this->entityValues($this->testEntityType);
$entity = $this->container->get('entity_type.manager')
->getStorage($this->testEntityType)
->create($entity_values);
$this->serialized = $serializer->serialize($entity, $this->defaultFormat);
}
/**
* Tests that CSRF check is not triggered for Basic Auth requests.
*/
public function testBasicAuth() {
$curl_options = $this->getCurlOptions();
$curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
$curl_options[CURLOPT_USERPWD] = $this->account->getUsername() . ':' . $this->account->pass_raw;
$this->curlExec($curl_options);
$this->assertResponse(201);
// Ensure that the entity was created.
$loaded_entity = $this->loadEntityFromLocationHeader($this->drupalGetHeader('location'));
$this->assertTrue($loaded_entity, 'An entity was created in the database');
}
/**
* Tests that CSRF check is triggered for Cookie Auth requests.
*/
public function testCookieAuth() {
$this->drupalLogin($this->account);
$curl_options = $this->getCurlOptions();
// Try to create an entity without the CSRF token.
// Note: this will fail with PHP 5.6 when always_populate_raw_post_data is
// set to something other than -1. See https://www.drupal.org/node/2456025.
$this->curlExec($curl_options);
$this->assertResponse(403);
// Ensure that the entity was not created.
$this->assertFalse(entity_load_multiple($this->testEntityType, NULL, TRUE), 'No entity has been created in the database.');
// Create an entity with the CSRF token.
$token = $this->drupalGet('rest/session/token');
$curl_options[CURLOPT_HTTPHEADER][] = "X-CSRF-Token: $token";
$this->curlExec($curl_options);
$this->assertResponse(201);
// Ensure that the entity was created.
$loaded_entity = $this->loadEntityFromLocationHeader($this->drupalGetHeader('location'));
$this->assertTrue($loaded_entity, 'An entity was created in the database');
}
/**
* Gets the cURL options to create an entity with POST.
*
* @return array
* The array of cURL options.
*/
protected function getCurlOptions() {
return array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $this->serialized,
CURLOPT_URL => Url::fromRoute('rest.entity.' . $this->testEntityType . '.POST')->setAbsolute()->toString(),
CURLOPT_NOBODY => FALSE,
CURLOPT_HTTPHEADER => array(
"Content-Type: {$this->defaultMimeType}",
),
);
}
}
@@ -1,76 +0,0 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\Core\Url;
/**
* Tests the deletion of resources.
*
* @group rest
*/
class DeleteTest extends RESTTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('hal', 'rest', 'entity_test');
/**
* Tests several valid and invalid delete requests on all entity types.
*/
public function testDelete() {
// Define the entity types we want to test.
// @todo expand this test to at least users once their access
// controllers are implemented.
$entity_types = array('entity_test', 'node');
foreach ($entity_types as $entity_type) {
$this->enableService('entity:' . $entity_type, 'DELETE');
// Create a user account that has the required permissions to delete
// resources via the REST API.
$permissions = $this->entityPermissions($entity_type, 'delete');
$permissions[] = 'restful delete entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
// Create an entity programmatically.
$entity = $this->entityCreate($entity_type);
$entity->save();
// Delete it over the REST API.
$response = $this->httpRequest($entity->urlInfo(), 'DELETE');
// Clear the static cache with entity_load(), otherwise we won't see the
// update.
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertFalse($entity, $entity_type . ' entity is not in the DB anymore.');
$this->assertResponse('204', 'HTTP response code is correct.');
$this->assertEqual($response, '', 'Response body is empty.');
// Try to delete an entity that does not exist.
$response = $this->httpRequest(Url::fromRoute('entity.' . $entity_type . '.canonical', [$entity_type => 9999]), 'DELETE');
$this->assertResponse(404);
$this->assertText('The requested page could not be found.');
// Try to delete an entity without proper permissions.
$this->drupalLogout();
// Re-save entity to the database.
$entity = $this->entityCreate($entity_type);
$entity->save();
$this->httpRequest($entity->urlInfo(), 'DELETE');
$this->assertResponse(403);
$this->assertNotIdentical(FALSE, entity_load($entity_type, $entity->id(), TRUE), 'The ' . $entity_type . ' entity is still in the database.');
}
// Try to delete a resource which is not REST API enabled.
$this->enableService(FALSE);
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
$this->httpRequest($account->urlInfo(), 'DELETE');
$user_storage = $this->container->get('entity.manager')->getStorage('user');
$user_storage->resetCache(array($account->id()));
$user = $user_storage->load($account->id());
$this->assertEqual($account->id(), $user->id(), 'User still exists in the database.');
$this->assertResponse(405);
}
}
-198
View File
@@ -1,198 +0,0 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
/**
* Tests special cases for node entities.
*
* @group rest
*/
class NodeTest extends RESTTestBase {
/**
* Modules to install.
*
* Ensure that the node resource works with comment module enabled.
*
* @var array
*/
public static $modules = array('hal', 'rest', 'comment');
/**
* Enables node specific REST API configuration and authentication.
*
* @param string $method
* The HTTP method to be tested.
* @param string $operation
* The operation, one of 'view', 'create', 'update' or 'delete'.
*/
protected function enableNodeConfiguration($method, $operation) {
$this->enableService('entity:node', $method);
$permissions = $this->entityPermissions('node', $operation);
$permissions[] = 'restful ' . strtolower($method) . ' entity:node';
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
}
/**
* Serializes and attempts to create a node via a REST "post" http request.
*
* @param array $data
*/
protected function postNode($data) {
// Enable node creation via POST.
$this->enableNodeConfiguration('POST', 'create');
$this->enableService('entity:node', 'POST', 'json');
// Create a JSON version of a simple node with the title.
$serialized = $this->container->get('serializer')->serialize($data, 'json');
// Post to the REST service to create the node.
$this->httpRequest('/entity/node', 'POST', $serialized, 'application/json');
}
/**
* Tests the title on a newly created node.
*
* @param array $data
* @return \Drupal\node\Entity\Node
*/
protected function assertNodeTitleMatch($data) {
/** @var \Drupal\node\Entity\Node $node */
// Load the newly created node.
$node = Node::load(1);
// Test that the title is the same as what we posted.
$this->assertEqual($node->title->value, $data['title'][0]['value']);
return $node;
}
/**
* Performs various tests on nodes and their REST API.
*/
public function testNodes() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$this->enableNodeConfiguration('GET', 'view');
$node = $this->entityCreate('node');
$node->save();
$this->httpRequest($node->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$this->assertResponse(200);
$this->assertHeader('Content-type', $this->defaultMimeType);
// Also check that JSON works and the routing system selects the correct
// REST route.
$this->enableService('entity:node', 'GET', 'json');
$this->httpRequest($node->urlInfo()->setRouteParameter('_format', 'json'), 'GET');
$this->assertResponse(200);
$this->assertHeader('Content-type', 'application/json');
// Check that a simple PATCH update to the node title works as expected.
$this->enableNodeConfiguration('PATCH', 'update');
// Create a PATCH request body that only updates the title field.
$new_title = $this->randomString();
$data = array(
'_links' => array(
'type' => array(
'href' => Url::fromUri('base:rest/type/node/resttest', array('absolute' => TRUE))->toString(),
),
),
'title' => array(
array(
'value' => $new_title,
),
),
);
$serialized = $this->container->get('serializer')->serialize($data, $this->defaultFormat);
$this->httpRequest($node->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(204);
// Reload the node from the DB and check if the title was correctly updated.
$node_storage->resetCache(array($node->id()));
$updated_node = $node_storage->load($node->id());
$this->assertEqual($updated_node->getTitle(), $new_title);
// Make sure that the UUID of the node has not changed.
$this->assertEqual($node->get('uuid')->getValue(), $updated_node->get('uuid')->getValue(), 'UUID was not changed.');
}
/**
* Test creating a node using json serialization.
*/
public function testCreate() {
// Data to be used for serialization.
$data = [
'type' => [['target_id' => 'resttest']],
'title' => [['value' => $this->randomString() ]],
];
$this->postNode($data);
// Make sure the response is "CREATED".
$this->assertResponse(201);
// Make sure the node was created and the title matches.
$node = $this->assertNodeTitleMatch($data);
// Make sure the request returned a redirect header to view the node.
$this->assertHeader('Location', $node->url('canonical', ['absolute' => TRUE]));
}
/**
* Test bundle normalization when posting bundle as a simple string.
*/
public function testBundleNormalization() {
// Data to be used for serialization.
$data = [
'type' => 'resttest',
'title' => [['value' => $this->randomString() ]],
];
$this->postNode($data);
// Make sure the response is "CREATED".
$this->assertResponse(201);
// Make sure the node was created and the title matches.
$this->assertNodeTitleMatch($data);
}
/**
* Test bundle normalization when posting using a simple string.
*/
public function testInvalidBundle() {
// Data to be used for serialization.
$data = [
'type' => 'bad_bundle_name',
'title' => [['value' => $this->randomString() ]],
];
$this->postNode($data);
// Make sure the response is "Bad Request".
$this->assertResponse(400);
$this->assertResponseBody('{"error":"\"bad_bundle_name\" is not a valid bundle type for denormalization."}');
}
/**
* Test when the bundle is missing.
*/
public function testMissingBundle() {
// Data to be used for serialization.
$data = [
'title' => [['value' => $this->randomString() ]],
];
// testing
$this->postNode($data);
// Make sure the response is "Bad Request".
$this->assertResponse(400);
$this->assertResponseBody('{"error":"A string must be provided as a bundle value."}');
}
}
@@ -1,90 +0,0 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
/**
* Tests page caching for REST GET requests.
*
* @group rest
*/
class PageCacheTest extends RESTTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('hal');
/**
* Tests that configuration changes also clear the page cache.
*/
public function testConfigChangePageCache() {
$this->enableService('entity:entity_test', 'GET');
// Allow anonymous users to issue GET requests.
$permissions = $this->entityPermissions('entity_test', 'view');
$permissions[] = 'restful get entity:entity_test';
user_role_grant_permissions('anonymous', $permissions);
// Create an entity programmatically.
$entity = $this->entityCreate('entity_test');
$entity->set('field_test_text', 'custom cache tag value');
$entity->save();
// Read it over the REST API.
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'MISS');
$this->assertCacheTag('config:rest.settings');
$this->assertCacheTag('entity_test:1');
$this->assertCacheTag('entity_test_access:field_test_text');
// Read it again, should be page-cached now.
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'HIT');
$this->assertCacheTag('config:rest.settings');
$this->assertCacheTag('entity_test:1');
$this->assertCacheTag('entity_test_access:field_test_text');
// Trigger a config save which should clear the page cache, so we should get
// a cache miss now for the same request.
$this->config('rest.settings')->save();
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('x-drupal-cache', 'MISS');
$this->assertCacheTag('config:rest.settings');
$this->assertCacheTag('entity_test:1');
$this->assertCacheTag('entity_test_access:field_test_text');
}
/**
* Tests HEAD support when a REST resource supports GET.
*/
public function testHeadSupport() {
user_role_grant_permissions('anonymous', ['view test entity', 'restful get entity:entity_test']);
// Create an entity programatically.
$this->entityCreate('entity_test')->save();
$url = Url::fromUri('internal:/entity_test/1?_format=' . $this->defaultFormat);
$this->enableService('entity:entity_test', 'GET');
$this->httpRequest($url, 'HEAD', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('X-Drupal-Cache', 'MISS');
$this->assertResponseBody('');
$response = $this->httpRequest($url, 'GET', NULL, $this->defaultMimeType);
$this->assertResponse(200, 'HTTP response code is correct.');
$this->assertHeader('X-Drupal-Cache', 'HIT');
$this->assertCacheTag('config:rest.settings');
$this->assertCacheTag('entity_test:1');
$data = Json::decode($response);
$this->assertEqual($data['type'][0]['value'], 'entity_test');
}
}
-137
View File
@@ -1,137 +0,0 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
/**
* Tests the retrieval of resources.
*
* @group rest
*/
class ReadTest extends RESTTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('hal', 'rest', 'entity_test');
/**
* Tests several valid and invalid read requests on all entity types.
*/
public function testRead() {
// @todo Expand this at least to users.
// Define the entity types we want to test.
$entity_types = array('entity_test', 'node');
foreach ($entity_types as $entity_type) {
$this->enableService('entity:' . $entity_type, 'GET');
// Create a user account that has the required permissions to read
// resources via the REST API.
$permissions = $this->entityPermissions($entity_type, 'view');
$permissions[] = 'restful get entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
// Create an entity programmatically.
$entity = $this->entityCreate($entity_type);
$entity->save();
// Verify that it exists: use a HEAD request.
$response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'HEAD');
$this->assertResponseBody('');
$head_headers = $this->drupalGetHeaders();
// Read it over the REST API.
$response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$get_headers = $this->drupalGetHeaders();
$this->assertResponse('200', 'HTTP response code is correct.');
// Verify that the GET and HEAD responses are the same, that the only
// difference is that there's no body.
unset($get_headers['date']);
unset($head_headers['date']);
unset($get_headers['content-length']);
unset($head_headers['content-length']);
unset($get_headers['x-drupal-dynamic-cache']);
unset($head_headers['x-drupal-dynamic-cache']);
$this->assertIdentical($get_headers, $head_headers);
$this->assertResponse('200', 'HTTP response code is correct.');
$this->assertHeader('content-type', $this->defaultMimeType);
$data = Json::decode($response);
// Only assert one example property here, other properties should be
// checked in serialization tests.
$this->assertEqual($data['uuid'][0]['value'], $entity->uuid(), 'Entity UUID is correct');
// Try to read the entity with an unsupported mime format.
$response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', 'wrongformat'), 'GET');
$this->assertResponse(406);
$this->assertHeader('Content-type', 'application/json');
// Try to read an entity that does not exist.
$response = $this->httpRequest(Url::fromUri('base://' . $entity_type . '/9999', ['query' => ['_format' => $this->defaultFormat]]), 'GET');
$this->assertResponse(404);
$path = $entity_type == 'node' ? '/node/{node}' : '/entity_test/{entity_test}';
$expected_message = Json::encode(['message' => 'The "' . $entity_type . '" parameter was not converted for the path "' . $path . '" (route name: "rest.entity.' . $entity_type . '.GET.hal_json")']);
$this->assertIdentical($expected_message, $response, 'Response message is correct.');
// Make sure that field level access works and that the according field is
// not available in the response. Only applies to entity_test.
// @see entity_test_entity_field_access()
if ($entity_type == 'entity_test') {
$entity->field_test_text->value = 'no access value';
$entity->save();
$response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$this->assertResponse(200);
$this->assertHeader('content-type', $this->defaultMimeType);
$data = Json::decode($response);
$this->assertFalse(isset($data['field_test_text']), 'Field access protected field is not visible in the response.');
}
// Try to read an entity without proper permissions.
$this->drupalLogout();
$response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
$this->assertResponse(403);
$this->assertIdentical('{"message":""}', $response);
}
// Try to read a resource which is not REST API enabled.
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
$response = $this->httpRequest($account->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->assertEqual($response, Json::encode([
'message' => 'Not acceptable format: hal_json',
]));
}
/**
* Tests the resource structure.
*/
public function testResourceStructure() {
// Enable a service with a format restriction but no authentication.
$this->enableService('entity:node', 'GET', 'json');
// Create a user account that has the required permissions to read
// resources via the REST API.
$permissions = $this->entityPermissions('node', 'view');
$permissions[] = 'restful get entity:node';
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
// Create an entity programmatically.
$entity = $this->entityCreate('node');
$entity->save();
// Read it over the REST API.
$response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', 'json'), 'GET');
$this->assertResponse('200', 'HTTP response code is correct.');
}
}
@@ -1,130 +0,0 @@
<?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 = ['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.');
}
}
}
}
@@ -1,56 +0,0 @@
<?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);
}
}
@@ -1,71 +0,0 @@
<?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'));
}
}
@@ -1,65 +0,0 @@
<?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());
}
}
@@ -1,36 +0,0 @@
<?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.');
}
}
-363
View File
@@ -1,363 +0,0 @@
<?php
namespace Drupal\rest\Tests;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\entity_test\Entity\EntityTest;
/**
* Tests the update of resources.
*
* @group rest
*/
class UpdateTest extends RESTTestBase {
use CommentTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['hal', 'rest', 'entity_test', 'comment'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->addDefaultCommentField('entity_test', 'entity_test');
}
/**
* Tests several valid and invalid partial update requests on test entities.
*/
public function testPatchUpdate() {
$serializer = $this->container->get('serializer');
// @todo Test all other entity types here as well.
$entity_type = 'entity_test';
$this->enableService('entity:' . $entity_type, 'PATCH');
// Create a user account that has the required permissions to create
// resources via the REST API.
$permissions = $this->entityPermissions($entity_type, 'update');
$permissions[] = 'restful patch entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
$context = ['account' => $account];
// Create an entity and save it to the database.
$entity = $this->entityCreate($entity_type);
$entity->save();
// Create a second stub entity for overwriting a field.
$patch_values['field_test_text'] = array(0 => array(
'value' => $this->randomString(),
'format' => 'plain_text',
));
$patch_entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create($patch_values);
// We don't want to overwrite the UUID.
$patch_entity->set('uuid', NULL);
$serialized = $serializer->serialize($patch_entity, $this->defaultFormat, $context);
// Update the entity over the REST API.
$this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(204);
// Re-load updated entity from the database.
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertEqual($entity->field_test_text->value, $patch_entity->field_test_text->value, 'Field was successfully updated.');
// Make sure that the field does not get deleted if it is not present in the
// PATCH request.
$normalized = $serializer->normalize($patch_entity, $this->defaultFormat, $context);
unset($normalized['field_test_text']);
$serialized = $serializer->encode($normalized, $this->defaultFormat);
$this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(204);
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertNotNull($entity->field_test_text->value . 'Test field has not been deleted.');
// Try to empty a field.
$normalized['field_test_text'] = array();
$serialized = $serializer->encode($normalized, $this->defaultFormat);
// Update the entity over the REST API.
$this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(204);
// Re-load updated entity from the database.
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertNull($entity->field_test_text->value, 'Test field has been cleared.');
// Enable access protection for the text field.
// @see entity_test_entity_field_access()
$entity->field_test_text->value = 'no edit access value';
$entity->field_test_text->format = 'plain_text';
$entity->save();
// Try to empty a field that is access protected.
$this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(403);
// Re-load the entity from the database.
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertEqual($entity->field_test_text->value, 'no edit access value', 'Text field was not deleted.');
// Try to update an access protected field.
$normalized = $serializer->normalize($patch_entity, $this->defaultFormat, $context);
$normalized['field_test_text'][0]['value'] = 'no access value';
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(403);
// Re-load the entity from the database.
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertEqual($entity->field_test_text->value, 'no edit access value', 'Text field was not updated.');
// Try to update the field with a text format this user has no access to.
// First change the original field value so we're allowed to edit it again.
$entity->field_test_text->value = 'test';
$entity->save();
$patch_entity->set('field_test_text', array(
'value' => 'test',
'format' => 'full_html',
));
$serialized = $serializer->serialize($patch_entity, $this->defaultFormat, $context);
$this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(422);
// Re-load the entity from the database.
$entity = entity_load($entity_type, $entity->id(), TRUE);
$this->assertEqual($entity->field_test_text->format, 'plain_text', 'Text format was not updated.');
// Restore the valid test value.
$entity->field_test_text->value = $this->randomString();
$entity->save();
// Try to send no data at all, which does not make sense on PATCH requests.
$this->httpRequest($entity->urlInfo(), 'PATCH', NULL, $this->defaultMimeType);
$this->assertResponse(400);
// Try to update a non-existing entity with ID 9999.
$this->httpRequest($entity_type . '/9999', 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(404);
$loaded_entity = entity_load($entity_type, 9999, TRUE);
$this->assertFalse($loaded_entity, 'Entity 9999 was not created.');
// Try to send invalid data to trigger the entity validation constraints.
// Send a UUID that is too long.
$entity->set('uuid', $this->randomMachineName(129));
$invalid_serialized = $serializer->serialize($entity, $this->defaultFormat, $context);
$response = $this->httpRequest($entity->urlInfo(), 'PATCH', $invalid_serialized, $this->defaultMimeType);
$this->assertResponse(422);
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
// Try to update an entity without proper permissions.
$this->drupalLogout();
$this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(403);
// Try to update a resource which is not REST API enabled.
$this->enableService(FALSE);
$this->drupalLogin($account);
$this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(405);
}
/**
* Tests several valid and invalid update requests for the 'user' entity type.
*/
public function testUpdateUser() {
$serializer = $this->container->get('serializer');
$entity_type = 'user';
// Enables the REST service for 'user' entity type.
$this->enableService('entity:' . $entity_type, 'PATCH');
$permissions = $this->entityPermissions($entity_type, 'update');
$permissions[] = 'restful patch entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$account->set('mail', 'old-email@example.com');
$this->drupalLogin($account);
// Create an entity and save it to the database.
$account->save();
$account->set('changed', NULL);
// Try and set a new email without providing the password.
$account->set('mail', 'new-email@example.com');
$context = ['account' => $account];
$normalized = $serializer->normalize($account, $this->defaultFormat, $context);
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(422);
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
// Try and send the new email with a password.
$normalized['pass'][0]['existing'] = 'wrong';
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(422);
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
// Try again with the password.
$normalized['pass'][0]['existing'] = $account->pass_raw;
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(204);
// Try to change the password without providing the current password.
$new_password = $this->randomString();
$normalized = $serializer->normalize($account, $this->defaultFormat, $context);
$normalized['pass'][0]['value'] = $new_password;
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(422);
$error = Json::decode($response);
$this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n");
// Try again with the password.
$normalized['pass'][0]['existing'] = $account->pass_raw;
$serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
$this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
$this->assertResponse(204);
// Verify that we can log in with the new password.
$account->pass_raw = $new_password;
$this->drupalLogin($account);
}
/**
* Test patching a comment using both HAL+JSON and JSON.
*/
public function testUpdateComment() {
$entity_type = 'comment';
// Enables the REST service for 'comment' entity type.
$this->enableService('entity:' . $entity_type, 'PATCH', ['hal_json', 'json']);
$permissions = $this->entityPermissions($entity_type, 'update');
$permissions[] = 'restful patch entity:' . $entity_type;
$account = $this->drupalCreateUser($permissions);
$account->set('mail', 'old-email@example.com');
$this->drupalLogin($account);
// Create & save an entity to comment on, plus a comment.
$entity_test = EntityTest::create();
$entity_test->save();
$entity_values = $this->entityValues($entity_type);
$entity_values['entity_id'] = $entity_test->id();
$entity_values['uid'] = $account->id();
$comment = Comment::create($entity_values);
$comment->save();
$this->pass('Test case 1: PATCH comment using HAL+JSON.');
$comment->setSubject('Initial subject')->save();
$read_only_fields = [
'name',
'created',
'changed',
'status',
'thread',
'entity_type',
'field_name',
'entity_id',
'uid',
];
$this->assertNotEqual('Updated subject', $comment->getSubject());
$comment->setSubject('Updated subject');
$this->patchEntity($comment, $read_only_fields, $account, 'hal_json', 'application/hal+json');
$comment = Comment::load($comment->id());
$this->assertEqual('Updated subject', $comment->getSubject());
$this->pass('Test case 1: PATCH comment using JSON.');
$comment->setSubject('Initial subject')->save();
$read_only_fields = [
'pid', // Extra compared to HAL+JSON.
'entity_id',
'uid',
'name',
'homepage', // Extra compared to HAL+JSON.
'created',
'changed',
'status',
'thread',
'entity_type',
'field_name',
];
$this->assertNotEqual('Updated subject', $comment->getSubject());
$comment->setSubject('Updated subject');
$this->patchEntity($comment, $read_only_fields, $account, 'json', 'application/json');
$comment = Comment::load($comment->id());
$this->assertEqual('Updated subject', $comment->getSubject());
}
/**
* Patches an existing entity using the passed in (modified) entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The updated entity to send.
* @param string[] $read_only_fields
* Names of the fields that are read-only, in validation order.
* @param \Drupal\Core\Session\AccountInterface $account
* The account to use for serialization.
* @param string $format
* A serialization format.
* @param string $mime_type
* The MIME type corresponding to the specified serialization format.
*/
protected function patchEntity(EntityInterface $entity, array $read_only_fields, AccountInterface $account, $format, $mime_type) {
$serializer = $this->container->get('serializer');
$url = $entity->toUrl();
$context = ['account' => $account];
// Certain fields are always read-only, others this user simply is not
// allowed to modify. For all of them, ensure they are not serialized, else
// we'll get a 403 plus an error message.
for ($i = 0; $i < count($read_only_fields); $i++) {
$field = $read_only_fields[$i];
$normalized = $serializer->normalize($entity, $format, $context);
if ($format !== 'hal_json') {
// The default normalizer always keeps fields, even if they are unset
// here because they should be omitted during a PATCH request. Therefore
// manually strip them
// @see \Drupal\Core\Entity\ContentEntityBase::__unset()
// @see \Drupal\serialization\Normalizer\EntityNormalizer::normalize()
// @see \Drupal\hal\Normalizer\ContentEntityNormalizer::normalize()
$read_only_fields_so_far = array_slice($read_only_fields, 0, $i);
$normalized = array_diff_key($normalized, array_flip($read_only_fields_so_far));
}
$serialized = $serializer->serialize($normalized, $format, $context);
$this->httpRequest($url, 'PATCH', $serialized, $mime_type);
$this->assertResponse(403);
$this->assertResponseBody('{"error":"Access denied on updating field \'' . $field . '\'."}');
if ($format === 'hal_json') {
// We've just tried with this read-only field, now unset it.
$entity->set($field, NULL);
}
}
// Finally, with all read-only fields unset, the request should succeed.
$normalized = $serializer->normalize($entity, $format, $context);
if ($format !== 'hal_json') {
$normalized = array_diff_key($normalized, array_combine($read_only_fields, $read_only_fields));
}
$serialized = $serializer->serialize($normalized, $format, $context);
$this->httpRequest($url, 'PATCH', $serialized, $mime_type);
$this->assertResponse(204);
$this->assertResponseBody('');
}
}
@@ -1,87 +0,0 @@
<?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));
}
}
@@ -1,838 +0,0 @@
<?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 = ['views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field', 'language', 'basic_auth'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['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), ['rest_test_views']);
$this->adminUser = $this->drupalCreateUser(['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(['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 = [];
foreach ($view->result as $row) {
$expected_row = [];
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 = [];
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', [
'type' => 'serializer',
'options' => [
'uses_fields' => FALSE,
'formats' => [
'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', [
'type' => 'serializer',
'options' => [
'uses_fields' => FALSE,
'formats' => [
'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.
*/
public 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, ['style_options[formats][xml]' => 'xml'], t('Apply'));
$this->drupalPostForm(NULL, [], 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, ['style_options[formats][json]' => 'json'], t('Apply'));
$this->drupalPostForm(NULL, [], 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', [], ['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, ['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, ['row_options[field_options][name][alias]' => ''], t('Apply'));
$this->drupalPostForm(NULL, [], t('Save'));
$view = Views::getView('test_serializer_display_field');
$view->setDisplay('rest_export_1');
$this->executeView($view);
$expected = [];
foreach ($view->result as $row) {
$expected_row = [];
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 = [
'name' => $this->randomMachineName(),
// Use # to produce an invalid character for the validation.
'nothing' => '#' . $this->randomMachineName(),
'created' => 'created',
];
$edit = ['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 = ['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, [], t('Save'));
$view = Views::getView('test_serializer_display_field');
$view->setDisplay('rest_export_1');
$this->executeView($view);
$expected = [];
foreach ($view->result as $row) {
$expected_row = [];
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 = [
'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, [], 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 = [];
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', [
'type' => 'serializer',
'options' => [
'uses_fields' => FALSE,
'formats' => [
'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 = [], 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(['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(['type' => 'page']);
$node0 = $this->drupalCreateNode(['title' => 'Node 1']);
$node1 = $this->drupalCreateNode(['title' => 'Node 11']);
$node2 = $this->drupalCreateNode(['title' => 'Node 111']);
// Test that no filter brings back all three nodes.
$result = $this->drupalGetJSON('test/serialize/node-exposed-filter');
$expected = [
0 => [
'nid' => $node0->id(),
'body' => $node0->body->processed,
],
1 => [
'nid' => $node1->id(),
'body' => $node1->body->processed,
],
2 => [
'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 = [
0 => [
'nid' => $node1->id(),
'body' => $node1->body->processed,
],
1 => [
'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.');
}
}