Commit fe81cfac authored by alexpott's avatar alexpott

Issue #2451397 by Jaesin, damiankloip, alexpott, dawehner: Entity...

Issue #2451397 by Jaesin, damiankloip, alexpott, dawehner: Entity denormalization fails to retrieve bundle
parent 6586221c
......@@ -8,7 +8,7 @@
namespace Drupal\rest\Tests;
use Drupal\Core\Url;
use Drupal\rest\Tests\RESTTestBase;
use Drupal\node\Entity\Node;
/**
* Tests special cases for node entities.
......@@ -42,6 +42,40 @@ protected function enableNodeConfiguration($method, $operation) {
$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.
*/
......@@ -90,4 +124,79 @@ public function testNodes() {
// 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."}');
}
}
......@@ -7,11 +7,9 @@
namespace Drupal\rest\Tests;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\node\NodeInterface;
use Drupal\simpletest\WebTestBase;
use Drupal\user\UserInterface;
/**
* Test helper class that provides a REST client method to send HTTP requests.
......@@ -46,6 +44,14 @@ abstract class RESTTestBase extends WebTestBase {
*/
protected $defaultAuth;
/**
* The raw response body from http request operations.
*
* @var array
*/
protected $responseBody;
/**
* Modules to install.
*
......@@ -153,7 +159,7 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
break;
}
$response = $this->curlExec($curl_options);
$this->responseBody = $this->curlExec($curl_options);
// Ensure that any changes to variables in the other thread are picked up.
$this->refreshVariables();
......@@ -163,9 +169,9 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
$this->verbose($method . ' request to: ' . $url .
'<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
'<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
'<hr />Response body: ' . $response);
'<hr />Response body: ' . $this->responseBody);
return $response;
return $this->responseBody;
}
/**
......@@ -388,4 +394,27 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
return $node;
}
/**
* Check to see if the HTTP request response body is identical to the expected
* value.
*
* @param $expected
* The first value to check.
* @param $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return bool
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertResponseBody($expected, $message = '', $group = 'REST Response') {
return $this->assertIdentical($expected, $this->responseBody, $message ? $message : strtr('Response body @expected (expected) is equal to @response (actual).', array('@expected' => var_export($expected, TRUE), '@response' => var_export($this->responseBody, TRUE))), $group);
}
}
......@@ -43,22 +43,54 @@ public function __construct(EntityManagerInterface $entity_manager) {
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = array()) {
if (empty($context['entity_type'])) {
throw new UnexpectedValueException('Entity type parameter must be included in context.');
public function denormalize($data, $class, $format = NULL, array $context = []) {
// Get the entity type ID while letting context override the $class param.
$entity_type_id = !empty($context['entity_type']) ? $context['entity_type'] : $this->entityManager->getEntityTypeFromClass($class);
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type_definition */
// Get the entity type definition.
$entity_type_definition = $this->entityManager->getDefinition($entity_type_id, FALSE);
// Don't try to create an entity without an entity type id.
if (!$entity_type_definition) {
throw new UnexpectedValueException(sprintf('The specified entity type "%s" does not exist. A valid etnity type is required for denormalization', $entity_type_id));
}
$entity_type = $this->entityManager->getDefinition($context['entity_type']);
// The bundle property will be required to denormalize a bundleable entity.
if ($entity_type_definition->hasKey('bundle')) {
$bundle_key = $entity_type_definition->getKey('bundle');
// Get the base field definitions for this entity type.
$base_field_definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id);
// Get the ID key from the base field definition for the bundle key or
// default to 'value'.
$key_id = isset($base_field_definitions[$bundle_key]) ? $base_field_definitions[$bundle_key]->getFieldStorageDefinition()->getMainPropertyName() : 'value';
// Normalize the bundle if it is not explicitly set.
$data[$bundle_key] = isset($data[$bundle_key][0][$key_id]) ? $data[$bundle_key][0][$key_id] : (isset($data[$bundle_key]) ? $data[$bundle_key] : NULL);
// Get the bundle entity type from the entity type definition.
$bundle_type_id = $entity_type_definition->getBundleEntityType();
$bundle_types = $bundle_type_id ? $this->entityManager->getStorage($bundle_type_id)->getQuery()->execute() : [];
// The bundle property behaves differently from other entity properties.
// i.e. the nested structure with a 'value' key does not work.
if ($entity_type->hasKey('bundle')) {
$bundle_key = $entity_type->getKey('bundle');
$type = $data[$bundle_key][0]['value'];
$data[$bundle_key] = $type;
// Make sure a bundle has been provided.
if (!is_string($data[$bundle_key])) {
throw new UnexpectedValueException('A string must be provided as a bundle value.');
}
// Make sure the submitted bundle is a valid bundle for the entity type.
if ($bundle_types && !in_array($data[$bundle_key], $bundle_types)) {
throw new UnexpectedValueException(sprintf('"%s" is not a valid bundle type for denormalization.', $data[$bundle_key]));
}
}
return $this->entityManager->getStorage($context['entity_type'])->create($data);
}
// Create the entity from data.
$entity = $this->entityManager->getStorage($entity_type_id)->create($data);
// Pass the names of the fields whose values can be merged.
// @todo https://www.drupal.org/node/2456257 remove this.
$entity->_restSubmittedFields = array_keys($data);
return $entity;
}
}
......@@ -99,14 +99,14 @@ public function testDenormalizeWithNoEntityType() {
*
* @covers ::denormalize
*/
public function testDenormalizeWithBundle() {
$test_data = array(
public function testDenormalizeWithValidBundle() {
$test_data = [
'key_1' => 'value_1',
'key_2' => 'value_2',
'test_type' => array(
array('value' => 'test_bundle'),
),
);
'test_type' => [
['name' => 'test_bundle'],
],
];
$entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$entity_type->expects($this->once())
......@@ -117,11 +117,47 @@ public function testDenormalizeWithBundle() {
->method('getKey')
->with('bundle')
->will($this->returnValue('test_type'));
$entity_type->expects($this->once())
->method('getBundleEntityType')
->will($this->returnValue('test_bundle'));
$this->entityManager->expects($this->once())
$entity_type_storage_definition = $this->getmock('Drupal\Core\Field\FieldStorageDefinitionInterface');
$entity_type_storage_definition->expects($this->once())
->method('getMainPropertyName')
->will($this->returnValue('name'));
$entity_type_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
$entity_type_definition->expects($this->once())
->method('getFieldStorageDefinition')
->will($this->returnValue($entity_type_storage_definition));
$base_definitions = [
'test_type' => $entity_type_definition,
];
$this->entityManager->expects($this->at(0))
->method('getDefinition')
->with('test')
->will($this->returnValue($entity_type));
$this->entityManager->expects($this->at(1))
->method('getBaseFieldDefinitions')
->with('test')
->will($this->returnValue($base_definitions));
$entity_query_mock = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
$entity_query_mock->expects($this->once())
->method('execute')
->will($this->returnValue(['test_bundle' => 'test_bundle']));
$entity_type_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
$entity_type_storage->expects($this->once())
->method('getQuery')
->will($this->returnValue($entity_query_mock));
$this->entityManager->expects($this->at(2))
->method('getStorage')
->with('test_bundle')
->will($this->returnValue($entity_type_storage));
// The expected test data should have a modified test_type property.
$expected_test_data = array(
......@@ -136,12 +172,83 @@ public function testDenormalizeWithBundle() {
->with($expected_test_data)
->will($this->returnValue($this->getMock('Drupal\Core\Entity\EntityInterface')));
$this->entityManager->expects($this->once())
$this->entityManager->expects($this->at(3))
->method('getStorage')
->with('test')
->will($this->returnValue($storage));
$this->assertNotNull($this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, array('entity_type' => 'test')));
$this->assertNotNull($this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test']));
}
/**
* Tests the denormalize method with a bundle property.
*
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
*
* @covers ::denormalize
*/
public function testDenormalizeWithInvalidBundle() {
$test_data = [
'key_1' => 'value_1',
'key_2' => 'value_2',
'test_type' => [
['name' => 'test_bundle'],
],
];
$entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$entity_type->expects($this->once())
->method('hasKey')
->with('bundle')
->will($this->returnValue(TRUE));
$entity_type->expects($this->once())
->method('getKey')
->with('bundle')
->will($this->returnValue('test_type'));
$entity_type->expects($this->once())
->method('getBundleEntityType')
->will($this->returnValue('test_bundle'));
$entity_type_storage_definition = $this->getmock('Drupal\Core\Field\FieldStorageDefinitionInterface');
$entity_type_storage_definition->expects($this->once())
->method('getMainPropertyName')
->will($this->returnValue('name'));
$entity_type_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
$entity_type_definition->expects($this->once())
->method('getFieldStorageDefinition')
->will($this->returnValue($entity_type_storage_definition));
$base_definitions = [
'test_type' => $entity_type_definition,
];
$this->entityManager->expects($this->at(0))
->method('getDefinition')
->with('test')
->will($this->returnValue($entity_type));
$this->entityManager->expects($this->at(1))
->method('getBaseFieldDefinitions')
->with('test')
->will($this->returnValue($base_definitions));
$entity_query_mock = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
$entity_query_mock->expects($this->once())
->method('execute')
->will($this->returnValue(['test_bundle_other' => 'test_bundle_other']));
$entity_type_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
$entity_type_storage->expects($this->once())
->method('getQuery')
->will($this->returnValue($entity_query_mock));
$this->entityManager->expects($this->at(2))
->method('getStorage')
->with('test_bundle')
->will($this->returnValue($entity_type_storage));
$this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test']);
}
/**
......@@ -179,6 +286,9 @@ public function testDenormalizeWithNoBundle() {
->with('test')
->will($this->returnValue($storage));
$this->entityManager->expects($this->never())
->method('getBaseFieldDefinitions');
$this->assertNotNull($this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, array('entity_type' => 'test')));
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment