Update to Drupal 8.2.4. For more information, see https://www.drupal.org/project/drupal/releases/8.2.4
This commit is contained in:
parent
0a95b8440e
commit
8544b60b39
284 changed files with 12980 additions and 3199 deletions
|
|
@ -243,24 +243,16 @@ class RestResourceConfig extends ConfigEntityBase implements RestResourceConfigI
|
|||
}
|
||||
|
||||
/**
|
||||
* Normalizes the method to upper case and check validity.
|
||||
* Normalizes the method.
|
||||
*
|
||||
* @param string $method
|
||||
* The request method.
|
||||
*
|
||||
* @return string
|
||||
* The normalised request method.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* If the method is not supported.
|
||||
* The normalized request method.
|
||||
*/
|
||||
protected function normalizeRestMethod($method) {
|
||||
$valid_methods = ['GET', 'POST', 'PATCH', 'DELETE'];
|
||||
$normalised_method = strtoupper($method);
|
||||
if (!in_array($normalised_method, $valid_methods)) {
|
||||
throw new \InvalidArgumentException('The method is not supported.');
|
||||
}
|
||||
return $normalised_method;
|
||||
return strtoupper($method);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,10 +137,6 @@ class DataFieldRow extends RowPluginBase {
|
|||
$output = array();
|
||||
|
||||
foreach ($this->view->field as $id => $field) {
|
||||
// Don't render anything if this field is excluded.
|
||||
if (!empty($field->options['exclude'])) {
|
||||
continue;
|
||||
}
|
||||
// If the raw output option has been set, just get the raw value.
|
||||
if (!empty($this->rawOutputOptions[$id])) {
|
||||
$value = $field->getValue($row);
|
||||
|
|
@ -150,7 +146,10 @@ class DataFieldRow extends RowPluginBase {
|
|||
$value = $field->advancedRender($row);
|
||||
}
|
||||
|
||||
$output[$this->getFieldKeyAlias($id)] = $value;
|
||||
// Omit excluded fields from the rendered output.
|
||||
if (empty($field->options['exclude'])) {
|
||||
$output[$this->getFieldKeyAlias($id)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
|
|
|||
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests authentication provider restrictions.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class AuthTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('basic_auth', 'hal', 'rest', 'entity_test');
|
||||
|
||||
/**
|
||||
* 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');
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Try to read the resource with session cookie authentication, which is
|
||||
// not enabled and should not work.
|
||||
$this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
|
||||
$this->assertResponse('403', 'HTTP response code is 403 when the request was authenticated by the wrong authentication provider.');
|
||||
|
||||
// Ensure that cURL settings/headers aren't carried over to next request.
|
||||
unset($this->curlHandle);
|
||||
|
||||
// Now read it with the Basic authentication which is enabled and should
|
||||
// work.
|
||||
$this->basicAuthGet($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), $account->getUsername(), $account->pass_raw);
|
||||
$this->assertResponse('200', 'HTTP response code is 200 for successfully authenticated requests.');
|
||||
$this->curlClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a HTTP request with Basic authentication.
|
||||
*
|
||||
* We do not use \Drupal\simpletest\WebTestBase::drupalGet because we need to
|
||||
* set curl settings for basic authentication.
|
||||
*
|
||||
* @param \Drupal\Core\Url $url
|
||||
* A Url object.
|
||||
* @param string $username
|
||||
* The user name to authenticate with.
|
||||
* @param string $password
|
||||
* The password.
|
||||
* @param string $mime_type
|
||||
* The MIME type for the Accept header.
|
||||
*
|
||||
* @return string
|
||||
* Curl output.
|
||||
*/
|
||||
protected function basicAuthGet(Url $url, $username, $password, $mime_type = NULL) {
|
||||
if (!isset($mime_type)) {
|
||||
$mime_type = $this->defaultMimeType;
|
||||
}
|
||||
$out = $this->curlExec(
|
||||
array(
|
||||
CURLOPT_HTTPGET => TRUE,
|
||||
CURLOPT_URL => $url->setAbsolute()->toString(),
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
|
||||
CURLOPT_USERPWD => $username . ':' . $password,
|
||||
CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
|
||||
)
|
||||
);
|
||||
|
||||
$this->verbose('GET request to: ' . $url->toString() .
|
||||
'<hr />' . $out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,484 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\comment\Entity\Comment;
|
||||
|
||||
/**
|
||||
* Tests the creation of resources.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class CreateTest extends RESTTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', 'entity_test', 'comment', 'node');
|
||||
|
||||
/**
|
||||
* The 'serializer' service.
|
||||
*
|
||||
* @var \Symfony\Component\Serializer\Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->addDefaultCommentField('node', 'resttest');
|
||||
// Get the 'serializer' service.
|
||||
$this->serializer = $this->container->get('serializer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create a resource which is not REST API enabled.
|
||||
*/
|
||||
public function testCreateResourceRestApiNotEnabled() {
|
||||
$entity_type = 'entity_test';
|
||||
// Enables the REST service for a specific entity type.
|
||||
$this->enableService('entity:' . $entity_type, 'POST');
|
||||
|
||||
// Get the necessary user permissions to create the current entity type.
|
||||
$permissions = $this->entityPermissions($entity_type, 'create');
|
||||
|
||||
// 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 testCreateWithoutPermissionIfBcFlagIsOn() {
|
||||
$rest_settings = $this->config('rest.settings');
|
||||
$rest_settings->set('bc_entity_resource_permissions', TRUE)
|
||||
->save(TRUE);
|
||||
|
||||
$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.');
|
||||
|
||||
// Create a user with the 'restful post entity:entity_test permission and
|
||||
// try again. This time, we should be able to create an entity.
|
||||
$permissions[] = 'restful post entity:' . $entity_type;
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($account);
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests valid and invalid create requests for 'entity_test' entity type.
|
||||
*/
|
||||
public function testCreateEntityTest() {
|
||||
$entity_type = 'entity_test';
|
||||
// Enables the REST service for 'entity_test' entity type.
|
||||
$this->enableService('entity:' . $entity_type, 'POST');
|
||||
// Create two accounts with the required permissions to create resources.
|
||||
// The second one has administrative permissions.
|
||||
$accounts = $this->createAccountPerEntity($entity_type);
|
||||
|
||||
// Verify create requests per user.
|
||||
foreach ($accounts as $key => $account) {
|
||||
$this->drupalLogin($account);
|
||||
// Populate some entity properties before create the entity.
|
||||
$entity_values = $this->entityValues($entity_type);
|
||||
$entity = EntityTest::create($entity_values);
|
||||
|
||||
// Serialize the entity before the POST request.
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
|
||||
// Create the entity over the REST API.
|
||||
$this->assertCreateEntityOverRestApi($entity_type, $serialized);
|
||||
// Get the entity ID from the location header and try to read it from the
|
||||
// database.
|
||||
$this->assertReadEntityIdFromHeaderAndDb($entity_type, $entity, $entity_values);
|
||||
|
||||
// Try to create an entity with an access protected field.
|
||||
// @see entity_test_entity_field_access()
|
||||
$normalized = $this->serializer->normalize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
$normalized['field_test_text'][0]['value'] = 'no access value';
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', $this->serializer->serialize($normalized, $this->defaultFormat, ['account' => $account]), $this->defaultMimeType);
|
||||
$this->assertResponse(403);
|
||||
$this->assertFalse(EntityTest::loadMultiple(), 'No entity has been created in the database.');
|
||||
|
||||
// Try to create a field with a text format this user has no access to.
|
||||
$entity->field_test_text->value = $entity_values['field_test_text'][0]['value'];
|
||||
$entity->field_test_text->format = 'full_html';
|
||||
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
|
||||
// The value selected is not a valid choice because the format must be
|
||||
// 'plain_txt'.
|
||||
$this->assertResponse(422);
|
||||
$this->assertFalse(EntityTest::loadMultiple(), 'No entity has been created in the database.');
|
||||
|
||||
// Restore the valid test value.
|
||||
$entity->field_test_text->format = 'plain_text';
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
|
||||
// Try to send invalid data that cannot be correctly deserialized.
|
||||
$this->assertCreateEntityInvalidData($entity_type);
|
||||
|
||||
// Try to send no data at all, which does not make sense on POST requests.
|
||||
$this->assertCreateEntityNoData($entity_type);
|
||||
|
||||
// Try to send invalid data to trigger the entity validation constraints.
|
||||
// Send a UUID that is too long.
|
||||
$this->assertCreateEntityInvalidSerialized($entity, $entity_type);
|
||||
|
||||
// Try to create an entity without proper permissions.
|
||||
$this->assertCreateEntityWithoutProperPermissions($entity_type, $serialized, ['account' => $account]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests several valid and invalid create requests for 'node' entity type.
|
||||
*/
|
||||
public function testCreateNode() {
|
||||
$entity_type = 'node';
|
||||
// Enables the REST service for 'node' entity type.
|
||||
$this->enableService('entity:' . $entity_type, 'POST');
|
||||
// Create two accounts that have the required permissions to create
|
||||
// resources. The second one has administrative permissions.
|
||||
$accounts = $this->createAccountPerEntity($entity_type);
|
||||
|
||||
// Verify create requests per user.
|
||||
foreach ($accounts as $key => $account) {
|
||||
$this->drupalLogin($account);
|
||||
// Populate some entity properties before create the entity.
|
||||
$entity_values = $this->entityValues($entity_type);
|
||||
$entity = Node::create($entity_values);
|
||||
|
||||
// Verify that user cannot create content when trying to write to fields
|
||||
// where it is not possible.
|
||||
if (!$account->hasPermission('administer nodes')) {
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(403);
|
||||
// Remove fields where non-administrative users cannot write.
|
||||
$entity = $this->removeNodeFieldsForNonAdminUsers($entity);
|
||||
}
|
||||
else {
|
||||
// Changed and revision_timestamp fields can never be added.
|
||||
$entity->set('changed', NULL);
|
||||
$entity->set('revision_timestamp', NULL);
|
||||
}
|
||||
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
|
||||
// Create the entity over the REST API.
|
||||
$this->assertCreateEntityOverRestApi($entity_type, $serialized);
|
||||
|
||||
// Get the new entity ID from the location header and try to read it from
|
||||
// the database.
|
||||
$this->assertReadEntityIdFromHeaderAndDb($entity_type, $entity, $entity_values);
|
||||
|
||||
// Try to send invalid data that cannot be correctly deserialized.
|
||||
$this->assertCreateEntityInvalidData($entity_type);
|
||||
|
||||
// Try to send no data at all, which does not make sense on POST requests.
|
||||
$this->assertCreateEntityNoData($entity_type);
|
||||
|
||||
// Try to send invalid data to trigger the entity validation constraints. Send a UUID that is too long.
|
||||
$this->assertCreateEntityInvalidSerialized($entity, $entity_type);
|
||||
|
||||
// Try to create an entity without proper permissions.
|
||||
$this->assertCreateEntityWithoutProperPermissions($entity_type, $serialized);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test comment creation.
|
||||
*/
|
||||
protected function testCreateComment() {
|
||||
$node = Node::create([
|
||||
'type' => 'resttest',
|
||||
'title' => 'some node',
|
||||
]);
|
||||
$node->save();
|
||||
$entity_type = 'comment';
|
||||
// Enable the REST service for 'comment' entity type.
|
||||
$this->enableService('entity:' . $entity_type, 'POST');
|
||||
// Create two accounts that have the required permissions to create
|
||||
// resources, The second one has administrative permissions.
|
||||
$accounts = $this->createAccountPerEntity($entity_type);
|
||||
$account = end($accounts);
|
||||
|
||||
$this->drupalLogin($account);
|
||||
$entity_values = $this->entityValues($entity_type);
|
||||
$entity_values['entity_id'] = $node->id();
|
||||
|
||||
$entity = Comment::create($entity_values);
|
||||
|
||||
// Changed field can never be added.
|
||||
unset($entity->changed);
|
||||
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
|
||||
// Create the entity over the REST API.
|
||||
$this->assertCreateEntityOverRestApi($entity_type, $serialized);
|
||||
|
||||
// Get the new entity ID from the location header and try to read it from
|
||||
// the database.
|
||||
$this->assertReadEntityIdFromHeaderAndDb($entity_type, $entity, $entity_values);
|
||||
|
||||
// Try to send invalid data that cannot be correctly deserialized.
|
||||
$this->assertCreateEntityInvalidData($entity_type);
|
||||
|
||||
// Try to send no data at all, which does not make sense on POST requests.
|
||||
$this->assertCreateEntityNoData($entity_type);
|
||||
|
||||
// Try to send invalid data to trigger the entity validation constraints.
|
||||
// Send a UUID that is too long.
|
||||
$this->assertCreateEntityInvalidSerialized($entity, $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests several valid and invalid create requests for 'user' entity type.
|
||||
*/
|
||||
public function testCreateUser() {
|
||||
$entity_type = 'user';
|
||||
// Enables the REST service for 'user' entity type.
|
||||
$this->enableService('entity:' . $entity_type, 'POST');
|
||||
// Create two accounts that have the required permissions to create
|
||||
// resources. The second one has administrative permissions.
|
||||
$accounts = $this->createAccountPerEntity($entity_type);
|
||||
|
||||
foreach ($accounts as $key => $account) {
|
||||
$this->drupalLogin($account);
|
||||
$entity_values = $this->entityValues($entity_type);
|
||||
$entity = User::create($entity_values);
|
||||
|
||||
// Verify that only administrative users can create users.
|
||||
if (!$account->hasPermission('administer users')) {
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(403);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Changed field can never be added.
|
||||
$entity->set('changed', NULL);
|
||||
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat, ['account' => $account]);
|
||||
|
||||
// Create the entity over the REST API.
|
||||
$this->assertCreateEntityOverRestApi($entity_type, $serialized);
|
||||
|
||||
// Get the new entity ID from the location header and try to read it from
|
||||
// the database.
|
||||
$this->assertReadEntityIdFromHeaderAndDb($entity_type, $entity, $entity_values);
|
||||
|
||||
// Try to send invalid data that cannot be correctly deserialized.
|
||||
$this->assertCreateEntityInvalidData($entity_type);
|
||||
|
||||
// Try to send no data at all, which does not make sense on POST requests.
|
||||
$this->assertCreateEntityNoData($entity_type);
|
||||
|
||||
// Try to send invalid data to trigger the entity validation constraints.
|
||||
// Send a UUID that is too long.
|
||||
$this->assertCreateEntityInvalidSerialized($entity, $entity_type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates user accounts that have the required permissions to create
|
||||
* resources via the REST API. The second one has administrative permissions.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* Entity type needed to apply user permissions.
|
||||
* @return array
|
||||
* An array that contains user accounts.
|
||||
*/
|
||||
public function createAccountPerEntity($entity_type) {
|
||||
$accounts = array();
|
||||
// Get the necessary user permissions for the current $entity_type creation.
|
||||
$permissions = $this->entityPermissions($entity_type, 'create');
|
||||
// Create user without administrative permissions.
|
||||
$accounts[] = $this->drupalCreateUser($permissions);
|
||||
// Add administrative permissions for nodes and users.
|
||||
$permissions[] = 'administer nodes';
|
||||
$permissions[] = 'administer users';
|
||||
$permissions[] = 'administer comments';
|
||||
// Create an administrative user.
|
||||
$accounts[] = $this->drupalCreateUser($permissions);
|
||||
|
||||
return $accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity over the REST API.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity that should be created.
|
||||
* @param string $serialized
|
||||
* The body for the POST request.
|
||||
*/
|
||||
public function assertCreateEntityOverRestApi($entity_type, $serialized = NULL) {
|
||||
// Note: this will fail with PHP 5.6 when always_populate_raw_post_data is
|
||||
// set to something other than -1. See https://www.drupal.org/node/2456025.
|
||||
// Try first without the CSRF token, which should fail.
|
||||
$url = Url::fromUri('internal:/entity/' . $entity_type)->setOption('query', ['_format' => $this->defaultFormat]);
|
||||
$this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType, FALSE);
|
||||
$this->assertResponse(403);
|
||||
$this->assertRaw('X-CSRF-Token request header is missing');
|
||||
// Then try with an invalid CSRF token.
|
||||
$this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType, 'invalid-csrf-token');
|
||||
$this->assertResponse(403);
|
||||
$this->assertRaw('X-CSRF-Token request header is invalid');
|
||||
// Then try with the CSRF token.
|
||||
$response = $this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(201);
|
||||
|
||||
// Make sure that the response includes an entity in the body and check the
|
||||
// UUID as an example.
|
||||
$request = Json::decode($serialized);
|
||||
$response = Json::decode($response);
|
||||
$this->assertEqual($request['uuid'][0]['value'], $response['uuid'][0]['value'], 'Got new entity created as response after successful POST over Rest API');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new entity ID from the location header and tries to read it from
|
||||
* the database.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* Entity type we need to load the entity from DB.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity we want to check that was inserted correctly.
|
||||
* @param array $entity_values
|
||||
* The values of $entity.
|
||||
*/
|
||||
public function assertReadEntityIdFromHeaderAndDb($entity_type, EntityInterface $entity, array $entity_values = array()) {
|
||||
// Get the location from the HTTP response header.
|
||||
$location_url = $this->drupalGetHeader('location');
|
||||
$url_parts = explode('/', $location_url);
|
||||
$id = end($url_parts);
|
||||
|
||||
// Get the entity using the ID found.
|
||||
$loaded_entity = \Drupal::entityManager()->getStorage($entity_type)->load($id);
|
||||
$this->assertNotIdentical(FALSE, $loaded_entity, 'The new ' . $entity_type . ' was found in the database.');
|
||||
$this->assertEqual($entity->uuid(), $loaded_entity->uuid(), 'UUID of created entity is correct.');
|
||||
|
||||
// Verify that the field values sent and received from DB are the same.
|
||||
foreach ($entity_values as $property => $value) {
|
||||
$actual_value = $loaded_entity->get($property)->value;
|
||||
$send_value = $entity->get($property)->value;
|
||||
$this->assertEqual($send_value, $actual_value, 'Created property ' . $property . ' expected: ' . $send_value . ', actual: ' . $actual_value);
|
||||
}
|
||||
|
||||
// Delete the entity loaded from DB.
|
||||
$loaded_entity->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to send invalid data that cannot be correctly deserialized.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity that should be created.
|
||||
*/
|
||||
public function assertCreateEntityInvalidData($entity_type) {
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', 'kaboom!', $this->defaultMimeType);
|
||||
$this->assertResponse(400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to send no data at all, which does not make sense on POST requests.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity that should be created.
|
||||
*/
|
||||
public function assertCreateEntityNoData($entity_type) {
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', NULL, $this->defaultMimeType);
|
||||
$this->assertResponse(400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an invalid UUID to trigger the entity validation.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity we want to check that was inserted correctly.
|
||||
* @param string $entity_type
|
||||
* The type of the entity that should be created.
|
||||
* @param array $context
|
||||
* Options normalizers/encoders have access to.
|
||||
*/
|
||||
public function assertCreateEntityInvalidSerialized(EntityInterface $entity, $entity_type, array $context = array()) {
|
||||
// Add a UUID that is too long.
|
||||
$entity->set('uuid', $this->randomMachineName(129));
|
||||
$invalid_serialized = $this->serializer->serialize($entity, $this->defaultFormat, $context);
|
||||
|
||||
$response = $this->httpRequest(Url::fromRoute("rest.entity.$entity_type.POST")->setRouteParameter('_format', $this->defaultFormat), '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['message'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create an entity without proper permissions.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity that should be created.
|
||||
* @param string $serialized
|
||||
* The body for the POST request.
|
||||
*/
|
||||
public function assertCreateEntityWithoutProperPermissions($entity_type, $serialized = NULL) {
|
||||
$this->drupalLogout();
|
||||
$this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
|
||||
// Forbidden Error as response.
|
||||
$this->assertResponse(403);
|
||||
$this->assertFalse(\Drupal::entityManager()->getStorage($entity_type)->loadMultiple(), 'No entity has been created in the database.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests the CSRF protection.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class CsrfTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', 'entity_test', 'basic_auth');
|
||||
|
||||
/**
|
||||
* A testing user account.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* The serialized entity.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $serialized;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->enableService('entity:' . $this->testEntityType, 'POST', 'hal_json', array('basic_auth', 'cookie'));
|
||||
|
||||
// Create a user account that has the required permissions to create
|
||||
// resources via the REST API.
|
||||
$permissions = $this->entityPermissions($this->testEntityType, 'create');
|
||||
$this->account = $this->drupalCreateUser($permissions);
|
||||
|
||||
// Serialize an entity to a string to use in the content body of the POST
|
||||
// request.
|
||||
$serializer = $this->container->get('serializer');
|
||||
$entity_values = $this->entityValues($this->testEntityType);
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->testEntityType)
|
||||
->create($entity_values);
|
||||
$this->serialized = $serializer->serialize($entity, $this->defaultFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that CSRF check is not triggered for Basic Auth requests.
|
||||
*/
|
||||
public function testBasicAuth() {
|
||||
$curl_options = $this->getCurlOptions();
|
||||
$curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
|
||||
$curl_options[CURLOPT_USERPWD] = $this->account->getUsername() . ':' . $this->account->pass_raw;
|
||||
$this->curlExec($curl_options);
|
||||
$this->assertResponse(201);
|
||||
// Ensure that the entity was created.
|
||||
$loaded_entity = $this->loadEntityFromLocationHeader($this->drupalGetHeader('location'));
|
||||
$this->assertTrue($loaded_entity, 'An entity was created in the database');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that CSRF check is triggered for Cookie Auth requests.
|
||||
*
|
||||
* @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0. Use
|
||||
* \Drupal\Tests\system\Functional\CsrfRequestHeaderTest::testRouteAccess
|
||||
* instead.
|
||||
*/
|
||||
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.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->testEntityType);
|
||||
$storage->resetCache();
|
||||
$this->assertFalse($storage->loadMultiple(), 'No entity has been created in the database.');
|
||||
|
||||
// Create an entity with the CSRF token.
|
||||
$token = $this->drupalGet('rest/session/token');
|
||||
$curl_options[CURLOPT_HTTPHEADER][] = "X-CSRF-Token: $token";
|
||||
$this->curlExec($curl_options);
|
||||
$this->assertResponse(201);
|
||||
// Ensure that the entity was created.
|
||||
$loaded_entity = $this->loadEntityFromLocationHeader($this->drupalGetHeader('location'));
|
||||
$this->assertTrue($loaded_entity, 'An entity was created in the database');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cURL options to create an entity with POST.
|
||||
*
|
||||
* @return array
|
||||
* The array of cURL options.
|
||||
*/
|
||||
protected function getCurlOptions() {
|
||||
return array(
|
||||
CURLOPT_HTTPGET => FALSE,
|
||||
CURLOPT_POST => TRUE,
|
||||
CURLOPT_POSTFIELDS => $this->serialized,
|
||||
CURLOPT_URL => Url::fromRoute('rest.entity.' . $this->testEntityType . '.POST')->setAbsolute()->toString(),
|
||||
CURLOPT_NOBODY => FALSE,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
"Content-Type: {$this->defaultMimeType}",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests the deletion of resources.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class DeleteTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', 'entity_test', 'node');
|
||||
|
||||
/**
|
||||
* 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');
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Create an entity programmatically.
|
||||
$entity = $this->entityCreate($entity_type);
|
||||
$entity->save();
|
||||
// Try first to delete over REST API without the CSRF token.
|
||||
$url = $entity->toUrl()->setRouteParameter('_format', $this->defaultFormat);
|
||||
$this->httpRequest($url, 'DELETE', NULL, 'application/hal+json', FALSE);
|
||||
$this->assertResponse(403);
|
||||
$this->assertRaw('X-CSRF-Token request header is missing');
|
||||
// Then try with an invalid CSRF token.
|
||||
$this->httpRequest($url, 'DELETE', NULL, 'application/hal+json', 'invalid-csrf-token');
|
||||
$this->assertResponse(403);
|
||||
$this->assertRaw('X-CSRF-Token request header is invalid');
|
||||
// Delete it over the REST API.
|
||||
$response = $this->httpRequest($url, 'DELETE');
|
||||
$this->assertResponse(204);
|
||||
// Clear the static cache with entity_load(), otherwise we won't see the
|
||||
// update.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$entity = $storage->load($entity->id());
|
||||
$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);
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$this->assertNotIdentical(FALSE, $storage->load($entity->id()),
|
||||
'The ' . $entity_type . ' entity is still in the database.');
|
||||
}
|
||||
// Try to delete a resource which is not REST API enabled.
|
||||
$this->enableService(FALSE);
|
||||
$account = $this->drupalCreateUser();
|
||||
$this->drupalLogin($account);
|
||||
$this->httpRequest($account->urlInfo(), 'DELETE');
|
||||
$user_storage = $this->container->get('entity.manager')->getStorage('user');
|
||||
$user_storage->resetCache(array($account->id()));
|
||||
$user = $user_storage->load($account->id());
|
||||
$this->assertEqual($account->id(), $user->id(), 'User still exists in the database.');
|
||||
$this->assertResponse(405);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\Entity\Node;
|
||||
|
||||
/**
|
||||
* Tests special cases for node entities.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class NodeTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Ensure that the node resource works with comment module enabled.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal', 'rest', 'comment', 'node');
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes and attempts to create a node via a REST "post" http request.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
protected function postNode($data) {
|
||||
// Enable node creation via POST.
|
||||
$this->enableNodeConfiguration('POST', 'create');
|
||||
$this->enableService('entity:node', 'POST', 'json');
|
||||
|
||||
// Create a JSON version of a simple node with the title.
|
||||
$serialized = $this->container->get('serializer')->serialize($data, 'json');
|
||||
|
||||
// Post to the REST service to create the node.
|
||||
$this->httpRequest('/entity/node', 'POST', $serialized, 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the title on a newly created node.
|
||||
*
|
||||
* @param array $data
|
||||
* @return \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected function assertNodeTitleMatch($data) {
|
||||
/** @var \Drupal\node\Entity\Node $node */
|
||||
// Load the newly created node.
|
||||
$node = Node::load(1);
|
||||
|
||||
// Test that the title is the same as what we posted.
|
||||
$this->assertEqual($node->title->value, $data['title'][0]['value']);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs various tests on nodes and their REST API.
|
||||
*/
|
||||
public function testNodes() {
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
$this->enableNodeConfiguration('GET', 'view');
|
||||
|
||||
$node = $this->entityCreate('node');
|
||||
$node->save();
|
||||
$this->httpRequest($node->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
|
||||
$this->assertResponse(200);
|
||||
$this->assertHeader('Content-type', $this->defaultMimeType);
|
||||
|
||||
// Also check that JSON works and the routing system selects the correct
|
||||
// REST route.
|
||||
$this->enableService('entity:node', 'GET', 'json');
|
||||
$this->httpRequest($node->urlInfo()->setRouteParameter('_format', 'json'), 'GET');
|
||||
$this->assertResponse(200);
|
||||
$this->assertHeader('Content-type', 'application/json');
|
||||
|
||||
// Check that a simple PATCH update to the node title works as expected.
|
||||
$this->enableNodeConfiguration('PATCH', 'update');
|
||||
|
||||
// Create a PATCH request body that only updates the title field.
|
||||
$new_title = $this->randomString();
|
||||
$data = array(
|
||||
'_links' => array(
|
||||
'type' => array(
|
||||
'href' => Url::fromUri('base:rest/type/node/resttest', array('absolute' => TRUE))->toString(),
|
||||
),
|
||||
),
|
||||
'title' => array(
|
||||
array(
|
||||
'value' => $new_title,
|
||||
),
|
||||
),
|
||||
);
|
||||
$serialized = $this->container->get('serializer')->serialize($data, $this->defaultFormat);
|
||||
$this->httpRequest($node->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Reload the node from the DB and check if the title was correctly updated.
|
||||
$node_storage->resetCache(array($node->id()));
|
||||
$updated_node = $node_storage->load($node->id());
|
||||
$this->assertEqual($updated_node->getTitle(), $new_title);
|
||||
// Make sure that the UUID of the node has not changed.
|
||||
$this->assertEqual($node->get('uuid')->getValue(), $updated_node->get('uuid')->getValue(), 'UUID was not changed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a node using json serialization.
|
||||
*/
|
||||
public function testCreate() {
|
||||
// Data to be used for serialization.
|
||||
$data = [
|
||||
'type' => [['target_id' => 'resttest']],
|
||||
'title' => [['value' => $this->randomString() ]],
|
||||
];
|
||||
|
||||
$this->postNode($data);
|
||||
|
||||
// Make sure the response is "CREATED".
|
||||
$this->assertResponse(201);
|
||||
|
||||
// Make sure the node was created and the title matches.
|
||||
$node = $this->assertNodeTitleMatch($data);
|
||||
|
||||
// Make sure the request returned a redirect header to view the node.
|
||||
$this->assertHeader('Location', $node->url('canonical', ['absolute' => TRUE]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bundle normalization when posting bundle as a simple string.
|
||||
*/
|
||||
public function testBundleNormalization() {
|
||||
// Data to be used for serialization.
|
||||
$data = [
|
||||
'type' => 'resttest',
|
||||
'title' => [['value' => $this->randomString() ]],
|
||||
];
|
||||
|
||||
$this->postNode($data);
|
||||
|
||||
// Make sure the response is "CREATED".
|
||||
$this->assertResponse(201);
|
||||
|
||||
// Make sure the node was created and the title matches.
|
||||
$this->assertNodeTitleMatch($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bundle normalization when posting using a simple string.
|
||||
*/
|
||||
public function testInvalidBundle() {
|
||||
// Data to be used for serialization.
|
||||
$data = [
|
||||
'type' => 'bad_bundle_name',
|
||||
'title' => [['value' => $this->randomString() ]],
|
||||
];
|
||||
|
||||
$this->postNode($data);
|
||||
|
||||
// Make sure the response is "Bad Request".
|
||||
$this->assertResponse(400);
|
||||
$this->assertResponseBody('{"error":"\"bad_bundle_name\" is not a valid bundle type for denormalization."}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the bundle is missing.
|
||||
*/
|
||||
public function testMissingBundle() {
|
||||
// Data to be used for serialization.
|
||||
$data = [
|
||||
'title' => [['value' => $this->randomString() ]],
|
||||
];
|
||||
|
||||
// testing
|
||||
$this->postNode($data);
|
||||
|
||||
// Make sure the response is "Bad Request".
|
||||
$this->assertResponse(400);
|
||||
$this->assertResponseBody('{"error":"A string must be provided as a bundle value."}');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Tests page caching for REST GET requests.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class PageCacheTest extends RESTTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('hal');
|
||||
|
||||
/**
|
||||
* The 'serializer' service.
|
||||
*
|
||||
* @var \Symfony\Component\Serializer\Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Get the 'serializer' service.
|
||||
$this->serializer = $this->container->get('serializer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that configuration changes also clear the page cache.
|
||||
*/
|
||||
public function testConfigChangePageCache() {
|
||||
// Allow anonymous users to issue GET requests.
|
||||
user_role_grant_permissions('anonymous', ['view test entity', 'restful get entity:entity_test']);
|
||||
|
||||
$this->enableService('entity:entity_test', 'POST');
|
||||
$permissions = [
|
||||
'administer entity_test content',
|
||||
];
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
|
||||
// Create an entity and test that the response from a post request is not
|
||||
// cacheable.
|
||||
$entity = $this->entityCreate('entity_test');
|
||||
$entity->set('field_test_text', 'custom cache tag value');
|
||||
$serialized = $this->serializer->serialize($entity, $this->defaultFormat);
|
||||
// Log in for creating the entity.
|
||||
$this->drupalLogin($account);
|
||||
$this->httpRequest('entity/entity_test', 'POST', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(201);
|
||||
|
||||
if ($this->getCacheHeaderValues('x-drupal-cache')) {
|
||||
$this->fail('Post request is cached.');
|
||||
}
|
||||
$this->drupalLogout();
|
||||
|
||||
$url = Url::fromUri('internal:/entity_test/1?_format=' . $this->defaultFormat);
|
||||
|
||||
// Read it over the REST API.
|
||||
$this->enableService('entity:entity_test', 'GET');
|
||||
$this->httpRequest($url, 'GET', NULL, $this->defaultMimeType);
|
||||
$this->assertResponse(200, 'HTTP response code is correct.');
|
||||
$this->assertHeader('x-drupal-cache', 'MISS');
|
||||
$this->assertCacheTag('config:rest.resource.entity.entity_test');
|
||||
$this->assertCacheTag('entity_test:1');
|
||||
$this->assertCacheTag('entity_test_access:field_test_text');
|
||||
|
||||
// Read it again, should be page-cached now.
|
||||
$this->httpRequest($url, 'GET', NULL, $this->defaultMimeType);
|
||||
$this->assertResponse(200, 'HTTP response code is correct.');
|
||||
$this->assertHeader('x-drupal-cache', 'HIT');
|
||||
$this->assertCacheTag('config:rest.resource.entity.entity_test');
|
||||
$this->assertCacheTag('entity_test:1');
|
||||
$this->assertCacheTag('entity_test_access:field_test_text');
|
||||
|
||||
// Trigger a resource config save which should clear the page cache, so we
|
||||
// should get a cache miss now for the same request.
|
||||
$this->resourceConfigStorage->load('entity.entity_test')->save();
|
||||
$this->httpRequest($url, 'GET', NULL, $this->defaultMimeType);
|
||||
$this->assertResponse(200, 'HTTP response code is correct.');
|
||||
$this->assertHeader('x-drupal-cache', 'MISS');
|
||||
$this->assertCacheTag('config:rest.resource.entity.entity_test');
|
||||
$this->assertCacheTag('entity_test:1');
|
||||
$this->assertCacheTag('entity_test_access:field_test_text');
|
||||
|
||||
// Log in for deleting / updating entity.
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Test that updating an entity is not cacheable.
|
||||
$this->enableService('entity:entity_test', 'PATCH');
|
||||
|
||||
// Create a second stub entity for overwriting a field.
|
||||
$patch_values['field_test_text'] = [0 => [
|
||||
'value' => 'patched value',
|
||||
'format' => 'plain_text',
|
||||
]];
|
||||
$patch_entity = $this->container->get('entity_type.manager')
|
||||
->getStorage('entity_test')
|
||||
->create($patch_values);
|
||||
// We don't want to overwrite the UUID.
|
||||
$patch_entity->set('uuid', NULL);
|
||||
$serialized = $this->container->get('serializer')
|
||||
->serialize($patch_entity, $this->defaultFormat);
|
||||
|
||||
// Update the entity over the REST API.
|
||||
$this->httpRequest($url, 'PATCH', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(200);
|
||||
|
||||
if ($this->getCacheHeaderValues('x-drupal-cache')) {
|
||||
$this->fail('Patch request is cached.');
|
||||
}
|
||||
|
||||
// Test that the response from a delete request is not cacheable.
|
||||
$this->enableService('entity:entity_test', 'DELETE');
|
||||
$this->httpRequest($url, 'DELETE');
|
||||
$this->assertResponse(204);
|
||||
|
||||
if ($this->getCacheHeaderValues('x-drupal-cache')) {
|
||||
$this->fail('Patch request is cached.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests HEAD support when a REST resource supports GET.
|
||||
*/
|
||||
public function testHeadSupport() {
|
||||
user_role_grant_permissions('anonymous', ['view test entity', 'restful get entity:entity_test']);
|
||||
|
||||
// Create an entity programatically.
|
||||
$this->entityCreate('entity_test')->save();
|
||||
|
||||
$url = Url::fromUri('internal:/entity_test/1?_format=' . $this->defaultFormat);
|
||||
|
||||
$this->enableService('entity:entity_test', 'GET');
|
||||
|
||||
$this->httpRequest($url, 'HEAD', NULL, $this->defaultMimeType);
|
||||
$this->assertResponse(200, 'HTTP response code is correct.');
|
||||
$this->assertHeader('X-Drupal-Cache', 'MISS');
|
||||
$this->assertResponseBody('');
|
||||
|
||||
$response = $this->httpRequest($url, 'GET', NULL, $this->defaultMimeType);
|
||||
$this->assertResponse(200, 'HTTP response code is correct.');
|
||||
$this->assertHeader('X-Drupal-Cache', 'HIT');
|
||||
$this->assertCacheTag('config:rest.resource.entity.entity_test');
|
||||
$this->assertCacheTag('entity_test:1');
|
||||
$data = Json::decode($response);
|
||||
$this->assertEqual($data['type'][0]['value'], 'entity_test');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,8 @@ use Drupal\simpletest\WebTestBase;
|
|||
|
||||
/**
|
||||
* Test helper class that provides a REST client method to send HTTP requests.
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x-dev and will be removed before Drupal 9.0.0. Use \Drupal\Tests\rest\Functional\ResourceTestBase and \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase instead. Only retained for contributed module tests that may be using this base class.
|
||||
*/
|
||||
abstract class RESTTestBase extends WebTestBase {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,205 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests the retrieval of resources.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class ReadTest extends RESTTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'hal',
|
||||
'rest',
|
||||
'node',
|
||||
'entity_test',
|
||||
'config_test',
|
||||
'taxonomy',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
'entity_test',
|
||||
'node',
|
||||
'config_test',
|
||||
'taxonomy_vocabulary',
|
||||
'block',
|
||||
'user_role',
|
||||
];
|
||||
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');
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Create an entity programmatically.
|
||||
$entity = $this->entityCreate($entity_type);
|
||||
$entity->save();
|
||||
|
||||
// Verify that it exists: use a HEAD request.
|
||||
$this->httpRequest($this->getReadUrl($entity), 'HEAD');
|
||||
$this->assertResponseBody('');
|
||||
$head_headers = $this->drupalGetHeaders();
|
||||
|
||||
// Read it over the REST API.
|
||||
$response = $this->httpRequest($this->getReadUrl($entity), 'GET');
|
||||
$get_headers = $this->drupalGetHeaders();
|
||||
$this->assertResponse('200', 'HTTP response code is correct.');
|
||||
|
||||
// Verify that the GET and HEAD responses are the same, that the only
|
||||
// difference is that there's no body.
|
||||
unset($get_headers['date']);
|
||||
unset($head_headers['date']);
|
||||
unset($get_headers['content-length']);
|
||||
unset($head_headers['content-length']);
|
||||
unset($get_headers['x-drupal-dynamic-cache']);
|
||||
unset($head_headers['x-drupal-dynamic-cache']);
|
||||
$this->assertIdentical($get_headers, $head_headers);
|
||||
$this->assertResponse('200', 'HTTP response code is correct.');
|
||||
|
||||
$this->assertHeader('content-type', $this->defaultMimeType);
|
||||
$data = Json::decode($response);
|
||||
// Only assert one example property here, other properties should be
|
||||
// checked in serialization tests.
|
||||
if ($entity instanceof ConfigEntityInterface) {
|
||||
$this->assertEqual($data['uuid'], $entity->uuid(), 'Entity UUID is correct');
|
||||
}
|
||||
else {
|
||||
$this->assertEqual($data['uuid'][0]['value'], $entity->uuid(), 'Entity UUID is correct');
|
||||
}
|
||||
|
||||
// Try to read the entity with an unsupported mime format.
|
||||
$this->httpRequest($this->getReadUrl($entity, 'wrongformat'), 'GET');
|
||||
$this->assertResponse(406);
|
||||
$this->assertHeader('Content-type', 'application/json');
|
||||
|
||||
// Try to read an entity that does not exist.
|
||||
$response = $this->httpRequest($this->getReadUrl($entity, $this->defaultFormat, 9999), 'GET');
|
||||
$this->assertResponse(404);
|
||||
switch ($entity_type) {
|
||||
case 'node':
|
||||
$path = '/node/{node}';
|
||||
break;
|
||||
|
||||
case 'entity_test':
|
||||
$path = '/entity_test/{entity_test}';
|
||||
break;
|
||||
|
||||
default:
|
||||
$path = "/entity/$entity_type/{" . $entity_type . '}';
|
||||
}
|
||||
$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($this->getReadUrl($entity), '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 a resource, the user entity, which is not REST API enabled.
|
||||
$account = $this->drupalCreateUser();
|
||||
$this->drupalLogin($account);
|
||||
$response = $this->httpRequest($this->getReadUrl($account), 'GET');
|
||||
|
||||
// \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
|
||||
// non-REST route a match, but a lower quality one: no format restrictions
|
||||
// means there's always a match and hence when there is no matching REST
|
||||
// route, the non-REST route is used, but can't render into
|
||||
// application/hal+json, so it returns a 406.
|
||||
$this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.');
|
||||
$this->assertEqual($response, Json::encode([
|
||||
'message' => 'Not acceptable format: hal_json',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the resource structure.
|
||||
*/
|
||||
public function testResourceStructure() {
|
||||
// Enable a service with a format restriction but no authentication.
|
||||
$this->enableService('entity:node', 'GET', 'json');
|
||||
// Create a user account that has the required permissions to read
|
||||
// resources via the REST API.
|
||||
$permissions = $this->entityPermissions('node', 'view');
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Create an entity programmatically.
|
||||
$entity = $this->entityCreate('node');
|
||||
$entity->save();
|
||||
|
||||
// Read it over the REST API.
|
||||
$this->httpRequest($this->getReadUrl($entity, 'json'), 'GET');
|
||||
$this->assertResponse('200', 'HTTP response code is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the read URL object for the entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to get the URL for.
|
||||
* @param string $format
|
||||
* The format to request the entity in.
|
||||
* @param string $entity_id
|
||||
* The entity ID to use in the URL, defaults to the entity's ID if know
|
||||
* given.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The Url object.
|
||||
*/
|
||||
protected function getReadUrl(EntityInterface $entity, $format = NULL, $entity_id = NULL) {
|
||||
if (!$format) {
|
||||
$format = $this->defaultFormat;
|
||||
}
|
||||
if (!$entity_id) {
|
||||
$entity_id = $entity->id();
|
||||
}
|
||||
$entity_type = $entity->getEntityTypeId();
|
||||
if ($entity->hasLinkTemplate('canonical')) {
|
||||
$url = $entity->toUrl('canonical');
|
||||
}
|
||||
else {
|
||||
$route_name = 'rest.entity.' . $entity_type . ".GET.";
|
||||
// If testing unsupported format don't use the format to construct route
|
||||
// name. This would give a RouteNotFoundException.
|
||||
if ($format == 'wrongformat') {
|
||||
$route_name .= $this->defaultFormat;
|
||||
}
|
||||
else {
|
||||
$route_name .= $format;
|
||||
}
|
||||
$url = Url::fromRoute($route_name);
|
||||
}
|
||||
$url->setRouteParameter($entity_type, $entity_id);
|
||||
$url->setRouteParameter('_format', $format);
|
||||
return $url;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,396 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests;
|
||||
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Tests the update of resources.
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class UpdateTest extends RESTTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['hal', 'rest', 'entity_test', 'node', 'comment'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->addDefaultCommentField('entity_test', 'entity_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests several valid and invalid partial update requests on test entities.
|
||||
*/
|
||||
public function testPatchUpdate() {
|
||||
$serializer = $this->container->get('serializer');
|
||||
// @todo Test all other entity types here as well.
|
||||
$entity_type = 'entity_test';
|
||||
|
||||
$this->enableService('entity:' . $entity_type, 'PATCH');
|
||||
// Create a user account that has the required permissions to create
|
||||
// resources via the REST API.
|
||||
$permissions = $this->entityPermissions($entity_type, 'update');
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$context = ['account' => $account];
|
||||
|
||||
// Create an entity and save it to the database.
|
||||
$entity = $this->entityCreate($entity_type);
|
||||
$entity->save();
|
||||
|
||||
// Create a second stub entity for overwriting a field.
|
||||
$patch_values['field_test_text'] = array(0 => array(
|
||||
'value' => $this->randomString(),
|
||||
'format' => 'plain_text',
|
||||
));
|
||||
$patch_entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create($patch_values);
|
||||
// We don't want to overwrite the UUID.
|
||||
$patch_entity->set('uuid', NULL);
|
||||
$serialized = $serializer->serialize($patch_entity, $this->defaultFormat, $context);
|
||||
|
||||
// Update the entity over the REST API but forget to specify a Content-Type
|
||||
// header, this should throw the proper exception.
|
||||
$this->httpRequest($entity->toUrl(), 'PATCH', $serialized, 'none');
|
||||
$this->assertResponse(Response::HTTP_UNSUPPORTED_MEDIA_TYPE);
|
||||
$this->assertRaw('No route found that matches "Content-Type: none"');
|
||||
|
||||
// Update the entity over the REST API.
|
||||
$response = $this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Make sure that the response includes an entity in the body, check the
|
||||
// updated field as an example.
|
||||
$request = Json::decode($serialized);
|
||||
$response = Json::decode($response);
|
||||
$this->assertEqual($request['field_test_text'][0]['value'], $response['field_test_text'][0]['value']);
|
||||
unset($request['_links']);
|
||||
unset($response['_links']);
|
||||
unset($response['id']);
|
||||
unset($response['uuid']);
|
||||
unset($response['name']);
|
||||
$this->assertEqual($request, $response);
|
||||
|
||||
// Re-load updated entity from the database.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$entity = $storage->load($entity->id());
|
||||
$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(200);
|
||||
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$entity = $storage->load($entity->id());
|
||||
$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(200);
|
||||
|
||||
// Re-load updated entity from the database.
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$entity = $storage->load($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.
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$entity = $storage->load($entity->id());
|
||||
$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.
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$entity = $storage->load($entity->id());
|
||||
$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.
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$entity = $storage->load($entity->id());
|
||||
$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);
|
||||
$storage->resetCache([9999]);
|
||||
$loaded_entity = $storage->load(9999);
|
||||
$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->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $invalid_serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(422);
|
||||
$error = Json::decode($response);
|
||||
$this->assertEqual($error['message'], "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');
|
||||
$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->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(422);
|
||||
$error = Json::decode($response);
|
||||
$this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
|
||||
|
||||
// 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->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(422);
|
||||
$error = Json::decode($response);
|
||||
$this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
|
||||
|
||||
// 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(200);
|
||||
|
||||
// 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->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
|
||||
$this->assertResponse(422);
|
||||
$error = Json::decode($response);
|
||||
$this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n");
|
||||
|
||||
// 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(200);
|
||||
|
||||
// Verify that we can log in with the new password.
|
||||
$account->pass_raw = $new_password;
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test patching a comment using both HAL+JSON and JSON.
|
||||
*/
|
||||
public function testUpdateComment() {
|
||||
$entity_type = 'comment';
|
||||
// Enables the REST service for 'comment' entity type.
|
||||
$this->enableService('entity:' . $entity_type, 'PATCH', ['hal_json', 'json']);
|
||||
$permissions = $this->entityPermissions($entity_type, 'update');
|
||||
$account = $this->drupalCreateUser($permissions);
|
||||
$account->set('mail', 'old-email@example.com');
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Create & save an entity to comment on, plus a comment.
|
||||
$entity_test = EntityTest::create();
|
||||
$entity_test->save();
|
||||
$entity_values = $this->entityValues($entity_type);
|
||||
$entity_values['entity_id'] = $entity_test->id();
|
||||
$entity_values['uid'] = $account->id();
|
||||
$comment = Comment::create($entity_values);
|
||||
$comment->save();
|
||||
|
||||
$this->pass('Test case 1: PATCH comment using HAL+JSON.');
|
||||
$comment->setSubject('Initial subject')->save();
|
||||
$read_only_fields = [
|
||||
'name',
|
||||
'created',
|
||||
'changed',
|
||||
'status',
|
||||
'thread',
|
||||
'entity_type',
|
||||
'field_name',
|
||||
'entity_id',
|
||||
'uid',
|
||||
];
|
||||
$this->assertNotEqual('Updated subject', $comment->getSubject());
|
||||
$comment->setSubject('Updated subject');
|
||||
$this->patchEntity($comment, $read_only_fields, $account, 'hal_json', 'application/hal+json');
|
||||
$comment = Comment::load($comment->id());
|
||||
$this->assertEqual('Updated subject', $comment->getSubject());
|
||||
|
||||
$this->pass('Test case 1: PATCH comment using JSON.');
|
||||
$comment->setSubject('Initial subject')->save();
|
||||
$read_only_fields = [
|
||||
'pid', // Extra compared to HAL+JSON.
|
||||
'entity_id',
|
||||
'uid',
|
||||
'name',
|
||||
'homepage', // Extra compared to HAL+JSON.
|
||||
'created',
|
||||
'changed',
|
||||
'status',
|
||||
'thread',
|
||||
'entity_type',
|
||||
'field_name',
|
||||
];
|
||||
$this->assertNotEqual('Updated subject', $comment->getSubject());
|
||||
$comment->setSubject('Updated subject');
|
||||
$this->patchEntity($comment, $read_only_fields, $account, 'json', 'application/json');
|
||||
$comment = Comment::load($comment->id());
|
||||
$this->assertEqual('Updated subject', $comment->getSubject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches an existing entity using the passed in (modified) entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The updated entity to send.
|
||||
* @param string[] $read_only_fields
|
||||
* Names of the fields that are read-only, in validation order.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account to use for serialization.
|
||||
* @param string $format
|
||||
* A serialization format.
|
||||
* @param string $mime_type
|
||||
* The MIME type corresponding to the specified serialization format.
|
||||
*/
|
||||
protected function patchEntity(EntityInterface $entity, array $read_only_fields, AccountInterface $account, $format, $mime_type) {
|
||||
$serializer = $this->container->get('serializer');
|
||||
|
||||
$url = $entity->toUrl()->setRouteParameter('_format', $this->defaultFormat);
|
||||
$context = ['account' => $account];
|
||||
// Certain fields are always read-only, others this user simply is not
|
||||
// allowed to modify. For all of them, ensure they are not serialized, else
|
||||
// we'll get a 403 plus an error message.
|
||||
for ($i = 0; $i < count($read_only_fields); $i++) {
|
||||
$field = $read_only_fields[$i];
|
||||
|
||||
$normalized = $serializer->normalize($entity, $format, $context);
|
||||
if ($format !== 'hal_json') {
|
||||
// The default normalizer always keeps fields, even if they are unset
|
||||
// here because they should be omitted during a PATCH request. Therefore
|
||||
// manually strip them
|
||||
// @see \Drupal\Core\Entity\ContentEntityBase::__unset()
|
||||
// @see \Drupal\serialization\Normalizer\EntityNormalizer::normalize()
|
||||
// @see \Drupal\hal\Normalizer\ContentEntityNormalizer::normalize()
|
||||
$read_only_fields_so_far = array_slice($read_only_fields, 0, $i);
|
||||
$normalized = array_diff_key($normalized, array_flip($read_only_fields_so_far));
|
||||
}
|
||||
$serialized = $serializer->serialize($normalized, $format, $context);
|
||||
|
||||
$this->httpRequest($url, 'PATCH', $serialized, $mime_type);
|
||||
$this->assertResponse(403);
|
||||
$this->assertResponseBody('{"message":"Access denied on updating field \\u0027' . $field . '\\u0027."}');
|
||||
|
||||
if ($format === 'hal_json') {
|
||||
// We've just tried with this read-only field, now unset it.
|
||||
$entity->set($field, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, with all read-only fields unset, the request should succeed.
|
||||
$normalized = $serializer->normalize($entity, $format, $context);
|
||||
if ($format !== 'hal_json') {
|
||||
$normalized = array_diff_key($normalized, array_combine($read_only_fields, $read_only_fields));
|
||||
}
|
||||
$serialized = $serializer->serialize($normalized, $format, $context);
|
||||
|
||||
// Try first without CSRF token which should fail.
|
||||
$this->httpRequest($url, 'PATCH', $serialized, $mime_type, FALSE);
|
||||
$this->assertResponse(403);
|
||||
$this->assertRaw('X-CSRF-Token request header is missing');
|
||||
// Then try with an invalid CSRF token.
|
||||
$this->httpRequest($url, 'PATCH', $serialized, $mime_type, 'invalid-csrf-token');
|
||||
$this->assertResponse(403);
|
||||
$this->assertRaw('X-CSRF-Token request header is invalid');
|
||||
// Then try with CSRF token.
|
||||
$this->httpRequest($url, 'PATCH', $serialized, $mime_type);
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
||||
}
|
||||
88
core/modules/rest/src/Tests/Views/ExcludedFieldTokenTest.php
Normal file
88
core/modules/rest/src/Tests/Views/ExcludedFieldTokenTest.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\Tests\Views;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\views\Tests\ViewTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests the display of an excluded field that is used as a token.
|
||||
*
|
||||
* @group rest
|
||||
* @see \Drupal\rest\Plugin\views\display\RestExport
|
||||
* @see \Drupal\rest\Plugin\views\row\DataFieldRow
|
||||
*/
|
||||
class ExcludedFieldTokenTest extends ViewTestBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\views\ViewExecutable
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* The views that are used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_excluded_field_token_display'];
|
||||
|
||||
/**
|
||||
* The modules that need to be installed for this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'entity_test',
|
||||
'rest_test_views',
|
||||
'node',
|
||||
'field',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
ViewTestData::createTestViews(get_class($this), ['rest_test_views']);
|
||||
|
||||
// Create some test content.
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
Node::create([
|
||||
'type' => 'article',
|
||||
'title' => 'Article test ' . $i,
|
||||
])->save();
|
||||
}
|
||||
|
||||
$this->enableViewsTestModule();
|
||||
|
||||
$this->view = Views::getView('test_excluded_field_token_display');
|
||||
$this->view->setDisplay('rest_export_1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the display of an excluded title field when used as a token.
|
||||
*/
|
||||
public function testExcludedTitleTokenDisplay() {
|
||||
$actual_json = $this->drupalGetWithFormat($this->view->getPath(), 'json');
|
||||
$this->assertResponse(200);
|
||||
|
||||
$expected = [
|
||||
['nothing' => 'Article test 10'],
|
||||
['nothing' => 'Article test 9'],
|
||||
['nothing' => 'Article test 8'],
|
||||
['nothing' => 'Article test 7'],
|
||||
['nothing' => 'Article test 6'],
|
||||
['nothing' => 'Article test 5'],
|
||||
['nothing' => 'Article test 4'],
|
||||
['nothing' => 'Article test 3'],
|
||||
['nothing' => 'Article test 2'],
|
||||
['nothing' => 'Article test 1'],
|
||||
];
|
||||
$this->assertIdentical($actual_json, json_encode($expected));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
name: 'Configuration test REST'
|
||||
type: module
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- config_test
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains hook implementations for testing REST module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_alter().
|
||||
*/
|
||||
function config_test_rest_entity_type_alter(array &$entity_types) {
|
||||
// Undo part of what config_test_entity_type_alter() did: remove this
|
||||
// config_test_no_status entity type, because it uses the same entity class as
|
||||
// the config_test entity type, which makes REST deserialization impossible.
|
||||
unset($entity_types['config_test_no_status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_access().
|
||||
*/
|
||||
function config_test_rest_config_test_access(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
// Add permission, so that EntityResourceTestBase's scenarios can test access
|
||||
// being denied. By default, all access is always allowed for the config_test
|
||||
// config entity.
|
||||
return AccessResult::forbiddenIf(!$account->hasPermission('view config_test'))->cachePerPermissions();
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
view config_test:
|
||||
title: 'View ConfigTest entities'
|
||||
|
|
@ -5,6 +5,11 @@
|
|||
* Contains hook implementations for testing REST module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
|
||||
/**
|
||||
* Implements hook_rest_type_uri_alter().
|
||||
*/
|
||||
|
|
@ -22,3 +27,24 @@ function rest_test_rest_relation_uri_alter(&$uri, $context = array()) {
|
|||
$uri = 'rest_test_relation';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_field_access().
|
||||
*
|
||||
* @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::setUp()
|
||||
* @see \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPost()
|
||||
*/
|
||||
function rest_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
|
||||
if ($field_definition->getName() === 'field_rest_test') {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
// Never ever allow this field to be viewed: this lets EntityResourceTestBase::testGet() test in a "vanilla" way.
|
||||
return AccessResult::forbidden();
|
||||
case 'edit':
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
// No opinion.
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,277 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.article
|
||||
module:
|
||||
- node
|
||||
- rest
|
||||
- user
|
||||
id: test_excluded_field_token_display
|
||||
label: 'Test Excluded Field Token Display'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: serializer
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: true
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
plugin_id: field
|
||||
nothing:
|
||||
id: nothing
|
||||
table: views
|
||||
field: nothing
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: true
|
||||
text: '{{ title }}'
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: false
|
||||
plugin_id: custom
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
value:
|
||||
article: article
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
order: DESC
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
plugin_id: standard
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- request_format
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
rest_export_1:
|
||||
display_plugin: rest_export
|
||||
id: rest_export_1
|
||||
display_title: 'REST export'
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: rest/test/excluded-field-token
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
style:
|
||||
type: serializer
|
||||
options:
|
||||
formats:
|
||||
json: json
|
||||
row:
|
||||
type: data_field
|
||||
options:
|
||||
field_options:
|
||||
title:
|
||||
alias: ''
|
||||
raw_output: false
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- request_format
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Trait for ResourceTestBase subclasses testing $auth=NULL, i.e. authless/anon.
|
||||
*
|
||||
* Characteristics:
|
||||
* - When no authentication provider is being used, there also cannot be any
|
||||
* particular error response for missing authentication, since by definition
|
||||
* there is not any authentication.
|
||||
* - For the same reason, there are no authentication edge cases to test.
|
||||
* - Because no authentication is required, this is vulnerable to CSRF attacks
|
||||
* by design. Hence a REST resource should probably only allow for anonymous
|
||||
* for safe (GET/HEAD) HTTP methods, and only with extreme care should unsafe
|
||||
* (POST/PATCH/DELETE) HTTP methods be allowed for a REST resource that allows
|
||||
* anonymous access.
|
||||
*/
|
||||
trait AnonResourceTestTrait {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
|
||||
throw new \LogicException('When testing for anonymous users, authentication cannot be missing.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Trait for ResourceTestBase subclasses testing $auth=basic_auth.
|
||||
*
|
||||
* Characteristics:
|
||||
* - Every request must send an Authorization header.
|
||||
* - When accessing a URI that requires authentication without being
|
||||
* authenticated, a 401 response must be sent.
|
||||
* - Because every request must send an authorization, there is no danger of
|
||||
* CSRF attacks.
|
||||
*/
|
||||
trait BasicAuthResourceTestTrait {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAuthenticationRequestOptions($method) {
|
||||
return [
|
||||
'headers' => [
|
||||
'Authorization' => 'Basic ' . base64_encode($this->account->name->value . ':' . $this->account->passRaw),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
|
||||
$this->assertResourceErrorResponse(401, 'No authentication credentials provided.', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Trait for ResourceTestBase subclasses testing $auth=cookie.
|
||||
*
|
||||
* Characteristics:
|
||||
* - After performing a valid "log in" request, the server responds with a 2xx
|
||||
* status code and a 'Set-Cookie' response header. This cookie is what
|
||||
* continues to identify the user in subsequent requests.
|
||||
* - When accessing a URI that requires authentication without being
|
||||
* authenticated, a standard 403 response must be sent.
|
||||
* - Because of the reliance on cookies, and the fact that user agents send
|
||||
* cookies with every request, this is vulnerable to CSRF attacks. To mitigate
|
||||
* this, the response for the "log in" request contains a CSRF token that must
|
||||
* be sent with every unsafe (POST/PATCH/DELETE) HTTP request.
|
||||
*/
|
||||
trait CookieResourceTestTrait {
|
||||
|
||||
/**
|
||||
* The session cookie.
|
||||
*
|
||||
* @see ::initAuthentication
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionCookie;
|
||||
|
||||
/**
|
||||
* The CSRF token.
|
||||
*
|
||||
* @see ::initAuthentication
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* The logout token.
|
||||
*
|
||||
* @see ::initAuthentication
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $logoutToken;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initAuthentication() {
|
||||
// @todo Remove hardcoded use of the 'json' format, and use static::$format
|
||||
// + static::$mimeType instead in https://www.drupal.org/node/2820888.
|
||||
$user_login_url = Url::fromRoute('user.login.http')
|
||||
->setRouteParameter('_format', 'json');
|
||||
|
||||
$request_body = [
|
||||
'name' => $this->account->name->value,
|
||||
'pass' => $this->account->passRaw,
|
||||
];
|
||||
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($request_body, 'json');
|
||||
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/json';
|
||||
$response = $this->request('POST', $user_login_url, $request_options);
|
||||
|
||||
// Parse and store the session cookie.
|
||||
$this->sessionCookie = explode(';', $response->getHeader('Set-Cookie')[0], 2)[0];
|
||||
|
||||
// Parse and store the CSRF token and logout token.
|
||||
$data = $this->serializer->decode((string)$response->getBody(), static::$format);
|
||||
$this->csrfToken = $data['csrf_token'];
|
||||
$this->logoutToken = $data['logout_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAuthenticationRequestOptions($method) {
|
||||
$request_options[RequestOptions::HEADERS]['Cookie'] = $this->sessionCookie;
|
||||
// @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
|
||||
if (!in_array($method, ['HEAD', 'GET', 'OPTIONS', 'TRACE'])) {
|
||||
$request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken;
|
||||
}
|
||||
return $request_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
|
||||
$this->assertResourceErrorResponse(403, '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {
|
||||
// X-CSRF-Token request header is unnecessary for safe and side effect-free
|
||||
// HTTP methods. No need for additional assertions.
|
||||
// @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
|
||||
if (in_array($method, ['HEAD', 'GET', 'OPTIONS', 'TRACE'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
unset($request_options[RequestOptions::HEADERS]['X-CSRF-Token']);
|
||||
|
||||
|
||||
// DX: 403 when missing X-CSRF-Token request header.
|
||||
$response = $this->request($method, $url, $request_options);
|
||||
$this->assertResourceErrorResponse(403, 'X-CSRF-Token request header is missing', $response);
|
||||
|
||||
|
||||
$request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = 'this-is-not-the-token-you-are-looking-for';
|
||||
|
||||
|
||||
// DX: 403 when invalid X-CSRF-Token request header.
|
||||
$response = $this->request($method, $url, $request_options);
|
||||
$this->assertResourceErrorResponse(403, 'X-CSRF-Token request header is invalid', $response);
|
||||
|
||||
|
||||
$request_options[RequestOptions::HEADERS]['X-CSRF-Token'] = $this->csrfToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockJsonAnonTest extends BlockResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockJsonBasicAuthTest extends BlockResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockJsonCookieTest extends BlockResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
|
||||
|
||||
use Drupal\block\Entity\Block;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
abstract class BlockResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'block';
|
||||
|
||||
/**
|
||||
* @var \Drupal\block\BlockInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->entity->setVisibilityConfig('user_role', [])->save();
|
||||
break;
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['administer blocks']);
|
||||
break;
|
||||
case 'PATCH':
|
||||
$this->grantPermissionsToTestedRole(['administer blocks']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$block = Block::create([
|
||||
'plugin' => 'llama_block',
|
||||
'region' => 'header',
|
||||
'id' => 'llama',
|
||||
'theme' => 'classy',
|
||||
]);
|
||||
// All blocks can be viewed by the anonymous user by default. An interesting
|
||||
// side effect of this is that any anonymous user is also able to read the
|
||||
// corresponding block config entity via REST, even if an authentication
|
||||
// provider is configured for the block config entity REST resource! In
|
||||
// other words: Block entities do not distinguish between 'view' as in
|
||||
// "render on a page" and 'view' as in "read the configuration".
|
||||
// This prevents that.
|
||||
// @todo Fix this in https://www.drupal.org/node/2820315.
|
||||
$block->setVisibilityConfig('user_role', [
|
||||
'id' => 'user_role',
|
||||
'roles' => ['non-existing-role' => 'non-existing-role'],
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [
|
||||
'user' => '@user.current_user_context:current_user',
|
||||
],
|
||||
]);
|
||||
$block->save();
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$normalization = [
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'id' => 'llama',
|
||||
'weight' => NULL,
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [
|
||||
'theme' => [
|
||||
'classy',
|
||||
],
|
||||
],
|
||||
'theme' => 'classy',
|
||||
'region' => 'header',
|
||||
'provider' => NULL,
|
||||
'plugin' => 'llama_block',
|
||||
'settings' => [
|
||||
'id' => 'broken',
|
||||
'label' => '',
|
||||
'provider' => 'core',
|
||||
'label_display' => 'visible',
|
||||
],
|
||||
'visibility' => [],
|
||||
];
|
||||
|
||||
return $normalization;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
// @see ::createEntity()
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheTags() {
|
||||
// Because the 'user.permissions' cache context is missing, the cache tag
|
||||
// for the anonymous user role is never added automatically.
|
||||
return array_filter(parent::getExpectedCacheTags(), function ($tag) {
|
||||
return $tag !== 'config:user.role.anonymous';
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class CommentJsonAnonTest extends CommentResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Anononymous users cannot edit their own comments.
|
||||
*
|
||||
* @see \Drupal\comment\CommentAccessControlHandler::checkAccess
|
||||
*
|
||||
* Therefore we grant them the 'administer comments' permission for the
|
||||
* purpose of this test.
|
||||
*
|
||||
* @see ::setUpAuthorization
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'pid',
|
||||
'entity_id',
|
||||
'changed',
|
||||
'thread',
|
||||
'entity_type',
|
||||
'field_name',
|
||||
];
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class CommentJsonBasicAuthTest extends CommentResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class CommentJsonCookieTest extends CommentResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
|
||||
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\comment\Entity\CommentType;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
|
||||
abstract class CommentResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
use CommentTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['comment', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'comment';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'pid',
|
||||
'entity_id',
|
||||
'uid',
|
||||
'name',
|
||||
'homepage',
|
||||
'created',
|
||||
'changed',
|
||||
'status',
|
||||
'thread',
|
||||
'entity_type',
|
||||
'field_name',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\comment\CommentInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['access comments', 'view test entity']);
|
||||
break;
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['post comments']);
|
||||
break;
|
||||
case 'PATCH':
|
||||
// Anononymous users are not ever allowed to edit their own comments. To
|
||||
// be able to test PATCHing comments as the anonymous user, the more
|
||||
// permissive 'administer comments' permission must be granted.
|
||||
// @see \Drupal\comment\CommentAccessControlHandler::checkAccess
|
||||
if (static::$auth) {
|
||||
$this->grantPermissionsToTestedRole(['edit own comments']);
|
||||
}
|
||||
else {
|
||||
$this->grantPermissionsToTestedRole(['administer comments']);
|
||||
}
|
||||
break;
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['administer comments']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "bar" bundle for the "entity_test" entity type and create.
|
||||
$bundle = 'bar';
|
||||
entity_test_create_bundle($bundle, NULL, 'entity_test');
|
||||
|
||||
// Create a comment field on this bundle.
|
||||
$this->addDefaultCommentField('entity_test', 'bar', 'comment');
|
||||
|
||||
// Create a "Camelids" test entity that the comment will be assigned to.
|
||||
$commented_entity = EntityTest::create(array(
|
||||
'name' => 'Camelids',
|
||||
'type' => 'bar',
|
||||
));
|
||||
$commented_entity->save();
|
||||
|
||||
// Create a "Llama" comment.
|
||||
$comment = Comment::create([
|
||||
'comment_body' => [
|
||||
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
'entity_id' => $commented_entity->id(),
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'comment',
|
||||
]);
|
||||
$comment->setSubject('Llama')
|
||||
->setOwnerId(static::$auth ? $this->account->id() : 0)
|
||||
->setPublished(TRUE)
|
||||
->setCreatedTime(123456789)
|
||||
->setChangedTime(123456789);
|
||||
$comment->save();
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$author = User::load($this->entity->getOwnerId());
|
||||
return [
|
||||
'cid' => [
|
||||
['value' => 1],
|
||||
],
|
||||
'uuid' => [
|
||||
['value' => $this->entity->uuid()],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'comment_type' => [
|
||||
[
|
||||
'target_id' => 'comment',
|
||||
'target_type' => 'comment_type',
|
||||
'target_uuid' => CommentType::load('comment')->uuid(),
|
||||
],
|
||||
],
|
||||
'subject' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
],
|
||||
],
|
||||
'status' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'uid' => [
|
||||
[
|
||||
'target_id' => $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => base_path() . 'user/' . $author->id(),
|
||||
],
|
||||
],
|
||||
'pid' => [],
|
||||
'entity_type' => [
|
||||
[
|
||||
'value' => 'entity_test',
|
||||
],
|
||||
],
|
||||
'entity_id' => [
|
||||
[
|
||||
'target_id' => '1',
|
||||
'target_type' => 'entity_test',
|
||||
'target_uuid' => EntityTest::load(1)->uuid(),
|
||||
'url' => base_path() . 'entity_test/1',
|
||||
],
|
||||
],
|
||||
'field_name' => [
|
||||
[
|
||||
'value' => 'comment',
|
||||
],
|
||||
],
|
||||
'name' => [],
|
||||
'homepage' => [],
|
||||
'thread' => [
|
||||
[
|
||||
'value' => '01/',
|
||||
],
|
||||
],
|
||||
'comment_body' => [
|
||||
[
|
||||
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'comment_type' => [
|
||||
[
|
||||
'target_id' => 'comment',
|
||||
],
|
||||
],
|
||||
'entity_type' => [
|
||||
[
|
||||
'value' => 'entity_test',
|
||||
],
|
||||
],
|
||||
'entity_id' => [
|
||||
[
|
||||
'target_id' => EntityTest::load(1)->id(),
|
||||
],
|
||||
],
|
||||
'field_name' => [
|
||||
[
|
||||
'value' => 'comment',
|
||||
],
|
||||
],
|
||||
'subject' => [
|
||||
[
|
||||
'value' => 'Dramallama',
|
||||
],
|
||||
],
|
||||
'comment_body' => [
|
||||
[
|
||||
'value' => 'Llamas are awesome.',
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPatchEntity() {
|
||||
return array_diff_key($this->getNormalizedPostEntity(), ['entity_type' => TRUE, 'entity_id' => TRUE, 'field_name' => TRUE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests POSTing a comment without critical base fields.
|
||||
*
|
||||
* testPost() is testing with the most minimal normalization possible: the one
|
||||
* returned by ::getNormalizedPostEntity().
|
||||
*
|
||||
* But Comment entities have some very special edge cases:
|
||||
* - base fields that are not marked as required in
|
||||
* \Drupal\comment\Entity\Comment::baseFieldDefinitions() yet in fact are
|
||||
* required.
|
||||
* - base fields that are marked as required, but yet can still result in
|
||||
* validation errors other than "missing required field".
|
||||
*/
|
||||
public function testPostDxWithoutCriticalBaseFields() {
|
||||
$this->initAuthentication();
|
||||
$this->provisionEntityResource();
|
||||
$this->setUpAuthorization('POST');
|
||||
|
||||
$url = $this->getPostUrl()->setOption('query', ['_format' => static::$format]);
|
||||
$request_options = [];
|
||||
$request_options[RequestOptions::HEADERS]['Accept'] = static::$mimeType;
|
||||
$request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
|
||||
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('POST'));
|
||||
|
||||
// DX: 422 when missing 'entity_type' field.
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['entity_type' => TRUE]), static::$format);
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
// @todo Uncomment, remove next line in https://www.drupal.org/node/2820364.
|
||||
$this->assertResourceErrorResponse(500, 'A fatal error occurred: Internal Server Error', $response);
|
||||
//$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nentity_type: This value should not be null.\n", $response);
|
||||
|
||||
// DX: 422 when missing 'entity_id' field.
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['entity_id' => TRUE]), static::$format);
|
||||
// @todo Remove the try/catch in favor of the two commented lines in
|
||||
// https://www.drupal.org/node/2820364.
|
||||
try {
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
// This happens on DrupalCI.
|
||||
//$this->assertSame(500, $response->getStatusCode());
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// This happens on Wim's local machine.
|
||||
//$this->assertSame("Error: Call to a member function get() on null\nDrupal\\comment\\Plugin\\Validation\\Constraint\\CommentNameConstraintValidator->getAnonymousContactDetailsSetting()() (Line: 96)\n", $e->getMessage());
|
||||
}
|
||||
//$response = $this->request('POST', $url, $request_options);
|
||||
//$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nentity_type: This value should not be null.\n", $response);
|
||||
|
||||
// DX: 422 when missing 'entity_type' field.
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode(array_diff_key($this->getNormalizedPostEntity(), ['field_name' => TRUE]), static::$format);
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
// @todo Uncomment, remove next line in https://www.drupal.org/node/2820364.
|
||||
$this->assertResourceErrorResponse(500, 'A fatal error occurred: Field is unknown.', $response);
|
||||
//$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nfield_name: This value should not be null.\n", $response);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ConfigTestJsonAnonTest extends ConfigTestResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ConfigTestJsonBasicAuthTest extends ConfigTestResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class ConfigTestJsonCookieTest extends ConfigTestResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
|
||||
|
||||
use Drupal\config_test\Entity\ConfigTest;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
abstract class ConfigTestResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['config_test', 'config_test_rest'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'config_test';
|
||||
|
||||
/**
|
||||
* @var \Drupal\config_test\ConfigTestInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['view config_test']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$config_test = ConfigTest::create([
|
||||
'id' => 'llama',
|
||||
'label' => 'Llama',
|
||||
]);
|
||||
$config_test->save();
|
||||
|
||||
return $config_test;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$normalization = [
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'id' => 'llama',
|
||||
'weight' => 0,
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [],
|
||||
'label' => 'Llama',
|
||||
'style' => NULL,
|
||||
'size' => NULL,
|
||||
'size_value' => NULL,
|
||||
'protected_property' => NULL,
|
||||
];
|
||||
|
||||
return $normalization;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class EntityTestJsonAnonTest extends EntityTestResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class EntityTestJsonBasicAuthTest extends EntityTestResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class EntityTestJsonCookieTest extends EntityTestResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
abstract class EntityTestResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'entity_test';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [];
|
||||
|
||||
/**
|
||||
* @var \Drupal\entity_test\Entity\EntityTest
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['view test entity']);
|
||||
break;
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['create entity_test entity_test_with_bundle entities']);
|
||||
break;
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['administer entity_test content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$entity_test = EntityTest::create([
|
||||
'name' => 'Llama',
|
||||
'type' => 'entity_test',
|
||||
]);
|
||||
$entity_test->setOwnerId(0);
|
||||
$entity_test->save();
|
||||
|
||||
return $entity_test;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$author = User::load(0);
|
||||
$normalization = [
|
||||
'uuid' => [
|
||||
[
|
||||
'value' => $this->entity->uuid()
|
||||
]
|
||||
],
|
||||
'id' => [
|
||||
[
|
||||
'value' => '1',
|
||||
],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'type' => [
|
||||
[
|
||||
'value' => 'entity_test',
|
||||
]
|
||||
],
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
]
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => $this->entity->get('created')->value,
|
||||
]
|
||||
],
|
||||
'user_id' => [
|
||||
[
|
||||
'target_id' => $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => $author->toUrl()->toString(),
|
||||
]
|
||||
],
|
||||
'field_test_text' => [],
|
||||
];
|
||||
|
||||
return $normalization;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'type' => 'entity_test',
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Dramallama',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class NodeJsonAnonTest extends NodeResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class NodeJsonBasicAuthTest extends NodeResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class NodeJsonCookieTest extends NodeResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
abstract class NodeResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'node';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'uid',
|
||||
'created',
|
||||
'changed',
|
||||
'promote',
|
||||
'sticky',
|
||||
'revision_timestamp',
|
||||
'revision_uid',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['access content']);
|
||||
break;
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['access content', 'create camelids content']);
|
||||
break;
|
||||
case 'PATCH':
|
||||
$this->grantPermissionsToTestedRole(['access content', 'edit any camelids content']);
|
||||
break;
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['access content', 'delete any camelids content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
if (!NodeType::load('camelids')) {
|
||||
// Create a "Camelids" node type.
|
||||
NodeType::create([
|
||||
'name' => 'Camelids',
|
||||
'type' => 'camelids',
|
||||
])->save();
|
||||
}
|
||||
|
||||
// Create a "Llama" node.
|
||||
$node = Node::create(['type' => 'camelids']);
|
||||
$node->setTitle('Llama')
|
||||
->setOwnerId(static::$auth ? $this->account->id() : 0)
|
||||
->setPublished(TRUE)
|
||||
->setCreatedTime(123456789)
|
||||
->setChangedTime(123456789)
|
||||
->setRevisionCreationTime(123456789)
|
||||
->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$author = User::load($this->entity->getOwnerId());
|
||||
return [
|
||||
'nid' => [
|
||||
['value' => 1],
|
||||
],
|
||||
'uuid' => [
|
||||
['value' => $this->entity->uuid()],
|
||||
],
|
||||
'vid' => [
|
||||
['value' => 1],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'type' => [
|
||||
[
|
||||
'target_id' => 'camelids',
|
||||
'target_type' => 'node_type',
|
||||
'target_uuid' => NodeType::load('camelids')->uuid(),
|
||||
],
|
||||
],
|
||||
'title' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
],
|
||||
],
|
||||
'status' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'promote' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'sticky' => [
|
||||
[
|
||||
'value' => '0',
|
||||
],
|
||||
],
|
||||
'revision_timestamp' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'revision_translation_affected' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'uid' => [
|
||||
[
|
||||
'target_id' => $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => base_path() . 'user/' . $author->id(),
|
||||
],
|
||||
],
|
||||
'revision_uid' => [
|
||||
[
|
||||
'target_id' => $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => base_path() . 'user/' . $author->id(),
|
||||
],
|
||||
],
|
||||
'revision_log' => [
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'type' => [
|
||||
[
|
||||
'target_id' => 'camelids',
|
||||
],
|
||||
],
|
||||
'title' => [
|
||||
[
|
||||
'value' => 'Dramallama',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonAnonTest extends RoleResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonBasicAuthTest extends RoleResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
|
||||
$this->assertSame(401, $response->getStatusCode());
|
||||
$this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string) $response->getBody());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class RoleJsonCookieTest extends RoleResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
|
||||
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
abstract class RoleResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'user_role';
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\RoleInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer permissions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$role = Role::create([
|
||||
'id' => 'llama',
|
||||
'name' => $this->randomString(),
|
||||
]);
|
||||
$role->save();
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'weight' => 2,
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [],
|
||||
'id' => 'llama',
|
||||
'label' => NULL,
|
||||
'is_admin' => NULL,
|
||||
'permissions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class TermJsonAnonTest extends TermResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class TermJsonBasicAuthTest extends TermResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class TermJsonCookieTest extends TermResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
|
||||
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
abstract class TermResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['taxonomy'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'taxonomy_term';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'changed',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\taxonomy\TermInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['access content']);
|
||||
break;
|
||||
case 'POST':
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
// @todo Update once https://www.drupal.org/node/2824408 lands.
|
||||
$this->grantPermissionsToTestedRole(['administer taxonomy']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$vocabulary = Vocabulary::load('camelids');
|
||||
if (!$vocabulary) {
|
||||
// Create a "Camelids" vocabulary.
|
||||
$vocabulary = Vocabulary::create([
|
||||
'name' => 'Camelids',
|
||||
'vid' => 'camelids',
|
||||
]);
|
||||
$vocabulary->save();
|
||||
}
|
||||
|
||||
// Create a "Llama" taxonomy term.
|
||||
$term = Term::create(['vid' => $vocabulary->id()])
|
||||
->setName('Llama')
|
||||
->setChangedTime(123456789);
|
||||
$term->save();
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'tid' => [
|
||||
['value' => 1],
|
||||
],
|
||||
'uuid' => [
|
||||
['value' => $this->entity->uuid()],
|
||||
],
|
||||
'vid' => [
|
||||
[
|
||||
'target_id' => 'camelids',
|
||||
'target_type' => 'taxonomy_vocabulary',
|
||||
'target_uuid' => Vocabulary::load('camelids')->uuid(),
|
||||
],
|
||||
],
|
||||
'name' => [
|
||||
['value' => 'Llama'],
|
||||
],
|
||||
'description' => [
|
||||
[
|
||||
'value' => NULL,
|
||||
'format' => NULL,
|
||||
],
|
||||
],
|
||||
'parent' => [],
|
||||
'weight' => [
|
||||
['value' => 0],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'vid' => [
|
||||
[
|
||||
'target_id' => 'camelids',
|
||||
],
|
||||
],
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Dramallama',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\User;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserJsonAnonTest extends UserResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\User;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserJsonBasicAuthTest extends UserResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\User;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class UserJsonCookieTest extends UserResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\User;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
|
||||
abstract class UserResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'user';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'changed',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $labelFieldName = 'name';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $firstCreatedEntityId = 4;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $secondCreatedEntityId = 5;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['access user profiles']);
|
||||
break;
|
||||
case 'POST':
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['administer users']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "Llama" user.
|
||||
$user = User::create(['created' => 123456789]);
|
||||
$user->setUsername('Llama')
|
||||
->setChangedTime(123456789)
|
||||
->activate()
|
||||
->save();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'uid' => [
|
||||
['value' => '3'],
|
||||
],
|
||||
'uuid' => [
|
||||
['value' => $this->entity->uuid()],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
],
|
||||
],
|
||||
'created' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => '123456789',
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'name' => [
|
||||
[
|
||||
'value' => 'Dramallama ' . $this->randomMachineName(),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests PATCHing security-sensitive base fields of the logged in account.
|
||||
*/
|
||||
public function testPatchDxForSecuritySensitiveBaseFields() {
|
||||
// The anonymous user is never allowed to modify itself.
|
||||
if (!static::$auth) {
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
$this->initAuthentication();
|
||||
$this->provisionEntityResource();
|
||||
$this->setUpAuthorization('PATCH');
|
||||
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = static::$auth ? $this->account : User::load(0);
|
||||
$original_normalization = array_diff_key($this->serializer->normalize($user, static::$format), ['changed' => TRUE]);
|
||||
|
||||
|
||||
// Since this test must be performed by the user that is being modified,
|
||||
// we cannot use $this->getUrl().
|
||||
$url = $user->toUrl()->setOption('query', ['_format' => static::$format]);
|
||||
$request_options = [
|
||||
RequestOptions::HEADERS => ['Content-Type' => static::$mimeType],
|
||||
];
|
||||
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
|
||||
|
||||
|
||||
// Test case 1: changing email.
|
||||
$normalization = $original_normalization;
|
||||
$normalization['mail'] = [['value' => 'new-email@example.com']];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
|
||||
// DX: 422 when changing email without providing the password.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
// @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813755 lands.
|
||||
// $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n", $response);
|
||||
$this->assertSame(422, $response->getStatusCode());
|
||||
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
|
||||
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n"], static::$format), (string) $response->getBody());
|
||||
|
||||
|
||||
$normalization['pass'] = [['existing' => 'wrong']];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
// DX: 422 when changing email while providing a wrong password.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
// @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813755 lands.
|
||||
// $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n", $response);
|
||||
$this->assertSame(422, $response->getStatusCode());
|
||||
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
|
||||
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n"], static::$format), (string) $response->getBody());
|
||||
|
||||
|
||||
$normalization['pass'] = [['existing' => $this->account->passRaw]];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
|
||||
// 200 for well-formed request.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceResponse(200, FALSE, $response);
|
||||
|
||||
|
||||
// Test case 2: changing password.
|
||||
$normalization = $original_normalization;
|
||||
$new_password = $this->randomString();
|
||||
$normalization['pass'] = [['value' => $new_password]];
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
|
||||
// DX: 422 when changing password without providing the current password.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
// @todo use this commented line instead of the 3 lines thereafter once https://www.drupal.org/node/2813755 lands.
|
||||
// $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n", $response);
|
||||
$this->assertSame(422, $response->getStatusCode());
|
||||
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
|
||||
$this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n"], static::$format), (string) $response->getBody());
|
||||
|
||||
|
||||
$normalization['pass'][0]['existing'] = $this->account->pass_raw;
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
|
||||
|
||||
|
||||
// 200 for well-formed request.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertResourceResponse(200, FALSE, $response);
|
||||
|
||||
|
||||
// Verify that we can log in with the new password.
|
||||
$request_body = [
|
||||
'name' => $user->getAccountName(),
|
||||
'pass' => $new_password,
|
||||
];
|
||||
$request_options = [
|
||||
RequestOptions::HEADERS => [],
|
||||
RequestOptions::BODY => $this->serializer->encode($request_body, 'json'),
|
||||
];
|
||||
$response = $this->httpClient->request('POST', Url::fromRoute('user.login.http')->setRouteParameter('_format', 'json')->toString(), $request_options);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Vocabulary;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class VocabularyJsonAnonTest extends VocabularyResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* Disable the GET test coverage due to bug in taxonomy module.
|
||||
* @todo Fix in https://www.drupal.org/node/2805281: remove this override.
|
||||
*/
|
||||
public function testGet() {
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Vocabulary;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class VocabularyJsonBasicAuthTest extends VocabularyResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
|
||||
use JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Vocabulary;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class VocabularyJsonCookieTest extends VocabularyResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional\EntityResource\Vocabulary;
|
||||
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
abstract class VocabularyResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['taxonomy'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'taxonomy_vocabulary';
|
||||
|
||||
/**
|
||||
* @var \Drupal\taxonomy\VocabularyInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer taxonomy']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$vocabulary = Vocabulary::create([
|
||||
'name' => 'Llama',
|
||||
'vid' => 'llama',
|
||||
]);
|
||||
$vocabulary->save();
|
||||
|
||||
return $vocabulary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'uuid' => $this->entity->uuid(),
|
||||
'vid' => 'llama',
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'dependencies' => [],
|
||||
'name' => 'Llama',
|
||||
'description' => NULL,
|
||||
'hierarchy' => 0,
|
||||
'weight' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
trait JsonBasicAuthWorkaroundFor2805281Trait {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Note that strange 'A fatal error occurred: ' prefix, that should not exist.
|
||||
*
|
||||
* @todo Fix in https://www.drupal.org/node/2805281: remove this trait.
|
||||
*/
|
||||
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
|
||||
$this->assertSame(401, $response->getStatusCode());
|
||||
$this->assertSame([static::$expectedErrorMimeType], $response->getHeader('Content-Type'));
|
||||
// Note that strange 'A fatal error occurred: ' prefix, that should not
|
||||
// exist.
|
||||
// @todo Fix in https://www.drupal.org/node/2805281.
|
||||
$this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string) $response->getBody());
|
||||
}
|
||||
|
||||
}
|
||||
349
core/modules/rest/tests/src/Functional/ResourceTestBase.php
Normal file
349
core/modules/rest/tests/src/Functional/ResourceTestBase.php
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Subclass this for every REST resource, every format and every auth provider.
|
||||
*
|
||||
* For more guidance see
|
||||
* \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase
|
||||
* which has recommendations for testing the
|
||||
* \Drupal\rest\Plugin\rest\resource\EntityResource REST resource for every
|
||||
* format and every auth provider. It's a special case (because that single REST
|
||||
* resource generates supports not just one thing, but many things — multiple
|
||||
* entity types), but the same principles apply.
|
||||
*/
|
||||
abstract class ResourceTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* The format to use in this test.
|
||||
*
|
||||
* A format is the combination of a certain normalizer and a certain
|
||||
* serializer.
|
||||
*
|
||||
* @see https://www.drupal.org/developing/api/8/serialization
|
||||
*
|
||||
* (The default is 'json' because that doesn't depend on any module.)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* The MIME type that corresponds to $format.
|
||||
*
|
||||
* (Sadly this cannot be computed automatically yet.)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* The expected MIME type in case of 4xx error responses.
|
||||
*
|
||||
* (Can be different, when $mimeType for example encodes a particular
|
||||
* normalization, such as 'application/hal+json': its error response MIME
|
||||
* type is 'application/json'.)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $expectedErrorMimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* The authentication mechanism to use in this test.
|
||||
*
|
||||
* (The default is 'cookie' because that doesn't depend on any module.)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $auth = FALSE;
|
||||
|
||||
/**
|
||||
* The account to use for authentication, if any.
|
||||
*
|
||||
* @var null|\Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account = NULL;
|
||||
|
||||
/**
|
||||
* The REST resource config entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $resourceConfigStorage;
|
||||
|
||||
/**
|
||||
* The serializer service.
|
||||
*
|
||||
* @var \Symfony\Component\Serializer\Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['rest'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Ensure the anonymous user role has no permissions at all.
|
||||
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
|
||||
foreach ($user_role->getPermissions() as $permission) {
|
||||
$user_role->revokePermission($permission);
|
||||
}
|
||||
$user_role->save();
|
||||
assert('[] === $user_role->getPermissions()', 'The anonymous user role has no permissions at all.');
|
||||
|
||||
if (static::$auth !== FALSE) {
|
||||
// Ensure the authenticated user role has no permissions at all.
|
||||
$user_role = Role::load(RoleInterface::AUTHENTICATED_ID);
|
||||
foreach ($user_role->getPermissions() as $permission) {
|
||||
$user_role->revokePermission($permission);
|
||||
}
|
||||
$user_role->save();
|
||||
assert('[] === $user_role->getPermissions()', 'The authenticated user role has no permissions at all.');
|
||||
|
||||
// Create an account.
|
||||
$this->account = $this->createUser();
|
||||
}
|
||||
else {
|
||||
// Otherwise, also create an account, so that any test involving User
|
||||
// entities will have the same user IDs regardless of authentication.
|
||||
$this->createUser();
|
||||
}
|
||||
|
||||
$this->resourceConfigStorage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
|
||||
|
||||
// Ensure there's a clean slate: delete all REST resource config entities.
|
||||
$this->resourceConfigStorage->delete($this->resourceConfigStorage->loadMultiple());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provisions a REST resource.
|
||||
*
|
||||
* @param string $resource_type
|
||||
* The resource type (REST resource plugin ID).
|
||||
* @param string[] $formats
|
||||
* The allowed formats for this resource.
|
||||
* @param string[] $authentication
|
||||
* The allowed authentication providers for this resource.
|
||||
*/
|
||||
protected function provisionResource($resource_type, $formats = [], $authentication = []) {
|
||||
$this->resourceConfigStorage->create([
|
||||
'id' => $resource_type,
|
||||
'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
|
||||
'configuration' => [
|
||||
'methods' => ['GET', 'POST', 'PATCH', 'DELETE'],
|
||||
'formats' => $formats,
|
||||
'authentication' => $authentication,
|
||||
]
|
||||
])->save();
|
||||
// @todo Remove this in https://www.drupal.org/node/2815845.
|
||||
drupal_flush_all_caches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the necessary authorization.
|
||||
*
|
||||
* In case of a test verifying publicly accessible REST resources: grant
|
||||
* permissions to the anonymous user role.
|
||||
*
|
||||
* In case of a test verifying behavior when using a particular authentication
|
||||
* provider: create a user with a particular set of permissions.
|
||||
*
|
||||
* Because of the $method parameter, it's possible to first set up
|
||||
* authentication for only GET, then add POST, et cetera. This then also
|
||||
* allows for verifying a 403 in case of missing authorization.
|
||||
*
|
||||
* @param string $method
|
||||
* The HTTP method for which to set up authentication.
|
||||
*
|
||||
* @see ::grantPermissionsToAnonymousRole()
|
||||
* @see ::grantPermissionsToAuthenticatedRole()
|
||||
*/
|
||||
abstract protected function setUpAuthorization($method);
|
||||
|
||||
/**
|
||||
* Verifies the error response in case of missing authentication.
|
||||
*/
|
||||
abstract protected function assertResponseWhenMissingAuthentication(ResponseInterface $response);
|
||||
|
||||
/**
|
||||
* Asserts normalization-specific edge cases.
|
||||
*
|
||||
* (Should be called before sending a well-formed request.)
|
||||
*
|
||||
* @see \GuzzleHttp\ClientInterface::request()
|
||||
*
|
||||
* @param string $method
|
||||
* HTTP method.
|
||||
* @param \Drupal\Core\Url $url
|
||||
* URL to request.
|
||||
* @param array $request_options
|
||||
* Request options to apply.
|
||||
*/
|
||||
abstract protected function assertNormalizationEdgeCases($method, Url $url, array $request_options);
|
||||
|
||||
/**
|
||||
* Asserts authentication provider-specific edge cases.
|
||||
*
|
||||
* (Should be called before sending a well-formed request.)
|
||||
*
|
||||
* @see \GuzzleHttp\ClientInterface::request()
|
||||
*
|
||||
* @param string $method
|
||||
* HTTP method.
|
||||
* @param \Drupal\Core\Url $url
|
||||
* URL to request.
|
||||
* @param array $request_options
|
||||
* Request options to apply.
|
||||
*/
|
||||
abstract protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options);
|
||||
|
||||
/**
|
||||
* Initializes authentication.
|
||||
*
|
||||
* E.g. for cookie authentication, we first need to get a cookie.
|
||||
*/
|
||||
protected function initAuthentication() {}
|
||||
|
||||
/**
|
||||
* Returns Guzzle request options for authentication.
|
||||
*
|
||||
* @param string $method
|
||||
* The HTTP method for this authenticated request.
|
||||
*
|
||||
* @return array
|
||||
* Guzzle request options to use for authentication.
|
||||
*
|
||||
* @see \GuzzleHttp\ClientInterface::request()
|
||||
*/
|
||||
protected function getAuthenticationRequestOptions($method) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants permissions to the anonymous role.
|
||||
*
|
||||
* @param string[] $permissions
|
||||
* Permissions to grant.
|
||||
*/
|
||||
protected function grantPermissionsToAnonymousRole(array $permissions) {
|
||||
$this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), $permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants permissions to the authenticated role.
|
||||
*
|
||||
* @param string[] $permissions
|
||||
* Permissions to grant.
|
||||
*/
|
||||
protected function grantPermissionsToAuthenticatedRole(array $permissions) {
|
||||
$this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), $permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants permissions to the tested role: anonymous or authenticated.
|
||||
*
|
||||
* @param string[] $permissions
|
||||
* Permissions to grant.
|
||||
*
|
||||
* @see ::grantPermissionsToAuthenticatedRole()
|
||||
* @see ::grantPermissionsToAnonymousRole()
|
||||
*/
|
||||
protected function grantPermissionsToTestedRole(array $permissions) {
|
||||
if (static::$auth) {
|
||||
$this->grantPermissionsToAuthenticatedRole($permissions);
|
||||
}
|
||||
else {
|
||||
$this->grantPermissionsToAnonymousRole($permissions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a HTTP request. Wraps the Guzzle HTTP client.
|
||||
*
|
||||
* Why wrap the Guzzle HTTP client? Because we want to keep the actual test
|
||||
* code as simple as possible, and hence not require them to specify the
|
||||
* 'http_errors = FALSE' request option, nor do we want them to have to
|
||||
* convert Drupal Url objects to strings.
|
||||
*
|
||||
* @see \GuzzleHttp\ClientInterface::request()
|
||||
*
|
||||
* @param string $method
|
||||
* HTTP method.
|
||||
* @param \Drupal\Core\Url $url
|
||||
* URL to request.
|
||||
* @param array $request_options
|
||||
* Request options to apply.
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
protected function request($method, Url $url, array $request_options) {
|
||||
$request_options[RequestOptions::HTTP_ERRORS] = FALSE;
|
||||
return $this->httpClient->request($method, $url->toString(), $request_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a resource response has the given status code and body.
|
||||
*
|
||||
* (Also asserts that the expected error MIME type is present, but this is
|
||||
* defined globally for the test via static::$expectedErrorMimeType, because
|
||||
* all error responses should use the same MIME type.)
|
||||
*
|
||||
* @param int $expected_status_code
|
||||
* The expected response status.
|
||||
* @param string|false $expected_body
|
||||
* The expected response body. FALSE in case this should not be asserted.
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* The response to assert.
|
||||
*/
|
||||
protected function assertResourceResponse($expected_status_code, $expected_body, ResponseInterface $response) {
|
||||
$this->assertSame($expected_status_code, $response->getStatusCode());
|
||||
if ($expected_status_code < 400) {
|
||||
$this->assertSame([static::$mimeType], $response->getHeader('Content-Type'));
|
||||
}
|
||||
else {
|
||||
$this->assertSame([static::$expectedErrorMimeType], $response->getHeader('Content-Type'));
|
||||
}
|
||||
if ($expected_body !== FALSE) {
|
||||
$this->assertSame($expected_body, (string) $response->getBody());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a resource error response has the given message.
|
||||
*
|
||||
* (Also asserts that the expected error MIME type is present, but this is
|
||||
* defined globally for the test via static::$expectedErrorMimeType, because
|
||||
* all error responses should use the same MIME type.)
|
||||
*
|
||||
* @param int $expected_status_code
|
||||
* The expected response status.
|
||||
* @param string $expected_message
|
||||
* The expected error message.
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* The error response to assert.
|
||||
*/
|
||||
protected function assertResourceErrorResponse($expected_status_code, $expected_message, ResponseInterface $response) {
|
||||
// @todo Fix this in https://www.drupal.org/node/2813755.
|
||||
$encode_options = ['json_encode_options' => JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT];
|
||||
$expected_body = $this->serializer->encode(['message' => $expected_message], static::$format, $encode_options);
|
||||
$this->assertResourceResponse($expected_status_code, $expected_body, $response);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\rest\Unit\Entity;
|
||||
|
||||
use Drupal\rest\Entity\RestResourceConfig;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\rest\Entity\RestResourceConfig
|
||||
*
|
||||
* @group rest
|
||||
*/
|
||||
class RestResourceConfigTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Asserts that rest methods are normalized to upper case.
|
||||
*
|
||||
* This also tests that no exceptions are thrown during that method so that
|
||||
* alternate methods such as OPTIONS and PUT are supported.
|
||||
*/
|
||||
public function testNormalizeRestMethod() {
|
||||
$expected = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'OPTIONS', 'FOO'];
|
||||
$methods = ['get', 'put', 'post', 'patch', 'delete', 'options', 'foo'];
|
||||
$configuration = [];
|
||||
foreach ($methods as $method) {
|
||||
$configuration[$method] = [
|
||||
'supported_auth' => ['cookie'],
|
||||
'supported_formats' => ['json'],
|
||||
];
|
||||
}
|
||||
|
||||
$entity = new RestResourceConfig([
|
||||
'plugin_id' => 'entity:entity_test',
|
||||
'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
|
||||
'configuration' => $configuration,
|
||||
], 'rest_resource_config');
|
||||
|
||||
$this->assertArrayEquals($expected, $entity->getMethods());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in a new issue