diff --git a/core/modules/datetime_range/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php b/core/modules/datetime_range/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php index 5bb4213..e05895b 100644 --- a/core/modules/datetime_range/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php +++ b/core/modules/datetime_range/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Url; use Drupal\entity_test\Entity\EntityTest; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -18,4 +19,191 @@ */ class EntityTestDatetimeTest extends EntityTestResourceTestBase { + use AnonResourceTestTrait; + + /** + * The starting ISO date string to use throughout the test. + * + * @var string + */ + protected static $startDateString = '2017-03-01T20:02:00'; + + /** + * The ending ISO date string to use throughout the test. + * + * @var string + */ + protected static $endDateString = '2017-03-02T20:02:00'; + + /** + * Datetime test field name. + * + * @var string + */ + protected static $fieldName = 'field_daterange'; + + /** + * {@inheritdoc} + */ + public static $modules = ['datetime', 'datetime_range', 'entity_test']; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + // Add daterange field. + FieldStorageConfig::create([ + 'field_name' => static::$fieldName, + 'type' => 'daterange', + 'entity_type' => static::$entityTypeId, + 'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATETIME], + ]) + ->save(); + + FieldConfig::create([ + 'field_name' => static::$fieldName, + 'entity_type' => static::$entityTypeId, + 'bundle' => $this->entity->bundle(), +// 'settings' => ['default_value' => static::$startDateString], + ]) + ->save(); + + // Reload entity so that it has the new field. + $this->entity = $this->entityStorage->load($this->entity->id()); + $this->entity->set(static::$fieldName, [ + 'value' => static::$startDateString, + 'end_value' => static::$endDateString, + ]); + $this->entity->save(); + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + $entity_test = EntityTest::create([ + 'name' => 'Llama', + 'type' => static::$entityTypeId, + static::$fieldName => [ + 'value' => static::$startDateString, + 'end_value' => static::$endDateString, + ], + ]); + $entity_test->setOwnerId(0); + $entity_test->save(); + + return $entity_test; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedNormalizedEntity() { + return parent::getExpectedNormalizedEntity() + [ + static::$fieldName => [ + [ + 'value' => $this->entity->get(static::$fieldName)->value, + 'end_value' => $this->entity->get(static::$fieldName)->end_value, + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + return parent::getNormalizedPostEntity() + [ + static::$fieldName => [ + [ + 'value' => static::$startDateString, + 'end_value' => static::$endDateString, + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) { + parent::assertNormalizationEdgeCases($method, $url, $request_options); + + if ($this->entity->getEntityType()->hasKey('bundle')) { + $fieldName = static::$fieldName; + $fieldFormat = DateTimeItemInterface::DATETIME_STORAGE_FORMAT; + + // DX: 422 when date type is incorrect. + + // Errors? + $value = ['2017', '03', '01', '21', '53', '00']; + $end_value = static::$endDateString; + $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The daterange start value must be a string.\n{$fieldName}.0: The daterange end value must be a string.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n"; + $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message); + + // Works! + $value = static::$startDateString; + $end_value = ['2017', '03', '02', '21', '53', '00']; + $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The daterange end value must be a string.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n"; + $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message); + + // Works?? + $value = ['2017', '03', '01', '21', '53', '00']; + $end_value = ['2017', '03', '02', '21', '53', '00']; + $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The daterange start value must be a string.\n{$fieldName}.0: The daterange end value must be a string.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n"; + $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message); + + // @todo Adjust the stuff below... + return; + + // DX: 422 when date format is incorrect. + $normalization = $this->getNormalizedPostEntity(); + $value = '2017-03-01'; + $normalization[static::$fieldName][0]['value'] = $value; + + $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format); + $response = $this->request($method, $url, $request_options); + $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The daterange start value '{$value}' is invalid for the format '{$fieldFormat}'\n"; + $this->assertResourceErrorResponse(422, $message, $response); + + // DX: 422 when date format is incorrect. + $normalization = $this->getNormalizedPostEntity(); + $value = '2017-13-55T20:02:00'; + $normalization[static::$fieldName][0]['value'] = $value; + + $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format); + $response = $this->request($method, $url, $request_options); + $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The daterange start value '{$value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n"; + $this->assertResourceErrorResponse(422, $message, $response); + } + } + + /** + * Performs a REST call to test an edge case. + * + * @param string $method + * HTTP method. + * @param \Drupal\Core\Url $url + * URL to request. + * @param array $request_options + * Request options to apply. + * @var mixed $value + * Foo. + * @var mixed $end_value + * Foo. + * @var string $message + * Foo. + */ + protected function doEdgeCaseCall($method, Url $url, array $request_options, $value, $end_value, $message) { + $normalization = $this->getNormalizedPostEntity(); + $normalization[static::$fieldName][0]['value'] = $value; + $normalization[static::$fieldName][0]['end_value'] = $end_value; + + $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format); + $response = $this->request($method, $url, $request_options); + $this->assertResourceErrorResponse(422, $message, $response); + } + } diff --git a/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraintValidator.php b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraintValidator.php index aec6c1b..752a7d6 100644 --- a/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraintValidator.php +++ b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraintValidator.php @@ -17,69 +17,77 @@ class DateRangeFormatConstraintValidator extends ConstraintValidator { * {@inheritdoc} */ public function validate($item, Constraint $constraint) { + $stop = FALSE; /* @var $item \Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem */ if (isset($item)) { $value = $item->getValue()['value']; $end_value = $item->getValue()['end_value']; + if (!is_string($value)) { $this->context->addViolation($constraint->badStartType); + $stop = TRUE; } - elseif (!is_string($end_value)) { + if (!is_string($end_value)) { $this->context->addViolation($constraint->badEndType); + $stop = TRUE; } - else { - $datetime_type = $item->getFieldDefinition()->getSetting('datetime_type'); - $format = $datetime_type === DateRangeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT; - $start_date = NULL; - try { - $start_date = DateTimePlus::createFromFormat($format, $value, new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE)); - } - catch (\InvalidArgumentException $e) { - $this->context->addViolation($constraint->badStartFormat, [ - '@value' => $value, - '@format' => $format, - ]); - return; - } - catch (\UnexpectedValueException $e) { - $this->context->addViolation($constraint->badStartValue, [ - '@value' => $value, - '@format' => $format, - ]); - return; - } - if ($start_date === NULL || $start_date->hasErrors()) { - $this->context->addViolation($constraint->badStartFormat, [ - '@value' => $value, - '@format' => $format, - ]); - } + if ($stop) return; + + // @todo Reorder this w/ $stop... + return; + + $datetime_type = $item->getFieldDefinition()->getSetting('datetime_type'); + $format = $datetime_type === DateRangeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT; - $end_date = NULL; - try { - $end_date = DateTimePlus::createFromFormat($format, $end_value, new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE)); - } - catch (\InvalidArgumentException $e) { - $this->context->addViolation($constraint->badEndFormat, [ - '@end_value' => $end_value, - '@format' => $format, - ]); - return; - } - catch (\UnexpectedValueException $e) { - $this->context->addViolation($constraint->badEndValue, [ - '@end_value' => $end_value, - '@format' => $format, - ]); - return; - } - if ($end_date === NULL || $end_date->hasErrors()) { - $this->context->addViolation($constraint->badEndFormat, [ - '@end_value' => $end_value, - '@format' => $format, - ]); - } + $start_date = NULL; + try { + $start_date = DateTimePlus::createFromFormat($format, $value, new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE)); + } + catch (\InvalidArgumentException $e) { + $this->context->addViolation($constraint->badStartFormat, [ + '@value' => $value, + '@format' => $format, + ]); + return; + } + catch (\UnexpectedValueException $e) { + $this->context->addViolation($constraint->badStartValue, [ + '@value' => $value, + '@format' => $format, + ]); + return; + } + if ($start_date === NULL || $start_date->hasErrors()) { + $this->context->addViolation($constraint->badStartFormat, [ + '@value' => $value, + '@format' => $format, + ]); + } + + $end_date = NULL; + try { + $end_date = DateTimePlus::createFromFormat($format, $end_value, new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE)); + } + catch (\InvalidArgumentException $e) { + $this->context->addViolation($constraint->badEndFormat, [ + '@end_value' => $end_value, + '@format' => $format, + ]); + return; + } + catch (\UnexpectedValueException $e) { + $this->context->addViolation($constraint->badEndValue, [ + '@end_value' => $end_value, + '@format' => $format, + ]); + return; + } + if ($end_date === NULL || $end_date->hasErrors()) { + $this->context->addViolation($constraint->badEndFormat, [ + '@end_value' => $end_value, + '@format' => $format, + ]); } } }