Issue #2451397 by Jaesin, damiankloip, alexpott, dawehner: Entity denormalization fails to retrieve bundle
parent
6586221c0c
commit
fe81cfac2a
|
@ -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 @@ class NodeTest extends RESTTestBase {
|
|||
$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 @@ class NodeTest extends RESTTestBase {
|
|||
// 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 @@ abstract class RESTTestBase extends WebTestBase {
|
|||
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 @@ abstract class RESTTestBase extends WebTestBase {
|
|||
$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 @@ abstract class RESTTestBase extends WebTestBase {
|
|||
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 @@ class EntityNormalizer extends ComplexDataNormalizer implements DenormalizerInte
|
|||
/**
|
||||
* {@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);
|
||||
|
||||
// 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;
|
||||
// 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() : [];
|
||||
|
||||
// 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 @@ class EntityNormalizerTest extends UnitTestCase {
|
|||
*
|
||||
* @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 @@ class EntityNormalizerTest extends UnitTestCase {
|
|||
->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 @@ class EntityNormalizerTest extends UnitTestCase {
|
|||
->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 @@ class EntityNormalizerTest extends UnitTestCase {
|
|||
->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')));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue