updated core
This commit is contained in:
@@ -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']);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user