Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
110
core/modules/rest/src/Tests/AuthTest.php
Normal file
110
core/modules/rest/src/Tests/AuthTest.php
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\AuthTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\rest\Tests\RESTTestBase;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* An 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;
|
||||
}
|
||||
|
||||
}
|
||||
413
core/modules/rest/src/Tests/CreateTest.php
Normal file
413
core/modules/rest/src/Tests/CreateTest.php
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\CreateTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests the creation of resources.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class CreateTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', 'entity_test');
|
||||
|
||||
/**
|
||||
* The 'serializer' service.
|
||||
*
|
||||
* @var \Symfony\Component\Serializer\Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// 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.
|
||||
unset($entity->changed);
|
||||
unset($entity->revision_timestamp);
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
// 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.
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
}
|
||||
|
||||
}
|
||||
119
core/modules/rest/src/Tests/CsrfTest.php
Normal file
119
core/modules/rest/src/Tests/CsrfTest.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\CsrfTest.
|
||||
*/
|
||||
|
||||
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 = entity_create($this->testEntityType, $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}",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
80
core/modules/rest/src/Tests/DeleteTest.php
Normal file
80
core/modules/rest/src/Tests/DeleteTest.php
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\DeleteTest.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
93
core/modules/rest/src/Tests/NodeTest.php
Normal file
93
core/modules/rest/src/Tests/NodeTest.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\NodeTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\rest\Tests\RESTTestBase;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
}
|
||||
}
|
||||
61
core/modules/rest/src/Tests/PageCacheTest.php
Normal file
61
core/modules/rest/src/Tests/PageCacheTest.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\PageCacheTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
/**
|
||||
* Tests page caching for REST GET requests.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class PageCacheTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', 'entity_test');
|
||||
|
||||
/**
|
||||
* 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->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');
|
||||
|
||||
// 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');
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
}
|
||||
364
core/modules/rest/src/Tests/RESTTestBase.php
Normal file
364
core/modules/rest/src/Tests/RESTTestBase.php
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\RESTTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Test helper class that provides a REST client method to send HTTP requests.
|
||||
*/
|
||||
abstract class RESTTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* The default serialization format to use for testing REST operations.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultFormat;
|
||||
|
||||
/**
|
||||
* The default MIME type to use for testing REST operations.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultMimeType;
|
||||
|
||||
/**
|
||||
* The entity type to use for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testEntityType = 'entity_test';
|
||||
|
||||
/**
|
||||
* The default authentication provider to use for testing REST operations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultAuth;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('rest', 'entity_test', 'node');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->defaultFormat = 'hal_json';
|
||||
$this->defaultMimeType = 'application/hal+json';
|
||||
$this->defaultAuth = array('cookie');
|
||||
// Create a test content type for node testing.
|
||||
$this->drupalCreateContentType(array('name' => 'resttest', 'type' => 'resttest'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to issue a HTTP request with simpletest's cURL.
|
||||
*
|
||||
* @param string|\Drupal\Core\Url $url
|
||||
* A Url object or system path.
|
||||
* @param string $method
|
||||
* HTTP method, one of GET, POST, PUT or DELETE.
|
||||
* @param string $body
|
||||
* The body for POST and PUT.
|
||||
* @param string $mime_type
|
||||
* The MIME type of the transmitted content.
|
||||
*
|
||||
* @return string
|
||||
* The content returned from the request.
|
||||
*/
|
||||
protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
|
||||
if (!isset($mime_type)) {
|
||||
$mime_type = $this->defaultMimeType;
|
||||
}
|
||||
if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) {
|
||||
// GET the CSRF token first for writing requests.
|
||||
$token = $this->drupalGet('rest/session/token');
|
||||
}
|
||||
|
||||
$url = $this->buildUrl($url);
|
||||
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
// Set query if there are additional GET parameters.
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => TRUE,
|
||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_POST => TRUE,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
'X-CSRF-Token: ' . $token,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_CUSTOMREQUEST => 'PUT',
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
'X-CSRF-Token: ' . $token,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_CUSTOMREQUEST => 'PATCH',
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Content-Type: ' . $mime_type,
|
||||
'X-CSRF-Token: ' . $token,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$curl_options = array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => array('X-CSRF-Token: ' . $token),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$response = $this->curlExec($curl_options);
|
||||
|
||||
// Ensure that any changes to variables in the other thread are picked up.
|
||||
$this->refreshVariables();
|
||||
|
||||
$headers = $this->drupalGetHeaders();
|
||||
|
||||
$this->verbose($method . ' request to: ' . $url .
|
||||
'<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
|
||||
'<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
|
||||
'<hr />Response body: ' . $response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates entity objects based on their types.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity that should be created.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The new entity object.
|
||||
*/
|
||||
protected function entityCreate($entity_type) {
|
||||
return entity_create($entity_type, $this->entityValues($entity_type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an array of suitable property values for an entity type.
|
||||
*
|
||||
* Required properties differ from entity type to entity type, so we keep a
|
||||
* minimum mapping here.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity that should be created.
|
||||
*
|
||||
* @return array
|
||||
* An array of values keyed by property name.
|
||||
*/
|
||||
protected function entityValues($entity_type) {
|
||||
switch ($entity_type) {
|
||||
case 'entity_test':
|
||||
return array(
|
||||
'name' => $this->randomMachineName(),
|
||||
'user_id' => 1,
|
||||
'field_test_text' => array(0 => array(
|
||||
'value' => $this->randomString(),
|
||||
'format' => 'plain_text',
|
||||
)),
|
||||
);
|
||||
case 'node':
|
||||
return array('title' => $this->randomString(), 'type' => 'resttest');
|
||||
case 'node_type':
|
||||
return array(
|
||||
'type' => 'article',
|
||||
'name' => $this->randomMachineName(),
|
||||
);
|
||||
case 'user':
|
||||
return array('name' => $this->randomMachineName());
|
||||
default:
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the REST service interface for a specific entity type.
|
||||
*
|
||||
* @param string|FALSE $resource_type
|
||||
* The resource type that should get REST API enabled or FALSE to disable all
|
||||
* resource types.
|
||||
* @param string $method
|
||||
* The HTTP method to enable, e.g. GET, POST etc.
|
||||
* @param string $format
|
||||
* (Optional) The serialization format, e.g. hal_json.
|
||||
* @param array $auth
|
||||
* (Optional) The list of valid authentication methods.
|
||||
*/
|
||||
protected function enableService($resource_type, $method = 'GET', $format = NULL, $auth = NULL) {
|
||||
// Enable REST API for this entity type.
|
||||
$config = $this->config('rest.settings');
|
||||
$settings = array();
|
||||
|
||||
if ($resource_type) {
|
||||
if ($format == NULL) {
|
||||
$format = $this->defaultFormat;
|
||||
}
|
||||
$settings[$resource_type][$method]['supported_formats'][] = $format;
|
||||
|
||||
if ($auth == NULL) {
|
||||
$auth = $this->defaultAuth;
|
||||
}
|
||||
$settings[$resource_type][$method]['supported_auth'] = $auth;
|
||||
}
|
||||
$config->set('resources', $settings);
|
||||
$config->save();
|
||||
$this->rebuildCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds routing caches.
|
||||
*/
|
||||
protected function rebuildCache() {
|
||||
// Rebuild routing cache, so that the REST API paths are available.
|
||||
$this->container->get('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This method is overridden to deal with a cURL quirk: the usage of
|
||||
* CURLOPT_CUSTOMREQUEST cannot be unset on the cURL handle, so we need to
|
||||
* override it every time it is omitted.
|
||||
*/
|
||||
protected function curlExec($curl_options, $redirect = FALSE) {
|
||||
if (!isset($curl_options[CURLOPT_CUSTOMREQUEST])) {
|
||||
if (!empty($curl_options[CURLOPT_HTTPGET])) {
|
||||
$curl_options[CURLOPT_CUSTOMREQUEST] = 'GET';
|
||||
}
|
||||
if (!empty($curl_options[CURLOPT_POST])) {
|
||||
$curl_options[CURLOPT_CUSTOMREQUEST] = 'POST';
|
||||
}
|
||||
}
|
||||
return parent::curlExec($curl_options, $redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the necessary user permissions for entity operations.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type.
|
||||
* @param string $operation
|
||||
* The operation, one of 'view', 'create', 'update' or 'delete'.
|
||||
*
|
||||
* @return array
|
||||
* The set of user permission strings.
|
||||
*/
|
||||
protected function entityPermissions($entity_type, $operation) {
|
||||
switch ($entity_type) {
|
||||
case 'entity_test':
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return array('view test entity');
|
||||
case 'create':
|
||||
case 'update':
|
||||
case 'delete':
|
||||
return array('administer entity_test content');
|
||||
}
|
||||
case 'node':
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return array('access content');
|
||||
case 'create':
|
||||
return array('create resttest content');
|
||||
case 'update':
|
||||
return array('edit any resttest content');
|
||||
case 'delete':
|
||||
return array('delete any resttest content');
|
||||
}
|
||||
|
||||
case 'user':
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return ['access user profiles'];
|
||||
|
||||
default:
|
||||
return ['administer users'];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an entity based on the location URL returned in the location header.
|
||||
*
|
||||
* @param string $location_url
|
||||
* The URL returned in the Location header.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Entity|FALSE.
|
||||
* The entity or FALSE if there is no matching entity.
|
||||
*/
|
||||
protected function loadEntityFromLocationHeader($location_url) {
|
||||
$url_parts = explode('/', $location_url);
|
||||
$id = end($url_parts);
|
||||
return entity_load($this->testEntityType, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove node fields that can only be written by an admin user.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node to remove fields where non-administrative users cannot write.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* The node with removed fields.
|
||||
*/
|
||||
protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
|
||||
$node->set('status', NULL);
|
||||
$node->set('created', NULL);
|
||||
$node->set('changed', NULL);
|
||||
$node->set('promote', NULL);
|
||||
$node->set('sticky', NULL);
|
||||
$node->set('revision_timestamp', NULL);
|
||||
$node->set('uid', NULL);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
}
|
||||
124
core/modules/rest/src/Tests/ReadTest.php
Normal file
124
core/modules/rest/src/Tests/ReadTest.php
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\ReadTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\rest\Tests\RESTTestBase;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
// Read it over the REST API.
|
||||
$response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
|
||||
$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',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
}
|
||||
|
||||
}
|
||||
103
core/modules/rest/src/Tests/ResourceTest.php
Normal file
103
core/modules/rest/src/Tests/ResourceTest.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\ResourceTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
/**
|
||||
* Tests the structure of a REST resource.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class ResourceTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', 'entity_test');
|
||||
|
||||
/**
|
||||
* The entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->config = $this->config('rest.settings');
|
||||
|
||||
// Create an entity programmatically.
|
||||
$this->entity = $this->entityCreate('entity_test');
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a resource without formats cannot be enabled.
|
||||
*/
|
||||
public function testFormats() {
|
||||
$settings = array(
|
||||
'entity:entity_test' => array(
|
||||
'GET' => array(
|
||||
'supported_auth' => array(
|
||||
'basic_auth',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Attempt to enable the resource.
|
||||
$this->config->set('resources', $settings);
|
||||
$this->config->save();
|
||||
$this->rebuildCache();
|
||||
|
||||
// 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() {
|
||||
$settings = array(
|
||||
'entity:entity_test' => array(
|
||||
'GET' => array(
|
||||
'supported_formats' => array(
|
||||
'hal_json',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Attempt to enable the resource.
|
||||
$this->config->set('resources', $settings);
|
||||
$this->config->save();
|
||||
$this->rebuildCache();
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
}
|
||||
90
core/modules/rest/src/Tests/RestLinkManagerTest.php
Normal file
90
core/modules/rest/src/Tests/RestLinkManagerTest.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\RestLinkManagerTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that REST type and relation link managers work as expected
|
||||
* @group rest
|
||||
*/
|
||||
class RestLinkManagerTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['rest', 'rest_test', 'system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', ['router']);
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that type hooks work as expected.
|
||||
*/
|
||||
public function testRestLinkManagers() {
|
||||
\Drupal::moduleHandler()->invoke('rest', 'install');
|
||||
/* @var \Drupal\rest\LinkManager\TypeLinkManagerInterface $type_manager */
|
||||
$type_manager = \Drupal::service('rest.link_manager.type');
|
||||
$base = Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString();
|
||||
$link = $type_manager->getTypeUri('node', 'page');
|
||||
$this->assertEqual($link, $base . 'rest/type/node/page');
|
||||
// Now with optional context.
|
||||
$link = $type_manager->getTypeUri('node', 'page', ['rest_test' => TRUE]);
|
||||
$this->assertEqual($link, 'rest_test_type');
|
||||
|
||||
/* @var \Drupal\rest\LinkManager\RelationLinkManagerInterface $relation_manager */
|
||||
$relation_manager = \Drupal::service('rest.link_manager.relation');
|
||||
$link = $relation_manager->getRelationUri('node', 'page', 'field_ref');
|
||||
$this->assertEqual($link, $base . 'rest/relation/node/page/field_ref');
|
||||
// Now with optional context.
|
||||
$link = $relation_manager->getRelationUri('node', 'page', 'foobar', ['rest_test' => TRUE]);
|
||||
$this->assertEqual($link, 'rest_test_relation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that type hooks work as expected even without install hook.
|
||||
*/
|
||||
public function testRestLinkManagersNoInstallHook() {
|
||||
/* @var \Drupal\rest\LinkManager\TypeLinkManagerInterface $type_manager */
|
||||
$type_manager = \Drupal::service('rest.link_manager.type');
|
||||
$base = Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString();
|
||||
$link = $type_manager->getTypeUri('node', 'page');
|
||||
$this->assertEqual($link, $base . 'rest/type/node/page');
|
||||
// Now with optional context.
|
||||
$link = $type_manager->getTypeUri('node', 'page', ['rest_test' => TRUE]);
|
||||
$this->assertEqual($link, 'rest_test_type');
|
||||
|
||||
/* @var \Drupal\rest\LinkManager\RelationLinkManagerInterface $relation_manager */
|
||||
$relation_manager = \Drupal::service('rest.link_manager.relation');
|
||||
$link = $relation_manager->getRelationUri('node', 'page', 'field_ref');
|
||||
$this->assertEqual($link, $base . 'rest/relation/node/page/field_ref');
|
||||
// Now with optional context.
|
||||
$link = $relation_manager->getRelationUri('node', 'page', 'foobar', ['rest_test' => TRUE]);
|
||||
$this->assertEqual($link, 'rest_test_relation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\rest\LinkManager\LinkManager::setLinkDomain().
|
||||
*/
|
||||
public function testRestLinkManagersSetLinkDomain() {
|
||||
/* @var \Drupal\rest\LinkManager\LinkManager $link_manager */
|
||||
$link_manager = \Drupal::service('rest.link_manager');
|
||||
$link_manager->setLinkDomain('http://example.com/');
|
||||
$link = $link_manager->getTypeUri('node', 'page');
|
||||
$this->assertEqual($link, 'http://example.com/rest/type/node/page');
|
||||
$link = $link_manager->getRelationUri('node', 'page', 'field_ref');
|
||||
$this->assertEqual($link, 'http://example.com/rest/relation/node/page/field_ref');
|
||||
}
|
||||
|
||||
}
|
||||
230
core/modules/rest/src/Tests/UpdateTest.php
Normal file
230
core/modules/rest/src/Tests/UpdateTest.php
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\UpdateTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\rest\Tests\RESTTestBase;
|
||||
|
||||
/**
|
||||
* Tests the update of resources.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class UpdateTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', '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 = entity_create($entity_type, $patch_values);
|
||||
// We don't want to overwrite the UUID.
|
||||
unset($patch_entity->uuid);
|
||||
$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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
547
core/modules/rest/src/Tests/Views/StyleSerializerTest.php
Normal file
547
core/modules/rest/src/Tests/Views/StyleSerializerTest.php
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\rest\Tests\Views\StyleSerializerTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\rest\Tests\Views;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\views\Entity\View;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views\Tests\Plugin\PluginTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests the serializer style plugin.
|
||||
*
|
||||
* @group rest
|
||||
* @see \Drupal\rest\Plugin\views\display\RestExport
|
||||
* @see \Drupal\rest\Plugin\views\style\Serializer
|
||||
* @see \Drupal\rest\Plugin\views\row\DataEntityRow
|
||||
* @see \Drupal\rest\Plugin\views\row\DataFieldRow
|
||||
*/
|
||||
class StyleSerializerTest extends PluginTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $dumpHeaders = TRUE;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field');
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = array('test_serializer_display_field', 'test_serializer_display_entity', 'test_serializer_node_display_field');
|
||||
|
||||
/**
|
||||
* A user with administrative privileges to look at test entity and configure views.
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
ViewTestData::createTestViews(get_class($this), array('rest_test_views'));
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(array('administer views', 'administer entity_test content', 'access user profiles', 'view test entity'));
|
||||
|
||||
// Save some entity_test entities.
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
entity_create('entity_test', array('name' => 'test_' . $i, 'user_id' => $this->adminUser->id()))->save();
|
||||
}
|
||||
|
||||
$this->enableViewsTestModule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the behavior of the Serializer callback paths and row plugins.
|
||||
*/
|
||||
public function testSerializerResponses() {
|
||||
// Test the serialize callback.
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->initDisplay();
|
||||
$this->executeView($view);
|
||||
|
||||
$actual_json = $this->drupalGetWithFormat('test/serialize/field', 'json');
|
||||
$this->assertResponse(200);
|
||||
$this->assertCacheTags($view->getCacheTags());
|
||||
$this->assertCacheContexts(['languages:language_interface', 'theme', 'request_format']);
|
||||
// @todo Due to https://www.drupal.org/node/2352009 we can't yet test the
|
||||
// propagation of cache max-age.
|
||||
|
||||
// Test the http Content-type.
|
||||
$headers = $this->drupalGetHeaders();
|
||||
$this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
|
||||
|
||||
$expected = array();
|
||||
foreach ($view->result as $row) {
|
||||
$expected_row = array();
|
||||
foreach ($view->field as $id => $field) {
|
||||
$expected_row[$id] = $field->render($row);
|
||||
}
|
||||
$expected[] = $expected_row;
|
||||
}
|
||||
|
||||
$this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.');
|
||||
|
||||
|
||||
// Test that the rendered output and the preview output are the same.
|
||||
$view->destroy();
|
||||
$view->setDisplay('rest_export_1');
|
||||
// Mock the request content type by setting it on the display handler.
|
||||
$view->display_handler->setContentType('json');
|
||||
$output = $view->preview();
|
||||
$this->assertIdentical($actual_json, drupal_render_root($output), 'The expected JSON preview output was found.');
|
||||
|
||||
// Test a 403 callback.
|
||||
$this->drupalGet('test/serialize/denied');
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Test the entity rows.
|
||||
$view = Views::getView('test_serializer_display_entity');
|
||||
$view->initDisplay();
|
||||
$this->executeView($view);
|
||||
|
||||
// Get the serializer service.
|
||||
$serializer = $this->container->get('serializer');
|
||||
|
||||
$entities = array();
|
||||
foreach ($view->result as $row) {
|
||||
$entities[] = $row->_entity;
|
||||
}
|
||||
|
||||
$expected = $serializer->serialize($entities, 'json');
|
||||
|
||||
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
|
||||
$this->assertResponse(200);
|
||||
$this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
|
||||
$expected_cache_tags = $view->getCacheTags();
|
||||
$expected_cache_tags[] = 'entity_test_list';
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity */
|
||||
foreach ($entities as $entity) {
|
||||
$expected_cache_tags = Cache::mergeTags($expected_cache_tags, $entity->getCacheTags());
|
||||
}
|
||||
$this->assertCacheTags($expected_cache_tags);
|
||||
$this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
|
||||
|
||||
$expected = $serializer->serialize($entities, 'hal_json');
|
||||
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'hal_json');
|
||||
$this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
|
||||
$this->assertCacheTags($expected_cache_tags);
|
||||
|
||||
// Change the default format to xml.
|
||||
$view->setDisplay('rest_export_1');
|
||||
$view->getDisplay()->setOption('style', array(
|
||||
'type' => 'serializer',
|
||||
'options' => array(
|
||||
'uses_fields' => FALSE,
|
||||
'formats' => array(
|
||||
'xml' => 'xml',
|
||||
),
|
||||
),
|
||||
));
|
||||
$view->save();
|
||||
$expected = $serializer->serialize($entities, 'xml');
|
||||
$actual_xml = $this->drupalGet('test/serialize/entity');
|
||||
$this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
|
||||
$this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
|
||||
|
||||
// Allow multiple formats.
|
||||
$view->setDisplay('rest_export_1');
|
||||
$view->getDisplay()->setOption('style', array(
|
||||
'type' => 'serializer',
|
||||
'options' => array(
|
||||
'uses_fields' => FALSE,
|
||||
'formats' => array(
|
||||
'xml' => 'xml',
|
||||
'json' => 'json',
|
||||
),
|
||||
),
|
||||
));
|
||||
$view->save();
|
||||
$expected = $serializer->serialize($entities, 'json');
|
||||
$actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
|
||||
$this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
|
||||
$expected = $serializer->serialize($entities, 'xml');
|
||||
$actual_xml = $this->drupalGetWithFormat('test/serialize/entity', 'xml');
|
||||
$this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a request on the request stack with a specified format.
|
||||
*
|
||||
* @param string $format
|
||||
* The new request format.
|
||||
*/
|
||||
protected function addRequestWithFormat($format) {
|
||||
$request = \Drupal::request();
|
||||
$request = clone $request;
|
||||
$request->setRequestFormat($format);
|
||||
|
||||
\Drupal::requestStack()->push($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests REST export with views render caching enabled.
|
||||
*/
|
||||
public function testRestRenderCaching() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
/** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
|
||||
$render_cache = \Drupal::service('render_cache');
|
||||
|
||||
// Enable render caching for the views.
|
||||
/** @var \Drupal\views\ViewEntityInterface $storage */
|
||||
$storage = View::load('test_serializer_display_entity');
|
||||
$options = &$storage->getDisplay('default');
|
||||
$options['display_options']['cache'] = [
|
||||
'type' => 'tag',
|
||||
];
|
||||
$storage->save();
|
||||
|
||||
$original = DisplayPluginBase::buildBasicRenderable('test_serializer_display_entity', 'rest_export_1');
|
||||
|
||||
// Ensure that there is no corresponding render cache item yet.
|
||||
$original['#cache'] += ['contexts' => []];
|
||||
$original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
|
||||
|
||||
$cache_tags = [
|
||||
'config:views.view.test_serializer_display_entity',
|
||||
'entity_test:1',
|
||||
'entity_test:10',
|
||||
'entity_test:2',
|
||||
'entity_test:3',
|
||||
'entity_test:4',
|
||||
'entity_test:5',
|
||||
'entity_test:6',
|
||||
'entity_test:7',
|
||||
'entity_test:8',
|
||||
'entity_test:9',
|
||||
'entity_test_list'
|
||||
];
|
||||
$cache_contexts = [
|
||||
'entity_test_view_grants',
|
||||
'languages:language_interface',
|
||||
'theme',
|
||||
'request_format',
|
||||
];
|
||||
|
||||
$this->assertFalse($render_cache->get($original));
|
||||
|
||||
// Request the page, once in XML and once in JSON to ensure that the caching
|
||||
// varies by it.
|
||||
$result1 = $this->drupalGetJSON('test/serialize/entity');
|
||||
$this->addRequestWithFormat('json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
$this->assertCacheTags($cache_tags);
|
||||
$this->assertTrue($render_cache->get($original));
|
||||
|
||||
$result_xml = $this->drupalGetWithFormat('test/serialize/entity', 'xml');
|
||||
$this->addRequestWithFormat('xml');
|
||||
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
$this->assertCacheTags($cache_tags);
|
||||
$this->assertTrue($render_cache->get($original));
|
||||
|
||||
// Ensure that the XML output is different from the JSON one.
|
||||
$this->assertNotEqual($result1, $result_xml);
|
||||
|
||||
// Ensure that the cached page works.
|
||||
$result2 = $this->drupalGetJSON('test/serialize/entity');
|
||||
$this->addRequestWithFormat('json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertEqual($result2, $result1);
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
$this->assertCacheTags($cache_tags);
|
||||
$this->assertTrue($render_cache->get($original));
|
||||
|
||||
// Create a new entity and ensure that the cache tags are taken over.
|
||||
EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save();
|
||||
$result3 = $this->drupalGetJSON('test/serialize/entity');
|
||||
$this->addRequestWithFormat('json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertNotEqual($result3, $result2);
|
||||
|
||||
// Add the new entity cache tag and remove the first one, because we just
|
||||
// show 10 items in total.
|
||||
$cache_tags[] = 'entity_test:11';
|
||||
unset($cache_tags[array_search('entity_test:1', $cache_tags)]);
|
||||
|
||||
$this->assertCacheContexts($cache_contexts);
|
||||
$this->assertCacheTags($cache_tags);
|
||||
$this->assertTrue($render_cache->get($original));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the response format configuration.
|
||||
*/
|
||||
public function testResponseFormatConfiguration() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options';
|
||||
|
||||
// Select only 'xml' as an accepted format.
|
||||
$this->drupalPostForm($style_options, array('style_options[formats][xml]' => 'xml'), t('Apply'));
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
// Should return a 406.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(406, 'A 406 response was returned when JSON was requested.');
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'xml');
|
||||
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
|
||||
$this->assertResponse(200, 'A 200 response was returned when XML was requested.');
|
||||
|
||||
// Add 'json' as an accepted format, so we have multiple.
|
||||
$this->drupalPostForm($style_options, array('style_options[formats][json]' => 'json'), t('Apply'));
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
// Should return a 200.
|
||||
// @todo This should be fixed when we have better content negotiation.
|
||||
$this->drupalGet('test/serialize/field');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when any format was requested.');
|
||||
|
||||
// Should return a 200. Emulates a sample Firefox header.
|
||||
$this->drupalGet('test/serialize/field', array(), array('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'));
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when a browser accept header was requested.');
|
||||
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
|
||||
$headers = $this->drupalGetHeaders();
|
||||
$this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'xml');
|
||||
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
|
||||
$this->assertResponse(200, 'A 200 response was returned when XML was requested');
|
||||
$headers = $this->drupalGetHeaders();
|
||||
$this->assertTrue(strpos($headers['content-type'], 'text/xml') !== FALSE, 'The header Content-type is correct.');
|
||||
// Should return a 406.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'html');
|
||||
// We want to show the first format by default, see
|
||||
// \Drupal\rest\Plugin\views\style\Serializer::render.
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
|
||||
|
||||
// Now configure now format, so all of them should be allowed.
|
||||
$this->drupalPostForm($style_options, array('style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'), t('Apply'));
|
||||
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'json');
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'xml');
|
||||
$this->assertHeader('content-type', 'text/xml; charset=UTF-8');
|
||||
$this->assertResponse(200, 'A 200 response was returned when XML was requested');
|
||||
// Should return a 200.
|
||||
$this->drupalGetWithFormat('test/serialize/field', 'html');
|
||||
// We want to show the first format by default, see
|
||||
// \Drupal\rest\Plugin\views\style\Serializer::render.
|
||||
$this->assertHeader('content-type', 'application/json');
|
||||
$this->assertResponse(200, 'A 200 response was returned when HTML was requested.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the field ID alias functionality of the DataFieldRow plugin.
|
||||
*/
|
||||
public function testUIFieldAlias() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Test the UI settings for adding field ID aliases.
|
||||
$this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
|
||||
$row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
|
||||
$this->assertLinkByHref($row_options);
|
||||
|
||||
// Test an empty string for an alias, this should not be used. This also
|
||||
// tests that the form can be submitted with no aliases.
|
||||
$this->drupalPostForm($row_options, array('row_options[field_options][name][alias]' => ''), t('Apply'));
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
$expected = array();
|
||||
foreach ($view->result as $row) {
|
||||
$expected_row = array();
|
||||
foreach ($view->field as $id => $field) {
|
||||
$expected_row[$id] = $field->render($row);
|
||||
}
|
||||
$expected[] = $expected_row;
|
||||
}
|
||||
|
||||
$this->assertIdentical($this->drupalGetJSON('test/serialize/field'), $expected);
|
||||
|
||||
// Test a random aliases for fields, they should be replaced.
|
||||
$alias_map = array(
|
||||
'name' => $this->randomMachineName(),
|
||||
// Use # to produce an invalid character for the validation.
|
||||
'nothing' => '#' . $this->randomMachineName(),
|
||||
'created' => 'created',
|
||||
);
|
||||
|
||||
$edit = array('row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']);
|
||||
$this->drupalPostForm($row_options, $edit, t('Apply'));
|
||||
$this->assertText(t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
|
||||
|
||||
// Change the map alias value to a valid one.
|
||||
$alias_map['nothing'] = $this->randomMachineName();
|
||||
|
||||
$edit = array('row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']);
|
||||
$this->drupalPostForm($row_options, $edit, t('Apply'));
|
||||
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
$expected = array();
|
||||
foreach ($view->result as $row) {
|
||||
$expected_row = array();
|
||||
foreach ($view->field as $id => $field) {
|
||||
$expected_row[$alias_map[$id]] = $field->render($row);
|
||||
}
|
||||
$expected[] = $expected_row;
|
||||
}
|
||||
|
||||
$this->assertIdentical($this->drupalGetJSON('test/serialize/field'), $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.
|
||||
$this->drupalPostForm($row_options, array('row_options[field_options][created][raw_output]' => '1'), t('Apply'));
|
||||
$this->drupalPostForm(NULL, array(), t('Save'));
|
||||
|
||||
$view = Views::getView('test_serializer_display_field');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
// 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.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->setFormat('drupal_ajax', 'application/vnd.drupal-ajax');
|
||||
$request->headers->set('Accept', 'application/vnd.drupal-ajax');
|
||||
/** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
|
||||
$request_stack = \Drupal::service('request_stack');
|
||||
$request_stack->push($request);
|
||||
|
||||
$view = Views::getView('test_serializer_display_entity');
|
||||
$view->setDisplay('rest_export_1');
|
||||
$this->executeView($view);
|
||||
|
||||
// Get the serializer service.
|
||||
$serializer = $this->container->get('serializer');
|
||||
|
||||
$entities = array();
|
||||
foreach ($view->result as $row) {
|
||||
$entities[] = $row->_entity;
|
||||
}
|
||||
|
||||
$expected = SafeMarkup::checkPlain($serializer->serialize($entities, 'json'));
|
||||
|
||||
$view->live_preview = TRUE;
|
||||
|
||||
$build = $view->preview();
|
||||
$rendered_json = $build['#markup'];
|
||||
$this->assertEqual($rendered_json, $expected, 'Ensure the previewed json is escaped.');
|
||||
$view->destroy();
|
||||
|
||||
$expected = SafeMarkup::checkPlain($serializer->serialize($entities, 'xml'));
|
||||
|
||||
// Change the request format to xml.
|
||||
$view->setDisplay('rest_export_1');
|
||||
$view->getDisplay()->setOption('style', array(
|
||||
'type' => 'serializer',
|
||||
'options' => array(
|
||||
'uses_fields' => FALSE,
|
||||
'formats' => array(
|
||||
'xml' => 'xml',
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
$this->executeView($view);
|
||||
$build = $view->preview();
|
||||
$rendered_xml = $build['#markup'];
|
||||
$this->assertEqual($rendered_xml, $expected, 'Ensure we preview xml when we change the request format.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the views interface for REST export displays.
|
||||
*/
|
||||
public function testSerializerViewsUI() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
// Click the "Update preview button".
|
||||
$this->drupalPostForm('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1', $edit = array(), t('Update preview'));
|
||||
$this->assertResponse(200);
|
||||
// Check if we receive the expected result.
|
||||
$result = $this->xpath('//div[@id="views-live-preview"]/pre');
|
||||
$this->assertIdentical($this->drupalGet('test/serialize/field'), (string) $result[0], 'The expected JSON preview output was found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the field row style using fieldapi fields.
|
||||
*/
|
||||
public function testFieldapiField() {
|
||||
$this->drupalCreateContentType(array('type' => 'page'));
|
||||
$node = $this->drupalCreateNode();
|
||||
|
||||
$result = $this->drupalGetJSON('test/serialize/node-field');
|
||||
$this->assertEqual($result[0]['nid'], $node->id());
|
||||
$this->assertEqual($result[0]['body'], $node->body->processed);
|
||||
|
||||
// Make sure that serialized fields are not exposed to XSS.
|
||||
$node = $this->drupalCreateNode();
|
||||
$node->body = [
|
||||
'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
|
||||
'format' => filter_default_format(),
|
||||
];
|
||||
$node->save();
|
||||
$result = $this->drupalGetJSON('test/serialize/node-field');
|
||||
$this->assertEqual($result[1]['nid'], $node->id());
|
||||
$this->assertTrue(strpos($this->getRawContent(), "<script") === FALSE, "No script tag is present in the raw page contents.");
|
||||
}
|
||||
}
|
||||
Reference in a new issue