Update to Drupal 8.1.0. For more information, see https://www.drupal.org/drupal-8.1.0-release-notes

This commit is contained in:
Pantheon Automation 2016-04-20 09:56:34 -07:00 committed by Greg Anderson
parent b11a755ba8
commit c0a0d5a94c
6920 changed files with 64395 additions and 57312 deletions

View file

@ -0,0 +1,82 @@
<?php
namespace Drupal\FunctionalJavascriptTests;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\CssSelector\CssSelector;
use Zumba\Mink\Driver\PhantomJSDriver;
/**
* Runs a browser test using PhantomJS.
*
* Base class for testing browser interaction implemented in JavaScript.
*/
abstract class JavascriptTestBase extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected $minkDefaultDriverClass = PhantomJSDriver::class;
/**
* {@inheritdoc}
*/
protected function initMink() {
// Set up the template cache used by the PhantomJS mink driver.
$path = $this->tempFilesDirectory . DIRECTORY_SEPARATOR . 'browsertestbase-templatecache';
$this->minkDefaultDriverArgs = [
'http://127.0.0.1:8510',
$path,
];
if (!file_exists($path)) {
mkdir($path);
}
return parent::initMink();
}
/**
* Asserts that the element with the given CSS selector is visible.
*
* @param string $css_selector
* The CSS selector identifying the element to check.
* @param string $message
* Optional message to show alongside the assertion.
*/
protected function assertElementVisible($css_selector, $message = '') {
$this->assertTrue($this->getSession()->getDriver()->isVisible(CssSelector::toXPath($css_selector)), $message);
}
/**
* Asserts that the element with the given CSS selector is not visible.
*
* @param string $css_selector
* The CSS selector identifying the element to check.
* @param string $message
* Optional message to show alongside the assertion.
*/
protected function assertElementNotVisible($css_selector, $message = '') {
$this->assertFalse($this->getSession()->getDriver()->isVisible(CssSelector::toXPath($css_selector)), $message);
}
/**
* Waits for the given time or until the given JS condition becomes TRUE.
*
* @param string $condition
* JS condition to wait until it becomes TRUE.
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 1000.
* @param string $message
* (optional) A message to display with the assertion. If left blank, a
* default message will be displayed.
*
* @throws \PHPUnit_Framework_AssertionFailedError
*
* @see \Behat\Mink\Driver\DriverInterface::evaluateScript()
*/
protected function assertJsCondition($condition, $timeout = 1000, $message = '') {
$message = $message ?: "Javascript condition met:\n" . $condition;
$result = $this->getSession()->getDriver()->wait($timeout, $condition);
$this->assertTrue($result, $message);
}
}

View file

@ -1,10 +1,5 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\AssertConfigTrait.
*/
namespace Drupal\KernelTests;
use Drupal\Component\Diff\Diff;

View file

@ -1,10 +1,5 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\AssertLegacyTrait.
*/
namespace Drupal\KernelTests;
/**

View file

@ -1,10 +1,5 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Component\Utility\SafeMarkupKernelTest.
*/
namespace Drupal\KernelTests\Component\Utility;
use Drupal\Component\Utility\SafeMarkup;
@ -29,7 +24,6 @@ class SafeMarkupKernelTest extends KernelTestBase {
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'router');
$this->container->get('router.builder')->rebuild();
}

View file

@ -1,10 +1,5 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Config\DefaultConfigTest.
*/
namespace Drupal\KernelTests\Config;
use Drupal\Core\Config\FileStorage;
@ -46,8 +41,6 @@ class DefaultConfigTest extends KernelTestBase {
// drupal_get_filename().
// @todo Remove as part of https://www.drupal.org/node/2186491
system_rebuild_module_data();
$this->installSchema('system', 'router');
}
/**
@ -63,6 +56,11 @@ class DefaultConfigTest extends KernelTestBase {
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
$config_manager = $this->container->get('config.manager');
// @todo https://www.drupal.org/node/2308745 Rest has an implicit dependency
// on the Node module remove once solved.
if (in_array($module, ['rest', 'hal'])) {
$module_installer->install(['node']);
}
$module_installer->install([$module]);
// System and user are required in order to be able to install some of the

View file

@ -1,7 +1,4 @@
<?php
/**
* @file \Drupal\KernelTests\Core\Cache\CacheCollectorTest.
*/
namespace Drupal\KernelTests\Core\Cache;
@ -17,19 +14,6 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class CacheCollectorTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['semaphore']);
}
/**
* {@inheritdoc}
*/

View file

@ -1,10 +1,5 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Core\Common\DrupalSetMessageTest.
*/
namespace Drupal\KernelTests\Core\Common;
use Drupal\KernelTests\KernelTestBase;

View file

@ -0,0 +1,89 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\config_override_test\Cache\PirateDayCacheContext;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests if configuration overrides correctly affect cacheability metadata.
*
* @group config
*/
class CacheabilityMetadataConfigOverrideTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'block',
'block_content',
'config',
'config_override_test',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(['config_override_test']);
}
/**
* Tests if config overrides correctly set cacheability metadata.
*/
public function testConfigOverride() {
// It's pirate day today!
$GLOBALS['it_is_pirate_day'] = TRUE;
$config_factory = $this->container->get('config.factory');
$config = $config_factory->get('system.theme');
// Check that we are using the Pirate theme.
$theme = $config->get('default');
$this->assertEqual('pirate', $theme);
// Check that the cacheability metadata is correct.
$this->assertEqual(['pirate_day'], $config->getCacheContexts());
$this->assertEqual(['config:system.theme', 'pirate-day-tag'], $config->getCacheTags());
$this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $config->getCacheMaxAge());
}
/**
* Tests if config overrides set cacheability metadata on config entities.
*/
public function testConfigEntityOverride() {
// It's pirate day today!
$GLOBALS['it_is_pirate_day'] = TRUE;
// Load the User login block and check that its cacheability metadata is
// overridden correctly. This verifies that the metadata is correctly
// applied to config entities.
/** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
$entity_manager = $this->container->get('entity.manager');
$block = $entity_manager->getStorage('block')->load('call_to_action');
// Check that our call to action message is appealing to filibusters.
$this->assertEqual($block->label(), 'Draw yer cutlasses!');
// Check that the cacheability metadata is correct.
$this->assertEqual(['pirate_day'], $block->getCacheContexts());
$this->assertEqual(['config:block.block.call_to_action', 'pirate-day-tag'], $block->getCacheTags());
$this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $block->getCacheMaxAge());
// Check that duplicating a config entity does not have the original config
// entity's cache tag.
$this->assertEqual(['config:block.block.', 'pirate-day-tag'], $block->createDuplicate()->getCacheTags());
// Check that renaming a config entity does not have the original config
// entity's cache tag.
$block->set('id', 'call_to_looting')->save();
$this->assertEqual(['pirate_day'], $block->getCacheContexts());
$this->assertEqual(['config:block.block.call_to_looting', 'pirate-day-tag'], $block->getCacheTags());
$this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $block->getCacheMaxAge());
}
}

View file

@ -0,0 +1,318 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\ConfigNameException;
use Drupal\Core\Config\ConfigValueException;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Config\DatabaseStorage;
use Drupal\Core\Config\UnsupportedDataTypeConfigException;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests CRUD operations on configuration objects.
*
* @group config
*/
class ConfigCRUDTest extends KernelTestBase {
/**
* Exempt from strict schema checking.
*
* @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
*
* @var bool
*/
protected $strictConfigSchema = FALSE;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/**
* Tests CRUD operations.
*/
function testCRUD() {
$storage = $this->container->get('config.storage');
$config_factory = $this->container->get('config.factory');
$name = 'config_test.crud';
$config = $this->config($name);
$this->assertIdentical($config->isNew(), TRUE);
// Create a new configuration object.
$config->set('value', 'initial');
$config->save();
$this->assertIdentical($config->isNew(), FALSE);
// Verify the active configuration contains the saved value.
$actual_data = $storage->read($name);
$this->assertIdentical($actual_data, array('value' => 'initial'));
// Update the configuration object instance.
$config->set('value', 'instance-update');
$config->save();
$this->assertIdentical($config->isNew(), FALSE);
// Verify the active configuration contains the updated value.
$actual_data = $storage->read($name);
$this->assertIdentical($actual_data, array('value' => 'instance-update'));
// Verify a call to $this->config() immediately returns the updated value.
$new_config = $this->config($name);
$this->assertIdentical($new_config->get(), $config->get());
$this->assertIdentical($config->isNew(), FALSE);
// Pollute the config factory static cache.
$config_factory->getEditable($name);
// Delete the configuration object.
$config->delete();
// Verify the configuration object is empty.
$this->assertIdentical($config->get(), array());
$this->assertIdentical($config->isNew(), TRUE);
// Verify that all copies of the configuration has been removed from the
// static cache.
$this->assertIdentical($config_factory->getEditable($name)->isNew(), TRUE);
// Verify the active configuration contains no value.
$actual_data = $storage->read($name);
$this->assertIdentical($actual_data, FALSE);
// Verify $this->config() returns no data.
$new_config = $this->config($name);
$this->assertIdentical($new_config->get(), $config->get());
$this->assertIdentical($config->isNew(), TRUE);
// Re-create the configuration object.
$config->set('value', 're-created');
$config->save();
$this->assertIdentical($config->isNew(), FALSE);
// Verify the active configuration contains the updated value.
$actual_data = $storage->read($name);
$this->assertIdentical($actual_data, array('value' => 're-created'));
// Verify a call to $this->config() immediately returns the updated value.
$new_config = $this->config($name);
$this->assertIdentical($new_config->get(), $config->get());
$this->assertIdentical($config->isNew(), FALSE);
// Rename the configuration object.
$new_name = 'config_test.crud_rename';
$this->container->get('config.factory')->rename($name, $new_name);
$renamed_config = $this->config($new_name);
$this->assertIdentical($renamed_config->get(), $config->get());
$this->assertIdentical($renamed_config->isNew(), FALSE);
// Ensure that the old configuration object is removed from both the cache
// and the configuration storage.
$config = $this->config($name);
$this->assertIdentical($config->get(), array());
$this->assertIdentical($config->isNew(), TRUE);
// Test renaming when config.factory does not have the object in its static
// cache.
$name = 'config_test.crud_rename';
// Pollute the non-overrides static cache.
$config_factory->getEditable($name);
// Pollute the overrides static cache.
$config = $config_factory->get($name);
// Rename and ensure that happened properly.
$new_name = 'config_test.crud_rename_no_cache';
$config_factory->rename($name, $new_name);
$renamed_config = $config_factory->get($new_name);
$this->assertIdentical($renamed_config->get(), $config->get());
$this->assertIdentical($renamed_config->isNew(), FALSE);
// Ensure the overrides static cache has been cleared.
$this->assertIdentical($config_factory->get($name)->isNew(), TRUE);
// Ensure the non-overrides static cache has been cleared.
$this->assertIdentical($config_factory->getEditable($name)->isNew(), TRUE);
// Merge data into the configuration object.
$new_config = $this->config($new_name);
$expected_values = array(
'value' => 'herp',
'404' => 'derp',
);
$new_config->merge($expected_values);
$new_config->save();
$this->assertIdentical($new_config->get('value'), $expected_values['value']);
$this->assertIdentical($new_config->get('404'), $expected_values['404']);
// Test that getMultiple() does not return new config objects that were
// previously accessed with get()
$new_config = $config_factory->get('non_existing_key');
$this->assertTrue($new_config->isNew());
$this->assertEqual(0, count($config_factory->loadMultiple(['non_existing_key'])), 'loadMultiple() does not return new objects');
}
/**
* Tests the validation of configuration object names.
*/
function testNameValidation() {
// Verify that an object name without namespace causes an exception.
$name = 'nonamespace';
$message = 'Expected ConfigNameException was thrown for a name without a namespace.';
try {
$this->config($name)->save();
$this->fail($message);
}
catch (ConfigNameException $e) {
$this->pass($message);
}
// Verify that a name longer than the maximum length causes an exception.
$name = 'config_test.herman_melville.moby_dick_or_the_whale.harper_1851.now_small_fowls_flew_screaming_over_the_yet_yawning_gulf_a_sullen_white_surf_beat_against_its_steep_sides_then_all_collapsed_and_the_great_shroud_of_the_sea_rolled_on_as_it_rolled_five_thousand_years_ago';
$message = 'Expected ConfigNameException was thrown for a name longer than Config::MAX_NAME_LENGTH.';
try {
$this->config($name)->save();
$this->fail($message);
}
catch (ConfigNameException $e) {
$this->pass($message);
}
// Verify that disallowed characters in the name cause an exception.
$characters = $test_characters = array(':', '?', '*', '<', '>', '"', '\'', '/', '\\');
foreach ($test_characters as $i => $c) {
try {
$name = 'namespace.object' . $c;
$config = $this->config($name);
$config->save();
}
catch (ConfigNameException $e) {
unset($test_characters[$i]);
}
}
$this->assertTrue(empty($test_characters), format_string('Expected ConfigNameException was thrown for all invalid name characters: @characters', array(
'@characters' => implode(' ', $characters),
)));
// Verify that a valid config object name can be saved.
$name = 'namespace.object';
$message = 'ConfigNameException was not thrown for a valid object name.';
try {
$config = $this->config($name);
$config->save();
$this->pass($message);
}
catch (ConfigNameException $e) {
$this->fail($message);
}
}
/**
* Tests the validation of configuration object values.
*/
function testValueValidation() {
// Verify that setData() will catch dotted keys.
$message = 'Expected ConfigValueException was thrown from setData() for value with dotted keys.';
try {
$this->config('namespace.object')->setData(array('key.value' => 12))->save();
$this->fail($message);
}
catch (ConfigValueException $e) {
$this->pass($message);
}
// Verify that set() will catch dotted keys.
$message = 'Expected ConfigValueException was thrown from set() for value with dotted keys.';
try {
$this->config('namespace.object')->set('foo', array('key.value' => 12))->save();
$this->fail($message);
}
catch (ConfigValueException $e) {
$this->pass($message);
}
}
/**
* Tests data type handling.
*/
public function testDataTypes() {
\Drupal::service('module_installer')->install(array('config_test'));
$storage = new DatabaseStorage($this->container->get('database'), 'config');
$name = 'config_test.types';
$config = $this->config($name);
$original_content = file_get_contents(drupal_get_path('module', 'config_test') . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY . "/$name.yml");
$this->verbose('<pre>' . $original_content . "\n" . var_export($storage->read($name), TRUE));
// Verify variable data types are intact.
$data = array(
'array' => array(),
'boolean' => TRUE,
'exp' => 1.2e+34,
'float' => 3.14159,
'float_as_integer' => (float) 1,
'hex' => 0xC,
'int' => 99,
'octal' => 0775,
'string' => 'string',
'string_int' => '1',
);
$data['_core']['default_config_hash'] = Crypt::hashBase64(serialize($data));
$this->assertIdentical($config->get(), $data);
// Re-set each key using Config::set().
foreach($data as $key => $value) {
$config->set($key, $value);
}
$config->save();
$this->assertIdentical($config->get(), $data);
// Assert the data against the file storage.
$this->assertIdentical($storage->read($name), $data);
$this->verbose('<pre>' . $name . var_export($storage->read($name), TRUE));
// Set data using config::setData().
$config->setData($data)->save();
$this->assertIdentical($config->get(), $data);
$this->assertIdentical($storage->read($name), $data);
// Test that schema type enforcement can be overridden by trusting the data.
$this->assertIdentical(99, $config->get('int'));
$config->set('int', '99')->save(TRUE);
$this->assertIdentical('99', $config->get('int'));
// Test that re-saving without testing the data enforces the schema type.
$config->save();
$this->assertIdentical($data, $config->get());
// Test that setting an unsupported type for a config object with a schema
// fails.
try {
$config->set('stream', fopen(__FILE__, 'r'))->save();
$this->fail('No Exception thrown upon saving invalid data type.');
}
catch (UnsupportedDataTypeConfigException $e) {
$this->pass(SafeMarkup::format('%class thrown upon saving invalid data type.', array(
'%class' => get_class($e),
)));
}
// Test that setting an unsupported type for a config object with no schema
// also fails.
$typed_config_manager = $this->container->get('config.typed');
$config_name = 'config_test.no_schema';
$config = $this->config($config_name);
$this->assertFalse($typed_config_manager->hasConfigSchema($config_name));
try {
$config->set('stream', fopen(__FILE__, 'r'))->save();
$this->fail('No Exception thrown upon saving invalid data type.');
}
catch (UnsupportedDataTypeConfigException $e) {
$this->pass(SafeMarkup::format('%class thrown upon saving invalid data type.', array(
'%class' => get_class($e),
)));
}
}
}

View file

@ -0,0 +1,481 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
/**
* Tests for configuration dependencies.
*
* @group config
*/
class ConfigDependencyTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* The entity_test module is enabled to provide content entity types.
*
* @var array
*/
public static $modules = array('config_test', 'entity_test', 'user');
/**
* Tests that calculating dependencies for system module.
*/
public function testNonEntity() {
$this->installConfig(array('system'));
$config_manager = \Drupal::service('config.manager');
$dependents = $config_manager->findConfigEntityDependents('module', array('system'));
$this->assertTrue(isset($dependents['system.site']), 'Simple configuration system.site has a UUID key even though it is not a configuration entity and therefore is found when looking for dependencies of the System module.');
// Ensure that calling
// \Drupal\Core\Config\ConfigManager::findConfigEntityDependentsAsEntities()
// does not try to load system.site as an entity.
$config_manager->findConfigEntityDependentsAsEntities('module', array('system'));
}
/**
* Tests creating dependencies on configuration entities.
*/
public function testDependencyManagement() {
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
$config_manager = \Drupal::service('config.manager');
$storage = $this->container->get('entity.manager')->getStorage('config_test');
// Test dependencies between modules.
$entity1 = $storage->create(
array(
'id' => 'entity1',
'dependencies' => array(
'enforced' => array(
'module' => array('node')
)
)
)
);
$entity1->save();
$dependents = $config_manager->findConfigEntityDependents('module', array('node'));
$this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
$dependents = $config_manager->findConfigEntityDependents('module', array('config_test'));
$this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the config_test module.');
$dependents = $config_manager->findConfigEntityDependents('module', array('views'));
$this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Views module.');
// Ensure that the provider of the config entity is not actually written to
// the dependencies array.
$raw_config = $this->config('config_test.dynamic.entity1');
$root_module_dependencies = $raw_config->get('dependencies.module');
$this->assertTrue(empty($root_module_dependencies), 'Node module is not written to the root dependencies array as it is enforced.');
// Create additional entities to test dependencies on config entities.
$entity2 = $storage->create(array('id' => 'entity2', 'dependencies' => array('enforced' => array('config' => array($entity1->getConfigDependencyName())))));
$entity2->save();
$entity3 = $storage->create(array('id' => 'entity3', 'dependencies' => array('enforced' => array('config' => array($entity2->getConfigDependencyName())))));
$entity3->save();
$entity4 = $storage->create(array('id' => 'entity4', 'dependencies' => array('enforced' => array('config' => array($entity3->getConfigDependencyName())))));
$entity4->save();
// Test getting $entity1's dependencies as configuration dependency objects.
$dependents = $config_manager->findConfigEntityDependents('config', array($entity1->getConfigDependencyName()));
$this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on itself.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
// Test getting $entity2's dependencies as entities.
$dependents = $config_manager->findConfigEntityDependentsAsEntities('config', array($entity2->getConfigDependencyName()));
$dependent_ids = $this->getDependentIds($dependents);
$this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on config_test.dynamic.entity1.');
$this->assertFalse(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 does not have a dependency on itself.');
$this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity2.');
$this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity2.');
// Test getting node module's dependencies as configuration dependency
// objects.
$dependents = $config_manager->findConfigEntityDependents('module', array('node'));
$this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the Node module.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
// Test getting node module's dependencies as configuration dependency
// objects after making $entity3 also dependent on node module but $entity1
// no longer depend on node module.
$entity1->setEnforcedDependencies([])->save();
$entity3->setEnforcedDependencies(['module' => ['node'], 'config' => [$entity2->getConfigDependencyName()]])->save();
$dependents = $config_manager->findConfigEntityDependents('module', array('node'));
$this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Node module.');
$this->assertFalse(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 does not have a dependency on the Node module.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
// Test dependency on a content entity.
$entity_test = EntityTest::create(array(
'name' => $this->randomString(),
'type' => 'entity_test',
));
$entity_test->save();
$entity2->setEnforcedDependencies(['config' => [$entity1->getConfigDependencyName()], 'content' => [$entity_test->getConfigDependencyName()]])->save();;
$dependents = $config_manager->findConfigEntityDependents('content', array($entity_test->getConfigDependencyName()));
$this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the content entity.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the content entity.');
$this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the content entity (via entity2).');
$this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the content entity (via entity3).');
// Create a configuration entity of a different type with the same ID as one
// of the entities already created.
$alt_storage = $this->container->get('entity.manager')->getStorage('config_query_test');
$alt_storage->create(array('id' => 'entity1', 'dependencies' => array('enforced' => array('config' => array($entity1->getConfigDependencyName())))))->save();
$alt_storage->create(array('id' => 'entity2', 'dependencies' => array('enforced' => array('module' => array('views')))))->save();
$dependents = $config_manager->findConfigEntityDependentsAsEntities('config', array($entity1->getConfigDependencyName()));
$dependent_ids = $this->getDependentIds($dependents);
$this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on itself.');
$this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
$this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
$this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
$this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_query_test.dynamic.entity1 has a dependency on config_test.dynamic.entity1.');
$this->assertFalse(in_array('config_query_test:entity2', $dependent_ids), 'config_query_test.dynamic.entity2 does not have a dependency on config_test.dynamic.entity1.');
$dependents = $config_manager->findConfigEntityDependentsAsEntities('module', array('node', 'views'));
$dependent_ids = $this->getDependentIds($dependents);
$this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on Views or Node.');
$this->assertFalse(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 does not have a dependency on Views or Node.');
$this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on Views or Node.');
$this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on Views or Node.');
$this->assertFalse(in_array('config_query_test:entity1', $dependent_ids), 'config_test.query.entity1 does not have a dependency on Views or Node.');
$this->assertTrue(in_array('config_query_test:entity2', $dependent_ids), 'config_test.query.entity2 has a dependency on Views or Node.');
$dependents = $config_manager->findConfigEntityDependentsAsEntities('module', array('config_test'));
$dependent_ids = $this->getDependentIds($dependents);
$this->assertTrue(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 has a dependency on config_test module.');
$this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test module.');
$this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test module.');
$this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test module.');
$this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_test.query.entity1 has a dependency on config_test module.');
$this->assertTrue(in_array('config_query_test:entity2', $dependent_ids), 'config_test.query.entity2 has a dependency on config_test module.');
// Test the ability to find missing content dependencies.
$missing_dependencies = $config_manager->findMissingContentDependencies();
$this->assertEqual([], $missing_dependencies);
$expected = [$entity_test->uuid() => [
'entity_type' => 'entity_test',
'bundle' => $entity_test->bundle(),
'uuid' => $entity_test->uuid(),
]];
// Delete the content entity so that is it now missing.
$entity_test->delete();
$missing_dependencies = $config_manager->findMissingContentDependencies();
$this->assertEqual($expected, $missing_dependencies);
// Add a fake missing dependency to ensure multiple missing dependencies
// work.
$entity1->setEnforcedDependencies(['content' => [$entity_test->getConfigDependencyName(), 'entity_test:bundle:uuid']])->save();;
$expected['uuid'] = [
'entity_type' => 'entity_test',
'bundle' => 'bundle',
'uuid' => 'uuid',
];
$missing_dependencies = $config_manager->findMissingContentDependencies();
$this->assertEqual($expected, $missing_dependencies);
}
/**
* Tests ConfigManager::uninstall() and config entity dependency management.
*/
public function testConfigEntityUninstall() {
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
$config_manager = \Drupal::service('config.manager');
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
$storage = $this->container->get('entity.manager')->getStorage('config_test');
// Test dependencies between modules.
$entity1 = $storage->create(
array(
'id' => 'entity1',
'dependencies' => array(
'enforced' => array(
'module' => array('node', 'config_test')
),
),
)
);
$entity1->save();
$entity2 = $storage->create(
array(
'id' => 'entity2',
'dependencies' => array(
'enforced' => array(
'config' => array($entity1->getConfigDependencyName()),
),
),
)
);
$entity2->save();
// Perform a module rebuild so we can know where the node module is located
// and uninstall it.
// @todo Remove as part of https://www.drupal.org/node/2186491
system_rebuild_module_data();
// Test that doing a config uninstall of the node module deletes entity2
// since it is dependent on entity1 which is dependent on the node module.
$config_manager->uninstall('module', 'node');
$this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
$this->assertFalse($storage->load('entity2'), 'Entity 2 deleted');
// Set a more complicated test where dependencies will be fixed.
\Drupal::state()->set('config_test.fix_dependencies', array($entity1->getConfigDependencyName()));
\Drupal::state()->set('config_test.on_dependency_removal_called', []);
// Entity1 will be deleted because it depends on node.
$entity1 = $storage->create(
array(
'id' => 'entity1',
'dependencies' => array(
'enforced' => array(
'module' => array('node', 'config_test')
),
),
)
);
$entity1->save();
// Entity2 has a dependency on Entity1 but it can be fixed because
// \Drupal\config_test\Entity::onDependencyRemoval() will remove the
// dependency before config entities are deleted.
$entity2 = $storage->create(
array(
'id' => 'entity2',
'dependencies' => array(
'enforced' => array(
'config' => array($entity1->getConfigDependencyName()),
),
),
)
);
$entity2->save();
// Entity3 will be unchanged because it is dependent on Entity2 which can
// be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
// not be called for this entity.
$entity3 = $storage->create(
array(
'id' => 'entity3',
'dependencies' => array(
'enforced' => array(
'config' => array($entity2->getConfigDependencyName()),
),
),
)
);
$entity3->save();
// Entity4's config dependency will be fixed but it will still be deleted
// because it also depends on the node module.
$entity4 = $storage->create(
array(
'id' => 'entity4',
'dependencies' => array(
'enforced' => array(
'config' => array($entity1->getConfigDependencyName()),
'module' => array('node', 'config_test')
),
),
)
);
$entity4->save();
// Do a dry run using
// \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
$config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
$this->assertEqual($entity1->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 1 will be deleted.');
$this->assertEqual($entity2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.');
$this->assertEqual($entity3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
$this->assertEqual($entity4->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 4 will be deleted.');
$called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
$this->assertFalse(in_array($entity3->id(), $called), 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
$this->assertIdentical(['entity1', 'entity2', 'entity4'], $called, 'The most dependent entites have ConfigEntityInterface::onDependencyRemoval() called first.');
// Perform the uninstall.
$config_manager->uninstall('module', 'node');
// Test that expected actions have been performed.
$this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
$entity2 = $storage->load('entity2');
$this->assertTrue($entity2, 'Entity 2 not deleted');
$this->assertEqual($entity2->calculateDependencies()->getDependencies()['config'], array(), 'Entity 2 dependencies updated to remove dependency on Entity1.');
$entity3 = $storage->load('entity3');
$this->assertTrue($entity3, 'Entity 3 not deleted');
$this->assertEqual($entity3->calculateDependencies()->getDependencies()['config'], [$entity2->getConfigDependencyName()], 'Entity 3 still depends on Entity 2.');
$this->assertFalse($storage->load('entity4'), 'Entity 4 deleted');
}
/**
* Tests deleting a configuration entity and dependency management.
*/
public function testConfigEntityDelete() {
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
$config_manager = \Drupal::service('config.manager');
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
$storage = $this->container->get('entity.manager')->getStorage('config_test');
// Test dependencies between configuration entities.
$entity1 = $storage->create(
array(
'id' => 'entity1'
)
);
$entity1->save();
$entity2 = $storage->create(
array(
'id' => 'entity2',
'dependencies' => array(
'enforced' => array(
'config' => array($entity1->getConfigDependencyName()),
),
),
)
);
$entity2->save();
// Do a dry run using
// \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
$config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
$this->assertEqual($entity2->uuid(), reset($config_entities['delete'])->uuid(), 'Entity 2 will be deleted.');
$this->assertTrue(empty($config_entities['update']), 'No dependent configuration entities will be updated.');
$this->assertTrue(empty($config_entities['unchanged']), 'No dependent configuration entities will be unchanged.');
// Test that doing a delete of entity1 deletes entity2 since it is dependent
// on entity1.
$entity1->delete();
$this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
$this->assertFalse($storage->load('entity2'), 'Entity 2 deleted');
// Set a more complicated test where dependencies will be fixed.
\Drupal::state()->set('config_test.fix_dependencies', array($entity1->getConfigDependencyName()));
// Entity1 will be deleted by the test.
$entity1 = $storage->create(
array(
'id' => 'entity1',
)
);
$entity1->save();
// Entity2 has a dependency on Entity1 but it can be fixed because
// \Drupal\config_test\Entity::onDependencyRemoval() will remove the
// dependency before config entities are deleted.
$entity2 = $storage->create(
array(
'id' => 'entity2',
'dependencies' => array(
'enforced' => array(
'config' => array($entity1->getConfigDependencyName()),
),
),
)
);
$entity2->save();
// Entity3 will be unchanged because it is dependent on Entity2 which can
// be fixed.
$entity3 = $storage->create(
array(
'id' => 'entity3',
'dependencies' => array(
'enforced' => array(
'config' => array($entity2->getConfigDependencyName()),
),
),
)
);
$entity3->save();
// Do a dry run using
// \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
$config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
$this->assertTrue(empty($config_entities['delete']), 'No dependent configuration entities will be deleted.');
$this->assertEqual($entity2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.');
$this->assertEqual($entity3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
// Perform the uninstall.
$entity1->delete();
// Test that expected actions have been performed.
$this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
$entity2 = $storage->load('entity2');
$this->assertTrue($entity2, 'Entity 2 not deleted');
$this->assertEqual($entity2->calculateDependencies()->getDependencies()['config'], array(), 'Entity 2 dependencies updated to remove dependency on Entity1.');
$entity3 = $storage->load('entity3');
$this->assertTrue($entity3, 'Entity 3 not deleted');
$this->assertEqual($entity3->calculateDependencies()->getDependencies()['config'], [$entity2->getConfigDependencyName()], 'Entity 3 still depends on Entity 2.');
}
/**
* Tests getConfigEntitiesToChangeOnDependencyRemoval() with content entities.
*
* At the moment there is no runtime code that calculates configuration
* dependencies on content entity delete because this calculation is expensive
* and all content dependencies are soft. This test ensures that the code
* works for content entities.
*
* @see \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval()
*/
public function testContentEntityDelete() {
$this->installEntitySchema('entity_test');
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
$config_manager = \Drupal::service('config.manager');
$content_entity = EntityTest::create();
$content_entity->save();
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
$storage = $this->container->get('entity.manager')->getStorage('config_test');
$entity1 = $storage->create(
array(
'id' => 'entity1',
'dependencies' => array(
'enforced' => array(
'content' => array($content_entity->getConfigDependencyName())
),
),
)
);
$entity1->save();
$entity2 = $storage->create(
array(
'id' => 'entity2',
'dependencies' => array(
'enforced' => array(
'config' => array($entity1->getConfigDependencyName())
),
),
)
);
$entity2->save();
// Create a configuration entity that is not in the dependency chain.
$entity3 = $storage->create(array('id' => 'entity3'));
$entity3->save();
$config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('content', [$content_entity->getConfigDependencyName()]);
$this->assertEqual($entity1->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 1 will be deleted.');
$this->assertEqual($entity2->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 2 will be deleted.');
$this->assertTrue(empty($config_entities['update']), 'No dependencies of the content entity will be updated.');
$this->assertTrue(empty($config_entities['unchanged']), 'No dependencies of the content entity will be unchanged.');
}
/**
* Gets a list of identifiers from an array of configuration entities.
*
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependents
* An array of configuration entities.
*
* @return array
* An array with values of entity_type_id:ID
*/
protected function getDependentIds(array $dependents) {
$dependent_ids = array();
foreach($dependents as $dependent) {
$dependent_ids[] = $dependent->getEntityTypeId() . ':' . $dependent->id();
}
return $dependent_ids;
}
}

View file

@ -0,0 +1,142 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\KernelTests\KernelTestBase;
/**
* Calculating the difference between two sets of configuration.
*
* @group config
*/
class ConfigDiffTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test', 'system');
/**
* Tests calculating the difference between two sets of configuration.
*/
function testDiff() {
$active = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
$config_name = 'config_test.system';
$change_key = 'foo';
$remove_key = '404';
$add_key = 'biff';
$add_data = 'bangpow';
$change_data = 'foobar';
// Install the default config.
$this->installConfig(array('config_test'));
$original_data = \Drupal::config($config_name)->get();
// Change a configuration value in sync.
$sync_data = $original_data;
$sync_data[$change_key] = $change_data;
$sync_data[$add_key] = $add_data;
$sync->write($config_name, $sync_data);
// Verify that the diff reflects a change.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'change', 'The first item in the diff is a change.');
$this->assertEqual($edits[0]->orig[0], $change_key . ': ' . $original_data[$change_key], format_string("The active value for key '%change_key' is '%original_data'.", array('%change_key' => $change_key, '%original_data' => $original_data[$change_key])));
$this->assertEqual($edits[0]->closing[0], $change_key . ': ' . $change_data, format_string("The sync value for key '%change_key' is '%change_data'.", array('%change_key' => $change_key, '%change_data' => $change_data)));
// Reset data back to original, and remove a key
$sync_data = $original_data;
unset($sync_data[$remove_key]);
$sync->write($config_name, $sync_data);
// Verify that the diff reflects a removed key.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
$this->assertEqual($edits[1]->type, 'delete', 'The second item in the diff is a delete.');
$this->assertEqual($edits[1]->orig[0], $remove_key . ': ' . $original_data[$remove_key], format_string("The active value for key '%remove_key' is '%original_data'.", array('%remove_key' => $remove_key, '%original_data' => $original_data[$remove_key])));
$this->assertFalse($edits[1]->closing, format_string("The key '%remove_key' does not exist in sync.", array('%remove_key' => $remove_key)));
// Reset data back to original and add a key
$sync_data = $original_data;
$sync_data[$add_key] = $add_data;
$sync->write($config_name, $sync_data);
// Verify that the diff reflects an added key.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
$this->assertEqual($edits[1]->type, 'add', 'The second item in the diff is an add.');
$this->assertFalse($edits[1]->orig, format_string("The key '%add_key' does not exist in active.", array('%add_key' => $add_key)));
$this->assertEqual($edits[1]->closing[0], $add_key . ': ' . $add_data, format_string("The sync value for key '%add_key' is '%add_data'.", array('%add_key' => $add_key, '%add_data' => $add_data)));
// Test diffing a renamed config entity.
$test_entity_id = $this->randomMachineName();
$test_entity = entity_create('config_test', array(
'id' => $test_entity_id,
'label' => $this->randomMachineName(),
));
$test_entity->save();
$data = $active->read('config_test.dynamic.' . $test_entity_id);
$sync->write('config_test.dynamic.' . $test_entity_id, $data);
$config_name = 'config_test.dynamic.' . $test_entity_id;
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name, $config_name);
// Prove the fields match.
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
$this->assertEqual(count($edits), 1, 'There is one item in the diff');
// Rename the entity.
$new_test_entity_id = $this->randomMachineName();
$test_entity->set('id', $new_test_entity_id);
$test_entity->save();
$diff = \Drupal::service('config.manager')->diff($active, $sync, 'config_test.dynamic.' . $new_test_entity_id, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
$this->assertEqual($edits[1]->type, 'change', 'The second item in the diff is a change.');
$this->assertEqual($edits[1]->orig, array('id: ' . $new_test_entity_id));
$this->assertEqual($edits[1]->closing, array('id: ' . $test_entity_id));
$this->assertEqual($edits[2]->type, 'copy', 'The third item in the diff is a copy.');
$this->assertEqual(count($edits), 3, 'There are three items in the diff.');
}
/**
* Tests calculating the difference between two sets of config collections.
*/
function testCollectionDiff() {
/** @var \Drupal\Core\Config\StorageInterface $active */
$active = $this->container->get('config.storage');
/** @var \Drupal\Core\Config\StorageInterface $sync */
$sync = $this->container->get('config.storage.sync');
$active_test_collection = $active->createCollection('test');
$sync_test_collection = $sync->createCollection('test');
$config_name = 'config_test.test';
$data = array('foo' => 'bar');
$active->write($config_name, $data);
$sync->write($config_name, $data);
$active_test_collection->write($config_name, $data);
$sync_test_collection->write($config_name, array('foo' => 'baz'));
// Test the fields match in the default collection diff.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
$this->assertEqual(count($edits), 1, 'There is one item in the diff');
// Test that the differences are detected when diffing the collection.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name, NULL, 'test');
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'change', 'The second item in the diff is a copy.');
$this->assertEqual($edits[0]->orig, array('foo: bar'));
$this->assertEqual($edits[0]->closing, array('foo: baz'));
$this->assertEqual($edits[1]->type, 'copy', 'The second item in the diff is a copy.');
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the listing of configuration entities.
*
* @group config
*/
class ConfigEntityNormalizeTest extends KernelTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('config_test');
protected function setUp() {
parent::setUp();
$this->installConfig(static::$modules);
}
public function testNormalize() {
$config_entity = entity_create('config_test', array('id' => 'system', 'label' => 'foobar', 'weight' => 1));
$config_entity->save();
// Modify stored config entity, this is comparable with a schema change.
$config = $this->config('config_test.dynamic.system');
$data = array(
'label' => 'foobar',
'additional_key' => TRUE
) + $config->getRawData();
$config->setData($data)->save();
$this->assertNotIdentical($config_entity->toArray(), $config->getRawData(), 'Stored config entity is not is equivalent to config schema.');
$config_entity = entity_load('config_test', 'system', TRUE);
$config_entity->save();
$config = $this->config('config_test.dynamic.system');
$this->assertIdentical($config_entity->toArray(), $config->getRawData(), 'Stored config entity is equivalent to config schema.');
}
}

View file

@ -0,0 +1,108 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\config_entity_static_cache_test\ConfigOverrider;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the entity static cache when used by config entities.
*
* @group config
*/
class ConfigEntityStaticCacheTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test', 'config_entity_static_cache_test');
/**
* The type ID of the entity under test.
*
* @var string
*/
protected $entityTypeId;
/**
* The entity ID of the entity under test.
*
* @var string
*/
protected $entityId;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->entityTypeId = 'config_test';
$this->entityId = 'test_1';
$this->container->get('entity_type.manager')
->getStorage($this->entityTypeId)
->create(array('id' => $this->entityId, 'label' => 'Original label'))
->save();
}
/**
* Tests that the static cache is working.
*/
public function testCacheHit() {
$entity_1 = entity_load($this->entityTypeId, $this->entityId);
$entity_2 = entity_load($this->entityTypeId, $this->entityId);
// config_entity_static_cache_test_config_test_load() sets _loadStamp to a
// random string. If they match, it means $entity_2 was retrieved from the
// static cache rather than going through a separate load sequence.
$this->assertIdentical($entity_1->_loadStamp, $entity_2->_loadStamp);
}
/**
* Tests that the static cache is reset on entity save and delete.
*/
public function testReset() {
$entity = entity_load($this->entityTypeId, $this->entityId);
// Ensure loading after a save retrieves the updated entity rather than an
// obsolete cached one.
$entity->label = 'New label';
$entity->save();
$entity = entity_load($this->entityTypeId, $this->entityId);
$this->assertIdentical($entity->label, 'New label');
// Ensure loading after a delete retrieves NULL rather than an obsolete
// cached one.
$entity->delete();
$this->assertNull(entity_load($this->entityTypeId, $this->entityId));
}
/**
* Tests that the static cache is sensitive to config overrides.
*/
public function testConfigOverride() {
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
$storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
// Prime the cache prior to adding a config override.
$storage->load($this->entityId);
// Add the config override, and ensure that what is loaded is correct
// despite the prior cache priming.
\Drupal::configFactory()->addOverride(new ConfigOverrider());
$entity_override = $storage->load($this->entityId);
$this->assertIdentical($entity_override->label, 'Overridden label');
// Load override free to ensure that loading the config entity again does
// not return the overridden value.
$entity_no_override = $storage->loadOverrideFree($this->entityId);
$this->assertNotIdentical($entity_no_override->label, 'Overridden label');
$this->assertNotIdentical($entity_override->_loadStamp, $entity_no_override->_loadStamp);
// Reload the entity and ensure the cache is used.
$this->assertIdentical($storage->loadOverrideFree($this->entityId)->_loadStamp, $entity_no_override->_loadStamp);
// Enable overrides and reload the entity and ensure the cache is used.
$this->assertIdentical($storage->load($this->entityId)->_loadStamp, $entity_override->_loadStamp);
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests configuration entity status functionality.
*
* @group config
*/
class ConfigEntityStatusTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test');
/**
* Tests the enabling/disabling of entities.
*/
function testCRUD() {
$entity = entity_create('config_test', array(
'id' => strtolower($this->randomMachineName()),
));
$this->assertTrue($entity->status(), 'Default status is enabled.');
$entity->save();
$this->assertTrue($entity->status(), 'Status is enabled after saving.');
$entity->disable()->save();
$this->assertFalse($entity->status(), 'Entity is disabled after disabling.');
$entity->enable()->save();
$this->assertTrue($entity->status(), 'Entity is enabled after enabling.');
$entity = entity_load('config_test', $entity->id());
$this->assertTrue($entity->status(), 'Status is enabled after reload.');
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\ConfigDuplicateUUIDException;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests sync and importing config entities with IDs and UUIDs that match
* existing config.
*
* @group config
*/
class ConfigEntityStorageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test');
/**
* Tests creating configuration entities with changed UUIDs.
*/
public function testUUIDConflict() {
$entity_type = 'config_test';
$id = 'test_1';
// Load the original configuration entity.
$this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('id' => $id))
->save();
$entity = entity_load($entity_type, $id);
$original_properties = $entity->toArray();
// Override with a new UUID and try to save.
$new_uuid = $this->container->get('uuid')->generate();
$entity->set('uuid', $new_uuid);
try {
$entity->save();
$this->fail('Exception thrown when attempting to save a configuration entity with a UUID that does not match the existing UUID.');
}
catch (ConfigDuplicateUUIDException $e) {
$this->pass(format_string('Exception thrown when attempting to save a configuration entity with a UUID that does not match existing data: %e.', array('%e' => $e)));
}
// Ensure that the config entity was not corrupted.
$entity = entity_load('config_test', $entity->id(), TRUE);
$this->assertIdentical($entity->toArray(), $original_properties);
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\KernelTests\KernelTestBase;
/**
* Unit tests for configuration entity base methods.
*
* @group config
*/
class ConfigEntityUnitTest extends KernelTestBase {
/**
* Exempt from strict schema checking.
*
* @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
*
* @var bool
*/
protected $strictConfigSchema = FALSE;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test');
/**
* The config_test entity storage.
*
* @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
*/
protected $storage;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->storage = $this->container->get('entity.manager')->getStorage('config_test');
}
/**
* Tests storage methods.
*/
public function testStorageMethods() {
$entity_type = \Drupal::entityManager()->getDefinition('config_test');
// Test the static extractID() method.
$expected_id = 'test_id';
$config_name = $entity_type->getConfigPrefix() . '.' . $expected_id;
$storage = $this->storage;
$this->assertIdentical($storage::getIDFromConfigName($config_name, $entity_type->getConfigPrefix()), $expected_id);
// Create three entities, two with the same style.
$style = $this->randomMachineName(8);
for ($i = 0; $i < 2; $i++) {
$entity = $this->storage->create(array(
'id' => $this->randomMachineName(),
'label' => $this->randomString(),
'style' => $style,
));
$entity->save();
}
$entity = $this->storage->create(array(
'id' => $this->randomMachineName(),
'label' => $this->randomString(),
// Use a different length for the entity to ensure uniqueness.
'style' => $this->randomMachineName(9),
));
$entity->save();
// Ensure that the configuration entity can be loaded by UUID.
$entity_loaded_by_uuid = \Drupal::entityManager()->loadEntityByUuid($entity_type->id(), $entity->uuid());
if (!$entity_loaded_by_uuid) {
$this->fail(sprintf("Failed to load '%s' entity ID '%s' by UUID '%s'.", $entity_type->id(), $entity->id(), $entity->uuid()));
}
// Compare UUIDs as the objects are not identical since
// $entity->enforceIsNew is FALSE and $entity_loaded_by_uuid->enforceIsNew
// is NULL.
$this->assertIdentical($entity->uuid(), $entity_loaded_by_uuid->uuid());
$entities = $this->storage->loadByProperties();
$this->assertEqual(count($entities), 3, 'Three entities are loaded when no properties are specified.');
$entities = $this->storage->loadByProperties(array('style' => $style));
$this->assertEqual(count($entities), 2, 'Two entities are loaded when the style property is specified.');
// Assert that both returned entities have a matching style property.
foreach ($entities as $entity) {
$this->assertIdentical($entity->get('style'), $style, 'The loaded entity has the correct style value specified.');
}
// Test that schema type enforcement can be overridden by trusting the data.
$entity = $this->storage->create(array(
'id' => $this->randomMachineName(),
'label' => $this->randomString(),
'style' => 999
));
$entity->save();
$this->assertIdentical('999', $entity->style);
$entity->style = 999;
$entity->trustData()->save();
$this->assertIdentical(999, $entity->style);
$entity->save();
$this->assertIdentical('999', $entity->style);
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigEvents;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests events fired on configuration objects.
*
* @group config
*/
class ConfigEventsTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_events_test');
/**
* Tests configuration events.
*/
function testConfigEvents() {
$name = 'config_events_test.test';
$config = new Config($name, \Drupal::service('config.storage'), \Drupal::service('event_dispatcher'), \Drupal::service('config.typed'));
$config->set('key', 'initial');
$this->assertIdentical(\Drupal::state()->get('config_events_test.event', array()), array(), 'No events fired by creating a new configuration object');
$config->save();
$event = \Drupal::state()->get('config_events_test.event', array());
$this->assertIdentical($event['event_name'], ConfigEvents::SAVE);
$this->assertIdentical($event['current_config_data'], array('key' => 'initial'));
$this->assertIdentical($event['raw_config_data'], array('key' => 'initial'));
$this->assertIdentical($event['original_config_data'], array());
$config->set('key', 'updated')->save();
$event = \Drupal::state()->get('config_events_test.event', array());
$this->assertIdentical($event['event_name'], ConfigEvents::SAVE);
$this->assertIdentical($event['current_config_data'], array('key' => 'updated'));
$this->assertIdentical($event['raw_config_data'], array('key' => 'updated'));
$this->assertIdentical($event['original_config_data'], array('key' => 'initial'));
$config->delete();
$event = \Drupal::state()->get('config_events_test.event', array());
$this->assertIdentical($event['event_name'], ConfigEvents::DELETE);
$this->assertIdentical($event['current_config_data'], array());
$this->assertIdentical($event['raw_config_data'], array());
$this->assertIdentical($event['original_config_data'], array('key' => 'updated'));
}
/**
* Tests configuration rename event that is fired from the ConfigFactory.
*/
function testConfigRenameEvent() {
$name = 'config_events_test.test';
$new_name = 'config_events_test.test_rename';
$GLOBALS['config'][$name] = array('key' => 'overridden');
$GLOBALS['config'][$new_name] = array('key' => 'new overridden');
$config = $this->config($name);
$config->set('key', 'initial')->save();
$event = \Drupal::state()->get('config_events_test.event', array());
$this->assertIdentical($event['event_name'], ConfigEvents::SAVE);
$this->assertIdentical($event['current_config_data'], array('key' => 'initial'));
// Override applies when getting runtime config.
$this->assertEqual($GLOBALS['config'][$name], \Drupal::config($name)->get());
\Drupal::configFactory()->rename($name, $new_name);
$event = \Drupal::state()->get('config_events_test.event', array());
$this->assertIdentical($event['event_name'], ConfigEvents::RENAME);
$this->assertIdentical($event['current_config_data'], array('key' => 'new overridden'));
$this->assertIdentical($event['raw_config_data'], array('key' => 'initial'));
$this->assertIdentical($event['original_config_data'], array('key' => 'new overridden'));
}
}

View file

@ -0,0 +1,230 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\FileStorage;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests reading and writing of configuration files.
*
* @group config
*/
class ConfigFileContentTest extends KernelTestBase {
/**
* Exempt from strict schema checking.
*
* @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
*
* @var bool
*/
protected $strictConfigSchema = FALSE;
/**
* Tests setting, writing, and reading of a configuration setting.
*/
function testReadWriteConfig() {
$storage = $this->container->get('config.storage');
$name = 'foo.bar';
$key = 'foo';
$value = 'bar';
$nested_key = 'biff.bang';
$nested_value = 'pow';
$array_key = 'array';
$array_value = array(
'foo' => 'bar',
'biff' => array(
'bang' => 'pow',
),
);
$casting_array_key = 'casting_array';
$casting_array_false_value_key = 'casting_array.cast.false';
$casting_array_value = array(
'cast' => array(
'false' => FALSE,
),
);
$nested_array_key = 'nested.array';
$true_key = 'true';
$false_key = 'false';
// Attempt to read non-existing configuration.
$config = $this->config($name);
// Verify a configuration object is returned.
$this->assertEqual($config->getName(), $name);
$this->assertTrue($config, 'Config object created.');
// Verify the configuration object is empty.
$this->assertEqual($config->get(), array(), 'New config object is empty.');
// Verify nothing was saved.
$data = $storage->read($name);
$this->assertIdentical($data, FALSE);
// Add a top level value.
$config = $this->config($name);
$config->set($key, $value);
// Add a nested value.
$config->set($nested_key, $nested_value);
// Add an array.
$config->set($array_key, $array_value);
// Add a nested array.
$config->set($nested_array_key, $array_value);
// Add a boolean false value. Should get cast to 0.
$config->set($false_key, FALSE);
// Add a boolean true value. Should get cast to 1.
$config->set($true_key, TRUE);
// Add a null value. Should get cast to an empty string.
$config->set('null', NULL);
// Add an array with a nested boolean false that should get cast to 0.
$config->set($casting_array_key, $casting_array_value);
$config->save();
// Verify the database entry exists.
$data = $storage->read($name);
$this->assertTrue($data);
// Read top level value.
$config = $this->config($name);
$this->assertEqual($config->getName(), $name);
$this->assertTrue($config, 'Config object created.');
$this->assertEqual($config->get($key), 'bar', 'Top level configuration value found.');
// Read nested value.
$this->assertEqual($config->get($nested_key), $nested_value, 'Nested configuration value found.');
// Read array.
$this->assertEqual($config->get($array_key), $array_value, 'Top level array configuration value found.');
// Read nested array.
$this->assertEqual($config->get($nested_array_key), $array_value, 'Nested array configuration value found.');
// Read a top level value that doesn't exist.
$this->assertNull($config->get('i_dont_exist'), 'Non-existent top level value returned NULL.');
// Read a nested value that doesn't exist.
$this->assertNull($config->get('i.dont.exist'), 'Non-existent nested value returned NULL.');
// Read false value.
$this->assertFalse($config->get($false_key), "Boolean FALSE value returned the FALSE.");
// Read true value.
$this->assertTrue($config->get($true_key), "Boolean TRUE value returned the TRUE.");
// Read null value.
$this->assertIdentical($config->get('null'), NULL);
// Read false that had been nested in an array value.
$this->assertSame($config->get($casting_array_false_value_key), FALSE, "Nested boolean FALSE value returned FALSE.");
// Unset a top level value.
$config->clear($key);
// Unset a nested value.
$config->clear($nested_key);
$config->save();
$config = $this->config($name);
// Read unset top level value.
$this->assertNull($config->get($key), 'Top level value unset.');
// Read unset nested value.
$this->assertNull($config->get($nested_key), 'Nested value unset.');
// Create two new configuration files to test listing.
$config = $this->config('foo.baz');
$config->set($key, $value);
$config->save();
// Test chained set()->save().
$chained_name = 'biff.bang';
$config = $this->config($chained_name);
$config->set($key, $value)->save();
// Verify the database entry exists from a chained save.
$data = $storage->read($chained_name);
$this->assertEqual($data, $config->get());
// Get file listing for all files starting with 'foo'. Should return
// two elements.
$files = $storage->listAll('foo');
$this->assertEqual(count($files), 2, 'Two files listed with the prefix \'foo\'.');
// Get file listing for all files starting with 'biff'. Should return
// one element.
$files = $storage->listAll('biff');
$this->assertEqual(count($files), 1, 'One file listed with the prefix \'biff\'.');
// Get file listing for all files starting with 'foo.bar'. Should return
// one element.
$files = $storage->listAll('foo.bar');
$this->assertEqual(count($files), 1, 'One file listed with the prefix \'foo.bar\'.');
// Get file listing for all files starting with 'bar'. Should return
// an empty array.
$files = $storage->listAll('bar');
$this->assertEqual($files, array(), 'No files listed with the prefix \'bar\'.');
// Delete the configuration.
$config = $this->config($name);
$config->delete();
// Verify the database entry no longer exists.
$data = $storage->read($name);
$this->assertIdentical($data, FALSE);
}
/**
* Tests serialization of configuration to file.
*/
function testSerialization() {
$name = $this->randomMachineName(10) . '.' . $this->randomMachineName(10);
$config_data = array(
// Indexed arrays; the order of elements is essential.
'numeric keys' => array('i', 'n', 'd', 'e', 'x', 'e', 'd'),
// Infinitely nested keys using arbitrary element names.
'nested keys' => array(
// HTML/XML in values.
'HTML' => '<strong> <bold> <em> <blockquote>',
// UTF-8 in values.
'UTF-8' => 'FrançAIS is ÜBER-åwesome',
// Unicode in keys and values.
'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ' => 'αβγδεζηθικλμνξοσὠ',
),
'invalid xml' => '</title><script type="text/javascript">alert("Title XSS!");</script> & < > " \' ',
);
// Encode and write, and reload and decode the configuration data.
$filestorage = new FileStorage(config_get_config_directory(CONFIG_SYNC_DIRECTORY));
$filestorage->write($name, $config_data);
$config_parsed = $filestorage->read($name);
$key = 'numeric keys';
$this->assertIdentical($config_data[$key], $config_parsed[$key]);
$key = 'nested keys';
$this->assertIdentical($config_data[$key], $config_parsed[$key]);
$key = 'HTML';
$this->assertIdentical($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
$key = 'UTF-8';
$this->assertIdentical($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
$key = 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ';
$this->assertIdentical($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
$key = 'invalid xml';
$this->assertIdentical($config_data[$key], $config_parsed[$key]);
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\StorageComparer;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\NodeType;
/**
* Tests importing recreated configuration entities.
*
* @group config
*/
class ConfigImportRecreateTest extends KernelTestBase {
/**
* Config Importer object used for testing.
*
* @var \Drupal\Core\Config\ConfigImporter
*/
protected $configImporter;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['system', 'field', 'text', 'user', 'node'];
protected function setUp() {
parent::setUp();
$this->installEntitySchema('node');
$this->installConfig(array('field', 'node'));
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
// Set up the ConfigImporter object for testing.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.sync'),
$this->container->get('config.storage'),
$this->container->get('config.manager')
);
$this->configImporter = new ConfigImporter(
$storage_comparer->createChangelist(),
$this->container->get('event_dispatcher'),
$this->container->get('config.manager'),
$this->container->get('lock'),
$this->container->get('config.typed'),
$this->container->get('module_handler'),
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation')
);
}
public function testRecreateEntity() {
$type_name = Unicode::strtolower($this->randomMachineName(16));
$content_type = NodeType::create([
'type' => $type_name,
'name' => 'Node type one',
]);
$content_type->save();
node_add_body_field($content_type);
/** @var \Drupal\Core\Config\StorageInterface $active */
$active = $this->container->get('config.storage');
/** @var \Drupal\Core\Config\StorageInterface $sync */
$sync = $this->container->get('config.storage.sync');
$config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id();
$this->copyConfig($active, $sync);
// Delete the content type. This will also delete a field storage, a field,
// an entity view display and an entity form display.
$content_type->delete();
$this->assertFalse($active->exists($config_name), 'Content type\'s old name does not exist active store.');
// Recreate with the same type - this will have a different UUID.
$content_type = NodeType::create([
'type' => $type_name,
'name' => 'Node type two',
]);
$content_type->save();
node_add_body_field($content_type);
$this->configImporter->reset();
// A node type, a field, an entity view display and an entity form display
// will be recreated.
$creates = $this->configImporter->getUnprocessedConfiguration('create');
$deletes = $this->configImporter->getUnprocessedConfiguration('delete');
$this->assertEqual(5, count($creates), 'There are 5 configuration items to create.');
$this->assertEqual(5, count($deletes), 'There are 5 configuration items to delete.');
$this->assertEqual(0, count($this->configImporter->getUnprocessedConfiguration('update')), 'There are no configuration items to update.');
$this->assertIdentical($creates, array_reverse($deletes), 'Deletes and creates contain the same configuration names in opposite orders due to dependencies.');
$this->configImporter->import();
// Verify that there is nothing more to import.
$this->assertFalse($this->configImporter->reset()->hasUnprocessedConfigurationChanges());
$content_type = NodeType::load($type_name);
$this->assertEqual('Node type one', $content_type->label());
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Uuid\Php;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Config\StorageComparer;
use Drupal\node\Entity\NodeType;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests validating renamed configuration in a configuration import.
*
* @group config
*/
class ConfigImportRenameValidationTest extends KernelTestBase {
/**
* Config Importer object used for testing.
*
* @var \Drupal\Core\Config\ConfigImporter
*/
protected $configImporter;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['system', 'user', 'node', 'field', 'text', 'config_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installConfig(array('field'));
// Set up the ConfigImporter object for testing.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.sync'),
$this->container->get('config.storage'),
$this->container->get('config.manager')
);
$this->configImporter = new ConfigImporter(
$storage_comparer->createChangelist(),
$this->container->get('event_dispatcher'),
$this->container->get('config.manager'),
$this->container->get('lock.persistent'),
$this->container->get('config.typed'),
$this->container->get('module_handler'),
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation')
);
}
/**
* Tests configuration renaming validation.
*/
public function testRenameValidation() {
// Create a test entity.
$test_entity_id = $this->randomMachineName();
$test_entity = entity_create('config_test', array(
'id' => $test_entity_id,
'label' => $this->randomMachineName(),
));
$test_entity->save();
$uuid = $test_entity->uuid();
// Stage the test entity and then delete it from the active storage.
$active = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
$this->copyConfig($active, $sync);
$test_entity->delete();
// Create a content type with a matching UUID in the active storage.
$content_type = NodeType::create([
'type' => Unicode::strtolower($this->randomMachineName(16)),
'name' => $this->randomMachineName(),
'uuid' => $uuid,
]);
$content_type->save();
// Confirm that the staged configuration is detected as a rename since the
// UUIDs match.
$this->configImporter->reset();
$expected = array(
'node.type.' . $content_type->id() . '::config_test.dynamic.' . $test_entity_id,
);
$renames = $this->configImporter->getUnprocessedConfiguration('rename');
$this->assertIdentical($expected, $renames);
// Try to import the configuration. We expect an exception to be thrown
// because the staged entity is of a different type.
try {
$this->configImporter->import();
$this->fail('Expected ConfigImporterException thrown when a renamed configuration entity does not match the existing entity type.');
}
catch (ConfigImporterException $e) {
$this->pass('Expected ConfigImporterException thrown when a renamed configuration entity does not match the existing entity type.');
$expected = array(
SafeMarkup::format('Entity type mismatch on rename. @old_type not equal to @new_type for existing configuration @old_name and staged configuration @new_name.', array('@old_type' => 'node_type', '@new_type' => 'config_test', '@old_name' => 'node.type.' . $content_type->id(), '@new_name' => 'config_test.dynamic.' . $test_entity_id))
);
$this->assertEqual($expected, $this->configImporter->getErrors());
}
}
/**
* Tests configuration renaming validation for simple configuration.
*/
public function testRenameSimpleConfigValidation() {
$uuid = new Php();
// Create a simple configuration with a UUID.
$config = $this->config('config_test.new');
$uuid_value = $uuid->generate();
$config->set('uuid', $uuid_value)->save();
$active = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
$this->copyConfig($active, $sync);
$config->delete();
// Create another simple configuration with the same UUID.
$config = $this->config('config_test.old');
$config->set('uuid', $uuid_value)->save();
// Confirm that the staged configuration is detected as a rename since the
// UUIDs match.
$this->configImporter->reset();
$expected = array(
'config_test.old::config_test.new'
);
$renames = $this->configImporter->getUnprocessedConfiguration('rename');
$this->assertIdentical($expected, $renames);
// Try to import the configuration. We expect an exception to be thrown
// because the rename is for simple configuration.
try {
$this->configImporter->import();
$this->fail('Expected ConfigImporterException thrown when simple configuration is renamed.');
}
catch (ConfigImporterException $e) {
$this->pass('Expected ConfigImporterException thrown when simple configuration is renamed.');
$expected = array(
SafeMarkup::format('Rename operation for simple configuration. Existing configuration @old_name and staged configuration @new_name.', array('@old_name' => 'config_test.old', '@new_name' => 'config_test.new'))
);
$this->assertEqual($expected, $this->configImporter->getErrors());
}
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\StorageComparer;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests importing configuration which has missing content dependencies.
*
* @group config
*/
class ConfigImporterMissingContentTest extends KernelTestBase {
/**
* Config Importer object used for testing.
*
* @var \Drupal\Core\Config\ConfigImporter
*/
protected $configImporter;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'user', 'entity_test', 'config_test', 'config_import_test');
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'sequences');
$this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$this->installConfig(array('config_test'));
// Installing config_test's default configuration pollutes the global
// variable being used for recording hook invocations by this test already,
// so it has to be cleared out manually.
unset($GLOBALS['hook_config_test']);
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
// Set up the ConfigImporter object for testing.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.sync'),
$this->container->get('config.storage'),
$this->container->get('config.manager')
);
$this->configImporter = new ConfigImporter(
$storage_comparer->createChangelist(),
$this->container->get('event_dispatcher'),
$this->container->get('config.manager'),
$this->container->get('lock'),
$this->container->get('config.typed'),
$this->container->get('module_handler'),
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation')
);
}
/**
* Tests the missing content event is fired.
*
* @see \Drupal\Core\Config\ConfigImporter::processMissingContent()
* @see \Drupal\config_import_test\EventSubscriber
*/
function testMissingContent() {
\Drupal::state()->set('config_import_test.config_import_missing_content', TRUE);
// Update a configuration entity in the sync directory to have a dependency
// on two content entities that do not exist.
$storage = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
$entity_one = EntityTest::create(array('name' => 'one'));
$entity_two = EntityTest::create(array('name' => 'two'));
$entity_three = EntityTest::create(array('name' => 'three'));
$dynamic_name = 'config_test.dynamic.dotted.default';
$original_dynamic_data = $storage->read($dynamic_name);
// Entity one will be resolved by
// \Drupal\config_import_test\EventSubscriber::onConfigImporterMissingContentOne().
$original_dynamic_data['dependencies']['content'][] = $entity_one->getConfigDependencyName();
// Entity two will be resolved by
// \Drupal\config_import_test\EventSubscriber::onConfigImporterMissingContentTwo().
$original_dynamic_data['dependencies']['content'][] = $entity_two->getConfigDependencyName();
// Entity three will be resolved by
// \Drupal\Core\Config\Importer\FinalMissingContentSubscriber.
$original_dynamic_data['dependencies']['content'][] = $entity_three->getConfigDependencyName();
$sync->write($dynamic_name, $original_dynamic_data);
// Import.
$this->configImporter->reset()->import();
$this->assertEqual([], $this->configImporter->getErrors(), 'There were no errors during the import.');
$this->assertEqual($entity_one->uuid(), \Drupal::state()->get('config_import_test.config_import_missing_content_one'), 'The missing content event is fired during configuration import.');
$this->assertEqual($entity_two->uuid(), \Drupal::state()->get('config_import_test.config_import_missing_content_two'), 'The missing content event is fired during configuration import.');
$original_dynamic_data = $storage->read($dynamic_name);
$this->assertEqual([$entity_one->getConfigDependencyName(), $entity_two->getConfigDependencyName(), $entity_three->getConfigDependencyName()], $original_dynamic_data['dependencies']['content'], 'The imported configuration entity has the missing content entity dependency.');
}
}

View file

@ -0,0 +1,689 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Config\StorageComparer;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests importing configuration from files into active configuration.
*
* @group config
*/
class ConfigImporterTest extends KernelTestBase {
/**
* Config Importer object used for testing.
*
* @var \Drupal\Core\Config\ConfigImporter
*/
protected $configImporter;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test', 'system', 'config_import_test');
protected function setUp() {
parent::setUp();
$this->installConfig(array('config_test'));
// Installing config_test's default configuration pollutes the global
// variable being used for recording hook invocations by this test already,
// so it has to be cleared out manually.
unset($GLOBALS['hook_config_test']);
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
// Set up the ConfigImporter object for testing.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.sync'),
$this->container->get('config.storage'),
$this->container->get('config.manager')
);
$this->configImporter = new ConfigImporter(
$storage_comparer->createChangelist(),
$this->container->get('event_dispatcher'),
$this->container->get('config.manager'),
$this->container->get('lock'),
$this->container->get('config.typed'),
$this->container->get('module_handler'),
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation')
);
}
/**
* Tests omission of module APIs for bare configuration operations.
*/
function testNoImport() {
$dynamic_name = 'config_test.dynamic.dotted.default';
// Verify the default configuration values exist.
$config = $this->config($dynamic_name);
$this->assertIdentical($config->get('id'), 'dotted.default');
// Verify that a bare $this->config() does not involve module APIs.
$this->assertFalse(isset($GLOBALS['hook_config_test']));
}
/**
* Tests that trying to import from an empty sync configuration directory
* fails.
*/
function testEmptyImportFails() {
try {
$this->container->get('config.storage.sync')->deleteAll();
$this->configImporter->reset()->import();
$this->fail('ConfigImporterException thrown, successfully stopping an empty import.');
}
catch (ConfigImporterException $e) {
$this->pass('ConfigImporterException thrown, successfully stopping an empty import.');
}
}
/**
* Tests verification of site UUID before importing configuration.
*/
function testSiteUuidValidate() {
$sync = \Drupal::service('config.storage.sync');
// Create updated configuration object.
$config_data = $this->config('system.site')->get();
// Generate a new site UUID.
$config_data['uuid'] = \Drupal::service('uuid')->generate();
$sync->write('system.site', $config_data);
try {
$this->configImporter->reset()->import();
$this->fail('ConfigImporterException not thrown, invalid import was not stopped due to mis-matching site UUID.');
}
catch (ConfigImporterException $e) {
$this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
$error_log = $this->configImporter->getErrors();
$expected = array('Site UUID in source storage does not match the target storage.');
$this->assertEqual($expected, $error_log);
}
}
/**
* Tests deletion of configuration during import.
*/
function testDeleted() {
$dynamic_name = 'config_test.dynamic.dotted.default';
$storage = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
// Verify the default configuration values exist.
$config = $this->config($dynamic_name);
$this->assertIdentical($config->get('id'), 'dotted.default');
// Delete the file from the sync directory.
$sync->delete($dynamic_name);
// Import.
$this->configImporter->reset()->import();
// Verify the file has been removed.
$this->assertIdentical($storage->read($dynamic_name), FALSE);
$config = $this->config($dynamic_name);
$this->assertIdentical($config->get('id'), NULL);
// Verify that appropriate module API hooks have been invoked.
$this->assertTrue(isset($GLOBALS['hook_config_test']['load']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['presave']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['insert']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['update']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['predelete']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
$this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
$logs = $this->configImporter->getErrors();
$this->assertEqual(count($logs), 0);
}
/**
* Tests creation of configuration during import.
*/
function testNew() {
$dynamic_name = 'config_test.dynamic.new';
$storage = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
// Verify the configuration to create does not exist yet.
$this->assertIdentical($storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
// Create new config entity.
$original_dynamic_data = array(
'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651',
'langcode' => \Drupal::languageManager()->getDefaultLanguage()->getId(),
'status' => TRUE,
'dependencies' => array(),
'id' => 'new',
'label' => 'New',
'weight' => 0,
'style' => '',
'size' => '',
'size_value' => '',
'protected_property' => '',
);
$sync->write($dynamic_name, $original_dynamic_data);
$this->assertIdentical($sync->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
// Import.
$this->configImporter->reset()->import();
// Verify the values appeared.
$config = $this->config($dynamic_name);
$this->assertIdentical($config->get('label'), $original_dynamic_data['label']);
// Verify that appropriate module API hooks have been invoked.
$this->assertFalse(isset($GLOBALS['hook_config_test']['load']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['presave']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['insert']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['update']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
// Verify that hook_config_import_steps_alter() can add steps to
// configuration synchronization.
$this->assertTrue(isset($GLOBALS['hook_config_test']['config_import_steps_alter']));
// Verify that there is nothing more to import.
$this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
$logs = $this->configImporter->getErrors();
$this->assertEqual(count($logs), 0);
}
/**
* Tests that secondary writes are overwritten.
*/
function testSecondaryWritePrimaryFirst() {
$name_primary = 'config_test.dynamic.primary';
$name_secondary = 'config_test.dynamic.secondary';
$sync = $this->container->get('config.storage.sync');
$uuid = $this->container->get('uuid');
$values_primary = array(
'id' => 'primary',
'label' => 'Primary',
'weight' => 0,
'uuid' => $uuid->generate(),
);
$sync->write($name_primary, $values_primary);
$values_secondary = array(
'id' => 'secondary',
'label' => 'Secondary Sync',
'weight' => 0,
'uuid' => $uuid->generate(),
// Add a dependency on primary, to ensure that is synced first.
'dependencies' => array(
'config' => array($name_primary),
)
);
$sync->write($name_secondary, $values_secondary);
// Import.
$this->configImporter->reset()->import();
$entity_storage = \Drupal::entityManager()->getStorage('config_test');
$primary = $entity_storage->load('primary');
$this->assertEqual($primary->id(), 'primary');
$this->assertEqual($primary->uuid(), $values_primary['uuid']);
$this->assertEqual($primary->label(), $values_primary['label']);
$secondary = $entity_storage->load('secondary');
$this->assertEqual($secondary->id(), 'secondary');
$this->assertEqual($secondary->uuid(), $values_secondary['uuid']);
$this->assertEqual($secondary->label(), $values_secondary['label']);
$logs = $this->configImporter->getErrors();
$this->assertEqual(count($logs), 1);
$this->assertEqual($logs[0], SafeMarkup::format('Deleted and replaced configuration entity "@name"', array('@name' => $name_secondary)));
}
/**
* Tests that secondary writes are overwritten.
*/
function testSecondaryWriteSecondaryFirst() {
$name_primary = 'config_test.dynamic.primary';
$name_secondary = 'config_test.dynamic.secondary';
$sync = $this->container->get('config.storage.sync');
$uuid = $this->container->get('uuid');
$values_primary = array(
'id' => 'primary',
'label' => 'Primary',
'weight' => 0,
'uuid' => $uuid->generate(),
// Add a dependency on secondary, so that is synced first.
'dependencies' => array(
'config' => array($name_secondary),
)
);
$sync->write($name_primary, $values_primary);
$values_secondary = array(
'id' => 'secondary',
'label' => 'Secondary Sync',
'weight' => 0,
'uuid' => $uuid->generate(),
);
$sync->write($name_secondary, $values_secondary);
// Import.
$this->configImporter->reset()->import();
$entity_storage = \Drupal::entityManager()->getStorage('config_test');
$primary = $entity_storage->load('primary');
$this->assertEqual($primary->id(), 'primary');
$this->assertEqual($primary->uuid(), $values_primary['uuid']);
$this->assertEqual($primary->label(), $values_primary['label']);
$secondary = $entity_storage->load('secondary');
$this->assertEqual($secondary->id(), 'secondary');
$this->assertEqual($secondary->uuid(), $values_secondary['uuid']);
$this->assertEqual($secondary->label(), $values_secondary['label']);
$logs = $this->configImporter->getErrors();
$this->assertEqual(count($logs), 1);
$this->assertEqual($logs[0], Html::escape("Unexpected error during import with operation create for $name_primary: 'config_test' entity with ID 'secondary' already exists."));
}
/**
* Tests that secondary updates for deleted files work as expected.
*/
function testSecondaryUpdateDeletedDeleterFirst() {
$name_deleter = 'config_test.dynamic.deleter';
$name_deletee = 'config_test.dynamic.deletee';
$name_other = 'config_test.dynamic.other';
$storage = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
$uuid = $this->container->get('uuid');
$values_deleter = array(
'id' => 'deleter',
'label' => 'Deleter',
'weight' => 0,
'uuid' => $uuid->generate(),
);
$storage->write($name_deleter, $values_deleter);
$values_deleter['label'] = 'Updated Deleter';
$sync->write($name_deleter, $values_deleter);
$values_deletee = array(
'id' => 'deletee',
'label' => 'Deletee',
'weight' => 0,
'uuid' => $uuid->generate(),
// Add a dependency on deleter, to make sure that is synced first.
'dependencies' => array(
'config' => array($name_deleter),
)
);
$storage->write($name_deletee, $values_deletee);
$values_deletee['label'] = 'Updated Deletee';
$sync->write($name_deletee, $values_deletee);
// Ensure that import will continue after the error.
$values_other = array(
'id' => 'other',
'label' => 'Other',
'weight' => 0,
'uuid' => $uuid->generate(),
// Add a dependency on deleter, to make sure that is synced first. This
// will also be synced after the deletee due to alphabetical ordering.
'dependencies' => array(
'config' => array($name_deleter),
)
);
$storage->write($name_other, $values_other);
$values_other['label'] = 'Updated other';
$sync->write($name_other, $values_other);
// Check update changelist order.
$updates = $this->configImporter->reset()->getStorageComparer()->getChangelist('update');
$expected = array(
$name_deleter,
$name_deletee,
$name_other,
);
$this->assertIdentical($expected, $updates);
// Import.
$this->configImporter->import();
$entity_storage = \Drupal::entityManager()->getStorage('config_test');
$deleter = $entity_storage->load('deleter');
$this->assertEqual($deleter->id(), 'deleter');
$this->assertEqual($deleter->uuid(), $values_deleter['uuid']);
$this->assertEqual($deleter->label(), $values_deleter['label']);
// The deletee was deleted in
// \Drupal\config_test\Entity\ConfigTest::postSave().
$this->assertFalse($entity_storage->load('deletee'));
$other = $entity_storage->load('other');
$this->assertEqual($other->id(), 'other');
$this->assertEqual($other->uuid(), $values_other['uuid']);
$this->assertEqual($other->label(), $values_other['label']);
$logs = $this->configImporter->getErrors();
$this->assertEqual(count($logs), 1);
$this->assertEqual($logs[0], SafeMarkup::format('Update target "@name" is missing.', array('@name' => $name_deletee)));
}
/**
* Tests that secondary updates for deleted files work as expected.
*
* This test is completely hypothetical since we only support full
* configuration tree imports. Therefore, any configuration updates that cause
* secondary deletes should be reflected already in the staged configuration.
*/
function testSecondaryUpdateDeletedDeleteeFirst() {
$name_deleter = 'config_test.dynamic.deleter';
$name_deletee = 'config_test.dynamic.deletee';
$storage = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
$uuid = $this->container->get('uuid');
$values_deleter = array(
'id' => 'deleter',
'label' => 'Deleter',
'weight' => 0,
'uuid' => $uuid->generate(),
// Add a dependency on deletee, to make sure that is synced first.
'dependencies' => array(
'config' => array($name_deletee),
),
);
$storage->write($name_deleter, $values_deleter);
$values_deleter['label'] = 'Updated Deleter';
$sync->write($name_deleter, $values_deleter);
$values_deletee = array(
'id' => 'deletee',
'label' => 'Deletee',
'weight' => 0,
'uuid' => $uuid->generate(),
);
$storage->write($name_deletee, $values_deletee);
$values_deletee['label'] = 'Updated Deletee';
$sync->write($name_deletee, $values_deletee);
// Import.
$this->configImporter->reset()->import();
$entity_storage = \Drupal::entityManager()->getStorage('config_test');
// Both entities are deleted. ConfigTest::postSave() causes updates of the
// deleter entity to delete the deletee entity. Since the deleter depends on
// the deletee, removing the deletee causes the deleter to be removed.
$this->assertFalse($entity_storage->load('deleter'));
$this->assertFalse($entity_storage->load('deletee'));
$logs = $this->configImporter->getErrors();
$this->assertEqual(count($logs), 0);
}
/**
* Tests that secondary deletes for deleted files work as expected.
*/
function testSecondaryDeletedDeleteeSecond() {
$name_deleter = 'config_test.dynamic.deleter';
$name_deletee = 'config_test.dynamic.deletee';
$storage = $this->container->get('config.storage');
$uuid = $this->container->get('uuid');
$values_deleter = array(
'id' => 'deleter',
'label' => 'Deleter',
'weight' => 0,
'uuid' => $uuid->generate(),
// Add a dependency on deletee, to make sure this delete is synced first.
'dependencies' => array(
'config' => array($name_deletee),
),
);
$storage->write($name_deleter, $values_deleter);
$values_deletee = array(
'id' => 'deletee',
'label' => 'Deletee',
'weight' => 0,
'uuid' => $uuid->generate(),
);
$storage->write($name_deletee, $values_deletee);
// Import.
$this->configImporter->reset()->import();
$entity_storage = \Drupal::entityManager()->getStorage('config_test');
$this->assertFalse($entity_storage->load('deleter'));
$this->assertFalse($entity_storage->load('deletee'));
// The deletee entity does not exist as the delete worked and although the
// delete occurred in \Drupal\config_test\Entity\ConfigTest::postDelete()
// this does not matter.
$logs = $this->configImporter->getErrors();
$this->assertEqual(count($logs), 0);
}
/**
* Tests updating of configuration during import.
*/
function testUpdated() {
$name = 'config_test.system';
$dynamic_name = 'config_test.dynamic.dotted.default';
$storage = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
// Verify that the configuration objects to import exist.
$this->assertIdentical($storage->exists($name), TRUE, $name . ' found.');
$this->assertIdentical($storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
// Replace the file content of the existing configuration objects in the
// sync directory.
$original_name_data = array(
'foo' => 'beer',
);
$sync->write($name, $original_name_data);
$original_dynamic_data = $storage->read($dynamic_name);
$original_dynamic_data['label'] = 'Updated';
$sync->write($dynamic_name, $original_dynamic_data);
// Verify the active configuration still returns the default values.
$config = $this->config($name);
$this->assertIdentical($config->get('foo'), 'bar');
$config = $this->config($dynamic_name);
$this->assertIdentical($config->get('label'), 'Default');
// Import.
$this->configImporter->reset()->import();
// Verify the values were updated.
\Drupal::configFactory()->reset($name);
$config = $this->config($name);
$this->assertIdentical($config->get('foo'), 'beer');
$config = $this->config($dynamic_name);
$this->assertIdentical($config->get('label'), 'Updated');
// Verify that the original file content is still the same.
$this->assertIdentical($sync->read($name), $original_name_data);
$this->assertIdentical($sync->read($dynamic_name), $original_dynamic_data);
// Verify that appropriate module API hooks have been invoked.
$this->assertTrue(isset($GLOBALS['hook_config_test']['load']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['presave']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['insert']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['update']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
// Verify that there is nothing more to import.
$this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
$logs = $this->configImporter->getErrors();
$this->assertEqual(count($logs), 0);
}
/**
* Tests the isInstallable method()
*/
function testIsInstallable() {
$config_name = 'config_test.dynamic.isinstallable';
$this->assertFalse($this->container->get('config.storage')->exists($config_name));
\Drupal::state()->set('config_test.isinstallable', TRUE);
$this->installConfig(array('config_test'));
$this->assertTrue($this->container->get('config.storage')->exists($config_name));
}
/**
* Tests dependency validation during configuration import.
*
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
* @see \Drupal\Core\Config\ConfigImporter::createExtensionChangelist()
*/
public function testUnmetDependency() {
$storage = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
// Test an unknown configuration owner.
$sync->write('unknown.config', ['test' => 'test']);
// Make a config entity have unmet dependencies.
$config_entity_data = $sync->read('config_test.dynamic.dotted.default');
$config_entity_data['dependencies'] = ['module' => ['unknown']];
$sync->write('config_test.dynamic.dotted.module', $config_entity_data);
$config_entity_data['dependencies'] = ['theme' => ['unknown']];
$sync->write('config_test.dynamic.dotted.theme', $config_entity_data);
$config_entity_data['dependencies'] = ['config' => ['unknown']];
$sync->write('config_test.dynamic.dotted.config', $config_entity_data);
// Make an active config depend on something that is missing in sync.
// The whole configuration needs to be consistent, not only the updated one.
$config_entity_data['dependencies'] = [];
$storage->write('config_test.dynamic.dotted.deleted', $config_entity_data);
$config_entity_data['dependencies'] = ['config' => ['config_test.dynamic.dotted.deleted']];
$storage->write('config_test.dynamic.dotted.existing', $config_entity_data);
$sync->write('config_test.dynamic.dotted.existing', $config_entity_data);
$extensions = $sync->read('core.extension');
// Add a module and a theme that do not exist.
$extensions['module']['unknown_module'] = 0;
$extensions['theme']['unknown_theme'] = 0;
// Add a module and a theme that depend on uninstalled extensions.
$extensions['module']['book'] = 0;
$extensions['theme']['bartik'] = 0;
$sync->write('core.extension', $extensions);
try {
$this->configImporter->reset()->import();
$this->fail('ConfigImporterException not thrown; an invalid import was not stopped due to missing dependencies.');
}
catch (ConfigImporterException $e) {
$this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
$error_log = $this->configImporter->getErrors();
$expected = [
'Unable to install the <em class="placeholder">unknown_module</em> module since it does not exist.',
'Unable to install the <em class="placeholder">Book</em> module since it requires the <em class="placeholder">Node, Text, Field, Filter, User</em> modules.',
'Unable to install the <em class="placeholder">unknown_theme</em> theme since it does not exist.',
'Unable to install the <em class="placeholder">Bartik</em> theme since it requires the <em class="placeholder">Classy</em> theme.',
'Configuration <em class="placeholder">config_test.dynamic.dotted.config</em> depends on the <em class="placeholder">unknown</em> configuration that will not exist after import.',
'Configuration <em class="placeholder">config_test.dynamic.dotted.existing</em> depends on the <em class="placeholder">config_test.dynamic.dotted.deleted</em> configuration that will not exist after import.',
'Configuration <em class="placeholder">config_test.dynamic.dotted.module</em> depends on the <em class="placeholder">unknown</em> module that will not be installed after import.',
'Configuration <em class="placeholder">config_test.dynamic.dotted.theme</em> depends on the <em class="placeholder">unknown</em> theme that will not be installed after import.',
'Configuration <em class="placeholder">unknown.config</em> depends on the <em class="placeholder">unknown</em> extension that will not be installed after import.',
];
foreach ($expected as $expected_message) {
$this->assertTrue(in_array($expected_message, $error_log), $expected_message);
}
}
// Make a config entity have mulitple unmet dependencies.
$config_entity_data = $sync->read('config_test.dynamic.dotted.default');
$config_entity_data['dependencies'] = ['module' => ['unknown', 'dblog']];
$sync->write('config_test.dynamic.dotted.module', $config_entity_data);
$config_entity_data['dependencies'] = ['theme' => ['unknown', 'seven']];
$sync->write('config_test.dynamic.dotted.theme', $config_entity_data);
$config_entity_data['dependencies'] = ['config' => ['unknown', 'unknown2']];
$sync->write('config_test.dynamic.dotted.config', $config_entity_data);
try {
$this->configImporter->reset()->import();
$this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing dependencies.');
}
catch (ConfigImporterException $e) {
$this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
$error_log = $this->configImporter->getErrors();
$expected = [
'Configuration <em class="placeholder">config_test.dynamic.dotted.config</em> depends on configuration (<em class="placeholder">unknown, unknown2</em>) that will not exist after import.',
'Configuration <em class="placeholder">config_test.dynamic.dotted.module</em> depends on modules (<em class="placeholder">unknown, Database Logging</em>) that will not be installed after import.',
'Configuration <em class="placeholder">config_test.dynamic.dotted.theme</em> depends on themes (<em class="placeholder">unknown, Seven</em>) that will not be installed after import.',
];
foreach ($expected as $expected_message) {
$this->assertTrue(in_array($expected_message, $error_log), $expected_message);
}
}
}
/**
* Tests missing core.extension during configuration import.
*
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
*/
public function testMissingCoreExtension() {
$sync = $this->container->get('config.storage.sync');
$sync->delete('core.extension');
try {
$this->configImporter->reset()->import();
$this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing dependencies.');
}
catch (ConfigImporterException $e) {
$this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
$error_log = $this->configImporter->getErrors();
$this->assertEqual(['The core.extension configuration does not exist.'], $error_log);
}
}
/**
* Tests install profile validation during configuration import.
*
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
*/
public function testInstallProfile() {
$sync = $this->container->get('config.storage.sync');
$extensions = $sync->read('core.extension');
// Add an install profile.
$extensions['module']['standard'] = 0;
$sync->write('core.extension', $extensions);
try {
$this->configImporter->reset()->import();
$this->fail('ConfigImporterException not thrown; an invalid import was not stopped due to missing dependencies.');
}
catch (ConfigImporterException $e) {
$this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
$error_log = $this->configImporter->getErrors();
// Install profiles should not even be scanned at this point.
$this->assertEqual(['Unable to install the <em class="placeholder">standard</em> module since it does not exist.'], $error_log);
}
}
/**
* Tests config_get_config_directory().
*/
public function testConfigGetConfigDirectory() {
global $config_directories;
$directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
$this->assertEqual($config_directories[CONFIG_SYNC_DIRECTORY], $directory);
$message = 'Calling config_get_config_directory() with CONFIG_ACTIVE_DIRECTORY results in an exception.';
try {
config_get_config_directory(CONFIG_ACTIVE_DIRECTORY);
$this->fail($message);
}
catch (\Exception $e) {
$this->pass($message);
}
}
}

View file

@ -0,0 +1,248 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Config\PreExistingConfigException;
use Drupal\Core\Config\UnmetDependenciesException;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests installation of configuration objects in installation functionality.
*
* @group config
* @see \Drupal\Core\Config\ConfigInstaller
*/
class ConfigInstallTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Ensure the global variable being asserted by this test does not exist;
// a previous test executed in this request/process might have set it.
unset($GLOBALS['hook_config_test']);
}
/**
* Tests module installation.
*/
function testModuleInstallation() {
$default_config = 'config_test.system';
$default_configuration_entity = 'config_test.dynamic.dotted.default';
// Verify that default module config does not exist before installation yet.
$config = $this->config($default_config);
$this->assertIdentical($config->isNew(), TRUE);
$config = $this->config($default_configuration_entity);
$this->assertIdentical($config->isNew(), TRUE);
// Ensure that schema provided by modules that are not installed is not
// available.
$this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'), 'Configuration schema for config_schema_test.someschema does not exist.');
// Install the test module.
$this->installModules(array('config_test'));
// Verify that default module config exists.
\Drupal::configFactory()->reset($default_config);
\Drupal::configFactory()->reset($default_configuration_entity);
$config = $this->config($default_config);
$this->assertIdentical($config->isNew(), FALSE);
$config = $this->config($default_configuration_entity);
$this->assertIdentical($config->isNew(), FALSE);
// Verify that config_test API hooks were invoked for the dynamic default
// configuration entity.
$this->assertFalse(isset($GLOBALS['hook_config_test']['load']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['presave']));
$this->assertTrue(isset($GLOBALS['hook_config_test']['insert']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['update']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
// Install the schema test module.
$this->enableModules(array('config_schema_test'));
$this->installConfig(array('config_schema_test'));
// After module installation the new schema should exist.
$this->assertTrue(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'), 'Configuration schema for config_schema_test.someschema exists.');
// Test that uninstalling configuration removes configuration schema.
$this->config('core.extension')->set('module', array())->save();
\Drupal::service('config.manager')->uninstall('module', 'config_test');
$this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'), 'Configuration schema for config_schema_test.someschema does not exist.');
}
/**
* Tests that collections are ignored if the event does not return anything.
*/
public function testCollectionInstallationNoCollections() {
// Install the test module.
$this->enableModules(array('config_collection_install_test'));
$this->installConfig(array('config_collection_install_test'));
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
$active_storage = \Drupal::service('config.storage');
$this->assertEqual(array(), $active_storage->getAllCollectionNames());
}
/**
* Tests config objects in collections are installed as expected.
*/
public function testCollectionInstallationCollections() {
$collections = array(
'another_collection',
'collection.test1',
'collection.test2',
);
// Set the event listener to return three possible collections.
// @see \Drupal\config_collection_install_test\EventSubscriber
\Drupal::state()->set('config_collection_install_test.collection_names', $collections);
// Install the test module.
$this->enableModules(array('config_collection_install_test'));
$this->installConfig(array('config_collection_install_test'));
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
$active_storage = \Drupal::service('config.storage');
$this->assertEqual($collections, $active_storage->getAllCollectionNames());
foreach ($collections as $collection) {
$collection_storage = $active_storage->createCollection($collection);
$data = $collection_storage->read('config_collection_install_test.test');
$this->assertEqual($collection, $data['collection']);
}
// Tests that clashing configuration in collections is detected.
try {
\Drupal::service('module_installer')->install(['config_collection_clash_install_test']);
$this->fail('Expected PreExistingConfigException not thrown.');
}
catch (PreExistingConfigException $e) {
$this->assertEqual($e->getExtension(), 'config_collection_clash_install_test');
$this->assertEqual($e->getConfigObjects(), [
'another_collection' => ['config_collection_install_test.test'],
'collection.test1' => ['config_collection_install_test.test'],
'collection.test2' => ['config_collection_install_test.test'],
]);
$this->assertEqual($e->getMessage(), 'Configuration objects (another_collection/config_collection_install_test.test, collection/test1/config_collection_install_test.test, collection/test2/config_collection_install_test.test) provided by config_collection_clash_install_test already exist in active configuration');
}
// Test that the we can use the config installer to install all the
// available default configuration in a particular collection for enabled
// extensions.
\Drupal::service('config.installer')->installCollectionDefaultConfig('entity');
// The 'entity' collection will not exist because the 'config_test' module
// is not enabled.
$this->assertEqual($collections, $active_storage->getAllCollectionNames());
// Enable the 'config_test' module and try again.
$this->enableModules(array('config_test'));
\Drupal::service('config.installer')->installCollectionDefaultConfig('entity');
$collections[] = 'entity';
$this->assertEqual($collections, $active_storage->getAllCollectionNames());
$collection_storage = $active_storage->createCollection('entity');
$data = $collection_storage->read('config_test.dynamic.dotted.default');
$this->assertIdentical(array('label' => 'entity'), $data);
// Test that the config manager uninstalls configuration from collections
// as expected.
\Drupal::service('config.manager')->uninstall('module', 'config_collection_install_test');
$this->assertEqual(array('entity'), $active_storage->getAllCollectionNames());
\Drupal::service('config.manager')->uninstall('module', 'config_test');
$this->assertEqual(array(), $active_storage->getAllCollectionNames());
}
/**
* Tests collections which do not support config entities install correctly.
*
* Config entity detection during config installation is done by matching
* config name prefixes. If a collection provides a configuration with a
* matching name but does not support config entities it should be created
* using simple configuration.
*/
public function testCollectionInstallationCollectionConfigEntity() {
$collections = array(
'entity',
);
\Drupal::state()->set('config_collection_install_test.collection_names', $collections);
// Install the test module.
$this->installModules(array('config_test', 'config_collection_install_test'));
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
$active_storage = \Drupal::service('config.storage');
$this->assertEqual($collections, $active_storage->getAllCollectionNames());
$collection_storage = $active_storage->createCollection('entity');
// The config_test.dynamic.dotted.default configuration object saved in the
// active store should be a configuration entity complete with UUID. Because
// the entity collection does not support configuration entities the
// configuration object stored there with the same name should only contain
// a label.
$name = 'config_test.dynamic.dotted.default';
$data = $active_storage->read($name);
$this->assertTrue(isset($data['uuid']));
$data = $collection_storage->read($name);
$this->assertIdentical(array('label' => 'entity'), $data);
}
/**
* Tests the configuration with unmet dependencies is not installed.
*/
public function testDependencyChecking() {
$this->installModules(['config_test']);
try {
$this->installModules(['config_install_dependency_test']);
$this->fail('Expected UnmetDependenciesException not thrown.');
}
catch (UnmetDependenciesException $e) {
$this->assertEqual($e->getExtension(), 'config_install_dependency_test');
$this->assertEqual($e->getConfigObjects(), ['config_test.dynamic.other_module_test_with_dependency']);
$this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.other_module_test_with_dependency) provided by config_install_dependency_test have unmet dependencies');
}
$this->installModules(['config_other_module_config_test']);
$this->installModules(['config_install_dependency_test']);
$entity = \Drupal::entityManager()->getStorage('config_test')->load('other_module_test_with_dependency');
$this->assertTrue($entity, 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.');
// Ensure that dependencies can be added during module installation by
// hooks.
$this->assertIdentical('config_install_dependency_test', $entity->getDependencies()['module'][0]);
}
/**
* Tests imported configuration entities with and without language information.
*/
function testLanguage() {
$this->installModules(['config_test_language']);
// Test imported configuration with implicit language code.
$storage = new InstallStorage();
$data = $storage->read('config_test.dynamic.dotted.english');
$this->assertTrue(!isset($data['langcode']));
$this->assertEqual(
$this->config('config_test.dynamic.dotted.english')->get('langcode'),
'en'
);
// Test imported configuration with explicit language code.
$data = $storage->read('config_test.dynamic.dotted.french');
$this->assertEqual($data['langcode'], 'fr');
$this->assertEqual(
$this->config('config_test.dynamic.dotted.french')->get('langcode'),
'fr'
);
}
/**
* Installs a module.
*
* @param array $modules
* The module names.
*/
protected function installModules(array $modules) {
$this->container->get('module_installer')->install($modules);
$this->container = \Drupal::getContainer();
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\KernelTests\KernelTestBase;
/**
* Confirm that language overrides work.
*
* @group config
*/
class ConfigLanguageOverrideTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('user', 'language', 'config_test', 'system', 'field');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('config_test'));
}
/**
* Tests locale override based on language.
*/
function testConfigLanguageOverride() {
// The language module implements a config factory override object that
// overrides configuration when the Language module is enabled. This test ensures that
// English overrides work.
\Drupal::languageManager()->setConfigOverrideLanguage(\Drupal::languageManager()->getLanguage('en'));
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), 'en bar');
// Ensure that the raw data is not translated.
$raw = $config->getRawData();
$this->assertIdentical($raw['foo'], 'bar');
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('de')->save();
\Drupal::languageManager()->setConfigOverrideLanguage(\Drupal::languageManager()->getLanguage('fr'));
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), 'fr bar');
\Drupal::languageManager()->setConfigOverrideLanguage(\Drupal::languageManager()->getLanguage('de'));
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), 'de bar');
// Test overrides of completely new configuration objects. In normal runtime
// this should only happen for configuration entities as we should not be
// creating simple configuration objects on the fly.
\Drupal::languageManager()
->getLanguageConfigOverride('de', 'config_test.new')
->set('language', 'override')
->save();
$config = \Drupal::config('config_test.new');
$this->assertTrue($config->isNew(), 'The configuration object config_test.new is new');
$this->assertIdentical($config->get('language'), 'override');
$this->assertIdentical($config->getOriginal('language', FALSE), NULL);
// Test how overrides react to base configuration changes. Set up some base
// values.
\Drupal::configFactory()->getEditable('config_test.foo')
->set('value', array('key' => 'original'))
->set('label', 'Original')
->save();
\Drupal::languageManager()
->getLanguageConfigOverride('de', 'config_test.foo')
->set('value', array('key' => 'override'))
->set('label', 'Override')
->save();
\Drupal::languageManager()
->getLanguageConfigOverride('fr', 'config_test.foo')
->set('value', array('key' => 'override'))
->save();
\Drupal::configFactory()->clearStaticCache();
$config = \Drupal::config('config_test.foo');
$this->assertIdentical($config->get('value'), array('key' => 'override'));
// Ensure renaming the config will rename the override.
\Drupal::languageManager()->setConfigOverrideLanguage(\Drupal::languageManager()->getLanguage('en'));
\Drupal::configFactory()->rename('config_test.foo', 'config_test.bar');
$config = \Drupal::config('config_test.bar');
$this->assertEqual($config->get('value'), array('key' => 'original'));
$override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'config_test.foo');
$this->assertTrue($override->isNew());
$this->assertEqual($override->get('value'), NULL);
$override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'config_test.bar');
$this->assertFalse($override->isNew());
$this->assertEqual($override->get('value'), array('key' => 'override'));
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'config_test.bar');
$this->assertFalse($override->isNew());
$this->assertEqual($override->get('value'), array('key' => 'override'));
// Ensure changing data in the config will update the overrides.
$config = \Drupal::configFactory()->getEditable('config_test.bar')->clear('value.key')->save();
$this->assertEqual($config->get('value'), array());
$override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'config_test.bar');
$this->assertFalse($override->isNew());
$this->assertEqual($override->get('value'), NULL);
// The French override will become empty and therefore removed.
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'config_test.bar');
$this->assertTrue($override->isNew());
$this->assertEqual($override->get('value'), NULL);
// Ensure deleting the config will delete the override.
\Drupal::configFactory()->getEditable('config_test.bar')->delete();
$override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'config_test.bar');
$this->assertTrue($override->isNew());
$this->assertEqual($override->get('value'), NULL);
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests module overrides of configuration using event subscribers.
*
* @group config
*/
class ConfigModuleOverridesTest extends KernelTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('system', 'config', 'config_override_test');
public function testSimpleModuleOverrides() {
$GLOBALS['config_test_run_module_overrides'] = TRUE;
$name = 'system.site';
$overridden_name = 'ZOMG overridden site name';
$non_overridden_name = 'ZOMG this name is on disk mkay';
$overridden_slogan = 'Yay for overrides!';
$non_overridden_slogan = 'Yay for defaults!';
$config_factory = $this->container->get('config.factory');
$config_factory
->getEditable($name)
->set('name', $non_overridden_name)
->set('slogan', $non_overridden_slogan)
->save();
$this->assertEqual($non_overridden_name, $config_factory->get('system.site')->getOriginal('name', FALSE));
$this->assertEqual($non_overridden_slogan, $config_factory->get('system.site')->getOriginal('slogan', FALSE));
$this->assertEqual($overridden_name, $config_factory->get('system.site')->get('name'));
$this->assertEqual($overridden_slogan, $config_factory->get('system.site')->get('slogan'));
// Test overrides of completely new configuration objects. In normal runtime
// this should only happen for configuration entities as we should not be
// creating simple configuration objects on the fly.
$config = $config_factory->get('config_override_test.new');
$this->assertTrue($config->isNew(), 'The configuration object config_override_test.new is new');
$this->assertIdentical($config->get('module'), 'override');
$this->assertIdentical($config->getOriginal('module', FALSE), NULL);
unset($GLOBALS['config_test_run_module_overrides']);
}
}

View file

@ -0,0 +1,138 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests configuration overrides via $config in settings.php.
*
* @group config
*/
class ConfigOverrideTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'config_test');
protected function setUp() {
parent::setUp();
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
}
/**
* Tests configuration override.
*/
function testConfOverride() {
$expected_original_data = array(
'foo' => 'bar',
'baz' => NULL,
'404' => 'herp',
);
// Set globals before installing to prove that the installed file does not
// contain these values.
$overrides['config_test.system']['foo'] = 'overridden';
$overrides['config_test.system']['baz'] = 'injected';
$overrides['config_test.system']['404'] = 'derp';
$GLOBALS['config'] = $overrides;
$this->installConfig(array('config_test'));
// Verify that the original configuration data exists. Have to read storage
// directly otherwise overrides will apply.
$active = $this->container->get('config.storage');
$data = $active->read('config_test.system');
$this->assertIdentical($data['foo'], $expected_original_data['foo']);
$this->assertFalse(isset($data['baz']));
$this->assertIdentical($data['404'], $expected_original_data['404']);
// Get the configuration object with overrides.
$config = \Drupal::configFactory()->get('config_test.system');
// Verify that it contains the overridden data from $config.
$this->assertIdentical($config->get('foo'), $overrides['config_test.system']['foo']);
$this->assertIdentical($config->get('baz'), $overrides['config_test.system']['baz']);
$this->assertIdentical($config->get('404'), $overrides['config_test.system']['404']);
// Get the configuration object which does not have overrides.
$config = \Drupal::configFactory()->getEditable('config_test.system');
// Verify that it does not contains the overridden data from $config.
$this->assertIdentical($config->get('foo'), $expected_original_data['foo']);
$this->assertIdentical($config->get('baz'), NULL);
$this->assertIdentical($config->get('404'), $expected_original_data['404']);
// Set the value for 'baz' (on the original data).
$expected_original_data['baz'] = 'original baz';
$config->set('baz', $expected_original_data['baz']);
// Set the value for '404' (on the original data).
$expected_original_data['404'] = 'original 404';
$config->set('404', $expected_original_data['404']);
// Save the configuration object (having overrides applied).
$config->save();
// Reload it and verify that it still contains overridden data from $config.
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), $overrides['config_test.system']['foo']);
$this->assertIdentical($config->get('baz'), $overrides['config_test.system']['baz']);
$this->assertIdentical($config->get('404'), $overrides['config_test.system']['404']);
// Verify that raw config data has changed.
$this->assertIdentical($config->getOriginal('foo', FALSE), $expected_original_data['foo']);
$this->assertIdentical($config->getOriginal('baz', FALSE), $expected_original_data['baz']);
$this->assertIdentical($config->getOriginal('404', FALSE), $expected_original_data['404']);
// Write file to sync.
$sync = $this->container->get('config.storage.sync');
$expected_new_data = array(
'foo' => 'barbar',
'404' => 'herpderp',
);
$sync->write('config_test.system', $expected_new_data);
// Import changed data from sync to active.
$this->configImporter()->import();
$data = $active->read('config_test.system');
// Verify that the new configuration data exists. Have to read storage
// directly otherwise overrides will apply.
$this->assertIdentical($data['foo'], $expected_new_data['foo']);
$this->assertFalse(isset($data['baz']));
$this->assertIdentical($data['404'], $expected_new_data['404']);
// Verify that the overrides are still working.
$config = \Drupal::config('config_test.system');
$this->assertIdentical($config->get('foo'), $overrides['config_test.system']['foo']);
$this->assertIdentical($config->get('baz'), $overrides['config_test.system']['baz']);
$this->assertIdentical($config->get('404'), $overrides['config_test.system']['404']);
// Test overrides of completely new configuration objects. In normal runtime
// this should only happen for configuration entities as we should not be
// creating simple configuration objects on the fly.
$GLOBALS['config']['config_test.new']['key'] = 'override';
$config = \Drupal::config('config_test.new');
$this->assertTrue($config->isNew(), 'The configuration object config_test.new is new');
$this->assertIdentical($config->get('key'), 'override');
$config_raw = \Drupal::configFactory()->getEditable('config_test.new');
$this->assertIdentical($config_raw->get('key'), NULL);
$config_raw
->set('key', 'raw')
->set('new_key', 'new_value')
->save();
// Ensure override is preserved but all other data has been updated
// accordingly.
$config = \Drupal::config('config_test.new');
$this->assertFalse($config->isNew(), 'The configuration object config_test.new is not new');
$this->assertIdentical($config->get('key'), 'override');
$this->assertIdentical($config->get('new_key'), 'new_value');
$raw_data = $config->getRawData();
$this->assertIdentical($raw_data['key'], 'raw');
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Language\Language;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that language, module and settings.php are applied in the correct
* order.
*
* @group config
*/
class ConfigOverridesPriorityTest extends KernelTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('system', 'config', 'config_override_test', 'language');
public function testOverridePriorities() {
$GLOBALS['config_test_run_module_overrides'] = FALSE;
$non_overridden_mail = 'site@example.com';
$language_overridden_mail = 'french@example.com';
$language_overridden_name = 'French site name';
$module_overridden_name = 'ZOMG overridden site name';
$non_overridden_name = 'ZOMG this name is on disk mkay';
$module_overridden_slogan = 'Yay for overrides!';
$non_overridden_slogan = 'Yay for defaults!';
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
$config_factory = $this->container->get('config.factory');
$config_factory
->getEditable('system.site')
->set('name', $non_overridden_name)
->set('slogan', $non_overridden_slogan)
->set('mail', $non_overridden_mail)
->set('weight_select_max', 50)
->save();
// Ensure that no overrides are applying.
$this->assertEqual($non_overridden_name, $config_factory->get('system.site')->get('name'));
$this->assertEqual($non_overridden_slogan, $config_factory->get('system.site')->get('slogan'));
$this->assertEqual($non_overridden_mail, $config_factory->get('system.site')->get('mail'));
$this->assertEqual(50, $config_factory->get('system.site')->get('weight_select_max'));
// Override using language.
$language = new Language(array(
'name' => 'French',
'id' => 'fr',
));
\Drupal::languageManager()->setConfigOverrideLanguage($language);
\Drupal::languageManager()
->getLanguageConfigOverride($language->getId(), 'system.site')
->set('name', $language_overridden_name)
->set('mail', $language_overridden_mail)
->save();
$this->assertEqual($language_overridden_name, $config_factory->get('system.site')->get('name'));
$this->assertEqual($non_overridden_slogan, $config_factory->get('system.site')->get('slogan'));
$this->assertEqual($language_overridden_mail, $config_factory->get('system.site')->get('mail'));
$this->assertEqual(50, $config_factory->get('system.site')->get('weight_select_max'));
// Enable module overrides. Do not override system.site:mail to prove that
// the language override still applies.
$GLOBALS['config_test_run_module_overrides'] = TRUE;
$config_factory->reset('system.site');
$this->assertEqual($module_overridden_name, $config_factory->get('system.site')->get('name'));
$this->assertEqual($module_overridden_slogan, $config_factory->get('system.site')->get('slogan'));
$this->assertEqual($language_overridden_mail, $config_factory->get('system.site')->get('mail'));
$this->assertEqual(50, $config_factory->get('system.site')->get('weight_select_max'));
// Configure a global override to simulate overriding using settings.php. Do
// not override system.site:mail or system.site:slogan to prove that the
// language and module overrides still apply.
$GLOBALS['config']['system.site']['name'] = 'Site name global conf override';
$config_factory->reset('system.site');
$this->assertEqual('Site name global conf override', $config_factory->get('system.site')->get('name'));
$this->assertEqual($module_overridden_slogan, $config_factory->get('system.site')->get('slogan'));
$this->assertEqual($language_overridden_mail, $config_factory->get('system.site')->get('mail'));
$this->assertEqual(50, $config_factory->get('system.site')->get('weight_select_max'));
$this->assertEqual($non_overridden_name, $config_factory->get('system.site')->getOriginal('name', FALSE));
$this->assertEqual($non_overridden_slogan, $config_factory->get('system.site')->getOriginal('slogan', FALSE));
$this->assertEqual($non_overridden_mail, $config_factory->get('system.site')->getOriginal('mail', FALSE));
$this->assertEqual(50, $config_factory->get('system.site')->getOriginal('weight_select_max', FALSE));
unset($GLOBALS['config_test_run_module_overrides']);
}
}

View file

@ -0,0 +1,636 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
use Drupal\Core\TypedData\Type\IntegerInterface;
use Drupal\Core\TypedData\Type\StringInterface;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests schema for configuration objects.
*
* @group config
*/
class ConfigSchemaTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'language', 'field', 'image', 'config_test', 'config_schema_test');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('system', 'image', 'config_schema_test'));
}
/**
* Tests the basic metadata retrieval layer.
*/
function testSchemaMapping() {
// Nonexistent configuration key will have Undefined as metadata.
$this->assertIdentical(FALSE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.no_such_key'));
$definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.no_such_key');
$expected = array();
$expected['label'] = 'Undefined';
$expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
$expected['type'] = 'undefined';
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for nonexistent configuration.');
// Configuration file without schema will return Undefined as well.
$this->assertIdentical(FALSE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.noschema'));
$definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.noschema');
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for configuration with no schema.');
// Configuration file with only some schema.
$this->assertIdentical(TRUE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'));
$definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.someschema');
$expected = array();
$expected['label'] = 'Schema test data';
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['mapping']['langcode']['type'] = 'string';
$expected['mapping']['langcode']['label'] = 'Language code';
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['mapping']['testitem'] = array('label' => 'Test item');
$expected['mapping']['testlist'] = array('label' => 'Test list');
$expected['type'] = 'config_schema_test.someschema';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for configuration with only some schema.');
// Check type detection on elements with undefined types.
$config = \Drupal::service('config.typed')->get('config_schema_test.someschema');
$definition = $config->get('testitem')->getDataDefinition()->toArray();
$expected = array();
$expected['label'] = 'Test item';
$expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
$expected['type'] = 'undefined';
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
$this->assertEqual($definition, $expected, 'Automatic type detected for a scalar is undefined.');
$definition = $config->get('testlist')->getDataDefinition()->toArray();
$expected = array();
$expected['label'] = 'Test list';
$expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
$expected['type'] = 'undefined';
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
$this->assertEqual($definition, $expected, 'Automatic type detected for a list is undefined.');
$definition = $config->get('testnoschema')->getDataDefinition()->toArray();
$expected = array();
$expected['label'] = 'Undefined';
$expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
$expected['type'] = 'undefined';
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
$this->assertEqual($definition, $expected, 'Automatic type detected for an undefined integer is undefined.');
// Simple case, straight metadata.
$definition = \Drupal::service('config.typed')->getDefinition('system.maintenance');
$expected = array();
$expected['label'] = 'Maintenance mode';
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['mapping']['message'] = array(
'label' => 'Message to display when in maintenance mode',
'type' => 'text',
);
$expected['mapping']['langcode'] = array(
'label' => 'Language code',
'type' => 'string',
);
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['type'] = 'system.maintenance';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for system.maintenance');
// Mixed schema with ignore elements.
$definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.ignore');
$expected = array();
$expected['label'] = 'Ignore test';
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$expected['mapping']['langcode'] = array(
'type' => 'string',
'label' => 'Language code',
);
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['mapping']['label'] = array(
'label' => 'Label',
'type' => 'label',
);
$expected['mapping']['irrelevant'] = array(
'label' => 'Irrelevant',
'type' => 'ignore',
);
$expected['mapping']['indescribable'] = array(
'label' => 'Indescribable',
'type' => 'ignore',
);
$expected['mapping']['weight'] = array(
'label' => 'Weight',
'type' => 'integer',
);
$expected['type'] = 'config_schema_test.ignore';
$this->assertEqual($definition, $expected);
// The ignore elements themselves.
$definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('irrelevant')->getDataDefinition()->toArray();
$expected = array();
$expected['type'] = 'ignore';
$expected['label'] = 'Irrelevant';
$expected['class'] = '\Drupal\Core\Config\Schema\Ignore';
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
$this->assertEqual($definition, $expected);
$definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('indescribable')->getDataDefinition()->toArray();
$expected['label'] = 'Indescribable';
$this->assertEqual($definition, $expected);
// More complex case, generic type. Metadata for image style.
$definition = \Drupal::service('config.typed')->getDefinition('image.style.large');
$expected = array();
$expected['label'] = 'Image style';
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$expected['mapping']['name']['type'] = 'string';
$expected['mapping']['uuid']['type'] = 'string';
$expected['mapping']['uuid']['label'] = 'UUID';
$expected['mapping']['langcode']['type'] = 'string';
$expected['mapping']['langcode']['label'] = 'Language code';
$expected['mapping']['status']['type'] = 'boolean';
$expected['mapping']['status']['label'] = 'Status';
$expected['mapping']['dependencies']['type'] = 'config_dependencies';
$expected['mapping']['dependencies']['label'] = 'Dependencies';
$expected['mapping']['name']['type'] = 'string';
$expected['mapping']['label']['type'] = 'label';
$expected['mapping']['label']['label'] = 'Label';
$expected['mapping']['effects']['type'] = 'sequence';
$expected['mapping']['effects']['sequence']['type'] = 'mapping';
$expected['mapping']['effects']['sequence']['mapping']['id']['type'] = 'string';
$expected['mapping']['effects']['sequence']['mapping']['data']['type'] = 'image.effect.[%parent.id]';
$expected['mapping']['effects']['sequence']['mapping']['weight']['type'] = 'integer';
$expected['mapping']['effects']['sequence']['mapping']['uuid']['type'] = 'string';
$expected['mapping']['third_party_settings']['type'] = 'sequence';
$expected['mapping']['third_party_settings']['label'] = 'Third party settings';
$expected['mapping']['third_party_settings']['sequence']['type'] = '[%parent.%parent.%type].third_party.[%key]';
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['type'] = 'image.style.*';
$this->assertEqual($definition, $expected);
// More complex, type based on a complex one.
$definition = \Drupal::service('config.typed')->getDefinition('image.effect.image_scale');
// This should be the schema for image.effect.image_scale.
$expected = array();
$expected['label'] = 'Image scale';
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$expected['mapping']['width']['type'] = 'integer';
$expected['mapping']['width']['label'] = 'Width';
$expected['mapping']['height']['type'] = 'integer';
$expected['mapping']['height']['label'] = 'Height';
$expected['mapping']['upscale']['type'] = 'boolean';
$expected['mapping']['upscale']['label'] = 'Upscale';
$expected['type'] = 'image.effect.image_scale';
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for image.effect.image_scale');
// Most complex case, get metadata for actual configuration element.
$effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects');
$definition = $effects->get('bddf0d06-42f9-4c75-a700-a33cafa25ea0')->get('data')->getDataDefinition()->toArray();
// This should be the schema for image.effect.image_scale, reuse previous one.
$expected['type'] = 'image.effect.image_scale';
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for the first effect of image.style.medium');
$a = \Drupal::config('config_test.dynamic.third_party');
$test = \Drupal::service('config.typed')->get('config_test.dynamic.third_party')->get('third_party_settings.config_schema_test');
$definition = $test->getDataDefinition()->toArray();
$expected = array();
$expected['type'] = 'config_test.dynamic.*.third_party.config_schema_test';
$expected['label'] = 'Mapping';
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$expected['mapping'] = [
'integer' => ['type' => 'integer'],
'string' => ['type' => 'string'],
];
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_test.dynamic.third_party:third_party_settings.config_schema_test');
// More complex, several level deep test.
$definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.someschema.somemodule.section_one.subsection');
// This should be the schema of config_schema_test.someschema.somemodule.*.*.
$expected = array();
$expected['label'] = 'Schema multiple filesystem marker test';
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['mapping']['langcode']['type'] = 'string';
$expected['mapping']['langcode']['label'] = 'Language code';
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['mapping']['testid']['type'] = 'string';
$expected['mapping']['testid']['label'] = 'ID';
$expected['mapping']['testdescription']['type'] = 'text';
$expected['mapping']['testdescription']['label'] = 'Description';
$expected['type'] = 'config_schema_test.someschema.somemodule.*.*';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_one.subsection');
$definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.someschema.somemodule.section_two.subsection');
// The other file should have the same schema.
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_two.subsection');
}
/**
* Tests metadata retrieval with several levels of %parent indirection.
*/
function testSchemaMappingWithParents() {
$config_data = \Drupal::service('config.typed')->get('config_schema_test.someschema.with_parents');
// Test fetching parent one level up.
$entry = $config_data->get('one_level');
$definition = $entry->get('testitem')->getDataDefinition()->toArray();
$expected = array(
'type' => 'config_schema_test.someschema.with_parents.key_1',
'label' => 'Test item nested one level',
'class' => '\Drupal\Core\TypedData\Plugin\DataType\StringData',
'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
);
$this->assertEqual($definition, $expected);
// Test fetching parent two levels up.
$entry = $config_data->get('two_levels');
$definition = $entry->get('wrapper')->get('testitem')->getDataDefinition()->toArray();
$expected = array(
'type' => 'config_schema_test.someschema.with_parents.key_2',
'label' => 'Test item nested two levels',
'class' => '\Drupal\Core\TypedData\Plugin\DataType\StringData',
'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
);
$this->assertEqual($definition, $expected);
// Test fetching parent three levels up.
$entry = $config_data->get('three_levels');
$definition = $entry->get('wrapper_1')->get('wrapper_2')->get('testitem')->getDataDefinition()->toArray();
$expected = array(
'type' => 'config_schema_test.someschema.with_parents.key_3',
'label' => 'Test item nested three levels',
'class' => '\Drupal\Core\TypedData\Plugin\DataType\StringData',
'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
);
$this->assertEqual($definition, $expected);
}
/**
* Tests metadata applied to configuration objects.
*/
function testSchemaData() {
// Try a simple property.
$meta = \Drupal::service('config.typed')->get('system.site');
$property = $meta->get('page')->get('front');
$this->assertTrue($property instanceof StringInterface, 'Got the right wrapper fo the page.front property.');
$this->assertEqual($property->getValue(), '/user/login', 'Got the right value for page.front data.');
$definition = $property->getDataDefinition();
$this->assertTrue(empty($definition['translatable']), 'Got the right translatability setting for page.front data.');
// Check nested array of properties.
$list = $meta->get('page')->getElements();
$this->assertEqual(count($list), 3, 'Got a list with the right number of properties for site page data');
$this->assertTrue(isset($list['front']) && isset($list['403']) && isset($list['404']), 'Got a list with the right properties for site page data.');
$this->assertEqual($list['front']->getValue(), '/user/login', 'Got the right value for page.front data from the list.');
// And test some TypedConfigInterface methods.
$properties = $list;
$this->assertTrue(count($properties) == 3 && $properties['front'] == $list['front'], 'Got the right properties for site page.');
$values = $meta->get('page')->toArray();
$this->assertTrue(count($values) == 3 && $values['front'] == '/user/login', 'Got the right property values for site page.');
// Now let's try something more complex, with nested objects.
$wrapper = \Drupal::service('config.typed')->get('image.style.large');
$effects = $wrapper->get('effects');
$this->assertTrue(count($effects->toArray()) == 1, 'Got an array with effects for image.style.large data');
$uuid = key($effects->getValue());
$effect = $effects->get($uuid)->getElements();
$this->assertTrue(!$effect['data']->isEmpty() && $effect['id']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.');
$this->assertTrue($effect['data']->get('width') instanceof IntegerInterface, 'Got the right type for the scale effect width.');
$this->assertEqual($effect['data']->get('width')->getValue(), 480, 'Got the right value for the scale effect width.' );
}
/**
* Test configuration value data type enforcement using schemas.
*/
public function testConfigSaveWithSchema() {
$untyped_values = array(
'string' => 1,
'empty_string' => '',
'null_string' => NULL,
'integer' => '100',
'null_integer' => '',
'boolean' => 1,
// If the config schema doesn't have a type it shouldn't be casted.
'no_type' => 1,
'mapping' => array(
'string' => 1
),
'float' => '3.14',
'null_float' => '',
'sequence' => array (1, 0, 1),
'sequence_bc' => array(1, 0, 1),
// Not in schema and therefore should be left untouched.
'not_present_in_schema' => TRUE,
// Test a custom type.
'config_schema_test_integer' => '1',
'config_schema_test_integer_empty_string' => '',
);
$untyped_to_typed = $untyped_values;
$typed_values = array(
'string' => '1',
'empty_string' => '',
'null_string' => NULL,
'integer' => 100,
'null_integer' => NULL,
'boolean' => TRUE,
'no_type' => 1,
'mapping' => array(
'string' => '1'
),
'float' => 3.14,
'null_float' => NULL,
'sequence' => array (TRUE, FALSE, TRUE),
'sequence_bc' => array(TRUE, FALSE, TRUE),
'not_present_in_schema' => TRUE,
'config_schema_test_integer' => 1,
'config_schema_test_integer_empty_string' => NULL,
);
// Save config which has a schema that enforces types.
$this->config('config_schema_test.schema_data_types')
->setData($untyped_to_typed)
->save();
$this->assertIdentical($this->config('config_schema_test.schema_data_types')->get(), $typed_values);
// Save config which does not have a schema that enforces types.
$this->config('config_schema_test.no_schema_data_types')
->setData($untyped_values)
->save();
$this->assertIdentical($this->config('config_schema_test.no_schema_data_types')->get(), $untyped_values);
// Ensure that configuration objects with keys marked as ignored are not
// changed when saved. The 'config_schema_test.ignore' will have been saved
// during the installation of configuration in the setUp method.
$extension_path = __DIR__ . '/../../../../../modules/config/tests/config_schema_test/';
$install_storage = new FileStorage($extension_path . InstallStorage::CONFIG_INSTALL_DIRECTORY);
$original_data = $install_storage->read('config_schema_test.ignore');
$installed_data = $this->config('config_schema_test.ignore')->get();
unset($installed_data['_core']);
$this->assertIdentical($installed_data, $original_data);
}
/**
* Tests fallback to a greedy wildcard.
*/
function testSchemaFallback() {
$definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.wildcard_fallback.something');
// This should be the schema of config_schema_test.wildcard_fallback.*.
$expected = array();
$expected['label'] = 'Schema wildcard fallback test';
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
$expected['mapping']['langcode']['type'] = 'string';
$expected['mapping']['langcode']['label'] = 'Language code';
$expected['mapping']['_core']['type'] = '_core_config_info';
$expected['mapping']['testid']['type'] = 'string';
$expected['mapping']['testid']['label'] = 'ID';
$expected['mapping']['testdescription']['type'] = 'text';
$expected['mapping']['testdescription']['label'] = 'Description';
$expected['type'] = 'config_schema_test.wildcard_fallback.*';
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.wildcard_fallback.something');
$definition2 = \Drupal::service('config.typed')->getDefinition('config_schema_test.wildcard_fallback.something.something');
// This should be the schema of config_schema_test.wildcard_fallback.* as
//well.
$this->assertIdentical($definition, $definition2);
}
/**
* Tests use of colons in schema type determination.
*
* @see \Drupal\Core\Config\TypedConfigManager::getFallbackName()
*/
function testColonsInSchemaTypeDetermination() {
$tests = \Drupal::service('config.typed')->get('config_schema_test.plugin_types')->get('tests')->getElements();
$definition = $tests[0]->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'test.plugin_types.boolean');
$definition = $tests[1]->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'test.plugin_types.boolean:*');
$definition = $tests[2]->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'test.plugin_types.*');
$definition = $tests[3]->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'test.plugin_types.*');
$tests = \Drupal::service('config.typed')->get('config_schema_test.plugin_types')->get('test_with_parents')->getElements();
$definition = $tests[0]->get('settings')->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'test_with_parents.plugin_types.boolean');
$definition = $tests[1]->get('settings')->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'test_with_parents.plugin_types.boolean:*');
$definition = $tests[2]->get('settings')->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'test_with_parents.plugin_types.*');
$definition = $tests[3]->get('settings')->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'test_with_parents.plugin_types.*');
}
/**
* Tests hook_config_schema_info_alter().
*/
public function testConfigSchemaInfoAlter() {
/** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config */
$typed_config = \Drupal::service('config.typed');
$typed_config->clearCachedDefinitions();
// Ensure that keys can not be added or removed by
// hook_config_schema_info_alter().
\Drupal::state()->set('config_schema_test_exception_remove', TRUE);
$message = 'Expected ConfigSchemaAlterException thrown.';
try {
$typed_config->getDefinitions();
$this->fail($message);
}
catch (ConfigSchemaAlterException $e) {
$this->pass($message);
$this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has removed (config_schema_test.hook) schema definitions');
}
\Drupal::state()->set('config_schema_test_exception_add', TRUE);
$message = 'Expected ConfigSchemaAlterException thrown.';
try {
$typed_config->getDefinitions();
$this->fail($message);
}
catch (ConfigSchemaAlterException $e) {
$this->pass($message);
$this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has added (config_schema_test.hook_added_defintion) and removed (config_schema_test.hook) schema definitions');
}
\Drupal::state()->set('config_schema_test_exception_remove', FALSE);
$message = 'Expected ConfigSchemaAlterException thrown.';
try {
$typed_config->getDefinitions();
$this->fail($message);
}
catch (ConfigSchemaAlterException $e) {
$this->pass($message);
$this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has added (config_schema_test.hook_added_defintion) schema definitions');
}
// Tests that hook_config_schema_info_alter() can add additional metadata to
// existing configuration schema.
\Drupal::state()->set('config_schema_test_exception_add', FALSE);
$definitions = $typed_config->getDefinitions();
$this->assertEqual($definitions['config_schema_test.hook']['additional_metadata'], 'new schema info');
}
/**
* Tests saving config when the type is wrapped by a dynamic type.
*/
public function testConfigSaveWithWrappingSchema() {
$untyped_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'plugin_id' => 'wrapper:foo',
'internal_value' => 100,
],
],
];
$typed_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'plugin_id' => 'wrapper:foo',
'internal_value' => '100',
],
],
];
// Save config which has a schema that enforces types.
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.plugin_types')
->setData($untyped_values)
->save();
$this->assertIdentical(\Drupal::config('wrapping.config_schema_test.plugin_types')
->get(), $typed_values);
}
/**
* Tests dynamic config schema type with multiple sub-key references.
*/
public function testConfigSaveWithWrappingSchemaDoubleBrackets() {
$untyped_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'turtle',
'bar' => 'horse',
// Converted to a string by 'test.double_brackets.turtle.horse'
// schema.
'another_key' => '100',
],
],
];
$typed_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'turtle',
'bar' => 'horse',
'another_key' => 100,
],
],
];
// Save config which has a schema that enforces types.
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
->setData($untyped_values)
->save();
$this->assertIdentical(\Drupal::config('wrapping.config_schema_test.double_brackets')
->get(), $typed_values);
$tests = \Drupal::service('config.typed')->get('wrapping.config_schema_test.double_brackets')->get('tests')->getElements();
$definition = $tests[0]->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'wrapping.test.double_brackets.*||test.double_brackets.turtle.horse');
$untyped_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'cat',
'bar' => 'dog',
// Converted to a string by 'test.double_brackets.cat.dog' schema.
'another_key' => 100,
],
],
];
$typed_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'cat',
'bar' => 'dog',
'another_key' => '100',
],
],
];
// Save config which has a schema that enforces types.
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
->setData($untyped_values)
->save();
$this->assertIdentical(\Drupal::config('wrapping.config_schema_test.double_brackets')
->get(), $typed_values);
$tests = \Drupal::service('config.typed')->get('wrapping.config_schema_test.double_brackets')->get('tests')->getElements();
$definition = $tests[0]->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'wrapping.test.double_brackets.*||test.double_brackets.cat.dog');
// Combine everything in a single save.
$typed_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'cat',
'bar' => 'dog',
'another_key' => 100,
],
[
'wrapper_value' => 'foo',
'foo' => 'turtle',
'bar' => 'horse',
'another_key' => '100',
],
],
];
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
->setData($typed_values)
->save();
$tests = \Drupal::service('config.typed')->get('wrapping.config_schema_test.double_brackets')->get('tests')->getElements();
$definition = $tests[0]->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'wrapping.test.double_brackets.*||test.double_brackets.cat.dog');
$definition = $tests[1]->getDataDefinition()->toArray();
$this->assertEqual($definition['type'], 'wrapping.test.double_brackets.*||test.double_brackets.turtle.horse');
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\StorageComparer;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests config snapshot creation and updating.
*
* @group config
*/
class ConfigSnapshotTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test', 'system');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Update the config snapshot. This allows the parent::setUp() to write
// configuration files.
\Drupal::service('config.manager')->createSnapshot(\Drupal::service('config.storage'), \Drupal::service('config.storage.snapshot'));
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
}
/**
* Tests config snapshot creation and updating.
*/
function testSnapshot() {
$active = $this->container->get('config.storage');
$sync = $this->container->get('config.storage.sync');
$snapshot = $this->container->get('config.storage.snapshot');
$config_manager = $this->container->get('config.manager');
$config_name = 'config_test.system';
$config_key = 'foo';
$new_data = 'foobar';
$active_snapshot_comparer = new StorageComparer($active, $snapshot, $config_manager);
$sync_snapshot_comparer = new StorageComparer($sync, $snapshot, $config_manager);
// Verify that we have an initial snapshot that matches the active
// configuration. This has to be true as no config should be installed.
$this->assertFalse($active_snapshot_comparer->createChangelist()->hasChanges());
// Install the default config.
$this->installConfig(array('config_test'));
// Although we have imported config this has not affected the snapshot.
$this->assertTrue($active_snapshot_comparer->reset()->hasChanges());
// Update the config snapshot.
\Drupal::service('config.manager')->createSnapshot($active, $snapshot);
// The snapshot and active config should now contain the same config
// objects.
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
// Change a configuration value in sync.
$sync_data = $this->config($config_name)->get();
$sync_data[$config_key] = $new_data;
$sync->write($config_name, $sync_data);
// Verify that active and snapshot match, and that sync doesn't match
// active.
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
$this->assertTrue($sync_snapshot_comparer->createChangelist()->hasChanges());
// Import changed data from sync to active.
$this->configImporter()->import();
// Verify changed config was properly imported.
\Drupal::configFactory()->reset($config_name);
$this->assertIdentical($this->config($config_name)->get($config_key), $new_data);
// Verify that a new snapshot was created which and that it matches
// the active config.
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\config_test\TestInstallStorage;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\DependencyInjection\Reference;
/**
* Tests that default configuration provided by all modules matches schema.
*
* @group config
*/
class DefaultConfigTest extends KernelTestBase {
use SchemaCheckTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'config_test');
/**
* Themes which provide default configuration and need enabling.
*
* If a theme provides default configuration but does not have a schema
* because it can rely on schemas added by system_config_schema_info_alter()
* then this test needs to enable it.
*
* @var array
*/
protected $themes = ['seven'];
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install($this->themes);
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$container->register('default_config_test.schema_storage')
->setClass('\Drupal\config_test\TestInstallStorage')
->addArgument(InstallStorage::CONFIG_SCHEMA_DIRECTORY);
$definition = $container->getDefinition('config.typed');
$definition->replaceArgument(1, new Reference('default_config_test.schema_storage'));
}
/**
* Tests default configuration data type.
*/
public function testDefaultConfig() {
$typed_config = \Drupal::service('config.typed');
// Create a configuration storage with access to default configuration in
// every module, profile and theme.
$default_config_storage = new TestInstallStorage();
foreach ($default_config_storage->listAll() as $config_name) {
// Skip files provided by the config_schema_test module since that module
// is explicitly for testing schema.
if (strpos($config_name, 'config_schema_test') === 0) {
continue;
}
$data = $default_config_storage->read($config_name);
$this->assertConfigSchema($typed_config, $config_name, $data);
}
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\Schema\SchemaCheckTrait;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the functionality of SchemaCheckTrait.
*
* @group config
*/
class SchemaCheckTraitTest extends KernelTestBase {
use SchemaCheckTrait;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfig;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test', 'config_schema_test');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('config_test', 'config_schema_test'));
$this->typedConfig = \Drupal::service('config.typed');
}
/**
* Tests \Drupal\Core\Config\Schema\SchemaCheckTrait.
*/
public function testTrait() {
// Test a non existing schema.
$ret = $this->checkConfigSchema($this->typedConfig, 'config_schema_test.noschema', $this->config('config_schema_test.noschema')->get());
$this->assertIdentical($ret, FALSE);
// Test an existing schema with valid data.
$config_data = $this->config('config_test.types')->get();
$ret = $this->checkConfigSchema($this->typedConfig, 'config_test.types', $config_data);
$this->assertIdentical($ret, TRUE);
// Add a new key, a new array and overwrite boolean with array to test the
// error messages.
$config_data = array('new_key' => 'new_value', 'new_array' => array()) + $config_data;
$config_data['boolean'] = array();
$ret = $this->checkConfigSchema($this->typedConfig, 'config_test.types', $config_data);
$expected = array(
'config_test.types:new_key' => 'missing schema',
'config_test.types:new_array' => 'missing schema',
'config_test.types:boolean' => 'non-scalar value but not defined as an array (such as mapping or sequence)',
);
$this->assertEqual($ret, $expected);
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\Schema\SchemaIncompleteException;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the functionality of ConfigSchemaChecker in KernelTestBase tests.
*
* @group config
*/
class SchemaConfigListenerTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('config_test');
/**
* Tests \Drupal\Core\Config\Testing\ConfigSchemaChecker.
*/
public function testConfigSchemaChecker() {
// Test a non-existing schema.
$message = 'Expected SchemaIncompleteException thrown';
try {
$this->config('config_schema_test.schemaless')->set('foo', 'bar')->save();
$this->fail($message);
}
catch (SchemaIncompleteException $e) {
$this->pass($message);
$this->assertEqual('No schema for config_schema_test.schemaless', $e->getMessage());
}
// Test a valid schema.
$message = 'Unexpected SchemaIncompleteException thrown';
$config = $this->config('config_test.types')->set('int', 10);
try {
$config->save();
$this->pass($message);
}
catch (SchemaIncompleteException $e) {
$this->fail($message);
}
// Test an invalid schema.
$message = 'Expected SchemaIncompleteException thrown';
$config = $this->config('config_test.types')
->set('foo', 'bar')
->set('array', 1);
try {
$config->save();
$this->fail($message);
}
catch (SchemaIncompleteException $e) {
$this->pass($message);
$this->assertEqual('Schema errors for config_test.types with the following errors: config_test.types:foo missing schema, config_test.types:array variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence', $e->getMessage());
}
}
}

View file

@ -0,0 +1,94 @@
<?php
namespace Drupal\KernelTests\Core\Config\Storage;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\CachedStorage;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\StreamWrapper\PublicStream;
use Symfony\Component\DependencyInjection\Reference;
/**
* Tests CachedStorage operations.
*
* @group config
*/
class CachedStorageTest extends ConfigStorageTestBase {
/**
* The cache backend the cached storage is using.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The file storage the cached storage is using.
*
* @var \Drupal\Core\Config\FileStorage
*/
protected $fileStorage;
protected function setUp() {
parent::setUp();
// Create a directory.
$dir = PublicStream::basePath() . '/config';
$this->fileStorage = new FileStorage($dir);
$this->storage = new CachedStorage($this->fileStorage, \Drupal::service('cache.config'));
$this->cache = \Drupal::service('cache_factory')->get('config');
// ::listAll() verifications require other configuration data to exist.
$this->storage->write('system.performance', array());
}
/**
* {@inheritdoc}
*/
public function testInvalidStorage() {
// No-op as this test does not make sense.
}
/**
* {@inheritdoc}
*/
protected function read($name) {
$data = $this->cache->get($name);
// Cache misses fall through to the underlying storage.
return $data ? $data->data : $this->fileStorage->read($name);
}
/**
* {@inheritdoc}
*/
protected function insert($name, $data) {
$this->fileStorage->write($name, $data);
$this->cache->set($name, $data);
}
/**
* {@inheritdoc}
*/
protected function update($name, $data) {
$this->fileStorage->write($name, $data);
$this->cache->set($name, $data);
}
/**
* {@inheritdoc}
*/
protected function delete($name) {
$this->cache->delete($name);
unlink($this->fileStorage->getFilePath($name));
}
/**
* {@inheritdoc}
*/
public function containerBuild(ContainerBuilder $container) {
parent::containerBuild($container);
// Use the regular database cache backend to aid testing.
$container->register('cache_factory', 'Drupal\Core\Cache\DatabaseBackendFactory')
->addArgument(new Reference('database'))
->addArgument(new Reference('cache_tags.invalidator.checksum'));
}
}

View file

@ -0,0 +1,266 @@
<?php
namespace Drupal\KernelTests\Core\Config\Storage;
use Drupal\KernelTests\KernelTestBase;
/**
* Base class for testing storage operations.
*
* All configuration storages are expected to behave identically in
* terms of reading, writing, listing, deleting, as well as error handling.
*
* Therefore, storage tests use an uncommon test case class structure;
* the base class defines the test method(s) to execute, which are identical
* for all storages. The storage specific test case classes supply the
* necessary helper methods to interact with the raw/native storage
* directly.
*/
abstract class ConfigStorageTestBase extends KernelTestBase {
/**
* @var \Drupal\Core\Config\StorageInterface
*/
protected $storage;
/**
* @var \Drupal\Core\Config\StorageInterface
*/
protected $invalidStorage;
/**
* Tests storage CRUD operations.
*
* @todo Coverage: Trigger PDOExceptions / Database exceptions.
*/
function testCRUD() {
$name = 'config_test.storage';
// Checking whether a non-existing name exists returns FALSE.
$this->assertIdentical($this->storage->exists($name), FALSE);
// Reading a non-existing name returns FALSE.
$data = $this->storage->read($name);
$this->assertIdentical($data, FALSE);
// Writing data returns TRUE and the data has been written.
$data = array('foo' => 'bar');
$result = $this->storage->write($name, $data);
$this->assertIdentical($result, TRUE);
$raw_data = $this->read($name);
$this->assertIdentical($raw_data, $data);
// Checking whether an existing name exists returns TRUE.
$this->assertIdentical($this->storage->exists($name), TRUE);
// Writing the identical data again still returns TRUE.
$result = $this->storage->write($name, $data);
$this->assertIdentical($result, TRUE);
// Listing all names returns all.
$names = $this->storage->listAll();
$this->assertTrue(in_array('system.performance', $names));
$this->assertTrue(in_array($name, $names));
// Listing all names with prefix returns names with that prefix only.
$names = $this->storage->listAll('config_test.');
$this->assertFalse(in_array('system.performance', $names));
$this->assertTrue(in_array($name, $names));
// Rename the configuration storage object.
$new_name = 'config_test.storage_rename';
$this->storage->rename($name, $new_name);
$raw_data = $this->read($new_name);
$this->assertIdentical($raw_data, $data);
// Rename it back so further tests work.
$this->storage->rename($new_name, $name);
// Deleting an existing name returns TRUE.
$result = $this->storage->delete($name);
$this->assertIdentical($result, TRUE);
// Deleting a non-existing name returns FALSE.
$result = $this->storage->delete($name);
$this->assertIdentical($result, FALSE);
// Deleting all names with prefix deletes the appropriate data and returns
// TRUE.
$files = array(
'config_test.test.biff',
'config_test.test.bang',
'config_test.test.pow',
);
foreach ($files as $name) {
$this->storage->write($name, $data);
}
$result = $this->storage->deleteAll('config_test.');
$names = $this->storage->listAll('config_test.');
$this->assertIdentical($result, TRUE);
$this->assertIdentical($names, array());
// Test renaming an object that does not exist throws an exception.
try {
$this->storage->rename('config_test.storage_does_not_exist', 'config_test.storage_does_not_exist_rename');
}
catch (\Exception $e) {
$class = get_class($e);
$this->pass($class . ' thrown upon renaming a nonexistent storage bin.');
}
// Test renaming to an object that already exists throws an exception.
try {
$this->storage->rename('system.cron', 'system.performance');
}
catch (\Exception $e) {
$class = get_class($e);
$this->pass($class . ' thrown upon renaming a nonexistent storage bin.');
}
}
/**
* Tests an invalid storage.
*/
public function testInvalidStorage() {
$name = 'config_test.storage';
// Write something to the valid storage to prove that the storages do not
// pollute one another.
$data = array('foo' => 'bar');
$result = $this->storage->write($name, $data);
$this->assertIdentical($result, TRUE);
$raw_data = $this->read($name);
$this->assertIdentical($raw_data, $data);
// Reading from a non-existing storage bin returns FALSE.
$result = $this->invalidStorage->read($name);
$this->assertIdentical($result, FALSE);
// Deleting from a non-existing storage bin throws an exception.
try {
$this->invalidStorage->delete($name);
$this->fail('Exception not thrown upon deleting from a non-existing storage bin.');
}
catch (\Exception $e) {
$class = get_class($e);
$this->pass($class . ' thrown upon deleting from a non-existing storage bin.');
}
// Listing on a non-existing storage bin returns an empty array.
$result = $this->invalidStorage->listAll();
$this->assertIdentical($result, array());
// Writing to a non-existing storage bin creates the bin.
$this->invalidStorage->write($name, array('foo' => 'bar'));
$result = $this->invalidStorage->read($name);
$this->assertIdentical($result, array('foo' => 'bar'));
}
/**
* Tests storage writing and reading data preserving data type.
*/
function testDataTypes() {
$name = 'config_test.types';
$data = array(
'array' => array(),
'boolean' => TRUE,
'exp' => 1.2e+34,
'float' => 3.14159,
'hex' => 0xC,
'int' => 99,
'octal' => 0775,
'string' => 'string',
'string_int' => '1',
);
$result = $this->storage->write($name, $data);
$this->assertIdentical($result, TRUE);
$read_data = $this->storage->read($name);
$this->assertIdentical($read_data, $data);
}
/**
* Tests that the storage supports collections.
*/
public function testCollection() {
$name = 'config_test.storage';
$data = array('foo' => 'bar');
$result = $this->storage->write($name, $data);
$this->assertIdentical($result, TRUE);
$this->assertIdentical($data, $this->storage->read($name));
// Create configuration in a new collection.
$new_storage = $this->storage->createCollection('collection.sub.new');
$this->assertFalse($new_storage->exists($name));
$this->assertEqual(array(), $new_storage->listAll());
$new_storage->write($name, $data);
$this->assertIdentical($result, TRUE);
$this->assertIdentical($data, $new_storage->read($name));
$this->assertEqual(array($name), $new_storage->listAll());
$this->assertTrue($new_storage->exists($name));
$new_data = array('foo' => 'baz');
$new_storage->write($name, $new_data);
$this->assertIdentical($result, TRUE);
$this->assertIdentical($new_data, $new_storage->read($name));
// Create configuration in another collection.
$another_storage = $this->storage->createCollection('collection.sub.another');
$this->assertFalse($another_storage->exists($name));
$this->assertEqual(array(), $another_storage->listAll());
$another_storage->write($name, $new_data);
$this->assertIdentical($result, TRUE);
$this->assertIdentical($new_data, $another_storage->read($name));
$this->assertEqual(array($name), $another_storage->listAll());
$this->assertTrue($another_storage->exists($name));
// Create configuration in yet another collection.
$alt_storage = $this->storage->createCollection('alternate');
$alt_storage->write($name, $new_data);
$this->assertIdentical($result, TRUE);
$this->assertIdentical($new_data, $alt_storage->read($name));
// Switch back to the collection-less mode and check the data still exists
// add has not been touched.
$this->assertIdentical($data, $this->storage->read($name));
// Check that the getAllCollectionNames() method works.
$this->assertIdentical(array('alternate', 'collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
// Check that the collections are removed when they are empty.
$alt_storage->delete($name);
$this->assertIdentical(array('collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
// Create configuration in collection called 'collection'. This ensures that
// FileStorage's collection storage works regardless of its use of
// subdirectories.
$parent_storage = $this->storage->createCollection('collection');
$this->assertFalse($parent_storage->exists($name));
$this->assertEqual(array(), $parent_storage->listAll());
$parent_storage->write($name, $new_data);
$this->assertIdentical($result, TRUE);
$this->assertIdentical($new_data, $parent_storage->read($name));
$this->assertEqual(array($name), $parent_storage->listAll());
$this->assertTrue($parent_storage->exists($name));
$this->assertIdentical(array('collection', 'collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
$parent_storage->deleteAll();
$this->assertIdentical(array('collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
// Check that the having an empty collection-less storage does not break
// anything. Before deleting check that the previous delete did not affect
// data in another collection.
$this->assertIdentical($data, $this->storage->read($name));
$this->storage->delete($name);
$this->assertIdentical(array('collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
}
abstract protected function read($name);
abstract protected function insert($name, $data);
abstract protected function update($name, $data);
abstract protected function delete($name);
}

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\KernelTests\Core\Config\Storage;
use Drupal\Core\Config\DatabaseStorage;
/**
* Tests DatabaseStorage operations.
*
* @group config
*/
class DatabaseStorageTest extends ConfigStorageTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->storage = new DatabaseStorage($this->container->get('database'), 'config');
$this->invalidStorage = new DatabaseStorage($this->container->get('database'), 'invalid');
// ::listAll() verifications require other configuration data to exist.
$this->storage->write('system.performance', array());
}
protected function read($name) {
$data = db_query('SELECT data FROM {config} WHERE name = :name', array(':name' => $name))->fetchField();
return unserialize($data);
}
protected function insert($name, $data) {
db_insert('config')->fields(array('name' => $name, 'data' => $data))->execute();
}
protected function update($name, $data) {
db_update('config')->fields(array('data' => $data))->condition('name', $name)->execute();
}
protected function delete($name) {
db_delete('config')->condition('name', $name)->execute();
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\KernelTests\Core\Config\Storage;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\UnsupportedDataTypeConfigException;
use Drupal\Core\StreamWrapper\PublicStream;
/**
* Tests FileStorage operations.
*
* @group config
*/
class FileStorageTest extends ConfigStorageTestBase {
/**
* A directory to store configuration in.
*
* @var string
*/
protected $directory;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a directory.
$this->directory = PublicStream::basePath() . '/config';
$this->storage = new FileStorage($this->directory);
$this->invalidStorage = new FileStorage($this->directory . '/nonexisting');
// FileStorage::listAll() requires other configuration data to exist.
$this->storage->write('system.performance', $this->config('system.performance')->get());
$this->storage->write('core.extension', array('module' => array()));
}
protected function read($name) {
$data = file_get_contents($this->storage->getFilePath($name));
return Yaml::decode($data);
}
protected function insert($name, $data) {
file_put_contents($this->storage->getFilePath($name), $data);
}
protected function update($name, $data) {
file_put_contents($this->storage->getFilePath($name), $data);
}
protected function delete($name) {
unlink($this->storage->getFilePath($name));
}
/**
* Tests the FileStorage::listAll method with a relative and absolute path.
*/
public function testlistAll() {
$expected_files = array(
'core.extension',
'system.performance',
);
$config_files = $this->storage->listAll();
$this->assertIdentical($config_files, $expected_files, 'Relative path, two config files found.');
// @todo https://www.drupal.org/node/2666954 FileStorage::listAll() is
// case-sensitive. However, \Drupal\Core\Config\DatabaseStorage::listAll()
// is case-insensitive.
$this->assertIdentical(['system.performance'], $this->storage->listAll('system'), 'The FileStorage::listAll() with prefix works.');
$this->assertIdentical([], $this->storage->listAll('System'), 'The FileStorage::listAll() is case sensitive.');
}
/**
* Test UnsupportedDataTypeConfigException displays path of
* erroneous file during read.
*/
public function testReadUnsupportedDataTypeConfigException() {
file_put_contents($this->storage->getFilePath('core.extension'), PHP_EOL . 'foo : [bar}', FILE_APPEND);
try {
$config_parsed = $this->storage->read('core.extension');
}
catch (UnsupportedDataTypeConfigException $e) {
$this->pass('Exception thrown when trying to read a field containing invalid data type.');
$this->assertTrue((strpos($e->getMessage(), $this->storage->getFilePath('core.extension')) !== FALSE), 'Erroneous file path is displayed.');
}
}
}

View file

@ -0,0 +1,150 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the hook_query_alter capabilities of the Select builder.
*
* @group Database
* @see database_test_query_alter()
*/
class AlterTest extends DatabaseTestBase {
/**
* Tests that we can do basic alters.
*/
function testSimpleAlter() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('database_test_alter_add_range');
$result = $query->execute()->fetchAll();
$this->assertEqual(count($result), 2, 'Returned the correct number of rows.');
}
/**
* Tests that we can alter the joins on a query.
*/
function testAlterWithJoin() {
$query = db_select('test_task');
$tid_field = $query->addField('test_task', 'tid');
$task_field = $query->addField('test_task', 'task');
$query->orderBy($task_field);
$query->addTag('database_test_alter_add_join');
$result = $query->execute();
$records = $result->fetchAll();
$this->assertEqual(count($records), 2, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.');
$this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.');
$this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.');
$this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.');
$this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.');
$this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.');
}
/**
* Tests that we can alter a query's conditionals.
*/
function testAlterChangeConditional() {
$query = db_select('test_task');
$tid_field = $query->addField('test_task', 'tid');
$pid_field = $query->addField('test_task', 'pid');
$task_field = $query->addField('test_task', 'task');
$people_alias = $query->join('test', 'people', "test_task.pid = people.id");
$name_field = $query->addField($people_alias, 'name', 'name');
$query->condition('test_task.tid', '1');
$query->orderBy($tid_field);
$query->addTag('database_test_alter_change_conditional');
$result = $query->execute();
$records = $result->fetchAll();
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
$this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.');
$this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.');
$this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.');
$this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.');
}
/**
* Tests that we can alter the fields of a query.
*/
function testAlterChangeFields() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->orderBy('name');
$query->addTag('database_test_alter_change_fields');
$record = $query->execute()->fetch();
$this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
$this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.');
}
/**
* Tests that we can alter expressions in the query.
*/
function testAlterExpression() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addExpression("age*2", 'double_age');
$query->condition('age', 27);
$query->addTag('database_test_alter_change_expressions');
$result = $query->execute();
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
$this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
}
/**
* Tests that we can remove a range() value from a query.
*
* This also tests hook_query_TAG_alter().
*/
function testAlterRemoveRange() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->range(0, 2);
$query->addTag('database_test_alter_remove_range');
$num_records = count($query->execute()->fetchAll());
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
}
/**
* Tests that we can do basic alters on subqueries.
*/
function testSimpleAlterSubquery() {
// Create a sub-query with an alter tag.
$subquery = db_select('test', 'p');
$subquery->addField('p', 'name');
$subquery->addField('p', 'id');
// Pick out George.
$subquery->condition('age', 27);
$subquery->addExpression("age*2", 'double_age');
// This query alter should change it to age * 3.
$subquery->addTag('database_test_alter_change_expressions');
// Create a main query and join to sub-query.
$query = db_select('test_task', 'tt');
$query->join($subquery, 'pq', 'pq.id = tt.pid');
$age_field = $query->addField('pq', 'double_age');
$name_field = $query->addField('pq', 'name');
$record = $query->execute()->fetch();
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
$this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests SQL syntax interpretation.
*
* In order to ensure consistent SQL handling throughout Drupal
* across multiple kinds of database systems, we test that the
* database system interprets SQL syntax in an expected fashion.
*
* @group Database
*/
class BasicSyntaxTest extends DatabaseTestBase {
/**
* Tests string concatenation.
*/
function testConcatLiterals() {
$result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', array(
':a1' => 'This',
':a2' => ' ',
':a3' => 'is',
':a4' => ' a ',
':a5' => 'test.',
));
$this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.');
}
/**
* Tests string concatenation with field values.
*/
function testConcatFields() {
$result = db_query('SELECT CONCAT(:a1, CONCAT(name, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', array(
':a1' => 'The age of ',
':a2' => ' is ',
':a3' => '.',
':age' => 25,
));
$this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.');
}
/**
* Tests string concatenation with separator.
*/
function testConcatWsLiterals() {
$result = db_query("SELECT CONCAT_WS(', ', :a1, NULL, :a2, :a3, :a4)", array(
':a1' => 'Hello',
':a2' => NULL,
':a3' => '',
':a4' => 'world.',
));
$this->assertIdentical($result->fetchField(), 'Hello, , world.');
}
/**
* Tests string concatenation with separator, with field values.
*/
function testConcatWsFields() {
$result = db_query("SELECT CONCAT_WS('-', :a1, name, :a2, age) FROM {test} WHERE age = :age", array(
':a1' => 'name',
':a2' => 'age',
':age' => 25,
));
$this->assertIdentical($result->fetchField(), 'name-John-age-25');
}
/**
* Tests escaping of LIKE wildcards.
*/
function testLikeEscape() {
db_insert('test')
->fields(array(
'name' => 'Ring_',
))
->execute();
// Match both "Ringo" and "Ring_".
$num_matches = db_select('test', 't')
->condition('name', 'Ring_', 'LIKE')
->countQuery()
->execute()
->fetchField();
$this->assertIdentical($num_matches, '2', 'Found 2 records.');
// Match only "Ring_" using a LIKE expression with no wildcards.
$num_matches = db_select('test', 't')
->condition('name', db_like('Ring_'), 'LIKE')
->countQuery()
->execute()
->fetchField();
$this->assertIdentical($num_matches, '1', 'Found 1 record.');
}
/**
* Tests a LIKE query containing a backslash.
*/
function testLikeBackslash() {
db_insert('test')
->fields(array('name'))
->values(array(
'name' => 'abcde\f',
))
->values(array(
'name' => 'abc%\_',
))
->execute();
// Match both rows using a LIKE expression with two wildcards and a verbatim
// backslash.
$num_matches = db_select('test', 't')
->condition('name', 'abc%\\\\_', 'LIKE')
->countQuery()
->execute()
->fetchField();
$this->assertIdentical($num_matches, '2', 'Found 2 records.');
// Match only the former using a LIKE expression with no wildcards.
$num_matches = db_select('test', 't')
->condition('name', db_like('abc%\_'), 'LIKE')
->countQuery()
->execute()
->fetchField();
$this->assertIdentical($num_matches, '1', 'Found 1 record.');
}
/**
* Tests \Drupal\Core\Database\Connection::getFullQualifiedTableName().
*/
public function testGetFullQualifiedTableName() {
$database = \Drupal::database();
$num_matches = $database->select($database->getFullQualifiedTableName('test'), 't')
->countQuery()
->execute()
->fetchField();
$this->assertIdentical($num_matches, '4', 'Found 4 records.');
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests handling case sensitive collation.
*
* @group Database
*/
class CaseSensitivityTest extends DatabaseTestBase {
/**
* Tests BINARY collation in MySQL.
*/
function testCaseSensitiveInsert() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
db_insert('test')
->fields(array(
'name' => 'john', // <- A record already exists with name 'John'.
'age' => 2,
'job' => 'Baby',
))
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
$this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.');
}
}

View file

@ -0,0 +1,175 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseExceptionWrapper;
/**
* Tests of the core database system.
*
* @group Database
*/
class ConnectionTest extends DatabaseTestBase {
/**
* Tests that connections return appropriate connection objects.
*/
function testConnectionRouting() {
// Clone the primary credentials to a replica connection.
// Note this will result in two independent connection objects that happen
// to point to the same place.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
$db1 = Database::getConnection('default', 'default');
$db2 = Database::getConnection('replica', 'default');
$this->assertNotNull($db1, 'default connection is a real connection object.');
$this->assertNotNull($db2, 'replica connection is a real connection object.');
$this->assertNotIdentical($db1, $db2, 'Each target refers to a different connection.');
// Try to open those targets another time, that should return the same objects.
$db1b = Database::getConnection('default', 'default');
$db2b = Database::getConnection('replica', 'default');
$this->assertIdentical($db1, $db1b, 'A second call to getConnection() returns the same object.');
$this->assertIdentical($db2, $db2b, 'A second call to getConnection() returns the same object.');
// Try to open an unknown target.
$unknown_target = $this->randomMachineName();
$db3 = Database::getConnection($unknown_target, 'default');
$this->assertNotNull($db3, 'Opening an unknown target returns a real connection object.');
$this->assertIdentical($db1, $db3, 'An unknown target opens the default connection.');
// Try to open that unknown target another time, that should return the same object.
$db3b = Database::getConnection($unknown_target, 'default');
$this->assertIdentical($db3, $db3b, 'A second call to getConnection() returns the same object.');
}
/**
* Tests that connections return appropriate connection objects.
*/
function testConnectionRoutingOverride() {
// Clone the primary credentials to a replica connection.
// Note this will result in two independent connection objects that happen
// to point to the same place.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
Database::ignoreTarget('default', 'replica');
$db1 = Database::getConnection('default', 'default');
$db2 = Database::getConnection('replica', 'default');
$this->assertIdentical($db1, $db2, 'Both targets refer to the same connection.');
}
/**
* Tests the closing of a database connection.
*/
function testConnectionClosing() {
// Open the default target so we have an object to compare.
$db1 = Database::getConnection('default', 'default');
// Try to close the default connection, then open a new one.
Database::closeConnection('default', 'default');
$db2 = Database::getConnection('default', 'default');
// Opening a connection after closing it should yield an object different than the original.
$this->assertNotIdentical($db1, $db2, 'Opening the default connection after it is closed returns a new object.');
}
/**
* Tests the connection options of the active database.
*/
function testConnectionOptions() {
$connection_info = Database::getConnectionInfo('default');
// Be sure we're connected to the default database.
$db = Database::getConnection('default', 'default');
$connectionOptions = $db->getConnectionOptions();
// In the MySQL driver, the port can be different, so check individual
// options.
$this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], 'The default connection info driver matches the current connection options driver.');
$this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], 'The default connection info database matches the current connection options database.');
// Set up identical replica and confirm connection options are identical.
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
$db2 = Database::getConnection('replica', 'default');
$connectionOptions2 = $db2->getConnectionOptions();
// Get a fresh copy of the default connection options.
$connectionOptions = $db->getConnectionOptions();
$this->assertIdentical($connectionOptions, $connectionOptions2, 'The default and replica connection options are identical.');
// Set up a new connection with different connection info.
$test = $connection_info['default'];
$test['database'] .= 'test';
Database::addConnectionInfo('test', 'default', $test);
$connection_info = Database::getConnectionInfo('test');
// Get a fresh copy of the default connection options.
$connectionOptions = $db->getConnectionOptions();
$this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.');
}
/**
* Ensure that you cannot execute multiple statements on phpversion() > 5.5.21 or > 5.6.5.
*/
public function testMultipleStatementsForNewPhp() {
// This just tests mysql, as other PDO integrations don't allow disabling
// multiple statements.
if (Database::getConnection()->databaseType() !== 'mysql' || !defined('\PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
return;
}
$db = Database::getConnection('default', 'default');
// Disable the protection at the PHP level.
try {
$db->query('SELECT * FROM {test}; SELECT * FROM {test_people}',
[],
[ 'allow_delimiter_in_query' => TRUE ]
);
$this->fail('No PDO exception thrown for multiple statements.');
}
catch (DatabaseExceptionWrapper $e) {
$this->pass('PDO exception thrown for multiple statements.');
}
}
/**
* Ensure that you cannot execute multiple statements.
*/
public function testMultipleStatements() {
$db = Database::getConnection('default', 'default');
try {
$db->query('SELECT * FROM {test}; SELECT * FROM {test_people}');
$this->fail('No exception thrown for multiple statements.');
}
catch (\InvalidArgumentException $e) {
$this->pass('Exception thrown for multiple statements.');
}
}
/**
* Test the escapeTable(), escapeField() and escapeAlias() methods with all possible reserved words in PostgreSQL.
*/
public function testPostgresqlReservedWords() {
if (Database::getConnection()->databaseType() !== 'pgsql') {
return;
}
$db = Database::getConnection('default', 'default');
$stmt = $db->query("SELECT word FROM pg_get_keywords() WHERE catcode IN ('R', 'T')");
$stmt->execute();
foreach ($stmt->fetchAllAssoc('word') as $word => $row) {
$expected = '"' . $word . '"';
$this->assertIdentical($db->escapeTable($word), $expected, format_string('The reserved word %word was correctly escaped when used as a table name.', array('%word' => $word)));
$this->assertIdentical($db->escapeField($word), $expected, format_string('The reserved word %word was correctly escaped when used as a column name.', array('%word' => $word)));
$this->assertIdentical($db->escapeAlias($word), $expected, format_string('The reserved word %word was correctly escaped when used as an alias.', array('%word' => $word)));
}
}
}

View file

@ -0,0 +1,243 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests management of database connections.
*
* @group Database
*/
class ConnectionUnitTest extends KernelTestBase {
protected $key;
protected $target;
protected $monitor;
protected $originalCount;
protected function setUp() {
parent::setUp();
$this->key = 'default';
$this->originalTarget = 'default';
$this->target = 'DatabaseConnectionUnitTest';
// Determine whether the database driver is MySQL. If it is not, the test
// methods will not be executed.
// @todo Make this test driver-agnostic, or find a proper way to skip it.
// See https://www.drupal.org/node/1273478.
$connection_info = Database::getConnectionInfo('default');
$this->skipTest = (bool) ($connection_info['default']['driver'] != 'mysql');
if ($this->skipTest) {
// Insert an assertion to prevent Simpletest from interpreting the test
// as failure.
$this->pass('This test is only compatible with MySQL.');
}
// Create an additional connection to monitor the connections being opened
// and closed in this test.
// @see TestBase::changeDatabasePrefix()
Database::addConnectionInfo('default', 'monitor', $connection_info['default']);
$this->monitor = Database::getConnection('monitor');
}
/**
* Adds a new database connection info to Database.
*/
protected function addConnection() {
// Add a new target to the connection, by cloning the current connection.
$connection_info = Database::getConnectionInfo($this->key);
Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]);
// Verify that the new target exists.
$info = Database::getConnectionInfo($this->key);
// Note: Custom assertion message to not expose database credentials.
$this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.');
}
/**
* Returns the connection ID of the current test connection.
*
* @return int
*/
protected function getConnectionID() {
return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField();
}
/**
* Asserts that a connection ID exists.
*
* @param int $id
* The connection ID to verify.
*/
protected function assertConnection($id) {
$list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id)));
}
/**
* Asserts that a connection ID does not exist.
*
* @param int $id
* The connection ID to verify.
*/
protected function assertNoConnection($id) {
$list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id)));
}
/**
* Tests Database::closeConnection() without query.
*
* @todo getConnectionID() executes a query.
*/
function testOpenClose() {
if ($this->skipTest) {
return;
}
// Add and open a new connection.
$this->addConnection();
$id = $this->getConnectionID();
Database::getConnection($this->target, $this->key);
// Verify that there is a new connection.
$this->assertConnection($id);
// Close the connection.
Database::closeConnection($this->target, $this->key);
// Wait 20ms to give the database engine sufficient time to react.
usleep(20000);
// Verify that we are back to the original connection count.
$this->assertNoConnection($id);
}
/**
* Tests Database::closeConnection() with a query.
*/
function testOpenQueryClose() {
if ($this->skipTest) {
return;
}
// Add and open a new connection.
$this->addConnection();
$id = $this->getConnectionID();
Database::getConnection($this->target, $this->key);
// Verify that there is a new connection.
$this->assertConnection($id);
// Execute a query.
Database::getConnection($this->target, $this->key)->query('SHOW TABLES');
// Close the connection.
Database::closeConnection($this->target, $this->key);
// Wait 20ms to give the database engine sufficient time to react.
usleep(20000);
// Verify that we are back to the original connection count.
$this->assertNoConnection($id);
}
/**
* Tests Database::closeConnection() with a query and custom prefetch method.
*/
function testOpenQueryPrefetchClose() {
if ($this->skipTest) {
return;
}
// Add and open a new connection.
$this->addConnection();
$id = $this->getConnectionID();
Database::getConnection($this->target, $this->key);
// Verify that there is a new connection.
$this->assertConnection($id);
// Execute a query.
Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol();
// Close the connection.
Database::closeConnection($this->target, $this->key);
// Wait 20ms to give the database engine sufficient time to react.
usleep(20000);
// Verify that we are back to the original connection count.
$this->assertNoConnection($id);
}
/**
* Tests Database::closeConnection() with a select query.
*/
function testOpenSelectQueryClose() {
if ($this->skipTest) {
return;
}
// Add and open a new connection.
$this->addConnection();
$id = $this->getConnectionID();
Database::getConnection($this->target, $this->key);
// Verify that there is a new connection.
$this->assertConnection($id);
// Create a table.
$name = 'foo';
Database::getConnection($this->target, $this->key)->schema()->createTable($name, array(
'fields' => array(
'name' => array(
'type' => 'varchar',
'length' => 255,
),
),
));
// Execute a query.
Database::getConnection($this->target, $this->key)->select('foo', 'f')
->fields('f', array('name'))
->execute()
->fetchAll();
// Drop the table.
Database::getConnection($this->target, $this->key)->schema()->dropTable($name);
// Close the connection.
Database::closeConnection($this->target, $this->key);
// Wait 20ms to give the database engine sufficient time to react.
usleep(20000);
// Verify that we are back to the original connection count.
$this->assertNoConnection($id);
}
/**
* Tests pdo options override.
*/
public function testConnectionOpen() {
$connection = Database::getConnection('default');
$reflection = new \ReflectionObject($connection);
$connection_property = $reflection->getProperty('connection');
$connection_property->setAccessible(TRUE);
$error_mode = $connection_property->getValue($connection)
->getAttribute(\PDO::ATTR_ERRMODE);
$this->assertEqual($error_mode, \PDO::ERRMODE_EXCEPTION, 'Ensure the default error mode is set to exception.');
$connection = Database::getConnectionInfo('default');
$connection['default']['pdo'][\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_SILENT;
Database::addConnectionInfo('test', 'default', $connection['default']);
$connection = Database::getConnection('default', 'test');
$reflection = new \ReflectionObject($connection);
$connection_property = $reflection->getProperty('connection');
$connection_property->setAccessible(TRUE);
$error_mode = $connection_property->getValue($connection)
->getAttribute(\PDO::ATTR_ERRMODE);
$this->assertEqual($error_mode, \PDO::ERRMODE_SILENT, 'Ensure PDO connection options can be overridden.');
Database::removeConnection('test');
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests exceptions thrown by queries.
*
* @group Database
*/
class DatabaseExceptionWrapperTest extends KernelTestBase {
/**
* Tests the expected database exception thrown for prepared statements.
*/
public function testPreparedStatement() {
$connection = Database::getConnection();
try {
// SQLite validates the syntax upon preparing a statement already.
// @throws \PDOException
$query = $connection->prepare('bananas');
// MySQL only validates the syntax upon trying to execute a query.
// @throws \Drupal\Core\Database\DatabaseExceptionWrapper
$connection->query($query);
$this->fail('Expected PDOException or DatabaseExceptionWrapper, none was thrown.');
}
catch (\PDOException $e) {
$this->pass('Expected PDOException was thrown.');
}
catch (DatabaseExceptionWrapper $e) {
$this->pass('Expected DatabaseExceptionWrapper was thrown.');
}
catch (\Exception $e) {
$this->fail("Thrown exception is not a PDOException:\n" . (string) $e);
}
}
/**
* Tests the expected database exception thrown for inexistent tables.
*/
public function testQueryThrowsDatabaseExceptionWrapperException() {
$connection = Database::getConnection();
try {
$connection->query('SELECT * FROM {does_not_exist}');
$this->fail('Expected PDOException, none was thrown.');
}
catch (DatabaseExceptionWrapper $e) {
$this->pass('Expected DatabaseExceptionWrapper was thrown.');
}
catch (\Exception $e) {
$this->fail("Thrown exception is not a DatabaseExceptionWrapper:\n" . (string) $e);
}
}
}

View file

@ -0,0 +1,146 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\KernelTests\KernelTestBase;
/**
* Base class for databases database tests.
*
* Because all database tests share the same test data, we can centralize that
* here.
*/
abstract class DatabaseTestBase extends KernelTestBase {
public static $modules = array('database_test');
protected function setUp() {
parent::setUp();
$this->installSchema('database_test', array(
'test',
'test_people',
'test_people_copy',
'test_one_blob',
'test_two_blobs',
'test_task',
'test_null',
'test_serialized',
'test_special_columns',
'TEST_UPPERCASE',
));
self::addSampleData();
}
/**
* Sets up tables for NULL handling.
*/
function ensureSampleDataNull() {
db_insert('test_null')
->fields(array('name', 'age'))
->values(array(
'name' => 'Kermit',
'age' => 25,
))
->values(array(
'name' => 'Fozzie',
'age' => NULL,
))
->values(array(
'name' => 'Gonzo',
'age' => 27,
))
->execute();
}
/**
* Sets up our sample data.
*/
static function addSampleData() {
// We need the IDs, so we can't use a multi-insert here.
$john = db_insert('test')
->fields(array(
'name' => 'John',
'age' => 25,
'job' => 'Singer',
))
->execute();
$george = db_insert('test')
->fields(array(
'name' => 'George',
'age' => 27,
'job' => 'Singer',
))
->execute();
db_insert('test')
->fields(array(
'name' => 'Ringo',
'age' => 28,
'job' => 'Drummer',
))
->execute();
$paul = db_insert('test')
->fields(array(
'name' => 'Paul',
'age' => 26,
'job' => 'Songwriter',
))
->execute();
db_insert('test_people')
->fields(array(
'name' => 'Meredith',
'age' => 30,
'job' => 'Speaker',
))
->execute();
db_insert('test_task')
->fields(array('pid', 'task', 'priority'))
->values(array(
'pid' => $john,
'task' => 'eat',
'priority' => 3,
))
->values(array(
'pid' => $john,
'task' => 'sleep',
'priority' => 4,
))
->values(array(
'pid' => $john,
'task' => 'code',
'priority' => 1,
))
->values(array(
'pid' => $george,
'task' => 'sing',
'priority' => 2,
))
->values(array(
'pid' => $george,
'task' => 'sleep',
'priority' => 2,
))
->values(array(
'pid' => $paul,
'task' => 'found new band',
'priority' => 1,
))
->values(array(
'pid' => $paul,
'task' => 'perform at superbowl',
'priority' => 3,
))
->execute();
db_insert('test_special_columns')
->fields(array(
'id' => 1,
'offset' => 'Offset value 1',
))
->execute();
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests delete and truncate queries.
*
* The DELETE tests are not as extensive, as all of the interesting code for
* DELETE queries is in the conditional which is identical to the UPDATE and
* SELECT conditional handling.
*
* The TRUNCATE tests are not extensive either, because the behavior of
* TRUNCATE queries is not consistent across database engines. We only test
* that a TRUNCATE query actually deletes all rows from the target table.
*
* @group Database
*/
class DeleteTruncateTest extends DatabaseTestBase {
/**
* Confirms that we can use a subselect in a delete successfully.
*/
function testSubselectDelete() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
$pid_to_delete = db_query("SELECT * FROM {test_task} WHERE task = 'sleep'")->fetchField();
$subquery = db_select('test', 't')
->fields('t', array('id'))
->condition('t.id', array($pid_to_delete), 'IN');
$delete = db_delete('test_task')
->condition('task', 'sleep')
->condition('pid', $subquery, 'IN');
$num_deleted = $delete->execute();
$this->assertEqual($num_deleted, 1, 'Deleted 1 record.');
$num_records_after = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
}
/**
* Confirms that we can delete a single record successfully.
*/
function testSimpleDelete() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$num_deleted = db_delete('test')
->condition('id', 1)
->execute();
$this->assertIdentical($num_deleted, 1, 'Deleted 1 record.');
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
}
/**
* Confirms that we can truncate a whole table successfully.
*/
function testTruncate() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$this->assertTrue($num_records_before > 0, 'The table is not empty.');
db_truncate('test')->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$this->assertEqual(0, $num_records_after, 'Truncate really deletes everything.');
}
/**
* Confirms that we can delete a single special column name record successfully.
*/
function testSpecialColumnDelete() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_special_columns}')->fetchField();
$num_deleted = db_delete('test_special_columns')
->condition('id', 1)
->execute();
$this->assertIdentical($num_deleted, 1, 'Deleted 1 special column record.');
$num_records_after = db_query('SELECT COUNT(*) FROM {test_special_columns}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
}
}

View file

@ -0,0 +1,146 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\RowCountException;
use Drupal\Core\Database\StatementInterface;
use Drupal\system\Tests\Database\FakeRecord;
/**
* Tests the Database system's various fetch capabilities.
*
* We get timeout errors if we try to run too many tests at once.
*
* @group Database
*/
class FetchTest extends DatabaseTestBase {
/**
* Confirms that we can fetch a record properly in default object mode.
*/
function testQueryFetchDefault() {
$records = array();
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25));
$this->assertTrue($result instanceof StatementInterface, 'Result set is a Drupal statement object.');
foreach ($result as $record) {
$records[] = $record;
$this->assertTrue(is_object($record), 'Record is an object.');
$this->assertIdentical($record->name, 'John', '25 year old is John.');
}
$this->assertIdentical(count($records), 1, 'There is only one record.');
}
/**
* Confirms that we can fetch a record to an object explicitly.
*/
function testQueryFetchObject() {
$records = array();
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => \PDO::FETCH_OBJ));
foreach ($result as $record) {
$records[] = $record;
$this->assertTrue(is_object($record), 'Record is an object.');
$this->assertIdentical($record->name, 'John', '25 year old is John.');
}
$this->assertIdentical(count($records), 1, 'There is only one record.');
}
/**
* Confirms that we can fetch a record to an associative array explicitly.
*/
function testQueryFetchArray() {
$records = array();
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => \PDO::FETCH_ASSOC));
foreach ($result as $record) {
$records[] = $record;
if ($this->assertTrue(is_array($record), 'Record is an array.')) {
$this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.');
}
}
$this->assertIdentical(count($records), 1, 'There is only one record.');
}
/**
* Confirms that we can fetch a record into a new instance of a custom class.
*
* @see \Drupal\system\Tests\Database\FakeRecord
*/
function testQueryFetchClass() {
$records = array();
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => 'Drupal\system\Tests\Database\FakeRecord'));
foreach ($result as $record) {
$records[] = $record;
if ($this->assertTrue($record instanceof FakeRecord, 'Record is an object of class FakeRecord.')) {
$this->assertIdentical($record->name, 'John', '25 year old is John.');
}
}
$this->assertIdentical(count($records), 1, 'There is only one record.');
}
/**
* Confirms that we can fetch a record into an indexed array explicitly.
*/
function testQueryFetchNum() {
$records = array();
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => \PDO::FETCH_NUM));
foreach ($result as $record) {
$records[] = $record;
if ($this->assertTrue(is_array($record), 'Record is an array.')) {
$this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.');
}
}
$this->assertIdentical(count($records), 1, 'There is only one record');
}
/**
* Confirms that we can fetch a record into a doubly-keyed array explicitly.
*/
function testQueryFetchBoth() {
$records = array();
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => \PDO::FETCH_BOTH));
foreach ($result as $record) {
$records[] = $record;
if ($this->assertTrue(is_array($record), 'Record is an array.')) {
$this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.');
$this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.');
}
}
$this->assertIdentical(count($records), 1, 'There is only one record.');
}
/**
* Confirms that we can fetch an entire column of a result set at once.
*/
function testQueryFetchCol() {
$result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25));
$column = $result->fetchCol();
$this->assertIdentical(count($column), 3, 'fetchCol() returns the right number of records.');
$result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25));
$i = 0;
foreach ($result as $record) {
$this->assertIdentical($record->name, $column[$i++], 'Column matches direct access.');
}
}
/**
* Tests that rowCount() throws exception on SELECT query.
*/
public function testRowCount() {
$result = db_query('SELECT name FROM {test}');
try {
$result->rowCount();
$exception = FALSE;
}
catch (RowCountException $e) {
$exception = TRUE;
}
$this->assertTrue($exception, 'Exception was thrown');
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Query\NoFieldsException;
/**
* Tests the Insert query builder with default values.
*
* @group Database
*/
class InsertDefaultsTest extends DatabaseTestBase {
/**
* Tests that we can run a query that uses default values for everything.
*/
function testDefaultInsert() {
$query = db_insert('test')->useDefaults(array('job'));
$id = $query->execute();
$schema = drupal_get_module_schema('database_test', 'test');
$job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
$this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
}
/**
* Tests that no action will be preformed if no fields are specified.
*/
function testDefaultEmptyInsert() {
$num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
try {
db_insert('test')->execute();
// This is only executed if no exception has been thrown.
$this->fail('Expected exception NoFieldsException has not been thrown.');
} catch (NoFieldsException $e) {
$this->pass('Expected exception NoFieldsException has been thrown.');
}
$num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$this->assertIdentical($num_records_before, $num_records_after, 'Do nothing as no fields are specified.');
}
/**
* Tests that we can insert fields with values and defaults in the same query.
*/
function testDefaultInsertWithFields() {
$query = db_insert('test')
->fields(array('name' => 'Bob'))
->useDefaults(array('job'));
$id = $query->execute();
$schema = drupal_get_module_schema('database_test', 'test');
$job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
$this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the Insert query builder with LOB fields.
*
* @group Database
*/
class InsertLobTest extends DatabaseTestBase {
/**
* Tests that we can insert a single blob field successfully.
*/
function testInsertOneBlob() {
$data = "This is\000a test.";
$this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.');
$id = db_insert('test_one_blob')
->fields(array('blob1' => $data))
->execute();
$r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc();
$this->assertTrue($r['blob1'] === $data, format_string('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r))));
}
/**
* Tests that we can insert multiple blob fields in the same query.
*/
function testInsertMultipleBlob() {
$id = db_insert('test_two_blobs')
->fields(array(
'blob1' => 'This is',
'blob2' => 'a test',
))
->execute();
$r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc();
$this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', 'Can insert multiple blobs per row.');
}
}

View file

@ -0,0 +1,209 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the insert builder.
*
* @group Database
*/
class InsertTest extends DatabaseTestBase {
/**
* Tests very basic insert functionality.
*/
function testSimpleInsert() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$query = db_insert('test');
$query->fields(array(
'name' => 'Yoko',
'age' => '29',
));
// Check how many records are queued for insertion.
$this->assertIdentical($query->count(), 1, 'One record is queued for insertion.');
$query->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Yoko'))->fetchField();
$this->assertIdentical($saved_age, '29', 'Can retrieve after inserting.');
}
/**
* Tests that we can insert multiple records in one query object.
*/
function testMultiInsert() {
$num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$query = db_insert('test');
$query->fields(array(
'name' => 'Larry',
'age' => '30',
));
// We should be able to specify values in any order if named.
$query->values(array(
'age' => '31',
'name' => 'Curly',
));
// Check how many records are queued for insertion.
$this->assertIdentical($query->count(), 2, 'Two records are queued for insertion.');
// We should be able to say "use the field order".
// This is not the recommended mechanism for most cases, but it should work.
$query->values(array('Moe', '32'));
// Check how many records are queued for insertion.
$this->assertIdentical($query->count(), 3, 'Three records are queued for insertion.');
$query->execute();
$num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$this->assertIdentical($num_records_before + 3, $num_records_after, 'Record inserts correctly.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
$this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
}
/**
* Tests that an insert object can be reused with new data after it executes.
*/
function testRepeatedInsert() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$query = db_insert('test');
$query->fields(array(
'name' => 'Larry',
'age' => '30',
));
// Check how many records are queued for insertion.
$this->assertIdentical($query->count(), 1, 'One record is queued for insertion.');
$query->execute(); // This should run the insert, but leave the fields intact.
// We should be able to specify values in any order if named.
$query->values(array(
'age' => '31',
'name' => 'Curly',
));
// Check how many records are queued for insertion.
$this->assertIdentical($query->count(), 1, 'One record is queued for insertion.');
$query->execute();
// We should be able to say "use the field order".
$query->values(array('Moe', '32'));
// Check how many records are queued for insertion.
$this->assertIdentical($query->count(), 1, 'One record is queued for insertion.');
$query->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
$this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, 'Record inserts correctly.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
$this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
}
/**
* Tests that we can specify fields without values and specify values later.
*/
function testInsertFieldOnlyDefinition() {
// This is useful for importers, when we want to create a query and define
// its fields once, then loop over a multi-insert execution.
db_insert('test')
->fields(array('name', 'age'))
->values(array('Larry', '30'))
->values(array('Curly', '31'))
->values(array('Moe', '32'))
->execute();
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
$this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
}
/**
* Tests that inserts return the proper auto-increment ID.
*/
function testInsertLastInsertID() {
$id = db_insert('test')
->fields(array(
'name' => 'Larry',
'age' => '30',
))
->execute();
$this->assertIdentical($id, '5', 'Auto-increment ID returned successfully.');
}
/**
* Tests that the INSERT INTO ... SELECT (fields) ... syntax works.
*/
function testInsertSelectFields() {
$query = db_select('test_people', 'tp');
// The query builder will always append expressions after fields.
// Add the expression first to test that the insert fields are correctly
// re-ordered.
$query->addExpression('tp.age', 'age');
$query
->fields('tp', array('name', 'job'))
->condition('tp.name', 'Meredith');
// The resulting query should be equivalent to:
// INSERT INTO test (age, name, job)
// SELECT tp.age AS age, tp.name AS name, tp.job AS job
// FROM test_people tp
// WHERE tp.name = 'Meredith'
db_insert('test')
->from($query)
->execute();
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Meredith'))->fetchField();
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
}
/**
* Tests that the INSERT INTO ... SELECT * ... syntax works.
*/
function testInsertSelectAll() {
$query = db_select('test_people', 'tp')
->fields('tp')
->condition('tp.name', 'Meredith');
// The resulting query should be equivalent to:
// INSERT INTO test_people_copy
// SELECT *
// FROM test_people tp
// WHERE tp.name = 'Meredith'
db_insert('test_people_copy')
->from($query)
->execute();
$saved_age = db_query('SELECT age FROM {test_people_copy} WHERE name = :name', array(':name' => 'Meredith'))->fetchField();
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
}
/**
* Tests that we can INSERT INTO a special named column.
*/
function testSpecialColumnInsert() {
$id = db_insert('test_special_columns')
->fields(array(
'id' => 2,
'offset' => 'Offset value 2',
))
->execute();
$saved_value = db_query('SELECT "offset" FROM {test_special_columns} WHERE id = :id', array(':id' => 2))->fetchField();
$this->assertIdentical($saved_value, 'Offset value 2', 'Can retrieve special column name value after inserting.');
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\IntegrityConstraintViolationException;
/**
* Tests handling of some invalid data.
*
* @group Database
*/
class InvalidDataTest extends DatabaseTestBase {
/**
* Tests aborting of traditional SQL database systems with invalid data.
*/
function testInsertDuplicateData() {
// Try to insert multiple records where at least one has bad data.
try {
db_insert('test')
->fields(array('name', 'age', 'job'))
->values(array(
'name' => 'Elvis',
'age' => 63,
'job' => 'Singer',
))->values(array(
'name' => 'John', // <-- Duplicate value on unique field.
'age' => 17,
'job' => 'Consultant',
))
->values(array(
'name' => 'Frank',
'age' => 75,
'job' => 'Singer',
))
->execute();
$this->fail('Insert succeeded when it should not have.');
}
catch (IntegrityConstraintViolationException $e) {
// Check if the first record was inserted.
$name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField();
if ($name == 'Elvis') {
if (!Database::getConnection()->supportsTransactions()) {
// This is an expected fail.
// Database engines that don't support transactions can leave partial
// inserts in place when an error occurs. This is the case for MySQL
// when running on a MyISAM table.
$this->pass("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions");
}
else {
$this->fail('The whole transaction is rolled back when a duplicate key insert occurs.');
}
}
else {
$this->pass('The whole transaction is rolled back when a duplicate key insert occurs.');
}
// Ensure the other values were not inserted.
$record = db_select('test')
->fields('test', array('name', 'age'))
->condition('age', array(17, 75), 'IN')
->execute()->fetchObject();
$this->assertFalse($record, 'The rest of the insert aborted as expected.');
}
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Component\Utility\Environment;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseException;
/**
* Tests handling of large queries.
*
* @group Database
*/
class LargeQueryTest extends DatabaseTestBase {
/**
* Tests truncation of messages when max_allowed_packet exception occurs.
*/
function testMaxAllowedPacketQueryTruncating() {
// This test only makes sense if we are running on a MySQL database.
// Test if we are.
$database = Database::getConnectionInfo('default');
if ($database['default']['driver'] == 'mysql') {
// The max_allowed_packet value is configured per database instance.
// Retrieve the max_allowed_packet value from the current instance and
// check if PHP is configured with sufficient allowed memory to be able
// to generate a query larger than max_allowed_packet.
$max_allowed_packet = db_query('SELECT @@global.max_allowed_packet')->fetchField();
if (Environment::checkMemoryLimit($max_allowed_packet + (16 * 1024 * 1024))) {
$long_name = str_repeat('a', $max_allowed_packet + 1);
try {
db_query('SELECT name FROM {test} WHERE name = :name', array(':name' => $long_name));
$this->fail("An exception should be thrown for queries larger than 'max_allowed_packet'");
} catch (DatabaseException $e) {
// Close and re-open the connection. Otherwise we will run into error
// 2006 "MySQL server had gone away" afterwards.
Database::closeConnection();
Database::getConnection();
$this->assertEqual($e->getPrevious()->errorInfo[1], 1153, "Got a packet bigger than 'max_allowed_packet' bytes exception thrown.");
// Use strlen() to count the bytes exactly, not the unicode chars.
$this->assertTrue(strlen($e->getMessage()) <= $max_allowed_packet, "'max_allowed_packet' exception message truncated.");
}
}
else {
$this->verbose('The configured max_allowed_packet exceeds the php memory limit. Therefore the test is skipped.');
}
}
else {
$this->verbose('The test requires MySQL. Therefore the test is skipped.');
}
}
}

View file

@ -0,0 +1,128 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
/**
* Tests the query logging facility.
*
* @group Database
*/
class LoggingTest extends DatabaseTestBase {
/**
* Tests that we can log the existence of a query.
*/
function testEnableLogging() {
Database::startLog('testing');
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol();
// Trigger a call that does not have file in the backtrace.
call_user_func_array('db_query', array('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo')))->fetchCol();
$queries = Database::getLog('testing', 'default');
$this->assertEqual(count($queries), 3, 'Correct number of queries recorded.');
foreach ($queries as $query) {
$this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.');
}
}
/**
* Tests that we can run two logs in parallel.
*/
function testEnableMultiLogging() {
Database::startLog('testing1');
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
Database::startLog('testing2');
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol();
$queries1 = Database::getLog('testing1');
$queries2 = Database::getLog('testing2');
$this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.');
$this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.');
}
/**
* Tests logging queries against multiple targets on the same connection.
*/
function testEnableTargetLogging() {
// Clone the primary credentials to a replica connection and to another fake
// connection.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
Database::startLog('testing1');
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'replica'));//->fetchCol();
$queries1 = Database::getLog('testing1');
$this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
$this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
$this->assertEqual($queries1[1]['target'], 'replica', 'Second query used replica target.');
}
/**
* Tests that logs to separate targets use the same connection properly.
*
* This test is identical to the one above, except that it doesn't create
* a fake target so the query should fall back to running on the default
* target.
*/
function testEnableTargetLoggingNoTarget() {
Database::startLog('testing1');
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
// We use "fake" here as a target because any non-existent target will do.
// However, because all of the tests in this class share a single page
// request there is likely to be a target of "replica" from one of the other
// unit tests, so we use a target here that we know with absolute certainty
// does not exist.
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'fake'))->fetchCol();
$queries1 = Database::getLog('testing1');
$this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
$this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
$this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.');
}
/**
* Tests that we can log queries separately on different connections.
*/
function testEnableMultiConnectionLogging() {
// Clone the primary credentials to a fake connection.
// That both connections point to the same physical database is irrelevant.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('test2', 'default', $connection_info['default']);
Database::startLog('testing1');
Database::startLog('testing1', 'test2');
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
$old_key = db_set_active('test2');
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'replica'))->fetchCol();
db_set_active($old_key);
$queries1 = Database::getLog('testing1');
$queries2 = Database::getLog('testing1', 'test2');
$this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.');
$this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
}
}

View file

@ -0,0 +1,232 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Query\Merge;
use Drupal\Core\Database\Query\InvalidMergeQueryException;
/**
* Tests the MERGE query builder.
*
* @group Database
*/
class MergeTest extends DatabaseTestBase {
/**
* Confirms that we can merge-insert a record successfully.
*/
function testMergeInsert() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$result = db_merge('test_people')
->key('job', 'Presenter')
->fields(array(
'age' => 31,
'name' => 'Tiffany',
))
->execute();
$this->assertEqual($result, Merge::STATUS_INSERT, 'Insert status returned.');
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.');
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
$this->assertEqual($person->age, 31, 'Age set correctly.');
$this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
}
/**
* Confirms that we can merge-update a record successfully.
*/
function testMergeUpdate() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$result = db_merge('test_people')
->key('job', 'Speaker')
->fields(array(
'age' => 31,
'name' => 'Tiffany',
))
->execute();
$this->assertEqual($result, Merge::STATUS_UPDATE, 'Update status returned.');
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
$this->assertEqual($person->age, 31, 'Age set correctly.');
$this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
}
/**
* Confirms that we can merge-update a record successfully.
*
* This test varies from the previous test because it manually defines which
* fields are inserted, and which fields are updated.
*/
function testMergeUpdateExcept() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
db_merge('test_people')
->key('job', 'Speaker')
->insertFields(array('age' => 31))
->updateFields(array('name' => 'Tiffany'))
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
$this->assertEqual($person->age, 30, 'Age skipped correctly.');
$this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
}
/**
* Confirms that we can merge-update a record, with alternate replacement.
*/
function testMergeUpdateExplicit() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
db_merge('test_people')
->key('job', 'Speaker')
->insertFields(array(
'age' => 31,
'name' => 'Tiffany',
))
->updateFields(array(
'name' => 'Joe',
))
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Joe', 'Name set correctly.');
$this->assertEqual($person->age, 30, 'Age skipped correctly.');
$this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
}
/**
* Confirms that we can merge-update a record successfully, with expressions.
*/
function testMergeUpdateExpression() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$age_before = db_query('SELECT age FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetchField();
// This is a very contrived example, as I have no idea why you'd want to
// change age this way, but that's beside the point.
// Note that we are also double-setting age here, once as a literal and
// once as an expression. This test will only pass if the expression wins,
// which is what is supposed to happen.
db_merge('test_people')
->key('job', 'Speaker')
->fields(array('name' => 'Tiffany'))
->insertFields(array('age' => 31))
->expression('age', 'age + :age', array(':age' => 4))
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
$this->assertEqual($person->age, $age_before + 4, 'Age updated correctly.');
$this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
}
/**
* Tests that we can merge-insert without any update fields.
*/
function testMergeInsertWithoutUpdate() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
db_merge('test_people')
->key('job', 'Presenter')
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.');
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
$this->assertEqual($person->name, '', 'Name set correctly.');
$this->assertEqual($person->age, 0, 'Age set correctly.');
$this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
}
/**
* Confirms that we can merge-update without any update fields.
*/
function testMergeUpdateWithoutUpdate() {
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
db_merge('test_people')
->key('job', 'Speaker')
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.');
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.');
$this->assertEqual($person->age, 30, 'Age skipped correctly.');
$this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
db_merge('test_people')
->key('job', 'Speaker')
->insertFields(array('age' => 31))
->execute();
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.');
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.');
$this->assertEqual($person->age, 30, 'Age skipped correctly.');
$this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
}
/**
* Tests that an invalid merge query throws an exception.
*/
function testInvalidMerge() {
try {
// This query will fail because there is no key field specified.
// Normally it would throw an exception but we are suppressing it with
// the throw_exception option.
$options['throw_exception'] = FALSE;
db_merge('test_people', $options)
->fields(array(
'age' => 31,
'name' => 'Tiffany',
))
->execute();
$this->pass('$options[\'throw_exception\'] is FALSE, no InvalidMergeQueryException thrown.');
}
catch (InvalidMergeQueryException $e) {
$this->fail('$options[\'throw_exception\'] is FALSE, but InvalidMergeQueryException thrown for invalid query.');
return;
}
try {
// This query will fail because there is no key field specified.
db_merge('test_people')
->fields(array(
'age' => 31,
'name' => 'Tiffany',
))
->execute();
}
catch (InvalidMergeQueryException $e) {
$this->pass('InvalidMergeQueryException thrown for invalid query.');
return;
}
$this->fail('No InvalidMergeQueryException thrown');
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the sequences API.
*
* @group Database
*/
class NextIdTest extends KernelTestBase {
/**
* The modules to enable.
* @var array
*/
public static $modules = array('system');
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'sequences');
}
/**
* Tests that the sequences API works.
*/
function testDbNextId() {
$first = db_next_id();
$second = db_next_id();
// We can test for exact increase in here because we know there is no
// other process operating on these tables -- normally we could only
// expect $second > $first.
$this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.');
$result = db_next_id(1000);
$this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.');
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests Drupal's extended prepared statement syntax..
*
* @group Database
*/
class QueryTest extends DatabaseTestBase {
/**
* Tests that we can pass an array of values directly in the query.
*/
function testArraySubstitution() {
$names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', array(':ages[]' => array(25, 26, 27)))->fetchAll();
$this->assertEqual(count($names), 3, 'Correct number of names returned');
$names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', array(':ages[]' => array(25)))->fetchAll();
$this->assertEqual(count($names), 1, 'Correct number of names returned');
}
/**
* Tests that we can not pass a scalar value when an array is expected.
*/
function testScalarSubstitution() {
try {
$names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', array(':ages[]' => 25))->fetchAll();
$this->fail('Array placeholder with scalar argument should result in an exception.');
}
catch (\InvalidArgumentException $e) {
$this->pass('Array placeholder with scalar argument should result in an exception.');
}
}
/**
* Tests SQL injection via database query array arguments.
*/
public function testArrayArgumentsSQLInjection() {
// Attempt SQL injection and verify that it does not work.
$condition = array(
"1 ;INSERT INTO {test} (name) VALUES ('test12345678'); -- " => '',
'1' => '',
);
try {
db_query("SELECT * FROM {test} WHERE name = :name", array(':name' => $condition))->fetchObject();
$this->fail('SQL injection attempt via array arguments should result in a database exception.');
}
catch (\InvalidArgumentException $e) {
$this->pass('SQL injection attempt via array arguments should result in a database exception.');
}
// Test that the insert query that was used in the SQL injection attempt did
// not result in a row being inserted in the database.
$result = db_select('test')
->condition('name', 'test12345678')
->countQuery()
->execute()
->fetchField();
$this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
}
/**
* Tests SQL injection via condition operator.
*/
public function testConditionOperatorArgumentsSQLInjection() {
$injection = "IS NOT NULL) ;INSERT INTO {test} (name) VALUES ('test12345678'); -- ";
// Convert errors to exceptions for testing purposes below.
set_error_handler(function ($severity, $message, $filename, $lineno) {
throw new \ErrorException($message, 0, $severity, $filename, $lineno);
});
try {
$result = db_select('test', 't')
->fields('t')
->condition('name', 1, $injection)
->execute();
$this->fail('Should not be able to attempt SQL injection via condition operator.');
}
catch (\ErrorException $e) {
$this->pass('SQL injection attempt via condition arguments should result in a database exception.');
}
// Test that the insert query that was used in the SQL injection attempt did
// not result in a row being inserted in the database.
$result = db_select('test')
->condition('name', 'test12345678')
->countQuery()
->execute()
->fetchField();
$this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
// Attempt SQLi via union query with no unsafe characters.
$this->enableModules(['user']);
$this->installEntitySchema('user');
db_insert('test')
->fields(['name' => '123456'])
->execute();
$injection = "= 1 UNION ALL SELECT password FROM user WHERE uid =";
try {
$result = db_select('test', 't')
->fields('t', array('name', 'name'))
->condition('name', 1, $injection)
->execute();
$this->fail('Should not be able to attempt SQL injection via operator.');
}
catch (\ErrorException $e) {
$this->pass('SQL injection attempt via condition arguments should result in a database exception.');
}
// Attempt SQLi via union query - uppercase tablename.
db_insert('TEST_UPPERCASE')
->fields(['name' => 'secrets'])
->execute();
$injection = "IS NOT NULL) UNION ALL SELECT name FROM {TEST_UPPERCASE} -- ";
try {
$result = db_select('test', 't')
->fields('t', array('name'))
->condition('name', 1, $injection)
->execute();
$this->fail('Should not be able to attempt SQL injection via operator.');
}
catch (\ErrorException $e) {
$this->pass('SQL injection attempt via condition arguments should result in a database exception.');
}
restore_error_handler();
}
/**
* Tests numeric query parameter expansion in expressions.
*
* @see \Drupal\Core\Database\Driver\sqlite\Statement::getStatement()
* @see http://bugs.php.net/bug.php?id=45259
*/
public function testNumericExpressionSubstitution() {
$count = db_query('SELECT COUNT(*) >= 3 FROM {test}')->fetchField();
$this->assertEqual((bool) $count, TRUE);
$count = db_query('SELECT COUNT(*) >= :count FROM {test}', array(
':count' => 3,
))->fetchField();
$this->assertEqual((bool) $count, TRUE);
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the Range query functionality.
*
* @group Database
*/
class RangeQueryTest extends DatabaseTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('database_test');
/**
* Confirms that range queries work and return the correct result.
*/
function testRangeQuery() {
// Test if return correct number of rows.
$range_rows = db_query_range("SELECT name FROM {test} ORDER BY name", 1, 3)->fetchAll();
$this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.');
// Test if return target data.
$raw_rows = db_query('SELECT name FROM {test} ORDER BY name')->fetchAll();
$raw_rows = array_slice($raw_rows, 1, 3);
$this->assertEqual($range_rows, $raw_rows);
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Regression tests cases for the database layer.
*
* @group Database
*/
class RegressionTest extends DatabaseTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'user');
/**
* Ensures that non-ASCII UTF-8 data is stored in the database properly.
*/
function testRegression_310447() {
// That's a 255 character UTF-8 string.
$job = str_repeat("é", 255);
db_insert('test')
->fields(array(
'name' => $this->randomMachineName(),
'age' => 20,
'job' => $job,
))->execute();
$from_database = db_query('SELECT job FROM {test} WHERE job = :job', array(':job' => $job))->fetchField();
$this->assertIdentical($job, $from_database, 'The database handles UTF-8 characters cleanly.');
}
/**
* Tests the db_table_exists() function.
*/
function testDBTableExists() {
$this->assertIdentical(TRUE, db_table_exists('test'), 'Returns true for existent table.');
$this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.');
}
/**
* Tests the db_field_exists() function.
*/
function testDBFieldExists() {
$this->assertIdentical(TRUE, db_field_exists('test', 'name'), 'Returns true for existent column.');
$this->assertIdentical(FALSE, db_field_exists('test', 'nosuchcolumn'), 'Returns false for nonexistent column.');
}
/**
* Tests the db_index_exists() function.
*/
function testDBIndexExists() {
$this->assertIdentical(TRUE, db_index_exists('test', 'ages'), 'Returns true for existent index.');
$this->assertIdentical(FALSE, db_index_exists('test', 'nosuchindex'), 'Returns false for nonexistent index.');
}
}

View file

@ -0,0 +1,791 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\SchemaException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Component\Utility\Unicode;
/**
* Tests table creation and modification via the schema API.
*
* @group Database
*/
class SchemaTest extends KernelTestBase {
/**
* A global counter for table and field creation.
*/
protected $counter;
/**
* Tests database interactions.
*/
function testSchema() {
// Try creating a table.
$table_specification = array(
'description' => 'Schema table description may contain "quotes" and could be long—very long indeed.',
'fields' => array(
'id' => array(
'type' => 'int',
'default' => NULL,
),
'test_field' => array(
'type' => 'int',
'not null' => TRUE,
'description' => 'Schema table description may contain "quotes" and could be long—very long indeed. There could be "multiple quoted regions".',
),
'test_field_string' => array(
'type' => 'varchar',
'length' => 20,
'not null' => TRUE,
'default' => "'\"funky default'\"",
'description' => 'Schema column description for string.',
),
'test_field_string_ascii' => array(
'type' => 'varchar_ascii',
'length' => 255,
'description' => 'Schema column description for ASCII string.',
),
),
);
db_create_table('test_table', $table_specification);
// Assert that the table exists.
$this->assertTrue(db_table_exists('test_table'), 'The table exists.');
// Assert that the table comment has been set.
$this->checkSchemaComment($table_specification['description'], 'test_table');
// Assert that the column comment has been set.
$this->checkSchemaComment($table_specification['fields']['test_field']['description'], 'test_table', 'test_field');
if (Database::getConnection()->databaseType() == 'mysql') {
// Make sure that varchar fields have the correct collation.
$columns = db_query('SHOW FULL COLUMNS FROM {test_table}');
foreach ($columns as $column) {
if ($column->Field == 'test_field_string') {
$string_check = ($column->Collation == 'utf8mb4_general_ci');
}
if ($column->Field == 'test_field_string_ascii') {
$string_ascii_check = ($column->Collation == 'ascii_general_ci');
}
}
$this->assertTrue(!empty($string_check), 'string field has the right collation.');
$this->assertTrue(!empty($string_ascii_check), 'ASCII string field has the right collation.');
}
// An insert without a value for the column 'test_table' should fail.
$this->assertFalse($this->tryInsert(), 'Insert without a default failed.');
// Add a default value to the column.
db_field_set_default('test_table', 'test_field', 0);
// The insert should now succeed.
$this->assertTrue($this->tryInsert(), 'Insert with a default succeeded.');
// Remove the default.
db_field_set_no_default('test_table', 'test_field');
// The insert should fail again.
$this->assertFalse($this->tryInsert(), 'Insert without a default failed.');
// Test for fake index and test for the boolean result of indexExists().
$index_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
$this->assertIdentical($index_exists, FALSE, 'Fake index does not exists');
// Add index.
db_add_index('test_table', 'test_field', array('test_field'), $table_specification);
// Test for created index and test for the boolean result of indexExists().
$index_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
$this->assertIdentical($index_exists, TRUE, 'Index created.');
// Rename the table.
db_rename_table('test_table', 'test_table2');
// Index should be renamed.
$index_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
$this->assertTrue($index_exists, 'Index was renamed.');
// We need the default so that we can insert after the rename.
db_field_set_default('test_table2', 'test_field', 0);
$this->assertFalse($this->tryInsert(), 'Insert into the old table failed.');
$this->assertTrue($this->tryInsert('test_table2'), 'Insert into the new table succeeded.');
// We should have successfully inserted exactly two rows.
$count = db_query('SELECT COUNT(*) FROM {test_table2}')->fetchField();
$this->assertEqual($count, 2, 'Two fields were successfully inserted.');
// Try to drop the table.
db_drop_table('test_table2');
$this->assertFalse(db_table_exists('test_table2'), 'The dropped table does not exist.');
// Recreate the table.
db_create_table('test_table', $table_specification);
db_field_set_default('test_table', 'test_field', 0);
db_add_field('test_table', 'test_serial', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => 'Added column description.'));
// Assert that the column comment has been set.
$this->checkSchemaComment('Added column description.', 'test_table', 'test_serial');
// Change the new field to a serial column.
db_change_field('test_table', 'test_serial', 'test_serial', array('type' => 'serial', 'not null' => TRUE, 'description' => 'Changed column description.'), array('primary key' => array('test_serial')));
// Assert that the column comment has been set.
$this->checkSchemaComment('Changed column description.', 'test_table', 'test_serial');
$this->assertTrue($this->tryInsert(), 'Insert with a serial succeeded.');
$max1 = db_query('SELECT MAX(test_serial) FROM {test_table}')->fetchField();
$this->assertTrue($this->tryInsert(), 'Insert with a serial succeeded.');
$max2 = db_query('SELECT MAX(test_serial) FROM {test_table}')->fetchField();
$this->assertTrue($max2 > $max1, 'The serial is monotone.');
$count = db_query('SELECT COUNT(*) FROM {test_table}')->fetchField();
$this->assertEqual($count, 2, 'There were two rows.');
// Test renaming of keys and constraints.
db_drop_table('test_table');
$table_specification = array(
'fields' => array(
'id' => array(
'type' => 'serial',
'not null' => TRUE,
),
'test_field' => array(
'type' => 'int',
'default' => 0,
),
),
'primary key' => array('id'),
'unique keys' => array(
'test_field' => array('test_field'),
),
);
db_create_table('test_table', $table_specification);
// Tests for indexes are Database specific.
$db_type = Database::getConnection()->databaseType();
// Test for existing primary and unique keys.
switch ($db_type) {
case 'pgsql':
$primary_key_exists = Database::getConnection()->schema()->constraintExists('test_table', '__pkey');
$unique_key_exists = Database::getConnection()->schema()->constraintExists('test_table', 'test_field' . '__key');
break;
case 'sqlite':
// SQLite does not create a standalone index for primary keys.
$primary_key_exists = TRUE;
$unique_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
break;
default:
$primary_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'PRIMARY');
$unique_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
break;
}
$this->assertIdentical($primary_key_exists, TRUE, 'Primary key created.');
$this->assertIdentical($unique_key_exists, TRUE, 'Unique key created.');
db_rename_table('test_table', 'test_table2');
// Test for renamed primary and unique keys.
switch ($db_type) {
case 'pgsql':
$renamed_primary_key_exists = Database::getConnection()->schema()->constraintExists('test_table2', '__pkey');
$renamed_unique_key_exists = Database::getConnection()->schema()->constraintExists('test_table2', 'test_field' . '__key');
break;
case 'sqlite':
// SQLite does not create a standalone index for primary keys.
$renamed_primary_key_exists = TRUE;
$renamed_unique_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
break;
default:
$renamed_primary_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'PRIMARY');
$renamed_unique_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
break;
}
$this->assertIdentical($renamed_primary_key_exists, TRUE, 'Primary key was renamed.');
$this->assertIdentical($renamed_unique_key_exists, TRUE, 'Unique key was renamed.');
// For PostgreSQL check in addition that sequence was renamed.
if ($db_type == 'pgsql') {
// Get information about new table.
$info = Database::getConnection()->schema()->queryTableInformation('test_table2');
$sequence_name = Database::getConnection()->schema()->prefixNonTable('test_table2', 'id', 'seq');
$this->assertEqual($sequence_name, current($info->sequences), 'Sequence was renamed.');
}
// Use database specific data type and ensure that table is created.
$table_specification = array(
'description' => 'Schema table description.',
'fields' => array(
'timestamp' => array(
'mysql_type' => 'timestamp',
'pgsql_type' => 'timestamp',
'sqlite_type' => 'datetime',
'not null' => FALSE,
'default' => NULL,
),
),
);
try {
db_create_table('test_timestamp', $table_specification);
}
catch (\Exception $e) {}
$this->assertTrue(db_table_exists('test_timestamp'), 'Table with database specific datatype was created.');
}
/**
* Tests that indexes on string fields are limited to 191 characters on MySQL.
*
* @see \Drupal\Core\Database\Driver\mysql\Schema::getNormalizedIndexes()
*/
function testIndexLength() {
if (Database::getConnection()->databaseType() != 'mysql') {
return;
}
$table_specification = array(
'fields' => array(
'id' => array(
'type' => 'int',
'default' => NULL,
),
'test_field_text' => array(
'type' => 'text',
'not null' => TRUE,
),
'test_field_string_long' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'test_field_string_ascii_long' => array(
'type' => 'varchar_ascii',
'length' => 255,
),
'test_field_string_short' => array(
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
),
),
'indexes' => array(
'test_regular' => array(
'test_field_text',
'test_field_string_long',
'test_field_string_ascii_long',
'test_field_string_short',
),
'test_length' => array(
array('test_field_text', 128),
array('test_field_string_long', 128),
array('test_field_string_ascii_long', 128),
array('test_field_string_short', 128),
),
'test_mixed' => array(
array('test_field_text', 200),
'test_field_string_long',
array('test_field_string_ascii_long', 200),
'test_field_string_short',
),
),
);
db_create_table('test_table_index_length', $table_specification);
$schema_object = Database::getConnection()->schema();
// Ensure expected exception thrown when adding index with missing info.
$expected_exception_message = "MySQL needs the 'test_field_text' field specification in order to normalize the 'test_regular' index";
$missing_field_spec = $table_specification;
unset($missing_field_spec['fields']['test_field_text']);
try {
$schema_object->addIndex('test_table_index_length', 'test_separate', [['test_field_text', 200]], $missing_field_spec);
$this->fail('SchemaException not thrown when adding index with missing information.');
}
catch (SchemaException $e) {
$this->assertEqual($expected_exception_message, $e->getMessage());
}
// Add a separate index.
$schema_object->addIndex('test_table_index_length', 'test_separate', [['test_field_text', 200]], $table_specification);
$table_specification_with_new_index = $table_specification;
$table_specification_with_new_index['indexes']['test_separate'] = [['test_field_text', 200]];
// Ensure that the exceptions of addIndex are thrown as expected.
try {
$schema_object->addIndex('test_table_index_length', 'test_separate', [['test_field_text', 200]], $table_specification);
$this->fail('\Drupal\Core\Database\SchemaObjectExistsException exception missed.');
}
catch (SchemaObjectExistsException $e) {
$this->pass('\Drupal\Core\Database\SchemaObjectExistsException thrown when index already exists.');
}
try {
$schema_object->addIndex('test_table_non_existing', 'test_separate', [['test_field_text', 200]], $table_specification);
$this->fail('\Drupal\Core\Database\SchemaObjectDoesNotExistException exception missed.');
}
catch (SchemaObjectDoesNotExistException $e) {
$this->pass('\Drupal\Core\Database\SchemaObjectDoesNotExistException thrown when index already exists.');
}
// Get index information.
$results = db_query('SHOW INDEX FROM {test_table_index_length}');
$expected_lengths = array(
'test_regular' => array(
'test_field_text' => 191,
'test_field_string_long' => 191,
'test_field_string_ascii_long' => NULL,
'test_field_string_short' => NULL,
),
'test_length' => array(
'test_field_text' => 128,
'test_field_string_long' => 128,
'test_field_string_ascii_long' => 128,
'test_field_string_short' => NULL,
),
'test_mixed' => array(
'test_field_text' => 191,
'test_field_string_long' => 191,
'test_field_string_ascii_long' => 200,
'test_field_string_short' => NULL,
),
'test_separate' => array(
'test_field_text' => 191,
),
);
// Count the number of columns defined in the indexes.
$column_count = 0;
foreach ($table_specification_with_new_index['indexes'] as $index) {
foreach ($index as $field) {
$column_count++;
}
}
$test_count = 0;
foreach ($results as $result) {
$this->assertEqual($result->Sub_part, $expected_lengths[$result->Key_name][$result->Column_name], 'Index length matches expected value.');
$test_count++;
}
$this->assertEqual($test_count, $column_count, 'Number of tests matches expected value.');
}
/**
* Tests inserting data into an existing table.
*
* @param $table
* The database table to insert data into.
*
* @return
* TRUE if the insert succeeded, FALSE otherwise.
*/
function tryInsert($table = 'test_table') {
try {
db_insert($table)
->fields(array('id' => mt_rand(10, 20)))
->execute();
return TRUE;
}
catch (\Exception $e) {
return FALSE;
}
}
/**
* Checks that a table or column comment matches a given description.
*
* @param $description
* The asserted description.
* @param $table
* The table to test.
* @param $column
* Optional column to test.
*/
function checkSchemaComment($description, $table, $column = NULL) {
if (method_exists(Database::getConnection()->schema(), 'getComment')) {
$comment = Database::getConnection()->schema()->getComment($table, $column);
// The schema comment truncation for mysql is different.
if (Database::getConnection()->databaseType() == 'mysql') {
$max_length = $column ? 255 : 60;
$description = Unicode::truncate($description, $max_length, TRUE, TRUE);
}
$this->assertEqual($comment, $description, 'The comment matches the schema description.');
}
}
/**
* Tests creating unsigned columns and data integrity thereof.
*/
function testUnsignedColumns() {
// First create the table with just a serial column.
$table_name = 'unsigned_table';
$table_spec = array(
'fields' => array('serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE)),
'primary key' => array('serial_column'),
);
db_create_table($table_name, $table_spec);
// Now set up columns for the other types.
$types = array('int', 'float', 'numeric');
foreach ($types as $type) {
$column_spec = array('type' => $type, 'unsigned'=> TRUE);
if ($type == 'numeric') {
$column_spec += array('precision' => 10, 'scale' => 0);
}
$column_name = $type . '_column';
$table_spec['fields'][$column_name] = $column_spec;
db_add_field($table_name, $column_name, $column_spec);
}
// Finally, check each column and try to insert invalid values into them.
foreach ($table_spec['fields'] as $column_name => $column_spec) {
$this->assertTrue(db_field_exists($table_name, $column_name), format_string('Unsigned @type column was created.', array('@type' => $column_spec['type'])));
$this->assertFalse($this->tryUnsignedInsert($table_name, $column_name), format_string('Unsigned @type column rejected a negative value.', array('@type' => $column_spec['type'])));
}
}
/**
* Tries to insert a negative value into columns defined as unsigned.
*
* @param $table_name
* The table to insert.
* @param $column_name
* The column to insert.
*
* @return
* TRUE if the insert succeeded, FALSE otherwise.
*/
function tryUnsignedInsert($table_name, $column_name) {
try {
db_insert($table_name)
->fields(array($column_name => -1))
->execute();
return TRUE;
}
catch (\Exception $e) {
return FALSE;
}
}
/**
* Tests adding columns to an existing table.
*/
function testSchemaAddField() {
// Test varchar types.
foreach (array(1, 32, 128, 256, 512) as $length) {
$base_field_spec = array(
'type' => 'varchar',
'length' => $length,
);
$variations = array(
array('not null' => FALSE),
array('not null' => FALSE, 'default' => '7'),
array('not null' => FALSE, 'default' => substr('"thing"', 0, $length)),
array('not null' => FALSE, 'default' => substr("\"'hing", 0, $length)),
array('not null' => TRUE, 'initial' => 'd'),
array('not null' => FALSE, 'default' => NULL),
array('not null' => TRUE, 'initial' => 'd', 'default' => '7'),
);
foreach ($variations as $variation) {
$field_spec = $variation + $base_field_spec;
$this->assertFieldAdditionRemoval($field_spec);
}
}
// Test int and float types.
foreach (array('int', 'float') as $type) {
foreach (array('tiny', 'small', 'medium', 'normal', 'big') as $size) {
$base_field_spec = array(
'type' => $type,
'size' => $size,
);
$variations = array(
array('not null' => FALSE),
array('not null' => FALSE, 'default' => 7),
array('not null' => TRUE, 'initial' => 1),
array('not null' => TRUE, 'initial' => 1, 'default' => 7),
);
foreach ($variations as $variation) {
$field_spec = $variation + $base_field_spec;
$this->assertFieldAdditionRemoval($field_spec);
}
}
}
// Test numeric types.
foreach (array(1, 5, 10, 40, 65) as $precision) {
foreach (array(0, 2, 10, 30) as $scale) {
// Skip combinations where precision is smaller than scale.
if ($precision <= $scale) {
continue;
}
$base_field_spec = array(
'type' => 'numeric',
'scale' => $scale,
'precision' => $precision,
);
$variations = array(
array('not null' => FALSE),
array('not null' => FALSE, 'default' => 7),
array('not null' => TRUE, 'initial' => 1),
array('not null' => TRUE, 'initial' => 1, 'default' => 7),
);
foreach ($variations as $variation) {
$field_spec = $variation + $base_field_spec;
$this->assertFieldAdditionRemoval($field_spec);
}
}
}
}
/**
* Asserts that a given field can be added and removed from a table.
*
* The addition test covers both defining a field of a given specification
* when initially creating at table and extending an existing table.
*
* @param $field_spec
* The schema specification of the field.
*/
protected function assertFieldAdditionRemoval($field_spec) {
// Try creating the field on a new table.
$table_name = 'test_table_' . ($this->counter++);
$table_spec = array(
'fields' => array(
'serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
'test_field' => $field_spec,
),
'primary key' => array('serial_column'),
);
db_create_table($table_name, $table_spec);
$this->pass(format_string('Table %table created.', array('%table' => $table_name)));
// Check the characteristics of the field.
$this->assertFieldCharacteristics($table_name, 'test_field', $field_spec);
// Clean-up.
db_drop_table($table_name);
// Try adding a field to an existing table.
$table_name = 'test_table_' . ($this->counter++);
$table_spec = array(
'fields' => array(
'serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
),
'primary key' => array('serial_column'),
);
db_create_table($table_name, $table_spec);
$this->pass(format_string('Table %table created.', array('%table' => $table_name)));
// Insert some rows to the table to test the handling of initial values.
for ($i = 0; $i < 3; $i++) {
db_insert($table_name)
->useDefaults(array('serial_column'))
->execute();
}
db_add_field($table_name, 'test_field', $field_spec);
$this->pass(format_string('Column %column created.', array('%column' => 'test_field')));
// Check the characteristics of the field.
$this->assertFieldCharacteristics($table_name, 'test_field', $field_spec);
// Clean-up.
db_drop_field($table_name, 'test_field');
// Add back the field and then try to delete a field which is also a primary
// key.
db_add_field($table_name, 'test_field', $field_spec);
db_drop_field($table_name, 'serial_column');
db_drop_table($table_name);
}
/**
* Asserts that a newly added field has the correct characteristics.
*/
protected function assertFieldCharacteristics($table_name, $field_name, $field_spec) {
// Check that the initial value has been registered.
if (isset($field_spec['initial'])) {
// There should be no row with a value different then $field_spec['initial'].
$count = db_select($table_name)
->fields($table_name, array('serial_column'))
->condition($field_name, $field_spec['initial'], '<>')
->countQuery()
->execute()
->fetchField();
$this->assertEqual($count, 0, 'Initial values filled out.');
}
// Check that the default value has been registered.
if (isset($field_spec['default'])) {
// Try inserting a row, and check the resulting value of the new column.
$id = db_insert($table_name)
->useDefaults(array('serial_column'))
->execute();
$field_value = db_select($table_name)
->fields($table_name, array($field_name))
->condition('serial_column', $id)
->execute()
->fetchField();
$this->assertEqual($field_value, $field_spec['default'], 'Default value registered.');
}
}
/**
* Tests changing columns between types.
*/
function testSchemaChangeField() {
$field_specs = array(
array('type' => 'int', 'size' => 'normal', 'not null' => FALSE),
array('type' => 'int', 'size' => 'normal', 'not null' => TRUE, 'initial' => 1, 'default' => 17),
array('type' => 'float', 'size' => 'normal', 'not null' => FALSE),
array('type' => 'float', 'size' => 'normal', 'not null' => TRUE, 'initial' => 1, 'default' => 7.3),
array('type' => 'numeric', 'scale' => 2, 'precision' => 10, 'not null' => FALSE),
array('type' => 'numeric', 'scale' => 2, 'precision' => 10, 'not null' => TRUE, 'initial' => 1, 'default' => 7),
);
foreach ($field_specs as $i => $old_spec) {
foreach ($field_specs as $j => $new_spec) {
if ($i === $j) {
// Do not change a field into itself.
continue;
}
$this->assertFieldChange($old_spec, $new_spec);
}
}
$field_specs = array(
array('type' => 'varchar_ascii', 'length' => '255'),
array('type' => 'varchar', 'length' => '255'),
array('type' => 'text'),
array('type' => 'blob', 'size' => 'big'),
);
foreach ($field_specs as $i => $old_spec) {
foreach ($field_specs as $j => $new_spec) {
if ($i === $j) {
// Do not change a field into itself.
continue;
}
// Note if the serialized data contained an object this would fail on
// Postgres.
// @see https://www.drupal.org/node/1031122
$this->assertFieldChange($old_spec, $new_spec, serialize(['string' => "This \n has \\\\ some backslash \"*string action.\\n"]));
}
}
}
/**
* Asserts that a field can be changed from one spec to another.
*
* @param $old_spec
* The beginning field specification.
* @param $new_spec
* The ending field specification.
*/
protected function assertFieldChange($old_spec, $new_spec, $test_data = NULL) {
$table_name = 'test_table_' . ($this->counter++);
$table_spec = array(
'fields' => array(
'serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
'test_field' => $old_spec,
),
'primary key' => array('serial_column'),
);
db_create_table($table_name, $table_spec);
$this->pass(format_string('Table %table created.', array('%table' => $table_name)));
// Check the characteristics of the field.
$this->assertFieldCharacteristics($table_name, 'test_field', $old_spec);
// Remove inserted rows.
db_truncate($table_name)->execute();
if ($test_data) {
$id = db_insert($table_name)
->fields(['test_field'], [$test_data])
->execute();
}
// Change the field.
db_change_field($table_name, 'test_field', 'test_field', $new_spec);
if ($test_data) {
$field_value = db_select($table_name)
->fields($table_name, ['test_field'])
->condition('serial_column', $id)
->execute()
->fetchField();
$this->assertIdentical($field_value, $test_data);
}
// Check the field was changed.
$this->assertFieldCharacteristics($table_name, 'test_field', $new_spec);
// Clean-up.
db_drop_table($table_name);
}
/**
* Tests the findTables() method.
*/
public function testFindTables() {
// We will be testing with three tables, two of them using the default
// prefix and the third one with an individually specified prefix.
// Set up a new connection with different connection info.
$connection_info = Database::getConnectionInfo();
// Add per-table prefix to the second table.
$new_connection_info = $connection_info['default'];
$new_connection_info['prefix']['test_2_table'] = $new_connection_info['prefix']['default'] . '_shared_';
Database::addConnectionInfo('test', 'default', $new_connection_info);
Database::setActiveConnection('test');
// Create the tables.
$table_specification = [
'description' => 'Test table.',
'fields' => [
'id' => [
'type' => 'int',
'default' => NULL,
],
],
];
Database::getConnection()->schema()->createTable('test_1_table', $table_specification);
Database::getConnection()->schema()->createTable('test_2_table', $table_specification);
Database::getConnection()->schema()->createTable('the_third_table', $table_specification);
// Check the "all tables" syntax.
$tables = Database::getConnection()->schema()->findTables('%');
sort($tables);
$expected = [
// The 'config' table is added by
// \Drupal\KernelTests\KernelTestBase::containerBuild().
'config',
'test_1_table',
// This table uses a per-table prefix, yet it is returned as un-prefixed.
'test_2_table',
'the_third_table',
];
$this->assertEqual($tables, $expected, 'All tables were found.');
// Check the restrictive syntax.
$tables = Database::getConnection()->schema()->findTables('test_%');
sort($tables);
$expected = [
'test_1_table',
'test_2_table',
];
$this->assertEqual($tables, $expected, 'Two tables were found.');
// Go back to the initial connection.
Database::setActiveConnection('default');
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests cloning Select queries.
*
* @group Database
*/
class SelectCloneTest extends DatabaseTestBase {
/**
* Test that subqueries as value within conditions are cloned properly.
*/
function testSelectConditionSubQueryCloning() {
$subquery = db_select('test', 't');
$subquery->addField('t', 'id', 'id');
$subquery->condition('age', 28, '<');
$query = db_select('test', 't');
$query->addField('t', 'name', 'name');
$query->condition('id', $subquery, 'IN');
$clone = clone $query;
// Cloned query should not be altered by the following modification
// happening on original query.
$subquery->condition('age', 25, '>');
$clone_result = $clone->countQuery()->execute()->fetchField();
$query_result = $query->countQuery()->execute()->fetchField();
// Make sure the cloned query has not been modified
$this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows');
$this->assertEqual(2, $query_result, 'The query returns the expected number of rows');
}
}

View file

@ -0,0 +1,378 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\RowCountException;
use Drupal\user\Entity\User;
/**
* Tests the Select query builder with more complex queries.
*
* @group Database
*/
class SelectComplexTest extends DatabaseTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'user', 'node_access_test', 'field');
/**
* Tests simple JOIN statements.
*/
function testDefaultJoin() {
$query = db_select('test_task', 't');
$people_alias = $query->join('test', 'p', 't.pid = p.id');
$name_field = $query->addField($people_alias, 'name', 'name');
$query->addField('t', 'task', 'task');
$priority_field = $query->addField('t', 'priority', 'priority');
$query->orderBy($priority_field);
$result = $query->execute();
$num_records = 0;
$last_priority = 0;
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.');
$this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.');
$last_priority = $record->$priority_field;
}
$this->assertEqual($num_records, 7, 'Returned the correct number of rows.');
}
/**
* Tests LEFT OUTER joins.
*/
function testLeftOuterJoin() {
$query = db_select('test', 'p');
$people_alias = $query->leftJoin('test_task', 't', 't.pid = p.id');
$name_field = $query->addField('p', 'name', 'name');
$query->addField($people_alias, 'task', 'task');
$query->addField($people_alias, 'priority', 'priority');
$query->orderBy($name_field);
$result = $query->execute();
$num_records = 0;
$last_name = 0;
foreach ($result as $record) {
$num_records++;
$this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.');
}
$this->assertEqual($num_records, 8, 'Returned the correct number of rows.');
}
/**
* Tests GROUP BY clauses.
*/
function testGroupBy() {
$query = db_select('test_task', 't');
$count_field = $query->addExpression('COUNT(task)', 'num');
$task_field = $query->addField('t', 'task');
$query->orderBy($count_field);
$query->groupBy($task_field);
$result = $query->execute();
$num_records = 0;
$last_count = 0;
$records = array();
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
$last_count = $record->$count_field;
$records[$record->$task_field] = $record->$count_field;
}
$correct_results = array(
'eat' => 1,
'sleep' => 2,
'code' => 1,
'found new band' => 1,
'perform at superbowl' => 1,
);
foreach ($correct_results as $task => $count) {
$this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
}
$this->assertEqual($num_records, 6, 'Returned the correct number of total rows.');
}
/**
* Tests GROUP BY and HAVING clauses together.
*/
function testGroupByAndHaving() {
$query = db_select('test_task', 't');
$count_field = $query->addExpression('COUNT(task)', 'num');
$task_field = $query->addField('t', 'task');
$query->orderBy($count_field);
$query->groupBy($task_field);
$query->having('COUNT(task) >= 2');
$result = $query->execute();
$num_records = 0;
$last_count = 0;
$records = array();
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->$count_field >= 2, 'Record has the minimum count.');
$this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
$last_count = $record->$count_field;
$records[$record->$task_field] = $record->$count_field;
}
$correct_results = array(
'sleep' => 2,
);
foreach ($correct_results as $task => $count) {
$this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
}
$this->assertEqual($num_records, 1, 'Returned the correct number of total rows.');
}
/**
* Tests range queries.
*
* The SQL clause varies with the database.
*/
function testRange() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->range(0, 2);
$query_result = $query->countQuery()->execute()->fetchField();
$this->assertEqual($query_result, 2, 'Returned the correct number of rows.');
}
/**
* Tests distinct queries.
*/
function testDistinct() {
$query = db_select('test_task');
$query->addField('test_task', 'task');
$query->distinct();
$query_result = $query->countQuery()->execute()->fetchField();
$this->assertEqual($query_result, 6, 'Returned the correct number of rows.');
}
/**
* Tests that we can generate a count query from a built query.
*/
function testCountQuery() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->orderBy('name');
$count = $query->countQuery()->execute()->fetchField();
$this->assertEqual($count, 4, 'Counted the correct number of records.');
// Now make sure we didn't break the original query! We should still have
// all of the fields we asked for.
$record = $query->execute()->fetch();
$this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
$this->assertEqual($record->$age_field, 27, 'Correct data retrieved.');
}
/**
* Tests having queries.
*/
function testHavingCountQuery() {
$query = db_select('test')
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
->groupBy('age')
->having('age + 1 > 0');
$query->addField('test', 'age');
$query->addExpression('age + 1');
$count = count($query->execute()->fetchCol());
$this->assertEqual($count, 4, 'Counted the correct number of records.');
}
/**
* Tests that countQuery removes 'all_fields' statements and ordering clauses.
*/
function testCountQueryRemovals() {
$query = db_select('test');
$query->fields('test');
$query->orderBy('name');
$count = $query->countQuery();
// Check that the 'all_fields' statement is handled properly.
$tables = $query->getTables();
$this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.');
$tables = $count->getTables();
$this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.');
// Check that the ordering clause is handled properly.
$orderby = $query->getOrderBy();
// The orderby string is different for PostgreSQL.
// @see Drupal\Core\Database\Driver\pgsql\Select::orderBy()
$db_type = Database::getConnection()->databaseType();
$this->assertEqual($orderby['name'], ($db_type == 'pgsql' ? 'ASC NULLS FIRST' : 'ASC'), 'Query correctly sets ordering clause.');
$orderby = $count->getOrderBy();
$this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering clause.');
// Make sure that the count query works.
$count = $count->execute()->fetchField();
$this->assertEqual($count, 4, 'Counted the correct number of records.');
}
/**
* Tests that countQuery properly removes fields and expressions.
*/
function testCountQueryFieldRemovals() {
// countQuery should remove all fields and expressions, so this can be
// tested by adding a non-existent field and expression: if it ends
// up in the query, an error will be thrown. If not, it will return the
// number of records, which in this case happens to be 4 (there are four
// records in the {test} table).
$query = db_select('test');
$query->fields('test', array('fail'));
$this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed fields');
$query = db_select('test');
$query->addExpression('fail');
$this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions');
}
/**
* Tests that we can generate a count query from a query with distinct.
*/
function testCountQueryDistinct() {
$query = db_select('test_task');
$query->addField('test_task', 'task');
$query->distinct();
$count = $query->countQuery()->execute()->fetchField();
$this->assertEqual($count, 6, 'Counted the correct number of records.');
}
/**
* Tests that we can generate a count query from a query with GROUP BY.
*/
function testCountQueryGroupBy() {
$query = db_select('test_task');
$query->addField('test_task', 'pid');
$query->groupBy('pid');
$count = $query->countQuery()->execute()->fetchField();
$this->assertEqual($count, 3, 'Counted the correct number of records.');
// Use a column alias as, without one, the query can succeed for the wrong
// reason.
$query = db_select('test_task');
$query->addField('test_task', 'pid', 'pid_alias');
$query->addExpression('COUNT(test_task.task)', 'count');
$query->groupBy('pid_alias');
$query->orderBy('pid_alias', 'asc');
$count = $query->countQuery()->execute()->fetchField();
$this->assertEqual($count, 3, 'Counted the correct number of records.');
}
/**
* Confirms that we can properly nest conditional clauses.
*/
function testNestedConditions() {
// This query should translate to:
// "SELECT job FROM {test} WHERE name = 'Paul' AND (age = 26 OR age = 27)"
// That should find only one record. Yes it's a non-optimal way of writing
// that query but that's not the point!
$query = db_select('test');
$query->addField('test', 'job');
$query->condition('name', 'Paul');
$query->condition(db_or()->condition('age', 26)->condition('age', 27));
$job = $query->execute()->fetchField();
$this->assertEqual($job, 'Songwriter', 'Correct data retrieved.');
}
/**
* Confirms we can join on a single table twice with a dynamic alias.
*/
function testJoinTwice() {
$query = db_select('test')->fields('test');
$alias = $query->join('test', 'test', 'test.job = %alias.job');
$query->addField($alias, 'name', 'othername');
$query->addField($alias, 'job', 'otherjob');
$query->where("$alias.name <> test.name");
$crowded_job = $query->execute()->fetch();
$this->assertEqual($crowded_job->job, $crowded_job->otherjob, 'Correctly joined same table twice.');
$this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.');
}
/**
* Tests that we can join on a query.
*/
function testJoinSubquery() {
$this->installSchema('system', 'sequences');
$account = User::create([
'name' => $this->randomMachineName(),
'mail' => $this->randomMachineName() . '@example.com',
]);
$query = db_select('test_task', 'tt', array('target' => 'replica'));
$query->addExpression('tt.pid + 1', 'abc');
$query->condition('priority', 1, '>');
$query->condition('priority', 100, '<');
$subquery = db_select('test', 'tp');
$subquery->join('test_one_blob', 'tpb', 'tp.id = tpb.id');
$subquery->join('node', 'n', 'tp.id = n.nid');
$subquery->addTag('node_access');
$subquery->addMetaData('account', $account);
$subquery->addField('tp', 'id');
$subquery->condition('age', 5, '>');
$subquery->condition('age', 500, '<');
$query->leftJoin($subquery, 'sq', 'tt.pid = sq.id');
$query->join('test_one_blob', 'tb3', 'tt.pid = tb3.id');
// Construct the query string.
// This is the same sequence that SelectQuery::execute() goes through.
$query->preExecute();
$query->getArguments();
$str = (string) $query;
// Verify that the string only has one copy of condition placeholder 0.
$pos = strpos($str, 'db_condition_placeholder_0', 0);
$pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1);
$this->assertFalse($pos2, 'Condition placeholder is not repeated.');
}
/**
* Tests that rowCount() throws exception on SELECT query.
*/
function testSelectWithRowCount() {
$query = db_select('test');
$query->addField('test', 'name');
$result = $query->execute();
try {
$result->rowCount();
$exception = FALSE;
}
catch (RowCountException $e) {
$exception = TRUE;
}
$this->assertTrue($exception, 'Exception was thrown');
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the Select query builder.
*
* @group Database
*/
class SelectOrderedTest extends DatabaseTestBase {
/**
* Tests basic ORDER BY.
*/
function testSimpleSelectOrdered() {
$query = db_select('test');
$query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->orderBy($age_field);
$result = $query->execute();
$num_records = 0;
$last_age = 0;
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->age >= $last_age, 'Results returned in correct order.');
$last_age = $record->age;
}
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
}
/**
* Tests multiple ORDER BY.
*/
function testSimpleSelectMultiOrdered() {
$query = db_select('test');
$query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$job_field = $query->addField('test', 'job');
$query->orderBy($job_field);
$query->orderBy($age_field);
$result = $query->execute();
$num_records = 0;
$expected = array(
array('Ringo', 28, 'Drummer'),
array('John', 25, 'Singer'),
array('George', 27, 'Singer'),
array('Paul', 26, 'Songwriter'),
);
$results = $result->fetchAll(\PDO::FETCH_NUM);
foreach ($expected as $k => $record) {
$num_records++;
foreach ($record as $kk => $col) {
if ($expected[$k][$kk] != $results[$k][$kk]) {
$this->assertTrue(FALSE, 'Results returned in correct order.');
}
}
}
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
}
/**
* Tests ORDER BY descending.
*/
function testSimpleSelectOrderedDesc() {
$query = db_select('test');
$query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->orderBy($age_field, 'DESC');
$result = $query->execute();
$num_records = 0;
$last_age = 100000000;
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->age <= $last_age, 'Results returned in correct order.');
$last_age = $record->age;
}
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
}
}

View file

@ -0,0 +1,178 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the Select query builder.
*
* @group Database
*/
class SelectSubqueryTest extends DatabaseTestBase {
/**
* Tests that we can use a subquery in a FROM clause.
*/
function testFromSubquerySelect() {
// Create a subquery, which is just a normal query object.
$subquery = db_select('test_task', 'tt');
$subquery->addField('tt', 'pid', 'pid');
$subquery->addField('tt', 'task', 'task');
$subquery->condition('priority', 1);
for ($i = 0; $i < 2; $i++) {
// Create another query that joins against the virtual table resulting
// from the subquery.
$select = db_select($subquery, 'tt2');
$select->join('test', 't', 't.id=tt2.pid');
$select->addField('t', 'name');
if ($i) {
// Use a different number of conditions here to confuse the subquery
// placeholder counter, testing https://www.drupal.org/node/1112854.
$select->condition('name', 'John');
}
$select->condition('task', 'code');
// The resulting query should be equivalent to:
// SELECT t.name
// FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt
// INNER JOIN test t ON t.id=tt.pid
// WHERE tt.task = 'code'
$people = $select->execute()->fetchCol();
$this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
}
}
/**
* Tests that we can use a subquery in a FROM clause with a LIMIT.
*/
function testFromSubquerySelectWithLimit() {
// Create a subquery, which is just a normal query object.
$subquery = db_select('test_task', 'tt');
$subquery->addField('tt', 'pid', 'pid');
$subquery->addField('tt', 'task', 'task');
$subquery->orderBy('priority', 'DESC');
$subquery->range(0, 1);
// Create another query that joins against the virtual table resulting
// from the subquery.
$select = db_select($subquery, 'tt2');
$select->join('test', 't', 't.id=tt2.pid');
$select->addField('t', 'name');
// The resulting query should be equivalent to:
// SELECT t.name
// FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt ORDER BY priority DESC LIMIT 1 OFFSET 0) tt
// INNER JOIN test t ON t.id=tt.pid
$people = $select->execute()->fetchCol();
$this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
}
/**
* Tests that we can use a subquery in a WHERE clause.
*/
function testConditionSubquerySelect() {
// Create a subquery, which is just a normal query object.
$subquery = db_select('test_task', 'tt');
$subquery->addField('tt', 'pid', 'pid');
$subquery->condition('tt.priority', 1);
// Create another query that joins against the virtual table resulting
// from the subquery.
$select = db_select('test_task', 'tt2');
$select->addField('tt2', 'task');
$select->condition('tt2.pid', $subquery, 'IN');
// The resulting query should be equivalent to:
// SELECT tt2.name
// FROM test tt2
// WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1)
$people = $select->execute()->fetchCol();
$this->assertEqual(count($people), 5, 'Returned the correct number of rows.');
}
/**
* Tests that we can use a subquery in a JOIN clause.
*/
function testJoinSubquerySelect() {
// Create a subquery, which is just a normal query object.
$subquery = db_select('test_task', 'tt');
$subquery->addField('tt', 'pid', 'pid');
$subquery->condition('priority', 1);
// Create another query that joins against the virtual table resulting
// from the subquery.
$select = db_select('test', 't');
$select->join($subquery, 'tt', 't.id=tt.pid');
$select->addField('t', 'name');
// The resulting query should be equivalent to:
// SELECT t.name
// FROM test t
// INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid
$people = $select->execute()->fetchCol();
$this->assertEqual(count($people), 2, 'Returned the correct number of rows.');
}
/**
* Tests EXISTS subquery conditionals on SELECT statements.
*
* We essentially select all rows from the {test} table that have matching
* rows in the {test_people} table based on the shared name column.
*/
function testExistsSubquerySelect() {
// Put George into {test_people}.
db_insert('test_people')
->fields(array(
'name' => 'George',
'age' => 27,
'job' => 'Singer',
))
->execute();
// Base query to {test}.
$query = db_select('test', 't')
->fields('t', array('name'));
// Subquery to {test_people}.
$subquery = db_select('test_people', 'tp')
->fields('tp', array('name'))
->where('tp.name = t.name');
$query->exists($subquery);
$result = $query->execute();
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.');
}
/**
* Tests NOT EXISTS subquery conditionals on SELECT statements.
*
* We essentially select all rows from the {test} table that don't have
* matching rows in the {test_people} table based on the shared name column.
*/
function testNotExistsSubquerySelect() {
// Put George into {test_people}.
db_insert('test_people')
->fields(array(
'name' => 'George',
'age' => 27,
'job' => 'Singer',
))
->execute();
// Base query to {test}.
$query = db_select('test', 't')
->fields('t', array('name'));
// Subquery to {test_people}.
$subquery = db_select('test_people', 'tp')
->fields('tp', array('name'))
->where('tp.name = t.name');
$query->notExists($subquery);
// Ensure that we got the right number of records.
$people = $query->execute()->fetchCol();
$this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.');
}
}

View file

@ -0,0 +1,530 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\InvalidQueryException;
use Drupal\Core\Database\Database;
/**
* Tests the Select query builder.
*
* @group Database
*/
class SelectTest extends DatabaseTestBase {
/**
* Tests rudimentary SELECT statements.
*/
function testSimpleSelect() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$num_records = $query->countQuery()->execute()->fetchField();
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
}
/**
* Tests rudimentary SELECT statement with a COMMENT.
*/
function testSimpleComment() {
$query = db_select('test')->comment('Testing query comments');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$result = $query->execute();
$records = $result->fetchAll();
$query = (string) $query;
$expected = "/* Testing query comments */";
$this->assertEqual(count($records), 4, 'Returned the correct number of rows.');
$this->assertNotIdentical(FALSE, strpos($query, $expected), 'The flattened query contains the comment string.');
}
/**
* Tests query COMMENT system against vulnerabilities.
*/
function testVulnerableComment() {
$query = db_select('test')->comment('Testing query comments */ SELECT nid FROM {node}; --');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$result = $query->execute();
$records = $result->fetchAll();
$query = (string) $query;
$expected = "/* Testing query comments * / SELECT nid FROM {node}. -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test";
$this->assertEqual(count($records), 4, 'Returned the correct number of rows.');
$this->assertNotIdentical(FALSE, strpos($query, $expected), 'The flattened query contains the sanitised comment string.');
$connection = Database::getConnection();
foreach ($this->makeCommentsProvider() as $test_set) {
list($expected, $comments) = $test_set;
$this->assertEqual($expected, $connection->makeComment($comments));
}
}
/**
* Provides expected and input values for testVulnerableComment().
*/
function makeCommentsProvider() {
return [
[
'/* */ ',
[''],
],
// Try and close the comment early.
[
'/* Exploit * / DROP TABLE node. -- */ ',
['Exploit */ DROP TABLE node; --'],
],
// Variations on comment closing.
[
'/* Exploit * / * / DROP TABLE node. -- */ ',
['Exploit */*/ DROP TABLE node; --'],
],
[
'/* Exploit * * // DROP TABLE node. -- */ ',
['Exploit **// DROP TABLE node; --'],
],
// Try closing the comment in the second string which is appended.
[
'/* Exploit * / DROP TABLE node. --. Another try * / DROP TABLE node. -- */ ',
['Exploit */ DROP TABLE node; --', 'Another try */ DROP TABLE node; --'],
],
];
}
/**
* Tests basic conditionals on SELECT statements.
*/
function testSimpleSelectConditional() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->condition('age', 27);
$result = $query->execute();
// Check that the aliases are being created the way we want.
$this->assertEqual($name_field, 'name', 'Name field alias is correct.');
$this->assertEqual($age_field, 'age', 'Age field alias is correct.');
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
$this->assertEqual($record->$age_field, 27, 'Fetched age is correct.');
}
/**
* Tests SELECT statements with expressions.
*/
function testSimpleSelectExpression() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addExpression("age*2", 'double_age');
$query->condition('age', 27);
$result = $query->execute();
// Check that the aliases are being created the way we want.
$this->assertEqual($name_field, 'name', 'Name field alias is correct.');
$this->assertEqual($age_field, 'double_age', 'Age field alias is correct.');
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
$this->assertEqual($record->$age_field, 27*2, 'Fetched age expression is correct.');
}
/**
* Tests SELECT statements with multiple expressions.
*/
function testSimpleSelectExpressionMultiple() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_double_field = $query->addExpression("age*2");
$age_triple_field = $query->addExpression("age*3");
$query->condition('age', 27);
$result = $query->execute();
// Check that the aliases are being created the way we want.
$this->assertEqual($age_double_field, 'expression', 'Double age field alias is correct.');
$this->assertEqual($age_triple_field, 'expression_2', 'Triple age field alias is correct.');
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
$this->assertEqual($record->$age_double_field, 27*2, 'Fetched double age expression is correct.');
$this->assertEqual($record->$age_triple_field, 27*3, 'Fetched triple age expression is correct.');
}
/**
* Tests adding multiple fields to a SELECT statement at the same time.
*/
function testSimpleSelectMultipleFields() {
$record = db_select('test')
->fields('test', array('id', 'name', 'age', 'job'))
->condition('age', 27)
->execute()->fetchObject();
// Check that all fields we asked for are present.
$this->assertNotNull($record->id, 'ID field is present.');
$this->assertNotNull($record->name, 'Name field is present.');
$this->assertNotNull($record->age, 'Age field is present.');
$this->assertNotNull($record->job, 'Job field is present.');
// Ensure that we got the right record.
// Check that all fields we asked for are present.
$this->assertEqual($record->id, 2, 'ID field has the correct value.');
$this->assertEqual($record->name, 'George', 'Name field has the correct value.');
$this->assertEqual($record->age, 27, 'Age field has the correct value.');
$this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
}
/**
* Tests adding all fields from a given table to a SELECT statement.
*/
function testSimpleSelectAllFields() {
$record = db_select('test')
->fields('test')
->condition('age', 27)
->execute()->fetchObject();
// Check that all fields we asked for are present.
$this->assertNotNull($record->id, 'ID field is present.');
$this->assertNotNull($record->name, 'Name field is present.');
$this->assertNotNull($record->age, 'Age field is present.');
$this->assertNotNull($record->job, 'Job field is present.');
// Ensure that we got the right record.
// Check that all fields we asked for are present.
$this->assertEqual($record->id, 2, 'ID field has the correct value.');
$this->assertEqual($record->name, 'George', 'Name field has the correct value.');
$this->assertEqual($record->age, 27, 'Age field has the correct value.');
$this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
}
/**
* Tests that a comparison with NULL is always FALSE.
*/
function testNullCondition() {
$this->ensureSampleDataNull();
$names = db_select('test_null', 'tn')
->fields('tn', array('name'))
->condition('age', NULL)
->execute()->fetchCol();
$this->assertEqual(count($names), 0, 'No records found when comparing to NULL.');
}
/**
* Tests that we can find a record with a NULL value.
*/
function testIsNullCondition() {
$this->ensureSampleDataNull();
$names = db_select('test_null', 'tn')
->fields('tn', array('name'))
->isNull('age')
->execute()->fetchCol();
$this->assertEqual(count($names), 1, 'Correct number of records found with NULL age.');
$this->assertEqual($names[0], 'Fozzie', 'Correct record returned for NULL age.');
}
/**
* Tests that we can find a record without a NULL value.
*/
function testIsNotNullCondition() {
$this->ensureSampleDataNull();
$names = db_select('test_null', 'tn')
->fields('tn', array('name'))
->isNotNull('tn.age')
->orderBy('name')
->execute()->fetchCol();
$this->assertEqual(count($names), 2, 'Correct number of records found withNOT NULL age.');
$this->assertEqual($names[0], 'Gonzo', 'Correct record returned for NOT NULL age.');
$this->assertEqual($names[1], 'Kermit', 'Correct record returned for NOT NULL age.');
}
/**
* Tests that we can UNION multiple Select queries together.
*
* This is semantically equal to UNION DISTINCT, so we don't explicitly test
* that.
*/
function testUnion() {
$query_1 = db_select('test', 't')
->fields('t', array('name'))
->condition('age', array(27, 28), 'IN');
$query_2 = db_select('test', 't')
->fields('t', array('name'))
->condition('age', 28);
$query_1->union($query_2);
$names = $query_1->execute()->fetchCol();
// Ensure we only get 2 records.
$this->assertEqual(count($names), 2, 'UNION correctly discarded duplicates.');
$this->assertEqual($names[0], 'George', 'First query returned correct name.');
$this->assertEqual($names[1], 'Ringo', 'Second query returned correct name.');
}
/**
* Tests that we can UNION ALL multiple SELECT queries together.
*/
function testUnionAll() {
$query_1 = db_select('test', 't')
->fields('t', array('name'))
->condition('age', array(27, 28), 'IN');
$query_2 = db_select('test', 't')
->fields('t', array('name'))
->condition('age', 28);
$query_1->union($query_2, 'ALL');
$names = $query_1->execute()->fetchCol();
// Ensure we get all 3 records.
$this->assertEqual(count($names), 3, 'UNION ALL correctly preserved duplicates.');
$this->assertEqual($names[0], 'George', 'First query returned correct first name.');
$this->assertEqual($names[1], 'Ringo', 'Second query returned correct second name.');
$this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.');
}
/**
* Tests that we can get a count query for a UNION Select query.
*/
function testUnionCount() {
$query_1 = db_select('test', 't')
->fields('t', array('name', 'age'))
->condition('age', array(27, 28), 'IN');
$query_2 = db_select('test', 't')
->fields('t', array('name', 'age'))
->condition('age', 28);
$query_1->union($query_2, 'ALL');
$names = $query_1->execute()->fetchCol();
$query_3 = $query_1->countQuery();
$count = $query_3->execute()->fetchField();
// Ensure the counts match.
$this->assertEqual(count($names), $count, "The count query's result matched the number of rows in the UNION query.");
}
/**
* Tests that random ordering of queries works.
*
* We take the approach of testing the Drupal layer only, rather than trying
* to test that the database's random number generator actually produces
* random queries (which is very difficult to do without an unacceptable risk
* of the test failing by accident).
*
* Therefore, in this test we simply run the same query twice and assert that
* the two results are reordered versions of each other (as well as of the
* same query without the random ordering). It is reasonable to assume that
* if we run the same select query twice and the results are in a different
* order each time, the only way this could happen is if we have successfully
* triggered the database's random ordering functionality.
*/
function testRandomOrder() {
// Use 52 items, so the chance that this test fails by accident will be the
// same as the chance that a deck of cards will come out in the same order
// after shuffling it (in other words, nearly impossible).
$number_of_items = 52;
while (db_query("SELECT MAX(id) FROM {test}")->fetchField() < $number_of_items) {
db_insert('test')->fields(array('name' => $this->randomMachineName()))->execute();
}
// First select the items in order and make sure we get an ordered list.
$expected_ids = range(1, $number_of_items);
$ordered_ids = db_select('test', 't')
->fields('t', array('id'))
->range(0, $number_of_items)
->orderBy('id')
->execute()
->fetchCol();
$this->assertEqual($ordered_ids, $expected_ids, 'A query without random ordering returns IDs in the correct order.');
// Now perform the same query, but instead choose a random ordering. We
// expect this to contain a differently ordered version of the original
// result.
$randomized_ids = db_select('test', 't')
->fields('t', array('id'))
->range(0, $number_of_items)
->orderRandom()
->execute()
->fetchCol();
$this->assertNotEqual($randomized_ids, $ordered_ids, 'A query with random ordering returns an unordered set of IDs.');
$sorted_ids = $randomized_ids;
sort($sorted_ids);
$this->assertEqual($sorted_ids, $ordered_ids, 'After sorting the random list, the result matches the original query.');
// Now perform the exact same query again, and make sure the order is
// different.
$randomized_ids_second_set = db_select('test', 't')
->fields('t', array('id'))
->range(0, $number_of_items)
->orderRandom()
->execute()
->fetchCol();
$this->assertNotEqual($randomized_ids_second_set, $randomized_ids, 'Performing the query with random ordering a second time returns IDs in a different order.');
$sorted_ids_second_set = $randomized_ids_second_set;
sort($sorted_ids_second_set);
$this->assertEqual($sorted_ids_second_set, $sorted_ids, 'After sorting the second random list, the result matches the sorted version of the first random list.');
}
/**
* Tests that filter by a regular expression works as expected.
*/
public function testRegexCondition() {
$test_groups[] = array(
'regex' => 'hn$',
'expected' => array(
'John',
),
);
$test_groups[] = array(
'regex' => '^Pau',
'expected' => array(
'Paul',
),
);
$test_groups[] = array(
'regex' => 'Ringo|George',
'expected' => array(
'Ringo', 'George',
),
);
$database = $this->container->get('database');
foreach ($test_groups as $test_group) {
$query = $database->select('test', 't');
$query->addField('t', 'name');
$query->condition('t.name', $test_group['regex'], 'REGEXP');
$result = $query->execute()->fetchCol();
$this->assertEqual(count($result), count($test_group['expected']), 'Returns the expected number of rows.');
$this->assertEqual(sort($result), sort($test_group['expected']), 'Returns the expected rows.');
}
// Ensure that filter by "#" still works due to the quoting.
$database->insert('test')
->fields(array(
'name' => 'Pete',
'age' => 26,
'job' => '#Drummer',
))
->execute();
$test_groups = array();
$test_groups[] = array(
'regex' => '#Drummer',
'expected' => array(
'Pete',
),
);
$test_groups[] = array(
'regex' => '#Singer',
'expected' => array(
),
);
foreach ($test_groups as $test_group) {
$query = $database->select('test', 't');
$query->addField('t', 'name');
$query->condition('t.job', $test_group['regex'], 'REGEXP');
$result = $query->execute()->fetchCol();
$this->assertEqual(count($result), count($test_group['expected']), 'Returns the expected number of rows.');
$this->assertEqual(sort($result), sort($test_group['expected']), 'Returns the expected rows.');
}
}
/**
* Tests that aliases are renamed when they are duplicates.
*/
function testSelectDuplicateAlias() {
$query = db_select('test', 't');
$alias1 = $query->addField('t', 'name', 'the_alias');
$alias2 = $query->addField('t', 'age', 'the_alias');
$this->assertNotIdentical($alias1, $alias2, 'Duplicate aliases are renamed.');
}
/**
* Tests that an invalid merge query throws an exception.
*/
function testInvalidSelectCount() {
try {
// This query will fail because the table does not exist.
// Normally it would throw an exception but we are suppressing
// it with the throw_exception option.
$options['throw_exception'] = FALSE;
db_select('some_table_that_doesnt_exist', 't', $options)
->fields('t')
->countQuery()
->execute();
$this->pass('$options[\'throw_exception\'] is FALSE, no Exception thrown.');
}
catch (\Exception $e) {
$this->fail('$options[\'throw_exception\'] is FALSE, but Exception thrown for invalid query.');
return;
}
try {
// This query will fail because the table does not exist.
db_select('some_table_that_doesnt_exist', 't')
->fields('t')
->countQuery()
->execute();
}
catch (\Exception $e) {
$this->pass('Exception thrown for invalid query.');
return;
}
$this->fail('No Exception thrown.');
}
/**
* Tests thrown exception for IN query conditions with an empty array.
*/
function testEmptyInCondition() {
try {
db_select('test', 't')
->fields('t')
->condition('age', array(), 'IN')
->execute();
$this->fail('Expected exception not thrown');
}
catch (InvalidQueryException $e) {
$this->assertEqual("Query condition 'age IN ()' cannot be empty.", $e->getMessage());
}
try {
db_select('test', 't')
->fields('t')
->condition('age', array(), 'NOT IN')
->execute();
$this->fail('Expected exception not thrown');
}
catch (InvalidQueryException $e) {
$this->assertEqual("Query condition 'age NOT IN ()' cannot be empty.", $e->getMessage());
}
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests serializing and unserializing a query.
*
* @group Database
*/
class SerializeQueryTest extends DatabaseTestBase {
/**
* Confirms that a query can be serialized and unserialized.
*/
function testSerializeQuery() {
$query = db_select('test');
$query->addField('test', 'age');
$query->condition('name', 'Ringo');
// If this doesn't work, it will throw an exception, so no need for an
// assertion.
$query = unserialize(serialize($query));
$results = $query->execute()->fetchCol();
$this->assertEqual($results[0], 28, 'Query properly executed after unserialization.');
}
}

View file

@ -0,0 +1,127 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the tagging capabilities of the Select builder.
*
* Tags are a way to flag queries for alter hooks so they know
* what type of query it is, such as "node_access".
*
* @group Database
*/
class TaggingTest extends DatabaseTestBase {
/**
* Confirms that a query has a tag added to it.
*/
function testHasTag() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
$this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
}
/**
* Tests query tagging "has all of these tags" functionality.
*/
function testHasAllTags() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$query->addTag('other');
$this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
$this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
}
/**
* Tests query tagging "has at least one of these tags" functionality.
*/
function testHasAnyTag() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
$this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
}
/**
* Confirms that an extended query has a tag added to it.
*/
function testExtenderHasTag() {
$query = db_select('test')
->extend('Drupal\Core\Database\Query\SelectExtender');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
$this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
}
/**
* Tests extended query tagging "has all of these tags" functionality.
*/
function testExtenderHasAllTags() {
$query = db_select('test')
->extend('Drupal\Core\Database\Query\SelectExtender');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$query->addTag('other');
$this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
$this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
}
/**
* Tests extended query tagging "has at least one of these tags" functionality.
*/
function testExtenderHasAnyTag() {
$query = db_select('test')
->extend('Drupal\Core\Database\Query\SelectExtender');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
$this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
}
/**
* Tests that we can attach metadata to a query object.
*
* This is how we pass additional context to alter hooks.
*/
function testMetaData() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$data = array(
'a' => 'A',
'b' => 'B',
);
$query->addMetaData('test', $data);
$return = $query->getMetaData('test');
$this->assertEqual($data, $return, 'Correct metadata returned.');
$return = $query->getMetaData('nothere');
$this->assertNull($return, 'Non-existent key returned NULL.');
}
}

View file

@ -0,0 +1,610 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\TransactionOutOfOrderException;
use Drupal\Core\Database\TransactionNoActiveException;
/**
* Tests the transaction abstraction system.
*
* We test nesting by having two transaction layers, an outer and inner. The
* outer layer encapsulates the inner layer. Our transaction nesting abstraction
* should allow the outer layer function to call any function it wants,
* especially the inner layer that starts its own transaction, and be
* confident that, when the function it calls returns, its own transaction
* is still "alive."
*
* Call structure:
* transactionOuterLayer()
* Start transaction
* transactionInnerLayer()
* Start transaction (does nothing in database)
* [Maybe decide to roll back]
* Do more stuff
* Should still be in transaction A
*
* @group Database
*/
class TransactionTest extends DatabaseTestBase {
/**
* Encapsulates a transaction's "inner layer" with an "outer layer".
*
* This "outer layer" transaction starts and then encapsulates the "inner
* layer" transaction. This nesting is used to evaluate whether the database
* transaction API properly supports nesting. By "properly supports," we mean
* the outer transaction continues to exist regardless of what functions are
* called and whether those functions start their own transactions.
*
* In contrast, a typical database would commit the outer transaction, start
* a new transaction for the inner layer, commit the inner layer transaction,
* and then be confused when the outer layer transaction tries to commit its
* transaction (which was already committed when the inner transaction
* started).
*
* @param $suffix
* Suffix to add to field values to differentiate tests.
* @param $rollback
* Whether or not to try rolling back the transaction when we're done.
* @param $ddl_statement
* Whether to execute a DDL statement during the inner transaction.
*/
protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
$connection = Database::getConnection();
$depth = $connection->transactionDepth();
$txn = db_transaction();
// Insert a single row into the testing table.
db_insert('test')
->fields(array(
'name' => 'David' . $suffix,
'age' => '24',
))
->execute();
$this->assertTrue($connection->inTransaction(), 'In transaction before calling nested transaction.');
// We're already in a transaction, but we call ->transactionInnerLayer
// to nest another transaction inside the current one.
$this->transactionInnerLayer($suffix, $rollback, $ddl_statement);
$this->assertTrue($connection->inTransaction(), 'In transaction after calling nested transaction.');
if ($rollback) {
// Roll back the transaction, if requested.
// This rollback should propagate to the last savepoint.
$txn->rollback();
$this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
}
}
/**
* Creates an "inner layer" transaction.
*
* This "inner layer" transaction is either used alone or nested inside of the
* "outer layer" transaction.
*
* @param $suffix
* Suffix to add to field values to differentiate tests.
* @param $rollback
* Whether or not to try rolling back the transaction when we're done.
* @param $ddl_statement
* Whether to execute a DDL statement during the transaction.
*/
protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
$connection = Database::getConnection();
$depth = $connection->transactionDepth();
// Start a transaction. If we're being called from ->transactionOuterLayer,
// then we're already in a transaction. Normally, that would make starting
// a transaction here dangerous, but the database API handles this problem
// for us by tracking the nesting and avoiding the danger.
$txn = db_transaction();
$depth2 = $connection->transactionDepth();
$this->assertTrue($depth < $depth2, 'Transaction depth is has increased with new transaction.');
// Insert a single row into the testing table.
db_insert('test')
->fields(array(
'name' => 'Daniel' . $suffix,
'age' => '19',
))
->execute();
$this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
if ($ddl_statement) {
$table = array(
'fields' => array(
'id' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
),
'primary key' => array('id'),
);
db_create_table('database_test_1', $table);
$this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
}
if ($rollback) {
// Roll back the transaction, if requested.
// This rollback should propagate to the last savepoint.
$txn->rollback();
$this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
}
}
/**
* Tests transaction rollback on a database that supports transactions.
*
* If the active connection does not support transactions, this test does
* nothing.
*/
function testTransactionRollBackSupported() {
// This test won't work right if transactions are not supported.
if (!Database::getConnection()->supportsTransactions()) {
return;
}
try {
// Create two nested transactions. Roll back from the inner one.
$this->transactionOuterLayer('B', TRUE);
// Neither of the rows we inserted in the two transaction layers
// should be present in the tables post-rollback.
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
$this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
$this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.');
}
catch (\Exception $e) {
$this->fail($e->getMessage());
}
}
/**
* Tests transaction rollback on a database that doesn't support transactions.
*
* If the active driver supports transactions, this test does nothing.
*/
function testTransactionRollBackNotSupported() {
// This test won't work right if transactions are supported.
if (Database::getConnection()->supportsTransactions()) {
return;
}
try {
// Create two nested transactions. Attempt to roll back from the inner one.
$this->transactionOuterLayer('B', TRUE);
// Because our current database claims to not support transactions,
// the inserted rows should be present despite the attempt to roll back.
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
$this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
$this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.');
}
catch (\Exception $e) {
$this->fail($e->getMessage());
}
}
/**
* Tests a committed transaction.
*
* The behavior of this test should be identical for connections that support
* transactions and those that do not.
*/
function testCommittedTransaction() {
try {
// Create two nested transactions. The changes should be committed.
$this->transactionOuterLayer('A');
// Because we committed, both of the inserted rows should be present.
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField();
$this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.');
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField();
$this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.');
}
catch (\Exception $e) {
$this->fail($e->getMessage());
}
}
/**
* Tests the compatibility of transactions with DDL statements.
*/
function testTransactionWithDdlStatement() {
// First, test that a commit works normally, even with DDL statements.
$transaction = db_transaction();
$this->insertRow('row');
$this->executeDDLStatement();
unset($transaction);
$this->assertRowPresent('row');
// Even in different order.
$this->cleanUp();
$transaction = db_transaction();
$this->executeDDLStatement();
$this->insertRow('row');
unset($transaction);
$this->assertRowPresent('row');
// Even with stacking.
$this->cleanUp();
$transaction = db_transaction();
$transaction2 = db_transaction();
$this->executeDDLStatement();
unset($transaction2);
$transaction3 = db_transaction();
$this->insertRow('row');
unset($transaction3);
unset($transaction);
$this->assertRowPresent('row');
// A transaction after a DDL statement should still work the same.
$this->cleanUp();
$transaction = db_transaction();
$transaction2 = db_transaction();
$this->executeDDLStatement();
unset($transaction2);
$transaction3 = db_transaction();
$this->insertRow('row');
$transaction3->rollback();
unset($transaction3);
unset($transaction);
$this->assertRowAbsent('row');
// The behavior of a rollback depends on the type of database server.
if (Database::getConnection()->supportsTransactionalDDL()) {
// For database servers that support transactional DDL, a rollback
// of a transaction including DDL statements should be possible.
$this->cleanUp();
$transaction = db_transaction();
$this->insertRow('row');
$this->executeDDLStatement();
$transaction->rollback();
unset($transaction);
$this->assertRowAbsent('row');
// Including with stacking.
$this->cleanUp();
$transaction = db_transaction();
$transaction2 = db_transaction();
$this->executeDDLStatement();
unset($transaction2);
$transaction3 = db_transaction();
$this->insertRow('row');
unset($transaction3);
$transaction->rollback();
unset($transaction);
$this->assertRowAbsent('row');
}
else {
// For database servers that do not support transactional DDL,
// the DDL statement should commit the transaction stack.
$this->cleanUp();
$transaction = db_transaction();
$this->insertRow('row');
$this->executeDDLStatement();
// Rollback the outer transaction.
try {
$transaction->rollback();
unset($transaction);
// @TODO: an exception should be triggered here, but is not, because
// "ROLLBACK" fails silently in MySQL if there is no transaction active.
// $this->fail(t('Rolling back a transaction containing DDL should fail.'));
}
catch (TransactionNoActiveException $e) {
$this->pass('Rolling back a transaction containing DDL should fail.');
}
$this->assertRowPresent('row');
}
}
/**
* Inserts a single row into the testing table.
*/
protected function insertRow($name) {
db_insert('test')
->fields(array(
'name' => $name,
))
->execute();
}
/**
* Executes a DDL statement.
*/
protected function executeDDLStatement() {
static $count = 0;
$table = array(
'fields' => array(
'id' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
),
'primary key' => array('id'),
);
db_create_table('database_test_' . ++$count, $table);
}
/**
* Starts over for a new test.
*/
protected function cleanUp() {
db_truncate('test')
->execute();
}
/**
* Asserts that a given row is present in the test table.
*
* @param $name
* The name of the row.
* @param $message
* The message to log for the assertion.
*/
function assertRowPresent($name, $message = NULL) {
if (!isset($message)) {
$message = format_string('Row %name is present.', array('%name' => $name));
}
$present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
return $this->assertTrue($present, $message);
}
/**
* Asserts that a given row is absent from the test table.
*
* @param $name
* The name of the row.
* @param $message
* The message to log for the assertion.
*/
function assertRowAbsent($name, $message = NULL) {
if (!isset($message)) {
$message = format_string('Row %name is absent.', array('%name' => $name));
}
$present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
return $this->assertFalse($present, $message);
}
/**
* Tests transaction stacking, commit, and rollback.
*/
function testTransactionStacking() {
// This test won't work right if transactions are not supported.
if (!Database::getConnection()->supportsTransactions()) {
return;
}
$database = Database::getConnection();
// Standard case: pop the inner transaction before the outer transaction.
$transaction = db_transaction();
$this->insertRow('outer');
$transaction2 = db_transaction();
$this->insertRow('inner');
// Pop the inner transaction.
unset($transaction2);
$this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction');
// Pop the outer transaction.
unset($transaction);
$this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction');
$this->assertRowPresent('outer');
$this->assertRowPresent('inner');
// Pop the transaction in a different order they have been pushed.
$this->cleanUp();
$transaction = db_transaction();
$this->insertRow('outer');
$transaction2 = db_transaction();
$this->insertRow('inner');
// Pop the outer transaction, nothing should happen.
unset($transaction);
$this->insertRow('inner-after-outer-commit');
$this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
// Pop the inner transaction, the whole transaction should commit.
unset($transaction2);
$this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
$this->assertRowPresent('outer');
$this->assertRowPresent('inner');
$this->assertRowPresent('inner-after-outer-commit');
// Rollback the inner transaction.
$this->cleanUp();
$transaction = db_transaction();
$this->insertRow('outer');
$transaction2 = db_transaction();
$this->insertRow('inner');
// Now rollback the inner transaction.
$transaction2->rollback();
unset($transaction2);
$this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
// Pop the outer transaction, it should commit.
$this->insertRow('outer-after-inner-rollback');
unset($transaction);
$this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
$this->assertRowPresent('outer');
$this->assertRowAbsent('inner');
$this->assertRowPresent('outer-after-inner-rollback');
// Rollback the inner transaction after committing the outer one.
$this->cleanUp();
$transaction = db_transaction();
$this->insertRow('outer');
$transaction2 = db_transaction();
$this->insertRow('inner');
// Pop the outer transaction, nothing should happen.
unset($transaction);
$this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
// Now rollback the inner transaction, it should rollback.
$transaction2->rollback();
unset($transaction2);
$this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
$this->assertRowPresent('outer');
$this->assertRowAbsent('inner');
// Rollback the outer transaction while the inner transaction is active.
// In that case, an exception will be triggered because we cannot
// ensure that the final result will have any meaning.
$this->cleanUp();
$transaction = db_transaction();
$this->insertRow('outer');
$transaction2 = db_transaction();
$this->insertRow('inner');
$transaction3 = db_transaction();
$this->insertRow('inner2');
// Rollback the outer transaction.
try {
$transaction->rollback();
unset($transaction);
$this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
}
catch (TransactionOutOfOrderException $e) {
$this->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
}
$this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction');
// Try to commit one inner transaction.
unset($transaction3);
$this->pass('Trying to commit an inner transaction resulted in an exception.');
// Try to rollback one inner transaction.
try {
$transaction->rollback();
unset($transaction2);
$this->fail('Trying to commit an inner transaction resulted in an exception.');
}
catch (TransactionNoActiveException $e) {
$this->pass('Trying to commit an inner transaction resulted in an exception.');
}
$this->assertRowAbsent('outer');
$this->assertRowAbsent('inner');
$this->assertRowAbsent('inner2');
}
/**
* Tests that transactions can continue to be used if a query fails.
*/
public function testQueryFailureInTransaction() {
$connection = Database::getConnection();
$transaction = $connection->startTransaction('test_transaction');
$connection->schema()->dropTable('test');
// Test a failed query using the query() method.
try {
$connection->query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'David'))->fetchField();
$this->fail('Using the query method failed.');
}
catch (\Exception $e) {
$this->pass('Using the query method failed.');
}
// Test a failed select query.
try {
$connection->select('test')
->fields('test', ['name'])
->execute();
$this->fail('Select query failed.');
}
catch (\Exception $e) {
$this->pass('Select query failed.');
}
// Test a failed insert query.
try {
$connection->insert('test')
->fields([
'name' => 'David',
'age' => '24',
])
->execute();
$this->fail('Insert query failed.');
}
catch (\Exception $e) {
$this->pass('Insert query failed.');
}
// Test a failed update query.
try {
$connection->update('test')
->fields(['name' => 'Tiffany'])
->condition('id', 1)
->execute();
$this->fail('Update query failed.');
}
catch (\Exception $e) {
$this->pass('Update query failed.');
}
// Test a failed delete query.
try {
$connection->delete('test')
->condition('id', 1)
->execute();
$this->fail('Delete query failed.');
}
catch (\Exception $e) {
$this->pass('Delete query failed.');
}
// Test a failed merge query.
try {
$connection->merge('test')
->key('job', 'Presenter')
->fields([
'age' => '31',
'name' => 'Tiffany',
])
->execute();
$this->fail('Merge query failed.');
}
catch (\Exception $e) {
$this->pass('Merge query failed.');
}
// Test a failed upsert query.
try {
$connection->upsert('test')
->key('job')
->fields(['job', 'age', 'name'])
->values([
'job' => 'Presenter',
'age' => 31,
'name' => 'Tiffany',
])
->execute();
$this->fail('Upset query failed.');
}
catch (\Exception $e) {
$this->pass('Upset query failed.');
}
// Create the missing schema and insert a row.
$this->installSchema('database_test', ['test']);
$connection->insert('test')
->fields(array(
'name' => 'David',
'age' => '24',
))
->execute();
// Commit the transaction.
unset($transaction);
$saved_age = $connection->query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'David'))->fetchField();
$this->assertEqual('24', $saved_age);
}
}

View file

@ -0,0 +1,145 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the Update query builder, complex queries.
*
* @group Database
*/
class UpdateComplexTest extends DatabaseTestBase {
/**
* Tests updates with OR conditionals.
*/
function testOrConditionUpdate() {
$update = db_update('test')
->fields(array('job' => 'Musician'))
->condition(db_or()
->condition('name', 'John')
->condition('name', 'Paul')
);
$num_updated = $update->execute();
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
}
/**
* Tests WHERE IN clauses.
*/
function testInConditionUpdate() {
$num_updated = db_update('test')
->fields(array('job' => 'Musician'))
->condition('name', array('John', 'Paul'), 'IN')
->execute();
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
}
/**
* Tests WHERE NOT IN clauses.
*/
function testNotInConditionUpdate() {
// The o is lowercase in the 'NoT IN' operator, to make sure the operators
// work in mixed case.
$num_updated = db_update('test')
->fields(array('job' => 'Musician'))
->condition('name', array('John', 'Paul', 'George'), 'NoT IN')
->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
}
/**
* Tests BETWEEN conditional clauses.
*/
function testBetweenConditionUpdate() {
$num_updated = db_update('test')
->fields(array('job' => 'Musician'))
->condition('age', array(25, 26), 'BETWEEN')
->execute();
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
}
/**
* Tests LIKE conditionals.
*/
function testLikeConditionUpdate() {
$num_updated = db_update('test')
->fields(array('job' => 'Musician'))
->condition('name', '%ge%', 'LIKE')
->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
}
/**
* Tests UPDATE with expression values.
*/
function testUpdateExpression() {
$before_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
$GLOBALS['larry_test'] = 1;
$num_updated = db_update('test')
->condition('name', 'Ringo')
->fields(array('job' => 'Musician'))
->expression('age', 'age + :age', array(':age' => 4))
->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
$person = db_query('SELECT * FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetch();
$this->assertEqual($person->name, 'Ringo', 'Name set correctly.');
$this->assertEqual($person->age, $before_age + 4, 'Age set correctly.');
$this->assertEqual($person->job, 'Musician', 'Job set correctly.');
$GLOBALS['larry_test'] = 0;
}
/**
* Tests UPDATE with only expression values.
*/
function testUpdateOnlyExpression() {
$before_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
$num_updated = db_update('test')
->condition('name', 'Ringo')
->expression('age', 'age + :age', array(':age' => 4))
->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
$after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
$this->assertEqual($before_age + 4, $after_age, 'Age updated correctly');
}
/**
* Test UPDATE with a subselect value.
*/
function testSubSelectUpdate() {
$subselect = db_select('test_task', 't');
$subselect->addExpression('MAX(priority) + :increment', 'max_priority', array(':increment' => 30));
// Clone this to make sure we are running a different query when
// asserting.
$select = clone $subselect;
$query = db_update('test')
->expression('age', $subselect)
->condition('name', 'Ringo');
// Save the number of rows that updated for assertion later.
$num_updated = $query->execute();
$after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
$expected_age = $select->execute()->fetchField();
$this->assertEqual($after_age, $expected_age);
$this->assertEqual(1, $num_updated, t('Expected 1 row to be updated in subselect update query.'));
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the Update query builder with LOB fields.
*
* @group Database
*/
class UpdateLobTest extends DatabaseTestBase {
/**
* Confirms that we can update a blob column.
*/
function testUpdateOneBlob() {
$data = "This is\000a test.";
$this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.');
$id = db_insert('test_one_blob')
->fields(array('blob1' => $data))
->execute();
$data .= $data;
db_update('test_one_blob')
->condition('id', $id)
->fields(array('blob1' => $data))
->execute();
$r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc();
$this->assertTrue($r['blob1'] === $data, format_string('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r))));
}
/**
* Confirms that we can update two blob columns in the same table.
*/
function testUpdateMultipleBlob() {
$id = db_insert('test_two_blobs')
->fields(array(
'blob1' => 'This is',
'blob2' => 'a test',
))
->execute();
db_update('test_two_blobs')
->condition('id', $id)
->fields(array('blob1' => 'and so', 'blob2' => 'is this'))
->execute();
$r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc();
$this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', 'Can update multiple blobs per row.');
}
}

View file

@ -0,0 +1,157 @@
<?php
namespace Drupal\KernelTests\Core\Database;
/**
* Tests the update query builder.
*
* @group Database
*/
class UpdateTest extends DatabaseTestBase {
/**
* Confirms that we can update a single record successfully.
*/
function testSimpleUpdate() {
$num_updated = db_update('test')
->fields(array('name' => 'Tiffany'))
->condition('id', 1)
->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
$saved_name = db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 1))->fetchField();
$this->assertIdentical($saved_name, 'Tiffany', 'Updated name successfully.');
}
/**
* Confirms updating to NULL.
*/
function testSimpleNullUpdate() {
$this->ensureSampleDataNull();
$num_updated = db_update('test_null')
->fields(array('age' => NULL))
->condition('name', 'Kermit')
->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
$saved_age = db_query('SELECT age FROM {test_null} WHERE name = :name', array(':name' => 'Kermit'))->fetchField();
$this->assertNull($saved_age, 'Updated name successfully.');
}
/**
* Confirms that we can update multiple records successfully.
*/
function testMultiUpdate() {
$num_updated = db_update('test')
->fields(array('job' => 'Musician'))
->condition('job', 'Singer')
->execute();
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
}
/**
* Confirms that we can update multiple records with a non-equality condition.
*/
function testMultiGTUpdate() {
$num_updated = db_update('test')
->fields(array('job' => 'Musician'))
->condition('age', 26, '>')
->execute();
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
}
/**
* Confirms that we can update multiple records with a where call.
*/
function testWhereUpdate() {
$num_updated = db_update('test')
->fields(array('job' => 'Musician'))
->where('age > :age', array(':age' => 26))
->execute();
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
}
/**
* Confirms that we can stack condition and where calls.
*/
function testWhereAndConditionUpdate() {
$update = db_update('test')
->fields(array('job' => 'Musician'))
->where('age > :age', array(':age' => 26))
->condition('name', 'Ringo');
$num_updated = $update->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
}
/**
* Tests updating with expressions.
*/
function testExpressionUpdate() {
// Ensure that expressions are handled properly. This should set every
// record's age to a square of itself.
$num_rows = db_update('test')
->expression('age', 'age * age')
->execute();
$this->assertIdentical($num_rows, 4, 'Updated 4 records.');
$saved_name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => pow(26, 2)))->fetchField();
$this->assertIdentical($saved_name, 'Paul', 'Successfully updated values using an algebraic expression.');
}
/**
* Tests return value on update.
*/
function testUpdateAffectedRows() {
// At 5am in the morning, all band members but those with a priority 1 task
// are sleeping. So we set their tasks to 'sleep'. 5 records match the
// condition and therefore are affected by the query, even though two of
// them actually don't have to be changed because their value was already
// 'sleep'. Still, execute() should return 5 affected rows, not only 3,
// because that's cross-db expected behavior.
$num_rows = db_update('test_task')
->condition('priority', 1, '<>')
->fields(array('task' => 'sleep'))
->execute();
$this->assertIdentical($num_rows, 5, 'Correctly returned 5 affected rows.');
}
/**
* Confirm that we can update the primary key of a record successfully.
*/
function testPrimaryKeyUpdate() {
$num_updated = db_update('test')
->fields(array('id' => 42, 'name' => 'John'))
->condition('id', 1)
->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
$saved_name= db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 42))->fetchField();
$this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.');
}
/**
* Confirm that we can update values in a column with special name.
*/
function testSpecialColumnUpdate() {
$num_updated = db_update('test_special_columns')
->fields(array('offset' => 'New offset value'))
->condition('id', 1)
->execute();
$this->assertIdentical($num_updated, 1, 'Updated 1 special column record.');
$saved_value = db_query('SELECT "offset" FROM {test_special_columns} WHERE id = :id', array(':id' => 1))->fetchField();
$this->assertIdentical($saved_value, 'New offset value', 'Updated special column name value successfully.');
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
/**
* Tests the Upsert query builder.
*
* @group Database
*/
class UpsertTest extends DatabaseTestBase {
/**
* Confirms that we can upsert (update-or-insert) records successfully.
*/
public function testUpsert() {
$connection = Database::getConnection();
$num_records_before = $connection->query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$upsert = $connection->upsert('test_people')
->key('job')
->fields(['job', 'age', 'name']);
// Add a new row.
$upsert->values([
'job' => 'Presenter',
'age' => 31,
'name' => 'Tiffany',
]);
// Update an existing row.
$upsert->values([
'job' => 'Speaker',
// The initial age was 30.
'age' => 32,
'name' => 'Meredith',
]);
$upsert->execute();
$num_records_after = $connection->query('SELECT COUNT(*) FROM {test_people}')->fetchField();
$this->assertEqual($num_records_before + 1, $num_records_after, 'Rows were inserted and updated properly.');
$person = $connection->query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
$this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
$this->assertEqual($person->age, 31, 'Age set correctly.');
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
$person = $connection->query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->job, 'Speaker', 'Job was not changed.');
$this->assertEqual($person->age, 32, 'Age updated correctly.');
$this->assertEqual($person->name, 'Meredith', 'Name was not changed.');
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests validation constraints for BundleConstraintValidator.
*
* @group Entity
*/
class BundleConstraintValidatorTest extends KernelTestBase {
/**
* The typed data manager to use.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedData;
public static $modules = array('node', 'field', 'text', 'user');
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->typedData = $this->container->get('typed_data_manager');
}
/**
* Tests bundle constraint validation.
*/
public function testValidation() {
// Test with multiple values.
$this->assertValidation(array('foo', 'bar'));
// Test with a single string value as well.
$this->assertValidation('foo');
}
/**
* Executes the BundleConstraintValidator test for a given bundle.
*
* @param string|array $bundle
* Bundle/bundles to use as constraint option.
*/
protected function assertValidation($bundle) {
// Create a typed data definition with a Bundle constraint.
$definition = DataDefinition::create('entity_reference')
->addConstraint('Bundle', $bundle);
// Test the validation.
$node = $this->container->get('entity.manager')->getStorage('node')->create(array('type' => 'foo'));
$typed_data = $this->typedData->create($definition, $node);
$violations = $typed_data->validate();
$this->assertEqual($violations->count(), 0, 'Validation passed for correct value.');
// Test the validation when an invalid value is passed.
$page_node = $this->container->get('entity.manager')->getStorage('node')->create(array('type' => 'baz'));
$typed_data = $this->typedData->create($definition, $page_node);
$violations = $typed_data->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed for incorrect value.');
// Make sure the information provided by a violation is correct.
$violation = $violations[0];
$this->assertEqual($violation->getMessage(), t('The entity must be of bundle %bundle.', array('%bundle' => implode(', ', (array) $bundle))), 'The message for invalid value is correct.');
$this->assertEqual($violation->getRoot(), $typed_data, 'Violation root is correct.');
$this->assertEqual($violation->getInvalidValue(), $page_node, 'The invalid value is set correctly in the violation.');
}
}

View file

@ -0,0 +1,665 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Config\Entity\Query\QueryFactory;
use Drupal\config_test\Entity\ConfigQueryTest;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests Config Entity Query functionality.
*
* @group Entity
* @see \Drupal\Core\Config\Entity\Query
*/
class ConfigEntityQueryTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
static $modules = array('config_test');
/**
* Stores the search results for alter comparison.
*
* @var array
*/
protected $queryResults;
/**
* The query factory used to construct all queries in the test.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $factory;
/**
* Stores all config entities created for the test.
*
* @var array
*/
protected $entities;
protected function setUp() {
parent::setUp();
$this->entities = array();
$this->factory = $this->container->get('entity.query');
// These two are here to make sure that matchArray needs to go over several
// non-matches on every levels.
$array['level1']['level2a'] = 9;
$array['level1a']['level2'] = 9;
// The tests match array.level1.level2.
$array['level1']['level2'] = 1;
$entity = ConfigQueryTest::create(array(
'label' => $this->randomMachineName(),
'id' => '1',
'number' => 31,
'array' => $array,
));
$this->entities[] = $entity;
$entity->enforceIsNew();
$entity->save();
$array['level1']['level2'] = 2;
$entity = ConfigQueryTest::create(array(
'label' => $this->randomMachineName(),
'id' => '2',
'number' => 41,
'array' => $array,
));
$this->entities[] = $entity;
$entity->enforceIsNew();
$entity->save();
$array['level1']['level2'] = 1;
$entity = ConfigQueryTest::create(array(
'label' => 'test_prefix_' . $this->randomMachineName(),
'id' => '3',
'number' => 59,
'array' => $array,
));
$this->entities[] = $entity;
$entity->enforceIsNew();
$entity->save();
$array['level1']['level2'] = 2;
$entity = ConfigQueryTest::create(array(
'label' => $this->randomMachineName() . '_test_suffix',
'id' => '4',
'number' => 26,
'array' => $array,
));
$this->entities[] = $entity;
$entity->enforceIsNew();
$entity->save();
$array['level1']['level2'] = 3;
$entity = ConfigQueryTest::create(array(
'label' => $this->randomMachineName() . '_TEST_contains_' . $this->randomMachineName(),
'id' => '5',
'number' => 53,
'array' => $array,
));
$this->entities[] = $entity;
$entity->enforceIsNew();
$entity->save();
}
/**
* Tests basic functionality.
*/
public function testConfigEntityQuery() {
// Run a test without any condition.
$this->queryResults = $this->factory->get('config_query_test')
->execute();
$this->assertResults(array('1', '2', '3', '4', '5'));
// No conditions, OR.
$this->queryResults = $this->factory->get('config_query_test', 'OR')
->execute();
$this->assertResults(array('1', '2', '3', '4', '5'));
// Filter by ID with equality.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '3')
->execute();
$this->assertResults(array('3'));
// Filter by label with a known prefix.
$this->queryResults = $this->factory->get('config_query_test')
->condition('label', 'test_prefix', 'STARTS_WITH')
->execute();
$this->assertResults(array('3'));
// Filter by label with a known suffix.
$this->queryResults = $this->factory->get('config_query_test')
->condition('label', 'test_suffix', 'ENDS_WITH')
->execute();
$this->assertResults(array('4'));
// Filter by label with a known containing word.
$this->queryResults = $this->factory->get('config_query_test')
->condition('label', 'test_contains', 'CONTAINS')
->execute();
$this->assertResults(array('5'));
// Filter by ID with the IN operator.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', array('2', '3'), 'IN')
->execute();
$this->assertResults(array('2', '3'));
// Filter by ID with the implicit IN operator.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', array('2', '3'))
->execute();
$this->assertResults(array('2', '3'));
// Filter by ID with the > operator.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '3', '>')
->execute();
$this->assertResults(array('4', '5'));
// Filter by ID with the >= operator.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '3', '>=')
->execute();
$this->assertResults(array('3', '4', '5'));
// Filter by ID with the <> operator.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '3', '<>')
->execute();
$this->assertResults(array('1', '2', '4', '5'));
// Filter by ID with the < operator.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '3', '<')
->execute();
$this->assertResults(array('1', '2'));
// Filter by ID with the <= operator.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '3', '<=')
->execute();
$this->assertResults(array('1', '2', '3'));
// Filter by two conditions on the same field.
$this->queryResults = $this->factory->get('config_query_test')
->condition('label', 'test_pref', 'STARTS_WITH')
->condition('label', 'test_prefix', 'STARTS_WITH')
->execute();
$this->assertResults(array('3'));
// Filter by two conditions on different fields. The first query matches for
// a different ID, so the result is empty.
$this->queryResults = $this->factory->get('config_query_test')
->condition('label', 'test_prefix', 'STARTS_WITH')
->condition('id', '5')
->execute();
$this->assertResults(array());
// Filter by two different conditions on different fields. This time the
// first condition matches on one item, but the second one does as well.
$this->queryResults = $this->factory->get('config_query_test')
->condition('label', 'test_prefix', 'STARTS_WITH')
->condition('id', '3')
->execute();
$this->assertResults(array('3'));
// Filter by two different conditions, of which the first one matches for
// every entry, the second one as well, but just the third one filters so
// that just two are left.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '1', '>=')
->condition('number', 10, '>=')
->condition('number', 50, '>=')
->execute();
$this->assertResults(array('3', '5'));
// Filter with an OR condition group.
$this->queryResults = $this->factory->get('config_query_test', 'OR')
->condition('id', 1)
->condition('id', '2')
->execute();
$this->assertResults(array('1', '2'));
// Simplify it with IN.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', array('1', '2'))
->execute();
$this->assertResults(array('1', '2'));
// Try explicit IN.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', array('1', '2'), 'IN')
->execute();
$this->assertResults(array('1', '2'));
// Try not IN.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', array('1', '2'), 'NOT IN')
->execute();
$this->assertResults(array('3', '4', '5'));
// Filter with an OR condition group on different fields.
$this->queryResults = $this->factory->get('config_query_test', 'OR')
->condition('id', 1)
->condition('number', 41)
->execute();
$this->assertResults(array('1', '2'));
// Filter with an OR condition group on different fields but matching on the
// same entity.
$this->queryResults = $this->factory->get('config_query_test', 'OR')
->condition('id', 1)
->condition('number', 31)
->execute();
$this->assertResults(array('1'));
// NO simple conditions, YES complex conditions, 'AND'.
$query = $this->factory->get('config_query_test', 'AND');
$and_condition_1 = $query->orConditionGroup()
->condition('id', '2')
->condition('label', $this->entities[0]->label);
$and_condition_2 = $query->orConditionGroup()
->condition('id', 1)
->condition('label', $this->entities[3]->label);
$this->queryResults = $query
->condition($and_condition_1)
->condition($and_condition_2)
->execute();
$this->assertResults(array('1'));
// NO simple conditions, YES complex conditions, 'OR'.
$query = $this->factory->get('config_query_test', 'OR');
$and_condition_1 = $query->andConditionGroup()
->condition('id', 1)
->condition('label', $this->entities[0]->label);
$and_condition_2 = $query->andConditionGroup()
->condition('id', '2')
->condition('label', $this->entities[1]->label);
$this->queryResults = $query
->condition($and_condition_1)
->condition($and_condition_2)
->execute();
$this->assertResults(array('1', '2'));
// YES simple conditions, YES complex conditions, 'AND'.
$query = $this->factory->get('config_query_test', 'AND');
$and_condition_1 = $query->orConditionGroup()
->condition('id', '2')
->condition('label', $this->entities[0]->label);
$and_condition_2 = $query->orConditionGroup()
->condition('id', 1)
->condition('label', $this->entities[3]->label);
$this->queryResults = $query
->condition('number', 31)
->condition($and_condition_1)
->condition($and_condition_2)
->execute();
$this->assertResults(array('1'));
// YES simple conditions, YES complex conditions, 'OR'.
$query = $this->factory->get('config_query_test', 'OR');
$and_condition_1 = $query->orConditionGroup()
->condition('id', '2')
->condition('label', $this->entities[0]->label);
$and_condition_2 = $query->orConditionGroup()
->condition('id', 1)
->condition('label', $this->entities[3]->label);
$this->queryResults = $query
->condition('number', 53)
->condition($and_condition_1)
->condition($and_condition_2)
->execute();
$this->assertResults(array('1', '2', '4', '5'));
// Test the exists and notExists conditions.
$this->queryResults = $this->factory->get('config_query_test')
->exists('id')
->execute();
$this->assertResults(array('1', '2', '3', '4', '5'));
$this->queryResults = $this->factory->get('config_query_test')
->exists('non-existent')
->execute();
$this->assertResults(array());
$this->queryResults = $this->factory->get('config_query_test')
->notExists('id')
->execute();
$this->assertResults(array());
$this->queryResults = $this->factory->get('config_query_test')
->notExists('non-existent')
->execute();
$this->assertResults(array('1', '2', '3', '4', '5'));
}
/**
* Tests ID conditions.
*/
public function testStringIdConditions() {
// We need an entity with a non-numeric ID.
$entity = ConfigQueryTest::create(array(
'label' => $this->randomMachineName(),
'id' => 'foo.bar',
));
$this->entities[] = $entity;
$entity->enforceIsNew();
$entity->save();
// Test 'STARTS_WITH' condition.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'foo.bar', 'STARTS_WITH')
->execute();
$this->assertResults(array('foo.bar'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'f', 'STARTS_WITH')
->execute();
$this->assertResults(array('foo.bar'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'miss', 'STARTS_WITH')
->execute();
$this->assertResults(array());
// Test 'CONTAINS' condition.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'foo.bar', 'CONTAINS')
->execute();
$this->assertResults(array('foo.bar'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'oo.ba', 'CONTAINS')
->execute();
$this->assertResults(array('foo.bar'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'miss', 'CONTAINS')
->execute();
$this->assertResults(array());
// Test 'ENDS_WITH' condition.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'foo.bar', 'ENDS_WITH')
->execute();
$this->assertResults(array('foo.bar'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'r', 'ENDS_WITH')
->execute();
$this->assertResults(array('foo.bar'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', 'miss', 'ENDS_WITH')
->execute();
$this->assertResults(array());
}
/**
* Tests count query.
*/
public function testCount() {
// Test count on no conditions.
$count = $this->factory->get('config_query_test')
->count()
->execute();
$this->assertIdentical($count, count($this->entities));
// Test count on a complex query.
$query = $this->factory->get('config_query_test', 'OR');
$and_condition_1 = $query->andConditionGroup()
->condition('id', 1)
->condition('label', $this->entities[0]->label);
$and_condition_2 = $query->andConditionGroup()
->condition('id', '2')
->condition('label', $this->entities[1]->label);
$count = $query
->condition($and_condition_1)
->condition($and_condition_2)
->count()
->execute();
$this->assertIdentical($count, 2);
}
/**
* Tests sorting and range on config entity queries.
*/
public function testSortRange() {
// Sort by simple ascending/descending.
$this->queryResults = $this->factory->get('config_query_test')
->sort('number', 'DESC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('3', '5', '2', '1', '4'));
$this->queryResults = $this->factory->get('config_query_test')
->sort('number', 'ASC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('4', '1', '2', '5', '3'));
// Apply some filters and sort.
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '3', '>')
->sort('number', 'DESC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('5', '4'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('id', '3', '>')
->sort('number', 'ASC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('4', '5'));
// Apply a pager and sort.
$this->queryResults = $this->factory->get('config_query_test')
->sort('number', 'DESC')
->range('2', '2')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('2', '1'));
$this->queryResults = $this->factory->get('config_query_test')
->sort('number', 'ASC')
->range('2', '2')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('2', '5'));
// Add a range to a query without a start parameter.
$this->queryResults = $this->factory->get('config_query_test')
->range(0, '3')
->sort('id', 'ASC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('1', '2', '3'));
// Apply a pager with limit 4.
$this->queryResults = $this->factory->get('config_query_test')
->pager('4', 0)
->sort('id', 'ASC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('1', '2', '3', '4'));
}
/**
* Tests sorting with tableSort on config entity queries.
*/
public function testTableSort() {
$header = array(
array('data' => t('ID'), 'specifier' => 'id'),
array('data' => t('Number'), 'specifier' => 'number'),
);
// Sort key: id
// Sorting with 'DESC' upper case
$this->queryResults = $this->factory->get('config_query_test')
->tableSort($header)
->sort('id', 'DESC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('5', '4', '3', '2', '1'));
// Sorting with 'ASC' upper case
$this->queryResults = $this->factory->get('config_query_test')
->tableSort($header)
->sort('id', 'ASC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('1', '2', '3', '4', '5'));
// Sorting with 'desc' lower case
$this->queryResults = $this->factory->get('config_query_test')
->tableSort($header)
->sort('id', 'desc')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('5', '4', '3', '2', '1'));
// Sorting with 'asc' lower case
$this->queryResults = $this->factory->get('config_query_test')
->tableSort($header)
->sort('id', 'asc')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('1', '2', '3', '4', '5'));
// Sort key: number
// Sorting with 'DeSc' mixed upper and lower case
$this->queryResults = $this->factory->get('config_query_test')
->tableSort($header)
->sort('number', 'DeSc')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('3', '5', '2', '1', '4'));
// Sorting with 'AsC' mixed upper and lower case
$this->queryResults = $this->factory->get('config_query_test')
->tableSort($header)
->sort('number', 'AsC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('4', '1', '2', '5', '3'));
// Sorting with 'dEsC' mixed upper and lower case
$this->queryResults = $this->factory->get('config_query_test')
->tableSort($header)
->sort('number', 'dEsC')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('3', '5', '2', '1', '4'));
// Sorting with 'aSc' mixed upper and lower case
$this->queryResults = $this->factory->get('config_query_test')
->tableSort($header)
->sort('number', 'aSc')
->execute();
$this->assertIdentical(array_values($this->queryResults), array('4', '1', '2', '5', '3'));
}
/**
* Tests dotted path matching.
*/
public function testDotted() {
$this->queryResults = $this->factory->get('config_query_test')
->condition('array.level1.*', 1)
->execute();
$this->assertResults(array('1', '3'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('*.level1.level2', 2)
->execute();
$this->assertResults(array('2', '4'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('array.level1.*', 3)
->execute();
$this->assertResults(array('5'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('array.level1.level2', 3)
->execute();
$this->assertResults(array('5'));
// Make sure that values on the wildcard level do not match if there are
// sub-keys defined. This must not find anything even if entity 2 has a
// top-level key number with value 41.
$this->queryResults = $this->factory->get('config_query_test')
->condition('*.level1.level2', 41)
->execute();
$this->assertResults(array());
}
/**
* Tests case sensitivity.
*/
public function testCaseSensitivity() {
// Filter by label with a known containing case-sensitive word.
$this->queryResults = $this->factory->get('config_query_test')
->condition('label', 'TEST', 'CONTAINS')
->execute();
$this->assertResults(array('3', '4', '5'));
$this->queryResults = $this->factory->get('config_query_test')
->condition('label', 'test', 'CONTAINS')
->execute();
$this->assertResults(array('3', '4', '5'));
}
/**
* Tests lookup keys are added to the key value store.
*/
public function testLookupKeys() {
\Drupal::service('state')->set('config_test.lookup_keys', TRUE);
\Drupal::entityManager()->clearCachedDefinitions();
$key_value = $this->container->get('keyvalue')->get(QueryFactory::CONFIG_LOOKUP_PREFIX . 'config_test');
$test_entities = [];
$entity = entity_create('config_test', array(
'label' => $this->randomMachineName(),
'id' => '1',
'style' => 'test',
));
$test_entities[$entity->getConfigDependencyName()] = $entity;
$entity->enforceIsNew();
$entity->save();
$expected[] = $entity->getConfigDependencyName();
$this->assertEqual($expected, $key_value->get('style:test'));
$entity = entity_create('config_test', array(
'label' => $this->randomMachineName(),
'id' => '2',
'style' => 'test',
));
$test_entities[$entity->getConfigDependencyName()] = $entity;
$entity->enforceIsNew();
$entity->save();
$expected[] = $entity->getConfigDependencyName();
$this->assertEqual($expected, $key_value->get('style:test'));
$entity = entity_create('config_test', array(
'label' => $this->randomMachineName(),
'id' => '3',
'style' => 'blah',
));
$entity->enforceIsNew();
$entity->save();
// Do not add this entity to the list of expected result as it has a
// different value.
$this->assertEqual($expected, $key_value->get('style:test'));
$this->assertEqual([$entity->getConfigDependencyName()], $key_value->get('style:blah'));
// Ensure that a delete clears a key.
$entity->delete();
$this->assertEqual(NULL, $key_value->get('style:blah'));
// Ensure that delete only clears one key.
$entity_id = array_pop($expected);
$test_entities[$entity_id]->delete();
$this->assertEqual($expected, $key_value->get('style:test'));
$entity_id = array_pop($expected);
$test_entities[$entity_id]->delete();
$this->assertEqual(NULL, $key_value->get('style:test'));
}
/**
* Asserts the results as expected regardless of order.
*
* @param array $expected
* Array of expected entity IDs.
*/
protected function assertResults($expected) {
$this->assertIdentical(count($this->queryResults), count($expected));
foreach ($expected as $value) {
// This also tests whether $this->queryResults[$value] is even set at all.
$this->assertIdentical($this->queryResults[$value], $value);
}
}
}

View file

@ -0,0 +1,540 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\entity_test\Entity\EntityTestMulChanged;
use Drupal\entity_test\Entity\EntityTestMulRevChanged;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests basic EntityChangedInterface functionality.
*
* @group Entity
*/
class ContentEntityChangedTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'user', 'system', 'field', 'text', 'filter', 'entity_test'];
/**
* The EntityTestMulChanged entity type storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $mulChangedStorage;
/**
* The EntityTestMulRevChanged entity type storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $mulRevChangedStorage;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable an additional language.
ConfigurableLanguage::createFromLangcode('de')->save();
ConfigurableLanguage::createFromLangcode('fr')->save();
$this->installEntitySchema('entity_test_mul_changed');
$this->installEntitySchema('entity_test_mulrev_changed');
$this->mulChangedStorage = $this->entityManager->getStorage('entity_test_mul_changed');
$this->mulRevChangedStorage = $this->entityManager->getStorage('entity_test_mulrev_changed');
}
/**
* Tests basic EntityChangedInterface functionality.
*/
public function testChanged() {
$user1 = $this->createUser();
$user2 = $this->createUser();
// Create a test entity.
$entity = EntityTestMulChanged::create(array(
'name' => $this->randomString(),
'user_id' => $user1->id(),
'language' => 'en',
));
$entity->save();
$this->assertTrue(
$entity->getChangedTime() >= REQUEST_TIME,
'Changed time of original language is valid.'
);
// We can't assert equality here because the created time is set to the
// request time, while instances of ChangedTestItem use the current
// timestamp every time. Therefor we check if the changed timestamp is
// between the created time and now.
$this->assertTrue(
($entity->getChangedTime() >= $entity->get('created')->value) &&
(($entity->getChangedTime() - $entity->get('created')->value) <= time() - REQUEST_TIME),
'Changed and created time of original language can be assumed to be identical.'
);
$this->assertEqual(
$entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
'Changed time of original language is the same as changed time across all translations.'
);
$changed_en = $entity->getChangedTime();
/** @var \Drupal\entity_test\Entity\EntityTestMulRevChanged $german */
$german = $entity->addTranslation('de');
$entity->save();
$this->assertEqual(
$entity->getChangedTime(), $changed_en,
'Changed time of original language did not change.'
);
$this->assertTrue(
$german->getChangedTime() > $entity->getChangedTime(),
'Changed time of the German translation is newer then the original language.'
);
$this->assertEqual(
$german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
'Changed time of the German translation is the newest time across all translations.'
);
$changed_de = $german->getChangedTime();
$entity->save();
$this->assertEqual(
$entity->getChangedTime(), $changed_en,
'Changed time of original language did not change.'
);
$this->assertEqual(
$german->getChangedTime(), $changed_de,
'Changed time of the German translation did not change.'
);
$entity->setOwner($user2);
$entity->save();
$this->assertTrue(
$entity->getChangedTime() > $changed_en,
'Changed time of original language did change.'
);
$this->assertEqual(
$german->getChangedTime(), $changed_de,
'Changed time of the German translation did not change.'
);
$this->assertTrue(
$entity->getChangedTime() > $german->getChangedTime(),
'Changed time of original language is newer then the German translation.'
);
$this->assertEqual(
$entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
'Changed time of the original language is the newest time across all translations.'
);
$changed_en = $entity->getChangedTime();
// Save entity without any changes.
$entity->save();
$this->assertEqual(
$entity->getChangedTime(), $changed_en,
'Changed time of original language did not change.'
);
$this->assertEqual(
$german->getChangedTime(), $changed_de,
'Changed time of the German translation did not change.'
);
// At this point the changed time of the original language (en) is newer
// than the changed time of the German translation. Now test that entity
// queries work as expected.
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_en)->execute();
$this->assertEqual(
reset($ids), $entity->id(),
'Entity query can access changed time of original language.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_en, '=', 'en')->execute();
$this->assertEqual(
reset($ids), $entity->id(),
'Entity query can access changed time of original language by setting the original language as condition.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_de, '=', 'en')->execute();
$this->assertFalse(
$ids,
'There\'s no original entity stored having the changed time of the German translation.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_en)->condition('default_langcode', '1')->execute();
$this->assertEqual(
reset($ids), $entity->id(),
'Entity query can access changed time of default language.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_de)->condition('default_langcode', '1')->execute();
$this->assertFalse(
$ids,
'There\'s no entity stored using the default language having the changed time of the German translation.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_de)->execute();
$this->assertEqual(
reset($ids), $entity->id(),
'Entity query can access changed time of the German translation.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_de, '=', 'de')->execute();
$this->assertEqual(
reset($ids), $entity->id(),
'Entity query can access changed time of the German translation.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_en, '=', 'de')->execute();
$this->assertFalse(
$ids,
'There\'s no German translation stored having the changed time of the original language.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_de, '>')->execute();
$this->assertEqual(
reset($ids), $entity->id(),
'Entity query can access changed time regardless of translation.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_en, '<')->execute();
$this->assertEqual(
reset($ids), $entity->id(),
'Entity query can access changed time regardless of translation.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', 0, '>')->execute();
$this->assertEqual(
reset($ids), $entity->id(),
'Entity query can access changed time regardless of translation.'
);
$query = $this->mulChangedStorage->getQuery();
$ids = $query->condition('changed', $changed_en, '>')->execute();
$this->assertFalse(
$ids,
'Entity query can access changed time regardless of translation.'
);
}
/**
* Tests revisionable EntityChangedInterface functionality.
*/
public function testRevisionChanged() {
$user1 = $this->createUser();
$user2 = $this->createUser();
// Create a test entity.
$entity = EntityTestMulRevChanged::create(array(
'name' => $this->randomString(),
'user_id' => $user1->id(),
'language' => 'en',
));
$entity->save();
$this->assertTrue(
$entity->getChangedTime() >= REQUEST_TIME,
'Changed time of original language is valid.'
);
// We can't assert equality here because the created time is set to the
// request time while instances of ChangedTestItem use the current
// timestamp every time.
$this->assertTrue(
($entity->getChangedTime() >= $entity->get('created')->value) &&
(($entity->getChangedTime() - $entity->get('created')->value) <= time() - REQUEST_TIME),
'Changed and created time of original language can be assumed to be identical.'
);
$this->assertEqual(
$entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
'Changed time of original language is the same as changed time across all translations.'
);
$this->assertTrue(
$this->getRevisionTranslationAffectedFlag($entity),
'Changed flag of original language is set for a new entity.'
);
$changed_en = $entity->getChangedTime();
$entity->setNewRevision();
// Save entity without any changes but create new revision.
$entity->save();
// A new revision without any changes should not set a new changed time.
$this->assertEqual(
$entity->getChangedTime(), $changed_en,
'Changed time of original language did not change.'
);
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($entity),
'Changed flag of original language is not set for new revision without changes.'
);
$entity->setNewRevision();
$entity->setOwner($user2);
$entity->save();
$this->assertTrue(
$entity->getChangedTime() > $changed_en,
'Changed time of original language has been updated by new revision.'
);
$this->assertTrue(
$this->getRevisionTranslationAffectedFlag($entity),
'Changed flag of original language is set for new revision with changes.'
);
$changed_en = $entity->getChangedTime();
/** @var \Drupal\entity_test\Entity\EntityTestMulRevChanged $german */
$german = $entity->addTranslation('de');
$entity->save();
$this->assertEqual(
$entity->getChangedTime(), $changed_en,
'Changed time of original language did not change.'
);
$this->assertTrue(
$german->getChangedTime() > $entity->getChangedTime(),
'Changed time of the German translation is newer then the original language.'
);
$this->assertEqual(
$german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
'Changed time of the German translation is the newest time across all translations.'
);
$this->assertTrue(
$this->getRevisionTranslationAffectedFlag($entity),
'Changed flag of original language is not reset by adding a new translation.'
);
$this->assertTrue(
$this->getRevisionTranslationAffectedFlag($german),
'Changed flag of German translation is set when adding the translation.'
);
$changed_de = $german->getChangedTime();
$entity->setNewRevision();
// Save entity without any changes but create new revision.
$entity->save();
$this->assertEqual(
$entity->getChangedTime(), $changed_en,
'Changed time of original language did not change.'
);
$this->assertEqual(
$german->getChangedTime(), $changed_de,
'Changed time of the German translation did not change.'
);
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($entity),
'Changed flag of original language is not set for new revision without changes.'
);
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($german),
'Changed flag of the German translation is not set for new revision without changes.'
);
$entity->setNewRevision();
$german->setOwner($user2);
$entity->save();
$this->assertEqual(
$entity->getChangedTime(), $changed_en,
'Changed time of original language did not change.'
);
$this->assertTrue(
$german->getChangedTime() > $changed_de,
'Changed time of the German translation did change.'
);
$this->assertEqual(
$german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
'Changed time of the German translation is the newest time across all translations.'
);
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($entity),
'Changed flag of original language is not set when changing the German Translation.'
);
$this->assertTrue(
$this->getRevisionTranslationAffectedFlag($german),
'Changed flag of German translation is set when changing the German translation.'
);
$french = $entity->addTranslation('fr');
$entity->setNewRevision();
$entity->save();
$this->assertEqual(
$entity->getChangedTime(), $changed_en,
'Changed time of original language did not change.'
);
$this->assertTrue(
$french->getChangedTime() > $entity->getChangedTime(),
'Changed time of the French translation is newer then the original language.'
);
$this->assertTrue(
$french->getChangedTime() > $entity->getChangedTime(),
'Changed time of the French translation is newer then the German translation.'
);
$this->assertEqual(
$french->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
'Changed time of the French translation is the newest time across all translations.'
);
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($entity),
'Changed flag of original language is reset by adding a new translation and a new revision.'
);
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($german),
'Changed flag of German translation is reset by adding a new translation and a new revision.'
);
$this->assertTrue(
$this->getRevisionTranslationAffectedFlag($french),
'Changed flag of French translation is set when adding the translation and a new revision.'
);
$entity->removeTranslation('fr');
$entity->setNewRevision();
$entity->save();
// This block simulates exactly the flow of a node form submission of a new
// translation and a new revision.
$form_entity_builder_entity = EntityTestMulRevChanged::load($entity->id());
// ContentTranslationController::prepareTranslation().
$form_entity_builder_entity = $form_entity_builder_entity->addTranslation('fr', $form_entity_builder_entity->toArray());
// EntityForm::buildEntity() during form submit.
$form_entity_builder_clone = clone $form_entity_builder_entity;
// NodeForm::submitForm().
$form_entity_builder_clone->setNewRevision();
// EntityForm::save().
$form_entity_builder_clone->save();
// The assertion fails unless https://www.drupal.org/node/2513094 is
// committed.
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($entity),
'Changed flag of original language is reset by adding a new translation and a new revision.'
);
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($german),
'Changed flag of German translation is reset by adding a new translation and a new revision.'
);
$this->assertTrue(
$this->getRevisionTranslationAffectedFlag($french),
'Changed flag of French translation is set when adding the translation and a new revision.'
);
$german->setOwner($user1);
$german->setRevisionTranslationAffected(FALSE);
$entity->save();
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($german),
'German translation changed but the changed flag is reset manually.'
);
$entity->setNewRevision();
$german->setRevisionTranslationAffected(TRUE);
$entity->save();
$this->assertTrue(
$this->getRevisionTranslationAffectedFlag($german),
'German translation is not changed and a new revision is created but the changed flag is set manually.'
);
$german->setOwner($user2);
$entity->setNewRevision();
$german->setRevisionTranslationAffected(FALSE);
$entity->save();
$this->assertFalse(
$this->getRevisionTranslationAffectedFlag($german),
'German translation changed and a new revision is created but the changed flag is reset manually.'
);
}
/**
* Retrieves the revision translation affected flag value.
*
* @param \Drupal\entity_test\Entity\EntityTestMulRevChanged $entity
* The entity object to be checked.
*
* @return bool
* The flag value.
*/
protected function getRevisionTranslationAffectedFlag(EntityTestMulRevChanged $entity) {
$query = $this->mulRevChangedStorage->getQuery();
$ids = $query->condition('revision_translation_affected', 1, '=', $entity->language()->getId())->execute();
$id = reset($ids);
return (bool) ($id == $entity->id());
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\entity_test\Entity\EntityTestMul;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests proper cloning of content entities.
*
* @group Entity
*/
class ContentEntityCloneTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language', 'entity_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable an additional language.
ConfigurableLanguage::createFromLangcode('de')->save();
$this->installEntitySchema('entity_test_mul');
}
/**
* Tests if entity references on fields are still correct after cloning.
*/
public function testFieldEntityReferenceAfterClone() {
$user = $this->createUser();
// Create a test entity.
$entity = EntityTestMul::create([
'name' => $this->randomString(),
'user_id' => $user->id(),
'language' => 'en',
]);
$clone = clone $entity->addTranslation('de');
$this->assertEqual($entity->getTranslationLanguages(), $clone->getTranslationLanguages(), 'The entity and its clone have the same translation languages.');
$default_langcode = $entity->getUntranslated()->language()->getId();
foreach (array_keys($clone->getTranslationLanguages()) as $langcode) {
$translation = $clone->getTranslation($langcode);
foreach ($translation->getFields() as $field_name => $field) {
if ($field->getFieldDefinition()->isTranslatable()) {
$args = ['%field_name' => $field_name, '%langcode' => $langcode];
$this->assertEqual($langcode, $field->getEntity()->language()->getId(), format_string('Translatable field %field_name on translation %langcode has correct entity reference in translation %langcode after cloning.', $args));
}
else {
$args = ['%field_name' => $field_name, '%langcode' => $langcode, '%default_langcode' => $default_langcode];
$this->assertEqual($default_langcode, $field->getEntity()->language()->getId(), format_string('Non translatable field %field_name on translation %langcode has correct entity reference in the default translation %default_langcode after cloning.', $args));
}
}
}
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\contact\Entity\ContactForm;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\StorageComparer;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests ContentEntityNullStorage entity query support.
*
* @see \Drupal\Core\Entity\ContentEntityNullStorage
* @see \Drupal\Core\Entity\Query\Null\Query
*
* @group Entity
*/
class ContentEntityNullStorageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'contact', 'user');
/**
* Tests using entity query with ContentEntityNullStorage.
*
* @see \Drupal\Core\Entity\Query\Null\Query
*/
public function testEntityQuery() {
$this->assertIdentical(0, \Drupal::entityQuery('contact_message')->count()->execute(), 'Counting a null storage returns 0.');
$this->assertIdentical([], \Drupal::entityQuery('contact_message')->execute(), 'Querying a null storage returns an empty array.');
$this->assertIdentical([], \Drupal::entityQuery('contact_message')->condition('contact_form', 'test')->execute(), 'Querying a null storage returns an empty array and conditions are ignored.');
$this->assertIdentical([], \Drupal::entityQueryAggregate('contact_message')->aggregate('name', 'AVG')->execute(), 'Aggregate querying a null storage returns an empty array');
}
/**
* Tests deleting a contact form entity via a configuration import.
*
* @see \Drupal\Core\Entity\Event\BundleConfigImportValidate
*/
public function testDeleteThroughImport() {
$contact_form = ContactForm::create(['id' => 'test']);
$contact_form->save();
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
// Set up the ConfigImporter object for testing.
$storage_comparer = new StorageComparer(
$this->container->get('config.storage.sync'),
$this->container->get('config.storage'),
$this->container->get('config.manager')
);
$config_importer = new ConfigImporter(
$storage_comparer->createChangelist(),
$this->container->get('event_dispatcher'),
$this->container->get('config.manager'),
$this->container->get('lock'),
$this->container->get('config.typed'),
$this->container->get('module_handler'),
$this->container->get('module_installer'),
$this->container->get('theme_handler'),
$this->container->get('string_translation')
);
// Delete the contact message in sync.
$sync = $this->container->get('config.storage.sync');
$sync->delete($contact_form->getConfigDependencyName());
// Import.
$config_importer->reset()->import();
$this->assertNull(ContactForm::load($contact_form->id()), 'The contact form has been deleted.');
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Tests the default table mapping class for content entities stored in SQL.
*
* @see \Drupal\Core\Entity\Sql\DefaultTableMapping
* @see \Drupal\Core\Entity\Sql\TableMappingInterface
*
* @coversDefaultClass \Drupal\Core\Entity\Sql\DefaultTableMapping
* @group Entity
*/
class DefaultTableMappingIntegrationTest extends EntityKernelTestBase {
/**
* The table mapping for the tested entity type.
*
* @var \Drupal\Core\Entity\Sql\TableMappingInterface
*/
protected $tableMapping;
/**
* {@inheritdoc}
*/
public static $modules = ['entity_test_extra'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Setup some fields for entity_test_extra to create.
$definitions['multivalued_base_field'] = BaseFieldDefinition::create('string')
->setName('multivalued_base_field')
->setTargetEntityTypeId('entity_test_mulrev')
->setTargetBundle('entity_test_mulrev')
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$this->state->set('entity_test_mulrev.additional_base_field_definitions', $definitions);
$this->entityManager->clearCachedDefinitions();
$this->tableMapping = $this->entityManager->getStorage('entity_test_mulrev')->getTableMapping();
}
/**
* Tests DefaultTableMapping::getFieldTableName().
*
* @covers ::getFieldTableName
*/
public function testGetFieldTableName() {
// Test the field table name for a single-valued base field, which is stored
// in the entity's base table.
$expected = 'entity_test_mulrev';
$this->assertEquals($this->tableMapping->getFieldTableName('uuid'), $expected);
// Test the field table name for a translatable and revisionable base field,
// which is stored in the entity's data table.
$expected = 'entity_test_mulrev_property_data';
$this->assertEquals($this->tableMapping->getFieldTableName('name'), $expected);
// Test the field table name for a multi-valued base field, which is stored
// in a dedicated table.
$expected = 'entity_test_mulrev__multivalued_base_field';
$this->assertEquals($this->tableMapping->getFieldTableName('multivalued_base_field'), $expected);
}
}

View file

@ -0,0 +1,327 @@
<?php
namespace Drupal\KernelTests\Core\Entity\Element;
use Drupal\Core\Entity\Element\EntityAutocomplete;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\user\Entity\User;
/**
* Tests the EntityAutocomplete Form API element.
*
* @group Form
*/
class EntityAutocompleteElementFormTest extends EntityKernelTestBase implements FormInterface {
/**
* User for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $testUser;
/**
* User for autocreate testing.
*
* @var \Drupal\user\UserInterface
*/
protected $testAutocreateUser;
/**
* An array of entities to be referenced in this test.
*
* @var \Drupal\Core\Entity\EntityInterface[]
*/
protected $referencedEntities;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['key_value_expire']);
\Drupal::service('router.builder')->rebuild();
$this->testUser = User::create(array(
'name' => 'foobar1',
'mail' => 'foobar1@example.com',
));
$this->testUser->save();
\Drupal::service('current_user')->setAccount($this->testUser);
$this->testAutocreateUser = User::create(array(
'name' => 'foobar2',
'mail' => 'foobar2@example.com',
));
$this->testAutocreateUser->save();
for ($i = 1; $i < 3; $i++) {
$entity = EntityTest::create(array(
'name' => $this->randomMachineName()
));
$entity->save();
$this->referencedEntities[] = $entity;
}
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'test_entity_autocomplete';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['single'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
);
$form['single_autocreate'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#autocreate' => array(
'bundle' => 'entity_test',
),
);
$form['single_autocreate_specific_uid'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#autocreate' => array(
'bundle' => 'entity_test',
'uid' => $this->testAutocreateUser->id(),
),
);
$form['tags'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#tags' => TRUE,
);
$form['tags_autocreate'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#tags' => TRUE,
'#autocreate' => array(
'bundle' => 'entity_test',
),
);
$form['tags_autocreate_specific_uid'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#tags' => TRUE,
'#autocreate' => array(
'bundle' => 'entity_test',
'uid' => $this->testAutocreateUser->id(),
),
);
$form['single_no_validate'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#validate_reference' => FALSE,
);
$form['single_autocreate_no_validate'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#validate_reference' => FALSE,
'#autocreate' => array(
'bundle' => 'entity_test',
),
);
$form['single_access'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#default_value' => $this->referencedEntities[0],
);
$form['tags_access'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => 'entity_test',
'#tags' => TRUE,
'#default_value' => array($this->referencedEntities[0], $this->referencedEntities[1]),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) { }
/**
* Tests valid entries in the EntityAutocomplete Form API element.
*/
public function testValidEntityAutocompleteElement() {
$form_state = (new FormState())
->setValues([
'single' => $this->getAutocompleteInput($this->referencedEntities[0]),
'single_autocreate' => 'single - autocreated entity label',
'single_autocreate_specific_uid' => 'single - autocreated entity label with specific uid',
'tags' => $this->getAutocompleteInput($this->referencedEntities[0]) . ', ' . $this->getAutocompleteInput($this->referencedEntities[1]),
'tags_autocreate' =>
$this->getAutocompleteInput($this->referencedEntities[0])
. ', tags - autocreated entity label, '
. $this->getAutocompleteInput($this->referencedEntities[1]),
'tags_autocreate_specific_uid' =>
$this->getAutocompleteInput($this->referencedEntities[0])
. ', tags - autocreated entity label with specific uid, '
. $this->getAutocompleteInput($this->referencedEntities[1]),
]);
$form_builder = $this->container->get('form_builder');
$form_builder->submitForm($this, $form_state);
// Valid form state.
$this->assertEqual(count($form_state->getErrors()), 0);
// Test the 'single' element.
$this->assertEqual($form_state->getValue('single'), $this->referencedEntities[0]->id());
// Test the 'single_autocreate' element.
$value = $form_state->getValue('single_autocreate');
$this->assertEqual($value['entity']->label(), 'single - autocreated entity label');
$this->assertEqual($value['entity']->bundle(), 'entity_test');
$this->assertEqual($value['entity']->getOwnerId(), $this->testUser->id());
// Test the 'single_autocreate_specific_uid' element.
$value = $form_state->getValue('single_autocreate_specific_uid');
$this->assertEqual($value['entity']->label(), 'single - autocreated entity label with specific uid');
$this->assertEqual($value['entity']->bundle(), 'entity_test');
$this->assertEqual($value['entity']->getOwnerId(), $this->testAutocreateUser->id());
// Test the 'tags' element.
$expected = array(
array('target_id' => $this->referencedEntities[0]->id()),
array('target_id' => $this->referencedEntities[1]->id()),
);
$this->assertEqual($form_state->getValue('tags'), $expected);
// Test the 'single_autocreate' element.
$value = $form_state->getValue('tags_autocreate');
// First value is an existing entity.
$this->assertEqual($value[0]['target_id'], $this->referencedEntities[0]->id());
// Second value is an autocreated entity.
$this->assertTrue(!isset($value[1]['target_id']));
$this->assertEqual($value[1]['entity']->label(), 'tags - autocreated entity label');
$this->assertEqual($value[1]['entity']->getOwnerId(), $this->testUser->id());
// Third value is an existing entity.
$this->assertEqual($value[2]['target_id'], $this->referencedEntities[1]->id());
// Test the 'tags_autocreate_specific_uid' element.
$value = $form_state->getValue('tags_autocreate_specific_uid');
// First value is an existing entity.
$this->assertEqual($value[0]['target_id'], $this->referencedEntities[0]->id());
// Second value is an autocreated entity.
$this->assertTrue(!isset($value[1]['target_id']));
$this->assertEqual($value[1]['entity']->label(), 'tags - autocreated entity label with specific uid');
$this->assertEqual($value[1]['entity']->getOwnerId(), $this->testAutocreateUser->id());
// Third value is an existing entity.
$this->assertEqual($value[2]['target_id'], $this->referencedEntities[1]->id());
}
/**
* Tests invalid entries in the EntityAutocomplete Form API element.
*/
public function testInvalidEntityAutocompleteElement() {
$form_builder = $this->container->get('form_builder');
// Test 'single' with a entity label that doesn't exist
$form_state = (new FormState())
->setValues([
'single' => 'single - non-existent label',
]);
$form_builder->submitForm($this, $form_state);
$this->assertEqual(count($form_state->getErrors()), 1);
$this->assertEqual($form_state->getErrors()['single'], t('There are no entities matching "%value".', array('%value' => 'single - non-existent label')));
// Test 'single' with a entity ID that doesn't exist.
$form_state = (new FormState())
->setValues([
'single' => 'single - non-existent label (42)',
]);
$form_builder->submitForm($this, $form_state);
$this->assertEqual(count($form_state->getErrors()), 1);
$this->assertEqual($form_state->getErrors()['single'], t('The referenced entity (%type: %id) does not exist.', array('%type' => 'entity_test', '%id' => 42)));
// Do the same tests as above but on an element with '#validate_reference'
// set to FALSE.
$form_state = (new FormState())
->setValues([
'single_no_validate' => 'single - non-existent label',
'single_autocreate_no_validate' => 'single - autocreate non-existent label'
]);
$form_builder->submitForm($this, $form_state);
// The element without 'autocreate' support still has to emit a warning when
// the input doesn't end with an entity ID enclosed in parentheses.
$this->assertEqual(count($form_state->getErrors()), 1);
$this->assertEqual($form_state->getErrors()['single_no_validate'], t('There are no entities matching "%value".', array('%value' => 'single - non-existent label')));
$form_state = (new FormState())
->setValues([
'single_no_validate' => 'single - non-existent label (42)',
'single_autocreate_no_validate' => 'single - autocreate non-existent label (43)'
]);
$form_builder->submitForm($this, $form_state);
// The input is complete (i.e. contains an entity ID at the end), no errors
// are triggered.
$this->assertEqual(count($form_state->getErrors()), 0);
}
/**
* Tests that access is properly checked by the EntityAutocomplete element.
*/
public function testEntityAutocompleteAccess() {
$form_builder = $this->container->get('form_builder');
$form = $form_builder->getForm($this);
// Check that the current user has proper access to view entity labels.
$expected = $this->referencedEntities[0]->label() . ' (' . $this->referencedEntities[0]->id() . ')';
$this->assertEqual($form['single_access']['#value'], $expected);
$expected .= ', ' . $this->referencedEntities[1]->label() . ' (' . $this->referencedEntities[1]->id() . ')';
$this->assertEqual($form['tags_access']['#value'], $expected);
// Set up a non-admin user that is *not* allowed to view test entities.
\Drupal::currentUser()->setAccount($this->createUser(array(), array()));
// Rebuild the form.
$form = $form_builder->getForm($this);
$expected = t('- Restricted access -') . ' (' . $this->referencedEntities[0]->id() . ')';
$this->assertEqual($form['single_access']['#value'], $expected);
$expected .= ', ' . t('- Restricted access -') . ' (' . $this->referencedEntities[1]->id() . ')';
$this->assertEqual($form['tags_access']['#value'], $expected);
}
/**
* Returns an entity label in the format needed by the EntityAutocomplete
* element.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* A Drupal entity.
*
* @return string
* A string that can be used as a value for EntityAutocomplete elements.
*/
protected function getAutocompleteInput(EntityInterface $entity) {
return EntityAutocomplete::getEntityLabels(array($entity));
}
}

View file

@ -0,0 +1,225 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\entity_test\Entity\EntityTestDefaultAccess;
use Drupal\entity_test\Entity\EntityTestLabel;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\user\Entity\User;
/**
* Tests the entity access control handler.
*
* @group Entity
*/
class EntityAccessControlHandlerTest extends EntityLanguageTestBase {
/**
* Asserts entity access correctly grants or denies access.
*/
function assertEntityAccess($ops, AccessibleInterface $object, AccountInterface $account = NULL) {
foreach ($ops as $op => $result) {
$message = format_string("Entity access returns @result with operation '@op'.", array(
'@result' => !isset($result) ? 'null' : ($result ? 'true' : 'false'),
'@op' => $op,
));
$this->assertEqual($result, $object->access($op, $account), $message);
}
}
/**
* Ensures user labels are accessible for everyone.
*/
public function testUserLabelAccess() {
// Set up a non-admin user.
\Drupal::currentUser()->setAccount($this->createUser(['uid' => 2]));
$anonymous_user = User::getAnonymousUser();
$user = $this->createUser();
// The current user is allowed to view the anonymous user label.
$this->assertEntityAccess(array(
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => FALSE,
'view label' => TRUE,
), $anonymous_user);
// The current user is allowed to view user labels.
$this->assertEntityAccess(array(
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => FALSE,
'view label' => TRUE,
), $user);
// Switch to a anonymous user account.
$account_switcher = \Drupal::service('account_switcher');
$account_switcher->switchTo(new AnonymousUserSession());
// The anonymous user is allowed to view the anonymous user label.
$this->assertEntityAccess(array(
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => FALSE,
'view label' => TRUE,
), $anonymous_user);
// The anonymous user is allowed to view user labels.
$this->assertEntityAccess(array(
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => FALSE,
'view label' => TRUE,
), $user);
// Restore user account.
$account_switcher->switchBack();
}
/**
* Ensures entity access is properly working.
*/
function testEntityAccess() {
// Set up a non-admin user that is allowed to view test entities.
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity')));
// Use the 'entity_test_label' entity type in order to test the 'view label'
// access operation.
$entity = EntityTestLabel::create(array(
'name' => 'test',
));
// The current user is allowed to view entities.
$this->assertEntityAccess(array(
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => TRUE,
'view label' => TRUE,
), $entity);
// The custom user is not allowed to perform any operation on test entities,
// except for viewing their label.
$custom_user = $this->createUser();
$this->assertEntityAccess(array(
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => FALSE,
'view label' => TRUE,
), $entity, $custom_user);
}
/**
* Ensures default entity access is checked when necessary.
*
* This ensures that the default checkAccess() implementation of the
* entity access control handler is considered if hook_entity_access() has not
* explicitly forbidden access. Therefore the default checkAccess()
* implementation can forbid access, even after access was already explicitly
* allowed by hook_entity_access().
*
* @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
* @see entity_test_entity_access()
*/
function testDefaultEntityAccess() {
// Set up a non-admin user that is allowed to view test entities.
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity')));
$entity = EntityTest::create(array(
'name' => 'forbid_access',
));
// The user is denied access to the entity.
$this->assertEntityAccess(array(
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => FALSE,
), $entity);
}
/**
* Ensures that the default handler is used as a fallback.
*/
function testEntityAccessDefaultController() {
// The implementation requires that the global user id can be loaded.
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2)));
// Check that the default access control handler is used for entities that don't
// have a specific access control handler defined.
$handler = $this->container->get('entity.manager')->getAccessControlHandler('entity_test_default_access');
$this->assertTrue($handler instanceof EntityAccessControlHandler, 'The default entity handler is used for the entity_test_default_access entity type.');
$entity = EntityTestDefaultAccess::create();
$this->assertEntityAccess(array(
'create' => FALSE,
'update' => FALSE,
'delete' => FALSE,
'view' => FALSE,
), $entity);
}
/**
* Ensures entity access for entity translations is properly working.
*/
function testEntityTranslationAccess() {
// Set up a non-admin user that is allowed to view test entity translations.
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity translations')));
// Create two test languages.
foreach (array('foo', 'bar') as $langcode) {
ConfigurableLanguage::create(array(
'id' => $langcode,
'label' => $this->randomString(),
))->save();
}
$entity = EntityTest::create(array(
'name' => 'test',
'langcode' => 'foo',
));
$entity->save();
$translation = $entity->addTranslation('bar');
$this->assertEntityAccess(array(
'view' => TRUE,
), $translation);
}
/**
* Tests hook invocations.
*/
public function testHooks() {
$state = $this->container->get('state');
$entity = EntityTest::create(array(
'name' => 'test',
));
// Test hook_entity_create_access() and hook_ENTITY_TYPE_create_access().
$entity->access('create');
$this->assertEqual($state->get('entity_test_entity_create_access'), TRUE);
$this->assertIdentical($state->get('entity_test_entity_create_access_context'), [
'entity_type_id' => 'entity_test',
'langcode' => LanguageInterface::LANGCODE_DEFAULT,
]);
$this->assertEqual($state->get('entity_test_entity_test_create_access'), TRUE);
// Test hook_entity_access() and hook_ENTITY_TYPE_access().
$entity->access('view');
$this->assertEqual($state->get('entity_test_entity_access'), TRUE);
$this->assertEqual($state->get('entity_test_entity_test_access'), TRUE);
}
}

View file

@ -0,0 +1,173 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\user\UserInterface;
/**
* Tests basic CRUD functionality.
*
* @group Entity
*/
class EntityApiTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
foreach (entity_test_entity_types() as $entity_type_id) {
// The entity_test schema is installed by the parent.
if ($entity_type_id != 'entity_test') {
$this->installEntitySchema($entity_type_id);
}
}
}
/**
* Tests basic CRUD functionality of the Entity API.
*/
public function testCRUD() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->assertCRUD($entity_type, $this->createUser());
}
}
/**
* Executes a test set for a defined entity type and user.
*
* @param string $entity_type
* The entity type to run the tests with.
* @param \Drupal\user\UserInterface $user1
* The user to run the tests with.
*/
protected function assertCRUD($entity_type, UserInterface $user1) {
// Create some test entities.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('name' => 'test', 'user_id' => $user1->id()));
$entity->save();
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('name' => 'test2', 'user_id' => $user1->id()));
$entity->save();
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('name' => 'test', 'user_id' => NULL));
$entity->save();
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test')));
$this->assertEqual($entities[0]->name->value, 'test', format_string('%entity_type: Created and loaded entity', array('%entity_type' => $entity_type)));
$this->assertEqual($entities[1]->name->value, 'test', format_string('%entity_type: Created and loaded entity', array('%entity_type' => $entity_type)));
// Test loading a single entity.
$loaded_entity = entity_load($entity_type, $entity->id());
$this->assertEqual($loaded_entity->id(), $entity->id(), format_string('%entity_type: Loaded a single entity by id.', array('%entity_type' => $entity_type)));
// Test deleting an entity.
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test2')));
$entities[0]->delete();
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test2')));
$this->assertEqual($entities, array(), format_string('%entity_type: Entity deleted.', array('%entity_type' => $entity_type)));
// Test updating an entity.
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test')));
$entities[0]->name->value = 'test3';
$entities[0]->save();
$entity = entity_load($entity_type, $entities[0]->id());
$this->assertEqual($entity->name->value, 'test3', format_string('%entity_type: Entity updated.', array('%entity_type' => $entity_type)));
// Try deleting multiple test entities by deleting all.
$ids = array_keys(entity_load_multiple($entity_type));
entity_delete_multiple($entity_type, $ids);
$all = entity_load_multiple($entity_type);
$this->assertTrue(empty($all), format_string('%entity_type: Deleted all entities.', array('%entity_type' => $entity_type)));
// Verify that all data got deleted.
$definition = \Drupal::entityManager()->getDefinition($entity_type);
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $definition->getBaseTable() . '}')->fetchField(), 'Base table was emptied');
if ($data_table = $definition->getDataTable()) {
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $data_table . '}')->fetchField(), 'Data table was emptied');
}
if ($revision_table = $definition->getRevisionTable()) {
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $revision_table . '}')->fetchField(), 'Data table was emptied');
}
// Test deleting a list of entities not indexed by entity id.
$entities = array();
$entity = entity_create($entity_type, array('name' => 'test', 'user_id' => $user1->id()));
$entity->save();
$entities['test'] = $entity;
$entity = entity_create($entity_type, array('name' => 'test2', 'user_id' => $user1->id()));
$entity->save();
$entities['test2'] = $entity;
$controller = \Drupal::entityManager()->getStorage($entity_type);
$controller->delete($entities);
// Verify that entities got deleted.
$all = entity_load_multiple($entity_type);
$this->assertTrue(empty($all), format_string('%entity_type: Deleted all entities.', array('%entity_type' => $entity_type)));
// Verify that all data got deleted from the tables.
$definition = \Drupal::entityManager()->getDefinition($entity_type);
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $definition->getBaseTable() . '}')->fetchField(), 'Base table was emptied');
if ($data_table = $definition->getDataTable()) {
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $data_table . '}')->fetchField(), 'Data table was emptied');
}
if ($revision_table = $definition->getRevisionTable()) {
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $revision_table . '}')->fetchField(), 'Data table was emptied');
}
}
/**
* Tests that exceptions are thrown when saving or deleting an entity.
*/
public function testEntityStorageExceptionHandling() {
$entity = EntityTest::create(array('name' => 'test'));
try {
$GLOBALS['entity_test_throw_exception'] = TRUE;
$entity->save();
$this->fail('Entity presave EntityStorageException thrown but not caught.');
}
catch (EntityStorageException $e) {
$this->assertEqual($e->getcode(), 1, 'Entity presave EntityStorageException caught.');
}
$entity = EntityTest::create(array('name' => 'test2'));
try {
unset($GLOBALS['entity_test_throw_exception']);
$entity->save();
$this->pass('Exception presave not thrown and not caught.');
}
catch (EntityStorageException $e) {
$this->assertNotEqual($e->getCode(), 1, 'Entity presave EntityStorageException caught.');
}
$entity = EntityTest::create(array('name' => 'test3'));
$entity->save();
try {
$GLOBALS['entity_test_throw_exception'] = TRUE;
$entity->delete();
$this->fail('Entity predelete EntityStorageException not thrown.');
}
catch (EntityStorageException $e) {
$this->assertEqual($e->getCode(), 2, 'Entity predelete EntityStorageException caught.');
}
unset($GLOBALS['entity_test_throw_exception']);
$entity = EntityTest::create(array('name' => 'test4'));
$entity->save();
try {
$entity->delete();
$this->pass('Entity predelete EntityStorageException not thrown and not caught.');
}
catch (EntityStorageException $e) {
$this->assertNotEqual($e->getCode(), 2, 'Entity predelete EntityStorageException thrown.');
}
}
}

View file

@ -0,0 +1,165 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Site\Settings;
use Drupal\system\Controller\EntityAutocompleteController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Tests the autocomplete functionality.
*
* @group Entity
*/
class EntityAutocompleteTest extends EntityKernelTestBase {
/**
* The entity type used in this test.
*
* @var string
*/
protected $entityType = 'entity_test';
/**
* The bundle used in this test.
*
* @var string
*/
protected $bundle = 'entity_test';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['key_value']);
}
/**
* Tests autocompletion edge cases with slashes in the names.
*/
function testEntityReferenceAutocompletion() {
// Add an entity with a slash in its name.
$entity_1 = $this->container->get('entity_type.manager')
->getStorage($this->entityType)
->create(array('name' => '10/16/2011'));
$entity_1->save();
// Add another entity that differs after the slash character.
$entity_2 = $this->container->get('entity_type.manager')
->getStorage($this->entityType)
->create(array('name' => '10/17/2011'));
$entity_2->save();
// Add another entity that has both a comma, a slash and markup.
$entity_3 = $this->container->get('entity_type.manager')
->getStorage($this->entityType)
->create(array('name' => 'label with, and / test'));
$entity_3->save();
// Try to autocomplete a entity label that matches both entities.
// We should get both entities in a JSON encoded string.
$input = '10/';
$data = $this->getAutocompleteResult($input);
$this->assertIdentical($data[0]['label'], Html::escape($entity_1->name->value), 'Autocomplete returned the first matching entity');
$this->assertIdentical($data[1]['label'], Html::escape($entity_2->name->value), 'Autocomplete returned the second matching entity');
// Try to autocomplete a entity label that matches the first entity.
// We should only get the first entity in a JSON encoded string.
$input = '10/16';
$data = $this->getAutocompleteResult($input);
$target = array(
'value' => $entity_1->name->value . ' (1)',
'label' => Html::escape($entity_1->name->value),
);
$this->assertIdentical(reset($data), $target, 'Autocomplete returns only the expected matching entity.');
// Try to autocomplete a entity label that matches the second entity, and
// the first entity is already typed in the autocomplete (tags) widget.
$input = $entity_1->name->value . ' (1), 10/17';
$data = $this->getAutocompleteResult($input);
$this->assertIdentical($data[0]['label'], Html::escape($entity_2->name->value), 'Autocomplete returned the second matching entity');
// Try to autocomplete a entity label with both a comma, a slash and markup.
$input = '"label with, and /"';
$data = $this->getAutocompleteResult($input);
$n = $entity_3->name->value . ' (3)';
// Entity labels containing commas or quotes must be wrapped in quotes.
$n = Tags::encode($n);
$target = array(
'value' => $n,
'label' => Html::escape($entity_3->name->value),
);
$this->assertIdentical(reset($data), $target, 'Autocomplete returns an entity label containing a comma and a slash.');
}
/**
* Tests that missing or invalid selection setting key are handled correctly.
*/
public function testSelectionSettingsHandling() {
$entity_reference_controller = EntityAutocompleteController::create($this->container);
$request = Request::create('entity_reference_autocomplete/' . $this->entityType . '/default');
$request->query->set('q', $this->randomString());
try {
// Pass an invalid selection settings key (i.e. one that does not exist
// in the key/value store).
$selection_settings_key = $this->randomString();
$entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key);
$this->fail('Non-existent selection settings key throws an exception.');
}
catch (AccessDeniedHttpException $e) {
$this->pass('Non-existent selection settings key throws an exception.');
}
try {
// Generate a valid hash key but store a modified settings array.
$selection_settings = [];
$selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $this->entityType . 'default', Settings::getHashSalt());
$selection_settings[$this->randomMachineName()] = $this->randomString();
\Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings);
$entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key);
}
catch (AccessDeniedHttpException $e) {
if ($e->getMessage() == 'Invalid selection settings key.') {
$this->pass('Invalid selection settings key throws an exception.');
}
else {
$this->fail('Invalid selection settings key throws an exception.');
}
}
}
/**
* Returns the result of an Entity reference autocomplete request.
*
* @param string $input
* The label of the entity to query by.
*
* @return mixed
* The JSON value encoded in its appropriate PHP type.
*/
protected function getAutocompleteResult($input) {
$request = Request::create('entity_reference_autocomplete/' . $this->entityType . '/default');
$request->query->set('q', $input);
$selection_settings = [];
$selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $this->entityType . 'default', Settings::getHashSalt());
\Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings);
$entity_reference_controller = EntityAutocompleteController::create($this->container);
$result = $entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key)->getContent();
return Json::decode($result);
}
}

View file

@ -0,0 +1,113 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
/**
* Tests adding a custom bundle field.
*
* @group Entity
*/
class EntityBundleFieldTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_schema_test');
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The database connection used.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('user', array('users_data'));
$this->moduleHandler = $this->container->get('module_handler');
$this->database = $this->container->get('database');
}
/**
* Tests making use of a custom bundle field.
*/
public function testCustomBundleFieldUsage() {
entity_test_create_bundle('custom');
// Check that an entity with bundle entity_test does not have the custom
// field.
$storage = $this->entityManager->getStorage('entity_test');
$entity = $storage->create([
'type' => 'entity_test',
]);
$this->assertFalse($entity->hasField('custom_bundle_field'));
// Check that the custom bundle has the defined custom field and check
// saving and deleting of custom field data.
$entity = $storage->create([
'type' => 'custom',
]);
$this->assertTrue($entity->hasField('custom_bundle_field'));
// Ensure that the field exists in the field map.
$field_map = \Drupal::entityManager()->getFieldMap();
$this->assertEqual($field_map['entity_test']['custom_bundle_field'], ['type' => 'string', 'bundles' => ['custom' => 'custom']]);
$entity->custom_bundle_field->value = 'swanky';
$entity->save();
$storage->resetCache();
$entity = $storage->load($entity->id());
$this->assertEqual($entity->custom_bundle_field->value, 'swanky', 'Entity was saved correctly');
$entity->custom_bundle_field->value = 'cozy';
$entity->save();
$storage->resetCache();
$entity = $storage->load($entity->id());
$this->assertEqual($entity->custom_bundle_field->value, 'cozy', 'Entity was updated correctly.');
$entity->delete();
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $storage->getTableMapping();
$table = $table_mapping->getDedicatedDataTableName($entity->getFieldDefinition('custom_bundle_field'));
$result = $this->database->select($table, 'f')
->fields('f')
->condition('f.entity_id', $entity->id())
->execute();
$this->assertFalse($result->fetchAssoc(), 'Field data has been deleted');
// Create another entity to test that values are marked as deleted when a
// bundle is deleted.
$entity = $storage->create(['type' => 'custom', 'custom_bundle_field' => 'new']);
$entity->save();
entity_test_delete_bundle('custom');
$table = $table_mapping->getDedicatedDataTableName($entity->getFieldDefinition('custom_bundle_field'));
$result = $this->database->select($table, 'f')
->condition('f.entity_id', $entity->id())
->condition('deleted', 1)
->countQuery()
->execute();
$this->assertEqual(1, $result->fetchField(), 'Field data has been deleted');
// Ensure that the field no longer exists in the field map.
$field_map = \Drupal::entityManager()->getFieldMap();
$this->assertFalse(isset($field_map['entity_test']['custom_bundle_field']));
// @todo Test field purge and table deletion once supported. See
// https://www.drupal.org/node/2282119.
// $this->assertFalse($this->database->schema()->tableExists($table), 'Custom field table was deleted');
}
}

View file

@ -0,0 +1,559 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Core\Database\Database;
use Drupal\Core\Language\LanguageInterface;
use Drupal\block\Entity\Block;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\node\Entity\NodeType;
use Drupal\taxonomy\Entity\Term;
use Drupal\node\Entity\Node;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\user\Entity\User;
use Drupal\file\Entity\File;
/**
* Tests the invocation of hooks when creating, inserting, loading, updating or
* deleting an entity.
*
* Tested hooks are:
* - hook_entity_insert() and hook_ENTITY_TYPE_insert()
* - hook_entity_load() and hook_ENTITY_TYPE_load()
* - hook_entity_update() and hook_ENTITY_TYPE_update()
* - hook_entity_predelete() and hook_ENTITY_TYPE_predelete()
* - hook_entity_delete() and hook_ENTITY_TYPE_delete()
*
* These hooks are each tested for several entity types.
*
* @group Entity
*/
class EntityCrudHookTest extends EntityKernelTestBase {
use CommentTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('block', 'block_test', 'entity_crud_hook_test', 'file', 'taxonomy', 'node', 'comment');
protected $ids = array();
protected function setUp() {
parent::setUp();
$this->installSchema('user', array('users_data'));
$this->installSchema('file', array('file_usage'));
$this->installSchema('node', array('node_access'));
$this->installSchema('comment', array('comment_entity_statistics'));
$this->installConfig(['node', 'comment']);
}
/**
* Checks the order of CRUD hook execution messages.
*
* entity_crud_hook_test.module implements all core entity CRUD hooks and
* stores a message for each in $GLOBALS['entity_crud_hook_test'].
*
* @param $messages
* An array of plain-text messages in the order they should appear.
*/
protected function assertHookMessageOrder($messages) {
$positions = array();
foreach ($messages as $message) {
// Verify that each message is found and record its position.
$position = array_search($message, $GLOBALS['entity_crud_hook_test']);
if ($this->assertTrue($position !== FALSE, $message)) {
$positions[] = $position;
}
}
// Sort the positions and ensure they remain in the same order.
$sorted = $positions;
sort($sorted);
$this->assertTrue($sorted == $positions, 'The hook messages appear in the correct order.');
}
/**
* Tests hook invocations for CRUD operations on blocks.
*/
public function testBlockHooks() {
$entity = Block::create(array(
'id' => 'stark_test_html',
'plugin' => 'test_html',
'theme' => 'stark',
));
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_block_create called',
'entity_crud_hook_test_entity_create called for type block',
));
$GLOBALS['entity_crud_hook_test'] = array();
$entity->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_block_presave called',
'entity_crud_hook_test_entity_presave called for type block',
'entity_crud_hook_test_block_insert called',
'entity_crud_hook_test_entity_insert called for type block',
));
$GLOBALS['entity_crud_hook_test'] = array();
$entity = Block::load($entity->id());
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_entity_load called for type block',
'entity_crud_hook_test_block_load called',
));
$GLOBALS['entity_crud_hook_test'] = array();
$entity->label = 'New label';
$entity->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_block_presave called',
'entity_crud_hook_test_entity_presave called for type block',
'entity_crud_hook_test_block_update called',
'entity_crud_hook_test_entity_update called for type block',
));
$GLOBALS['entity_crud_hook_test'] = array();
$entity->delete();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_block_predelete called',
'entity_crud_hook_test_entity_predelete called for type block',
'entity_crud_hook_test_block_delete called',
'entity_crud_hook_test_entity_delete called for type block',
));
}
/**
* Tests hook invocations for CRUD operations on comments.
*/
public function testCommentHooks() {
$account = $this->createUser();
NodeType::create([
'type' => 'article',
'name' => 'Article',
])->save();
$this->addDefaultCommentField('node', 'article', 'comment', CommentItemInterface::OPEN);
$node = Node::create([
'uid' => $account->id(),
'type' => 'article',
'title' => 'Test node',
'status' => 1,
'promote' => 0,
'sticky' => 0,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
]);
$node->save();
$nid = $node->id();
$GLOBALS['entity_crud_hook_test'] = array();
$comment = Comment::create(array(
'cid' => NULL,
'pid' => 0,
'entity_id' => $nid,
'entity_type' => 'node',
'field_name' => 'comment',
'uid' => $account->id(),
'subject' => 'Test comment',
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
'status' => 1,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_comment_create called',
'entity_crud_hook_test_entity_create called for type comment',
));
$GLOBALS['entity_crud_hook_test'] = array();
$comment->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_comment_presave called',
'entity_crud_hook_test_entity_presave called for type comment',
'entity_crud_hook_test_comment_insert called',
'entity_crud_hook_test_entity_insert called for type comment',
));
$GLOBALS['entity_crud_hook_test'] = array();
$comment = Comment::load($comment->id());
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_entity_load called for type comment',
'entity_crud_hook_test_comment_load called',
));
$GLOBALS['entity_crud_hook_test'] = array();
$comment->setSubject('New subject');
$comment->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_comment_presave called',
'entity_crud_hook_test_entity_presave called for type comment',
'entity_crud_hook_test_comment_update called',
'entity_crud_hook_test_entity_update called for type comment',
));
$GLOBALS['entity_crud_hook_test'] = array();
$comment->delete();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_comment_predelete called',
'entity_crud_hook_test_entity_predelete called for type comment',
'entity_crud_hook_test_comment_delete called',
'entity_crud_hook_test_entity_delete called for type comment',
));
}
/**
* Tests hook invocations for CRUD operations on files.
*/
public function testFileHooks() {
$this->installEntitySchema('file');
$url = 'public://entity_crud_hook_test.file';
file_put_contents($url, 'Test test test');
$file = File::create([
'fid' => NULL,
'uid' => 1,
'filename' => 'entity_crud_hook_test.file',
'uri' => $url,
'filemime' => 'text/plain',
'filesize' => filesize($url),
'status' => 1,
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
]);
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_file_create called',
'entity_crud_hook_test_entity_create called for type file',
));
$GLOBALS['entity_crud_hook_test'] = array();
$file->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_file_presave called',
'entity_crud_hook_test_entity_presave called for type file',
'entity_crud_hook_test_file_insert called',
'entity_crud_hook_test_entity_insert called for type file',
));
$GLOBALS['entity_crud_hook_test'] = array();
$file = File::load($file->id());
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_entity_load called for type file',
'entity_crud_hook_test_file_load called',
));
$GLOBALS['entity_crud_hook_test'] = array();
$file->setFilename('new.entity_crud_hook_test.file');
$file->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_file_presave called',
'entity_crud_hook_test_entity_presave called for type file',
'entity_crud_hook_test_file_update called',
'entity_crud_hook_test_entity_update called for type file',
));
$GLOBALS['entity_crud_hook_test'] = array();
$file->delete();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_file_predelete called',
'entity_crud_hook_test_entity_predelete called for type file',
'entity_crud_hook_test_file_delete called',
'entity_crud_hook_test_entity_delete called for type file',
));
}
/**
* Tests hook invocations for CRUD operations on nodes.
*/
public function testNodeHooks() {
$account = $this->createUser();
$node = Node::create([
'uid' => $account->id(),
'type' => 'article',
'title' => 'Test node',
'status' => 1,
'promote' => 0,
'sticky' => 0,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
]);
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_node_create called',
'entity_crud_hook_test_entity_create called for type node',
));
$GLOBALS['entity_crud_hook_test'] = array();
$node->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_node_presave called',
'entity_crud_hook_test_entity_presave called for type node',
'entity_crud_hook_test_node_insert called',
'entity_crud_hook_test_entity_insert called for type node',
));
$GLOBALS['entity_crud_hook_test'] = array();
$node = Node::load($node->id());
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_entity_load called for type node',
'entity_crud_hook_test_node_load called',
));
$GLOBALS['entity_crud_hook_test'] = array();
$node->title = 'New title';
$node->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_node_presave called',
'entity_crud_hook_test_entity_presave called for type node',
'entity_crud_hook_test_node_update called',
'entity_crud_hook_test_entity_update called for type node',
));
$GLOBALS['entity_crud_hook_test'] = array();
$node->delete();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_node_predelete called',
'entity_crud_hook_test_entity_predelete called for type node',
'entity_crud_hook_test_node_delete called',
'entity_crud_hook_test_entity_delete called for type node',
));
}
/**
* Tests hook invocations for CRUD operations on taxonomy terms.
*/
public function testTaxonomyTermHooks() {
$this->installEntitySchema('taxonomy_term');
$vocabulary = Vocabulary::create([
'name' => 'Test vocabulary',
'vid' => 'test',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'description' => NULL,
'module' => 'entity_crud_hook_test',
]);
$vocabulary->save();
$GLOBALS['entity_crud_hook_test'] = array();
$term = Term::create([
'vid' => $vocabulary->id(),
'name' => 'Test term',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'description' => NULL,
'format' => 1,
]);
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_taxonomy_term_create called',
'entity_crud_hook_test_entity_create called for type taxonomy_term',
));
$GLOBALS['entity_crud_hook_test'] = array();
$term->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_taxonomy_term_presave called',
'entity_crud_hook_test_entity_presave called for type taxonomy_term',
'entity_crud_hook_test_taxonomy_term_insert called',
'entity_crud_hook_test_entity_insert called for type taxonomy_term',
));
$GLOBALS['entity_crud_hook_test'] = array();
$term = Term::load($term->id());
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_entity_load called for type taxonomy_term',
'entity_crud_hook_test_taxonomy_term_load called',
));
$GLOBALS['entity_crud_hook_test'] = array();
$term->setName('New name');
$term->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_taxonomy_term_presave called',
'entity_crud_hook_test_entity_presave called for type taxonomy_term',
'entity_crud_hook_test_taxonomy_term_update called',
'entity_crud_hook_test_entity_update called for type taxonomy_term',
));
$GLOBALS['entity_crud_hook_test'] = array();
$term->delete();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_taxonomy_term_predelete called',
'entity_crud_hook_test_entity_predelete called for type taxonomy_term',
'entity_crud_hook_test_taxonomy_term_delete called',
'entity_crud_hook_test_entity_delete called for type taxonomy_term',
));
}
/**
* Tests hook invocations for CRUD operations on taxonomy vocabularies.
*/
public function testTaxonomyVocabularyHooks() {
$this->installEntitySchema('taxonomy_term');
$vocabulary = Vocabulary::create([
'name' => 'Test vocabulary',
'vid' => 'test',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'description' => NULL,
'module' => 'entity_crud_hook_test',
]);
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_taxonomy_vocabulary_create called',
'entity_crud_hook_test_entity_create called for type taxonomy_vocabulary',
));
$GLOBALS['entity_crud_hook_test'] = array();
$vocabulary->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_taxonomy_vocabulary_presave called',
'entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary',
'entity_crud_hook_test_taxonomy_vocabulary_insert called',
'entity_crud_hook_test_entity_insert called for type taxonomy_vocabulary',
));
$GLOBALS['entity_crud_hook_test'] = array();
$vocabulary = Vocabulary::load($vocabulary->id());
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_entity_load called for type taxonomy_vocabulary',
'entity_crud_hook_test_taxonomy_vocabulary_load called',
));
$GLOBALS['entity_crud_hook_test'] = array();
$vocabulary->set('name', 'New name');
$vocabulary->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_taxonomy_vocabulary_presave called',
'entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary',
'entity_crud_hook_test_taxonomy_vocabulary_update called',
'entity_crud_hook_test_entity_update called for type taxonomy_vocabulary',
));
$GLOBALS['entity_crud_hook_test'] = array();
$vocabulary->delete();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_taxonomy_vocabulary_predelete called',
'entity_crud_hook_test_entity_predelete called for type taxonomy_vocabulary',
'entity_crud_hook_test_taxonomy_vocabulary_delete called',
'entity_crud_hook_test_entity_delete called for type taxonomy_vocabulary',
));
}
/**
* Tests hook invocations for CRUD operations on users.
*/
public function testUserHooks() {
$account = User::create([
'name' => 'Test user',
'mail' => 'test@example.com',
'created' => REQUEST_TIME,
'status' => 1,
'language' => 'en',
]);
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_user_create called',
'entity_crud_hook_test_entity_create called for type user',
));
$GLOBALS['entity_crud_hook_test'] = array();
$account->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_user_presave called',
'entity_crud_hook_test_entity_presave called for type user',
'entity_crud_hook_test_user_insert called',
'entity_crud_hook_test_entity_insert called for type user',
));
$GLOBALS['entity_crud_hook_test'] = array();
User::load($account->id());
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_entity_load called for type user',
'entity_crud_hook_test_user_load called',
));
$GLOBALS['entity_crud_hook_test'] = array();
$account->name = 'New name';
$account->save();
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_user_presave called',
'entity_crud_hook_test_entity_presave called for type user',
'entity_crud_hook_test_user_update called',
'entity_crud_hook_test_entity_update called for type user',
));
$GLOBALS['entity_crud_hook_test'] = array();
user_delete($account->id());
$this->assertHookMessageOrder(array(
'entity_crud_hook_test_user_predelete called',
'entity_crud_hook_test_entity_predelete called for type user',
'entity_crud_hook_test_user_delete called',
'entity_crud_hook_test_entity_delete called for type user',
));
}
/**
* Tests rollback from failed entity save.
*/
function testEntityRollback() {
// Create a block.
try {
EntityTest::create(array('name' => 'fail_insert'))->save();
$this->fail('Expected exception has not been thrown.');
}
catch (\Exception $e) {
$this->pass('Expected exception has been thrown.');
}
if (Database::getConnection()->supportsTransactions()) {
// Check that the block does not exist in the database.
$ids = \Drupal::entityQuery('entity_test')->condition('name', 'fail_insert')->execute();
$this->assertTrue(empty($ids), 'Transactions supported, and entity not found in database.');
}
else {
// Check that the block exists in the database.
$ids = \Drupal::entityQuery('entity_test')->condition('name', 'fail_insert')->execute();
$this->assertFalse(empty($ids), 'Transactions not supported, and entity found in database.');
}
}
}

View file

@ -0,0 +1,809 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\IntegrityConstraintViolationException;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeEvents;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionEvents;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\Entity\EntityTestUpdate;
use Drupal\system\Tests\Entity\EntityDefinitionTestTrait;
/**
* Tests EntityDefinitionUpdateManager functionality.
*
* @group Entity
*/
class EntityDefinitionUpdateTest extends EntityKernelTestBase {
use EntityDefinitionTestTrait;
/**
* The entity definition update manager.
*
* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
*/
protected $entityDefinitionUpdateManager;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager');
$this->database = $this->container->get('database');
// Install every entity type's schema that wasn't installed in the parent
// method.
foreach (array_diff_key($this->entityManager->getDefinitions(), array_flip(array('user', 'entity_test'))) as $entity_type_id => $entity_type) {
$this->installEntitySchema($entity_type_id);
}
}
/**
* Tests that new entity type definitions are correctly handled.
*/
public function testNewEntityType() {
$entity_type_id = 'entity_test_new';
$schema = $this->database->schema();
// Check that the "entity_test_new" is not defined.
$entity_types = $this->entityManager->getDefinitions();
$this->assertFalse(isset($entity_types[$entity_type_id]), 'The "entity_test_new" entity type does not exist.');
$this->assertFalse($schema->tableExists($entity_type_id), 'Schema for the "entity_test_new" entity type does not exist.');
// Check that the "entity_test_new" is now defined and the related schema
// has been created.
$this->enableNewEntityType();
$entity_types = $this->entityManager->getDefinitions();
$this->assertTrue(isset($entity_types[$entity_type_id]), 'The "entity_test_new" entity type exists.');
$this->assertTrue($schema->tableExists($entity_type_id), 'Schema for the "entity_test_new" entity type has been created.');
}
/**
* Tests when no definition update is needed.
*/
public function testNoUpdates() {
// Ensure that the definition update manager reports no updates.
$this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that no updates are needed.');
$this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), array(), 'EntityDefinitionUpdateManager reports an empty change summary.');
// Ensure that applyUpdates() runs without error (it's not expected to do
// anything when there aren't updates).
$this->entityDefinitionUpdateManager->applyUpdates();
}
/**
* Tests updating entity schema when there are no existing entities.
*/
public function testEntityTypeUpdateWithoutData() {
// The 'entity_test_update' entity type starts out non-revisionable, so
// ensure the revision table hasn't been created during setUp().
$this->assertFalse($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table not created for entity_test_update.');
// Update it to be revisionable and ensure the definition update manager
// reports that an update is needed.
$this->updateEntityTypeToRevisionable();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.');
// Run the update and ensure the revision table is created.
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table created for entity_test_update.');
}
/**
* Tests updating entity schema when there are existing entities.
*/
public function testEntityTypeUpdateWithData() {
// Save an entity.
$this->entityManager->getStorage('entity_test_update')->create()->save();
// Update the entity type to be revisionable and try to apply the update.
// It's expected to throw an exception.
$this->updateEntityTypeToRevisionable();
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->fail('EntityStorageException thrown when trying to apply an update that requires data migration.');
}
catch (EntityStorageException $e) {
$this->pass('EntityStorageException thrown when trying to apply an update that requires data migration.');
}
}
/**
* Tests creating, updating, and deleting a base field if no entities exist.
*/
public function testBaseFieldCreateUpdateDeleteWithoutData() {
// Add a base field, ensure the update manager reports it, and the update
// creates its schema.
$this->addBaseField();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %field_name field needs to be installed.', ['%field_name' => t('A new base field')]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
// Add an index on the base field, ensure the update manager reports it,
// and the update creates it.
$this->addBaseFieldIndex();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index created.');
// Remove the above index, ensure the update manager reports it, and the
// update deletes it.
$this->removeBaseFieldIndex();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index deleted.');
// Update the type of the base field from 'string' to 'text', ensure the
// update manager reports it, and the update adjusts the schema
// accordingly.
$this->modifyBaseField();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Original column deleted in shared table for new_base_field.');
$this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__value'), 'Value column created in shared table for new_base_field.');
$this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__format'), 'Format column created in shared table for new_base_field.');
// Remove the base field, ensure the update manager reports it, and the
// update deletes the schema.
$this->removeBaseField();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %field_name field needs to be uninstalled.', ['%field_name' => t('A new base field')]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_value'), 'Value column deleted from shared table for new_base_field.');
$this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_format'), 'Format column deleted from shared table for new_base_field.');
}
/**
* Tests creating, updating, and deleting a bundle field if no entities exist.
*/
public function testBundleFieldCreateUpdateDeleteWithoutData() {
// Add a bundle field, ensure the update manager reports it, and the update
// creates its schema.
$this->addBundleField();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %field_name field needs to be installed.', ['%field_name' => t('A new bundle field')]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
// Update the type of the base field from 'string' to 'text', ensure the
// update manager reports it, and the update adjusts the schema
// accordingly.
$this->modifyBundleField();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => [
t('The %field_name field needs to be updated.', ['%field_name' => t('A new bundle field')]),
],
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->fieldExists('entity_test_update__new_bundle_field', 'new_bundle_field_format'), 'Format column created in dedicated table for new_base_field.');
// Remove the bundle field, ensure the update manager reports it, and the
// update deletes the schema.
$this->removeBundleField();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %field_name field needs to be uninstalled.', ['%field_name' => t('A new bundle field')]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
}
/**
* Tests creating and deleting a base field if entities exist.
*
* This tests deletion when there are existing entities, but not existing data
* for the field being deleted.
*
* @see testBaseFieldDeleteWithExistingData()
*/
public function testBaseFieldCreateDeleteWithExistingEntities() {
// Save an entity.
$name = $this->randomString();
$storage = $this->entityManager->getStorage('entity_test_update');
$entity = $storage->create(array('name' => $name));
$entity->save();
// Add a base field and run the update. Ensure the base field's column is
// created and the prior saved entity data is still there.
$this->addBaseField();
$this->entityDefinitionUpdateManager->applyUpdates();
$schema_handler = $this->database->schema();
$this->assertTrue($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
$entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
$this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
// Remove the base field and run the update. Ensure the base field's column
// is deleted and the prior saved entity data is still there.
$this->removeBaseField();
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertFalse($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.');
$entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
$this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
// Add a base field with a required property and run the update. Ensure
// 'not null' is not applied and thus no exception is thrown.
$this->addBaseField('shape_required');
$this->entityDefinitionUpdateManager->applyUpdates();
$assert = $schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && $schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
$this->assertTrue($assert, 'Columns created in shared table for new_base_field.');
// Recreate the field after emptying the base table and check that its
// columns are not 'not null'.
// @todo Revisit this test when allowing for required storage field
// definitions. See https://www.drupal.org/node/2390495.
$entity->delete();
$this->removeBaseField();
$this->entityDefinitionUpdateManager->applyUpdates();
$assert = !$schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && !$schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
$this->assert($assert, 'Columns removed from the shared table for new_base_field.');
$this->addBaseField('shape_required');
$this->entityDefinitionUpdateManager->applyUpdates();
$assert = $schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && $schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
$this->assertTrue($assert, 'Columns created again in shared table for new_base_field.');
$entity = $storage->create(array('name' => $name));
$entity->save();
$this->pass('The new_base_field columns are still nullable');
}
/**
* Tests creating and deleting a bundle field if entities exist.
*
* This tests deletion when there are existing entities, but not existing data
* for the field being deleted.
*
* @see testBundleFieldDeleteWithExistingData()
*/
public function testBundleFieldCreateDeleteWithExistingEntities() {
// Save an entity.
$name = $this->randomString();
$storage = $this->entityManager->getStorage('entity_test_update');
$entity = $storage->create(array('name' => $name));
$entity->save();
// Add a bundle field and run the update. Ensure the bundle field's table
// is created and the prior saved entity data is still there.
$this->addBundleField();
$this->entityDefinitionUpdateManager->applyUpdates();
$schema_handler = $this->database->schema();
$this->assertTrue($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
$entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
$this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
// Remove the base field and run the update. Ensure the bundle field's
// table is deleted and the prior saved entity data is still there.
$this->removeBundleField();
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertFalse($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
$entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
$this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
// Test that required columns are created as 'not null'.
$this->addBundleField('shape_required');
$this->entityDefinitionUpdateManager->applyUpdates();
$message = 'The new_bundle_field_shape column is not nullable.';
$values = array(
'bundle' => $entity->bundle(),
'deleted'=> 0,
'entity_id' => $entity->id(),
'revision_id' => $entity->id(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'delta' => 0,
'new_bundle_field_color' => $this->randomString(),
);
try {
// Try to insert a record without providing a value for the 'not null'
// column. This should fail.
$this->database->insert('entity_test_update__new_bundle_field')
->fields($values)
->execute();
$this->fail($message);
}
catch (\RuntimeException $e) {
if ($e instanceof DatabaseExceptionWrapper || $e instanceof IntegrityConstraintViolationException) {
// Now provide a value for the 'not null' column. This is expected to
// succeed.
$values['new_bundle_field_shape'] = $this->randomString();
$this->database->insert('entity_test_update__new_bundle_field')
->fields($values)
->execute();
$this->pass($message);
} else {
// Keep throwing it.
throw $e;
}
}
}
/**
* Tests deleting a base field when it has existing data.
*/
public function testBaseFieldDeleteWithExistingData() {
// Add the base field and run the update.
$this->addBaseField();
$this->entityDefinitionUpdateManager->applyUpdates();
// Save an entity with the base field populated.
$this->entityManager->getStorage('entity_test_update')->create(array('new_base_field' => 'foo'))->save();
// Remove the base field and apply updates. It's expected to throw an
// exception.
// @todo Revisit that expectation once purging is implemented for
// all fields: https://www.drupal.org/node/2282119.
$this->removeBaseField();
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
}
}
/**
* Tests deleting a bundle field when it has existing data.
*/
public function testBundleFieldDeleteWithExistingData() {
// Add the bundle field and run the update.
$this->addBundleField();
$this->entityDefinitionUpdateManager->applyUpdates();
// Save an entity with the bundle field populated.
entity_test_create_bundle('custom');
$this->entityManager->getStorage('entity_test_update')->create(array('type' => 'test_bundle', 'new_bundle_field' => 'foo'))->save();
// Remove the bundle field and apply updates. It's expected to throw an
// exception.
// @todo Revisit that expectation once purging is implemented for
// all fields: https://www.drupal.org/node/2282119.
$this->removeBundleField();
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
}
}
/**
* Tests updating a base field when it has existing data.
*/
public function testBaseFieldUpdateWithExistingData() {
// Add the base field and run the update.
$this->addBaseField();
$this->entityDefinitionUpdateManager->applyUpdates();
// Save an entity with the base field populated.
$this->entityManager->getStorage('entity_test_update')->create(array('new_base_field' => 'foo'))->save();
// Change the field's field type and apply updates. It's expected to
// throw an exception.
$this->modifyBaseField();
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
}
}
/**
* Tests updating a bundle field when it has existing data.
*/
public function testBundleFieldUpdateWithExistingData() {
// Add the bundle field and run the update.
$this->addBundleField();
$this->entityDefinitionUpdateManager->applyUpdates();
// Save an entity with the bundle field populated.
entity_test_create_bundle('custom');
$this->entityManager->getStorage('entity_test_update')->create(array('type' => 'test_bundle', 'new_bundle_field' => 'foo'))->save();
// Change the field's field type and apply updates. It's expected to
// throw an exception.
$this->modifyBundleField();
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
}
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
}
}
/**
* Tests creating and deleting a multi-field index when there are no existing entities.
*/
public function testEntityIndexCreateDeleteWithoutData() {
// Add an entity index and ensure the update manager reports that as an
// update to the entity type.
$this->addEntityIndex();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
// Run the update and ensure the new index is created.
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
// Remove the index and ensure the update manager reports that as an
// update to the entity type.
$this->removeEntityIndex();
$this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
$expected = array(
'entity_test_update' => array(
t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
),
);
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
// Run the update and ensure the index is deleted.
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
// Test that composite indexes are handled correctly when dropping and
// re-creating one of their columns.
$this->addEntityIndex();
$this->entityDefinitionUpdateManager->applyUpdates();
$storage_definition = $this->entityDefinitionUpdateManager->getFieldStorageDefinition('name', 'entity_test_update');
$this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
$this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
$this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('name', 'entity_test_update', 'entity_test', $storage_definition);
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created again.');
}
/**
* Tests creating a multi-field index when there are existing entities.
*/
public function testEntityIndexCreateWithData() {
// Save an entity.
$name = $this->randomString();
$entity = $this->entityManager->getStorage('entity_test_update')->create(array('name' => $name));
$entity->save();
// Add an entity index, run the update. Ensure that the index is created
// despite having data.
$this->addEntityIndex();
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index added.');
}
/**
* Tests entity type and field storage definition events.
*/
public function testDefinitionEvents() {
/** @var \Drupal\entity_test\EntityTestDefinitionSubscriber $event_subscriber */
$event_subscriber = $this->container->get('entity_test.definition.subscriber');
$event_subscriber->enableEventTracking();
// Test field storage definition events.
$storage_definition = current($this->entityManager->getFieldStorageDefinitions('entity_test_rev'));
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
$this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create was not dispatched yet.');
$this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.');
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.');
$this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.');
// Test entity type events.
$entity_type = $this->entityManager->getDefinition('entity_test_rev');
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create was not dispatched yet.');
$this->entityManager->onEntityTypeCreate($entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create event successfully dispatched.');
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update was not dispatched yet.');
$this->entityManager->onEntityTypeUpdate($entity_type, $entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.');
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.');
$this->entityManager->onEntityTypeDelete($entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.');
}
/**
* Tests updating entity schema and creating a base field.
*
* This tests updating entity schema and creating a base field at the same
* time when there are no existing entities.
*/
public function testEntityTypeSchemaUpdateAndBaseFieldCreateWithoutData() {
$this->updateEntityTypeToRevisionable();
$this->addBaseField();
$message = 'Successfully updated entity schema and created base field at the same time.';
// Entity type updates create base fields as well, thus make sure doing both
// at the same time does not lead to errors due to the base field being
// created twice.
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->pass($message);
}
catch (\Exception $e) {
$this->fail($message);
throw $e;
}
}
/**
* Tests updating entity schema and creating a revisionable base field.
*
* This tests updating entity schema and creating a revisionable base field
* at the same time when there are no existing entities.
*/
public function testEntityTypeSchemaUpdateAndRevisionableBaseFieldCreateWithoutData() {
$this->updateEntityTypeToRevisionable();
$this->addRevisionableBaseField();
$message = 'Successfully updated entity schema and created revisionable base field at the same time.';
// Entity type updates create base fields as well, thus make sure doing both
// at the same time does not lead to errors due to the base field being
// created twice.
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->pass($message);
}
catch (\Exception $e) {
$this->fail($message);
throw $e;
}
}
/**
* Tests applying single updates.
*/
public function testSingleActionCalls() {
$db_schema = $this->database->schema();
// Ensure that a non-existing entity type cannot be installed.
$message = 'A non-existing entity type cannot be installed';
try {
$this->entityDefinitionUpdateManager->installEntityType(new ContentEntityType(['id' => 'foo']));
$this->fail($message);
}
catch (PluginNotFoundException $e) {
$this->pass($message);
}
// Ensure that a field cannot be installed on non-existing entity type.
$message = 'A field cannot be installed on a non-existing entity type';
try {
$storage_definition = BaseFieldDefinition::create('string')
->setLabel(t('A new revisionable base field'))
->setRevisionable(TRUE);
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'foo', 'entity_test', $storage_definition);
$this->fail($message);
}
catch (PluginNotFoundException $e) {
$this->pass($message);
}
// Ensure that a non-existing field cannot be installed.
$storage_definition = BaseFieldDefinition::create('string')
->setLabel(t('A new revisionable base field'))
->setRevisionable(TRUE);
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'entity_test_update', 'entity_test', $storage_definition);
$this->assertFalse($db_schema->fieldExists('entity_test_update', 'bar'), "A non-existing field cannot be installed.");
// Ensure that installing an existing entity type is a no-op.
$entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
$this->entityDefinitionUpdateManager->installEntityType($entity_type);
$this->assertTrue($db_schema->tableExists('entity_test_update'), 'Installing an existing entity type is a no-op');
// Create a new base field.
$this->addRevisionableBaseField();
$storage_definition = BaseFieldDefinition::create('string')
->setLabel(t('A new revisionable base field'))
->setRevisionable(TRUE);
$this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
$this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
// Ensure that installing an existing field is a no-op.
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
$this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), 'Installing an existing field is a no-op');
// Update an existing field schema.
$this->modifyBaseField();
$storage_definition = BaseFieldDefinition::create('text')
->setName('new_base_field')
->setTargetEntityTypeId('entity_test_update')
->setLabel(t('A new revisionable base field'))
->setRevisionable(TRUE);
$this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
$this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "Previous schema for 'new_base_field' no longer exists.");
$this->assertTrue(
$db_schema->fieldExists('entity_test_update', 'new_base_field__value') && $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
"New schema for 'new_base_field' has been created."
);
// Drop an existing field schema.
$this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
$this->assertFalse(
$db_schema->fieldExists('entity_test_update', 'new_base_field__value') || $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
"The schema for 'new_base_field' has been dropped."
);
// Make the entity type revisionable.
$this->updateEntityTypeToRevisionable();
$this->assertFalse($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' does not exist before applying the update.");
$entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
$keys = $entity_type->getKeys();
$keys['revision'] = 'revision_id';
$entity_type->set('entity_keys', $keys);
$this->entityDefinitionUpdateManager->updateEntityType($entity_type);
$this->assertTrue($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' table has been created.");
}
/**
* Ensures that a new field and index on a shared table are created.
*
* @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::createSharedTableSchema
*/
public function testCreateFieldAndIndexOnSharedTable() {
$this->addBaseField();
$this->addBaseFieldIndex();
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), "New index 'entity_test_update_field__new_base_field' has been created on the 'entity_test_update' table.");
// Check index size in for MySQL.
if (Database::getConnection()->driver() == 'mysql') {
$result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update_field__new_base_field\' and column_name = \'new_base_field\'')->fetchObject();
$this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
}
}
/**
* Ensures that a new entity level index is created when data exists.
*
* @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::onEntityTypeUpdate
*/
public function testCreateIndexUsingEntityStorageSchemaWithData() {
// Save an entity.
$name = $this->randomString();
$storage = $this->entityManager->getStorage('entity_test_update');
$entity = $storage->create(array('name' => $name));
$entity->save();
// Create an index.
$indexes = array(
'entity_test_update__type_index' => array('type'),
);
$this->state->set('entity_test_update.additional_entity_indexes', $indexes);
$this->entityDefinitionUpdateManager->applyUpdates();
$this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__type_index'), "New index 'entity_test_update__type_index' has been created on the 'entity_test_update' table.");
// Check index size in for MySQL.
if (Database::getConnection()->driver() == 'mysql') {
$result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update__type_index\' and column_name = \'type\'')->fetchObject();
$this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
}
}
/**
* Tests updating a base field when it has existing data.
*/
public function testBaseFieldEntityKeyUpdateWithExistingData() {
// Add the base field and run the update.
$this->addBaseField();
$this->entityDefinitionUpdateManager->applyUpdates();
// Save an entity with the base field populated.
$this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => $this->randomString()])->save();
// Save an entity with the base field not populated.
/** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
$entity = $this->entityManager->getStorage('entity_test_update')->create();
$entity->save();
// Promote the base field to an entity key. This will trigger the addition
// of a NOT NULL constraint.
$this->makeBaseFieldEntityKey();
// Try to apply the update and verify they fail since we have a NULL value.
$message = 'An error occurs when trying to enabling NOT NULL constraints with NULL data.';
try {
$this->entityDefinitionUpdateManager->applyUpdates();
$this->fail($message);
}
catch (EntityStorageException $e) {
$this->pass($message);
}
// Check that the update is correctly applied when no NULL data is left.
$entity->set('new_base_field', $this->randomString());
$entity->save();
$this->entityDefinitionUpdateManager->applyUpdates();
$this->pass('The update is correctly performed when no NULL data exists.');
// Check that the update actually applied a NOT NULL constraint.
$entity->set('new_base_field', NULL);
$message = 'The NOT NULL constraint was correctly applied.';
try {
$entity->save();
$this->fail($message);
}
catch (EntityStorageException $e) {
$this->pass($message);
}
}
/**
* Check that field schema is correctly handled with long-named fields.
*/
function testLongNameFieldIndexes() {
$this->addLongNameBaseField();
$entity_type_id = 'entity_test_update';
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$definitions = EntityTestUpdate::baseFieldDefinitions($entity_type);
$name = 'new_long_named_entity_reference_base_field';
$this->entityDefinitionUpdateManager->installFieldStorageDefinition($name, $entity_type_id, 'entity_test', $definitions[$name]);
$this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'Entity and field schema data are correctly detected.');
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Component\Uuid\Uuid;
use Drupal\Component\Utility\SafeMarkup;
/**
* Tests default values for entity fields.
*
* @group Entity
*/
class EntityFieldDefaultValueTest extends EntityKernelTestBase {
/**
* The UUID object to be used for generating UUIDs.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuid;
protected function setUp() {
parent::setUp();
// Initiate the generator object.
$this->uuid = $this->container->get('uuid');
}
/**
* Tests default values on entities and fields.
*/
public function testDefaultValues() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->assertDefaultValues($entity_type);
}
}
/**
* Executes a test set for a defined entity type.
*
* @param string $entity_type_id
* The entity type to run the tests with.
*/
protected function assertDefaultValues($entity_type_id) {
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type_id)
->create();
$definition = $this->entityManager->getDefinition($entity_type_id);
$langcode_key = $definition->getKey('langcode');
$this->assertEqual($entity->{$langcode_key}->value, 'en', SafeMarkup::format('%entity_type: Default language', array('%entity_type' => $entity_type_id)));
$this->assertTrue(Uuid::isValid($entity->uuid->value), SafeMarkup::format('%entity_type: Default UUID', array('%entity_type' => $entity_type_id)));
$this->assertEqual($entity->name->getValue(), array(), 'Field has one empty value by default.');
}
/**
* Tests custom default value callbacks.
*/
public function testDefaultValueCallback() {
$entity = $this->entityManager->getStorage('entity_test_default_value')->create();
// The description field has a default value callback for testing, see
// entity_test_field_default_value().
$string = 'description_' . $entity->language()->getId();
$expected = array(
array(
'shape' => "shape:0:$string",
'color' => "color:0:$string",
),
array(
'shape' => "shape:1:$string",
'color' => "color:1:$string",
),
);
$this->assertEqual($entity->description->getValue(), $expected);
}
}

View file

@ -0,0 +1,747 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\Type\StringInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Tests the Entity Field API.
*
* @group Entity
*/
class EntityFieldTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('filter', 'text', 'node', 'user', 'field_test');
/**
* @var string
*/
protected $entityName;
/**
* @var \Drupal\user\Entity\User
*/
protected $entityUser;
/**
* @var string
*/
protected $entityFieldText;
protected function setUp() {
parent::setUp();
foreach (entity_test_entity_types() as $entity_type_id) {
// The entity_test schema is installed by the parent.
if ($entity_type_id != 'entity_test') {
$this->installEntitySchema($entity_type_id);
}
}
// Create the test field.
module_load_install('entity_test');
entity_test_install();
// Install required default configuration for filter module.
$this->installConfig(array('system', 'filter'));
}
/**
* Creates a test entity.
*
* @return \Drupal\Core\Entity\EntityInterface
*/
protected function createTestEntity($entity_type) {
$this->entityName = $this->randomMachineName();
$this->entityUser = $this->createUser();
$this->entityFieldText = $this->randomMachineName();
// Pass in the value of the name field when creating. With the user
// field we test setting a field after creation.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create();
$entity->user_id->target_id = $this->entityUser->id();
$entity->name->value = $this->entityName;
// Set a value for the test field.
$entity->field_test_text->value = $this->entityFieldText;
return $entity;
}
/**
* Tests reading and writing properties and field items.
*/
public function testReadWrite() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->doTestReadWrite($entity_type);
}
}
/**
* Executes the read write test set for a defined entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestReadWrite($entity_type) {
$entity = $this->createTestEntity($entity_type);
$langcode = 'en';
// Access the name field.
$this->assertTrue($entity->name instanceof FieldItemListInterface, format_string('%entity_type: Field implements interface', array('%entity_type' => $entity_type)));
$this->assertTrue($entity->name[0] instanceof FieldItemInterface, format_string('%entity_type: Field item implements interface', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityName, $entity->name->value, format_string('%entity_type: Name value can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityName, $entity->name[0]->value, format_string('%entity_type: Name value can be read through list access.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name->getValue(), array(0 => array('value' => $this->entityName)), format_string('%entity_type: Plain field value returned.', array('%entity_type' => $entity_type)));
// Change the name.
$new_name = $this->randomMachineName();
$entity->name->value = $new_name;
$this->assertEqual($new_name, $entity->name->value, format_string('%entity_type: Name can be updated and read.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name->getValue(), array(0 => array('value' => $new_name)), format_string('%entity_type: Plain field value reflects the update.', array('%entity_type' => $entity_type)));
$new_name = $this->randomMachineName();
$entity->name[0]->value = $new_name;
$this->assertEqual($new_name, $entity->name->value, format_string('%entity_type: Name can be updated and read through list access.', array('%entity_type' => $entity_type)));
// Access the user field.
$this->assertTrue($entity->user_id instanceof FieldItemListInterface, format_string('%entity_type: Field implements interface', array('%entity_type' => $entity_type)));
$this->assertTrue($entity->user_id[0] instanceof FieldItemInterface, format_string('%entity_type: Field item implements interface', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityUser->id(), $entity->user_id->target_id, format_string('%entity_type: User id can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityUser->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: User name can be read.', array('%entity_type' => $entity_type)));
// Change the assigned user by entity.
$new_user1 = $this->createUser();
$entity->user_id->entity = $new_user1;
$this->assertEqual($new_user1->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($new_user1->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated username value can be read.', array('%entity_type' => $entity_type)));
// Change the assigned user by id.
$new_user2 = $this->createUser();
$entity->user_id->target_id = $new_user2->id();
$this->assertEqual($new_user2->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($new_user2->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated username value can be read.', array('%entity_type' => $entity_type)));
// Try unsetting a field property.
$entity->name->value = NULL;
$entity->user_id->target_id = NULL;
$this->assertNull($entity->name->value, format_string('%entity_type: Name field is not set.', array('%entity_type' => $entity_type)));
$this->assertNull($entity->user_id->target_id, format_string('%entity_type: User ID field is not set.', array('%entity_type' => $entity_type)));
$this->assertNull($entity->user_id->entity, format_string('%entity_type: User entity field is not set.', array('%entity_type' => $entity_type)));
// Test setting the values via the typed data API works as well.
// Change the assigned user by entity.
$entity->user_id->first()->get('entity')->setValue($new_user2);
$this->assertEqual($new_user2->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($new_user2->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated user name value can be read.', array('%entity_type' => $entity_type)));
// Change the assigned user by id.
$entity->user_id->first()->get('target_id')->setValue($new_user2->id());
$this->assertEqual($new_user2->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($new_user2->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated user name value can be read.', array('%entity_type' => $entity_type)));
// Try unsetting a field.
$entity->name->first()->get('value')->setValue(NULL);
$entity->user_id->first()->get('target_id')->setValue(NULL);
$this->assertNull($entity->name->value, format_string('%entity_type: Name field is not set.', array('%entity_type' => $entity_type)));
$this->assertNull($entity->user_id->target_id, format_string('%entity_type: User ID field is not set.', array('%entity_type' => $entity_type)));
$this->assertNull($entity->user_id->entity, format_string('%entity_type: User entity field is not set.', array('%entity_type' => $entity_type)));
// Create a fresh entity so target_id does not get its property object
// instantiated, then verify setting a new value via typed data API works.
$entity2 = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
'user_id' => array('target_id' => $new_user1->id()),
));
// Access the property object, and set a value.
$entity2->user_id->first()->get('target_id')->setValue($new_user2->id());
$this->assertEqual($new_user2->id(), $entity2->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($new_user2->name->value, $entity2->user_id->entity->name->value, format_string('%entity_type: Updated user name value can be read.', array('%entity_type' => $entity_type)));
// Test using isset(), empty() and unset().
$entity->name->value = 'test unset';
unset($entity->name->value);
$this->assertFalse(isset($entity->name->value), format_string('%entity_type: Name is not set.', array('%entity_type' => $entity_type)));
$this->assertFalse(isset($entity->name[0]->value), format_string('%entity_type: Name is not set.', array('%entity_type' => $entity_type)));
$this->assertTrue(empty($entity->name->value), format_string('%entity_type: Name is empty.', array('%entity_type' => $entity_type)));
$this->assertTrue(empty($entity->name[0]->value), format_string('%entity_type: Name is empty.', array('%entity_type' => $entity_type)));
$entity->name->value = 'a value';
$this->assertTrue(isset($entity->name->value), format_string('%entity_type: Name is set.', array('%entity_type' => $entity_type)));
$this->assertTrue(isset($entity->name[0]->value), format_string('%entity_type: Name is set.', array('%entity_type' => $entity_type)));
$this->assertFalse(empty($entity->name->value), format_string('%entity_type: Name is not empty.', array('%entity_type' => $entity_type)));
$this->assertFalse(empty($entity->name[0]->value), format_string('%entity_type: Name is not empty.', array('%entity_type' => $entity_type)));
$this->assertTrue(isset($entity->name[0]), format_string('%entity_type: Name string item is set.', array('%entity_type' => $entity_type)));
$this->assertFalse(isset($entity->name[1]), format_string('%entity_type: Second name string item is not set as it does not exist', array('%entity_type' => $entity_type)));
$this->assertTrue(isset($entity->name), format_string('%entity_type: Name field is set.', array('%entity_type' => $entity_type)));
$this->assertFalse(isset($entity->nameInvalid), format_string('%entity_type: Not existing field is not set.', array('%entity_type' => $entity_type)));
unset($entity->name[0]);
$this->assertFalse(isset($entity->name[0]), format_string('%entity_type: Name field item is not set.', array('%entity_type' => $entity_type)));
$this->assertFalse(isset($entity->name[0]->value), format_string('%entity_type: Name is not set.', array('%entity_type' => $entity_type)));
$this->assertFalse(isset($entity->name->value), format_string('%entity_type: Name is not set.', array('%entity_type' => $entity_type)));
// Test emptying a field by assigning an empty value. NULL and array()
// behave the same.
foreach ([NULL, array(), 'unset'] as $empty) {
// Make sure a value is present
$entity->name->value = 'a value';
$this->assertTrue(isset($entity->name->value), format_string('%entity_type: Name is set.', array('%entity_type' => $entity_type)));
// Now, empty the field.
if ($empty === 'unset') {
unset($entity->name);
}
else {
$entity->name = $empty;
}
$this->assertTrue(isset($entity->name), format_string('%entity_type: Name field is set.', array('%entity_type' => $entity_type)));
$this->assertTrue($entity->name->isEmpty(), format_string('%entity_type: Name field is set.', array('%entity_type' => $entity_type)));
$this->assertIdentical(count($entity->name), 0, format_string('%entity_type: Name field contains no items.', array('%entity_type' => $entity_type)));
$this->assertIdentical($entity->name->getValue(), array(), format_string('%entity_type: Name field value is an empty array.', array('%entity_type' => $entity_type)));
$this->assertFalse(isset($entity->name[0]), format_string('%entity_type: Name field item is not set.', array('%entity_type' => $entity_type)));
$this->assertFalse(isset($entity->name[0]->value), format_string('%entity_type: First name item value is not set.', array('%entity_type' => $entity_type)));
$this->assertFalse(isset($entity->name->value), format_string('%entity_type: Name value is not set.', array('%entity_type' => $entity_type)));
}
// Access the language field.
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$this->assertEqual($langcode, $entity->{$langcode_key}->value, format_string('%entity_type: Language code can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual(\Drupal::languageManager()->getLanguage($langcode), $entity->{$langcode_key}->language, format_string('%entity_type: Language object can be read.', array('%entity_type' => $entity_type)));
// Change the language by code.
$entity->{$langcode_key}->value = \Drupal::languageManager()->getDefaultLanguage()->getId();
$this->assertEqual(\Drupal::languageManager()->getDefaultLanguage()->getId(), $entity->{$langcode_key}->value, format_string('%entity_type: Language code can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual(\Drupal::languageManager()->getDefaultLanguage(), $entity->{$langcode_key}->language, format_string('%entity_type: Language object can be read.', array('%entity_type' => $entity_type)));
// Revert language by code then try setting it by language object.
$entity->{$langcode_key}->value = $langcode;
$entity->{$langcode_key}->language = \Drupal::languageManager()->getDefaultLanguage();
$this->assertEqual(\Drupal::languageManager()->getDefaultLanguage()->getId(), $entity->{$langcode_key}->value, format_string('%entity_type: Language code can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual(\Drupal::languageManager()->getDefaultLanguage(), $entity->{$langcode_key}->language, format_string('%entity_type: Language object can be read.', array('%entity_type' => $entity_type)));
// Access the text field and test updating.
$this->assertEqual($entity->field_test_text->value, $this->entityFieldText, format_string('%entity_type: Text field can be read.', array('%entity_type' => $entity_type)));
$new_text = $this->randomMachineName();
$entity->field_test_text->value = $new_text;
$this->assertEqual($entity->field_test_text->value, $new_text, format_string('%entity_type: Updated text field can be read.', array('%entity_type' => $entity_type)));
// Test creating the entity by passing in plain values.
$this->entityName = $this->randomMachineName();
$name_item[0]['value'] = $this->entityName;
$this->entityUser = $this->createUser();
$user_item[0]['target_id'] = $this->entityUser->id();
$this->entityFieldText = $this->randomMachineName();
$text_item[0]['value'] = $this->entityFieldText;
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
'name' => $name_item,
'user_id' => $user_item,
'field_test_text' => $text_item,
));
$this->assertEqual($this->entityName, $entity->name->value, format_string('%entity_type: Name value can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityUser->id(), $entity->user_id->target_id, format_string('%entity_type: User id can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityUser->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: User name can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityFieldText, $entity->field_test_text->value, format_string('%entity_type: Text field can be read.', array('%entity_type' => $entity_type)));
// Tests copying field values by assigning the TypedData objects.
$entity2 = $this->createTestEntity($entity_type);
$entity2->name = $entity->name;
$entity2->user_id = $entity->user_id;
$entity2->field_test_text = $entity->field_test_text;
$this->assertFalse($entity->name === $entity2->name, format_string('%entity_type: Copying properties results in a different field object.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name->value, $entity2->name->value, format_string('%entity_type: Name field copied.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->user_id->target_id, $entity2->user_id->target_id, format_string('%entity_type: User id field copied.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->field_test_text->value, $entity2->field_test_text->value, format_string('%entity_type: Text field copied.', array('%entity_type' => $entity_type)));
// Tests that assigning TypedData objects to non-field properties keeps the
// assigned value as is.
$entity2 = $this->createTestEntity($entity_type);
$entity2->_not_a_field = $entity->name;
$this->assertTrue($entity2->_not_a_field === $entity->name, format_string('%entity_type: Typed data objects can be copied to non-field properties as is.', array('%entity_type' => $entity_type)));
// Tests adding a value to a field item list.
$entity->name[] = 'Another name';
$this->assertEqual($entity->name[1]->value, 'Another name', format_string('%entity_type: List item added via [] and the first property.', array('%entity_type' => $entity_type)));
$entity->name[] = array('value' => 'Third name');
$this->assertEqual($entity->name[2]->value, 'Third name', format_string('%entity_type: List item added via [] and an array of properties.', array('%entity_type' => $entity_type)));
$entity->name[3] = array('value' => 'Fourth name');
$this->assertEqual($entity->name[3]->value, 'Fourth name', format_string('%entity_type: List item added via offset and an array of properties.', array('%entity_type' => $entity_type)));
unset($entity->name[3]);
// Test removing and empty-ing list items.
$this->assertEqual(count($entity->name), 3, format_string('%entity_type: List has 3 items.', array('%entity_type' => $entity_type)));
unset($entity->name[1]);
$this->assertEqual(count($entity->name), 2, format_string('%entity_type: Second list item has been removed.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name[1]->value, 'Third name', format_string('%entity_type: The subsequent items have been shifted up.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name[1]->getName(), 1, format_string('%entity_type: The items names have been updated to their new delta.', array('%entity_type' => $entity_type)));
$entity->name[1] = NULL;
$this->assertEqual(count($entity->name), 2, format_string('%entity_type: Assigning NULL does not reduce array count.', array('%entity_type' => $entity_type)));
$this->assertTrue($entity->name[1]->isEmpty(), format_string('%entity_type: Assigning NULL empties the item.', array('%entity_type' => $entity_type)));
// Test using isEmpty().
unset($entity->name[1]);
$this->assertFalse($entity->name[0]->isEmpty(), format_string('%entity_type: Name item is not empty.', array('%entity_type' => $entity_type)));
$entity->name->value = NULL;
$this->assertTrue($entity->name[0]->isEmpty(), format_string('%entity_type: Name item is empty.', array('%entity_type' => $entity_type)));
$this->assertTrue($entity->name->isEmpty(), format_string('%entity_type: Name field is empty.', array('%entity_type' => $entity_type)));
$this->assertEqual(count($entity->name), 1, format_string('%entity_type: Empty item is considered when counting.', array('%entity_type' => $entity_type)));
$this->assertEqual(count(iterator_to_array($entity->name->getIterator())), count($entity->name), format_string('%entity_type: Count matches iterator count.', array('%entity_type' => $entity_type)));
$this->assertTrue($entity->name->getValue() === array(0 => array('value' => NULL)), format_string('%entity_type: Name field value contains a NULL value.', array('%entity_type' => $entity_type)));
// Test using filterEmptyItems().
$entity->name = array(NULL, 'foo');
$this->assertEqual(count($entity->name), 2, format_string('%entity_type: List has 2 items.', array('%entity_type' => $entity_type)));
$entity->name->filterEmptyItems();
$this->assertEqual(count($entity->name), 1, format_string('%entity_type: The empty item was removed.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name[0]->value, 'foo', format_string('%entity_type: The items were renumbered.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name[0]->getName(), 0, format_string('%entity_type: The deltas were updated in the items.', array('%entity_type' => $entity_type)));
// Test get and set field values.
$entity->name = 'foo';
$this->assertEqual($entity->name[0]->toArray(), array('value' => 'foo'), format_string('%entity_type: Field value has been retrieved via toArray()', array('%entity_type' => $entity_type)));
$values = $entity->toArray();
$this->assertEqual($values['name'], array(0 => array('value' => 'foo')), format_string('%entity_type: Field value has been retrieved via toArray() from an entity.', array('%entity_type' => $entity_type)));
// Make sure the user id can be set to zero.
$user_item[0]['target_id'] = 0;
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
'name' => $name_item,
'user_id' => $user_item,
'field_test_text' => $text_item,
));
$this->assertNotNull($entity->user_id->target_id, format_string('%entity_type: User id is not NULL', array('%entity_type' => $entity_type)));
$this->assertIdentical($entity->user_id->target_id, 0, format_string('%entity_type: User id has been set to 0', array('%entity_type' => $entity_type)));
// Test setting the ID with the value only.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
'name' => $name_item,
'user_id' => 0,
'field_test_text' => $text_item,
));
$this->assertNotNull($entity->user_id->target_id, format_string('%entity_type: User id is not NULL', array('%entity_type' => $entity_type)));
$this->assertIdentical($entity->user_id->target_id, 0, format_string('%entity_type: User id has been set to 0', array('%entity_type' => $entity_type)));
}
/**
* Tries to save and load an entity again.
*/
public function testSave() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->doTestSave($entity_type);
}
}
/**
* Executes the save tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestSave($entity_type) {
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$entity = $this->createTestEntity($entity_type);
$entity->save();
$this->assertTrue((bool) $entity->id(), format_string('%entity_type: Entity has received an id.', array('%entity_type' => $entity_type)));
$entity = entity_load($entity_type, $entity->id());
$this->assertTrue((bool) $entity->id(), format_string('%entity_type: Entity loaded.', array('%entity_type' => $entity_type)));
// Access the name field.
$this->assertEqual(1, $entity->id->value, format_string('%entity_type: ID value can be read.', array('%entity_type' => $entity_type)));
$this->assertTrue(is_string($entity->uuid->value), format_string('%entity_type: UUID value can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual('en', $entity->{$langcode_key}->value, format_string('%entity_type: Language code can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual(\Drupal::languageManager()->getLanguage('en'), $entity->{$langcode_key}->language, format_string('%entity_type: Language object can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityUser->id(), $entity->user_id->target_id, format_string('%entity_type: User id can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityUser->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: User name can be read.', array('%entity_type' => $entity_type)));
$this->assertEqual($this->entityFieldText, $entity->field_test_text->value, format_string('%entity_type: Text field can be read.', array('%entity_type' => $entity_type)));
}
/**
* Tests introspection and getting metadata upfront.
*/
public function testIntrospection() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->doTestIntrospection($entity_type);
}
}
/**
* Executes the introspection tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestIntrospection($entity_type) {
// Test getting metadata upfront. The entity types used for this test have
// a default bundle that is the same as the entity type.
$definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type, $entity_type);
$this->assertEqual($definitions['name']->getType(), 'string', $entity_type .': Name field found.');
$this->assertEqual($definitions['user_id']->getType(), 'entity_reference', $entity_type .': User field found.');
$this->assertEqual($definitions['field_test_text']->getType(), 'text', $entity_type .': Test-text-field field found.');
// Test deriving further metadata.
$this->assertTrue($definitions['name'] instanceof FieldDefinitionInterface);
$field_item_definition = $definitions['name']->getItemDefinition();
$this->assertTrue($field_item_definition instanceof ComplexDataDefinitionInterface);
$this->assertEqual($field_item_definition->getDataType(), 'field_item:string');
$value_definition = $field_item_definition->getPropertyDefinition('value');
$this->assertTrue($value_definition instanceof DataDefinitionInterface);
$this->assertEqual($value_definition->getDataType(), 'string');
// Test deriving metadata from references.
$entity_definition = \Drupal\Core\Entity\TypedData\EntityDataDefinition::create($entity_type);
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$reference_definition = $entity_definition->getPropertyDefinition($langcode_key)
->getPropertyDefinition('language')
->getTargetDefinition();
$this->assertEqual($reference_definition->getDataType(), 'language');
$reference_definition = $entity_definition->getPropertyDefinition('user_id')
->getPropertyDefinition('entity')
->getTargetDefinition();
$this->assertTrue($reference_definition instanceof \Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface, 'Definition of the referenced user retrieved.');
$this->assertEqual($reference_definition->getEntityTypeId(), 'user', 'Referenced entity is of type "user".');
// Test propagating down.
$name_definition = $reference_definition->getPropertyDefinition('name');
$this->assertTrue($name_definition instanceof FieldDefinitionInterface);
$this->assertEqual($name_definition->getPropertyDefinition('value')->getDataType(), 'string');
// Test introspecting an entity object.
// @todo: Add bundles and test bundles as well.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create();
$definitions = $entity->getFieldDefinitions();
$this->assertEqual($definitions['name']->getType(), 'string', $entity_type .': Name field found.');
$this->assertEqual($definitions['user_id']->getType(), 'entity_reference', $entity_type .': User field found.');
$this->assertEqual($definitions['field_test_text']->getType(), 'text', $entity_type .': Test-text-field field found.');
$name_properties = $entity->name->getFieldDefinition()->getPropertyDefinitions();
$this->assertEqual($name_properties['value']->getDataType(), 'string', $entity_type .': String value property of the name found.');
$userref_properties = $entity->user_id->getFieldDefinition()->getPropertyDefinitions();
$this->assertEqual($userref_properties['target_id']->getDataType(), 'integer', $entity_type .': Entity id property of the user found.');
$this->assertEqual($userref_properties['entity']->getDataType(), 'entity_reference', $entity_type .': Entity reference property of the user found.');
$textfield_properties = $entity->field_test_text->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinitions();
$this->assertEqual($textfield_properties['value']->getDataType(), 'string', $entity_type .': String value property of the test-text field found.');
$this->assertEqual($textfield_properties['format']->getDataType(), 'filter_format', $entity_type .': String format field of the test-text field found.');
$this->assertEqual($textfield_properties['processed']->getDataType(), 'string', $entity_type .': String processed property of the test-text field found.');
// Make sure provided contextual information is right.
$entity_adapter = $entity->getTypedData();
$this->assertIdentical($entity_adapter->getRoot(), $entity_adapter, 'Entity is root object.');
$this->assertEqual($entity_adapter->getPropertyPath(), '');
$this->assertEqual($entity_adapter->getName(), '');
$this->assertEqual($entity_adapter->getParent(), NULL);
$field = $entity->user_id;
$this->assertIdentical($field->getRoot()->getValue(), $entity, 'Entity is root object.');
$this->assertIdentical($field->getEntity(), $entity, 'getEntity() returns the entity.');
$this->assertEqual($field->getPropertyPath(), 'user_id');
$this->assertEqual($field->getName(), 'user_id');
$this->assertIdentical($field->getParent()->getValue(), $entity, 'Parent object matches.');
$field_item = $field[0];
$this->assertIdentical($field_item->getRoot()->getValue(), $entity, 'Entity is root object.');
$this->assertIdentical($field_item->getEntity(), $entity, 'getEntity() returns the entity.');
$this->assertEqual($field_item->getPropertyPath(), 'user_id.0');
$this->assertEqual($field_item->getName(), '0');
$this->assertIdentical($field_item->getParent(), $field, 'Parent object matches.');
$item_value = $field_item->get('entity');
$this->assertIdentical($item_value->getRoot()->getValue(), $entity, 'Entity is root object.');
$this->assertEqual($item_value->getPropertyPath(), 'user_id.0.entity');
$this->assertEqual($item_value->getName(), 'entity');
$this->assertIdentical($item_value->getParent(), $field_item, 'Parent object matches.');
}
/**
* Tests iterating over properties.
*/
public function testIterator() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->doTestIterator($entity_type);
}
}
/**
* Executes the iterator tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestIterator($entity_type) {
$entity = $this->createTestEntity($entity_type);
foreach ($entity as $name => $field) {
$this->assertTrue($field instanceof FieldItemListInterface, $entity_type . ": Field $name implements interface.");
foreach ($field as $delta => $item) {
$this->assertTrue($field[0] instanceof FieldItemInterface, $entity_type . ": Item $delta of field $name implements interface.");
foreach ($item as $value_name => $value_property) {
$this->assertTrue($value_property instanceof TypedDataInterface, $entity_type . ": Value $value_name of item $delta of field $name implements interface.");
$value = $value_property->getValue();
$this->assertTrue(!isset($value) || is_scalar($value) || $value instanceof EntityInterface, $entity_type . ": Value $value_name of item $delta of field $name is a primitive or an entity.");
}
}
}
$fields = $entity->getFields();
$this->assertEqual(array_keys($fields), array_keys($entity->getTypedData()->getDataDefinition()->getPropertyDefinitions()), format_string('%entity_type: All fields returned.', array('%entity_type' => $entity_type)));
$this->assertEqual($fields, iterator_to_array($entity->getIterator()), format_string('%entity_type: Entity iterator iterates over all fields.', array('%entity_type' => $entity_type)));
}
/**
* Tests working with the entity based upon the TypedData API.
*/
public function testDataStructureInterfaces() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->doTestDataStructureInterfaces($entity_type);
}
}
/**
* Executes the data structure interfaces tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestDataStructureInterfaces($entity_type) {
$entity = $this->createTestEntity($entity_type);
// Test using the whole tree of typed data by navigating through the tree of
// contained properties and getting all contained strings, limited by a
// certain depth.
$strings = array();
$this->getContainedStrings($entity->getTypedData(), 0, $strings);
// @todo: Once the user entity has defined properties this should contain
// the user name and other user entity strings as well.
$target_strings = array(
$entity->uuid->value,
'en',
$this->entityName,
// Bundle name.
$entity->bundle(),
$this->entityFieldText,
// Field format.
NULL,
);
asort($strings);
asort($target_strings);
$this->assertEqual(array_values($strings), array_values($target_strings), format_string('%entity_type: All contained strings found.', array('%entity_type' => $entity_type)));
}
/**
* Recursive helper for getting all contained strings,
* i.e. properties of type string.
*/
public function getContainedStrings(TypedDataInterface $wrapper, $depth, array &$strings) {
if ($wrapper instanceof StringInterface) {
$strings[] = $wrapper->getValue();
}
// Recurse until a certain depth is reached if possible.
if ($depth < 7) {
if ($wrapper instanceof \Drupal\Core\TypedData\ListInterface) {
foreach ($wrapper as $item) {
$this->getContainedStrings($item, $depth + 1, $strings);
}
}
elseif ($wrapper instanceof \Drupal\Core\TypedData\ComplexDataInterface) {
foreach ($wrapper as $property) {
$this->getContainedStrings($property, $depth + 1, $strings);
}
}
}
}
/**
* Makes sure data types are correctly derived for all entity types.
*/
public function testDataTypes() {
$types = \Drupal::typedDataManager()->getDefinitions();
foreach (entity_test_entity_types() as $entity_type) {
$this->assertTrue($types['entity:' . $entity_type]['class'], 'Entity data type registered.');
}
// Check bundle types are provided as well.
entity_test_create_bundle('bundle');
$types = \Drupal::typedDataManager()->getDefinitions();
$this->assertTrue($types['entity:entity_test:bundle']['class'], 'Entity bundle data type registered.');
}
/**
* Tests a base field override on a non-existing base field.
*
* @see entity_test_entity_base_field_info_alter()
*/
public function testBaseFieldNonExistingBaseField() {
$this->entityManager->getStorage('node_type')->create(array(
'type' => 'page',
'name' => 'page',
))->save();
$this->entityManager->clearCachedFieldDefinitions();
$fields = $this->entityManager->getFieldDefinitions('node', 'page');
$override = $fields['status']->getConfig('page');
$override->setLabel($this->randomString())->save();
\Drupal::state()->set('entity_test.node_remove_status_field', TRUE);
$this->entityManager->clearCachedFieldDefinitions();
$fields = $this->entityManager->getFieldDefinitions('node', 'page');
// A base field override on a non-existing base field should not cause a
// field definition to come into existence.
$this->assertFalse(isset($fields['status']), 'Node\'s status base field does not exist.');
}
/**
* Tests creating a field override config for a bundle field.
*
* @see entity_test_entity_base_field_info_alter()
*/
public function testFieldOverrideBundleField() {
// First make sure the bundle field override in code, which is provided by
// the test entity works.
entity_test_create_bundle('some_test_bundle', 'Some test bundle', 'entity_test_field_override');
$field_definitions = $this->entityManager->getFieldDefinitions('entity_test_field_override', 'entity_test_field_override');
$this->assertEqual($field_definitions['name']->getDescription(), 'The default description.');
$this->assertNull($field_definitions['name']->getTargetBundle());
$field_definitions = $this->entityManager->getFieldDefinitions('entity_test_field_override', 'some_test_bundle');
$this->assertEqual($field_definitions['name']->getDescription(), 'Custom description.');
$this->assertEqual($field_definitions['name']->getTargetBundle(), 'some_test_bundle');
// Now create a config override of the bundle field.
$field_config = $field_definitions['name']->getConfig('some_test_bundle');
$field_config->setTranslatable(FALSE);
$field_config->save();
// Make sure both overrides are present.
$this->entityManager->clearCachedFieldDefinitions();
$field_definitions = $this->entityManager->getFieldDefinitions('entity_test_field_override', 'some_test_bundle');
$this->assertEqual($field_definitions['name']->getDescription(), 'Custom description.');
$this->assertEqual($field_definitions['name']->getTargetBundle(), 'some_test_bundle');
$this->assertFalse($field_definitions['name']->isTranslatable());
}
/**
* Tests validation constraints provided by the Entity API.
*/
public function testEntityConstraintValidation() {
$entity = $this->createTestEntity('entity_test');
$entity->save();
// Create a reference field item and let it reference the entity.
$definition = BaseFieldDefinition::create('entity_reference')
->setLabel('Test entity')
->setSetting('target_type', 'entity_test');
$reference_field = \Drupal::typedDataManager()->create($definition);
$reference = $reference_field->appendItem(array('entity' => $entity))->get('entity');
// Test validation the typed data object.
$violations = $reference->validate();
$this->assertEqual($violations->count(), 0);
// Test validating an entity of the wrong type.
$user = $this->createUser();
$user->save();
$node = $node = Node::create([
'type' => 'page',
'uid' => $user->id(),
'title' => $this->randomString(),
]);
$reference->setValue($node);
$violations = $reference->validate();
$this->assertEqual($violations->count(), 1);
// Test bundle validation.
NodeType::create(array('type' => 'article'))
->save();
$definition = BaseFieldDefinition::create('entity_reference')
->setLabel('Test entity')
->setSetting('target_type', 'node')
->setSetting('handler_settings', ['target_bundles' => ['article' => 'article']]);
$reference_field = \Drupal::TypedDataManager()->create($definition);
$reference_field->appendItem(array('entity' => $node));
$violations = $reference_field->validate();
$this->assertEqual($violations->count(), 1);
$node = Node::create([
'type' => 'article',
'uid' => $user->id(),
'title' => $this->randomString(),
]);
$node->save();
$reference_field->entity = $node;
$violations = $reference_field->validate();
$this->assertEqual($violations->count(), 0);
}
/**
* Tests getting processed property values via a computed property.
*/
public function testComputedProperties() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->doTestComputedProperties($entity_type);
}
}
/**
* Executes the computed properties tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestComputedProperties($entity_type) {
$entity = $this->createTestEntity($entity_type);
$entity->field_test_text->value = "The <strong>text</strong> text to filter.";
$entity->field_test_text->format = filter_default_format();
$target = "<p>The &lt;strong&gt;text&lt;/strong&gt; text to filter.</p>\n";
$this->assertEqual($entity->field_test_text->processed, $target, format_string('%entity_type: Text is processed with the default filter.', array('%entity_type' => $entity_type)));
// Save and load entity and make sure it still works.
$entity->save();
$entity = entity_load($entity_type, $entity->id());
$this->assertEqual($entity->field_test_text->processed, $target, format_string('%entity_type: Text is processed with the default filter.', array('%entity_type' => $entity_type)));
}
}

View file

@ -1,10 +1,5 @@
<?php
/**
* @file
* Contains \Drupal\KernelTests\Core\Entity\EntityKernelTestBase.
*/
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\EntityInterface;

View file

@ -0,0 +1,136 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Component\Utility\Unicode;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Base class for language-aware entity tests.
*/
abstract class EntityLanguageTestBase extends EntityKernelTestBase {
/**
* The language manager service.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The available language codes.
*
* @var array
*/
protected $langcodes;
/**
* The test field name.
*
* @var string
*/
protected $fieldName;
/**
* The untranslatable test field name.
*
* @var string
*/
protected $untranslatableFieldName;
public static $modules = array('language', 'entity_test');
protected function setUp() {
parent::setUp();
$this->languageManager = $this->container->get('language_manager');
foreach (entity_test_entity_types() as $entity_type_id) {
// The entity_test schema is installed by the parent.
if ($entity_type_id != 'entity_test') {
$this->installEntitySchema($entity_type_id);
}
}
$this->installConfig(array('language'));
// Create the test field.
module_load_install('entity_test');
entity_test_install();
// Enable translations for the test entity type.
$this->state->set('entity_test.translation', TRUE);
// Create a translatable test field.
$this->fieldName = Unicode::strtolower($this->randomMachineName() . '_field_name');
// Create an untranslatable test field.
$this->untranslatableFieldName = Unicode::strtolower($this->randomMachineName() . '_field_name');
// Create field fields in all entity variations.
foreach (entity_test_entity_types() as $entity_type) {
FieldStorageConfig::create(array(
'field_name' => $this->fieldName,
'entity_type' => $entity_type,
'type' => 'text',
'cardinality' => 4,
))->save();
FieldConfig::create([
'field_name' => $this->fieldName,
'entity_type' => $entity_type,
'bundle' => $entity_type,
'translatable' => TRUE,
])->save();
FieldStorageConfig::create(array(
'field_name' => $this->untranslatableFieldName,
'entity_type' => $entity_type,
'type' => 'text',
'cardinality' => 4,
))->save();
FieldConfig::create([
'field_name' => $this->untranslatableFieldName,
'entity_type' => $entity_type,
'bundle' => $entity_type,
'translatable' => FALSE,
])->save();
}
// Create the default languages.
$this->installConfig(array('language'));
// Create test languages.
$this->langcodes = array();
for ($i = 0; $i < 3; ++$i) {
$language = ConfigurableLanguage::create(array(
'id' => 'l' . $i,
'label' => $this->randomString(),
'weight' => $i,
));
$this->langcodes[$i] = $language->getId();
$language->save();
}
}
/**
* Toggles field storage translatability.
*
* @param string $entity_type
* The type of the entity fields are attached to.
*/
protected function toggleFieldTranslatability($entity_type, $bundle) {
$fields = array($this->fieldName, $this->untranslatableFieldName);
foreach ($fields as $field_name) {
$field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
$translatable = !$field->isTranslatable();
$field->set('translatable', $translatable);
$field->save();
$field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
$this->assertEqual($field->isTranslatable(), $translatable, 'Field translatability changed.');
}
\Drupal::cache('entity')->deleteAll();
}
}

View file

@ -0,0 +1,584 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the Entity Query Aggregation API.
*
* @group Entity
* @see \Drupal\entity_test\Entity\EntityTest
*/
class EntityQueryAggregateTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array();
/**
* The entity_test storage to create the test entities.
*
* @var \Drupal\entity_test\EntityTestStorage
*/
protected $entityStorage;
/**
* The actual query result, to compare later.
*
* @var array
*/
protected $queryResult;
/**
* The query factory to create entity queries.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
public $factory;
protected function setUp() {
parent::setUp();
$this->entityStorage = $this->entityManager->getStorage('entity_test');
$this->factory = $this->container->get('entity.query');
// Add some fieldapi fields to be used in the test.
for ($i = 1; $i <= 2; $i++) {
$field_name = 'field_test_' . $i;
FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'integer',
'cardinality' => 2,
))->save();
FieldConfig::create([
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
])->save();
}
$entity = $this->entityStorage->create(array(
'id' => 1,
'user_id' => 1,
'field_test_1' => 1,
'field_test_2' => 2,
));
$entity->enforceIsNew();
$entity->save();
$entity = $this->entityStorage->create(array(
'id' => 2,
'user_id' => 2,
'field_test_1' => 1,
'field_test_2' => 7,
));
$entity->enforceIsNew();
$entity->save();
$entity = $this->entityStorage->create(array(
'id' => 3,
'user_id' => 2,
'field_test_1' => 2,
'field_test_2' => 1,
));
$entity->enforceIsNew();
$entity->save();
$entity = $this->entityStorage->create(array(
'id' => 4,
'user_id' => 2,
'field_test_1' => 2,
'field_test_2' => 8,
));
$entity->enforceIsNew();
$entity->save();
$entity = $this->entityStorage->create(array(
'id' => 5,
'user_id' => 3,
'field_test_1' => 2,
'field_test_2' => 2,
));
$entity->enforceIsNew();
$entity->save();
$entity = $this->entityStorage->create(array(
'id' => 6,
'user_id' => 3,
'field_test_1' => 3,
'field_test_2' => 8,
));
$entity->enforceIsNew();
$entity->save();
}
/**
* Test aggregation support.
*/
public function testAggregation() {
// Apply a simple groupby.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('user_id' => 1),
array('user_id' => 2),
array('user_id' => 3),
));
$function_expected = array();
$function_expected['count'] = array(array('id_count' => 6));
$function_expected['min'] = array(array('id_min' => 1));
$function_expected['max'] = array(array('id_max' => 6));
$function_expected['sum'] = array(array('id_sum' => 21));
$function_expected['avg'] = array(array('id_avg' => (21.0/6.0)));
// Apply a simple aggregation for different aggregation functions.
foreach ($function_expected as $aggregation_function => $expected) {
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', $aggregation_function)
->execute();
$this->assertEqual($this->queryResult, $expected);
}
// Apply aggregation and groupby on the same query.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'COUNT')
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('user_id' => 1, 'id_count' => 1),
array('user_id' => 2, 'id_count' => 3),
array('user_id' => 3, 'id_count' => 2),
));
// Apply aggregation and a condition which matches.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'COUNT')
->groupBy('id')
->conditionAggregate('id', 'COUNT', 8)
->execute();
$this->assertResults(array());
// Don't call aggregate to test the implicit aggregate call.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('id')
->conditionAggregate('id', 'COUNT', 8)
->execute();
$this->assertResults(array());
// Apply aggregation and a condition which matches.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'count')
->groupBy('id')
->conditionAggregate('id', 'COUNT', 6)
->execute();
$this->assertResults(array(array('id_count' => 6)));
// Apply aggregation, a groupby and a condition which matches partially via
// the operator '='.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'count')
->conditionAggregate('id', 'count', 2)
->groupBy('user_id')
->execute();
$this->assertResults(array(array('id_count' => 2, 'user_id' => 3)));
// Apply aggregation, a groupby and a condition which matches partially via
// the operator '>'.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'count')
->conditionAggregate('id', 'COUNT', 1, '>')
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('id_count' => 2, 'user_id' => 3),
array('id_count' => 3, 'user_id' => 2),
));
// Apply aggregation and a sort. This might not be useful, but have a proper
// test coverage.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'COUNT')
->sortAggregate('id', 'COUNT')
->execute();
$this->assertSortedResults(array(array('id_count' => 6)));
// Don't call aggregate to test the implicit aggregate call.
$this->queryResult = $this->factory->getAggregate('entity_test')
->sortAggregate('id', 'COUNT')
->execute();
$this->assertSortedResults(array(array('id_count' => 6)));
// Apply aggregation, groupby and a sort descending.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'COUNT')
->groupBy('user_id')
->sortAggregate('id', 'COUNT', 'DESC')
->execute();
$this->assertSortedResults(array(
array('user_id' => 2, 'id_count' => 3),
array('user_id' => 3, 'id_count' => 2),
array('user_id' => 1, 'id_count' => 1),
));
// Apply aggregation, groupby and a sort ascending.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'COUNT')
->groupBy('user_id')
->sortAggregate('id', 'COUNT', 'ASC')
->execute();
$this->assertSortedResults(array(
array('user_id' => 1, 'id_count' => 1),
array('user_id' => 3, 'id_count' => 2),
array('user_id' => 2, 'id_count' => 3),
));
// Apply aggregation, groupby, an aggregation condition and a sort with the
// operator '='.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'COUNT')
->groupBy('user_id')
->sortAggregate('id', 'COUNT')
->conditionAggregate('id', 'COUNT', 2)
->execute();
$this->assertSortedResults(array(array('id_count' => 2, 'user_id' => 3)));
// Apply aggregation, groupby, an aggregation condition and a sort with the
// operator '<' and order ASC.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'COUNT')
->groupBy('user_id')
->sortAggregate('id', 'COUNT', 'ASC')
->conditionAggregate('id', 'COUNT', 3, '<')
->execute();
$this->assertSortedResults(array(
array('id_count' => 1, 'user_id' => 1),
array('id_count' => 2, 'user_id' => 3),
));
// Apply aggregation, groupby, an aggregation condition and a sort with the
// operator '<' and order DESC.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('id', 'COUNT')
->groupBy('user_id')
->sortAggregate('id', 'COUNT', 'DESC')
->conditionAggregate('id', 'COUNT', 3, '<')
->execute();
$this->assertSortedResults(array(
array('id_count' => 2, 'user_id' => 3),
array('id_count' => 1, 'user_id' => 1),
));
// Test aggregation/groupby support for fieldapi fields.
// Just group by a fieldapi field.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->execute();
$this->assertResults(array(
array('field_test_1' => 1),
array('field_test_1' => 2),
array('field_test_1' => 3),
));
// Group by a fieldapi field and aggregate a normal property.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('user_id', 'COUNT')
->groupBy('field_test_1')
->execute();
$this->assertResults(array(
array('field_test_1' => 1, 'user_id_count' => 2),
array('field_test_1' => 2, 'user_id_count' => 3),
array('field_test_1' => 3, 'user_id_count' => 1),
));
// Group by a normal property and aggregate a fieldapi field.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('field_test_1', 'COUNT')
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('user_id' => 1, 'field_test_1_count' => 1),
array('user_id' => 2, 'field_test_1_count' => 3),
array('user_id' => 3, 'field_test_1_count' => 2),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('field_test_1', 'SUM')
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('user_id' => 1, 'field_test_1_sum' => 1),
array('user_id' => 2, 'field_test_1_sum' => 5),
array('user_id' => 3, 'field_test_1_sum' => 5),
));
// Aggregate by two different fieldapi fields.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('field_test_1', 'SUM')
->aggregate('field_test_2', 'SUM')
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('user_id' => 1, 'field_test_1_sum' => 1, 'field_test_2_sum' => 2),
array('user_id' => 2, 'field_test_1_sum' => 5, 'field_test_2_sum' => 16),
array('user_id' => 3, 'field_test_1_sum' => 5, 'field_test_2_sum' => 10),
));
// This time aggregate the same field twice.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('field_test_1', 'SUM')
->aggregate('field_test_1', 'COUNT')
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('user_id' => 1, 'field_test_1_sum' => 1, 'field_test_1_count' => 1),
array('user_id' => 2, 'field_test_1_sum' => 5, 'field_test_1_count' => 3),
array('user_id' => 3, 'field_test_1_sum' => 5, 'field_test_1_count' => 2),
));
// Group by and aggregate by a fieldapi field.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->aggregate('field_test_2', 'COUNT')
->execute();
$this->assertResults(array(
array('field_test_1' => 1, 'field_test_2_count' => 2),
array('field_test_1' => 2, 'field_test_2_count' => 3),
array('field_test_1' => 3, 'field_test_2_count' => 1),
));
// Group by and aggregate by a fieldapi field and use multiple aggregate
// functions.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->aggregate('field_test_2', 'COUNT')
->aggregate('field_test_2', 'SUM')
->execute();
$this->assertResults(array(
array('field_test_1' => 1, 'field_test_2_count' => 2, 'field_test_2_sum' => 9),
array('field_test_1' => 2, 'field_test_2_count' => 3, 'field_test_2_sum' => 11),
array('field_test_1' => 3, 'field_test_2_count' => 1, 'field_test_2_sum' => 8),
));
// Apply an aggregate condition for a fieldapi field and group by a simple
// property.
$this->queryResult = $this->factory->getAggregate('entity_test')
->conditionAggregate('field_test_1', 'COUNT', 3)
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('user_id' => 2, 'field_test_1_count' => 3),
array('user_id' => 3, 'field_test_1_count' => 2),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('field_test_1', 'SUM')
->conditionAggregate('field_test_1', 'COUNT', 2, '>')
->groupBy('user_id')
->execute();
$this->assertResults(array(
array('user_id' => 2, 'field_test_1_sum' => 5, 'field_test_1_count' => 3),
array('user_id' => 3, 'field_test_1_sum' => 5, 'field_test_1_count' => 2),
));
// Apply an aggregate condition for a simple property and a group by a
// fieldapi field.
$this->queryResult = $this->factory->getAggregate('entity_test')
->conditionAggregate('user_id', 'COUNT', 2)
->groupBy('field_test_1')
->execute();
$this->assertResults(array(
array('field_test_1' => 1, 'user_id_count' => 2),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->conditionAggregate('user_id', 'COUNT', 2, '>')
->groupBy('field_test_1')
->execute();
$this->assertResults(array(
array('field_test_1' => 1, 'user_id_count' => 2),
array('field_test_1' => 2, 'user_id_count' => 3),
));
// Apply an aggregate condition and a group by fieldapi fields.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->conditionAggregate('field_test_2', 'COUNT', 2)
->execute();
$this->assertResults(array(
array('field_test_1' => 1, 'field_test_2_count' => 2),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->conditionAggregate('field_test_2', 'COUNT', 2, '>')
->execute();
$this->assertResults(array(
array('field_test_1' => 1, 'field_test_2_count' => 2),
array('field_test_1' => 2, 'field_test_2_count' => 3),
));
// Apply an aggregate condition and a group by fieldapi fields with multiple
// conditions via AND.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->conditionAggregate('field_test_2', 'COUNT', 2)
->conditionAggregate('field_test_2', 'SUM', 8)
->execute();
$this->assertResults(array());
// Apply an aggregate condition and a group by fieldapi fields with multiple
// conditions via OR.
$this->queryResult = $this->factory->getAggregate('entity_test', 'OR')
->groupBy('field_test_1')
->conditionAggregate('field_test_2', 'COUNT', 2)
->conditionAggregate('field_test_2', 'SUM', 8)
->execute();
$this->assertResults(array(
array('field_test_1' => 1, 'field_test_2_count' => 2, 'field_test_2_sum' => 9),
array('field_test_1' => 3, 'field_test_2_count' => 1, 'field_test_2_sum' => 8),
));
// Group by a normal property and aggregate a fieldapi field and sort by the
// groupby field.
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('field_test_1', 'COUNT')
->groupBy('user_id')
->sort('user_id', 'DESC')
->execute();
$this->assertSortedResults(array(
array('user_id' => 3, 'field_test_1_count' => 2),
array('user_id' => 2, 'field_test_1_count' => 3),
array('user_id' => 1, 'field_test_1_count' => 1),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->aggregate('field_test_1', 'COUNT')
->groupBy('user_id')
->sort('user_id', 'ASC')
->execute();
$this->assertSortedResults(array(
array('user_id' => 1, 'field_test_1_count' => 1),
array('user_id' => 2, 'field_test_1_count' => 3),
array('user_id' => 3, 'field_test_1_count' => 2),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->conditionAggregate('field_test_1', 'COUNT', 2, '>')
->groupBy('user_id')
->sort('user_id', 'ASC')
->execute();
$this->assertSortedResults(array(
array('user_id' => 2, 'field_test_1_count' => 3),
array('user_id' => 3, 'field_test_1_count' => 2),
));
// Group by a normal property, aggregate a fieldapi field, and sort by the
// aggregated field.
$this->queryResult = $this->factory->getAggregate('entity_test')
->sortAggregate('field_test_1', 'COUNT', 'DESC')
->groupBy('user_id')
->execute();
$this->assertSortedResults(array(
array('user_id' => 2, 'field_test_1_count' => 3),
array('user_id' => 3, 'field_test_1_count' => 2),
array('user_id' => 1, 'field_test_1_count' => 1),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->sortAggregate('field_test_1', 'COUNT', 'ASC')
->groupBy('user_id')
->execute();
$this->assertSortedResults(array(
array('user_id' => 1, 'field_test_1_count' => 1),
array('user_id' => 3, 'field_test_1_count' => 2),
array('user_id' => 2, 'field_test_1_count' => 3),
));
// Group by and aggregate by fieldapi field, and sort by the groupby field.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->aggregate('field_test_2', 'COUNT')
->sort('field_test_1', 'ASC')
->execute();
$this->assertSortedResults(array(
array('field_test_1' => 1, 'field_test_2_count' => 2),
array('field_test_1' => 2, 'field_test_2_count' => 3),
array('field_test_1' => 3, 'field_test_2_count' => 1),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->aggregate('field_test_2', 'COUNT')
->sort('field_test_1', 'DESC')
->execute();
$this->assertSortedResults(array(
array('field_test_1' => 3, 'field_test_2_count' => 1),
array('field_test_1' => 2, 'field_test_2_count' => 3),
array('field_test_1' => 1, 'field_test_2_count' => 2),
));
// Groupby and aggregate by fieldapi field, and sort by the aggregated
// field.
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->sortAggregate('field_test_2', 'COUNT', 'DESC')
->execute();
$this->assertSortedResults(array(
array('field_test_1' => 2, 'field_test_2_count' => 3),
array('field_test_1' => 1, 'field_test_2_count' => 2),
array('field_test_1' => 3, 'field_test_2_count' => 1),
));
$this->queryResult = $this->factory->getAggregate('entity_test')
->groupBy('field_test_1')
->sortAggregate('field_test_2', 'COUNT', 'ASC')
->execute();
$this->assertSortedResults(array(
array('field_test_1' => 3, 'field_test_2_count' => 1),
array('field_test_1' => 1, 'field_test_2_count' => 2),
array('field_test_1' => 2, 'field_test_2_count' => 3),
));
}
/**
* Asserts the results as expected regardless of order between and in rows.
*
* @param array $expected
* An array of the expected results.
*/
protected function assertResults($expected, $sorted = FALSE) {
$found = TRUE;
$expected_keys = array_keys($expected);
foreach ($this->queryResult as $key => $row) {
$keys = $sorted ? array($key) : $expected_keys;
foreach ($keys as $key) {
$expected_row = $expected[$key];
if (!array_diff_assoc($row, $expected_row) && !array_diff_assoc($expected_row, $row)) {
continue 2;
}
}
$found = FALSE;
break;
}
return $this->assertTrue($found, strtr('!expected expected, !found found', array('!expected' => print_r($expected, TRUE), '!found' => print_r($this->queryResult, TRUE))));
}
/**
* Asserts the results as expected regardless of order in rows.
*
* @param array $expected
* An array of the expected results.
*/
protected function assertSortedResults($expected) {
return $this->assertResults($expected, TRUE);
}
}

View file

@ -0,0 +1,172 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Component\Utility\Unicode;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
/**
* Tests the Entity Query relationship API.
*
* @group Entity
*/
class EntityQueryRelationshipTest extends EntityKernelTestBase {
use EntityReferenceTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('taxonomy');
/**
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $factory;
/**
* Term entities.
*
* @var array
*/
protected $terms;
/**
* User entities.
*
* @var array
*/
public $accounts;
/**
* entity_test entities.
*
* @var array
*/
protected $entities;
/**
* The name of the taxonomy field used for test.
*
* @var string
*/
protected $fieldName;
/**
* The results returned by EntityQuery.
*
* @var array
*/
protected $queryResults;
protected function setUp() {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
// We want an entity reference field. It needs a vocabulary, terms, a field
// storage and a field. First, create the vocabulary.
$vocabulary = Vocabulary::create([
'vid' => Unicode::strtolower($this->randomMachineName()),
]);
$vocabulary->save();
// Second, create the field.
entity_test_create_bundle('test_bundle');
$this->fieldName = strtolower($this->randomMachineName());
$handler_settings = array(
'target_bundles' => array(
$vocabulary->id() => $vocabulary->id(),
),
'auto_create' => TRUE,
);
$this->createEntityReferenceField('entity_test', 'test_bundle', $this->fieldName, NULL, 'taxonomy_term', 'default', $handler_settings);
// Create two terms and also two accounts.
for ($i = 0; $i <= 1; $i++) {
$term = Term::create([
'name' => $this->randomMachineName(),
'vid' => $vocabulary->id(),
]);
$term->save();
$this->terms[] = $term;
$this->accounts[] = $this->createUser();
}
// Create three entity_test entities, the 0th entity will point to the
// 0th account and 0th term, the 1st and 2nd entity will point to the
// 1st account and 1st term.
for ($i = 0; $i <= 2; $i++) {
$entity = EntityTest::create(array('type' => 'test_bundle'));
$entity->name->value = $this->randomMachineName();
$index = $i ? 1 : 0;
$entity->user_id->target_id = $this->accounts[$index]->id();
$entity->{$this->fieldName}->target_id = $this->terms[$index]->id();
$entity->save();
$this->entities[] = $entity;
}
$this->factory = \Drupal::service('entity.query');
}
/**
* Tests querying.
*/
public function testQuery() {
// This returns the 0th entity as that's only one pointing to the 0th
// account.
$this->queryResults = $this->factory->get('entity_test')
->condition("user_id.entity.name", $this->accounts[0]->getUsername())
->execute();
$this->assertResults(array(0));
// This returns the 1st and 2nd entity as those point to the 1st account.
$this->queryResults = $this->factory->get('entity_test')
->condition("user_id.entity.name", $this->accounts[0]->getUsername(), '<>')
->execute();
$this->assertResults(array(1, 2));
// This returns all three entities because all of them point to an
// account.
$this->queryResults = $this->factory->get('entity_test')
->exists("user_id.entity.name")
->execute();
$this->assertResults(array(0, 1, 2));
// This returns no entities because all of them point to an account.
$this->queryResults = $this->factory->get('entity_test')
->notExists("user_id.entity.name")
->execute();
$this->assertEqual(count($this->queryResults), 0);
// This returns the 0th entity as that's only one pointing to the 0th
// term (test without specifying the field column).
$this->queryResults = $this->factory->get('entity_test')
->condition("$this->fieldName.entity.name", $this->terms[0]->name->value)
->execute();
$this->assertResults(array(0));
// This returns the 0th entity as that's only one pointing to the 0th
// term (test with specifying the column name).
$this->queryResults = $this->factory->get('entity_test')
->condition("$this->fieldName.target_id.entity.name", $this->terms[0]->name->value)
->execute();
$this->assertResults(array(0));
// This returns the 1st and 2nd entity as those point to the 1st term.
$this->queryResults = $this->factory->get('entity_test')
->condition("$this->fieldName.entity.name", $this->terms[0]->name->value, '<>')
->execute();
$this->assertResults(array(1, 2));
}
/**
* Assert the results.
*
* @param array $expected
* A list of indexes in the $this->entities array.
*/
protected function assertResults($expected) {
$this->assertEqual(count($this->queryResults), count($expected));
foreach ($expected as $key) {
$id = $this->entities[$key]->id();
$this->assertEqual($this->queryResults[$id], $id);
}
}
}

View file

@ -0,0 +1,865 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Component\Utility\Unicode;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests Entity Query functionality.
*
* @group Entity
*/
class EntityQueryTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('field_test', 'language');
/**
* @var array
*/
protected $queryResults;
/**
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $factory;
/**
* A list of bundle machine names created for this test.
*
* @var string[]
*/
protected $bundles;
/**
* Field name for the greetings field.
*
* @var string
*/
public $greetings;
/**
* Field name for the figures field.
*
* @var string
*/
public $figures;
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_mulrev');
$this->installConfig(array('language'));
$figures = Unicode::strtolower($this->randomMachineName());
$greetings = Unicode::strtolower($this->randomMachineName());
foreach (array($figures => 'shape', $greetings => 'text') as $field_name => $field_type) {
$field_storage = FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => 'entity_test_mulrev',
'type' => $field_type,
'cardinality' => 2,
));
$field_storage->save();
$field_storages[] = $field_storage;
}
$bundles = array();
for ($i = 0; $i < 2; $i++) {
// For the sake of tablesort, make sure the second bundle is higher than
// the first one. Beware: MySQL is not case sensitive.
do {
$bundle = $this->randomMachineName();
} while ($bundles && strtolower($bundles[0]) >= strtolower($bundle));
entity_test_create_bundle($bundle);
foreach ($field_storages as $field_storage) {
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $bundle,
])->save();
}
$bundles[] = $bundle;
}
// Each unit is a list of field name, langcode and a column-value array.
$units[] = array($figures, 'en', array(
'color' => 'red',
'shape' => 'triangle',
));
$units[] = array($figures, 'en', array(
'color' => 'blue',
'shape' => 'circle',
));
// To make it easier to test sorting, the greetings get formats according
// to their langcode.
$units[] = array($greetings, 'tr', array(
'value' => 'merhaba',
'format' => 'format-tr'
));
$units[] = array($greetings, 'pl', array(
'value' => 'siema',
'format' => 'format-pl'
));
// Make these languages available to the greetings field.
ConfigurableLanguage::createFromLangcode('tr')->save();
ConfigurableLanguage::createFromLangcode('pl')->save();
// Calculate the cartesian product of the unit array by looking at the
// bits of $i and add the unit at the bits that are 1. For example,
// decimal 13 is binary 1101 so unit 3,2 and 0 will be added to the
// entity.
for ($i = 1; $i <= 15; $i++) {
$entity = EntityTestMulRev::create(array(
'type' => $bundles[$i & 1],
'name' => $this->randomMachineName(),
'langcode' => 'en',
));
// Make sure the name is set for every language that we might create.
foreach (array('tr', 'pl') as $langcode) {
$entity->addTranslation($langcode)->name = $this->randomMachineName();
}
foreach (array_reverse(str_split(decbin($i))) as $key => $bit) {
if ($bit) {
list($field_name, $langcode, $values) = $units[$key];
$entity->getTranslation($langcode)->{$field_name}[] = $values;
}
}
$entity->save();
}
$this->bundles = $bundles;
$this->figures = $figures;
$this->greetings = $greetings;
$this->factory = \Drupal::service('entity.query');
}
/**
* Test basic functionality.
*/
function testEntityQuery() {
$greetings = $this->greetings;
$figures = $this->figures;
$this->queryResults = $this->factory->get('entity_test_mulrev')
->exists($greetings, 'tr')
->condition("$figures.color", 'red')
->sort('id')
->execute();
// As unit 0 was the red triangle and unit 2 was the turkish greeting,
// bit 0 and bit 2 needs to be set.
$this->assertResult(5, 7, 13, 15);
$query = $this->factory->get('entity_test_mulrev', 'OR')
->exists($greetings, 'tr')
->condition("$figures.color", 'red')
->sort('id');
$count_query = clone $query;
$this->assertEqual(12, $count_query->count()->execute());
$this->queryResults = $query->execute();
// Now bit 0 (1, 3, 5, 7, 9, 11, 13, 15) or bit 2 (4, 5, 6, 7, 12, 13, 14,
// 15) needs to be set.
$this->assertResult(1, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15);
// Test cloning of query conditions.
$query = $this->factory->get('entity_test_mulrev')
->condition("$figures.color", 'red')
->sort('id');
$cloned_query = clone $query;
$cloned_query
->condition("$figures.shape", 'circle');
// Bit 0 (1, 3, 5, 7, 9, 11, 13, 15) needs to be set.
$this->queryResults = $query->execute();
$this->assertResult(1, 3, 5, 7, 9, 11, 13, 15);
// No red color has a circle shape.
$this->queryResults = $cloned_query->execute();
$this->assertResult();
$query = $this->factory->get('entity_test_mulrev');
$group = $query->orConditionGroup()
->exists($greetings, 'tr')
->condition("$figures.color", 'red');
$this->queryResults = $query
->condition($group)
->condition("$greetings.value", 'sie', 'STARTS_WITH')
->sort('revision_id')
->execute();
// Bit 3 and (bit 0 or 2) -- the above 8 part of the above.
$this->assertResult(9, 11, 12, 13, 14, 15);
// No figure has both the colors blue and red at the same time.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition("$figures.color", 'blue')
->condition("$figures.color", 'red')
->sort('id')
->execute();
$this->assertResult();
// But an entity might have a red and a blue figure both.
$query = $this->factory->get('entity_test_mulrev');
$group_blue = $query->andConditionGroup()->condition("$figures.color", 'blue');
$group_red = $query->andConditionGroup()->condition("$figures.color", 'red');
$this->queryResults = $query
->condition($group_blue)
->condition($group_red)
->sort('revision_id')
->execute();
// Unit 0 and unit 1, so bits 0 1.
$this->assertResult(3, 7, 11, 15);
// Do the same test but with IN operator.
$query = $this->factory->get('entity_test_mulrev');
$group_blue = $query->andConditionGroup()->condition("$figures.color", array('blue'), 'IN');
$group_red = $query->andConditionGroup()->condition("$figures.color", array('red'), 'IN');
$this->queryResults = $query
->condition($group_blue)
->condition($group_red)
->sort('id')
->execute();
// Unit 0 and unit 1, so bits 0 1.
$this->assertResult(3, 7, 11, 15);
// An entity might have either red or blue figure.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition("$figures.color", array('blue', 'red'), 'IN')
->sort('id')
->execute();
// Bit 0 or 1 is on.
$this->assertResult(1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15);
$this->queryResults = $this->factory->get('entity_test_mulrev')
->exists("$figures.color")
->notExists("$greetings.value")
->sort('id')
->execute();
// Bit 0 or 1 is on but 2 and 3 are not.
$this->assertResult(1, 2, 3);
// Now update the 'merhaba' string to xsiemax which is not a meaningful
// word but allows us to test revisions and string operations.
$ids = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'merhaba')
->sort('id')
->execute();
$entities = entity_load_multiple('entity_test_mulrev', $ids);
$first_entity = reset($entities);
$old_name = $first_entity->name->value;
foreach ($entities as $entity) {
$entity->setNewRevision();
$entity->getTranslation('tr')->$greetings->value = 'xsiemax';
$entity->name->value .= 'x';
$entity->save();
}
// We changed the entity names, so the current revision should not match.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition('name.value', $old_name)
->execute();
$this->assertResult();
// Only if all revisions are queried, we find the old revision.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition('name.value', $old_name)
->allRevisions()
->sort('revision_id')
->execute();
$this->assertRevisionResult(array($first_entity->id()), array($first_entity->id()));
// When querying current revisions, this string is no longer found.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'merhaba')
->execute();
$this->assertResult();
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'merhaba')
->allRevisions()
->sort('revision_id')
->execute();
// The query only matches the original revisions.
$this->assertRevisionResult(array(4, 5, 6, 7, 12, 13, 14, 15), array(4, 5, 6, 7, 12, 13, 14, 15));
$results = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'siema', 'CONTAINS')
->sort('id')
->execute();
// This matches both the original and new current revisions, multiple
// revisions are returned for some entities.
$assert = array(16 => '4', 17 => '5', 18 => '6', 19 => '7', 8 => '8', 9 => '9', 10 => '10', 11 => '11', 20 => '12', 21 => '13', 22 => '14', 23 => '15');
$this->assertIdentical($results, $assert);
$results = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'siema', 'STARTS_WITH')
->sort('revision_id')
->execute();
// Now we only get the ones that originally were siema, entity id 8 and
// above.
$this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
$results = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'a', 'ENDS_WITH')
->sort('revision_id')
->execute();
// It is very important that we do not get the ones which only have
// xsiemax despite originally they were merhaba, ie. ended with a.
$this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
$results = $this->factory->get('entity_test_mulrev')
->condition("$greetings.value", 'a', 'ENDS_WITH')
->allRevisions()
->sort('id')
->sort('revision_id')
->execute();
// Now we get everything.
$assert = array(4 => '4', 5 => '5', 6 => '6', 7 => '7', 8 => '8', 9 => '9', 10 => '10', 11 => '11', 12 => '12', 20 => '12', 13 => '13', 21 => '13', 14 => '14', 22 => '14', 15 => '15', 23 => '15');
$this->assertIdentical($results, $assert);
}
/**
* Test sort().
*
* Warning: this is complicated.
*/
function testSort() {
$greetings = $this->greetings;
$figures = $this->figures;
// Order up and down on a number.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->sort('id')
->execute();
$this->assertResult(range(1, 15));
$this->queryResults = $this->factory->get('entity_test_mulrev')
->sort('id', 'DESC')
->execute();
$this->assertResult(range(15, 1));
$query = $this->factory->get('entity_test_mulrev')
->sort("$figures.color")
->sort("$greetings.format")
->sort('id');
// As we do not have any conditions, here are the possible colors and
// language codes, already in order, with the first occurrence of the
// entity id marked with *:
// 8 NULL pl *
// 12 NULL pl *
// 4 NULL tr *
// 12 NULL tr
// 2 blue NULL *
// 3 blue NULL *
// 10 blue pl *
// 11 blue pl *
// 14 blue pl *
// 15 blue pl *
// 6 blue tr *
// 7 blue tr *
// 14 blue tr
// 15 blue tr
// 1 red NULL
// 3 red NULL
// 9 red pl *
// 11 red pl
// 13 red pl *
// 15 red pl
// 5 red tr *
// 7 red tr
// 13 red tr
// 15 red tr
$count_query = clone $query;
$this->assertEqual(15, $count_query->count()->execute());
$this->queryResults = $query->execute();
$this->assertResult(8, 12, 4, 2, 3, 10, 11, 14, 15, 6, 7, 1, 9, 13, 5);
// Test the pager by setting element #1 to page 2 with a page size of 4.
// Results will be #8-12 from above.
$request = Request::createFromGlobals();
$request->query->replace(array(
'page' => '0,2',
));
\Drupal::getContainer()->get('request_stack')->push($request);
$this->queryResults = $this->factory->get('entity_test_mulrev')
->sort("$figures.color")
->sort("$greetings.format")
->sort('id')
->pager(4, 1)
->execute();
$this->assertResult(15, 6, 7, 1);
// Now test the reversed order.
$query = $this->factory->get('entity_test_mulrev')
->sort("$figures.color", 'DESC')
->sort("$greetings.format", 'DESC')
->sort('id', 'DESC');
$count_query = clone $query;
$this->assertEqual(15, $count_query->count()->execute());
$this->queryResults = $query->execute();
$this->assertResult(15, 13, 7, 5, 11, 9, 3, 1, 14, 6, 10, 2, 12, 4, 8);
}
/**
* Test tablesort().
*/
public function testTableSort() {
// While ordering on bundles do not give us a definite order, we can still
// assert that all entities from one bundle are after the other as the
// order dictates.
$request = Request::createFromGlobals();
$request->query->replace(array(
'sort' => 'asc',
'order' => 'Type',
));
\Drupal::getContainer()->get('request_stack')->push($request);
$header = array(
'id' => array('data' => 'Id', 'specifier' => 'id'),
'type' => array('data' => 'Type', 'specifier' => 'type'),
);
$this->queryResults = array_values($this->factory->get('entity_test_mulrev')
->tableSort($header)
->execute());
$this->assertBundleOrder('asc');
$request->query->add(array(
'sort' => 'desc',
));
\Drupal::getContainer()->get('request_stack')->push($request);
$header = array(
'id' => array('data' => 'Id', 'specifier' => 'id'),
'type' => array('data' => 'Type', 'specifier' => 'type'),
);
$this->queryResults = array_values($this->factory->get('entity_test_mulrev')
->tableSort($header)
->execute());
$this->assertBundleOrder('desc');
// Ordering on ID is definite, however.
$request->query->add(array(
'order' => 'Id',
));
\Drupal::getContainer()->get('request_stack')->push($request);
$this->queryResults = $this->factory->get('entity_test_mulrev')
->tableSort($header)
->execute();
$this->assertResult(range(15, 1));
}
/**
* Test that count queries are separated across entity types.
*/
public function testCount() {
// Create a field with the same name in a different entity type.
$field_name = $this->figures;
$field_storage = FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'shape',
'cardinality' => 2,
'translatable' => TRUE,
));
$field_storage->save();
$bundle = $this->randomMachineName();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $bundle,
])->save();
$entity = EntityTest::create(array(
'id' => 1,
'type' => $bundle,
));
$entity->enforceIsNew();
$entity->save();
// As the single entity of this type we just saved does not have a value
// in the color field, the result should be 0.
$count = $this->factory->get('entity_test')
->exists("$field_name.color")
->count()
->execute();
$this->assertFalse($count);
}
/**
* Tests that nested condition groups work as expected.
*/
public function testNestedConditionGroups() {
// Query for all entities of the first bundle that have either a red
// triangle as a figure or the Turkish greeting as a greeting.
$query = $this->factory->get('entity_test_mulrev');
$first_and = $query->andConditionGroup()
->condition($this->figures . '.color', 'red')
->condition($this->figures . '.shape', 'triangle');
$second_and = $query->andConditionGroup()
->condition($this->greetings . '.value', 'merhaba')
->condition($this->greetings . '.format', 'format-tr');
$or = $query->orConditionGroup()
->condition($first_and)
->condition($second_and);
$this->queryResults = $query
->condition($or)
->condition('type', reset($this->bundles))
->sort('id')
->execute();
$this->assertResult(6, 14);
}
protected function assertResult() {
$assert = array();
$expected = func_get_args();
if ($expected && is_array($expected[0])) {
$expected = $expected[0];
}
foreach ($expected as $binary) {
$assert[$binary] = strval($binary);
}
$this->assertIdentical($this->queryResults, $assert);
}
protected function assertRevisionResult($keys, $expected) {
$assert = array();
foreach ($expected as $key => $binary) {
$assert[$keys[$key]] = strval($binary);
}
$this->assertIdentical($this->queryResults, $assert);
return $assert;
}
protected function assertBundleOrder($order) {
// This loop is for bundle1 entities.
for ($i = 1; $i <= 15; $i +=2) {
$ok = TRUE;
$index1 = array_search($i, $this->queryResults);
$this->assertNotIdentical($index1, FALSE, "$i found at $index1.");
// This loop is for bundle2 entities.
for ($j = 2; $j <= 15; $j += 2) {
if ($ok) {
if ($order == 'asc') {
$ok = $index1 > array_search($j, $this->queryResults);
}
else {
$ok = $index1 < array_search($j, $this->queryResults);
}
}
}
$this->assertTrue($ok, "$i is after all entities in bundle2");
}
}
/**
* Test adding a tag and metadata to the Entity query object.
*
* The tags and metadata should propagate to the SQL query object.
*/
public function testMetaData() {
$query = \Drupal::entityQuery('entity_test_mulrev');
$query
->addTag('efq_metadata_test')
->addMetaData('foo', 'bar')
->execute();
global $efq_test_metadata;
$this->assertEqual($efq_test_metadata, 'bar', 'Tag and metadata propagated to the SQL query object.');
}
/**
* Test case sensitive and in-sensitive query conditions.
*/
public function testCaseSensitivity() {
$bundle = $this->randomMachineName();
$field_storage = FieldStorageConfig::create(array(
'field_name' => 'field_ci',
'entity_type' => 'entity_test_mulrev',
'type' => 'string',
'cardinality' => 1,
'translatable' => FALSE,
'settings' => array(
'case_sensitive' => FALSE,
)
));
$field_storage->save();
FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => $bundle,
))->save();
$field_storage = FieldStorageConfig::create(array(
'field_name' => 'field_cs',
'entity_type' => 'entity_test_mulrev',
'type' => 'string',
'cardinality' => 1,
'translatable' => FALSE,
'settings' => array(
'case_sensitive' => TRUE,
),
));
$field_storage->save();
FieldConfig::create(array(
'field_storage' => $field_storage,
'bundle' => $bundle,
))->save();
$fixtures = array();
for ($i = 0; $i < 2; $i++) {
// If the last 4 of the string are all numbers, then there is no
// difference between upper and lowercase and the case sensitive CONTAINS
// test will fail. Ensure that can not happen by appending a non-numeric
// character. See https://www.drupal.org/node/2397297.
$string = $this->randomMachineName(7) . 'a';
$fixtures[] = array(
'original' => $string,
'uppercase' => Unicode::strtoupper($string),
'lowercase' => Unicode::strtolower($string),
);
}
EntityTestMulRev::create(array(
'type' => $bundle,
'name' => $this->randomMachineName(),
'langcode' => 'en',
'field_ci' => $fixtures[0]['uppercase'] . $fixtures[1]['lowercase'],
'field_cs' => $fixtures[0]['uppercase'] . $fixtures[1]['lowercase']
))->save();
// Check the case insensitive field, = operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase']
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, lowercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase']
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, uppercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase']
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, mixed.');
// Check the case sensitive field, = operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', $fixtures[0]['lowercase'] . $fixtures[1]['lowercase']
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, lowercase.');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['uppercase']
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, uppercase.');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', $fixtures[0]['uppercase'] . $fixtures[1]['lowercase']
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
// Check the case insensitive field, IN operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', array($fixtures[0]['lowercase'] . $fixtures[1]['lowercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, lowercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', array($fixtures[0]['uppercase'] . $fixtures[1]['uppercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, uppercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', array($fixtures[0]['uppercase'] . $fixtures[1]['lowercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, mixed');
// Check the case sensitive field, IN operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', array($fixtures[0]['lowercase'] . $fixtures[1]['lowercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, lowercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', array($fixtures[0]['uppercase'] . $fixtures[1]['uppercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, uppercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', array($fixtures[0]['uppercase'] . $fixtures[1]['lowercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, mixed');
// Check the case insensitive field, STARTS_WITH operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', $fixtures[0]['lowercase'], 'STARTS_WITH'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', $fixtures[0]['uppercase'], 'STARTS_WITH'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
// Check the case sensitive field, STARTS_WITH operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', $fixtures[0]['lowercase'], 'STARTS_WITH'
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, lowercase.');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', $fixtures[0]['uppercase'], 'STARTS_WITH'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
// Check the case insensitive field, ENDS_WITH operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', $fixtures[1]['lowercase'], 'ENDS_WITH'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', $fixtures[1]['uppercase'], 'ENDS_WITH'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
// Check the case sensitive field, ENDS_WITH operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', $fixtures[1]['lowercase'], 'ENDS_WITH'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', $fixtures[1]['uppercase'], 'ENDS_WITH'
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, exact match.');
// Check the case insensitive field, CONTAINS operator, use the inner 8
// characters of the uppercase and lowercase strings.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', Unicode::strtolower(Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
// Check the case sensitive field, CONTAINS operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8), 'CONTAINS'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, lowercase.');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', Unicode::strtolower(Unicode::substr($fixtures[0]['uppercase'] . $fixtures[1]['lowercase'], 4, 8)), 'CONTAINS'
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, exact match.');
}
/**
* Test base fields with multiple columns.
*/
public function testBaseFieldMultipleColumns() {
$this->enableModules(['taxonomy']);
$this->installEntitySchema('taxonomy_term');
Vocabulary::create(['vid' => 'tags']);
$term1 = Term::create([
'name' => $this->randomMachineName(),
'vid' => 'tags',
'description' => array(
'value' => $this->randomString(),
'format' => 'format1',
)]);
$term1->save();
$term2 = Term::create([
'name' => $this->randomMachineName(),
'vid' => 'tags',
'description' => array(
'value' => $this->randomString(),
'format' => 'format2',
)]);
$term2->save();
$ids = \Drupal::entityQuery('taxonomy_term')
->condition('description.format', 'format1')
->execute();
$this->assertEqual(count($ids), 1);
$this->assertEqual($term1->id(), reset($ids));
}
/**
* Test forward-revisions.
*/
public function testForwardRevisions() {
// Ensure entity 14 is returned.
$result = \Drupal::entityQuery('entity_test_mulrev')
->condition('id', [14], 'IN')
->execute();
$this->assertEqual(count($result), 1);
// Set a revision on entity 14 that isn't the current default.
$entity = EntityTestMulRev::load(14);
$current_values = $entity->{$this->figures}->getValue();
$entity->setNewRevision(TRUE);
$entity->isDefaultRevision(FALSE);
$entity->{$this->figures}->setValue([
'color' => 'red',
'shape' => 'square'
]);
$entity->save();
// Entity query should still return entity 14.
$result = \Drupal::entityQuery('entity_test_mulrev')
->condition('id', [14], 'IN')
->execute();
$this->assertEqual(count($result), 1);
// Verify that field conditions on the default and forward revision are
// work as expected.
$result = \Drupal::entityQuery('entity_test_mulrev')
->condition('id', [14], 'IN')
->condition("$this->figures.color", $current_values[0]['color'])
->execute();
$this->assertEqual($result, [14 => '14']);
$result = $this->factory->get('entity_test_mulrev')
->condition('id', [14], 'IN')
->condition("$this->figures.color", 'red')
->allRevisions()
->execute();
$this->assertEqual($result, [16 => '14']);
}
/**
* Test against SQL inject of condition field. This covers a
* database driver's EntityQuery\Condition class.
*/
public function testInjectionInCondition() {
try {
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition('1 ; -- ', array(0, 1), 'IN')
->sort('id')
->execute();
$this->fail('SQL Injection attempt in Entity Query condition in operator should result in an exception.');
}
catch (\Exception $e) {
$this->pass('SQL Injection attempt in Entity Query condition in operator should result in an exception.');
}
}
}

View file

@ -0,0 +1,454 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\RoleInterface;
use Drupal\user\UserInterface;
use Drupal\entity_test\Entity\EntityTestStringId;
/**
* Tests for the entity reference field.
*
* @group Entity
*/
class EntityReferenceFieldTest extends EntityKernelTestBase {
use SchemaCheckTestTrait;
use EntityReferenceTestTrait;
/**
* The entity type used in this test.
*
* @var string
*/
protected $entityType = 'entity_test';
/**
* The entity type that is being referenced.
*
* @var string
*/
protected $referencedEntityType = 'entity_test_rev';
/**
* The bundle used in this test.
*
* @var string
*/
protected $bundle = 'entity_test';
/**
* The name of the field used in this test.
*
* @var string
*/
protected $fieldName = 'field_test';
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('entity_reference_test');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_rev');
// Create a field.
$this->createEntityReferenceField(
$this->entityType,
$this->bundle,
$this->fieldName,
'Field test',
$this->referencedEntityType,
'default',
array('target_bundles' => array($this->bundle)),
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
);
}
/**
* Tests reference field validation.
*/
public function testEntityReferenceFieldValidation() {
// Test a valid reference.
$referenced_entity = $this->container->get('entity_type.manager')
->getStorage($this->referencedEntityType)
->create(array('type' => $this->bundle));
$referenced_entity->save();
$entity = $this->container->get('entity_type.manager')
->getStorage($this->entityType)
->create(array('type' => $this->bundle));
$entity->{$this->fieldName}->target_id = $referenced_entity->id();
$violations = $entity->{$this->fieldName}->validate();
$this->assertEqual($violations->count(), 0, 'Validation passes.');
// Test an invalid reference.
$entity->{$this->fieldName}->target_id = 9999;
$violations = $entity->{$this->fieldName}->validate();
$this->assertEqual($violations->count(), 1, 'Validation throws a violation.');
$this->assertEqual($violations[0]->getMessage(), t('The referenced entity (%type: %id) does not exist.', array('%type' => $this->referencedEntityType, '%id' => 9999)));
// Test a non-referenceable bundle.
entity_test_create_bundle('non_referenceable', NULL, $this->referencedEntityType);
$referenced_entity = entity_create($this->referencedEntityType, array('type' => 'non_referenceable'));
$referenced_entity->save();
$entity->{$this->fieldName}->target_id = $referenced_entity->id();
$violations = $entity->{$this->fieldName}->validate();
$this->assertEqual($violations->count(), 1, 'Validation throws a violation.');
$this->assertEqual($violations[0]->getMessage(), t('This entity (%type: %id) cannot be referenced.', array('%type' => $this->referencedEntityType, '%id' => $referenced_entity->id())));
}
/**
* Tests the multiple target entities loader.
*/
public function testReferencedEntitiesMultipleLoad() {
// Create the parent entity.
$entity = $this->container->get('entity_type.manager')
->getStorage($this->entityType)
->create(array('type' => $this->bundle));
// Create three target entities and attach them to parent field.
$target_entities = array();
$reference_field = array();
for ($i = 0; $i < 3; $i++) {
$target_entity = $this->container->get('entity_type.manager')
->getStorage($this->referencedEntityType)
->create(array('type' => $this->bundle));
$target_entity->save();
$target_entities[] = $target_entity;
$reference_field[]['target_id'] = $target_entity->id();
}
// Also attach a non-existent entity and a NULL target id.
$reference_field[3]['target_id'] = 99999;
$target_entities[3] = NULL;
$reference_field[4]['target_id'] = NULL;
$target_entities[4] = NULL;
// Attach the first created target entity as the sixth item ($delta == 5) of
// the parent entity field. We want to test the case when the same target
// entity is referenced twice (or more times) in the same entity reference
// field.
$reference_field[5] = $reference_field[0];
$target_entities[5] = $target_entities[0];
// Create a new target entity that is not saved, thus testing the
// "autocreate" feature.
$target_entity_unsaved = $this->container->get('entity_type.manager')
->getStorage($this->referencedEntityType)
->create(array('type' => $this->bundle, 'name' => $this->randomString()));
$reference_field[6]['entity'] = $target_entity_unsaved;
$target_entities[6] = $target_entity_unsaved;
// Set the field value.
$entity->{$this->fieldName}->setValue($reference_field);
// Load the target entities using EntityReferenceField::referencedEntities().
$entities = $entity->{$this->fieldName}->referencedEntities();
// Test returned entities:
// - Deltas must be preserved.
// - Non-existent entities must not be retrieved in target entities result.
foreach ($target_entities as $delta => $target_entity) {
if (!empty($target_entity)) {
if (!$target_entity->isNew()) {
// There must be an entity in the loaded set having the same id for
// the same delta.
$this->assertEqual($target_entity->id(), $entities[$delta]->id());
}
else {
// For entities that were not yet saved, there must an entity in the
// loaded set having the same label for the same delta.
$this->assertEqual($target_entity->label(), $entities[$delta]->label());
}
}
else {
// A non-existent or NULL entity target id must not return any item in
// the target entities set.
$this->assertFalse(isset($entities[$delta]));
}
}
}
/**
* Tests referencing entities with string IDs.
*/
public function testReferencedEntitiesStringId() {
$field_name = 'entity_reference_string_id';
$this->installEntitySchema('entity_test_string_id');
$this->createEntityReferenceField(
$this->entityType,
$this->bundle,
$field_name,
'Field test',
'entity_test_string_id',
'default',
array('target_bundles' => array($this->bundle)),
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
);
// Create the parent entity.
$entity = $this->container->get('entity_type.manager')
->getStorage($this->entityType)
->create(array('type' => $this->bundle));
// Create the default target entity.
$target_entity = EntityTestStringId::create([
'id' => $this->randomString(),
'type' => $this->bundle
]);
$target_entity->save();
// Set the field value.
$entity->{$field_name}->setValue(array(array('target_id' => $target_entity->id())));
// Load the target entities using EntityReferenceField::referencedEntities().
$entities = $entity->{$field_name}->referencedEntities();
$this->assertEqual($entities[0]->id(), $target_entity->id());
// Test that a string ID works as a default value and the field's config
// schema is correct.
$field = FieldConfig::loadByName($this->entityType, $this->bundle, $field_name);
$field->setDefaultValue($target_entity->id());
$field->save();
$this->assertConfigSchema(\Drupal::service('config.typed'), 'field.field.' . $field->id(), $field->toArray());
// Test that the default value works.
$entity = $this->container->get('entity_type.manager')
->getStorage($this->entityType)
->create(array('type' => $this->bundle));
$entities = $entity->{$field_name}->referencedEntities();
$this->assertEqual($entities[0]->id(), $target_entity->id());
}
/**
* Tests all the possible ways to autocreate an entity via the API.
*/
function testAutocreateApi() {
$entity = $this->entityManager
->getStorage($this->entityType)
->create(array('name' => $this->randomString()));
// Test content entity autocreation.
$this->assertUserAutocreate($entity, function(EntityInterface $entity, UserInterface $user) {
$entity->set('user_id', $user);
});
$this->assertUserAutocreate($entity, function(EntityInterface $entity, UserInterface $user) {
$entity->set('user_id', $user, FALSE);
});
$this->assertUserAutocreate($entity, function(EntityInterface $entity, UserInterface $user) {
$entity->user_id->setValue($user);
});
$this->assertUserAutocreate($entity, function(EntityInterface $entity, UserInterface $user) {
$entity->user_id[0]->get('entity')->setValue($user);
});
$this->assertUserAutocreate($entity, function(EntityInterface $entity, UserInterface $user) {
$entity->user_id->setValue(array('entity' => $user, 'target_id' => NULL));
});
try {
$message = 'Setting both the entity and an invalid target_id property fails.';
$this->assertUserAutocreate($entity, function(EntityInterface $entity, UserInterface $user) {
$user->save();
$entity->user_id->setValue(array('entity' => $user, 'target_id' => $this->generateRandomEntityId()));
});
$this->fail($message);
}
catch (\InvalidArgumentException $e) {
$this->pass($message);
}
$this->assertUserAutocreate($entity, function(EntityInterface $entity, UserInterface $user) {
$entity->user_id = $user;
});
$this->assertUserAutocreate($entity, function(EntityInterface $entity, UserInterface $user) {
$entity->user_id->entity = $user;
});
// Test config entity autocreation.
$this->assertUserRoleAutocreate($entity, function(EntityInterface $entity, RoleInterface $role) {
$entity->set('user_role', $role);
});
$this->assertUserRoleAutocreate($entity, function(EntityInterface $entity, RoleInterface $role) {
$entity->set('user_role', $role, FALSE);
});
$this->assertUserRoleAutocreate($entity, function(EntityInterface $entity, RoleInterface $role) {
$entity->user_role->setValue($role);
});
$this->assertUserRoleAutocreate($entity, function(EntityInterface $entity, RoleInterface $role) {
$entity->user_role[0]->get('entity')->setValue($role);
});
$this->assertUserRoleAutocreate($entity, function(EntityInterface $entity, RoleInterface $role) {
$entity->user_role->setValue(array('entity' => $role, 'target_id' => NULL));
});
try {
$message = 'Setting both the entity and an invalid target_id property fails.';
$this->assertUserRoleAutocreate($entity, function(EntityInterface $entity, RoleInterface $role) {
$role->save();
$entity->user_role->setValue(array('entity' => $role, 'target_id' => $this->generateRandomEntityId(TRUE)));
});
$this->fail($message);
}
catch (\InvalidArgumentException $e) {
$this->pass($message);
}
$this->assertUserRoleAutocreate($entity, function(EntityInterface $entity, RoleInterface $role) {
$entity->user_role = $role;
});
$this->assertUserRoleAutocreate($entity, function(EntityInterface $entity, RoleInterface $role) {
$entity->user_role->entity = $role;
});
// Test target entity saving after setting it as new.
$storage = $this->entityManager->getStorage('user');
$user_id = $this->generateRandomEntityId();
$user = $storage->create(array('uid' => $user_id, 'name' => $this->randomString()));
$entity->user_id = $user;
$user->save();
$entity->save();
$this->assertEqual($entity->user_id->target_id, $user->id());
}
/**
* Asserts that the setter callback performs autocreation for users.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The referencing entity.
* @param $setter_callback
* A callback setting the target entity on the referencing entity.
*
* @return bool
* TRUE if the user was autocreated, FALSE otherwise.
*/
protected function assertUserAutocreate(EntityInterface $entity, $setter_callback) {
$storage = $this->entityManager->getStorage('user');
$user_id = $this->generateRandomEntityId();
$user = $storage->create(array('uid' => $user_id, 'name' => $this->randomString()));
$setter_callback($entity, $user);
$entity->save();
$storage->resetCache();
$user = User::load($user_id);
return $this->assertEqual($entity->user_id->target_id, $user->id());
}
/**
* Asserts that the setter callback performs autocreation for user roles.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The referencing entity.
* @param $setter_callback
* A callback setting the target entity on the referencing entity.
*
* @return bool
* TRUE if the user was autocreated, FALSE otherwise.
*/
protected function assertUserRoleAutocreate(EntityInterface $entity, $setter_callback) {
$storage = $this->entityManager->getStorage('user_role');
$role_id = $this->generateRandomEntityId(TRUE);
$role = $storage->create(array('id' => $role_id, 'label' => $this->randomString()));
$setter_callback($entity, $role);
$entity->save();
$storage->resetCache();
$role = Role::load($role_id);
return $this->assertEqual($entity->user_role->target_id, $role->id());
}
/**
* Tests that the target entity is not unnecessarily loaded.
*/
public function testTargetEntityNoLoad() {
// Setup a test entity type with an entity reference field to itself. We use
// a special storage class throwing exceptions when a load operation is
// triggered to be able to detect them.
$entity_type = clone $this->entityManager->getDefinition('entity_test_update');
$entity_type->setHandlerClass('storage', '\Drupal\entity_test\EntityTestNoLoadStorage');
$this->state->set('entity_test_update.entity_type', $entity_type);
$definitions = array(
'target_reference' => BaseFieldDefinition::create('entity_reference')
->setSetting('target_type', $entity_type->id())
->setSetting('handler', 'default')
);
$this->state->set('entity_test_update.additional_base_field_definitions', $definitions);
$this->entityManager->clearCachedDefinitions();
$this->installEntitySchema($entity_type->id());
// Create the target entity.
$storage = $this->entityManager->getStorage($entity_type->id());
$target_id = $this->generateRandomEntityId();
$target = $storage->create(array('id' => $target_id, 'name' => $this->randomString()));
$target->save();
$this->assertEqual($target_id, $target->id(), 'The target entity has a random identifier.');
// Check that populating the reference with an existing target id does not
// trigger a load operation.
$message = 'The target entity was not loaded.';
try {
$entity = $this->entityManager
->getStorage($entity_type->id())
->create(array('name' => $this->randomString()));
$entity->target_reference = $target_id;
$this->pass($message);
}
catch (EntityStorageException $e) {
$this->fail($message);
}
// Check that the storage actually triggers the expected exception when
// trying to load the target entity.
$message = 'An exception is thrown when trying to load the target entity';
try {
$storage->load($target_id);
$this->fail($message);
}
catch (EntityStorageException $e) {
$this->pass($message);
}
}
/**
* Tests the dependencies entity reference fields are created with.
*/
public function testEntityReferenceFieldDependencies() {
$field_name = 'user_reference_field';
$entity_type = 'entity_test';
$field_storage = FieldStorageConfig::create([
'field_name' => $field_name,
'type' => 'entity_reference',
'entity_type' => $entity_type,
'settings' => [
'target_type' => 'user',
],
]);
$field_storage->save();
$this->assertEqual(['module' => ['entity_test', 'user']], $field_storage->getDependencies());
$field = FieldConfig::create([
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => 'entity_test',
'label' => $field_name,
'settings' => [
'handler' => 'default',
],
]);
$field->save();
$this->assertEqual(['config' => ['field.storage.entity_test.user_reference_field'], 'module' => ['entity_test']], $field->getDependencies());
}
}

View file

@ -0,0 +1,133 @@
<?php
namespace Drupal\KernelTests\Core\Entity\EntityReferenceSelection;
use Drupal\Component\Utility\Html;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\system\Tests\Entity\EntityUnitTestBase;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests sorting referenced items.
*
* @group entity_reference
*/
class EntityReferenceSelectionSortTest extends EntityUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node');
protected function setUp() {
parent::setUp();
// Create an Article node type.
$article = NodeType::create(array(
'type' => 'article',
));
$article->save();
// Test as a non-admin.
$normal_user = $this->createUser(array(), array('access content'));
\Drupal::currentUser()->setAccount($normal_user);
}
/**
* Assert sorting by field and property.
*/
public function testSort() {
// Add text field to entity, to sort by.
FieldStorageConfig::create(array(
'field_name' => 'field_text',
'entity_type' => 'node',
'type' => 'text',
'entity_types' => array('node'),
))->save();
FieldConfig::create([
'label' => 'Text Field',
'field_name' => 'field_text',
'entity_type' => 'node',
'bundle' => 'article',
'settings' => array(),
'required' => FALSE,
])->save();
// Build a set of test data.
$node_values = array(
'published1' => array(
'type' => 'article',
'status' => 1,
'title' => 'Node published1 (<&>)',
'uid' => 1,
'field_text' => array(
array(
'value' => 1,
),
),
),
'published2' => array(
'type' => 'article',
'status' => 1,
'title' => 'Node published2 (<&>)',
'uid' => 1,
'field_text' => array(
array(
'value' => 2,
),
),
),
);
$nodes = array();
$node_labels = array();
foreach ($node_values as $key => $values) {
$node = Node::create($values);
$node->save();
$nodes[$key] = $node;
$node_labels[$key] = Html::escape($node->label());
}
$selection_options = array(
'target_type' => 'node',
'handler' => 'default',
'handler_settings' => array(
'target_bundles' => NULL,
// Add sorting.
'sort' => array(
'field' => 'field_text.value',
'direction' => 'DESC',
),
),
);
$handler = $this->container->get('plugin.manager.entity_reference_selection')->getInstance($selection_options);
// Not only assert the result, but make sure the keys are sorted as
// expected.
$result = $handler->getReferenceableEntities();
$expected_result = array(
$nodes['published2']->id() => $node_labels['published2'],
$nodes['published1']->id() => $node_labels['published1'],
);
$this->assertIdentical($result['article'], $expected_result, 'Query sorted by field returned expected values.');
// Assert sort by base field.
$selection_options['handler_settings']['sort'] = array(
'field' => 'nid',
'direction' => 'ASC',
);
$handler = $this->container->get('plugin.manager.entity_reference_selection')->getInstance($selection_options);
$result = $handler->getReferenceableEntities();
$expected_result = array(
$nodes['published1']->id() => $node_labels['published1'],
$nodes['published2']->id() => $node_labels['published2'],
);
$this->assertIdentical($result['article'], $expected_result, 'Query sorted by property returned expected values.');
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests proper revision propagation of entities.
*
* @group Entity
*/
class EntityRevisionTranslationTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable an additional language.
ConfigurableLanguage::createFromLangcode('de')->save();
$this->installEntitySchema('entity_test_mulrev');
}
/**
* Tests if the translation object has the right revision id after new revision.
*/
public function testNewRevisionAfterTranslation() {
$user = $this->createUser();
// Create a test entity.
$entity = EntityTestMulRev::create([
'name' => $this->randomString(),
'user_id' => $user->id(),
'language' => 'en',
]);
$entity->save();
$old_rev_id = $entity->getRevisionId();
$translation = $entity->addTranslation('de');
$translation->setNewRevision();
$translation->save();
$this->assertTrue($translation->getRevisionId() > $old_rev_id, 'The saved translation in new revision has a newer revision id.');
$this->assertTrue($this->reloadEntity($entity)->getRevisionId() > $old_rev_id, 'The entity from the storage has a newer revision id.');
}
/**
* Tests if the translation object has the right revision id after new revision.
*/
public function testRevertRevisionAfterTranslation() {
$user = $this->createUser();
$storage = $this->entityManager->getStorage('entity_test_mulrev');
// Create a test entity.
$entity = EntityTestMulRev::create([
'name' => $this->randomString(),
'user_id' => $user->id(),
'language' => 'en',
]);
$entity->save();
$old_rev_id = $entity->getRevisionId();
$translation = $entity->addTranslation('de');
$translation->setNewRevision();
$translation->save();
$entity = $this->reloadEntity($entity);
$this->assertTrue($entity->hasTranslation('de'));
$entity = $storage->loadRevision($old_rev_id);
$entity->setNewRevision();
$entity->isDefaultRevision(TRUE);
$entity->save();
$entity = $this->reloadEntity($entity);
$this->assertFalse($entity->hasTranslation('de'));
}
}

View file

@ -0,0 +1,191 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Component\Utility\SafeMarkup;
/**
* Tests adding a custom bundle field.
*
* @group system
*/
class EntitySchemaTest extends EntityKernelTestBase {
/**
* The database connection used.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('user', array('users_data'));
$this->database = $this->container->get('database');
}
/**
* Tests the custom bundle field creation and deletion.
*/
public function testCustomFieldCreateDelete() {
// Install the module which adds the field.
$this->installModule('entity_schema_test');
$this->entityManager->clearCachedDefinitions();
$storage_definitions = $this->entityManager->getFieldStorageDefinitions('entity_test');
$this->assertNotNull($storage_definitions['custom_base_field'], 'Base field definition found.');
$this->assertNotNull($storage_definitions['custom_bundle_field'], 'Bundle field definition found.');
// Make sure the field schema can be created.
$this->entityManager->onFieldStorageDefinitionCreate($storage_definitions['custom_base_field']);
$this->entityManager->onFieldStorageDefinitionCreate($storage_definitions['custom_bundle_field']);
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $this->entityManager->getStorage('entity_test')->getTableMapping();
$base_table = current($table_mapping->getTableNames());
$base_column = current($table_mapping->getColumnNames('custom_base_field'));
$this->assertTrue($this->database->schema()->fieldExists($base_table, $base_column), 'Table column created');
$table = $table_mapping->getDedicatedDataTableName($storage_definitions['custom_bundle_field']);
$this->assertTrue($this->database->schema()->tableExists($table), 'Table created');
// Make sure the field schema can be deleted.
$this->entityManager->onFieldStorageDefinitionDelete($storage_definitions['custom_base_field']);
$this->entityManager->onFieldStorageDefinitionDelete($storage_definitions['custom_bundle_field']);
$this->assertFalse($this->database->schema()->fieldExists($base_table, $base_column), 'Table column dropped');
$this->assertFalse($this->database->schema()->tableExists($table), 'Table dropped');
}
/**
* Updates the entity type definition.
*
* @param bool $alter
* Whether the original definition should be altered or not.
*/
protected function updateEntityType($alter) {
$entity_test_id = 'entity_test';
$original = $this->entityManager->getDefinition($entity_test_id);
$this->entityManager->clearCachedDefinitions();
$this->state->set('entity_schema_update', $alter);
$entity_type = $this->entityManager->getDefinition($entity_test_id);
$this->entityManager->onEntityTypeUpdate($entity_type, $original);
}
/**
* Tests that entity schema responds to changes in the entity type definition.
*/
public function testEntitySchemaUpdate() {
$this->installModule('entity_schema_test');
$storage_definitions = $this->entityManager->getFieldStorageDefinitions('entity_test');
$this->entityManager->onFieldStorageDefinitionCreate($storage_definitions['custom_base_field']);
$this->entityManager->onFieldStorageDefinitionCreate($storage_definitions['custom_bundle_field']);
$schema_handler = $this->database->schema();
$tables = array('entity_test', 'entity_test_revision', 'entity_test_field_data', 'entity_test_field_revision');
$dedicated_tables = array('entity_test__custom_bundle_field', 'entity_test_revision__custom_bundle_field');
// Initially only the base table and the dedicated field data table should
// exist.
foreach ($tables as $index => $table) {
$this->assertEqual($schema_handler->tableExists($table), !$index, SafeMarkup::format('Entity schema correct for the @table table.', array('@table' => $table)));
}
$this->assertTrue($schema_handler->tableExists($dedicated_tables[0]), SafeMarkup::format('Field schema correct for the @table table.', array('@table' => $table)));
// Update the entity type definition and check that the entity schema now
// supports translations and revisions.
$this->updateEntityType(TRUE);
foreach ($tables as $table) {
$this->assertTrue($schema_handler->tableExists($table), SafeMarkup::format('Entity schema correct for the @table table.', array('@table' => $table)));
}
foreach ($dedicated_tables as $table) {
$this->assertTrue($schema_handler->tableExists($table), SafeMarkup::format('Field schema correct for the @table table.', array('@table' => $table)));
}
// Revert changes and check that the entity schema now does not support
// neither translations nor revisions.
$this->updateEntityType(FALSE);
foreach ($tables as $index => $table) {
$this->assertEqual($schema_handler->tableExists($table), !$index, SafeMarkup::format('Entity schema correct for the @table table.', array('@table' => $table)));
}
$this->assertTrue($schema_handler->tableExists($dedicated_tables[0]), SafeMarkup::format('Field schema correct for the @table table.', array('@table' => $table)));
}
/**
* {@inheritdoc}
*/
protected function refreshServices() {
parent::refreshServices();
$this->database = $this->container->get('database');
}
/**
* Tests that modifying the UUID field for a translatable entity works.
*/
public function testModifyingTranslatableColumnSchema() {
$this->installModule('entity_schema_test');
$this->updateEntityType(TRUE);
$fields = ['revision_log', 'uuid'];
foreach ($fields as $field_name) {
$original_definition = $this->entityManager->getBaseFieldDefinitions('entity_test')[$field_name];
$new_definition = clone $original_definition;
$new_definition->setLabel($original_definition->getLabel() . ', the other one');
$this->assertTrue($this->entityManager->getStorage('entity_test')
->requiresFieldDataMigration($new_definition, $original_definition));
}
}
/**
* Tests fields from an uninstalled module are removed from the schema.
*/
public function testCleanUpStorageDefinition() {
// Find all the entity types provided by the entity_test module and install
// the schema for them.
$entity_type_ids = [];
$entities = \Drupal::entityManager()->getDefinitions();
foreach ($entities as $entity_type_id => $definition) {
if ($definition->getProvider() == 'entity_test') {
$this->installEntitySchema($entity_type_id);
$entity_type_ids[] = $entity_type_id;
};
}
// Get a list of all the entities in the schema.
$key_value_store = \Drupal::keyValue('entity.storage_schema.sql');
$schema = $key_value_store->getAll();
// Count the storage definitions provided by the entity_test module, so that
// after uninstall we can be sure there were some to be deleted.
$entity_type_id_count = 0;
foreach (array_keys($schema) as $storage_definition_name) {
list($entity_type_id, ,) = explode('.', $storage_definition_name);
if (in_array($entity_type_id, $entity_type_ids)) {
$entity_type_id_count++;
}
}
// Ensure that there are storage definitions from the entity_test module.
$this->assertNotEqual($entity_type_id_count, 0, 'There are storage definitions provided by the entity_test module in the schema.');
// Uninstall the entity_test module.
$this->container->get('module_installer')->uninstall(array('entity_test'));
// Get a list of all the entities in the schema.
$key_value_store = \Drupal::keyValue('entity.storage_schema.sql');
$schema = $key_value_store->getAll();
// Count the storage definitions that come from entity types provided by
// the entity_test module.
$entity_type_id_count = 0;
foreach (array_keys($schema) as $storage_definition_name) {
list($entity_type_id, ,) = explode('.', $storage_definition_name);
if (in_array($entity_type_id, $entity_type_ids)) {
$entity_type_id_count++;
}
}
// Ensure that all storage definitions have been removed from the schema.
$this->assertEqual($entity_type_id_count, 0, 'After uninstalling entity_test module the schema should not contains fields from entities provided by the module.');
}
}

View file

@ -0,0 +1,932 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests entity translation functionality.
*
* @group Entity
*/
class EntityTranslationTest extends EntityLanguageTestBase {
/**
* Tests language related methods of the Entity class.
*/
public function testEntityLanguageMethods() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->doTestEntityLanguageMethods($entity_type);
}
}
/**
* Executes the entity language method tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestEntityLanguageMethods($entity_type) {
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
'name' => 'test',
'user_id' => $this->container->get('current_user')->id(),
));
$this->assertEqual($entity->language()->getId(), $this->languageManager->getDefaultLanguage()->getId(), format_string('%entity_type: Entity created with API has default language.', array('%entity_type' => $entity_type)));
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
'name' => 'test',
'user_id' => \Drupal::currentUser()->id(),
$langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED,
));
$this->assertEqual($entity->language()->getId(), LanguageInterface::LANGCODE_NOT_SPECIFIED, format_string('%entity_type: Entity language not specified.', array('%entity_type' => $entity_type)));
$this->assertFalse($entity->getTranslationLanguages(FALSE), format_string('%entity_type: No translations are available', array('%entity_type' => $entity_type)));
// Set the value in default language.
$entity->set($this->fieldName, array(0 => array('value' => 'default value')));
// Get the value.
$field = $entity->getTranslation(LanguageInterface::LANGCODE_DEFAULT)->get($this->fieldName);
$this->assertEqual($field->value, 'default value', format_string('%entity_type: Untranslated value retrieved.', array('%entity_type' => $entity_type)));
$this->assertEqual($field->getLangcode(), LanguageInterface::LANGCODE_NOT_SPECIFIED, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
// Try to get add a translation to language neutral entity.
$message = 'Adding a translation to a language-neutral entity results in an error.';
try {
$entity->addTranslation($this->langcodes[1]);
$this->fail($message);
}
catch (\InvalidArgumentException $e) {
$this->pass($message);
}
// Now, make the entity language-specific by assigning a language and test
// translating it.
$default_langcode = $this->langcodes[0];
$entity->{$langcode_key}->value = $default_langcode;
$entity->{$this->fieldName} = array();
$this->assertEqual($entity->language(), \Drupal::languageManager()->getLanguage($this->langcodes[0]), format_string('%entity_type: Entity language retrieved.', array('%entity_type' => $entity_type)));
$this->assertFalse($entity->getTranslationLanguages(FALSE), format_string('%entity_type: No translations are available', array('%entity_type' => $entity_type)));
// Set the value in default language.
$entity->set($this->fieldName, array(0 => array('value' => 'default value')));
// Get the value.
$field = $entity->get($this->fieldName);
$this->assertEqual($field->value, 'default value', format_string('%entity_type: Untranslated value retrieved.', array('%entity_type' => $entity_type)));
$this->assertEqual($field->getLangcode(), $default_langcode, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
// Set a translation.
$entity->addTranslation($this->langcodes[1])->set($this->fieldName, array(0 => array('value' => 'translation 1')));
$field = $entity->getTranslation($this->langcodes[1])->{$this->fieldName};
$this->assertEqual($field->value, 'translation 1', format_string('%entity_type: Translated value set.', array('%entity_type' => $entity_type)));
$this->assertEqual($field->getLangcode(), $this->langcodes[1], format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
// Make sure the untranslated value stays.
$field = $entity->get($this->fieldName);
$this->assertEqual($field->value, 'default value', 'Untranslated value stays.');
$this->assertEqual($field->getLangcode(), $default_langcode, 'Untranslated value has the expected langcode.');
$translations[$this->langcodes[1]] = \Drupal::languageManager()->getLanguage($this->langcodes[1]);
$this->assertEqual($entity->getTranslationLanguages(FALSE), $translations, 'Translations retrieved.');
// Try to get a value using a language code for a non-existing translation.
$message = 'Getting a non existing translation results in an error.';
try {
$entity->getTranslation($this->langcodes[2])->get($this->fieldName)->value;
$this->fail($message);
}
catch (\InvalidArgumentException $e) {
$this->pass($message);
}
// Try to get a not available translation.
$this->assertNull($entity->addTranslation($this->langcodes[2])->get($this->fieldName)->value, format_string('%entity_type: A translation that is not available is NULL.', array('%entity_type' => $entity_type)));
// Try to get a value using an invalid language code.
$message = 'Getting an invalid translation results in an error.';
try {
$entity->getTranslation('invalid')->get($this->fieldName)->value;
$this->fail($message);
}
catch (\InvalidArgumentException $e) {
$this->pass($message);
}
// Try to set a value using an invalid language code.
try {
$entity->getTranslation('invalid')->set($this->fieldName, NULL);
$this->fail(format_string('%entity_type: Setting a translation for an invalid language throws an exception.', array('%entity_type' => $entity_type)));
}
catch (\InvalidArgumentException $e) {
$this->pass(format_string('%entity_type: Setting a translation for an invalid language throws an exception.', array('%entity_type' => $entity_type)));
}
// Set the value in default language.
$field_name = 'field_test_text';
$entity->getTranslation($this->langcodes[1])->set($field_name, array(0 => array('value' => 'default value2')));
// Get the value.
$field = $entity->get($field_name);
$this->assertEqual($field->value, 'default value2', format_string('%entity_type: Untranslated value set into a translation in non-strict mode.', array('%entity_type' => $entity_type)));
$this->assertEqual($field->getLangcode(), $default_langcode, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
}
/**
* Tests multilingual properties.
*/
public function testMultilingualProperties() {
// Test all entity variations with data table support.
foreach (entity_test_entity_types(ENTITY_TEST_TYPES_MULTILINGUAL) as $entity_type) {
$this->doTestMultilingualProperties($entity_type);
}
}
/**
* Executes the multilingual property tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestMultilingualProperties($entity_type) {
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$default_langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('default_langcode');
$name = $this->randomMachineName();
$uid = mt_rand(0, 127);
$langcode = $this->langcodes[0];
// Create a language neutral entity and check that properties are stored
// as language neutral.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('name' => $name, 'user_id' => $uid, $langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED));
$entity->save();
$entity = entity_load($entity_type, $entity->id());
$default_langcode = $entity->language()->getId();
$this->assertEqual($default_langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED, format_string('%entity_type: Entity created as language neutral.', array('%entity_type' => $entity_type)));
$field = $entity->getTranslation(LanguageInterface::LANGCODE_DEFAULT)->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name has been correctly stored as language neutral.', array('%entity_type' => $entity_type)));
$this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->getTranslation(LanguageInterface::LANGCODE_DEFAULT)->get('user_id')->target_id, format_string('%entity_type: The entity author has been correctly stored as language neutral.', array('%entity_type' => $entity_type)));
$translation = $entity->getTranslation(LanguageInterface::LANGCODE_DEFAULT);
$field = $translation->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name defaults to neutral language.', array('%entity_type' => $entity_type)));
$this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $translation->get('user_id')->target_id, format_string('%entity_type: The entity author defaults to neutral language.', array('%entity_type' => $entity_type)));
$field = $entity->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name can be retrieved without specifying a language.', array('%entity_type' => $entity_type)));
$this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->get('user_id')->target_id, format_string('%entity_type: The entity author can be retrieved without specifying a language.', array('%entity_type' => $entity_type)));
// Create a language-aware entity and check that properties are stored
// as language-aware.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('name' => $name, 'user_id' => $uid, $langcode_key => $langcode));
$entity->save();
$entity = entity_load($entity_type, $entity->id());
$default_langcode = $entity->language()->getId();
$this->assertEqual($default_langcode, $langcode, format_string('%entity_type: Entity created as language specific.', array('%entity_type' => $entity_type)));
$field = $entity->getTranslation($langcode)->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name has been correctly stored as a language-aware property.', array('%entity_type' => $entity_type)));
$this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->getTranslation($langcode)->get('user_id')->target_id, format_string('%entity_type: The entity author has been correctly stored as a language-aware property.', array('%entity_type' => $entity_type)));
// Create property translations.
$properties = array();
$default_langcode = $langcode;
foreach ($this->langcodes as $langcode) {
if ($langcode != $default_langcode) {
$properties[$langcode] = array(
'name' => array(0 => $this->randomMachineName()),
'user_id' => array(0 => mt_rand(128, 256)),
);
}
else {
$properties[$langcode] = array(
'name' => array(0 => $name),
'user_id' => array(0 => $uid),
);
}
$translation = $entity->hasTranslation($langcode) ? $entity->getTranslation($langcode) : $entity->addTranslation($langcode);
foreach ($properties[$langcode] as $field_name => $values) {
$translation->set($field_name, $values);
}
}
$entity->save();
// Check that property translation were correctly stored.
$entity = entity_load($entity_type, $entity->id());
foreach ($this->langcodes as $langcode) {
$args = array(
'%entity_type' => $entity_type,
'%langcode' => $langcode,
);
$field = $entity->getTranslation($langcode)->get('name');
$this->assertEqual($properties[$langcode]['name'][0], $field->value, format_string('%entity_type: The entity name has been correctly stored for language %langcode.', $args));
$field_langcode = ($langcode == $entity->language()->getId()) ? $default_langcode : $langcode;
$this->assertEqual($field_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expected langcode %langcode.', $args));
$this->assertEqual($properties[$langcode]['user_id'][0], $entity->getTranslation($langcode)->get('user_id')->target_id, format_string('%entity_type: The entity author has been correctly stored for language %langcode.', $args));
}
// Test query conditions (cache is reset at each call).
$translated_id = $entity->id();
// Create an additional entity with only the uid set. The uid for the
// original language is the same of one used for a translation.
$langcode = $this->langcodes[1];
$this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
'user_id' => $properties[$langcode]['user_id'],
'name' => 'some name',
$langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED,
))
->save();
$entities = entity_load_multiple($entity_type);
$this->assertEqual(count($entities), 3, format_string('%entity_type: Three entities were created.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple($entity_type, array($translated_id));
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity correctly loaded by id.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array('name' => $name));
$this->assertEqual(count($entities), 2, format_string('%entity_type: Two entities correctly loaded by name.', array('%entity_type' => $entity_type)));
// @todo The default language condition should go away in favor of an
// explicit parameter.
$entities = entity_load_multiple_by_properties($entity_type, array('name' => $properties[$langcode]['name'][0], $default_langcode_key => 0));
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity correctly loaded by name translation.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $default_langcode, 'name' => $name));
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity correctly loaded by name and language.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0]));
$this->assertEqual(count($entities), 0, format_string('%entity_type: No entity loaded by name translation specifying the translation language.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0], $default_langcode_key => 0));
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity loaded by name translation and language specifying to look for translations.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array('user_id' => $properties[$langcode]['user_id'][0], $default_langcode_key => NULL));
$this->assertEqual(count($entities), 2, format_string('%entity_type: Two entities loaded by uid without caring about property translatability.', array('%entity_type' => $entity_type)));
// Test property conditions and orders with multiple languages in the same
// query.
$query = \Drupal::entityQuery($entity_type);
$group = $query->andConditionGroup()
->condition('user_id', $properties[$default_langcode]['user_id'][0], '=', $default_langcode)
->condition('name', $properties[$default_langcode]['name'][0], '=', $default_langcode);
$result = $query
->condition($group)
->condition('name', $properties[$langcode]['name'][0], '=', $langcode)
->execute();
$this->assertEqual(count($result), 1, format_string('%entity_type: One entity loaded by name and uid using different language meta conditions.', array('%entity_type' => $entity_type)));
// Test mixed property and field conditions.
$entity = entity_load($entity_type, reset($result), TRUE);
$field_value = $this->randomString();
$entity->getTranslation($langcode)->set($this->fieldName, array(array('value' => $field_value)));
$entity->save();
$query = \Drupal::entityQuery($entity_type);
$default_langcode_group = $query->andConditionGroup()
->condition('user_id', $properties[$default_langcode]['user_id'][0], '=', $default_langcode)
->condition('name', $properties[$default_langcode]['name'][0], '=', $default_langcode);
$langcode_group = $query->andConditionGroup()
->condition('name', $properties[$langcode]['name'][0], '=', $langcode)
->condition("$this->fieldName.value", $field_value, '=', $langcode);
$result = $query
->condition($langcode_key, $default_langcode)
->condition($default_langcode_group)
->condition($langcode_group)
->execute();
$this->assertEqual(count($result), 1, format_string('%entity_type: One entity loaded by name, uid and field value using different language meta conditions.', array('%entity_type' => $entity_type)));
}
/**
* Tests the Entity Translation API behavior.
*/
function testEntityTranslationAPI() {
// Test all entity variations with data table support.
foreach (entity_test_entity_types(ENTITY_TEST_TYPES_MULTILINGUAL) as $entity_type) {
$this->doTestEntityTranslationAPI($entity_type);
}
}
/**
* Executes the Entity Translation API tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestEntityTranslationAPI($entity_type) {
$default_langcode = $this->langcodes[0];
$langcode = $this->langcodes[1];
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$default_langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('default_langcode');
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->entityManager
->getStorage($entity_type)
->create(array('name' => $this->randomMachineName(), $langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED));
$entity->save();
$hooks = $this->getHooksInfo();
$this->assertFalse($hooks, 'No entity translation hooks are fired when creating an entity.');
// Verify that we obtain the entity object itself when we attempt to
// retrieve a translation referring to it.
$translation = $entity->getTranslation(LanguageInterface::LANGCODE_NOT_SPECIFIED);
$this->assertFalse($translation->isNewTranslation(), 'Existing translations are not marked as new.');
$this->assertIdentical($entity, $translation, 'The translation object corresponding to a non-default language is the entity object itself when the entity is language-neutral.');
$entity->{$langcode_key}->value = $default_langcode;
$translation = $entity->getTranslation($default_langcode);
$this->assertIdentical($entity, $translation, 'The translation object corresponding to the default language (explicit) is the entity object itself.');
$translation = $entity->getTranslation(LanguageInterface::LANGCODE_DEFAULT);
$this->assertIdentical($entity, $translation, 'The translation object corresponding to the default language (implicit) is the entity object itself.');
$this->assertTrue($entity->{$default_langcode_key}->value, 'The translation object is the default one.');
// Verify that trying to retrieve a translation for a locked language when
// the entity is language-aware causes an exception to be thrown.
$message = 'A language-neutral translation cannot be retrieved.';
try {
$entity->getTranslation(LanguageInterface::LANGCODE_NOT_SPECIFIED);
$this->fail($message);
}
catch (\LogicException $e) {
$this->pass($message);
}
// Create a translation and verify that the translation object and the
// original object behave independently.
$name = $default_langcode . '_' . $this->randomMachineName();
$entity->name->value = $name;
$name_translated = $langcode . '_' . $this->randomMachineName();
$translation = $entity->addTranslation($langcode);
$this->assertTrue($translation->isNewTranslation(), 'Newly added translations are marked as new.');
$this->assertNotIdentical($entity, $translation, 'The entity and the translation object differ from one another.');
$this->assertTrue($entity->hasTranslation($langcode), 'The new translation exists.');
$this->assertEqual($translation->language()->getId(), $langcode, 'The translation language matches the specified one.');
$this->assertEqual($translation->{$langcode_key}->value, $langcode, 'The translation field language value matches the specified one.');
$this->assertFalse($translation->{$default_langcode_key}->value, 'The translation object is not the default one.');
$this->assertEqual($translation->getUntranslated()->language()->getId(), $default_langcode, 'The original language can still be retrieved.');
$translation->name->value = $name_translated;
$this->assertEqual($entity->name->value, $name, 'The original name is retained after setting a translated value.');
$entity->name->value = $name;
$this->assertEqual($translation->name->value, $name_translated, 'The translated name is retained after setting the original value.');
// Save the translation and check that the expected hooks are fired.
$translation->save();
$hooks = $this->getHooksInfo();
$this->assertEqual($hooks['entity_translation_create'], $langcode, 'The generic entity translation creation hook has fired.');
$this->assertEqual($hooks[$entity_type . '_translation_create'], $langcode, 'The entity-type-specific entity translation creation hook has fired.');
$this->assertEqual($hooks['entity_translation_insert'], $langcode, 'The generic entity translation insertion hook has fired.');
$this->assertEqual($hooks[$entity_type . '_translation_insert'], $langcode, 'The entity-type-specific entity translation insertion hook has fired.');
// Verify that changing translation language causes an exception to be
// thrown.
$message = 'The translation language cannot be changed.';
try {
$translation->{$langcode_key}->value = $this->langcodes[2];
$this->fail($message);
}
catch (\LogicException $e) {
$this->pass($message);
}
// Verify that reassigning the same translation language is allowed.
$message = 'The translation language can be reassigned the same value.';
try {
$translation->{$langcode_key}->value = $langcode;
$this->pass($message);
}
catch (\LogicException $e) {
$this->fail($message);
}
// Verify that changing the default translation flag causes an exception to
// be thrown.
foreach ($entity->getTranslationLanguages() as $t_langcode => $language) {
$translation = $entity->getTranslation($t_langcode);
$default = $translation->isDefaultTranslation();
$message = 'The default translation flag can be reassigned the same value.';
try {
$translation->{$default_langcode_key}->value = $default;
$this->pass($message);
}
catch (\LogicException $e) {
$this->fail($message);
}
$message = 'The default translation flag cannot be changed.';
try {
$translation->{$default_langcode_key}->value = !$default;
$this->fail($message);
}
catch (\LogicException $e) {
$this->pass($message);
}
$this->assertEqual($translation->{$default_langcode_key}->value, $default);
}
// Check that after loading an entity the language is the default one.
$entity = $this->reloadEntity($entity);
$this->assertEqual($entity->language()->getId(), $default_langcode, 'The loaded entity is the original one.');
// Add another translation and check that everything works as expected. A
// new translation object can be obtained also by just specifying a valid
// language.
$langcode2 = $this->langcodes[2];
$translation = $entity->addTranslation($langcode2);
$value = $entity !== $translation && $translation->language()->getId() == $langcode2 && $entity->hasTranslation($langcode2);
$this->assertTrue($value, 'A new translation object can be obtained also by specifying a valid language.');
$this->assertEqual($entity->language()->getId(), $default_langcode, 'The original language has been preserved.');
$translation->save();
$hooks = $this->getHooksInfo();
$this->assertEqual($hooks['entity_translation_create'], $langcode2, 'The generic entity translation creation hook has fired.');
$this->assertEqual($hooks[$entity_type . '_translation_create'], $langcode2, 'The entity-type-specific entity translation creation hook has fired.');
$this->assertEqual($hooks['entity_translation_insert'], $langcode2, 'The generic entity translation insertion hook has fired.');
$this->assertEqual($hooks[$entity_type . '_translation_insert'], $langcode2, 'The entity-type-specific entity translation insertion hook has fired.');
// Verify that trying to manipulate a translation object referring to a
// removed translation results in exceptions being thrown.
$entity = $this->reloadEntity($entity);
$translation = $entity->getTranslation($langcode2);
$entity->removeTranslation($langcode2);
foreach (array('get', 'set', '__get', '__set', 'createDuplicate') as $method) {
$message = format_string('The @method method raises an exception when trying to manipulate a removed translation.', array('@method' => $method));
try {
$translation->{$method}('name', $this->randomMachineName());
$this->fail($message);
}
catch (\Exception $e) {
$this->pass($message);
}
}
// Verify that deletion hooks are fired when saving an entity with a removed
// translation.
$entity->save();
$hooks = $this->getHooksInfo();
$this->assertEqual($hooks['entity_translation_delete'], $langcode2, 'The generic entity translation deletion hook has fired.');
$this->assertEqual($hooks[$entity_type . '_translation_delete'], $langcode2, 'The entity-type-specific entity translation deletion hook has fired.');
$entity = $this->reloadEntity($entity);
$this->assertFalse($entity->hasTranslation($langcode2), 'The translation does not appear among available translations after saving the entity.');
// Check that removing an invalid translation causes an exception to be
// thrown.
foreach (array($default_langcode, LanguageInterface::LANGCODE_DEFAULT, $this->randomMachineName()) as $invalid_langcode) {
$message = format_string('Removing an invalid translation (@langcode) causes an exception to be thrown.', array('@langcode' => $invalid_langcode));
try {
$entity->removeTranslation($invalid_langcode);
$this->fail($message);
}
catch (\Exception $e) {
$this->pass($message);
}
}
// Check that hooks are fired only when actually storing data.
$entity = $this->reloadEntity($entity);
$entity->addTranslation($langcode2);
$entity->removeTranslation($langcode2);
$entity->save();
$hooks = $this->getHooksInfo();
$this->assertTrue(isset($hooks['entity_translation_create']), 'The generic entity translation creation hook is run when adding and removing a translation without storing it.');
unset($hooks['entity_translation_create']);
$this->assertTrue(isset($hooks[$entity_type . '_translation_create']), 'The entity-type-specific entity translation creation hook is run when adding and removing a translation without storing it.');
unset($hooks[$entity_type . '_translation_create']);
$this->assertFalse($hooks, 'No other hooks beyond the entity translation creation hooks are run when adding and removing a translation without storing it.');
// Check that hooks are fired only when actually storing data.
$entity = $this->reloadEntity($entity);
$entity->addTranslation($langcode2);
$entity->save();
$entity = $this->reloadEntity($entity);
$this->assertTrue($entity->hasTranslation($langcode2), 'Entity has translation after adding one and saving.');
$entity->removeTranslation($langcode2);
$entity->save();
$entity = $this->reloadEntity($entity);
$this->assertFalse($entity->hasTranslation($langcode2), 'Entity does not have translation after removing it and saving.');
// Reset hook firing information.
$this->getHooksInfo();
// Verify that entity serialization does not cause stale references to be
// left around.
$entity = $this->reloadEntity($entity);
$translation = $entity->getTranslation($langcode);
$entity = unserialize(serialize($entity));
$entity->name->value = $this->randomMachineName();
$name = $default_langcode . '_' . $this->randomMachineName();
$entity->getTranslation($default_langcode)->name->value = $name;
$this->assertEqual($entity->name->value, $name, 'No stale reference for the translation object corresponding to the original language.');
$translation2 = $entity->getTranslation($langcode);
$translation2->name->value .= $this->randomMachineName();
$this->assertNotEqual($translation->name->value, $translation2->name->value, 'No stale reference for the actual translation object.');
$this->assertEqual($entity, $translation2->getUntranslated(), 'No stale reference in the actual translation object.');
// Verify that deep-cloning is still available when we are not instantiating
// a translation object, which instead relies on shallow cloning.
$entity = $this->reloadEntity($entity);
$entity->getTranslation($langcode);
$cloned = clone $entity;
$translation = $cloned->getTranslation($langcode);
$this->assertNotIdentical($entity, $translation->getUntranslated(), 'A cloned entity object has no reference to the original one.');
$entity->removeTranslation($langcode);
$this->assertFalse($entity->hasTranslation($langcode));
$this->assertTrue($cloned->hasTranslation($langcode));
// Check that untranslatable field references keep working after serializing
// and cloning the entity.
$entity = $this->reloadEntity($entity);
$type = $this->randomMachineName();
$entity->getTranslation($langcode)->type->value = $type;
$entity = unserialize(serialize($entity));
$cloned = clone $entity;
$translation = $cloned->getTranslation($langcode);
$translation->type->value = strrev($type);
$this->assertEqual($cloned->type->value, $translation->type->value, 'Untranslatable field references keep working after serializing and cloning the entity.');
// Check that per-language defaults are properly populated. The
// 'entity_test_mul_default_value' entity type is translatable and uses
// entity_test_field_default_value() as a "default value callback" for its
// 'description' field.
$entity = $this->entityManager
->getStorage('entity_test_mul_default_value')
->create(['name' => $this->randomMachineName(), 'langcode' => $langcode]);
$translation = $entity->addTranslation($langcode2);
$expected = array(
array(
'shape' => "shape:0:description_$langcode2",
'color' => "color:0:description_$langcode2",
),
array(
'shape' => "shape:1:description_$langcode2",
'color' => "color:1:description_$langcode2",
),
);
$this->assertEqual($translation->description->getValue(), $expected, 'Language-aware default values correctly populated.');
$this->assertEqual($translation->description->getLangcode(), $langcode2, 'Field object has the expected langcode.');
// Reset hook firing information.
$this->getHooksInfo();
}
/**
* Tests language fallback applied to field and entity translations.
*/
function testLanguageFallback() {
// Test all entity variations with data table support.
foreach (entity_test_entity_types(ENTITY_TEST_TYPES_MULTILINGUAL) as $entity_type) {
$this->doTestLanguageFallback($entity_type);
}
}
/**
* Executes the language fallback test for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestLanguageFallback($entity_type) {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$current_langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
$this->langcodes[] = $current_langcode;
$values = array();
foreach ($this->langcodes as $langcode) {
$values[$langcode]['name'] = $this->randomMachineName();
$values[$langcode]['user_id'] = mt_rand(0, 127);
}
$default_langcode = $this->langcodes[0];
$langcode = $this->langcodes[1];
$langcode2 = $this->langcodes[2];
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$languages = $this->languageManager->getLanguages();
$language = ConfigurableLanguage::load($languages[$langcode]->getId());
$language->set('weight', 1);
$language->save();
$this->languageManager->reset();
$controller = $this->entityManager->getStorage($entity_type);
$entity = $controller->create(array($langcode_key => $default_langcode) + $values[$default_langcode]);
$entity->save();
$entity->addTranslation($langcode, $values[$langcode]);
$entity->save();
// Check that retrieving the current translation works as expected.
$entity = $this->reloadEntity($entity);
$translation = $this->entityManager->getTranslationFromContext($entity, $langcode2);
$this->assertEqual($translation->language()->getId(), $default_langcode, 'The current translation language matches the expected one.');
// Check that language fallback respects language weight by default.
$language = ConfigurableLanguage::load($languages[$langcode]->getId());
$language->set('weight', -1);
$language->save();
$translation = $this->entityManager->getTranslationFromContext($entity, $langcode2);
$this->assertEqual($translation->language()->getId(), $langcode, 'The current translation language matches the expected one.');
// Check that the current translation is properly returned.
$translation = $this->entityManager->getTranslationFromContext($entity);
$this->assertEqual($langcode, $translation->language()->getId(), 'The current translation language matches the topmost language fallback candidate.');
$entity->addTranslation($current_langcode, $values[$current_langcode]);
$translation = $this->entityManager->getTranslationFromContext($entity);
$this->assertEqual($current_langcode, $translation->language()->getId(), 'The current translation language matches the current language.');
// Check that if the entity has no translation no fallback is applied.
$entity2 = $controller->create(array($langcode_key => $default_langcode));
// Get an view builder.
$controller = $this->entityManager->getViewBuilder($entity_type);
$entity2_build = $controller->view($entity2);
$entity2_output = (string) $renderer->renderRoot($entity2_build);
$translation = $this->entityManager->getTranslationFromContext($entity2, $default_langcode);
$translation_build = $controller->view($translation);
$translation_output = (string) $renderer->renderRoot($translation_build);
$this->assertIdentical($entity2_output, $translation_output, 'When the entity has no translation no fallback is applied.');
// Checks that entity translations are rendered properly.
$controller = $this->entityManager->getViewBuilder($entity_type);
$build = $controller->view($entity);
$renderer->renderRoot($build);
$this->assertEqual($build['label']['#markup'], $values[$current_langcode]['name'], 'By default the entity is rendered in the current language.');
$langcodes = array_combine($this->langcodes, $this->langcodes);
// We have no translation for the $langcode2 language, hence the expected
// result is the topmost existing translation, that is $langcode.
$langcodes[$langcode2] = $langcode;
foreach ($langcodes as $desired => $expected) {
$build = $controller->view($entity, 'full', $desired);
// Unset the #cache key so that a fresh render is produced with each pass,
// making the renderable array keys available to compare.
unset($build['#cache']);
$renderer->renderRoot($build);
$this->assertEqual($build['label']['#markup'], $values[$expected]['name'], 'The entity is rendered in the expected language.');
}
}
/**
* Check that field translatability is handled properly.
*/
function testFieldDefinitions() {
// Check that field translatability can be altered to be enabled or disabled
// in field definitions.
$entity_type = 'entity_test_mulrev';
$this->state->set('entity_test.field_definitions.translatable', array('name' => FALSE));
$this->entityManager->clearCachedFieldDefinitions();
$definitions = $this->entityManager->getBaseFieldDefinitions($entity_type);
$this->assertFalse($definitions['name']->isTranslatable(), 'Field translatability can be disabled programmatically.');
$this->state->set('entity_test.field_definitions.translatable', array('name' => TRUE));
$this->entityManager->clearCachedFieldDefinitions();
$definitions = $this->entityManager->getBaseFieldDefinitions($entity_type);
$this->assertTrue($definitions['name']->isTranslatable(), 'Field translatability can be enabled programmatically.');
// Check that field translatability is disabled by default.
$base_field_definitions = EntityTestMulRev::baseFieldDefinitions($this->entityManager->getDefinition($entity_type));
$this->assertTrue(!isset($base_field_definitions['id']->translatable), 'Translatability for the <em>id</em> field is not defined.');
$this->assertFalse($definitions['id']->isTranslatable(), 'Field translatability is disabled by default.');
// Check that entity id keys have the expect translatability.
$translatable_fields = array(
'id' => TRUE,
'uuid' => TRUE,
'revision_id' => TRUE,
'type' => TRUE,
'langcode' => FALSE,
);
foreach ($translatable_fields as $name => $translatable) {
$this->state->set('entity_test.field_definitions.translatable', array($name => $translatable));
$this->entityManager->clearCachedFieldDefinitions();
$message = format_string('Field %field cannot be translatable.', array('%field' => $name));
try {
$this->entityManager->getBaseFieldDefinitions($entity_type);
$this->fail($message);
}
catch (\LogicException $e) {
$this->pass($message);
}
}
}
/**
* Tests that changing entity language does not break field language.
*/
public function testLanguageChange() {
// Test all entity variations with data table support.
foreach (entity_test_entity_types(ENTITY_TEST_TYPES_MULTILINGUAL) as $entity_type) {
$this->doTestLanguageChange($entity_type);
}
}
/**
* Executes the entity language change test for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function doTestLanguageChange($entity_type) {
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$controller = $this->entityManager->getStorage($entity_type);
$langcode = $this->langcodes[0];
// check that field languages match entity language regardless of field
// translatability.
$values = array(
$langcode_key => $langcode,
$this->fieldName => $this->randomMachineName(),
$this->untranslatableFieldName => $this->randomMachineName(),
);
$entity = $controller->create($values);
foreach (array($this->fieldName, $this->untranslatableFieldName) as $field_name) {
$this->assertEqual($entity->get($field_name)->getLangcode(), $langcode, 'Field language works as expected.');
}
// Check that field languages keep matching entity language even after
// changing it.
$langcode = $this->langcodes[1];
$entity->{$langcode_key}->value = $langcode;
foreach (array($this->fieldName, $this->untranslatableFieldName) as $field_name) {
$this->assertEqual($entity->get($field_name)->getLangcode(), $langcode, 'Field language works as expected after changing entity language.');
}
// Check that entity translation does not affect the language of original
// field values and untranslatable ones.
$langcode = $this->langcodes[0];
$entity->addTranslation($this->langcodes[2], array($this->fieldName => $this->randomMachineName()));
$entity->{$langcode_key}->value = $langcode;
foreach (array($this->fieldName, $this->untranslatableFieldName) as $field_name) {
$this->assertEqual($entity->get($field_name)->getLangcode(), $langcode, 'Field language works as expected after translating the entity and changing language.');
}
// Check that setting the default language to an existing translation
// language causes an exception to be thrown.
$message = 'An exception is thrown when setting the default language to an existing translation language';
try {
$entity->{$langcode_key}->value = $this->langcodes[2];
$this->fail($message);
}
catch (\InvalidArgumentException $e) {
$this->pass($message);
}
}
/**
* Tests how entity adapters work with translations.
*/
function testEntityAdapter() {
$entity_type = 'entity_test';
$default_langcode = 'en';
$values[$default_langcode] = array('name' => $this->randomString());
$controller = $this->entityManager->getStorage($entity_type);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $controller->create($values[$default_langcode]);
foreach ($this->langcodes as $langcode) {
$values[$langcode] = array('name' => $this->randomString());
$entity->addTranslation($langcode, $values[$langcode]);
}
$langcodes = array_merge(array($default_langcode), $this->langcodes);
foreach ($langcodes as $langcode) {
$adapter = $entity->getTranslation($langcode)->getTypedData();
$name = $adapter->get('name')->value;
$this->assertEqual($name, $values[$langcode]['name'], SafeMarkup::format('Name correctly retrieved from "@langcode" adapter', array('@langcode' => $langcode)));
}
}
/**
* Tests if entity references are correct after adding a new translation.
*/
public function testFieldEntityReference() {
$entity_type = 'entity_test_mul';
$controller = $this->entityManager->getStorage($entity_type);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $controller->create();
foreach ($this->langcodes as $langcode) {
$entity->addTranslation($langcode);
}
$default_langcode = $entity->getUntranslated()->language()->getId();
foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
$translation = $entity->getTranslation($langcode);
foreach ($translation->getFields() as $field_name => $field) {
if ($field->getFieldDefinition()->isTranslatable()) {
$args = ['%field_name' => $field_name, '%langcode' => $langcode];
$this->assertEqual($langcode, $field->getEntity()->language()->getId(), format_string('Translatable field %field_name on translation %langcode has correct entity reference in translation %langcode.', $args));
}
else {
$args = ['%field_name' => $field_name, '%langcode' => $langcode, '%default_langcode' => $default_langcode];
$this->assertEqual($default_langcode, $field->getEntity()->language()->getId(), format_string('Non translatable field %field_name on translation %langcode has correct entity reference in the default translation %default_langcode.', $args));
}
}
}
}
/**
* Tests if entity translation statuses are correct after removing two
* translation.
*/
public function testDeleteEntityTranslation() {
$entity_type = 'entity_test_mul';
$controller = $this->entityManager->getStorage($entity_type);
// Create a translatable test field.
$field_storage = FieldStorageConfig::create([
'entity_type' => $entity_type,
'field_name' => 'translatable_test_field',
'type' => 'field_test',
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'label' => $this->randomMachineName(),
'bundle' => $entity_type,
]);
$field->save();
// Create an untranslatable test field.
$field_storage = FieldStorageConfig::create([
'entity_type' => $entity_type,
'field_name' => 'untranslatable_test_field',
'type' => 'field_test',
'translatable' => FALSE,
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'label' => $this->randomMachineName(),
'bundle' => $entity_type,
]);
$field->save();
// Create an entity with both translatable and untranslatable test fields.
$values = array(
'name' => $this->randomString(),
'translatable_test_field' => $this->randomString(),
'untranslatable_test_field' => $this->randomString(),
);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $controller->create($values);
foreach ($this->langcodes as $langcode) {
$entity->addTranslation($langcode, $values);
}
$entity->save();
// Assert there are no deleted languages in the lists yet.
$this->assertNull(\Drupal::state()->get('entity_test.delete.translatable_test_field'));
$this->assertNull(\Drupal::state()->get('entity_test.delete.untranslatable_test_field'));
// Remove the second and third langcodes from the entity.
$entity->removeTranslation('l1');
$entity->removeTranslation('l2');
$entity->save();
// Ensure that for the translatable test field the second and third
// langcodes are in the deleted languages list.
$actual = \Drupal::state()->get('entity_test.delete.translatable_test_field');
$expected_translatable = ['l1', 'l2'];
sort($actual);
sort($expected_translatable);
$this->assertEqual($actual, $expected_translatable);
// Ensure that the untranslatable test field is untouched.
$this->assertNull(\Drupal::state()->get('entity_test.delete.untranslatable_test_field'));
// Delete the entity, which removes all remaining translations.
$entity->delete();
// All languages have been deleted now.
$actual = \Drupal::state()->get('entity_test.delete.translatable_test_field');
$expected_translatable[] = 'en';
$expected_translatable[] = 'l0';
sort($actual);
sort($expected_translatable);
$this->assertEqual($actual, $expected_translatable);
// The untranslatable field is shared and only deleted once, for the
// default langcode.
$actual = \Drupal::state()->get('entity_test.delete.untranslatable_test_field');
$expected_untranslatable = ['en'];
sort($actual);
sort($expected_untranslatable);
$this->assertEqual($actual, $expected_untranslatable);
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\TypedData\DataDefinition;
/**
* Tests validation constraints for EntityTypeConstraintValidator.
*
* @group Entity
*/
class EntityTypeConstraintValidatorTest extends EntityKernelTestBase {
/**
* The typed data manager to use.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedData;
public static $modules = array('node', 'field', 'user');
protected function setUp() {
parent::setUp();
$this->typedData = $this->container->get('typed_data_manager');
}
/**
* Tests the EntityTypeConstraintValidator.
*/
public function testValidation() {
// Create a typed data definition with an EntityType constraint.
$entity_type = 'node';
$definition = DataDefinition::create('entity_reference')
->setConstraints(array(
'EntityType' => $entity_type,
)
);
// Test the validation.
$node = $this->container->get('entity.manager')->getStorage('node')->create(array('type' => 'page'));
$typed_data = $this->typedData->create($definition, $node);
$violations = $typed_data->validate();
$this->assertEqual($violations->count(), 0, 'Validation passed for correct value.');
// Test the validation when an invalid value (in this case a user entity)
// is passed.
$account = $this->createUser();
$typed_data = $this->typedData->create($definition, $account);
$violations = $typed_data->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed for incorrect value.');
// Make sure the information provided by a violation is correct.
$violation = $violations[0];
$this->assertEqual($violation->getMessage(), t('The entity must be of type %type.', array('%type' => $entity_type)), 'The message for invalid value is correct.');
$this->assertEqual($violation->getRoot(), $typed_data, 'Violation root is correct.');
$this->assertEqual($violation->getInvalidValue(), $account, 'The invalid value is set correctly in the violation.');
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
/**
* Tests entity level validation constraints.
*
* @group Entity
*/
class EntityTypeConstraintsTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_constraints');
}
/**
* Tests defining entity constraints via entity type annotations and hooks.
*/
public function testConstraintDefinition() {
// Test reading the annotation. There should be two constraints, the defined
// constraint and the automatically added EntityChanged constraint.
$entity_type = $this->entityManager->getDefinition('entity_test_constraints');
$default_constraints = ['NotNull' => [], 'EntityChanged' => NULL];
$this->assertEqual($default_constraints, $entity_type->getConstraints());
// Enable our test module and test extending constraints.
$this->enableModules(['entity_test_constraints']);
$this->container->get('module_handler')->resetImplementations();
$extra_constraints = ['Test' => []];
$this->state->set('entity_test_constraints.build', $extra_constraints);
// Re-fetch the entity manager from the new container built after the new
// modules were enabled.
$this->entityManager = $this->container->get('entity.manager');
$this->entityManager->clearCachedDefinitions();
$entity_type = $this->entityManager->getDefinition('entity_test_constraints');
$this->assertEqual($default_constraints + $extra_constraints, $entity_type->getConstraints());
// Test altering constraints.
$altered_constraints = ['Test' => [ 'some_setting' => TRUE]];
$this->state->set('entity_test_constraints.alter', $altered_constraints);
// Clear the cache in state instance in the Drupal container, so it can pick
// up the modified value.
\Drupal::state()->resetCache();
$this->entityManager->clearCachedDefinitions();
$entity_type = $this->entityManager->getDefinition('entity_test_constraints');
$this->assertEqual($altered_constraints, $entity_type->getConstraints());
}
/**
* Tests entity constraints are validated.
*/
public function testConstraintValidation() {
$entity = $this->entityManager->getStorage('entity_test_constraints')->create();
$entity->user_id->target_id = 0;
$violations = $entity->validate();
$this->assertEqual($violations->count(), 0, 'Validation passed.');
$entity->save();
$entity->changed->value = REQUEST_TIME - 86400;
$violations = $entity->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed.');
$this->assertEqual($violations[0]->getMessage(), t('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.'));
}
}

View file

@ -0,0 +1,129 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
use Drupal\Core\TypedData\DataReferenceDefinition;
use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
use Drupal\Core\TypedData\ListDataDefinitionInterface;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests deriving metadata of entity and field data types.
*
* @group Entity
*/
class EntityTypedDataDefinitionTest extends KernelTestBase {
/**
* The typed data manager to use.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedDataManager;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('filter', 'text', 'node', 'user');
protected function setUp() {
parent::setup();
$this->typedDataManager = $this->container->get('typed_data_manager');
}
/**
* Tests deriving metadata about fields.
*/
public function testFields() {
$field_definition = BaseFieldDefinition::create('integer');
// Fields are lists of complex data.
$this->assertTrue($field_definition instanceof ListDataDefinitionInterface);
$this->assertFalse($field_definition instanceof ComplexDataDefinitionInterface);
$field_item_definition = $field_definition->getItemDefinition();
$this->assertFalse($field_item_definition instanceof ListDataDefinitionInterface);
$this->assertTrue($field_item_definition instanceof ComplexDataDefinitionInterface);
// Derive metadata about field item properties.
$this->assertEqual(array_keys($field_item_definition->getPropertyDefinitions()), array('value'));
$this->assertEqual($field_item_definition->getPropertyDefinition('value')->getDataType(), 'integer');
$this->assertEqual($field_item_definition->getMainPropertyName(), 'value');
$this->assertNull($field_item_definition->getPropertyDefinition('invalid'));
// Test accessing field item property metadata via the field definition.
$this->assertTrue($field_definition instanceof FieldDefinitionInterface);
$this->assertEqual(array_keys($field_definition->getPropertyDefinitions()), array('value'));
$this->assertEqual($field_definition->getPropertyDefinition('value')->getDataType(), 'integer');
$this->assertEqual($field_definition->getMainPropertyName(), 'value');
$this->assertNull($field_definition->getPropertyDefinition('invalid'));
// Test using the definition factory for field item lists and field items.
$field_item = $this->typedDataManager->createDataDefinition('field_item:integer');
$this->assertFalse($field_item instanceof ListDataDefinitionInterface);
$this->assertTrue($field_item instanceof ComplexDataDefinitionInterface);
// Comparison should ignore the internal static cache, so compare the
// serialized objects instead.
$this->assertEqual(serialize($field_item_definition), serialize($field_item));
$field_definition2 = $this->typedDataManager->createListDataDefinition('field_item:integer');
$this->assertTrue($field_definition2 instanceof ListDataDefinitionInterface);
$this->assertFalse($field_definition2 instanceof ComplexDataDefinitionInterface);
$this->assertEqual(serialize($field_definition), serialize($field_definition2));
}
/**
* Tests deriving metadata about entities.
*/
public function testEntities() {
$entity_definition = EntityDataDefinition::create('node');
// Entities are complex data.
$this->assertFalse($entity_definition instanceof ListDataDefinitionInterface);
$this->assertTrue($entity_definition instanceof ComplexDataDefinitionInterface);
$field_definitions = $entity_definition->getPropertyDefinitions();
// Comparison should ignore the internal static cache, so compare the
// serialized objects instead.
$this->assertEqual(serialize($field_definitions), serialize(\Drupal::entityManager()->getBaseFieldDefinitions('node')));
$this->assertEqual($entity_definition->getPropertyDefinition('title')->getItemDefinition()->getDataType(), 'field_item:string');
$this->assertNull($entity_definition->getMainPropertyName());
$this->assertNull($entity_definition->getPropertyDefinition('invalid'));
$entity_definition2 = $this->typedDataManager->createDataDefinition('entity:node');
$this->assertFalse($entity_definition2 instanceof ListDataDefinitionInterface);
$this->assertTrue($entity_definition2 instanceof ComplexDataDefinitionInterface);
$this->assertEqual(serialize($entity_definition), serialize($entity_definition2));
// Test that the definition factory creates the right definitions for all
// entity data types variants.
$this->assertEqual($this->typedDataManager->createDataDefinition('entity'), EntityDataDefinition::create());
$this->assertEqual($this->typedDataManager->createDataDefinition('entity:node'), EntityDataDefinition::create('node'));
// Config entities don't support typed data.
$entity_definition = EntityDataDefinition::create('node_type');
$this->assertEqual(array(), $entity_definition->getPropertyDefinitions());
}
/**
* Tests deriving metadata from entity references.
*/
public function testEntityReferences() {
$reference_definition = DataReferenceDefinition::create('entity');
$this->assertTrue($reference_definition instanceof DataReferenceDefinitionInterface);
// Test retrieving metadata about the referenced data.
$this->assertEqual($reference_definition->getTargetDefinition()->getDataType(), 'entity');
$this->assertTrue($reference_definition->getTargetDefinition() instanceof EntityDataDefinitionInterface);
// Test that the definition factory creates the right definition object.
$reference_definition2 = $this->typedDataManager->createDataDefinition('entity_reference');
$this->assertTrue($reference_definition2 instanceof DataReferenceDefinitionInterface);
$this->assertEqual($reference_definition2, $reference_definition);
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
/**
* Tests creation, saving, and loading of entity UUIDs.
*
* @group Entity
*/
class EntityUUIDTest extends EntityKernelTestBase {
protected function setUp() {
parent::setUp();
foreach (entity_test_entity_types() as $entity_type_id) {
// The entity_test schema is installed by the parent.
if ($entity_type_id != 'entity_test') {
$this->installEntitySchema($entity_type_id);
}
}
}
/**
* Tests UUID generation in entity CRUD operations.
*/
function testCRUD() {
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->assertCRUD($entity_type);
}
}
/**
* Executes the UUID CRUD tests for the given entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function assertCRUD($entity_type) {
// Verify that no UUID is auto-generated when passing one for creation.
$uuid_service = $this->container->get('uuid');
$uuid = $uuid_service->generate();
$custom_entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
'name' => $this->randomMachineName(),
'uuid' => $uuid,
));
$this->assertIdentical($custom_entity->uuid(), $uuid);
// Save this entity, so we have more than one later.
$custom_entity->save();
// Verify that a new UUID is generated upon creating an entity.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('name' => $this->randomMachineName()));
$uuid = $entity->uuid();
$this->assertTrue($uuid);
// Verify that the new UUID is different.
$this->assertNotEqual($custom_entity->uuid(), $uuid);
// Verify that the UUID is retained upon saving.
$entity->save();
$this->assertIdentical($entity->uuid(), $uuid);
// Verify that the UUID is retained upon loading.
$entity_loaded = entity_load($entity_type, $entity->id(), TRUE);
$this->assertIdentical($entity_loaded->uuid(), $uuid);
// Verify that \Drupal::entityManager()->loadEntityByUuid() loads the same entity.
$entity_loaded_by_uuid = \Drupal::entityManager()->loadEntityByUuid($entity_type, $uuid, TRUE);
$this->assertIdentical($entity_loaded_by_uuid->uuid(), $uuid);
$this->assertEqual($entity_loaded_by_uuid->id(), $entity_loaded->id());
// Creating a duplicate needs to result in a new UUID.
$entity_duplicate = $entity->createDuplicate();
foreach ($entity->getFields() as $property => $value) {
switch($property) {
case 'uuid':
$this->assertNotNull($entity_duplicate->uuid());
$this->assertNotNull($entity->uuid());
$this->assertNotEqual($entity_duplicate->uuid(), $entity->uuid());
break;
case 'id':
$this->assertNull($entity_duplicate->id());
$this->assertNotNull($entity->id());
$this->assertNotEqual($entity_duplicate->id(), $entity->id());
break;
case 'revision_id':
$this->assertNull($entity_duplicate->getRevisionId());
$this->assertNotNull($entity->getRevisionId());
$this->assertNotEqual($entity_duplicate->getRevisionId(), $entity->getRevisionId());
$this->assertNotEqual($entity_duplicate->{$property}->getValue(), $entity->{$property}->getValue());
break;
default:
$this->assertEqual($entity_duplicate->{$property}->getValue(), $entity->{$property}->getValue());
}
}
$entity_duplicate->save();
$this->assertNotEqual($entity->id(), $entity_duplicate->id());
}
}

View file

@ -0,0 +1,203 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
/**
* Tests the Entity Validation API.
*
* @group Entity
*/
class EntityValidationTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('filter', 'text');
/**
* @var string
*/
protected $entityName;
/**
* @var \Drupal\user\Entity\User
*/
protected $entityUser;
/**
* @var string
*/
protected $entityFieldText;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create the test field.
module_load_install('entity_test');
entity_test_install();
// Install required default configuration for filter module.
$this->installConfig(array('system', 'filter'));
}
/**
* Creates a test entity.
*
* @param string $entity_type
* An entity type.
*
* @return \Drupal\Core\Entity\EntityInterface
* The created test entity.
*/
protected function createTestEntity($entity_type) {
$this->entityName = $this->randomMachineName();
$this->entityUser = $this->createUser();
// Pass in the value of the name field when creating. With the user
// field we test setting a field after creation.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create();
$entity->user_id->target_id = $this->entityUser->id();
$entity->name->value = $this->entityName;
// Set a value for the test field.
if ($entity->hasField('field_test_text')) {
$this->entityFieldText = $this->randomMachineName();
$entity->field_test_text->value = $this->entityFieldText;
}
return $entity;
}
/**
* Tests validating test entity types.
*/
public function testValidation() {
// Ensure that the constraint manager is marked as cached cleared.
// Use the protected property on the cache_clearer first to check whether
// the constraint manager is added there.
// Ensure that the proxy class is initialized, which has the necessary
// method calls attached.
\Drupal::service('plugin.cache_clearer');
$plugin_cache_clearer = \Drupal::service('drupal.proxy_original_service.plugin.cache_clearer');
$get_cached_discoveries = function () {
return $this->cachedDiscoveries;
};
$get_cached_discoveries = $get_cached_discoveries->bindTo($plugin_cache_clearer, $plugin_cache_clearer);
$cached_discoveries = $get_cached_discoveries();
$cached_discovery_classes = [];
foreach ($cached_discoveries as $cached_discovery) {
$cached_discovery_classes[] = get_class($cached_discovery);
}
$this->assertTrue(in_array('Drupal\Core\Validation\ConstraintManager', $cached_discovery_classes));
// All entity variations have to have the same results.
foreach (entity_test_entity_types() as $entity_type) {
$this->checkValidation($entity_type);
}
}
/**
* Executes the validation test set for a defined entity type.
*
* @param string $entity_type
* The entity type to run the tests with.
*/
protected function checkValidation($entity_type) {
$entity = $this->createTestEntity($entity_type);
$violations = $entity->validate();
$this->assertEqual($violations->count(), 0, 'Validation passes.');
// Test triggering a fail for each of the constraints specified.
$test_entity = clone $entity;
$test_entity->id->value = -1;
$violations = $test_entity->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed.');
$this->assertEqual($violations[0]->getMessage(), t('%name: The integer must be larger or equal to %min.', array('%name' => 'ID', '%min' => 0)));
$test_entity = clone $entity;
$test_entity->uuid->value = $this->randomString(129);
$violations = $test_entity->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed.');
$this->assertEqual($violations[0]->getMessage(), t('%name: may not be longer than @max characters.', array('%name' => 'UUID', '@max' => 128)));
$test_entity = clone $entity;
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$test_entity->{$langcode_key}->value = $this->randomString(13);
$violations = $test_entity->validate();
// This should fail on AllowedValues and Length constraints.
$this->assertEqual($violations->count(), 2, 'Validation failed.');
$this->assertEqual($violations[0]->getMessage(), t('This value is too long. It should have %limit characters or less.', array('%limit' => '12')));
$this->assertEqual($violations[1]->getMessage(), t('The value you selected is not a valid choice.'));
$test_entity = clone $entity;
$test_entity->type->value = NULL;
$violations = $test_entity->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed.');
$this->assertEqual($violations[0]->getMessage(), t('This value should not be null.'));
$test_entity = clone $entity;
$test_entity->name->value = $this->randomString(33);
$violations = $test_entity->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed.');
$this->assertEqual($violations[0]->getMessage(), t('%name: may not be longer than @max characters.', array('%name' => 'Name', '@max' => 32)));
// Make sure the information provided by a violation is correct.
$violation = $violations[0];
$this->assertEqual($violation->getRoot()->getValue(), $test_entity, 'Violation root is entity.');
$this->assertEqual($violation->getPropertyPath(), 'name.0.value', 'Violation property path is correct.');
$this->assertEqual($violation->getInvalidValue(), $test_entity->name->value, 'Violation contains invalid value.');
$test_entity = clone $entity;
$test_entity->set('user_id', 9999);
$violations = $test_entity->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed.');
$this->assertEqual($violations[0]->getMessage(), t('The referenced entity (%type: %id) does not exist.', array('%type' => 'user', '%id' => 9999)));
$test_entity = clone $entity;
$test_entity->field_test_text->format = $this->randomString(33);
$violations = $test_entity->validate();
$this->assertEqual($violations->count(), 1, 'Validation failed.');
$this->assertEqual($violations[0]->getMessage(), t('The value you selected is not a valid choice.'));
// Make sure the information provided by a violation is correct.
$violation = $violations[0];
$this->assertEqual($violation->getRoot()->getValue(), $test_entity, 'Violation root is entity.');
$this->assertEqual($violation->getPropertyPath(), 'field_test_text.0.format', 'Violation property path is correct.');
$this->assertEqual($violation->getInvalidValue(), $test_entity->field_test_text->format, 'Violation contains invalid value.');
}
/**
* Tests composite constraints.
*/
public function testCompositeConstraintValidation() {
$entity = $this->createTestEntity('entity_test_composite_constraint');
$violations = $entity->validate();
$this->assertEqual($violations->count(), 0);
// Trigger violation condition.
$entity->name->value = 'test';
$entity->type->value = 'test2';
$violations = $entity->validate();
$this->assertEqual($violations->count(), 1);
// Make sure we can determine this is composite constraint.
$constraint = $violations[0]->getConstraint();
$this->assertTrue($constraint instanceof CompositeConstraintBase, 'Constraint is composite constraint.');
$this->assertEqual('type', $violations[0]->getPropertyPath());
/** @var CompositeConstraintBase $constraint */
$this->assertEqual($constraint->coversFields(), ['name', 'type'], 'Information about covered fields can be retrieved.');
}
}

View file

@ -0,0 +1,212 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\Core\Cache\Cache;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the entity view builder.
*
* @group Entity
*/
class EntityViewBuilderTest extends EntityKernelTestBase {
use EntityReferenceTestTrait;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(array('user', 'entity_test'));
// Give anonymous users permission to view test entities.
Role::load(RoleInterface::ANONYMOUS_ID)
->grantPermission('view test entity')
->save();
}
/**
* Tests entity render cache handling.
*/
public function testEntityViewBuilderCache() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$cache_contexts_manager = \Drupal::service("cache_contexts_manager");
$cache = \Drupal::cache();
// Force a request via GET so we can get drupal_render() cache working.
$request = \Drupal::request();
$request_method = $request->server->get('REQUEST_METHOD');
$request->setMethod('GET');
$entity_test = $this->createTestEntity('entity_test');
// Test that new entities (before they are saved for the first time) do not
// generate a cache entry.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'The render array element of new (unsaved) entities is not cached, but does have cache tags set.');
// Get a fully built entity view render array.
$entity_test->save();
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$cid = implode(':', $cid_parts);
$bin = $build['#cache']['bin'];
// Mock the build array to not require the theme registry.
unset($build['#theme']);
$build['#markup'] = 'entity_render_test';
// Test that a cache entry is created.
$renderer->renderRoot($build);
$this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
// Re-save the entity and check that the cache entry has been deleted.
$cache->set('kittens', 'Kitten data', Cache::PERMANENT, $build['#cache']['tags']);
$entity_test->save();
$this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was saved.');
$this->assertFalse($cache->get('kittens'), 'The entity saving has invalidated cache tags.');
// Rebuild the render array (creating a new cache entry in the process) and
// delete the entity to check the cache entry is deleted.
unset($build['#printed']);
$renderer->renderRoot($build);
$this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
$entity_test->delete();
$this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was deleted.');
// Restore the previous request method.
$request->setMethod($request_method);
}
/**
* Tests entity render cache with references.
*/
public function testEntityViewBuilderCacheWithReferences() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$cache_contexts_manager = \Drupal::service("cache_contexts_manager");
// Force a request via GET so we can get drupal_render() cache working.
$request = \Drupal::request();
$request_method = $request->server->get('REQUEST_METHOD');
$request->setMethod('GET');
// Create an entity reference field and an entity that will be referenced.
$this->createEntityReferenceField('entity_test', 'entity_test', 'reference_field', 'Reference', 'entity_test');
entity_get_display('entity_test', 'entity_test', 'full')->setComponent('reference_field', [
'type' => 'entity_reference_entity_view',
'settings' => ['link' => FALSE],
])->save();
$entity_test_reference = $this->createTestEntity('entity_test');
$entity_test_reference->save();
// Get a fully built entity view render array for the referenced entity.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full');
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$cid_reference = implode(':', $cid_parts);
$bin_reference = $build['#cache']['bin'];
// Mock the build array to not require the theme registry.
unset($build['#theme']);
$build['#markup'] = 'entity_render_test';
$renderer->renderRoot($build);
// Test that a cache entry was created for the referenced entity.
$this->assertTrue($this->container->get('cache.' . $bin_reference)->get($cid_reference), 'The entity render element for the referenced entity has been cached.');
// Create another entity that references the first one.
$entity_test = $this->createTestEntity('entity_test');
$entity_test->reference_field->entity = $entity_test_reference;
$entity_test->save();
// Get a fully built entity view render array.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$cid = implode(':', $cid_parts);
$bin = $build['#cache']['bin'];
// Mock the build array to not require the theme registry.
unset($build['#theme']);
$build['#markup'] = 'entity_render_test';
$renderer->renderRoot($build);
// Test that a cache entry is created.
$this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
// Save the entity and verify that both cache entries have been deleted.
$entity_test_reference->save();
$this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was deleted.');
$this->assertFalse($this->container->get('cache.' . $bin_reference)->get($cid_reference), 'The entity render cache for the referenced entity has been cleared when the entity was deleted.');
// Restore the previous request method.
$request->setMethod($request_method);
}
/**
* Tests entity render cache toggling.
*/
public function testEntityViewBuilderCacheToggling() {
$entity_test = $this->createTestEntity('entity_test');
$entity_test->save();
// Test a view mode in default conditions: render caching is enabled for
// the entity type and the view mode.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age', 'keys', 'bin'] , 'A view mode with render cache enabled has the correct output (cache tags, keys, contexts, max-age and bin).');
// Test that a view mode can opt out of render caching.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'test');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'A view mode with render cache disabled has the correct output (only cache tags, contexts and max-age).');
// Test that an entity type can opt out of render caching completely.
$entity_test_no_cache = $this->createTestEntity('entity_test_label');
$entity_test_no_cache->save();
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test_label')->view($entity_test_no_cache, 'full');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == ['tags', 'contexts', 'max-age'], 'An entity type can opt out of render caching regardless of view mode configuration, but always has cache tags, contexts and max-age set.');
}
/**
* Tests weighting of display components.
*/
public function testEntityViewBuilderWeight() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
// Set a weight for the label component.
entity_get_display('entity_test', 'entity_test', 'full')
->setComponent('label', array('weight' => 20))
->save();
// Create and build a test entity.
$entity_test = $this->createTestEntity('entity_test');
$view = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$renderer->renderRoot($view);
// Check that the weight is respected.
$this->assertEqual($view['label']['#weight'], 20, 'The weight of a display component is respected.');
}
/**
* Creates an entity for testing.
*
* @param string $entity_type
* The entity type.
*
* @return \Drupal\Core\Entity\EntityInterface
* The created entity.
*/
protected function createTestEntity($entity_type) {
$data = array(
'bundle' => $entity_type,
'name' => $this->randomMachineName(),
);
return $this->container->get('entity.manager')->getStorage($entity_type)->create($data);
}
}

Some files were not shown because too many files have changed in this diff Show more