Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
90
2017/web/core/tests/Drupal/KernelTests/AssertConfigTrait.php
Normal file
90
2017/web/core/tests/Drupal/KernelTests/AssertConfigTrait.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests;
|
||||
|
||||
use Drupal\Component\Diff\Diff;
|
||||
|
||||
/**
|
||||
* Trait to help with diffing config.
|
||||
*/
|
||||
trait AssertConfigTrait {
|
||||
|
||||
/**
|
||||
* Ensures that a specific config diff does not contain unwanted changes.
|
||||
*
|
||||
* @param \Drupal\Component\Diff\Diff $result
|
||||
* The diff result for the passed in config name.
|
||||
* @param string $config_name
|
||||
* The config name to check.
|
||||
* @param array $skipped_config
|
||||
* An array of skipped config, keyed by string. If the value is TRUE, the
|
||||
* entire file will be ignored, otherwise it's an array of strings which are
|
||||
* ignored.
|
||||
*
|
||||
* @throws \Exception
|
||||
* Thrown when a configuration is different.
|
||||
*/
|
||||
protected function assertConfigDiff(Diff $result, $config_name, array $skipped_config) {
|
||||
foreach ($result->getEdits() as $op) {
|
||||
switch (get_class($op)) {
|
||||
case 'Drupal\Component\Diff\Engine\DiffOpCopy':
|
||||
// Nothing to do, a copy is what we expect.
|
||||
break;
|
||||
case 'Drupal\Component\Diff\Engine\DiffOpDelete':
|
||||
case 'Drupal\Component\Diff\Engine\DiffOpChange':
|
||||
// It is not part of the skipped config, so we can directly throw the
|
||||
// exception.
|
||||
if (!in_array($config_name, array_keys($skipped_config))) {
|
||||
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
|
||||
}
|
||||
|
||||
// Allow to skip entire config files.
|
||||
if ($skipped_config[$config_name] === TRUE) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Allow to skip some specific lines of imported config files.
|
||||
// Ensure that the only changed lines are the ones we marked as
|
||||
// skipped.
|
||||
$all_skipped = TRUE;
|
||||
|
||||
$changes = get_class($op) == 'Drupal\Component\Diff\Engine\DiffOpDelete' ? $op->orig : $op->closing;
|
||||
foreach ($changes as $closing) {
|
||||
// Skip some of the changes, as they are caused by module install
|
||||
// code.
|
||||
$found = FALSE;
|
||||
if (!empty($skipped_config[$config_name])) {
|
||||
foreach ($skipped_config[$config_name] as $line) {
|
||||
if (strpos($closing, $line) !== FALSE) {
|
||||
$found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$all_skipped = $all_skipped && $found;
|
||||
}
|
||||
|
||||
if (!$all_skipped) {
|
||||
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
|
||||
}
|
||||
break;
|
||||
case 'Drupal\Component\Diff\Engine\DiffOpAdd':
|
||||
// The _core property does not exist in the default config.
|
||||
if ($op->closing[0] === '_core:') {
|
||||
break;
|
||||
}
|
||||
foreach ($op->closing as $closing) {
|
||||
// The UUIDs don't exist in the default config.
|
||||
if (strpos($closing, 'uuid: ') === 0) {
|
||||
break;
|
||||
}
|
||||
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1496
2017/web/core/tests/Drupal/KernelTests/AssertContentTrait.php
Normal file
1496
2017/web/core/tests/Drupal/KernelTests/AssertContentTrait.php
Normal file
File diff suppressed because it is too large
Load diff
124
2017/web/core/tests/Drupal/KernelTests/AssertLegacyTrait.php
Normal file
124
2017/web/core/tests/Drupal/KernelTests/AssertLegacyTrait.php
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests;
|
||||
|
||||
/**
|
||||
* Translates Simpletest assertion methods to PHPUnit.
|
||||
*
|
||||
* Protected methods are custom. Public static methods override methods of
|
||||
* \PHPUnit\Framework\Assert.
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0. Use PHPUnit's native
|
||||
* assert methods instead.
|
||||
*/
|
||||
trait AssertLegacyTrait {
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::assert()
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertTrue()
|
||||
* instead.
|
||||
*/
|
||||
protected function assert($actual, $message = '') {
|
||||
parent::assertTrue((bool) $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::assertTrue()
|
||||
*/
|
||||
public static function assertTrue($actual, $message = '') {
|
||||
if (is_bool($actual)) {
|
||||
parent::assertTrue($actual, $message);
|
||||
}
|
||||
else {
|
||||
parent::assertNotEmpty($actual, $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::assertFalse()
|
||||
*/
|
||||
public static function assertFalse($actual, $message = '') {
|
||||
if (is_bool($actual)) {
|
||||
parent::assertFalse($actual, $message);
|
||||
}
|
||||
else {
|
||||
parent::assertEmpty($actual, $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::assertEqual()
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertEquals()
|
||||
* instead.
|
||||
*/
|
||||
protected function assertEqual($actual, $expected, $message = '') {
|
||||
$this->assertEquals($expected, $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::assertNotEqual()
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0. Use
|
||||
* self::assertNotEquals() instead.
|
||||
*/
|
||||
protected function assertNotEqual($actual, $expected, $message = '') {
|
||||
$this->assertNotEquals($expected, $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::assertIdentical()
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertSame()
|
||||
* instead.
|
||||
*/
|
||||
protected function assertIdentical($actual, $expected, $message = '') {
|
||||
$this->assertSame($expected, $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::assertNotIdentical()
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0. Use
|
||||
* self::assertNotSame() instead.
|
||||
*/
|
||||
protected function assertNotIdentical($actual, $expected, $message = '') {
|
||||
$this->assertNotSame($expected, $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::assertIdenticalObject()
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertEquals()
|
||||
* instead.
|
||||
*/
|
||||
protected function assertIdenticalObject($actual, $expected, $message = '') {
|
||||
// Note: ::assertSame checks whether its the same object. ::assertEquals
|
||||
// though compares
|
||||
|
||||
$this->assertEquals($expected, $actual, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::pass()
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertTrue()
|
||||
* instead.
|
||||
*/
|
||||
protected function pass($message) {
|
||||
$this->assertTrue(TRUE, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\simpletest\TestBase::verbose()
|
||||
*/
|
||||
protected function verbose($message) {
|
||||
if (in_array('--debug', $_SERVER['argv'], TRUE)) {
|
||||
// Write directly to STDOUT to not produce unexpected test output.
|
||||
// The STDOUT stream does not obey output buffering.
|
||||
fwrite(STDOUT, $message . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Component\Utility;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Provides a test covering integration of SafeMarkup with other systems.
|
||||
*
|
||||
* @group Utility
|
||||
*/
|
||||
class SafeMarkupKernelTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->container->get('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets arguments for FormattableMarkup based on Url::fromUri() parameters.
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI of the resource.
|
||||
* @param array $options
|
||||
* The options to pass to Url::fromUri().
|
||||
*
|
||||
* @return array
|
||||
* Array containing:
|
||||
* - ':url': A URL string.
|
||||
*
|
||||
* @see \Drupal\Component\Render\FormattableMarkup
|
||||
*/
|
||||
protected static function getSafeMarkupUriArgs($uri, $options = []) {
|
||||
$args[':url'] = Url::fromUri($uri, $options)->toString();
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests URL ":placeholders" in \Drupal\Component\Render\FormattableMarkup.
|
||||
*
|
||||
* @dataProvider providerTestSafeMarkupUri
|
||||
*/
|
||||
public function testSafeMarkupUri($string, $uri, $options, $expected) {
|
||||
$args = self::getSafeMarkupUriArgs($uri, $options);
|
||||
$this->assertEquals($expected, new FormattableMarkup($string, $args));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestSafeMarkupUri() {
|
||||
$data = [];
|
||||
$data['routed-url'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
'route:system.admin',
|
||||
[],
|
||||
'Hey giraffe <a href="/admin">MUUUH</a>',
|
||||
];
|
||||
$data['routed-with-query'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
'route:system.admin',
|
||||
['query' => ['bar' => 'baz#']],
|
||||
'Hey giraffe <a href="/admin?bar=baz%23">MUUUH</a>',
|
||||
];
|
||||
$data['routed-with-fragment'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
'route:system.admin',
|
||||
['fragment' => 'bar<'],
|
||||
'Hey giraffe <a href="/admin#bar&lt;">MUUUH</a>',
|
||||
];
|
||||
$data['unrouted-url'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
'base://foo',
|
||||
[],
|
||||
'Hey giraffe <a href="/foo">MUUUH</a>',
|
||||
];
|
||||
$data['unrouted-with-query'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
'base://foo',
|
||||
['query' => ['bar' => 'baz#']],
|
||||
'Hey giraffe <a href="/foo?bar=baz%23">MUUUH</a>',
|
||||
];
|
||||
$data['unrouted-with-fragment'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
'base://foo',
|
||||
['fragment' => 'bar<'],
|
||||
'Hey giraffe <a href="/foo#bar&lt;">MUUUH</a>',
|
||||
];
|
||||
$data['mailto-protocol'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
'mailto:test@example.com',
|
||||
[],
|
||||
'Hey giraffe <a href="mailto:test@example.com">MUUUH</a>',
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestSafeMarkupUriWithException
|
||||
*/
|
||||
public function testSafeMarkupUriWithExceptionUri($string, $uri) {
|
||||
// Should throw an \InvalidArgumentException, due to Uri::toString().
|
||||
$this->setExpectedException(\InvalidArgumentException::class);
|
||||
$args = self::getSafeMarkupUriArgs($uri);
|
||||
|
||||
new FormattableMarkup($string, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestSafeMarkupUriWithException() {
|
||||
$data = [];
|
||||
$data['js-protocol'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
"javascript:alert('xss')",
|
||||
];
|
||||
$data['js-with-fromCharCode'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
"javascript:alert(String.fromCharCode(88,83,83))",
|
||||
];
|
||||
$data['non-url-with-colon'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
"llamas: they are not URLs",
|
||||
];
|
||||
$data['non-url-with-html'] = [
|
||||
'Hey giraffe <a href=":url">MUUUH</a>',
|
||||
'<span>not a url</span>',
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Config;
|
||||
|
||||
use Drupal\Core\Config\FileStorage;
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\KernelTests\AssertConfigTrait;
|
||||
use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the installed config matches the default config.
|
||||
*
|
||||
* @group Config
|
||||
*/
|
||||
class DefaultConfigTest extends KernelTestBase {
|
||||
|
||||
use AssertConfigTrait;
|
||||
use FileSystemModuleDiscoveryDataProviderTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $timeLimit = 500;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'user'];
|
||||
|
||||
/**
|
||||
* The following config entries are changed on module install.
|
||||
*
|
||||
* Comparing them does not make sense.
|
||||
*
|
||||
* @todo Figure out why simpletest.settings is not installed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $skippedConfig = [
|
||||
'locale.settings' => ['path: '],
|
||||
'syslog.settings' => ['facility: '],
|
||||
'simpletest.settings' => TRUE,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// @todo ModuleInstaller calls system_rebuild_module_data which is part of
|
||||
// system.module, see https://www.drupal.org/node/2208429.
|
||||
include_once $this->root . '/core/modules/system/system.module';
|
||||
|
||||
// Set up the state values so we know where to find the files when running
|
||||
// drupal_get_filename().
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
system_rebuild_module_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if installed config is equal to the exported config.
|
||||
*
|
||||
* @dataProvider coreModuleListDataProvider
|
||||
*/
|
||||
public function testModuleConfig($module) {
|
||||
// System and user are required in order to be able to install some of the
|
||||
// other modules. Therefore they are put into static::$modules, which though
|
||||
// doesn't install config files, so import those config files explicitly.
|
||||
switch ($module) {
|
||||
case 'system':
|
||||
case 'user':
|
||||
$this->installConfig([$module]);
|
||||
break;
|
||||
}
|
||||
|
||||
$module_path = drupal_get_path('module', $module) . '/';
|
||||
|
||||
/** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */
|
||||
$module_installer = $this->container->get('module_installer');
|
||||
|
||||
// Work out any additional modules and themes that need installing to create
|
||||
// an optional config.
|
||||
$optional_config_storage = new FileStorage($module_path . InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION);
|
||||
$modules_to_install = [$module];
|
||||
$themes_to_install = [];
|
||||
foreach ($optional_config_storage->listAll() as $config_name) {
|
||||
$data = $optional_config_storage->read($config_name);
|
||||
if (isset($data['dependencies']['module'])) {
|
||||
$modules_to_install = array_merge($modules_to_install, $data['dependencies']['module']);
|
||||
}
|
||||
if (isset($data['dependencies']['theme'])) {
|
||||
$themes_to_install = array_merge($themes_to_install, $data['dependencies']['theme']);
|
||||
}
|
||||
}
|
||||
$module_installer->install(array_unique($modules_to_install));
|
||||
$this->container->get('theme_installer')->install($themes_to_install);
|
||||
|
||||
// Test configuration in the module's config/install directory.
|
||||
$module_config_storage = new FileStorage($module_path . InstallStorage::CONFIG_INSTALL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION);
|
||||
$this->doTestsOnConfigStorage($module_config_storage);
|
||||
|
||||
// Test configuration in the module's config/optional directory.
|
||||
$this->doTestsOnConfigStorage($optional_config_storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that default config matches the installed config.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $default_config_storage
|
||||
* The default config storage to test.
|
||||
*/
|
||||
protected function doTestsOnConfigStorage(StorageInterface $default_config_storage) {
|
||||
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
|
||||
$config_manager = $this->container->get('config.manager');
|
||||
|
||||
// Just connect directly to the config table so we don't need to worry about
|
||||
// the cache layer.
|
||||
$active_config_storage = $this->container->get('config.storage');
|
||||
|
||||
foreach ($default_config_storage->listAll() as $config_name) {
|
||||
if ($active_config_storage->exists($config_name)) {
|
||||
// If it is a config entity re-save it. This ensures that any
|
||||
// recalculation of dependencies does not cause config change.
|
||||
if ($entity_type = $config_manager->getEntityTypeIdByName($config_name)) {
|
||||
$entity_storage = $config_manager
|
||||
->getEntityManager()
|
||||
->getStorage($entity_type);
|
||||
$id = $entity_storage->getIDFromConfigName($config_name, $entity_storage->getEntityType()
|
||||
->getConfigPrefix());
|
||||
$entity_storage->load($id)->calculateDependencies()->save();
|
||||
}
|
||||
$result = $config_manager->diff($default_config_storage, $active_config_storage, $config_name);
|
||||
$this->assertConfigDiff($result, $config_name, static::$skippedConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Config;
|
||||
|
||||
use Drupal\Core\Config\Schema\SequenceDataDefinition;
|
||||
use Drupal\Core\Config\Schema\TypedConfigInterface;
|
||||
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use Drupal\Core\TypedData\Type\IntegerInterface;
|
||||
use Drupal\Core\TypedData\Type\StringInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||
|
||||
/**
|
||||
* Tests config validation mechanism.
|
||||
*
|
||||
* @group Config
|
||||
*/
|
||||
class TypedConfigTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['config_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig('config_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the Typed Data API is implemented correctly.
|
||||
*/
|
||||
public function testTypedDataAPI() {
|
||||
/** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
|
||||
$typed_config_manager = \Drupal::service('config.typed');
|
||||
/** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
|
||||
$typed_config = $typed_config_manager->get('config_test.validation');
|
||||
|
||||
// Test a primitive.
|
||||
$string_data = $typed_config->get('llama');
|
||||
$this->assertInstanceOf(StringInterface::class, $string_data);
|
||||
$this->assertEquals('llama', $string_data->getValue());
|
||||
|
||||
// Test complex data.
|
||||
$mapping = $typed_config->get('cat');
|
||||
/** @var \Drupal\Core\TypedData\ComplexDataInterface $mapping */
|
||||
$this->assertInstanceOf(ComplexDataInterface::class, $mapping);
|
||||
$this->assertInstanceOf(StringInterface::class, $mapping->get('type'));
|
||||
$this->assertEquals('kitten', $mapping->get('type')->getValue());
|
||||
$this->assertInstanceOf(IntegerInterface::class, $mapping->get('count'));
|
||||
$this->assertEquals(2, $mapping->get('count')->getValue());
|
||||
// Verify the item metadata is available.
|
||||
$this->assertInstanceOf(ComplexDataDefinitionInterface::class, $mapping->getDataDefinition());
|
||||
$this->assertArrayHasKey('type', $mapping->getProperties());
|
||||
$this->assertArrayHasKey('count', $mapping->getProperties());
|
||||
|
||||
// Test accessing sequences.
|
||||
$sequence = $typed_config->get('giraffe');
|
||||
/** @var \Drupal\Core\TypedData\ListInterface $sequence */
|
||||
$this->assertInstanceOf(ComplexDataInterface::class, $sequence);
|
||||
$this->assertInstanceOf(StringInterface::class, $sequence->get('hum1'));
|
||||
$this->assertEquals('hum1', $sequence->get('hum1')->getValue());
|
||||
$this->assertEquals('hum2', $sequence->get('hum2')->getValue());
|
||||
$this->assertEquals(2, count($sequence->getIterator()));
|
||||
// Verify the item metadata is available.
|
||||
$this->assertInstanceOf(SequenceDataDefinition::class, $sequence->getDataDefinition());
|
||||
|
||||
// Test accessing typed config objects for simple config and config
|
||||
// entities.
|
||||
$typed_config_manager = \Drupal::service('config.typed');
|
||||
$typed_config = $typed_config_manager->createFromNameAndData('config_test.validation', \Drupal::configFactory()->get('config_test.validation')->get());
|
||||
$this->assertInstanceOf(TypedConfigInterface::class, $typed_config);
|
||||
$this->assertEquals(['llama', 'cat', 'giraffe', 'uuid', '_core'], array_keys($typed_config->getElements()));
|
||||
|
||||
$config_test_entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([
|
||||
'id' => 'asterix',
|
||||
'label' => 'Asterix',
|
||||
'weight' => 11,
|
||||
'style' => 'test_style',
|
||||
]);
|
||||
|
||||
$typed_config = $typed_config_manager->createFromNameAndData($config_test_entity->getConfigDependencyName(), $config_test_entity->toArray());
|
||||
$this->assertInstanceOf(TypedConfigInterface::class, $typed_config);
|
||||
$this->assertEquals(['uuid', 'langcode', 'status', 'dependencies', 'id', 'label', 'weight', 'style', 'size', 'size_value', 'protected_property'], array_keys($typed_config->getElements()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests config validation via the Typed Data API.
|
||||
*/
|
||||
public function testSimpleConfigValidation() {
|
||||
$config = \Drupal::configFactory()->getEditable('config_test.validation');
|
||||
/** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
|
||||
$typed_config_manager = \Drupal::service('config.typed');
|
||||
/** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
|
||||
$typed_config = $typed_config_manager->get('config_test.validation');
|
||||
|
||||
$result = $typed_config->validate();
|
||||
$this->assertInstanceOf(ConstraintViolationListInterface::class, $result);
|
||||
$this->assertEmpty($result);
|
||||
|
||||
// Test constraints on primitive types.
|
||||
$config->set('llama', 'elephant');
|
||||
$config->save();
|
||||
|
||||
$typed_config = $typed_config_manager->get('config_test.validation');
|
||||
$result = $typed_config->validate();
|
||||
// Its not a valid llama anymore.
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals('no valid llama', $result->get(0)->getMessage());
|
||||
|
||||
// Test constraints on mapping.
|
||||
$config->set('llama', 'llama');
|
||||
$config->set('cat.type', 'nyans');
|
||||
$config->save();
|
||||
|
||||
$typed_config = $typed_config_manager->get('config_test.validation');
|
||||
$result = $typed_config->validate();
|
||||
$this->assertEmpty($result);
|
||||
|
||||
// Test constrains on nested mapping.
|
||||
$config->set('cat.type', 'miaus');
|
||||
$config->save();
|
||||
|
||||
$typed_config = $typed_config_manager->get('config_test.validation');
|
||||
$result = $typed_config->validate();
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals('no valid cat', $result->get(0)->getMessage());
|
||||
|
||||
// Test constrains on sequences elements.
|
||||
$config->set('cat.type', 'nyans');
|
||||
$config->set('giraffe', ['muh', 'hum2']);
|
||||
$config->save();
|
||||
$typed_config = $typed_config_manager->get('config_test.validation');
|
||||
$result = $typed_config->validate();
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals('Giraffes just hum', $result->get(0)->getMessage());
|
||||
|
||||
// Test constrains on the sequence itself.
|
||||
$config->set('giraffe', ['hum', 'hum2', 'invalid-key' => 'hum']);
|
||||
$config->save();
|
||||
|
||||
$typed_config = $typed_config_manager->get('config_test.validation');
|
||||
$result = $typed_config->validate();
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals('giraffe', $result->get(0)->getPropertyPath());
|
||||
$this->assertEquals('Invalid giraffe key.', $result->get(0)->getMessage());
|
||||
|
||||
// Validates mapping.
|
||||
$typed_config = $typed_config_manager->get('config_test.validation');
|
||||
$value = $typed_config->getValue();
|
||||
unset($value['giraffe']);
|
||||
$value['elephant'] = 'foo';
|
||||
$value['zebra'] = 'foo';
|
||||
$typed_config->setValue($value);
|
||||
$result = $typed_config->validate();
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals('', $result->get(0)->getPropertyPath());
|
||||
$this->assertEquals('Unexpected keys: elephant, zebra', $result->get(0)->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests;
|
||||
|
||||
use Drupal\Core\Form\FormState;
|
||||
|
||||
/**
|
||||
* Full generic test suite for any form that data with the configuration system.
|
||||
*
|
||||
* @see UserAdminSettingsFormTest
|
||||
* For a full working implementation.
|
||||
*/
|
||||
abstract class ConfigFormTestBase extends KernelTestBase {
|
||||
/**
|
||||
* Form ID to use for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Form\FormInterface
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* Values to use for testing.
|
||||
*
|
||||
* Contains details for form key, configuration object name, and config key.
|
||||
* Example:
|
||||
* @code
|
||||
* array(
|
||||
* 'user_mail_cancel_confirm_body' => array(
|
||||
* '#value' => $this->randomString(),
|
||||
* '#config_name' => 'user.mail',
|
||||
* '#config_key' => 'cancel_confirm.body',
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $values;
|
||||
|
||||
/**
|
||||
* Submit the system_config_form ensure the configuration has expected values.
|
||||
*/
|
||||
public function testConfigForm() {
|
||||
// Programmatically submit the given values.
|
||||
$values = [];
|
||||
foreach ($this->values as $form_key => $data) {
|
||||
$values[$form_key] = $data['#value'];
|
||||
}
|
||||
$form_state = (new FormState())->setValues($values);
|
||||
\Drupal::formBuilder()->submitForm($this->form, $form_state);
|
||||
|
||||
// Check that the form returns an error when expected, and vice versa.
|
||||
$errors = $form_state->getErrors();
|
||||
$valid_form = empty($errors);
|
||||
$args = [
|
||||
'%values' => print_r($values, TRUE),
|
||||
'%errors' => $valid_form ? t('None') : implode(' ', $errors),
|
||||
];
|
||||
$this->assertTrue($valid_form, format_string('Input values: %values<br/>Validation handler errors: %errors', $args));
|
||||
|
||||
foreach ($this->values as $data) {
|
||||
$this->assertEqual($data['#value'], $this->config($data['#config_name'])->get($data['#config_key']));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Action;
|
||||
|
||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\system\Entity\Action;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* @group Action
|
||||
*/
|
||||
class DeleteActionTest extends KernelTestBase {
|
||||
|
||||
protected $testUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'entity_test', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installSchema('system', ['sequences', 'key_value_expire']);
|
||||
|
||||
$this->testUser = User::create([
|
||||
'name' => 'foobar',
|
||||
'mail' => 'foobar@example.com',
|
||||
]);
|
||||
$this->testUser->save();
|
||||
\Drupal::service('current_user')->setAccount($this->testUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver::getDerivativeDefinitions
|
||||
*/
|
||||
public function testGetDerivativeDefinitions() {
|
||||
$deriver = new EntityDeleteActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation());
|
||||
$this->assertEquals([
|
||||
'entity_test_mulrevpub' => [
|
||||
'type' => 'entity_test_mulrevpub',
|
||||
'label' => 'Delete test entity - revisions, data table, and published interface',
|
||||
'action_label' => 'Delete',
|
||||
'confirm_form_route_name' => 'entity.entity_test_mulrevpub.delete_multiple_form',
|
||||
],
|
||||
'entity_test_rev' => [
|
||||
'type' => 'entity_test_rev',
|
||||
'label' => 'Delete test entity - revisions',
|
||||
'action_label' => 'Delete',
|
||||
'confirm_form_route_name' => 'entity.entity_test_rev.delete_multiple_form',
|
||||
],
|
||||
], $deriver->getDerivativeDefinitions([
|
||||
'action_label' => 'Delete',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Action\Plugin\Action\DeleteAction::execute
|
||||
*/
|
||||
public function testDeleteAction() {
|
||||
$entity = EntityTestMulRevPub::create(['name' => 'test']);
|
||||
$entity->save();
|
||||
|
||||
$action = Action::create([
|
||||
'id' => 'entity_delete_action',
|
||||
'plugin' => 'entity:delete_action:entity_test_mulrevpub',
|
||||
]);
|
||||
$action->save();
|
||||
|
||||
$action->execute([$entity]);
|
||||
$this->assertArraySubset(['module' => ['entity_test']], $action->getDependencies());
|
||||
|
||||
/** @var \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store */
|
||||
$temp_store = \Drupal::service('tempstore.private');
|
||||
$store_entries = $temp_store->get('entity_delete_multiple_confirm')->get($this->testUser->id() . ':entity_test_mulrevpub');
|
||||
$this->assertArraySubset([$this->testUser->id() => ['en' => 'en']], $store_entries);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Action;
|
||||
|
||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\system\Entity\Action;
|
||||
|
||||
/**
|
||||
* @group Action
|
||||
*/
|
||||
class PublishActionTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'entity_test', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver::getDerivativeDefinitions
|
||||
*/
|
||||
public function testGetDerivativeDefinitions() {
|
||||
$deriver = new EntityPublishedActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation());
|
||||
$this->assertArraySubset([
|
||||
'entity_test_mulrevpub' => [
|
||||
'type' => 'entity_test_mulrevpub',
|
||||
'label' => 'Save test entity - revisions, data table, and published interface',
|
||||
'action_label' => 'Save',
|
||||
],
|
||||
], $deriver->getDerivativeDefinitions([
|
||||
'action_label' => 'Save',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Action\Plugin\Action\PublishAction::execute
|
||||
*/
|
||||
public function testPublishAction() {
|
||||
$entity = EntityTestMulRevPub::create(['name' => 'test']);
|
||||
$entity->setUnpublished()->save();
|
||||
|
||||
$action = Action::create([
|
||||
'id' => 'entity_publish_action',
|
||||
'plugin' => 'entity:publish_action:entity_test_mulrevpub',
|
||||
]);
|
||||
$action->save();
|
||||
$this->assertFalse($entity->isPublished());
|
||||
$action->execute([$entity]);
|
||||
$this->assertTrue($entity->isPublished());
|
||||
$this->assertArraySubset(['module' => ['entity_test']], $action->getDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Action\Plugin\Action\UnpublishAction::execute
|
||||
*/
|
||||
public function testUnpublishAction() {
|
||||
$entity = EntityTestMulRevPub::create(['name' => 'test']);
|
||||
$entity->setPublished()->save();
|
||||
|
||||
$action = Action::create([
|
||||
'id' => 'entity_unpublish_action',
|
||||
'plugin' => 'entity:unpublish_action:entity_test_mulrevpub',
|
||||
]);
|
||||
$action->save();
|
||||
$this->assertTrue($entity->isPublished());
|
||||
$action->execute([$entity]);
|
||||
$this->assertFalse($entity->isPublished());
|
||||
$this->assertArraySubset(['module' => ['entity_test']], $action->getDependencies());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Action;
|
||||
|
||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver;
|
||||
use Drupal\entity_test\Entity\EntityTestMulChanged;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\system\Entity\Action;
|
||||
|
||||
/**
|
||||
* @group Action
|
||||
*/
|
||||
class SaveActionTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'entity_test', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_mul_changed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver::getDerivativeDefinitions
|
||||
*/
|
||||
public function testGetDerivativeDefinitions() {
|
||||
$deriver = new EntityChangedActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation());
|
||||
$this->assertArraySubset([
|
||||
'entity_test_mul_changed' => [
|
||||
'type' => 'entity_test_mul_changed',
|
||||
'label' => 'Save test entity - data table',
|
||||
'action_label' => 'Save',
|
||||
],
|
||||
], $deriver->getDerivativeDefinitions([
|
||||
'action_label' => 'Save',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Action\Plugin\Action\SaveAction::execute
|
||||
*/
|
||||
public function testSaveAction() {
|
||||
$entity = EntityTestMulChanged::create(['name' => 'test']);
|
||||
$entity->save();
|
||||
$saved_time = $entity->getChangedTime();
|
||||
|
||||
$action = Action::create([
|
||||
'id' => 'entity_save_action',
|
||||
'plugin' => 'entity:save_action:entity_test_mul_changed',
|
||||
]);
|
||||
$action->save();
|
||||
$action->execute([$entity]);
|
||||
$this->assertNotSame($saved_time, $entity->getChangedTime());
|
||||
$this->assertArraySubset(['module' => ['entity_test']], $action->getDependencies());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
/**
|
||||
* Performs tests on AJAX framework commands.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class CommandsTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'node', 'ajax_test', 'ajax_forms_test'];
|
||||
|
||||
/**
|
||||
* Regression test: Settings command exists regardless of JS aggregation.
|
||||
*/
|
||||
public function testAttachedSettings() {
|
||||
$assert = function ($message) {
|
||||
$response = new AjaxResponse();
|
||||
$response->setAttachments([
|
||||
'library' => ['core/drupalSettings'],
|
||||
'drupalSettings' => ['foo' => 'bar'],
|
||||
]);
|
||||
|
||||
$ajax_response_attachments_processor = \Drupal::service('ajax_response.attachments_processor');
|
||||
$subscriber = new AjaxResponseSubscriber($ajax_response_attachments_processor);
|
||||
$event = new FilterResponseEvent(
|
||||
\Drupal::service('http_kernel'),
|
||||
new Request(),
|
||||
HttpKernelInterface::MASTER_REQUEST,
|
||||
$response
|
||||
);
|
||||
$subscriber->onResponse($event);
|
||||
$expected = [
|
||||
'command' => 'settings',
|
||||
];
|
||||
$this->assertCommand($response->getCommands(), $expected, $message);
|
||||
};
|
||||
|
||||
$config = $this->config('system.performance');
|
||||
|
||||
$config->set('js.preprocess', FALSE)->save();
|
||||
$assert('Settings command exists when JS aggregation is disabled.');
|
||||
|
||||
$config->set('js.preprocess', TRUE)->save();
|
||||
$assert('Settings command exists when JS aggregation is enabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the array of Ajax commands contains the searched command.
|
||||
*
|
||||
* An AjaxResponse object stores an array of Ajax commands. This array
|
||||
* sometimes includes commands automatically provided by the framework in
|
||||
* addition to commands returned by a particular controller. During testing,
|
||||
* we're usually interested that a particular command is present, and don't
|
||||
* care whether other commands precede or follow the one we're interested in.
|
||||
* Additionally, the command we're interested in may include additional data
|
||||
* that we're not interested in. Therefore, this function simply asserts that
|
||||
* one of the commands in $haystack contains all of the keys and values in
|
||||
* $needle. Furthermore, if $needle contains a 'settings' key with an array
|
||||
* value, we simply assert that all keys and values within that array are
|
||||
* present in the command we're checking, and do not consider it a failure if
|
||||
* the actual command contains additional settings that aren't part of
|
||||
* $needle.
|
||||
*
|
||||
* @param $haystack
|
||||
* An array of rendered Ajax commands returned by the server.
|
||||
* @param $needle
|
||||
* Array of info we're expecting in one of those commands.
|
||||
* @param $message
|
||||
* An assertion message.
|
||||
*/
|
||||
protected function assertCommand($haystack, $needle, $message) {
|
||||
$found = FALSE;
|
||||
foreach ($haystack as $command) {
|
||||
// If the command has additional settings that we're not testing for, do
|
||||
// not consider that a failure.
|
||||
if (isset($command['settings']) && is_array($command['settings']) && isset($needle['settings']) && is_array($needle['settings'])) {
|
||||
$command['settings'] = array_intersect_key($command['settings'], $needle['settings']);
|
||||
}
|
||||
// If the command has additional data that we're not testing for, do not
|
||||
// consider that a failure. Also, == instead of ===, because we don't
|
||||
// require the key/value pairs to be in any particular order
|
||||
// (http://php.net/manual/language.operators.array.php).
|
||||
if (array_intersect_key($command, $needle) == $needle) {
|
||||
$found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($found, $message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,480 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Asset;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests #attached assets: attached asset libraries and JavaScript settings.
|
||||
*
|
||||
* I.e. tests:
|
||||
*
|
||||
* @code
|
||||
* $build['#attached']['library'] = …
|
||||
* $build['#attached']['drupalSettings'] = …
|
||||
* @endcode
|
||||
*
|
||||
* @group Common
|
||||
* @group Asset
|
||||
*/
|
||||
class AttachedAssetsTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The asset resolver service.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\AssetResolverInterface
|
||||
*/
|
||||
protected $assetResolver;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['language', 'simpletest', 'common_test', 'system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->container->get('router.builder')->rebuild();
|
||||
|
||||
$this->assetResolver = $this->container->get('asset.resolver');
|
||||
$this->renderer = $this->container->get('renderer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that default CSS and JavaScript is empty.
|
||||
*/
|
||||
public function testDefault() {
|
||||
$assets = new AttachedAssets();
|
||||
$this->assertEqual([], $this->assetResolver->getCssAssets($assets, FALSE), 'Default CSS is empty.');
|
||||
list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, FALSE);
|
||||
$this->assertEqual([], $js_assets_header, 'Default header JavaScript is empty.');
|
||||
$this->assertEqual([], $js_assets_footer, 'Default footer JavaScript is empty.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests non-existing libraries.
|
||||
*/
|
||||
public function testLibraryUnknown() {
|
||||
$build['#attached']['library'][] = 'core/unknown';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$this->assertSame([], $this->assetResolver->getJsAssets($assets, FALSE)[0], 'Unknown library was not added to the page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a CSS and a JavaScript file.
|
||||
*/
|
||||
public function testAddFiles() {
|
||||
$build['#attached']['library'][] = 'common_test/files';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$css = $this->assetResolver->getCssAssets($assets, FALSE);
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/bar.css', $css), 'CSS files are correctly added.');
|
||||
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/foo.js', $js), 'JavaScript files are correctly added.');
|
||||
|
||||
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_css = $this->renderer->renderPlain($css_render_array);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
|
||||
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/bar.css')) . '?' . $query_string . '" media="all" />'), FALSE, 'Rendering an external CSS file.');
|
||||
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/foo.js')) . '?' . $query_string . '"></script>'), FALSE, 'Rendering an external JavaScript file.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding JavaScript settings.
|
||||
*/
|
||||
public function testAddJsSettings() {
|
||||
// Add a file in order to test default settings.
|
||||
$build['#attached']['library'][] = 'core/drupalSettings';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$this->assertEqual([], $assets->getSettings(), 'JavaScript settings on $assets are empty.');
|
||||
$javascript = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$this->assertTrue(array_key_exists('currentPath', $javascript['drupalSettings']['data']['path']), 'The current path JavaScript setting is set correctly.');
|
||||
$this->assertTrue(array_key_exists('currentPath', $assets->getSettings()['path']), 'JavaScript settings on $assets are resolved after retrieving JavaScript assets, and are equal to the returned JavaScript settings.');
|
||||
|
||||
$assets->setSettings(['drupal' => 'rocks', 'dries' => 280342800]);
|
||||
$javascript = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$this->assertEqual(280342800, $javascript['drupalSettings']['data']['dries'], 'JavaScript setting is set correctly.');
|
||||
$this->assertEqual('rocks', $javascript['drupalSettings']['data']['drupal'], 'The other JavaScript setting is set correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding external CSS and JavaScript files.
|
||||
*/
|
||||
public function testAddExternalFiles() {
|
||||
$build['#attached']['library'][] = 'common_test/external';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$css = $this->assetResolver->getCssAssets($assets, FALSE);
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$this->assertTrue(array_key_exists('http://example.com/stylesheet.css', $css), 'External CSS files are correctly added.');
|
||||
$this->assertTrue(array_key_exists('http://example.com/script.js', $js), 'External JavaScript files are correctly added.');
|
||||
|
||||
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_css = $this->renderer->renderPlain($css_render_array);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="http://example.com/stylesheet.css" media="all" />'), FALSE, 'Rendering an external CSS file.');
|
||||
$this->assertNotIdentical(strpos($rendered_js, '<script src="http://example.com/script.js"></script>'), FALSE, 'Rendering an external JavaScript file.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding JavaScript files with additional attributes.
|
||||
*/
|
||||
public function testAttributes() {
|
||||
$build['#attached']['library'][] = 'common_test/js-attributes';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
|
||||
$expected_2 = '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js')) . '?v=1" defer bar="foo"></script>';
|
||||
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered external JavaScript with correct defer and random attributes.');
|
||||
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered internal JavaScript with correct defer and random attributes.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that attributes are maintained when JS aggregation is enabled.
|
||||
*/
|
||||
public function testAggregatedAttributes() {
|
||||
$build['#attached']['library'][] = 'common_test/js-attributes';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$js = $this->assetResolver->getJsAssets($assets, TRUE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
|
||||
$expected_2 = '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js')) . '?v=1" defer bar="foo"></script>';
|
||||
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered external JavaScript with correct defer and random attributes.');
|
||||
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered internal JavaScript with correct defer and random attributes.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Integration test for CSS/JS aggregation.
|
||||
*/
|
||||
public function testAggregation() {
|
||||
$build['#attached']['library'][] = 'core/drupal.timezone';
|
||||
$build['#attached']['library'][] = 'core/drupal.vertical-tabs';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$this->assertEqual(1, count($this->assetResolver->getCssAssets($assets, TRUE)), 'There is a sole aggregated CSS asset.');
|
||||
|
||||
list($header_js, $footer_js) = $this->assetResolver->getJsAssets($assets, TRUE);
|
||||
$this->assertEqual([], \Drupal::service('asset.js.collection_renderer')->render($header_js), 'There are 0 JavaScript assets in the header.');
|
||||
$rendered_footer_js = \Drupal::service('asset.js.collection_renderer')->render($footer_js);
|
||||
$this->assertEqual(2, count($rendered_footer_js), 'There are 2 JavaScript assets in the footer.');
|
||||
$this->assertEqual('drupal-settings-json', $rendered_footer_js[0]['#attributes']['data-drupal-selector'], 'The first of the two JavaScript assets in the footer has drupal settings.');
|
||||
$this->assertEqual(0, strpos($rendered_footer_js[1]['#attributes']['src'], base_path()), 'The second of the two JavaScript assets in the footer has the sole aggregated JavaScript asset.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests JavaScript settings.
|
||||
*/
|
||||
public function testSettings() {
|
||||
$build = [];
|
||||
$build['#attached']['library'][] = 'core/drupalSettings';
|
||||
// Nonsensical value to verify if it's possible to override path settings.
|
||||
$build['#attached']['drupalSettings']['path']['pathPrefix'] = 'yarhar';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
// Cast to string since this returns a \Drupal\Core\Render\Markup object.
|
||||
$rendered_js = (string) $this->renderer->renderPlain($js_render_array);
|
||||
|
||||
// Parse the generated drupalSettings <script> back to a PHP representation.
|
||||
$startToken = '{';
|
||||
$endToken = '}';
|
||||
$start = strpos($rendered_js, $startToken);
|
||||
$end = strrpos($rendered_js, $endToken);
|
||||
// Convert to a string, as $renderer_js is a \Drupal\Core\Render\Markup
|
||||
// object.
|
||||
$json = mb_substr($rendered_js, $start, $end - $start + 1);
|
||||
$parsed_settings = Json::decode($json);
|
||||
|
||||
// Test whether the settings for core/drupalSettings are available.
|
||||
$this->assertTrue(isset($parsed_settings['path']['baseUrl']), 'drupalSettings.path.baseUrl is present.');
|
||||
$this->assertIdentical($parsed_settings['path']['pathPrefix'], 'yarhar', 'drupalSettings.path.pathPrefix is present and has the correct (overridden) value.');
|
||||
$this->assertIdentical($parsed_settings['path']['currentPath'], '', 'drupalSettings.path.currentPath is present and has the correct value.');
|
||||
$this->assertIdentical($parsed_settings['path']['currentPathIsAdmin'], FALSE, 'drupalSettings.path.currentPathIsAdmin is present and has the correct value.');
|
||||
$this->assertIdentical($parsed_settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is present and has the correct value.');
|
||||
$this->assertIdentical($parsed_settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is present and has the correct value.');
|
||||
|
||||
// Tests whether altering JavaScript settings via hook_js_settings_alter()
|
||||
// is working as expected.
|
||||
// @see common_test_js_settings_alter()
|
||||
$this->assertIdentical($parsed_settings['pluralDelimiter'], '☃');
|
||||
$this->assertIdentical($parsed_settings['foo'], 'bar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests JS assets depending on the 'core/<head>' virtual library.
|
||||
*/
|
||||
public function testHeaderHTML() {
|
||||
$build['#attached']['library'][] = 'common_test/js-header';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
|
||||
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/header.js')) . '?' . $query_string . '"></script>'), FALSE, 'The JS asset in common_test/js-header appears in the header.');
|
||||
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/misc/drupal.js'))), FALSE, 'The JS asset of the direct dependency (core/drupal) of common_test/js-header appears in the header.');
|
||||
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/assets/vendor/domready/ready.min.js'))), FALSE, 'The JS asset of the indirect dependency (core/domready) of common_test/js-header appears in the header.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that for assets with cache = FALSE, Drupal sets preprocess = FALSE.
|
||||
*/
|
||||
public function testNoCache() {
|
||||
$build['#attached']['library'][] = 'common_test/no-cache';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$this->assertFalse($js['core/modules/system/tests/modules/common_test/nocache.js']['preprocess'], 'Setting cache to FALSE sets preprocess to FALSE when adding JavaScript.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding JavaScript within conditional comments.
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\HtmlTag::preRenderConditionalComments()
|
||||
*/
|
||||
public function testBrowserConditionalComments() {
|
||||
$default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
|
||||
|
||||
$build['#attached']['library'][] = 'common_test/browsers';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$expected_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/old-ie.js')) . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
|
||||
$expected_2 = "<!--[if !IE]><!-->\n" . '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/no-ie.js')) . '?' . $default_query_string . '"></script>' . "\n<!--<![endif]-->";
|
||||
|
||||
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered JavaScript within downlevel-hidden conditional comments.');
|
||||
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered JavaScript within downlevel-revealed conditional comments.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests JavaScript versioning.
|
||||
*/
|
||||
public function testVersionQueryString() {
|
||||
$build['#attached']['library'][] = 'core/backbone';
|
||||
$build['#attached']['library'][] = 'core/domready';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$this->assertTrue(strpos($rendered_js, 'core/assets/vendor/backbone/backbone-min.js?v=1.2.3') > 0 && strpos($rendered_js, 'core/assets/vendor/domready/ready.min.js?v=1.0.8') > 0, 'JavaScript version identifiers correctly appended to URLs');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests JavaScript and CSS asset ordering.
|
||||
*/
|
||||
public function testRenderOrder() {
|
||||
$build['#attached']['library'][] = 'common_test/order';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
// Construct the expected result from the regex.
|
||||
$expected_order_js = [
|
||||
"-8_1",
|
||||
"-8_2",
|
||||
"-8_3",
|
||||
"-8_4",
|
||||
// The external script.
|
||||
"-5_1",
|
||||
"-3_1",
|
||||
"-3_2",
|
||||
"0_1",
|
||||
"0_2",
|
||||
"0_3",
|
||||
];
|
||||
|
||||
// Retrieve the rendered JavaScript and test against the regex.
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$matches = [];
|
||||
if (preg_match_all('/weight_([-0-9]+_[0-9]+)/', $rendered_js, $matches)) {
|
||||
$result = $matches[1];
|
||||
}
|
||||
else {
|
||||
$result = [];
|
||||
}
|
||||
$this->assertIdentical($result, $expected_order_js, 'JavaScript is added in the expected weight order.');
|
||||
|
||||
// Construct the expected result from the regex.
|
||||
$expected_order_css = [
|
||||
// Base.
|
||||
'base_weight_-101_1',
|
||||
'base_weight_-8_1',
|
||||
'layout_weight_-101_1',
|
||||
'base_weight_0_1',
|
||||
'base_weight_0_2',
|
||||
// Layout.
|
||||
'layout_weight_-8_1',
|
||||
'component_weight_-101_1',
|
||||
'layout_weight_0_1',
|
||||
'layout_weight_0_2',
|
||||
// Component.
|
||||
'component_weight_-8_1',
|
||||
'state_weight_-101_1',
|
||||
'component_weight_0_1',
|
||||
'component_weight_0_2',
|
||||
// State.
|
||||
'state_weight_-8_1',
|
||||
'theme_weight_-101_1',
|
||||
'state_weight_0_1',
|
||||
'state_weight_0_2',
|
||||
// Theme.
|
||||
'theme_weight_-8_1',
|
||||
'theme_weight_0_1',
|
||||
'theme_weight_0_2',
|
||||
];
|
||||
|
||||
// Retrieve the rendered CSS and test against the regex.
|
||||
$css = $this->assetResolver->getCssAssets($assets, FALSE);
|
||||
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
|
||||
$rendered_css = $this->renderer->renderPlain($css_render_array);
|
||||
$matches = [];
|
||||
if (preg_match_all('/([a-z]+)_weight_([-0-9]+_[0-9]+)/', $rendered_css, $matches)) {
|
||||
$result = $matches[0];
|
||||
}
|
||||
else {
|
||||
$result = [];
|
||||
}
|
||||
$this->assertIdentical($result, $expected_order_css, 'CSS is added in the expected weight order.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests rendering the JavaScript with a file's weight above jQuery's.
|
||||
*/
|
||||
public function testRenderDifferentWeight() {
|
||||
// If a library contains assets A and B, and A is listed first, then B can
|
||||
// still make itself appear first by defining a lower weight.
|
||||
$build['#attached']['library'][] = 'core/jquery';
|
||||
$build['#attached']['library'][] = 'common_test/weight';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$this->assertTrue(strpos($rendered_js, 'lighter.css') < strpos($rendered_js, 'first.js'), 'Lighter CSS assets are rendered first.');
|
||||
$this->assertTrue(strpos($rendered_js, 'lighter.js') < strpos($rendered_js, 'first.js'), 'Lighter JavaScript assets are rendered first.');
|
||||
$this->assertTrue(strpos($rendered_js, 'before-jquery.js') < strpos($rendered_js, 'core/assets/vendor/jquery/jquery.min.js'), 'Rendering a JavaScript file above jQuery.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests altering a JavaScript's weight via hook_js_alter().
|
||||
*
|
||||
* @see simpletest_js_alter()
|
||||
*/
|
||||
public function testAlter() {
|
||||
// Add both tableselect.js and simpletest.js.
|
||||
$build['#attached']['library'][] = 'core/drupal.tableselect';
|
||||
$build['#attached']['library'][] = 'simpletest/drupal.simpletest';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
// Render the JavaScript, testing if simpletest.js was altered to be before
|
||||
// tableselect.js. See simpletest_js_alter() to see where this alteration
|
||||
// takes place.
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$this->assertTrue(strpos($rendered_js, 'simpletest.js') < strpos($rendered_js, 'core/misc/tableselect.js'), 'Altering JavaScript weight through the alter hook.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a JavaScript library to the page and alters it.
|
||||
*
|
||||
* @see common_test_library_info_alter()
|
||||
*/
|
||||
public function testLibraryAlter() {
|
||||
// Verify that common_test altered the title of Farbtastic.
|
||||
/** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
|
||||
$library_discovery = \Drupal::service('library.discovery');
|
||||
$library = $library_discovery->getLibraryByName('core', 'jquery.farbtastic');
|
||||
$this->assertEqual($library['version'], '0.0', 'Registered libraries were altered.');
|
||||
|
||||
// common_test_library_info_alter() also added a dependency on jQuery Form.
|
||||
$build['#attached']['library'][] = 'core/jquery.farbtastic';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$this->assertTrue(strpos($rendered_js, 'core/assets/vendor/jquery-form/jquery.form.min.js'), 'Altered library dependencies are added to the page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically defines an asset library and alters it.
|
||||
*/
|
||||
public function testDynamicLibrary() {
|
||||
/** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
|
||||
$library_discovery = \Drupal::service('library.discovery');
|
||||
// Retrieve a dynamic library definition.
|
||||
// @see common_test_library_info_build()
|
||||
\Drupal::state()->set('common_test.library_info_build_test', TRUE);
|
||||
$library_discovery->clearCachedDefinitions();
|
||||
$dynamic_library = $library_discovery->getLibraryByName('common_test', 'dynamic_library');
|
||||
$this->assertTrue(is_array($dynamic_library));
|
||||
if ($this->assertTrue(isset($dynamic_library['version']))) {
|
||||
$this->assertSame('1.0', $dynamic_library['version']);
|
||||
}
|
||||
// Make sure the dynamic library definition could be altered.
|
||||
// @see common_test_library_info_alter()
|
||||
if ($this->assertTrue(isset($dynamic_library['dependencies']))) {
|
||||
$this->assertSame(['core/jquery'], $dynamic_library['dependencies']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that multiple modules can implement libraries with the same name.
|
||||
*
|
||||
* @see common_test.library.yml
|
||||
*/
|
||||
public function testLibraryNameConflicts() {
|
||||
/** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
|
||||
$library_discovery = \Drupal::service('library.discovery');
|
||||
$farbtastic = $library_discovery->getLibraryByName('common_test', 'jquery.farbtastic');
|
||||
$this->assertEqual($farbtastic['version'], '0.1', 'Alternative libraries can be added to the page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests JavaScript files that have querystrings attached get added right.
|
||||
*/
|
||||
public function testAddJsFileWithQueryString() {
|
||||
$build['#attached']['library'][] = 'common_test/querystring';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$css = $this->assetResolver->getCssAssets($assets, FALSE);
|
||||
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
|
||||
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2', $css), 'CSS file with query string is correctly added.');
|
||||
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js), 'JavaScript file with query string is correctly added.');
|
||||
|
||||
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
|
||||
$rendered_css = $this->renderer->renderPlain($css_render_array);
|
||||
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
|
||||
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . str_replace('&', '&', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2'))) . '&' . $query_string . '" media="all" />'), FALSE, 'CSS file with query string gets version query string correctly appended..');
|
||||
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . str_replace('&', '&', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2'))) . '&' . $query_string . '"></script>'), FALSE, 'JavaScript file with query string gets version query string correctly appended.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Asset;
|
||||
|
||||
use Drupal\Core\Asset\Exception\InvalidLibrariesExtendSpecificationException;
|
||||
use Drupal\Core\Asset\Exception\InvalidLibrariesOverrideSpecificationException;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the library discovery and library discovery parser.
|
||||
*
|
||||
* @group Render
|
||||
*/
|
||||
class LibraryDiscoveryIntegrationTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The library discovery service.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\LibraryDiscoveryInterface
|
||||
*/
|
||||
protected $libraryDiscovery;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->container->get('theme_installer')->install(['test_theme', 'classy']);
|
||||
$this->libraryDiscovery = $this->container->get('library.discovery');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that hook_library_info is invoked and the cache is cleared.
|
||||
*/
|
||||
public function testHookLibraryInfoByTheme() {
|
||||
// Activate test_theme and verify that the library 'kitten' is added using
|
||||
// hook_library_info_alter().
|
||||
$this->activateTheme('test_theme');
|
||||
$this->assertTrue($this->libraryDiscovery->getLibraryByName('test_theme', 'kitten'));
|
||||
|
||||
// Now make classy the active theme and assert that library is not added.
|
||||
$this->activateTheme('classy');
|
||||
$this->assertFalse($this->libraryDiscovery->getLibraryByName('test_theme', 'kitten'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that libraries-override are applied to library definitions.
|
||||
*/
|
||||
public function testLibrariesOverride() {
|
||||
// Assert some classy libraries that will be overridden or removed.
|
||||
$this->activateTheme('classy');
|
||||
$this->assertAssetInLibrary('core/themes/classy/css/components/button.css', 'classy', 'base', 'css');
|
||||
$this->assertAssetInLibrary('core/themes/classy/css/components/collapse-processed.css', 'classy', 'base', 'css');
|
||||
$this->assertAssetInLibrary('core/themes/classy/css/components/container-inline.css', 'classy', 'base', 'css');
|
||||
$this->assertAssetInLibrary('core/themes/classy/css/components/details.css', 'classy', 'base', 'css');
|
||||
$this->assertAssetInLibrary('core/themes/classy/css/components/dialog.css', 'classy', 'dialog', 'css');
|
||||
|
||||
// Confirmatory assert on core library to be removed.
|
||||
$this->assertTrue($this->libraryDiscovery->getLibraryByName('core', 'drupal.progress'), 'Confirmatory test on "core/drupal.progress"');
|
||||
|
||||
// Activate test theme that defines libraries overrides.
|
||||
$this->activateTheme('test_theme');
|
||||
|
||||
// Assert that entire library was correctly overridden.
|
||||
$this->assertEqual($this->libraryDiscovery->getLibraryByName('core', 'drupal.collapse'), $this->libraryDiscovery->getLibraryByName('test_theme', 'collapse'), 'Entire library correctly overridden.');
|
||||
|
||||
// Assert that classy library assets were correctly overridden or removed.
|
||||
$this->assertNoAssetInLibrary('core/themes/classy/css/components/button.css', 'classy', 'base', 'css');
|
||||
$this->assertNoAssetInLibrary('core/themes/classy/css/components/collapse-processed.css', 'classy', 'base', 'css');
|
||||
$this->assertNoAssetInLibrary('core/themes/classy/css/components/container-inline.css', 'classy', 'base', 'css');
|
||||
$this->assertNoAssetInLibrary('core/themes/classy/css/components/details.css', 'classy', 'base', 'css');
|
||||
$this->assertNoAssetInLibrary('core/themes/classy/css/components/dialog.css', 'classy', 'dialog', 'css');
|
||||
|
||||
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme/css/my-button.css', 'classy', 'base', 'css');
|
||||
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme/css/my-collapse-processed.css', 'classy', 'base', 'css');
|
||||
$this->assertAssetInLibrary('themes/my_theme/css/my-container-inline.css', 'classy', 'base', 'css');
|
||||
$this->assertAssetInLibrary('themes/my_theme/css/my-details.css', 'classy', 'base', 'css');
|
||||
|
||||
// Assert that entire library was correctly removed.
|
||||
$this->assertFalse($this->libraryDiscovery->getLibraryByName('core', 'drupal.progress'), 'Entire library correctly removed.');
|
||||
|
||||
// Assert that overridden library asset still retains attributes.
|
||||
$library = $this->libraryDiscovery->getLibraryByName('core', 'jquery');
|
||||
foreach ($library['js'] as $definition) {
|
||||
if ($definition['data'] == 'core/modules/system/tests/themes/test_theme/js/collapse.js') {
|
||||
$this->assertTrue($definition['minified'] && $definition['weight'] == -20, 'Previous attributes retained');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests libraries-override on drupalSettings.
|
||||
*/
|
||||
public function testLibrariesOverrideDrupalSettings() {
|
||||
// Activate test theme that attempts to override drupalSettings.
|
||||
$this->activateTheme('test_theme_libraries_override_with_drupal_settings');
|
||||
|
||||
// Assert that drupalSettings cannot be overridden and throws an exception.
|
||||
try {
|
||||
$this->libraryDiscovery->getLibraryByName('core', 'drupal.ajax');
|
||||
$this->fail('Throw Exception when trying to override drupalSettings');
|
||||
}
|
||||
catch (InvalidLibrariesOverrideSpecificationException $e) {
|
||||
$expected_message = 'drupalSettings may not be overridden in libraries-override. Trying to override core/drupal.ajax/drupalSettings. Use hook_library_info_alter() instead.';
|
||||
$this->assertEqual($e->getMessage(), $expected_message, 'Throw Exception when trying to override drupalSettings');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests libraries-override on malformed assets.
|
||||
*/
|
||||
public function testLibrariesOverrideMalformedAsset() {
|
||||
// Activate test theme that overrides with a malformed asset.
|
||||
$this->activateTheme('test_theme_libraries_override_with_invalid_asset');
|
||||
|
||||
// Assert that improperly formed asset "specs" throw an exception.
|
||||
try {
|
||||
$this->libraryDiscovery->getLibraryByName('core', 'drupal.dialog');
|
||||
$this->fail('Throw Exception when specifying invalid override');
|
||||
}
|
||||
catch (InvalidLibrariesOverrideSpecificationException $e) {
|
||||
$expected_message = 'Library asset core/drupal.dialog/css is not correctly specified. It should be in the form "extension/library_name/sub_key/path/to/asset.js".';
|
||||
$this->assertEqual($e->getMessage(), $expected_message, 'Throw Exception when specifying invalid override');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests library assets with other ways for specifying paths.
|
||||
*/
|
||||
public function testLibrariesOverrideOtherAssetLibraryNames() {
|
||||
// Activate a test theme that defines libraries overrides on other types of
|
||||
// assets.
|
||||
$this->activateTheme('test_theme');
|
||||
|
||||
// Assert Drupal-relative paths.
|
||||
$this->assertAssetInLibrary('themes/my_theme/css/dropbutton.css', 'core', 'drupal.dropbutton', 'css');
|
||||
|
||||
// Assert stream wrapper paths.
|
||||
$this->assertAssetInLibrary('public://my_css/vertical-tabs.css', 'core', 'drupal.vertical-tabs', 'css');
|
||||
|
||||
// Assert a protocol-relative URI.
|
||||
$this->assertAssetInLibrary('//my-server/my_theme/css/jquery_ui.css', 'core', 'jquery.ui', 'css');
|
||||
|
||||
// Assert an absolute URI.
|
||||
$this->assertAssetInLibrary('http://example.com/my_theme/css/farbtastic.css', 'core', 'jquery.farbtastic', 'css');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that base theme libraries-override still apply in sub themes.
|
||||
*/
|
||||
public function testBaseThemeLibrariesOverrideInSubTheme() {
|
||||
// Activate a test theme that has subthemes.
|
||||
$this->activateTheme('test_subtheme');
|
||||
|
||||
// Assert that libraries-override specified in the base theme still applies
|
||||
// in the sub theme.
|
||||
$this->assertNoAssetInLibrary('core/misc/dialog/dialog.js', 'core', 'drupal.dialog', 'js');
|
||||
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_basetheme/css/farbtastic.css', 'core', 'jquery.farbtastic', 'css');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests libraries-extend.
|
||||
*/
|
||||
public function testLibrariesExtend() {
|
||||
// Activate classy themes and verify the libraries are not extended.
|
||||
$this->activateTheme('classy');
|
||||
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_1.css', 'classy', 'book-navigation', 'css');
|
||||
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/js/extend_1.js', 'classy', 'book-navigation', 'js');
|
||||
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_2.css', 'classy', 'book-navigation', 'css');
|
||||
|
||||
// Activate the theme that extends the book-navigation library in classy.
|
||||
$this->activateTheme('test_theme_libraries_extend');
|
||||
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_1.css', 'classy', 'book-navigation', 'css');
|
||||
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/js/extend_1.js', 'classy', 'book-navigation', 'js');
|
||||
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_2.css', 'classy', 'book-navigation', 'css');
|
||||
|
||||
// Activate a sub theme and confirm that it inherits the library assets
|
||||
// extended in the base theme as well as its own.
|
||||
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_basetheme/css/base-libraries-extend.css', 'classy', 'base', 'css');
|
||||
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_subtheme/css/sub-libraries-extend.css', 'classy', 'base', 'css');
|
||||
$this->activateTheme('test_subtheme');
|
||||
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_basetheme/css/base-libraries-extend.css', 'classy', 'base', 'css');
|
||||
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_subtheme/css/sub-libraries-extend.css', 'classy', 'base', 'css');
|
||||
|
||||
// Activate test theme that extends with a non-existent library. An
|
||||
// exception should be thrown.
|
||||
$this->activateTheme('test_theme_libraries_extend');
|
||||
try {
|
||||
$this->libraryDiscovery->getLibraryByName('core', 'drupal.dialog');
|
||||
$this->fail('Throw Exception when specifying non-existent libraries-extend.');
|
||||
}
|
||||
catch (InvalidLibrariesExtendSpecificationException $e) {
|
||||
$expected_message = 'The specified library "test_theme_libraries_extend/non_existent_library" does not exist.';
|
||||
$this->assertEqual($e->getMessage(), $expected_message, 'Throw Exception when specifying non-existent libraries-extend.');
|
||||
}
|
||||
|
||||
// Also, test non-string libraries-extend. An exception should be thrown.
|
||||
$this->container->get('theme_installer')->install(['test_theme']);
|
||||
try {
|
||||
$this->libraryDiscovery->getLibraryByName('test_theme', 'collapse');
|
||||
$this->fail('Throw Exception when specifying non-string libraries-extend.');
|
||||
}
|
||||
catch (InvalidLibrariesExtendSpecificationException $e) {
|
||||
$expected_message = 'The libraries-extend specification for each library must be a list of strings.';
|
||||
$this->assertEqual($e->getMessage(), $expected_message, 'Throw Exception when specifying non-string libraries-extend.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates a specified theme.
|
||||
*
|
||||
* Installs the theme if not already installed and makes it the active theme.
|
||||
*
|
||||
* @param string $theme_name
|
||||
* The name of the theme to be activated.
|
||||
*/
|
||||
protected function activateTheme($theme_name) {
|
||||
$this->container->get('theme_installer')->install([$theme_name]);
|
||||
|
||||
/** @var \Drupal\Core\Theme\ThemeInitializationInterface $theme_initializer */
|
||||
$theme_initializer = $this->container->get('theme.initialization');
|
||||
|
||||
/** @var \Drupal\Core\Theme\ThemeManagerInterface $theme_manager */
|
||||
$theme_manager = $this->container->get('theme.manager');
|
||||
|
||||
$theme_manager->setActiveTheme($theme_initializer->getActiveThemeByName($theme_name));
|
||||
|
||||
$this->libraryDiscovery->clearCachedDefinitions();
|
||||
|
||||
// Assert message.
|
||||
$this->pass(sprintf('Activated theme "%s"', $theme_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the specified asset is in the given library.
|
||||
*
|
||||
* @param string $asset
|
||||
* The asset file with the path for the file.
|
||||
* @param string $extension
|
||||
* The extension in which the $library is defined.
|
||||
* @param string $library_name
|
||||
* Name of the library.
|
||||
* @param mixed $sub_key
|
||||
* The library sub key where the given asset is defined.
|
||||
* @param string $message
|
||||
* (optional) A message to display with the assertion.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the specified asset is found in the library.
|
||||
*/
|
||||
protected function assertAssetInLibrary($asset, $extension, $library_name, $sub_key, $message = NULL) {
|
||||
if (!isset($message)) {
|
||||
$message = sprintf('Asset %s found in library "%s/%s"', $asset, $extension, $library_name);
|
||||
}
|
||||
$library = $this->libraryDiscovery->getLibraryByName($extension, $library_name);
|
||||
foreach ($library[$sub_key] as $definition) {
|
||||
if ($asset == $definition['data']) {
|
||||
return $this->pass($message);
|
||||
}
|
||||
}
|
||||
return $this->fail($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the specified asset is not in the given library.
|
||||
*
|
||||
* @param string $asset
|
||||
* The asset file with the path for the file.
|
||||
* @param string $extension
|
||||
* The extension in which the $library_name is defined.
|
||||
* @param string $library_name
|
||||
* Name of the library.
|
||||
* @param mixed $sub_key
|
||||
* The library sub key where the given asset is defined.
|
||||
* @param string $message
|
||||
* (optional) A message to display with the assertion.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the specified asset is not found in the library.
|
||||
*/
|
||||
protected function assertNoAssetInLibrary($asset, $extension, $library_name, $sub_key, $message = NULL) {
|
||||
if (!isset($message)) {
|
||||
$message = sprintf('Asset %s not found in library "%s/%s"', $asset, $extension, $library_name);
|
||||
}
|
||||
$library = $this->libraryDiscovery->getLibraryByName($extension, $library_name);
|
||||
foreach ($library[$sub_key] as $definition) {
|
||||
if ($asset == $definition['data']) {
|
||||
return $this->fail($message);
|
||||
}
|
||||
}
|
||||
return $this->pass($message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Asset;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the asset files for all core libraries exist.
|
||||
*
|
||||
* This test also changes the active theme to each core theme to verify
|
||||
* the libraries after theme-level libraries-override and libraries-extend are
|
||||
* applied.
|
||||
*
|
||||
* @group Asset
|
||||
*/
|
||||
class ResolvedLibraryDefinitionsFilesMatchTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The theme handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ThemeHandlerInterface
|
||||
*/
|
||||
protected $themeHandler;
|
||||
|
||||
/**
|
||||
* The theme initialization.
|
||||
*
|
||||
* @var \Drupal\Core\Theme\ThemeInitializationInterface
|
||||
*/
|
||||
protected $themeInitialization;
|
||||
|
||||
/**
|
||||
* The theme manager.
|
||||
*
|
||||
* @var \Drupal\Core\Theme\ThemeManagerInterface
|
||||
*/
|
||||
protected $themeManager;
|
||||
|
||||
/**
|
||||
* The library discovery service.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\LibraryDiscoveryInterface
|
||||
*/
|
||||
protected $libraryDiscovery;
|
||||
|
||||
/**
|
||||
* A list of all core modules.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $allModules;
|
||||
|
||||
/**
|
||||
* A list of all core themes.
|
||||
*
|
||||
* We hardcode this because test themes don't use a 'package' or 'hidden' key
|
||||
* so we don't have a good way of filtering to only get "real" themes.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $allThemes = [
|
||||
'bartik',
|
||||
'classy',
|
||||
'seven',
|
||||
'stable',
|
||||
'stark',
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of libraries to skip checking, in the format extension/library_name.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $librariesToSkip = [
|
||||
// Locale has a "dummy" library that does not actually exist.
|
||||
'locale/translations',
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of all paths that have been checked.
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
protected $pathsChecked;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Install all core themes.
|
||||
sort($this->allThemes);
|
||||
$this->container->get('theme_installer')->install($this->allThemes);
|
||||
|
||||
// Enable all core modules.
|
||||
$all_modules = system_rebuild_module_data();
|
||||
$all_modules = array_filter($all_modules, function ($module) {
|
||||
// Filter contrib, hidden, already enabled modules and modules in the
|
||||
// Testing package.
|
||||
if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing') {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
});
|
||||
// Remove demo_umami_content module as its install hook creates content
|
||||
// that relies on the presence of entity tables and various other elements
|
||||
// not present in a kernel test.
|
||||
unset($all_modules['demo_umami_content']);
|
||||
$this->allModules = array_keys($all_modules);
|
||||
$this->allModules[] = 'system';
|
||||
sort($this->allModules);
|
||||
$this->container->get('module_installer')->install($this->allModules);
|
||||
|
||||
$this->themeHandler = $this->container->get('theme_handler');
|
||||
$this->themeInitialization = $this->container->get('theme.initialization');
|
||||
$this->themeManager = $this->container->get('theme.manager');
|
||||
$this->libraryDiscovery = $this->container->get('library.discovery');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that all core module and theme library files exist.
|
||||
*/
|
||||
public function testCoreLibraryCompleteness() {
|
||||
// First verify all libraries with no active theme.
|
||||
$this->verifyLibraryFilesExist($this->getAllLibraries());
|
||||
|
||||
// Then verify all libraries for each core theme. This may seem like
|
||||
// overkill but themes can override and extend other extensions' libraries
|
||||
// and these changes are only applied for the active theme.
|
||||
foreach ($this->allThemes as $theme) {
|
||||
$this->themeManager->setActiveTheme($this->themeInitialization->getActiveThemeByName($theme));
|
||||
$this->libraryDiscovery->clearCachedDefinitions();
|
||||
|
||||
$this->verifyLibraryFilesExist($this->getAllLibraries());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that all the library files exist.
|
||||
*
|
||||
* @param array[] $library_definitions
|
||||
* An array of library definitions, keyed by extension, then by library, and
|
||||
* so on.
|
||||
*/
|
||||
protected function verifyLibraryFilesExist($library_definitions) {
|
||||
foreach ($library_definitions as $extension => $libraries) {
|
||||
foreach ($libraries as $library_name => $library) {
|
||||
if (in_array("$extension/$library_name", $this->librariesToSkip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that all the assets exist.
|
||||
foreach (['css', 'js'] as $asset_type) {
|
||||
foreach ($library[$asset_type] as $asset) {
|
||||
$file = $asset['data'];
|
||||
$path = $this->root . '/' . $file;
|
||||
// Only check and assert each file path once.
|
||||
if (!isset($this->pathsChecked[$path])) {
|
||||
$this->assertTrue(is_file($path), "$file file referenced from the $extension/$library_name library exists.");
|
||||
$this->pathsChecked[$path] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all libraries for core and all installed modules.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\Extension[]
|
||||
*/
|
||||
protected function getAllLibraries() {
|
||||
$modules = \Drupal::moduleHandler()->getModuleList();
|
||||
$extensions = $modules;
|
||||
$module_list = array_keys($modules);
|
||||
sort($module_list);
|
||||
$this->assertEqual($this->allModules, $module_list, 'All core modules are installed.');
|
||||
|
||||
$themes = $this->themeHandler->listInfo();
|
||||
$extensions += $themes;
|
||||
$theme_list = array_keys($themes);
|
||||
sort($theme_list);
|
||||
$this->assertEqual($this->allThemes, $theme_list, 'All core themes are installed.');
|
||||
|
||||
$libraries['core'] = $this->libraryDiscovery->getLibrariesByExtension('core');
|
||||
|
||||
foreach ($extensions as $extension_name => $extension) {
|
||||
$library_file = $extension->getPath() . '/' . $extension_name . '.libraries.yml';
|
||||
if (is_file($this->root . '/' . $library_file)) {
|
||||
$libraries[$extension_name] = $this->libraryDiscovery->getLibrariesByExtension($extension_name);
|
||||
}
|
||||
}
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Batch;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests batch functionality.
|
||||
*
|
||||
* @group Batch
|
||||
*/
|
||||
class BatchKernelTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
require_once $this->root . '/core/includes/batch.inc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests _batch_needs_update().
|
||||
*/
|
||||
public function testNeedsUpdate() {
|
||||
// Before ever being called, the return value should be FALSE.
|
||||
$this->assertEquals(FALSE, _batch_needs_update());
|
||||
|
||||
// Set the value to TRUE.
|
||||
$this->assertEquals(TRUE, _batch_needs_update(TRUE));
|
||||
// Check that without a parameter TRUE is returned.
|
||||
$this->assertEquals(TRUE, _batch_needs_update());
|
||||
|
||||
// Set the value to FALSE.
|
||||
$this->assertEquals(FALSE, _batch_needs_update(FALSE));
|
||||
$this->assertEquals(FALSE, _batch_needs_update());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Block;
|
||||
|
||||
use Drupal\block_test\PluginForm\EmptyBlockForm;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that blocks can have multiple forms.
|
||||
*
|
||||
* @group block
|
||||
*/
|
||||
class MultipleBlockFormTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'block', 'block_test'];
|
||||
|
||||
/**
|
||||
* Tests that blocks can have multiple forms.
|
||||
*/
|
||||
public function testMultipleForms() {
|
||||
$configuration = ['label' => 'A very cool block'];
|
||||
$block = \Drupal::service('plugin.manager.block')->createInstance('test_multiple_forms_block', $configuration);
|
||||
|
||||
$form_object1 = \Drupal::service('plugin_form.factory')->createInstance($block, 'configure');
|
||||
$form_object2 = \Drupal::service('plugin_form.factory')->createInstance($block, 'secondary');
|
||||
|
||||
// Assert that the block itself is used for the default form.
|
||||
$this->assertSame($block, $form_object1);
|
||||
|
||||
// Ensure that EmptyBlockForm is used and the plugin is set.
|
||||
$this->assertInstanceOf(EmptyBlockForm::class, $form_object2);
|
||||
$this->assertAttributeEquals($block, 'plugin', $form_object2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Bootstrap;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that drupal_get_filename() works correctly.
|
||||
*
|
||||
* @group Bootstrap
|
||||
*/
|
||||
class GetFilenameTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container) {
|
||||
parent::register($container);
|
||||
// Use the testing install profile.
|
||||
$container->setParameter('install_profile', 'testing');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that drupal_get_filename() works when the file is not in database.
|
||||
*/
|
||||
public function testDrupalGetFilename() {
|
||||
// Rebuild system.module.files state data.
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
drupal_static_reset('system_rebuild_module_data');
|
||||
system_rebuild_module_data();
|
||||
|
||||
// Retrieving the location of a module.
|
||||
$this->assertIdentical(drupal_get_filename('module', 'system'), 'core/modules/system/system.info.yml');
|
||||
|
||||
// Retrieving the location of a theme.
|
||||
\Drupal::service('theme_handler')->install(['stark']);
|
||||
$this->assertIdentical(drupal_get_filename('theme', 'stark'), 'core/themes/stark/stark.info.yml');
|
||||
|
||||
// Retrieving the location of a theme engine.
|
||||
$this->assertIdentical(drupal_get_filename('theme_engine', 'twig'), 'core/themes/engines/twig/twig.info.yml');
|
||||
|
||||
// Retrieving the location of a profile. Profiles are a special case with
|
||||
// a fixed location and naming.
|
||||
$this->assertIdentical(drupal_get_filename('profile', 'testing'), 'core/profiles/testing/testing.info.yml');
|
||||
|
||||
// Set a custom error handler so we can ignore the file not found error.
|
||||
set_error_handler(function ($severity, $message, $file, $line) {
|
||||
// Skip error handling if this is a "file not found" error.
|
||||
if (strstr($message, 'is missing from the file system:')) {
|
||||
\Drupal::state()->set('get_filename_test_triggered_error', $message);
|
||||
return;
|
||||
}
|
||||
throw new \ErrorException($message, 0, $severity, $file, $line);
|
||||
});
|
||||
$this->assertNull(drupal_get_filename('module', 'there_is_a_module_for_that'), 'Searching for an item that does not exist returns NULL.');
|
||||
$this->assertEquals('The following module is missing from the file system: there_is_a_module_for_that', \Drupal::state()->get('get_filename_test_triggered_error'));
|
||||
|
||||
$this->assertNull(drupal_get_filename('theme', 'there_is_a_theme_for_you'), 'Searching for an item that does not exist returns NULL.');
|
||||
$this->assertEquals('The following theme is missing from the file system: there_is_a_theme_for_you', \Drupal::state()->get('get_filename_test_triggered_error'));
|
||||
|
||||
$this->assertNull(drupal_get_filename('profile', 'there_is_an_install_profile_for_you'), 'Searching for an item that does not exist returns NULL.');
|
||||
$this->assertEquals('The following profile is missing from the file system: there_is_an_install_profile_for_you', \Drupal::state()->get('get_filename_test_triggered_error'));
|
||||
|
||||
// Restore the original error handler.
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Bootstrap;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that drupal_static() and drupal_static_reset() work.
|
||||
*
|
||||
* @group Bootstrap
|
||||
*/
|
||||
class ResettableStaticTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests drupal_static() function.
|
||||
*
|
||||
* Tests that a variable reference returned by drupal_static() gets reset when
|
||||
* drupal_static_reset() is called.
|
||||
*/
|
||||
public function testDrupalStatic() {
|
||||
$name = __CLASS__ . '_' . __METHOD__;
|
||||
$var = &drupal_static($name, 'foo');
|
||||
$this->assertEqual($var, 'foo', 'Variable returned by drupal_static() was set to its default.');
|
||||
|
||||
// Call the specific reset and the global reset each twice to ensure that
|
||||
// multiple resets can be issued without odd side effects.
|
||||
$var = 'bar';
|
||||
drupal_static_reset($name);
|
||||
$this->assertEqual($var, 'foo', 'Variable was reset after first invocation of name-specific reset.');
|
||||
$var = 'bar';
|
||||
drupal_static_reset($name);
|
||||
$this->assertEqual($var, 'foo', 'Variable was reset after second invocation of name-specific reset.');
|
||||
$var = 'bar';
|
||||
drupal_static_reset();
|
||||
$this->assertEqual($var, 'foo', 'Variable was reset after first invocation of global reset.');
|
||||
$var = 'bar';
|
||||
drupal_static_reset();
|
||||
$this->assertEqual($var, 'foo', 'Variable was reset after second invocation of global reset.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\Apcu4Backend;
|
||||
use Drupal\Core\Cache\ApcuBackend;
|
||||
|
||||
/**
|
||||
* Tests the APCu cache backend.
|
||||
*
|
||||
* @group Cache
|
||||
* @requires extension apcu
|
||||
*/
|
||||
class ApcuBackendTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
/**
|
||||
* Get a list of failed requirements.
|
||||
*
|
||||
* This specifically bypasses checkRequirements because it fails tests. PHP 7
|
||||
* does not have APCu and simpletest does not have a explicit "skip"
|
||||
* functionality so to emulate it we override all test methods and explicitly
|
||||
* pass when requirements are not met.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getRequirements() {
|
||||
$requirements = [];
|
||||
if (!extension_loaded('apcu')) {
|
||||
$requirements[] = 'APCu extension not found.';
|
||||
}
|
||||
else {
|
||||
if (PHP_SAPI === 'cli' && !ini_get('apc.enable_cli')) {
|
||||
$requirements[] = 'apc.enable_cli must be enabled to run this test.';
|
||||
}
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if requirements fail.
|
||||
*
|
||||
* If the requirements fail the test method should return immediately instead
|
||||
* of running any tests. Messages will be output to display why the test was
|
||||
* skipped.
|
||||
*/
|
||||
protected function requirementsFail() {
|
||||
$requirements = $this->getRequirements();
|
||||
if (!empty($requirements)) {
|
||||
foreach ($requirements as $message) {
|
||||
$this->pass($message);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createCacheBackend($bin) {
|
||||
if (version_compare(phpversion('apcu'), '5.0.0', '>=')) {
|
||||
return new ApcuBackend($bin, $this->databasePrefix, \Drupal::service('cache_tags.invalidator.checksum'));
|
||||
}
|
||||
else {
|
||||
return new Apcu4Backend($bin, $this->databasePrefix, \Drupal::service('cache_tags.invalidator.checksum'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function tearDown() {
|
||||
foreach ($this->cachebackends as $bin => $cachebackend) {
|
||||
$this->cachebackends[$bin]->removeBin();
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testSetGet() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testSetGet();
|
||||
|
||||
// Make sure entries are permanent (i.e. no TTL).
|
||||
$backend = $this->getCacheBackend($this->getTestBin());
|
||||
$key = $backend->getApcuKey('TEST8');
|
||||
|
||||
if (class_exists('\APCUIterator')) {
|
||||
$iterator = new \APCUIterator('/^' . $key . '/');
|
||||
}
|
||||
else {
|
||||
$iterator = new \APCIterator('user', '/^' . $key . '/');
|
||||
}
|
||||
|
||||
foreach ($iterator as $item) {
|
||||
$this->assertEqual(0, $item['ttl']);
|
||||
$found = TRUE;
|
||||
}
|
||||
$this->assertTrue($found);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testDelete() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testValueTypeIsKept() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testValueTypeIsKept();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testGetMultiple() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testGetMultiple();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testSetMultiple() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testSetMultiple();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testDeleteMultiple() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testDeleteMultiple();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testDeleteAll() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testDeleteAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testInvalidate() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testInvalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testInvalidateTags() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testInvalidateTags();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testInvalidateAll() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testInvalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testRemoveBin() {
|
||||
if ($this->requirementsFail()) {
|
||||
return;
|
||||
}
|
||||
parent::testRemoveBin();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\BackendChain;
|
||||
use Drupal\Core\Cache\MemoryBackend;
|
||||
|
||||
/**
|
||||
* Unit test of the backend chain using the generic cache unit test base.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class BackendChainTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
protected function createCacheBackend($bin) {
|
||||
$chain = new BackendChain($bin);
|
||||
|
||||
// We need to create some various backends in the chain.
|
||||
$chain
|
||||
->appendBackend(new MemoryBackend())
|
||||
->prependBackend(new MemoryBackend())
|
||||
->appendBackend(new MemoryBackend());
|
||||
|
||||
\Drupal::service('cache_tags.invalidator')->addInvalidator($chain);
|
||||
|
||||
return $chain;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\Core\Cache\CacheCollectorHelper;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Tests DatabaseBackend cache tag implementation.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class CacheCollectorTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container) {
|
||||
parent::register($container);
|
||||
// Change container to database cache backends.
|
||||
$container
|
||||
->register('cache_factory', 'Drupal\Core\Cache\CacheFactory')
|
||||
->addArgument(new Reference('settings'))
|
||||
->addMethodCall('setContainer', [new Reference('service_container')]);
|
||||
|
||||
// Change container to use database lock backends.
|
||||
$container
|
||||
->register('lock', 'Drupal\Core\Lock\DatabaseLockBackend')
|
||||
->addArgument(new Reference('database'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting and invalidating
|
||||
*
|
||||
* @dataProvider providerTestInvalidCharacters
|
||||
*/
|
||||
public function testCacheCollector($cid, $key, $value) {
|
||||
$collector = new CacheCollectorHelper($cid, $this->container->get('cache.default'), $this->container->get('lock'));
|
||||
$this->assertNull($collector->get($key));
|
||||
$collector->set($key, $value);
|
||||
$this->assertEquals($value, $collector->get($key));
|
||||
$collector->destruct();
|
||||
// @todo Shouldn't this be empty after destruction?
|
||||
$this->assertEquals($value, $collector->get($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testCacheCollector().
|
||||
*/
|
||||
public function providerTestInvalidCharacters() {
|
||||
return [
|
||||
// Nothing special.
|
||||
['foo', 'bar', 'baz'],
|
||||
// Invalid characters in CID.
|
||||
['éøïвβ中國書۞', 'foo', 'bar'],
|
||||
// Really long CID.
|
||||
[$this->randomString(1024), 'foo', 'bar'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\simpletest\UserCreationTrait;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests the cache context optimization.
|
||||
*
|
||||
* @group Render
|
||||
*/
|
||||
class CacheContextOptimizationTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public static $modules = ['user', 'system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->installConfig(['user']);
|
||||
$this->installSchema('system', ['sequences']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that 'user.permissions' cache context is able to define cache tags.
|
||||
*/
|
||||
public function testUserPermissionCacheContextOptimization() {
|
||||
$user1 = $this->createUser();
|
||||
$this->assertEqual($user1->id(), 1);
|
||||
|
||||
$authenticated_user = $this->createUser(['administer permissions']);
|
||||
$role = $authenticated_user->getRoles()[1];
|
||||
|
||||
$test_element = [
|
||||
'#cache' => [
|
||||
'keys' => ['test'],
|
||||
'contexts' => ['user', 'user.permissions'],
|
||||
],
|
||||
];
|
||||
\Drupal::service('account_switcher')->switchTo($authenticated_user);
|
||||
$element = $test_element;
|
||||
$element['#markup'] = 'content for authenticated users';
|
||||
$output = \Drupal::service('renderer')->renderRoot($element);
|
||||
$this->assertEqual($output, 'content for authenticated users');
|
||||
|
||||
// Verify that the render caching is working so that other tests can be
|
||||
// trusted.
|
||||
$element = $test_element;
|
||||
$element['#markup'] = 'this should not be visible';
|
||||
$output = \Drupal::service('renderer')->renderRoot($element);
|
||||
$this->assertEqual($output, 'content for authenticated users');
|
||||
|
||||
// Even though the cache contexts have been optimized to only include 'user'
|
||||
// cache context, the element should have been changed because
|
||||
// 'user.permissions' cache context defined a cache tags for permission
|
||||
// changes, which should have bubbled up for the element when it was
|
||||
// optimized away.
|
||||
Role::load($role)
|
||||
->revokePermission('administer permissions')
|
||||
->save();
|
||||
$element = $test_element;
|
||||
$element['#markup'] = 'this should be visible';
|
||||
$output = \Drupal::service('renderer')->renderRoot($element);
|
||||
$this->assertEqual($output, 'this should be visible');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that 'user.roles' still works when it is optimized away.
|
||||
*/
|
||||
public function testUserRolesCacheContextOptimization() {
|
||||
$root_user = $this->createUser();
|
||||
$this->assertEqual($root_user->id(), 1);
|
||||
|
||||
$authenticated_user = $this->createUser(['administer permissions']);
|
||||
$role = $authenticated_user->getRoles()[1];
|
||||
|
||||
$test_element = [
|
||||
'#cache' => [
|
||||
'keys' => ['test'],
|
||||
'contexts' => ['user', 'user.roles'],
|
||||
],
|
||||
];
|
||||
\Drupal::service('account_switcher')->switchTo($authenticated_user);
|
||||
$element = $test_element;
|
||||
$element['#markup'] = 'content for authenticated users';
|
||||
$output = \Drupal::service('renderer')->renderRoot($element);
|
||||
$this->assertEqual($output, 'content for authenticated users');
|
||||
|
||||
// Verify that the render caching is working so that other tests can be
|
||||
// trusted.
|
||||
$element = $test_element;
|
||||
$element['#markup'] = 'this should not be visible';
|
||||
$output = \Drupal::service('renderer')->renderRoot($element);
|
||||
$this->assertEqual($output, 'content for authenticated users');
|
||||
|
||||
// Even though the cache contexts have been optimized to only include 'user'
|
||||
// cache context, the element should have been changed because 'user.roles'
|
||||
// cache context defined a cache tag for user entity changes, which should
|
||||
// have bubbled up for the element when it was optimized away.
|
||||
$authenticated_user->removeRole($role);
|
||||
$authenticated_user->save();
|
||||
$element = $test_element;
|
||||
$element['#markup'] = 'this should be visible';
|
||||
$output = \Drupal::service('renderer')->renderRoot($element);
|
||||
$this->assertEqual($output, 'this should be visible');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\ChainedFastBackend;
|
||||
use Drupal\Core\Cache\DatabaseBackend;
|
||||
use Drupal\Core\Cache\PhpBackend;
|
||||
|
||||
/**
|
||||
* Unit test of the fast chained backend using the generic cache unit test base.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class ChainedFastBackendTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
/**
|
||||
* Creates a new instance of ChainedFastBackend.
|
||||
*
|
||||
* @return \Drupal\Core\Cache\ChainedFastBackend
|
||||
* A new ChainedFastBackend object.
|
||||
*/
|
||||
protected function createCacheBackend($bin) {
|
||||
$consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin, 100);
|
||||
$fast_backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'));
|
||||
$backend = new ChainedFastBackend($consistent_backend, $fast_backend, $bin);
|
||||
// Explicitly register the cache bin as it can not work through the
|
||||
// cache bin list in the container.
|
||||
\Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
|
||||
return $backend;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Tests DatabaseBackend cache tag implementation.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class DatabaseBackendTagTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container) {
|
||||
parent::register($container);
|
||||
// Change container to database cache backends.
|
||||
$container
|
||||
->register('cache_factory', 'Drupal\Core\Cache\CacheFactory')
|
||||
->addArgument(new Reference('settings'))
|
||||
->addMethodCall('setContainer', [new Reference('service_container')]);
|
||||
}
|
||||
|
||||
public function testTagInvalidations() {
|
||||
// Create cache entry in multiple bins.
|
||||
$tags = ['test_tag:1', 'test_tag:2', 'test_tag:3'];
|
||||
$bins = ['data', 'bootstrap', 'render'];
|
||||
foreach ($bins as $bin) {
|
||||
$bin = \Drupal::cache($bin);
|
||||
$bin->set('test', 'value', Cache::PERMANENT, $tags);
|
||||
$this->assertTrue($bin->get('test'), 'Cache item was set in bin.');
|
||||
}
|
||||
|
||||
$invalidations_before = intval(db_select('cachetags')->fields('cachetags', ['invalidations'])->condition('tag', 'test_tag:2')->execute()->fetchField());
|
||||
Cache::invalidateTags(['test_tag:2']);
|
||||
|
||||
// Test that cache entry has been invalidated in multiple bins.
|
||||
foreach ($bins as $bin) {
|
||||
$bin = \Drupal::cache($bin);
|
||||
$this->assertFalse($bin->get('test'), 'Tag invalidation affected item in bin.');
|
||||
}
|
||||
|
||||
// Test that only one tag invalidation has occurred.
|
||||
$invalidations_after = intval(db_select('cachetags')->fields('cachetags', ['invalidations'])->condition('tag', 'test_tag:2')->execute()->fetchField());
|
||||
$this->assertEqual($invalidations_after, $invalidations_before + 1, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\DatabaseBackend;
|
||||
|
||||
/**
|
||||
* Unit test of the database backend using the generic cache unit test base.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class DatabaseBackendTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
/**
|
||||
* The max rows to use for test bins.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $maxRows = 100;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* Creates a new instance of DatabaseBackend.
|
||||
*
|
||||
* @return
|
||||
* A new DatabaseBackend object.
|
||||
*/
|
||||
protected function createCacheBackend($bin) {
|
||||
return new DatabaseBackend($this->container->get('database'), $this->container->get('cache_tags.invalidator.checksum'), $bin, static::$maxRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testSetGet() {
|
||||
parent::testSetGet();
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
// Set up a cache ID that is not ASCII and longer than 255 characters so we
|
||||
// can test cache ID normalization.
|
||||
$cid_long = str_repeat('愛€', 500);
|
||||
$cached_value_long = $this->randomMachineName();
|
||||
$backend->set($cid_long, $cached_value_long);
|
||||
$this->assertSame($cached_value_long, $backend->get($cid_long)->data, "Backend contains the correct value for long, non-ASCII cache id.");
|
||||
|
||||
$cid_short = '愛1€';
|
||||
$cached_value_short = $this->randomMachineName();
|
||||
$backend->set($cid_short, $cached_value_short);
|
||||
$this->assertSame($cached_value_short, $backend->get($cid_short)->data, "Backend contains the correct value for short, non-ASCII cache id.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the row count limiting of cache bin database tables.
|
||||
*/
|
||||
public function testGarbageCollection() {
|
||||
$backend = $this->getCacheBackend();
|
||||
$max_rows = static::$maxRows;
|
||||
|
||||
$this->assertSame(0, (int) $this->getNumRows());
|
||||
|
||||
// Fill to just the limit.
|
||||
for ($i = 0; $i < $max_rows; $i++) {
|
||||
// Ensure that each cache item created happens in a different millisecond,
|
||||
// by waiting 1 ms (1000 microseconds). The garbage collection might
|
||||
// otherwise keep less than exactly 100 records (which is acceptable for
|
||||
// real-world cases, but not for this test).
|
||||
usleep(1000);
|
||||
$backend->set("test$i", $i);
|
||||
}
|
||||
$this->assertSame($max_rows, $this->getNumRows());
|
||||
|
||||
// Garbage collection has no effect.
|
||||
$backend->garbageCollection();
|
||||
$this->assertSame($max_rows, $this->getNumRows());
|
||||
|
||||
// Go one row beyond the limit.
|
||||
$backend->set('test' . ($max_rows + 1), $max_rows + 1);
|
||||
$this->assertSame($max_rows + 1, $this->getNumRows());
|
||||
|
||||
// Garbage collection removes one row: the oldest.
|
||||
$backend->garbageCollection();
|
||||
$this->assertSame($max_rows, $this->getNumRows());
|
||||
$this->assertFalse($backend->get('test0'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of rows in the test cache bin database table.
|
||||
*
|
||||
* @return int
|
||||
* The number of rows in the test cache bin database table.
|
||||
*/
|
||||
protected function getNumRows() {
|
||||
$table = 'cache_' . $this->testBin;
|
||||
$connection = $this->container->get('database');
|
||||
$query = $connection->select($table);
|
||||
$query->addExpression('COUNT(cid)', 'cid');
|
||||
return (int) $query->execute()->fetchField();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,625 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests any cache backend.
|
||||
*
|
||||
* Full generic unit test suite for any cache backend. In order to use it for a
|
||||
* cache backend implementation, extend this class and override the
|
||||
* createBackendInstance() method to return an object.
|
||||
*
|
||||
* @see DatabaseBackendUnitTestCase
|
||||
* For a full working implementation.
|
||||
*/
|
||||
abstract class GenericCacheBackendUnitTestBase extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Array of objects implementing Drupal\Core\Cache\CacheBackendInterface.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cachebackends;
|
||||
|
||||
/**
|
||||
* Cache bin to use for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testBin;
|
||||
|
||||
/**
|
||||
* Random value to use in tests.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultValue;
|
||||
|
||||
/**
|
||||
* Gets the testing bin.
|
||||
*
|
||||
* Override this method if you want to work on a different bin than the
|
||||
* default one.
|
||||
*
|
||||
* @return string
|
||||
* Bin name.
|
||||
*/
|
||||
protected function getTestBin() {
|
||||
if (!isset($this->testBin)) {
|
||||
$this->testBin = 'page';
|
||||
}
|
||||
return $this->testBin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cache backend to test.
|
||||
*
|
||||
* Override this method to test a CacheBackend.
|
||||
*
|
||||
* @param string $bin
|
||||
* Bin name to use for this backend instance.
|
||||
*
|
||||
* @return \Drupal\Core\Cache\CacheBackendInterface
|
||||
* Cache backend to test.
|
||||
*/
|
||||
abstract protected function createCacheBackend($bin);
|
||||
|
||||
/**
|
||||
* Allows specific implementation to change the environment before a test run.
|
||||
*/
|
||||
public function setUpCacheBackend() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows alteration of environment after a test run but before tear down.
|
||||
*
|
||||
* Used before the real tear down because the tear down will change things
|
||||
* such as the database prefix.
|
||||
*/
|
||||
public function tearDownCacheBackend() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a backend to test; this will get a shared instance set in the object.
|
||||
*
|
||||
* @return \Drupal\Core\Cache\CacheBackendInterface
|
||||
* Cache backend to test.
|
||||
*/
|
||||
protected function getCacheBackend($bin = NULL) {
|
||||
if (!isset($bin)) {
|
||||
$bin = $this->getTestBin();
|
||||
}
|
||||
if (!isset($this->cachebackends[$bin])) {
|
||||
$this->cachebackends[$bin] = $this->createCacheBackend($bin);
|
||||
// Ensure the backend is empty.
|
||||
$this->cachebackends[$bin]->deleteAll();
|
||||
}
|
||||
return $this->cachebackends[$bin];
|
||||
}
|
||||
|
||||
protected function setUp() {
|
||||
$this->cachebackends = [];
|
||||
$this->defaultValue = $this->randomMachineName(10);
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpCacheBackend();
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
// Destruct the registered backend, each test will get a fresh instance,
|
||||
// properly emptying it here ensure that on persistent data backends they
|
||||
// will come up empty the next test.
|
||||
foreach ($this->cachebackends as $bin => $cachebackend) {
|
||||
$this->cachebackends[$bin]->deleteAll();
|
||||
}
|
||||
unset($this->cachebackends);
|
||||
|
||||
$this->tearDownCacheBackend();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the get and set methods of Drupal\Core\Cache\CacheBackendInterface.
|
||||
*/
|
||||
public function testSetGet() {
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
$this->assertSame(FALSE, $backend->get('test1'), "Backend does not contain data for cache id test1.");
|
||||
$with_backslash = ['foo' => '\Drupal\foo\Bar'];
|
||||
$backend->set('test1', $with_backslash);
|
||||
$cached = $backend->get('test1');
|
||||
$this->assert(is_object($cached), "Backend returned an object for cache id test1.");
|
||||
$this->assertSame($with_backslash, $cached->data);
|
||||
$this->assertTrue($cached->valid, 'Item is marked as valid.');
|
||||
// We need to round because microtime may be rounded up in the backend.
|
||||
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
|
||||
$this->assertEqual($cached->expire, Cache::PERMANENT, 'Expire time is correct.');
|
||||
|
||||
$this->assertSame(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2.");
|
||||
$backend->set('test2', ['value' => 3], REQUEST_TIME + 3);
|
||||
$cached = $backend->get('test2');
|
||||
$this->assert(is_object($cached), "Backend returned an object for cache id test2.");
|
||||
$this->assertSame(['value' => 3], $cached->data);
|
||||
$this->assertTrue($cached->valid, 'Item is marked as valid.');
|
||||
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
|
||||
$this->assertEqual($cached->expire, REQUEST_TIME + 3, 'Expire time is correct.');
|
||||
|
||||
$backend->set('test3', 'foobar', REQUEST_TIME - 3);
|
||||
$this->assertFalse($backend->get('test3'), 'Invalid item not returned.');
|
||||
$cached = $backend->get('test3', TRUE);
|
||||
$this->assert(is_object($cached), 'Backend returned an object for cache id test3.');
|
||||
$this->assertFalse($cached->valid, 'Item is marked as valid.');
|
||||
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
|
||||
$this->assertEqual($cached->expire, REQUEST_TIME - 3, 'Expire time is correct.');
|
||||
|
||||
$this->assertSame(FALSE, $backend->get('test4'), "Backend does not contain data for cache id test4.");
|
||||
$with_eof = ['foo' => "\nEOF\ndata"];
|
||||
$backend->set('test4', $with_eof);
|
||||
$cached = $backend->get('test4');
|
||||
$this->assert(is_object($cached), "Backend returned an object for cache id test4.");
|
||||
$this->assertSame($with_eof, $cached->data);
|
||||
$this->assertTrue($cached->valid, 'Item is marked as valid.');
|
||||
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
|
||||
$this->assertEqual($cached->expire, Cache::PERMANENT, 'Expire time is correct.');
|
||||
|
||||
$this->assertSame(FALSE, $backend->get('test5'), "Backend does not contain data for cache id test5.");
|
||||
$with_eof_and_semicolon = ['foo' => "\nEOF;\ndata"];
|
||||
$backend->set('test5', $with_eof_and_semicolon);
|
||||
$cached = $backend->get('test5');
|
||||
$this->assert(is_object($cached), "Backend returned an object for cache id test5.");
|
||||
$this->assertSame($with_eof_and_semicolon, $cached->data);
|
||||
$this->assertTrue($cached->valid, 'Item is marked as valid.');
|
||||
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
|
||||
$this->assertEqual($cached->expire, Cache::PERMANENT, 'Expire time is correct.');
|
||||
|
||||
$with_variable = ['foo' => '$bar'];
|
||||
$backend->set('test6', $with_variable);
|
||||
$cached = $backend->get('test6');
|
||||
$this->assert(is_object($cached), "Backend returned an object for cache id test6.");
|
||||
$this->assertSame($with_variable, $cached->data);
|
||||
|
||||
// Make sure that a cached object is not affected by changing the original.
|
||||
$data = new \stdClass();
|
||||
$data->value = 1;
|
||||
$data->obj = new \stdClass();
|
||||
$data->obj->value = 2;
|
||||
$backend->set('test7', $data);
|
||||
$expected_data = clone $data;
|
||||
// Add a property to the original. It should not appear in the cached data.
|
||||
$data->this_should_not_be_in_the_cache = TRUE;
|
||||
$cached = $backend->get('test7');
|
||||
$this->assert(is_object($cached), "Backend returned an object for cache id test7.");
|
||||
$this->assertEqual($expected_data, $cached->data);
|
||||
$this->assertFalse(isset($cached->data->this_should_not_be_in_the_cache));
|
||||
// Add a property to the cache data. It should not appear when we fetch
|
||||
// the data from cache again.
|
||||
$cached->data->this_should_not_be_in_the_cache = TRUE;
|
||||
$fresh_cached = $backend->get('test7');
|
||||
$this->assertFalse(isset($fresh_cached->data->this_should_not_be_in_the_cache));
|
||||
|
||||
// Check with a long key.
|
||||
$cid = str_repeat('a', 300);
|
||||
$backend->set($cid, 'test');
|
||||
$this->assertEqual('test', $backend->get($cid)->data);
|
||||
|
||||
// Check that the cache key is case sensitive.
|
||||
$backend->set('TEST8', 'value');
|
||||
$this->assertEqual('value', $backend->get('TEST8')->data);
|
||||
$this->assertFalse($backend->get('test8'));
|
||||
|
||||
// Calling ::set() with invalid cache tags. This should fail an assertion.
|
||||
try {
|
||||
$backend->set('assertion_test', 'value', Cache::PERMANENT, ['node' => [3, 5, 7]]);
|
||||
$this->fail('::set() was called with invalid cache tags, runtime assertion did not fail.');
|
||||
}
|
||||
catch (\AssertionError $e) {
|
||||
$this->pass('::set() was called with invalid cache tags, runtime assertion failed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Drupal\Core\Cache\CacheBackendInterface::delete().
|
||||
*/
|
||||
public function testDelete() {
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
$this->assertSame(FALSE, $backend->get('test1'), "Backend does not contain data for cache id test1.");
|
||||
$backend->set('test1', 7);
|
||||
$this->assert(is_object($backend->get('test1')), "Backend returned an object for cache id test1.");
|
||||
|
||||
$this->assertSame(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2.");
|
||||
$backend->set('test2', 3);
|
||||
$this->assert(is_object($backend->get('test2')), "Backend returned an object for cache id %cid.");
|
||||
|
||||
$backend->delete('test1');
|
||||
$this->assertSame(FALSE, $backend->get('test1'), "Backend does not contain data for cache id test1 after deletion.");
|
||||
|
||||
$this->assert(is_object($backend->get('test2')), "Backend still has an object for cache id test2.");
|
||||
|
||||
$backend->delete('test2');
|
||||
$this->assertSame(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2 after deletion.");
|
||||
|
||||
$long_cid = str_repeat('a', 300);
|
||||
$backend->set($long_cid, 'test');
|
||||
$backend->delete($long_cid);
|
||||
$this->assertSame(FALSE, $backend->get($long_cid), "Backend does not contain data for long cache id after deletion.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests data type preservation.
|
||||
*/
|
||||
public function testValueTypeIsKept() {
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
$variables = [
|
||||
'test1' => 1,
|
||||
'test2' => '0',
|
||||
'test3' => '',
|
||||
'test4' => 12.64,
|
||||
'test5' => FALSE,
|
||||
'test6' => [1, 2, 3],
|
||||
];
|
||||
|
||||
// Create cache entries.
|
||||
foreach ($variables as $cid => $data) {
|
||||
$backend->set($cid, $data);
|
||||
}
|
||||
|
||||
// Retrieve and test cache objects.
|
||||
foreach ($variables as $cid => $value) {
|
||||
$object = $backend->get($cid);
|
||||
$this->assert(is_object($object), sprintf("Backend returned an object for cache id %s.", $cid));
|
||||
$this->assertSame($value, $object->data, sprintf("Data of cached id %s kept is identical in type and value", $cid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Drupal\Core\Cache\CacheBackendInterface::getMultiple().
|
||||
*/
|
||||
public function testGetMultiple() {
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
// Set numerous testing keys.
|
||||
$long_cid = str_repeat('a', 300);
|
||||
$backend->set('test1', 1);
|
||||
$backend->set('test2', 3);
|
||||
$backend->set('test3', 5);
|
||||
$backend->set('test4', 7);
|
||||
$backend->set('test5', 11);
|
||||
$backend->set('test6', 13);
|
||||
$backend->set('test7', 17);
|
||||
$backend->set($long_cid, 300);
|
||||
|
||||
// Mismatch order for harder testing.
|
||||
$reference = [
|
||||
'test3',
|
||||
'test7',
|
||||
// Cid does not exist.
|
||||
'test21',
|
||||
'test6',
|
||||
// Cid does not exist until added before second getMultiple().
|
||||
'test19',
|
||||
'test2',
|
||||
];
|
||||
|
||||
$cids = $reference;
|
||||
$ret = $backend->getMultiple($cids);
|
||||
// Test return - ensure it contains existing cache ids.
|
||||
$this->assert(isset($ret['test2']), "Existing cache id test2 is set.");
|
||||
$this->assert(isset($ret['test3']), "Existing cache id test3 is set.");
|
||||
$this->assert(isset($ret['test6']), "Existing cache id test6 is set.");
|
||||
$this->assert(isset($ret['test7']), "Existing cache id test7 is set.");
|
||||
// Test return - ensure that objects has expected properties.
|
||||
$this->assertTrue($ret['test2']->valid, 'Item is marked as valid.');
|
||||
$this->assertTrue($ret['test2']->created >= REQUEST_TIME && $ret['test2']->created <= round(microtime(TRUE), 3), 'Created time is correct.');
|
||||
$this->assertEqual($ret['test2']->expire, Cache::PERMANENT, 'Expire time is correct.');
|
||||
// Test return - ensure it does not contain nonexistent cache ids.
|
||||
$this->assertFalse(isset($ret['test19']), "Nonexistent cache id test19 is not set.");
|
||||
$this->assertFalse(isset($ret['test21']), "Nonexistent cache id test21 is not set.");
|
||||
// Test values.
|
||||
$this->assertIdentical($ret['test2']->data, 3, "Existing cache id test2 has the correct value.");
|
||||
$this->assertIdentical($ret['test3']->data, 5, "Existing cache id test3 has the correct value.");
|
||||
$this->assertIdentical($ret['test6']->data, 13, "Existing cache id test6 has the correct value.");
|
||||
$this->assertIdentical($ret['test7']->data, 17, "Existing cache id test7 has the correct value.");
|
||||
// Test $cids array - ensure it contains cache id's that do not exist.
|
||||
$this->assert(in_array('test19', $cids), "Nonexistent cache id test19 is in cids array.");
|
||||
$this->assert(in_array('test21', $cids), "Nonexistent cache id test21 is in cids array.");
|
||||
// Test $cids array - ensure it does not contain cache id's that exist.
|
||||
$this->assertFalse(in_array('test2', $cids), "Existing cache id test2 is not in cids array.");
|
||||
$this->assertFalse(in_array('test3', $cids), "Existing cache id test3 is not in cids array.");
|
||||
$this->assertFalse(in_array('test6', $cids), "Existing cache id test6 is not in cids array.");
|
||||
$this->assertFalse(in_array('test7', $cids), "Existing cache id test7 is not in cids array.");
|
||||
|
||||
// Test a second time after deleting and setting new keys which ensures that
|
||||
// if the backend uses statics it does not cause unexpected results.
|
||||
$backend->delete('test3');
|
||||
$backend->delete('test6');
|
||||
$backend->set('test19', 57);
|
||||
|
||||
$cids = $reference;
|
||||
$ret = $backend->getMultiple($cids);
|
||||
// Test return - ensure it contains existing cache ids.
|
||||
$this->assert(isset($ret['test2']), "Existing cache id test2 is set");
|
||||
$this->assert(isset($ret['test7']), "Existing cache id test7 is set");
|
||||
$this->assert(isset($ret['test19']), "Added cache id test19 is set");
|
||||
// Test return - ensure it does not contain nonexistent cache ids.
|
||||
$this->assertFalse(isset($ret['test3']), "Deleted cache id test3 is not set");
|
||||
$this->assertFalse(isset($ret['test6']), "Deleted cache id test6 is not set");
|
||||
$this->assertFalse(isset($ret['test21']), "Nonexistent cache id test21 is not set");
|
||||
// Test values.
|
||||
$this->assertIdentical($ret['test2']->data, 3, "Existing cache id test2 has the correct value.");
|
||||
$this->assertIdentical($ret['test7']->data, 17, "Existing cache id test7 has the correct value.");
|
||||
$this->assertIdentical($ret['test19']->data, 57, "Added cache id test19 has the correct value.");
|
||||
// Test $cids array - ensure it contains cache id's that do not exist.
|
||||
$this->assert(in_array('test3', $cids), "Deleted cache id test3 is in cids array.");
|
||||
$this->assert(in_array('test6', $cids), "Deleted cache id test6 is in cids array.");
|
||||
$this->assert(in_array('test21', $cids), "Nonexistent cache id test21 is in cids array.");
|
||||
// Test $cids array - ensure it does not contain cache id's that exist.
|
||||
$this->assertFalse(in_array('test2', $cids), "Existing cache id test2 is not in cids array.");
|
||||
$this->assertFalse(in_array('test7', $cids), "Existing cache id test7 is not in cids array.");
|
||||
$this->assertFalse(in_array('test19', $cids), "Added cache id test19 is not in cids array.");
|
||||
|
||||
// Test with a long $cid and non-numeric array key.
|
||||
$cids = ['key:key' => $long_cid];
|
||||
$return = $backend->getMultiple($cids);
|
||||
$this->assertEqual(300, $return[$long_cid]->data);
|
||||
$this->assertTrue(empty($cids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Cache\CacheBackendInterface::setMultiple().
|
||||
*/
|
||||
public function testSetMultiple() {
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
$future_expiration = REQUEST_TIME + 100;
|
||||
|
||||
// Set multiple testing keys.
|
||||
$backend->set('cid_1', 'Some other value');
|
||||
$items = [
|
||||
'cid_1' => ['data' => 1],
|
||||
'cid_2' => ['data' => 2],
|
||||
'cid_3' => ['data' => [1, 2]],
|
||||
'cid_4' => ['data' => 1, 'expire' => $future_expiration],
|
||||
'cid_5' => ['data' => 1, 'tags' => ['test:a', 'test:b']],
|
||||
];
|
||||
$backend->setMultiple($items);
|
||||
$cids = array_keys($items);
|
||||
$cached = $backend->getMultiple($cids);
|
||||
|
||||
$this->assertEqual($cached['cid_1']->data, $items['cid_1']['data'], 'Over-written cache item set correctly.');
|
||||
$this->assertTrue($cached['cid_1']->valid, 'Item is marked as valid.');
|
||||
$this->assertTrue($cached['cid_1']->created >= REQUEST_TIME && $cached['cid_1']->created <= round(microtime(TRUE), 3), 'Created time is correct.');
|
||||
$this->assertEqual($cached['cid_1']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
|
||||
|
||||
$this->assertEqual($cached['cid_2']->data, $items['cid_2']['data'], 'New cache item set correctly.');
|
||||
$this->assertEqual($cached['cid_2']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
|
||||
|
||||
$this->assertEqual($cached['cid_3']->data, $items['cid_3']['data'], 'New cache item with serialized data set correctly.');
|
||||
$this->assertEqual($cached['cid_3']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
|
||||
|
||||
$this->assertEqual($cached['cid_4']->data, $items['cid_4']['data'], 'New cache item set correctly.');
|
||||
$this->assertEqual($cached['cid_4']->expire, $future_expiration, 'Cache expiration has been correctly set.');
|
||||
|
||||
$this->assertEqual($cached['cid_5']->data, $items['cid_5']['data'], 'New cache item set correctly.');
|
||||
|
||||
// Calling ::setMultiple() with invalid cache tags. This should fail an
|
||||
// assertion.
|
||||
try {
|
||||
$items = [
|
||||
'exception_test_1' => ['data' => 1, 'tags' => []],
|
||||
'exception_test_2' => ['data' => 2, 'tags' => ['valid']],
|
||||
'exception_test_3' => ['data' => 3, 'tags' => ['node' => [3, 5, 7]]],
|
||||
];
|
||||
$backend->setMultiple($items);
|
||||
$this->fail('::setMultiple() was called with invalid cache tags, runtime assertion did not fail.');
|
||||
}
|
||||
catch (\AssertionError $e) {
|
||||
$this->pass('::setMultiple() was called with invalid cache tags, runtime assertion failed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Drupal\Core\Cache\CacheBackendInterface::delete() and
|
||||
* Drupal\Core\Cache\CacheBackendInterface::deleteMultiple().
|
||||
*/
|
||||
public function testDeleteMultiple() {
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
// Set numerous testing keys.
|
||||
$backend->set('test1', 1);
|
||||
$backend->set('test2', 3);
|
||||
$backend->set('test3', 5);
|
||||
$backend->set('test4', 7);
|
||||
$backend->set('test5', 11);
|
||||
$backend->set('test6', 13);
|
||||
$backend->set('test7', 17);
|
||||
|
||||
$backend->delete('test1');
|
||||
// Nonexistent key should not cause an error.
|
||||
$backend->delete('test23');
|
||||
$backend->deleteMultiple([
|
||||
'test3',
|
||||
'test5',
|
||||
'test7',
|
||||
// Nonexistent key should not cause an error.
|
||||
'test19',
|
||||
// Nonexistent key should not cause an error.
|
||||
'test21',
|
||||
]);
|
||||
|
||||
// Test if expected keys have been deleted.
|
||||
$this->assertSame(FALSE, $backend->get('test1'), "Cache id test1 deleted.");
|
||||
$this->assertSame(FALSE, $backend->get('test3'), "Cache id test3 deleted.");
|
||||
$this->assertSame(FALSE, $backend->get('test5'), "Cache id test5 deleted.");
|
||||
$this->assertSame(FALSE, $backend->get('test7'), "Cache id test7 deleted.");
|
||||
|
||||
// Test if expected keys exist.
|
||||
$this->assertNotIdentical(FALSE, $backend->get('test2'), "Cache id test2 exists.");
|
||||
$this->assertNotIdentical(FALSE, $backend->get('test4'), "Cache id test4 exists.");
|
||||
$this->assertNotIdentical(FALSE, $backend->get('test6'), "Cache id test6 exists.");
|
||||
|
||||
// Test if that expected keys do not exist.
|
||||
$this->assertSame(FALSE, $backend->get('test19'), "Cache id test19 does not exist.");
|
||||
$this->assertSame(FALSE, $backend->get('test21'), "Cache id test21 does not exist.");
|
||||
|
||||
// Calling deleteMultiple() with an empty array should not cause an error.
|
||||
$this->assertFalse($backend->deleteMultiple([]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Drupal\Core\Cache\CacheBackendInterface::deleteAll().
|
||||
*/
|
||||
public function testDeleteAll() {
|
||||
$backend_a = $this->getCacheBackend();
|
||||
$backend_b = $this->getCacheBackend('bootstrap');
|
||||
|
||||
// Set both expiring and permanent keys.
|
||||
$backend_a->set('test1', 1, Cache::PERMANENT);
|
||||
$backend_a->set('test2', 3, time() + 1000);
|
||||
$backend_b->set('test3', 4, Cache::PERMANENT);
|
||||
|
||||
$backend_a->deleteAll();
|
||||
|
||||
$this->assertFalse($backend_a->get('test1'), 'First key has been deleted.');
|
||||
$this->assertFalse($backend_a->get('test2'), 'Second key has been deleted.');
|
||||
$this->assertTrue($backend_b->get('test3'), 'Item in other bin is preserved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Drupal\Core\Cache\CacheBackendInterface::invalidate() and
|
||||
* Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple().
|
||||
*/
|
||||
public function testInvalidate() {
|
||||
$backend = $this->getCacheBackend();
|
||||
$backend->set('test1', 1);
|
||||
$backend->set('test2', 2);
|
||||
$backend->set('test3', 2);
|
||||
$backend->set('test4', 2);
|
||||
|
||||
$reference = ['test1', 'test2', 'test3', 'test4'];
|
||||
|
||||
$cids = $reference;
|
||||
$ret = $backend->getMultiple($cids);
|
||||
$this->assertEqual(count($ret), 4, 'Four items returned.');
|
||||
|
||||
$backend->invalidate('test1');
|
||||
$backend->invalidateMultiple(['test2', 'test3']);
|
||||
|
||||
$cids = $reference;
|
||||
$ret = $backend->getMultiple($cids);
|
||||
$this->assertEqual(count($ret), 1, 'Only one item element returned.');
|
||||
|
||||
$cids = $reference;
|
||||
$ret = $backend->getMultiple($cids, TRUE);
|
||||
$this->assertEqual(count($ret), 4, 'Four items returned.');
|
||||
|
||||
// Calling invalidateMultiple() with an empty array should not cause an
|
||||
// error.
|
||||
$this->assertFalse($backend->invalidateMultiple([]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
|
||||
*/
|
||||
public function testInvalidateTags() {
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
// Create two cache entries with the same tag and tag value.
|
||||
$backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, ['test_tag:2']);
|
||||
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, ['test_tag:2']);
|
||||
$this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.');
|
||||
|
||||
// Invalidate test_tag of value 1. This should invalidate both entries.
|
||||
Cache::invalidateTags(['test_tag:2']);
|
||||
$this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after invalidating a cache tag.');
|
||||
$this->assertTrue($backend->get('test_cid_invalidate1', TRUE) && $backend->get('test_cid_invalidate2', TRUE), 'Cache items not deleted after invalidating a cache tag.');
|
||||
|
||||
// Create two cache entries with the same tag and an array tag value.
|
||||
$backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, ['test_tag:1']);
|
||||
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, ['test_tag:1']);
|
||||
$this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.');
|
||||
|
||||
// Invalidate test_tag of value 1. This should invalidate both entries.
|
||||
Cache::invalidateTags(['test_tag:1']);
|
||||
$this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two caches removed after invalidating a cache tag.');
|
||||
$this->assertTrue($backend->get('test_cid_invalidate1', TRUE) && $backend->get('test_cid_invalidate2', TRUE), 'Cache items not deleted after invalidating a cache tag.');
|
||||
|
||||
// Create three cache entries with a mix of tags and tag values.
|
||||
$backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, ['test_tag:1']);
|
||||
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, ['test_tag:2']);
|
||||
$backend->set('test_cid_invalidate3', $this->defaultValue, Cache::PERMANENT, ['test_tag_foo:3']);
|
||||
$this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2') && $backend->get('test_cid_invalidate3'), 'Three cached items were created.');
|
||||
Cache::invalidateTags(['test_tag_foo:3']);
|
||||
$this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Cache items not matching the tag were not invalidated.');
|
||||
$this->assertFalse($backend->get('test_cid_invalidated3'), 'Cached item matching the tag was removed.');
|
||||
|
||||
// Create cache entry in multiple bins. Two cache entries
|
||||
// (test_cid_invalidate1 and test_cid_invalidate2) still exist from previous
|
||||
// tests.
|
||||
$tags = ['test_tag:1', 'test_tag:2', 'test_tag:3'];
|
||||
$bins = ['path', 'bootstrap', 'page'];
|
||||
foreach ($bins as $bin) {
|
||||
$this->getCacheBackend($bin)->set('test', $this->defaultValue, Cache::PERMANENT, $tags);
|
||||
$this->assertTrue($this->getCacheBackend($bin)->get('test'), 'Cache item was set in bin.');
|
||||
}
|
||||
|
||||
Cache::invalidateTags(['test_tag:2']);
|
||||
|
||||
// Test that the cache entry has been invalidated in multiple bins.
|
||||
foreach ($bins as $bin) {
|
||||
$this->assertFalse($this->getCacheBackend($bin)->get('test'), 'Tag invalidation affected item in bin.');
|
||||
}
|
||||
// Test that the cache entry with a matching tag has been invalidated.
|
||||
$this->assertFalse($this->getCacheBackend($bin)->get('test_cid_invalidate2'), 'Cache items matching tag were invalidated.');
|
||||
// Test that the cache entry with without a matching tag still exists.
|
||||
$this->assertTrue($this->getCacheBackend($bin)->get('test_cid_invalidate1'), 'Cache items not matching tag were not invalidated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
|
||||
*/
|
||||
public function testInvalidateAll() {
|
||||
$backend_a = $this->getCacheBackend();
|
||||
$backend_b = $this->getCacheBackend('bootstrap');
|
||||
|
||||
// Set both expiring and permanent keys.
|
||||
$backend_a->set('test1', 1, Cache::PERMANENT);
|
||||
$backend_a->set('test2', 3, time() + 1000);
|
||||
$backend_b->set('test3', 4, Cache::PERMANENT);
|
||||
|
||||
$backend_a->invalidateAll();
|
||||
|
||||
$this->assertFalse($backend_a->get('test1'), 'First key has been invalidated.');
|
||||
$this->assertFalse($backend_a->get('test2'), 'Second key has been invalidated.');
|
||||
$this->assertTrue($backend_b->get('test3'), 'Item in other bin is preserved.');
|
||||
$this->assertTrue($backend_a->get('test1', TRUE), 'First key has not been deleted.');
|
||||
$this->assertTrue($backend_a->get('test2', TRUE), 'Second key has not been deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Drupal\Core\Cache\CacheBackendInterface::removeBin().
|
||||
*/
|
||||
public function testRemoveBin() {
|
||||
$backend_a = $this->getCacheBackend();
|
||||
$backend_b = $this->getCacheBackend('bootstrap');
|
||||
|
||||
// Set both expiring and permanent keys.
|
||||
$backend_a->set('test1', 1, Cache::PERMANENT);
|
||||
$backend_a->set('test2', 3, time() + 1000);
|
||||
$backend_b->set('test3', 4, Cache::PERMANENT);
|
||||
|
||||
$backend_a->removeBin();
|
||||
|
||||
$this->assertFalse($backend_a->get('test1'), 'First key has been deleted.');
|
||||
$this->assertFalse($backend_a->get('test2', TRUE), 'Second key has been deleted.');
|
||||
$this->assertTrue($backend_b->get('test3'), 'Item in other bin is preserved.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\MemoryBackend;
|
||||
|
||||
/**
|
||||
* Unit test of the memory cache backend using the generic cache unit test base.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class MemoryBackendTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
/**
|
||||
* Creates a new instance of MemoryBackend.
|
||||
*
|
||||
* @return
|
||||
* A new MemoryBackend object.
|
||||
*/
|
||||
protected function createCacheBackend($bin) {
|
||||
$backend = new MemoryBackend();
|
||||
\Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
|
||||
return $backend;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Cache;
|
||||
|
||||
use Drupal\Core\Cache\PhpBackend;
|
||||
|
||||
/**
|
||||
* Unit test of the PHP cache backend using the generic cache unit test base.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class PhpBackendTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
/**
|
||||
* Creates a new instance of MemoryBackend.
|
||||
*
|
||||
* @return
|
||||
* A new MemoryBackend object.
|
||||
*/
|
||||
protected function createCacheBackend($bin) {
|
||||
$backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'));
|
||||
return $backend;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Command;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Command\DbDumpApplication;
|
||||
use Drupal\Core\Config\DatabaseStorage;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Tests for the database dump commands.
|
||||
*
|
||||
* @group Update
|
||||
*/
|
||||
class DbDumpTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'config', 'dblog', 'menu_link_content', 'link', 'block_content', 'file', 'user'];
|
||||
|
||||
/**
|
||||
* Test data to write into config.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Flag to skip these tests, which are database-backend dependent (MySQL).
|
||||
*
|
||||
* @see \Drupal\Core\Command\DbDumpCommand
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $skipTests = FALSE;
|
||||
|
||||
/**
|
||||
* An array of original table schemas.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $originalTableSchemas = [];
|
||||
|
||||
/**
|
||||
* An array of original table indexes (including primary and unique keys).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $originalTableIndexes = [];
|
||||
|
||||
/**
|
||||
* Tables that should be part of the exported script.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tables;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Register a database cache backend rather than memory-based.
|
||||
*/
|
||||
public function register(ContainerBuilder $container) {
|
||||
parent::register($container);
|
||||
$container->register('cache_factory', 'Drupal\Core\Cache\DatabaseBackendFactory')
|
||||
->addArgument(new Reference('database'))
|
||||
->addArgument(new Reference('cache_tags.invalidator.checksum'))
|
||||
->addArgument(new Reference('settings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Determine what database backend is running, and set the skip flag.
|
||||
$this->skipTests = Database::getConnection()->databaseType() !== 'mysql';
|
||||
|
||||
// Create some schemas so our export contains tables.
|
||||
$this->installSchema('system', [
|
||||
'key_value_expire',
|
||||
'sessions',
|
||||
]);
|
||||
$this->installSchema('dblog', ['watchdog']);
|
||||
$this->installEntitySchema('block_content');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('file');
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
$this->installSchema('system', 'sequences');
|
||||
|
||||
// Place some sample config to test for in the export.
|
||||
$this->data = [
|
||||
'foo' => $this->randomMachineName(),
|
||||
'bar' => $this->randomMachineName(),
|
||||
];
|
||||
$storage = new DatabaseStorage(Database::getConnection(), 'config');
|
||||
$storage->write('test_config', $this->data);
|
||||
|
||||
// Create user account with some potential syntax issues.
|
||||
$account = User::create(['mail' => 'q\'uote$dollar@example.com', 'name' => '$dollar']);
|
||||
$account->save();
|
||||
|
||||
// Create url_alias (this will create 'url_alias').
|
||||
$this->container->get('path.alias_storage')->save('/user/' . $account->id(), '/user/example');
|
||||
|
||||
// Create a cache table (this will create 'cache_discovery').
|
||||
\Drupal::cache('discovery')->set('test', $this->data);
|
||||
|
||||
// These are all the tables that should now be in place.
|
||||
$this->tables = [
|
||||
'block_content',
|
||||
'block_content_field_data',
|
||||
'block_content_field_revision',
|
||||
'block_content_revision',
|
||||
'cachetags',
|
||||
'config',
|
||||
'cache_bootstrap',
|
||||
'cache_config',
|
||||
'cache_data',
|
||||
'cache_discovery',
|
||||
'cache_entity',
|
||||
'file_managed',
|
||||
'key_value_expire',
|
||||
'menu_link_content',
|
||||
'menu_link_content_data',
|
||||
'sequences',
|
||||
'sessions',
|
||||
'url_alias',
|
||||
'user__roles',
|
||||
'users',
|
||||
'users_field_data',
|
||||
'watchdog',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the command directly.
|
||||
*/
|
||||
public function testDbDumpCommand() {
|
||||
if ($this->skipTests) {
|
||||
$this->pass("Skipping test since the DbDumpCommand is currently only compatible with MySql");
|
||||
return;
|
||||
}
|
||||
|
||||
$application = new DbDumpApplication();
|
||||
$command = $application->find('dump-database-d8-mysql');
|
||||
$command_tester = new CommandTester($command);
|
||||
$command_tester->execute([]);
|
||||
|
||||
// Tables that are schema-only should not have data exported.
|
||||
$pattern = preg_quote("\$connection->insert('sessions')");
|
||||
$this->assertFalse(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Tables defined as schema-only do not have data exported to the script.');
|
||||
|
||||
// Table data is exported.
|
||||
$pattern = preg_quote("\$connection->insert('config')");
|
||||
$this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Table data is properly exported to the script.');
|
||||
|
||||
// The test data are in the dump (serialized).
|
||||
$pattern = preg_quote(serialize($this->data));
|
||||
$this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Generated data is found in the exported script.');
|
||||
|
||||
// Check that the user account name and email address was properly escaped.
|
||||
$pattern = preg_quote('"q\'uote\$dollar@example.com"');
|
||||
$this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'The user account email address was properly escaped in the exported script.');
|
||||
$pattern = preg_quote('\'$dollar\'');
|
||||
$this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'The user account name was properly escaped in the exported script.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test loading the script back into the database.
|
||||
*/
|
||||
public function testScriptLoad() {
|
||||
if ($this->skipTests) {
|
||||
$this->pass("Skipping test since the DbDumpCommand is currently only compatible with MySql");
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the script.
|
||||
$application = new DbDumpApplication();
|
||||
$command = $application->find('dump-database-d8-mysql');
|
||||
$command_tester = new CommandTester($command);
|
||||
$command_tester->execute([]);
|
||||
$script = $command_tester->getDisplay();
|
||||
|
||||
// Store original schemas and drop tables to avoid errors.
|
||||
foreach ($this->tables as $table) {
|
||||
$this->originalTableSchemas[$table] = $this->getTableSchema($table);
|
||||
$this->originalTableIndexes[$table] = $this->getTableIndexes($table);
|
||||
Database::getConnection()->schema()->dropTable($table);
|
||||
}
|
||||
|
||||
// This will load the data.
|
||||
$file = sys_get_temp_dir() . '/' . $this->randomMachineName();
|
||||
file_put_contents($file, $script);
|
||||
require_once $file;
|
||||
|
||||
// The tables should now exist and the schemas should match the originals.
|
||||
foreach ($this->tables as $table) {
|
||||
$this->assertTrue(Database::getConnection()
|
||||
->schema()
|
||||
->tableExists($table), new FormattableMarkup('Table @table created by the database script.', ['@table' => $table]));
|
||||
$this->assertSame($this->originalTableSchemas[$table], $this->getTableSchema($table), new FormattableMarkup('The schema for @table was properly restored.', ['@table' => $table]));
|
||||
$this->assertSame($this->originalTableIndexes[$table], $this->getTableIndexes($table), new FormattableMarkup('The indexes for @table were properly restored.', ['@table' => $table]));
|
||||
}
|
||||
|
||||
// Ensure the test config has been replaced.
|
||||
$config = unserialize(db_query("SELECT data FROM {config} WHERE name = 'test_config'")->fetchField());
|
||||
$this->assertIdentical($config, $this->data, 'Script has properly restored the config table data.');
|
||||
|
||||
// Ensure the cache data was not exported.
|
||||
$this->assertFalse(\Drupal::cache('discovery')
|
||||
->get('test'), 'Cache data was not exported to the script.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get a simplified schema for a given table.
|
||||
*
|
||||
* @param string $table
|
||||
*
|
||||
* @return array
|
||||
* Array keyed by field name, with the values being the field type.
|
||||
*/
|
||||
protected function getTableSchema($table) {
|
||||
// Verify the field type on the data column in the cache table.
|
||||
// @todo this is MySQL specific.
|
||||
$query = db_query("SHOW COLUMNS FROM {" . $table . "}");
|
||||
$definition = [];
|
||||
while ($row = $query->fetchAssoc()) {
|
||||
$definition[$row['Field']] = $row['Type'];
|
||||
}
|
||||
return $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns indexes for a given table.
|
||||
*
|
||||
* @param string $table
|
||||
* The table to find indexes for.
|
||||
*
|
||||
* @return array
|
||||
* The 'primary key', 'unique keys', and 'indexes' portion of the Drupal
|
||||
* table schema.
|
||||
*/
|
||||
protected function getTableIndexes($table) {
|
||||
$query = db_query("SHOW INDEX FROM {" . $table . "}");
|
||||
$definition = [];
|
||||
while ($row = $query->fetchAssoc()) {
|
||||
$index_name = $row['Key_name'];
|
||||
$column = $row['Column_name'];
|
||||
// Key the arrays by the index sequence for proper ordering (start at 0).
|
||||
$order = $row['Seq_in_index'] - 1;
|
||||
|
||||
// If specified, add length to the index.
|
||||
if ($row['Sub_part']) {
|
||||
$column = [$column, $row['Sub_part']];
|
||||
}
|
||||
|
||||
if ($index_name === 'PRIMARY') {
|
||||
$definition['primary key'][$order] = $column;
|
||||
}
|
||||
elseif ($row['Non_unique'] == 0) {
|
||||
$definition['unique keys'][$index_name][$order] = $column;
|
||||
}
|
||||
else {
|
||||
$definition['indexes'][$index_name][$order] = $column;
|
||||
}
|
||||
}
|
||||
return $definition;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Common;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* @covers ::drupal_set_message
|
||||
* @group Common
|
||||
* @group legacy
|
||||
*/
|
||||
class DrupalSetMessageTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The basic functionality of drupal_set_message().
|
||||
*
|
||||
* @expectedDeprecation drupal_set_message() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931
|
||||
* @expectedDeprecation drupal_get_message() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::all() or \Drupal\Core\Messenger\MessengerInterface::messagesByType() instead. See https://www.drupal.org/node/2774931
|
||||
*/
|
||||
public function testDrupalSetMessage() {
|
||||
drupal_set_message(t('A message: @foo', ['@foo' => 'bar']));
|
||||
$messages = drupal_get_messages();
|
||||
$this->assertInstanceOf('Drupal\Core\Render\Markup', $messages['status'][0]);
|
||||
$this->assertEquals('A message: bar', (string) $messages['status'][0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Common;
|
||||
|
||||
use Drupal\Component\Utility\Bytes;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Parse a predefined amount of bytes and compare the output with the expected
|
||||
* value.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class SizeTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Checks that format_size() returns the expected string.
|
||||
*
|
||||
* @dataProvider providerTestCommonFormatSize
|
||||
*/
|
||||
public function testCommonFormatSize($expected, $input) {
|
||||
$size = format_size($input, NULL);
|
||||
$this->assertEquals($expected, $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of byte size to test.
|
||||
*/
|
||||
public function providerTestCommonFormatSize() {
|
||||
$kb = Bytes::KILOBYTE;
|
||||
return [
|
||||
['0 bytes', 0],
|
||||
['1 byte', 1],
|
||||
['-1 bytes', -1],
|
||||
['2 bytes', 2],
|
||||
['-2 bytes', -2],
|
||||
['1023 bytes', $kb - 1],
|
||||
['1 KB', $kb],
|
||||
['1 MB', pow($kb, 2)],
|
||||
['1 GB', pow($kb, 3)],
|
||||
['1 TB', pow($kb, 4)],
|
||||
['1 PB', pow($kb, 5)],
|
||||
['1 EB', pow($kb, 6)],
|
||||
['1 ZB', pow($kb, 7)],
|
||||
['1 YB', pow($kb, 8)],
|
||||
['1024 YB', pow($kb, 9)],
|
||||
// Rounded to 1 MB - not 1000 or 1024 kilobytes
|
||||
['1 MB', ($kb * $kb) - 1],
|
||||
['-1 MB', -(($kb * $kb) - 1)],
|
||||
// Decimal Megabytes
|
||||
['3.46 MB', 3623651],
|
||||
['3.77 GB', 4053371676],
|
||||
// Decimal Petabytes
|
||||
['59.72 PB', 67234178751368124],
|
||||
// Decimal Yottabytes
|
||||
['194.67 YB', 235346823821125814962843827],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Common;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Confirm that \Drupal\Component\Utility\Xss::filter() and check_url() work
|
||||
* correctly, including invalid multi-byte sequences.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class XssUnitTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['filter', 'system'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['system']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests t() functionality.
|
||||
*/
|
||||
public function testT() {
|
||||
$text = t('Simple text');
|
||||
$this->assertEqual($text, 'Simple text', 't leaves simple text alone.');
|
||||
$text = t('Escaped text: @value', ['@value' => '<script>']);
|
||||
$this->assertEqual($text, 'Escaped text: <script>', 't replaces and escapes string.');
|
||||
$text = t('Placeholder text: %value', ['%value' => '<script>']);
|
||||
$this->assertEqual($text, 'Placeholder text: <em class="placeholder"><script></em>', 't replaces, escapes and themes string.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that harmful protocols are stripped.
|
||||
*/
|
||||
public function testBadProtocolStripping() {
|
||||
// Ensure that check_url() strips out harmful protocols, and encodes for
|
||||
// HTML.
|
||||
// Ensure \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() can
|
||||
// be used to return a plain-text string stripped of harmful protocols.
|
||||
$url = 'javascript:http://www.example.com/?x=1&y=2';
|
||||
$expected_plain = 'http://www.example.com/?x=1&y=2';
|
||||
$expected_html = 'http://www.example.com/?x=1&y=2';
|
||||
$this->assertIdentical(check_url($url), $expected_html, 'check_url() filters a URL and encodes it for HTML.');
|
||||
$this->assertIdentical(UrlHelper::filterBadProtocol($url), $expected_html, '\Drupal\Component\Utility\UrlHelper::filterBadProtocol() filters a URL and encodes it for HTML.');
|
||||
$this->assertIdentical(UrlHelper::stripDangerousProtocols($url), $expected_plain, '\Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() filters a URL and returns plain text.');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?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',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
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\Development\ConfigSchemaChecker
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* Tests CRUD operations.
|
||||
*/
|
||||
public 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, ['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, ['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(), []);
|
||||
$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, ['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(), []);
|
||||
$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 = [
|
||||
'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.
|
||||
*/
|
||||
public 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 = [':', '?', '*', '<', '>', '"', '\'', '/', '\\'];
|
||||
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', [
|
||||
'@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.
|
||||
*/
|
||||
public 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(['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', ['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(['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' => [],
|
||||
'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->assertSame(99, $config->get('int'));
|
||||
$config->set('int', '99')->save(TRUE);
|
||||
$this->assertSame('99', $config->get('int'));
|
||||
// Test that re-saving without testing the data enforces the schema type.
|
||||
$config->save();
|
||||
$this->assertSame($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(new FormattableMarkup('%class thrown upon saving invalid data type.', [
|
||||
'%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(new FormattableMarkup('%class thrown upon saving invalid data type.', [
|
||||
'%class' => get_class($e),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,670 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests for configuration dependencies.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Core\Config\ConfigManager
|
||||
*
|
||||
* @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 = ['config_test', 'entity_test', 'user'];
|
||||
|
||||
/**
|
||||
* Tests that calculating dependencies for system module.
|
||||
*/
|
||||
public function testNonEntity() {
|
||||
$this->installConfig(['system']);
|
||||
$config_manager = \Drupal::service('config.manager');
|
||||
$dependents = $config_manager->findConfigEntityDependents('module', ['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', ['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(
|
||||
[
|
||||
'id' => 'entity1',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'module' => ['node'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity1->save();
|
||||
|
||||
$dependents = $config_manager->findConfigEntityDependents('module', ['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', ['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', ['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(['id' => 'entity2', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]);
|
||||
$entity2->save();
|
||||
$entity3 = $storage->create(['id' => 'entity3', 'dependencies' => ['enforced' => ['config' => [$entity2->getConfigDependencyName()]]]]);
|
||||
$entity3->save();
|
||||
$entity4 = $storage->create(['id' => 'entity4', 'dependencies' => ['enforced' => ['config' => [$entity3->getConfigDependencyName()]]]]);
|
||||
$entity4->save();
|
||||
|
||||
// Test getting $entity1's dependencies as configuration dependency objects.
|
||||
$dependents = $config_manager->findConfigEntityDependents('config', [$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', [$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', ['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', ['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([
|
||||
'name' => $this->randomString(),
|
||||
'type' => 'entity_test',
|
||||
]);
|
||||
$entity_test->save();
|
||||
$entity2->setEnforcedDependencies(['config' => [$entity1->getConfigDependencyName()], 'content' => [$entity_test->getConfigDependencyName()]])->save();;
|
||||
$dependents = $config_manager->findConfigEntityDependents('content', [$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(['id' => 'entity1', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]])->save();
|
||||
$alt_storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['module' => ['views']]]])->save();
|
||||
|
||||
$dependents = $config_manager->findConfigEntityDependentsAsEntities('config', [$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', ['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', ['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(
|
||||
[
|
||||
'id' => 'entity1',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'module' => ['node', 'config_test'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity1->save();
|
||||
$entity2 = $storage->create(
|
||||
[
|
||||
'id' => 'entity2',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for self::testConfigEntityUninstallComplex().
|
||||
*/
|
||||
public function providerConfigEntityUninstallComplex() {
|
||||
// Ensure that alphabetical order has no influence on dependency fixing and
|
||||
// removal.
|
||||
return [
|
||||
[['a', 'b', 'c', 'd', 'e']],
|
||||
[['e', 'd', 'c', 'b', 'a']],
|
||||
[['e', 'c', 'd', 'a', 'b']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests complex configuration entity dependency handling during uninstall.
|
||||
*
|
||||
* Configuration entities can be deleted or updated during module uninstall
|
||||
* because they have dependencies on the module.
|
||||
*
|
||||
* @param array $entity_id_suffixes
|
||||
* The suffixes to add to the 4 entities created by the test.
|
||||
*
|
||||
* @dataProvider providerConfigEntityUninstallComplex
|
||||
*/
|
||||
public function testConfigEntityUninstallComplex(array $entity_id_suffixes) {
|
||||
/** @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');
|
||||
// Entity 1 will be deleted because it depends on node.
|
||||
$entity_1 = $storage->create(
|
||||
[
|
||||
'id' => 'entity_' . $entity_id_suffixes[0],
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'module' => ['node', 'config_test'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity_1->save();
|
||||
|
||||
// Entity 2 has a dependency on entity 1 but it can be fixed because
|
||||
// \Drupal\config_test\Entity::onDependencyRemoval() will remove the
|
||||
// dependency before config entities are deleted.
|
||||
$entity_2 = $storage->create(
|
||||
[
|
||||
'id' => 'entity_' . $entity_id_suffixes[1],
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$entity_1->getConfigDependencyName()],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity_2->save();
|
||||
|
||||
// Entity 3 will be unchanged because it is dependent on entity 2 which can
|
||||
// be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
|
||||
// not be called for this entity.
|
||||
$entity_3 = $storage->create(
|
||||
[
|
||||
'id' => 'entity_' . $entity_id_suffixes[2],
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$entity_2->getConfigDependencyName()],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity_3->save();
|
||||
|
||||
// Entity 4's config dependency will be fixed but it will still be deleted
|
||||
// because it also depends on the node module.
|
||||
$entity_4 = $storage->create(
|
||||
[
|
||||
'id' => 'entity_' . $entity_id_suffixes[3],
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$entity_1->getConfigDependencyName()],
|
||||
'module' => ['node', 'config_test'],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity_4->save();
|
||||
|
||||
// Entity 5 will be fixed because it is dependent on entity 3, which is
|
||||
// unchanged, and entity 1 which will be fixed because
|
||||
// \Drupal\config_test\Entity::onDependencyRemoval() will remove the
|
||||
// dependency.
|
||||
$entity_5 = $storage->create(
|
||||
[
|
||||
'id' => 'entity_' . $entity_id_suffixes[4],
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [
|
||||
$entity_1->getConfigDependencyName(),
|
||||
$entity_3->getConfigDependencyName(),
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity_5->save();
|
||||
|
||||
// Set a more complicated test where dependencies will be fixed.
|
||||
\Drupal::state()->set('config_test.fix_dependencies', [$entity_1->getConfigDependencyName()]);
|
||||
\Drupal::state()->set('config_test.on_dependency_removal_called', []);
|
||||
|
||||
// Do a dry run using
|
||||
// \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
|
||||
$config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
|
||||
|
||||
// Assert that \Drupal\config_test\Entity\ConfigTest::onDependencyRemoval()
|
||||
// is called as expected and with the correct dependencies.
|
||||
$called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
|
||||
$this->assertArrayNotHasKey($entity_3->id(), $called, 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
|
||||
$this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id(), $entity_5->id()], array_keys($called), 'The most dependent entites have ConfigEntityInterface::onDependencyRemoval() called first.');
|
||||
$this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_1->id()]);
|
||||
$this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => [], 'theme' => []], $called[$entity_2->id()]);
|
||||
$this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_4->id()]);
|
||||
$this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => [], 'theme' => []], $called[$entity_5->id()]);
|
||||
|
||||
$this->assertEqual($entity_1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
|
||||
$this->assertEqual($entity_2->uuid(), $config_entities['update'][0]->uuid(), 'Entity 2 will be updated.');
|
||||
$this->assertEqual($entity_3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
|
||||
$this->assertEqual($entity_4->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 4 will be deleted.');
|
||||
$this->assertEqual($entity_5->uuid(), $config_entities['update'][1]->uuid(), 'Entity 5 is updated.');
|
||||
|
||||
// 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();
|
||||
// Perform the uninstall.
|
||||
$config_manager->uninstall('module', 'node');
|
||||
|
||||
// Test that expected actions have been performed.
|
||||
$this->assertFalse($storage->load($entity_1->id()), 'Entity 1 deleted');
|
||||
$entity_2 = $storage->load($entity_2->id());
|
||||
$this->assertTrue($entity_2, 'Entity 2 not deleted');
|
||||
$this->assertEqual($entity_2->calculateDependencies()->getDependencies()['config'], [], 'Entity 2 dependencies updated to remove dependency on entity 1.');
|
||||
$entity_3 = $storage->load($entity_3->id());
|
||||
$this->assertTrue($entity_3, 'Entity 3 not deleted');
|
||||
$this->assertEqual($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
|
||||
$this->assertFalse($storage->load($entity_4->id()), 'Entity 4 deleted');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::uninstall
|
||||
* @covers ::getConfigEntitiesToChangeOnDependencyRemoval
|
||||
*/
|
||||
public function testConfigEntityUninstallThirdParty() {
|
||||
/** @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_type.manager')
|
||||
->getStorage('config_test');
|
||||
// Entity 1 will be fixed because it only has a dependency via third-party
|
||||
// settings, which are fixable.
|
||||
$entity_1 = $storage->create([
|
||||
'id' => 'entity_1',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'module' => ['config_test'],
|
||||
],
|
||||
],
|
||||
'third_party_settings' => [
|
||||
'node' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$entity_1->save();
|
||||
|
||||
// Entity 2 has a dependency on entity 1.
|
||||
$entity_2 = $storage->create([
|
||||
'id' => 'entity_2',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$entity_1->getConfigDependencyName()],
|
||||
],
|
||||
],
|
||||
'third_party_settings' => [
|
||||
'node' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$entity_2->save();
|
||||
|
||||
// Entity 3 will be unchanged because it is dependent on entity 2 which can
|
||||
// be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
|
||||
// not be called for this entity.
|
||||
$entity_3 = $storage->create([
|
||||
'id' => 'entity_3',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$entity_2->getConfigDependencyName()],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$entity_3->save();
|
||||
|
||||
// Entity 4's config dependency will be fixed but it will still be deleted
|
||||
// because it also depends on the node module.
|
||||
$entity_4 = $storage->create([
|
||||
'id' => 'entity_4',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$entity_1->getConfigDependencyName()],
|
||||
'module' => ['node', 'config_test'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$entity_4->save();
|
||||
|
||||
\Drupal::state()->set('config_test.fix_dependencies', []);
|
||||
\Drupal::state()->set('config_test.on_dependency_removal_called', []);
|
||||
|
||||
// Do a dry run using
|
||||
// \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
|
||||
$config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
|
||||
$config_entity_ids = [
|
||||
'update' => [],
|
||||
'delete' => [],
|
||||
'unchanged' => [],
|
||||
];
|
||||
foreach ($config_entities as $type => $config_entities_by_type) {
|
||||
foreach ($config_entities_by_type as $config_entity) {
|
||||
$config_entity_ids[$type][] = $config_entity->id();
|
||||
}
|
||||
}
|
||||
$expected = [
|
||||
'update' => [$entity_1->id(), $entity_2->id()],
|
||||
'delete' => [$entity_4->id()],
|
||||
'unchanged' => [$entity_3->id()],
|
||||
];
|
||||
$this->assertSame($expected, $config_entity_ids);
|
||||
|
||||
$called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
|
||||
$this->assertArrayNotHasKey($entity_3->id(), $called, 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
|
||||
$this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id()], array_keys($called), 'The most dependent entities have ConfigEntityInterface::onDependencyRemoval() called first.');
|
||||
$this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_1->id()]);
|
||||
$this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_2->id()]);
|
||||
$this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_4->id()]);
|
||||
|
||||
// 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();
|
||||
// Perform the uninstall.
|
||||
$config_manager->uninstall('module', 'node');
|
||||
|
||||
// Test that expected actions have been performed.
|
||||
$entity_1 = $storage->load($entity_1->id());
|
||||
$this->assertTrue($entity_1, 'Entity 1 not deleted');
|
||||
$this->assertSame($entity_1->getThirdPartySettings('node'), [], 'Entity 1 third party settings updated.');
|
||||
$entity_2 = $storage->load($entity_2->id());
|
||||
$this->assertTrue($entity_2, 'Entity 2 not deleted');
|
||||
$this->assertSame($entity_2->getThirdPartySettings('node'), [], 'Entity 2 third party settings updated.');
|
||||
$this->assertSame($entity_2->calculateDependencies()->getDependencies()['config'], [$entity_1->getConfigDependencyName()], 'Entity 2 still depends on entity 1.');
|
||||
$entity_3 = $storage->load($entity_3->id());
|
||||
$this->assertTrue($entity_3, 'Entity 3 not deleted');
|
||||
$this->assertSame($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
|
||||
$this->assertFalse($storage->load($entity_4->id()), '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(
|
||||
[
|
||||
'id' => 'entity1',
|
||||
]
|
||||
);
|
||||
$entity1->save();
|
||||
$entity2 = $storage->create(
|
||||
[
|
||||
'id' => 'entity2',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$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', [$entity1->getConfigDependencyName()]);
|
||||
|
||||
// Entity1 will be deleted by the test.
|
||||
$entity1 = $storage->create(
|
||||
[
|
||||
'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(
|
||||
[
|
||||
'id' => 'entity2',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$entity1->getConfigDependencyName()],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity2->save();
|
||||
|
||||
// Entity3 will be unchanged because it is dependent on Entity2 which can
|
||||
// be fixed.
|
||||
$entity3 = $storage->create(
|
||||
[
|
||||
'id' => 'entity3',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$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'], [], '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(
|
||||
[
|
||||
'id' => 'entity1',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'content' => [$content_entity->getConfigDependencyName()],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity1->save();
|
||||
$entity2 = $storage->create(
|
||||
[
|
||||
'id' => 'entity2',
|
||||
'dependencies' => [
|
||||
'enforced' => [
|
||||
'config' => [$entity1->getConfigDependencyName()],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
$entity2->save();
|
||||
|
||||
// Create a configuration entity that is not in the dependency chain.
|
||||
$entity3 = $storage->create(['id' => 'entity3']);
|
||||
$entity3->save();
|
||||
|
||||
$config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('content', [$content_entity->getConfigDependencyName()]);
|
||||
$this->assertEqual($entity1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
|
||||
$this->assertEqual($entity2->uuid(), $config_entities['delete'][0]->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 = [];
|
||||
foreach ($dependents as $dependent) {
|
||||
$dependent_ids[] = $dependent->getEntityTypeId() . ':' . $dependent->id();
|
||||
}
|
||||
return $dependent_ids;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
<?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 = ['config_test', 'system'];
|
||||
|
||||
/**
|
||||
* Tests calculating the difference between two sets of configuration.
|
||||
*/
|
||||
public 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(['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->assertYamlEdit($edits, $change_key, 'change',
|
||||
[$change_key . ': ' . $original_data[$change_key]],
|
||||
[$change_key . ': ' . $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->assertYamlEdit($edits, $change_key, 'copy');
|
||||
$this->assertYamlEdit($edits, $remove_key, 'delete',
|
||||
[$remove_key . ': ' . $original_data[$remove_key]],
|
||||
FALSE
|
||||
);
|
||||
|
||||
// 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->assertYamlEdit($edits, $change_key, 'copy');
|
||||
$this->assertYamlEdit($edits, $add_key, 'add', FALSE, [$add_key . ': ' . $add_data]);
|
||||
|
||||
// Test diffing a renamed config entity.
|
||||
$test_entity_id = $this->randomMachineName();
|
||||
$test_entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([
|
||||
'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->assertYamlEdit($edits, 'uuid', 'copy');
|
||||
$this->assertYamlEdit($edits, 'id', 'change',
|
||||
['id: ' . $new_test_entity_id],
|
||||
['id: ' . $test_entity_id]);
|
||||
$this->assertYamlEdit($edits, 'label', 'copy');
|
||||
$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.
|
||||
*/
|
||||
public 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 = ['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, ['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->assertYamlEdit($edits, 'foo', 'change', ['foo: bar'], ['foo: baz']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to test that an edit is found in a diff'd YAML file.
|
||||
*
|
||||
* @param array $edits
|
||||
* A list of edits.
|
||||
* @param string $field
|
||||
* The field key that is being asserted.
|
||||
* @param string $type
|
||||
* The type of edit that is being asserted.
|
||||
* @param mixed $orig
|
||||
* (optional) The original value of of the edit. If not supplied, assertion
|
||||
* is skipped.
|
||||
* @param mixed $closing
|
||||
* (optional) The closing value of of the edit. If not supplied, assertion
|
||||
* is skipped.
|
||||
*/
|
||||
protected function assertYamlEdit(array $edits, $field, $type, $orig = NULL, $closing = NULL) {
|
||||
$match = FALSE;
|
||||
foreach ($edits as $edit) {
|
||||
// Choose which section to search for the field.
|
||||
$haystack = $type == 'add' ? $edit->closing : $edit->orig;
|
||||
// Look through each line and try and find the key.
|
||||
if (is_array($haystack)) {
|
||||
foreach ($haystack as $item) {
|
||||
if (strpos($item, $field . ':') === 0) {
|
||||
$match = TRUE;
|
||||
// Assert that the edit is of the type specified.
|
||||
$this->assertEqual($edit->type, $type, "The $field item in the diff is a $type");
|
||||
// If an original value was given, assert that it matches.
|
||||
if (isset($orig)) {
|
||||
$this->assertIdentical($edit->orig, $orig, "The original value for key '$field' is correct.");
|
||||
}
|
||||
// If a closing value was given, assert that it matches.
|
||||
if (isset($closing)) {
|
||||
$this->assertIdentical($edit->closing, $closing, "The closing value for key '$field' is correct.");
|
||||
}
|
||||
// Break out of the search entirely.
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't match anything, fail.
|
||||
if (!$match) {
|
||||
$this->fail("$field edit was not matched");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 = ['config_test'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(static::$modules);
|
||||
}
|
||||
|
||||
public function testNormalize() {
|
||||
$config_entity = \Drupal::entityTypeManager()->getStorage('config_test')->create(['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 = [
|
||||
'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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<?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 = ['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(['id' => $this->entityId, 'label' => 'Original label'])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the static cache is working.
|
||||
*/
|
||||
public function testCacheHit() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity_1 = $storage->load($this->entityId);
|
||||
$entity_2 = $storage->load($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->assertSame($entity_1->_loadStamp, $entity_2->_loadStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the static cache is reset on entity save and delete.
|
||||
*/
|
||||
public function testReset() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->load($this->entityId);
|
||||
|
||||
// Ensure loading after a save retrieves the updated entity rather than an
|
||||
// obsolete cached one.
|
||||
$entity->label = 'New label';
|
||||
$entity->save();
|
||||
$entity = $storage->load($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($storage->load($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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 = ['config_test'];
|
||||
|
||||
/**
|
||||
* Tests the enabling/disabling of entities.
|
||||
*/
|
||||
public function testCRUD() {
|
||||
$entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([
|
||||
'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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?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 = ['config_test'];
|
||||
|
||||
/**
|
||||
* Tests creating configuration entities with changed UUIDs.
|
||||
*/
|
||||
public function testUUIDConflict() {
|
||||
$entity_type = 'config_test';
|
||||
$id = 'test_1';
|
||||
// Load the original configuration entity.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
$storage->create(['id' => $id])->save();
|
||||
$entity = $storage->load($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.', ['%e' => $e]));
|
||||
}
|
||||
|
||||
// Ensure that the config entity was not corrupted.
|
||||
$entity = entity_load('config_test', $entity->id(), TRUE);
|
||||
$this->assertIdentical($entity->toArray(), $original_properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the hasData() method for config entity storage.
|
||||
*
|
||||
* @covers \Drupal\Core\Config\Entity\ConfigEntityStorage::hasData
|
||||
*/
|
||||
public function testHasData() {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('config_test');
|
||||
$this->assertFalse($storage->hasData());
|
||||
|
||||
// Add a test config entity and check again.
|
||||
$storage->create(['id' => $this->randomMachineName()])->save();
|
||||
$this->assertTrue($storage->hasData());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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\Development\ConfigSchemaChecker
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['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([
|
||||
'id' => $this->randomMachineName(),
|
||||
'label' => $this->randomString(),
|
||||
'style' => $style,
|
||||
]);
|
||||
$entity->save();
|
||||
}
|
||||
$entity = $this->storage->create([
|
||||
'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->assertSame($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(['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([
|
||||
'id' => $this->randomMachineName(),
|
||||
'label' => $this->randomString(),
|
||||
'style' => 999,
|
||||
]);
|
||||
$entity->save();
|
||||
$this->assertSame('999', $entity->style);
|
||||
$entity->style = 999;
|
||||
$entity->trustData()->save();
|
||||
$this->assertSame(999, $entity->style);
|
||||
$entity->save();
|
||||
$this->assertSame('999', $entity->style);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 = ['config_events_test'];
|
||||
|
||||
/**
|
||||
* Tests configuration events.
|
||||
*/
|
||||
public 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', []), [], 'No events fired by creating a new configuration object');
|
||||
$config->save();
|
||||
|
||||
$event = \Drupal::state()->get('config_events_test.event', []);
|
||||
$this->assertIdentical($event['event_name'], ConfigEvents::SAVE);
|
||||
$this->assertIdentical($event['current_config_data'], ['key' => 'initial']);
|
||||
$this->assertIdentical($event['raw_config_data'], ['key' => 'initial']);
|
||||
$this->assertIdentical($event['original_config_data'], []);
|
||||
|
||||
$config->set('key', 'updated')->save();
|
||||
$event = \Drupal::state()->get('config_events_test.event', []);
|
||||
$this->assertIdentical($event['event_name'], ConfigEvents::SAVE);
|
||||
$this->assertIdentical($event['current_config_data'], ['key' => 'updated']);
|
||||
$this->assertIdentical($event['raw_config_data'], ['key' => 'updated']);
|
||||
$this->assertIdentical($event['original_config_data'], ['key' => 'initial']);
|
||||
|
||||
$config->delete();
|
||||
$event = \Drupal::state()->get('config_events_test.event', []);
|
||||
$this->assertIdentical($event['event_name'], ConfigEvents::DELETE);
|
||||
$this->assertIdentical($event['current_config_data'], []);
|
||||
$this->assertIdentical($event['raw_config_data'], []);
|
||||
$this->assertIdentical($event['original_config_data'], ['key' => 'updated']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuration rename event that is fired from the ConfigFactory.
|
||||
*/
|
||||
public function testConfigRenameEvent() {
|
||||
$name = 'config_events_test.test';
|
||||
$new_name = 'config_events_test.test_rename';
|
||||
$GLOBALS['config'][$name] = ['key' => 'overridden'];
|
||||
$GLOBALS['config'][$new_name] = ['key' => 'new overridden'];
|
||||
|
||||
$config = $this->config($name);
|
||||
$config->set('key', 'initial')->save();
|
||||
$event = \Drupal::state()->get('config_events_test.event', []);
|
||||
$this->assertIdentical($event['event_name'], ConfigEvents::SAVE);
|
||||
$this->assertIdentical($event['current_config_data'], ['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', []);
|
||||
$this->assertIdentical($event['event_name'], ConfigEvents::RENAME);
|
||||
$this->assertIdentical($event['current_config_data'], ['key' => 'new overridden']);
|
||||
$this->assertIdentical($event['raw_config_data'], ['key' => 'initial']);
|
||||
$this->assertIdentical($event['original_config_data'], ['key' => 'new overridden']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
<?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\Development\ConfigSchemaChecker
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* Tests setting, writing, and reading of a configuration setting.
|
||||
*/
|
||||
public 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 = [
|
||||
'foo' => 'bar',
|
||||
'biff' => [
|
||||
'bang' => 'pow',
|
||||
],
|
||||
];
|
||||
$casting_array_key = 'casting_array';
|
||||
$casting_array_false_value_key = 'casting_array.cast.false';
|
||||
$casting_array_value = [
|
||||
'cast' => [
|
||||
'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(), [], '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(FALSE, $config->get($casting_array_false_value_key), "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, [], '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.
|
||||
*/
|
||||
public function testSerialization() {
|
||||
$name = $this->randomMachineName(10) . '.' . $this->randomMachineName(10);
|
||||
$config_data = [
|
||||
// Indexed arrays; the order of elements is essential.
|
||||
'numeric keys' => ['i', 'n', 'd', 'e', 'x', 'e', 'd'],
|
||||
// Infinitely nested keys using arbitrary element names.
|
||||
'nested keys' => [
|
||||
// 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->assertSame($config_data[$key], $config_parsed[$key]);
|
||||
|
||||
$key = 'nested keys';
|
||||
$this->assertSame($config_data[$key], $config_parsed[$key]);
|
||||
|
||||
$key = 'HTML';
|
||||
$this->assertSame($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
|
||||
|
||||
$key = 'UTF-8';
|
||||
$this->assertSame($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
|
||||
|
||||
$key = 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ';
|
||||
$this->assertSame($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
|
||||
|
||||
$key = 'invalid xml';
|
||||
$this->assertSame($config_data[$key], $config_parsed[$key]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config;
|
||||
|
||||
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(['system', '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 = mb_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->assertSame($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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
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(['system', '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 = \Drupal::entityTypeManager()->getStorage('config_test')->create([
|
||||
'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' => mb_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 = [
|
||||
'node.type.' . $content_type->id() . '::config_test.dynamic.' . $test_entity_id,
|
||||
];
|
||||
$renames = $this->configImporter->getUnprocessedConfiguration('rename');
|
||||
$this->assertSame($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 = [
|
||||
new FormattableMarkup('Entity type mismatch on rename. @old_type not equal to @new_type for existing configuration @old_name and staged configuration @new_name.', ['@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 = [
|
||||
'config_test.old::config_test.new',
|
||||
];
|
||||
$renames = $this->configImporter->getUnprocessedConfiguration('rename');
|
||||
$this->assertSame($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 = [
|
||||
new FormattableMarkup('Rename operation for simple configuration. Existing configuration @old_name and staged configuration @new_name.', ['@old_name' => 'config_test.old', '@new_name' => 'config_test.new']),
|
||||
];
|
||||
$this->assertEqual($expected, $this->configImporter->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 = ['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(['system', '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
|
||||
*/
|
||||
public 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(['name' => 'one']);
|
||||
$entity_two = EntityTest::create(['name' => 'two']);
|
||||
$entity_three = EntityTest::create(['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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,862 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* The beginning of an import validation error.
|
||||
*/
|
||||
const FAIL_MESSAGE = 'There were errors validating the config synchronization.';
|
||||
|
||||
/**
|
||||
* Config Importer object used for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
protected $configImporter;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['config_test', 'system', 'config_import_test'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['system', '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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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) {
|
||||
$actual_message = $e->getMessage();
|
||||
|
||||
$actual_error_log = $this->configImporter->getErrors();
|
||||
$expected_error_log = ['Site UUID in source storage does not match the target storage.'];
|
||||
$this->assertEqual($actual_error_log, $expected_error_log);
|
||||
|
||||
$expected = static::FAIL_MESSAGE . PHP_EOL . 'Site UUID in source storage does not match the target storage.';
|
||||
$this->assertEquals($expected, $actual_message);
|
||||
foreach ($expected_error_log as $log_row) {
|
||||
$this->assertTrue(preg_match("/$log_row/", $actual_message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deletion of configuration during import.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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 = [
|
||||
'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651',
|
||||
'langcode' => \Drupal::languageManager()->getDefaultLanguage()->getId(),
|
||||
'status' => TRUE,
|
||||
'dependencies' => [],
|
||||
'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.
|
||||
*/
|
||||
public 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 = [
|
||||
'id' => 'primary',
|
||||
'label' => 'Primary',
|
||||
'weight' => 0,
|
||||
'uuid' => $uuid->generate(),
|
||||
];
|
||||
$sync->write($name_primary, $values_primary);
|
||||
$values_secondary = [
|
||||
'id' => 'secondary',
|
||||
'label' => 'Secondary Sync',
|
||||
'weight' => 0,
|
||||
'uuid' => $uuid->generate(),
|
||||
// Add a dependency on primary, to ensure that is synced first.
|
||||
'dependencies' => [
|
||||
'config' => [$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], new FormattableMarkup('Deleted and replaced configuration entity "@name"', ['@name' => $name_secondary]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that secondary writes are overwritten.
|
||||
*/
|
||||
public 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 = [
|
||||
'id' => 'primary',
|
||||
'label' => 'Primary',
|
||||
'weight' => 0,
|
||||
'uuid' => $uuid->generate(),
|
||||
// Add a dependency on secondary, so that is synced first.
|
||||
'dependencies' => [
|
||||
'config' => [$name_secondary],
|
||||
],
|
||||
];
|
||||
$sync->write($name_primary, $values_primary);
|
||||
$values_secondary = [
|
||||
'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.
|
||||
*/
|
||||
public 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 = [
|
||||
'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 = [
|
||||
'id' => 'deletee',
|
||||
'label' => 'Deletee',
|
||||
'weight' => 0,
|
||||
'uuid' => $uuid->generate(),
|
||||
// Add a dependency on deleter, to make sure that is synced first.
|
||||
'dependencies' => [
|
||||
'config' => [$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 = [
|
||||
'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' => [
|
||||
'config' => [$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 = [
|
||||
$name_deleter,
|
||||
$name_deletee,
|
||||
$name_other,
|
||||
];
|
||||
$this->assertSame($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], new FormattableMarkup('Update target "@name" is missing.', ['@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.
|
||||
*/
|
||||
public 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 = [
|
||||
'id' => 'deleter',
|
||||
'label' => 'Deleter',
|
||||
'weight' => 0,
|
||||
'uuid' => $uuid->generate(),
|
||||
// Add a dependency on deletee, to make sure that is synced first.
|
||||
'dependencies' => [
|
||||
'config' => [$name_deletee],
|
||||
],
|
||||
];
|
||||
$storage->write($name_deleter, $values_deleter);
|
||||
$values_deleter['label'] = 'Updated Deleter';
|
||||
$sync->write($name_deleter, $values_deleter);
|
||||
$values_deletee = [
|
||||
'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.
|
||||
*/
|
||||
public 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 = [
|
||||
'id' => 'deleter',
|
||||
'label' => 'Deleter',
|
||||
'weight' => 0,
|
||||
'uuid' => $uuid->generate(),
|
||||
// Add a dependency on deletee, to make sure this delete is synced first.
|
||||
'dependencies' => [
|
||||
'config' => [$name_deletee],
|
||||
],
|
||||
];
|
||||
$storage->write($name_deleter, $values_deleter);
|
||||
$values_deletee = [
|
||||
'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.
|
||||
*/
|
||||
public 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 = [
|
||||
'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()
|
||||
*/
|
||||
public 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(['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) {
|
||||
$expected = [
|
||||
static::FAIL_MESSAGE,
|
||||
'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.',
|
||||
'Unable to install the <em class="placeholder">Bartik</em> theme since it requires the <em class="placeholder">Stable</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.',
|
||||
];
|
||||
$this->assertEquals(implode(PHP_EOL, $expected), $e->getMessage());
|
||||
$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 multiple 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) {
|
||||
$expected = [
|
||||
static::FAIL_MESSAGE,
|
||||
'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.',
|
||||
'Unable to install the <em class="placeholder">Bartik</em> theme since it requires the <em class="placeholder">Stable</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.',
|
||||
'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.',
|
||||
'Unable to install the <em class="placeholder">Bartik</em> theme since it requires the <em class="placeholder">Stable</em> theme.',
|
||||
'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.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 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.',
|
||||
'Configuration <em class="placeholder">unknown.config</em> depends on the <em class="placeholder">unknown</em> extension that will not be installed after import.',
|
||||
];
|
||||
$this->assertEquals(implode(PHP_EOL, $expected), $e->getMessage());
|
||||
$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) {
|
||||
$expected = static::FAIL_MESSAGE . PHP_EOL . 'The core.extension configuration does not exist.';
|
||||
$this->assertEquals($expected, $e->getMessage());
|
||||
$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) {
|
||||
$expected = static::FAIL_MESSAGE . PHP_EOL . 'Unable to install the <em class="placeholder">standard</em> module since it does not exist.';
|
||||
$this->assertEquals($expected, $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 install profile validation during configuration import.
|
||||
*
|
||||
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
|
||||
*/
|
||||
public function testInstallProfileMisMatch() {
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
|
||||
$extensions = $sync->read('core.extension');
|
||||
// Change the install profile.
|
||||
$extensions['profile'] = 'this_will_not_work';
|
||||
$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) {
|
||||
$expected = static::FAIL_MESSAGE . PHP_EOL . 'Cannot change the install profile from <em class="placeholder"></em> to <em class="placeholder">this_will_not_work</em> once Drupal is installed.';
|
||||
$this->assertEquals($expected, $e->getMessage(), 'There were errors validating the config synchronization.');
|
||||
$error_log = $this->configImporter->getErrors();
|
||||
// Install profiles can not be changed. Note that KernelTestBase currently
|
||||
// does not use an install profile. This situation should be impossible
|
||||
// to get in but site's can removed the install profile setting from
|
||||
// settings.php so the test is valid.
|
||||
$this->assertEqual(['Cannot change the install profile from <em class="placeholder"></em> to <em class="placeholder">this_will_not_work</em> once Drupal is installed.'], $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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the isSyncing flags.
|
||||
*/
|
||||
public function testIsSyncingInHooks() {
|
||||
$dynamic_name = 'config_test.dynamic.dotted.default';
|
||||
$storage = $this->container->get('config.storage');
|
||||
|
||||
// Verify the default configuration values exist.
|
||||
$config = $this->config($dynamic_name);
|
||||
$this->assertSame('dotted.default', $config->get('id'));
|
||||
|
||||
// Delete the config so that create hooks will fire.
|
||||
$storage->delete($dynamic_name);
|
||||
\Drupal::state()->set('config_test.store_isSyncing', []);
|
||||
$this->configImporter->reset()->import();
|
||||
|
||||
// The values of the syncing values should be stored in state by
|
||||
// config_test_config_test_create().
|
||||
$state = \Drupal::state()->get('config_test.store_isSyncing');
|
||||
$this->assertTrue($state['global_state::create'], '\Drupal::isConfigSyncing() returns TRUE');
|
||||
$this->assertTrue($state['entity_state::create'], 'ConfigEntity::isSyncing() returns TRUE');
|
||||
$this->assertTrue($state['global_state::presave'], '\Drupal::isConfigSyncing() returns TRUE');
|
||||
$this->assertTrue($state['entity_state::presave'], 'ConfigEntity::isSyncing() returns TRUE');
|
||||
$this->assertTrue($state['global_state::insert'], '\Drupal::isConfigSyncing() returns TRUE');
|
||||
$this->assertTrue($state['entity_state::insert'], 'ConfigEntity::isSyncing() returns TRUE');
|
||||
|
||||
// Cause a config update so update hooks will fire.
|
||||
$config = $this->config($dynamic_name);
|
||||
$config->set('label', 'A new name')->save();
|
||||
\Drupal::state()->set('config_test.store_isSyncing', []);
|
||||
$this->configImporter->reset()->import();
|
||||
|
||||
// The values of the syncing values should be stored in state by
|
||||
// config_test_config_test_create().
|
||||
$state = \Drupal::state()->get('config_test.store_isSyncing');
|
||||
$this->assertTrue($state['global_state::presave'], '\Drupal::isConfigSyncing() returns TRUE');
|
||||
$this->assertTrue($state['entity_state::presave'], 'ConfigEntity::isSyncing() returns TRUE');
|
||||
$this->assertTrue($state['global_state::update'], '\Drupal::isConfigSyncing() returns TRUE');
|
||||
$this->assertTrue($state['entity_state::update'], 'ConfigEntity::isSyncing() returns TRUE');
|
||||
|
||||
// Cause a config delete so delete hooks will fire.
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$sync->delete($dynamic_name);
|
||||
\Drupal::state()->set('config_test.store_isSyncing', []);
|
||||
$this->configImporter->reset()->import();
|
||||
|
||||
// The values of the syncing values should be stored in state by
|
||||
// config_test_config_test_create().
|
||||
$state = \Drupal::state()->get('config_test.store_isSyncing');
|
||||
$this->assertTrue($state['global_state::predelete'], '\Drupal::isConfigSyncing() returns TRUE');
|
||||
$this->assertTrue($state['entity_state::predelete'], 'ConfigEntity::isSyncing() returns TRUE');
|
||||
$this->assertTrue($state['global_state::delete'], '\Drupal::isConfigSyncing() returns TRUE');
|
||||
$this->assertTrue($state['entity_state::delete'], 'ConfigEntity::isSyncing() returns TRUE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the isConfigSyncing flag is cleanup after an invalid step.
|
||||
*/
|
||||
public function testInvalidStep() {
|
||||
$this->assertFalse(\Drupal::isConfigSyncing(), 'Before an import \Drupal::isConfigSyncing() returns FALSE');
|
||||
$context = [];
|
||||
try {
|
||||
$this->configImporter->doSyncStep('a_non_existent_step', $context);
|
||||
$this->fail('Expected \InvalidArgumentException thrown');
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
$this->pass('Expected \InvalidArgumentException thrown');
|
||||
}
|
||||
$this->assertFalse(\Drupal::isConfigSyncing(), 'After an invalid step \Drupal::isConfigSyncing() returns FALSE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the isConfigSyncing flag is set correctly during a custom step.
|
||||
*/
|
||||
public function testCustomStep() {
|
||||
$this->assertFalse(\Drupal::isConfigSyncing(), 'Before an import \Drupal::isConfigSyncing() returns FALSE');
|
||||
$context = [];
|
||||
$this->configImporter->doSyncStep([self::class, 'customStep'], $context);
|
||||
$this->assertTrue($context['is_syncing'], 'Inside a custom step \Drupal::isConfigSyncing() returns TRUE');
|
||||
$this->assertFalse(\Drupal::isConfigSyncing(), 'After an valid custom step \Drupal::isConfigSyncing() returns FALSE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to test custom config installer steps.
|
||||
*
|
||||
* @param array $context
|
||||
* Batch context.
|
||||
* @param \Drupal\Core\Config\ConfigImporter $importer
|
||||
* The config importer.
|
||||
*/
|
||||
public static function customStep(array &$context, ConfigImporter $importer) {
|
||||
$context['is_syncing'] = \Drupal::isConfigSyncing();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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(['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(['config_schema_test']);
|
||||
$this->installConfig(['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', [])->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(['config_collection_install_test']);
|
||||
$this->installConfig(['config_collection_install_test']);
|
||||
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
|
||||
$active_storage = \Drupal::service('config.storage');
|
||||
$this->assertEqual([], $active_storage->getAllCollectionNames());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests config objects in collections are installed as expected.
|
||||
*/
|
||||
public function testCollectionInstallationCollections() {
|
||||
$collections = [
|
||||
'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(['config_collection_install_test']);
|
||||
$this->installConfig(['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(['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->assertSame(['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(['entity'], $active_storage->getAllCollectionNames());
|
||||
\Drupal::service('config.manager')->uninstall('module', 'config_test');
|
||||
$this->assertEqual([], $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 = [
|
||||
'entity',
|
||||
];
|
||||
\Drupal::state()->set('config_collection_install_test.collection_names', $collections);
|
||||
// Install the test module.
|
||||
$this->installModules(['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->assertSame(['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' => ['config_other_module_config_test', 'config_test.dynamic.dotted.english']]);
|
||||
$this->assertEqual($e->getMessage(), 'Configuration objects provided by <em class="placeholder">config_install_dependency_test</em> have unmet dependencies: <em class="placeholder">config_test.dynamic.other_module_test_with_dependency (config_other_module_config_test, config_test.dynamic.dotted.english)</em>');
|
||||
}
|
||||
try {
|
||||
$this->installModules(['config_install_double_dependency_test']);
|
||||
$this->fail('Expected UnmetDependenciesException not thrown.');
|
||||
}
|
||||
catch (UnmetDependenciesException $e) {
|
||||
$this->assertEquals('config_install_double_dependency_test', $e->getExtension());
|
||||
$this->assertEquals(['config_test.dynamic.other_module_test_with_dependency' => ['config_other_module_config_test', 'config_test.dynamic.dotted.english']], $e->getConfigObjects());
|
||||
$this->assertEquals('Configuration objects provided by <em class="placeholder">config_install_double_dependency_test</em> have unmet dependencies: <em class="placeholder">config_test.dynamic.other_module_test_with_dependency (config_other_module_config_test, config_test.dynamic.dotted.english)</em>', $e->getMessage());
|
||||
}
|
||||
$this->installModules(['config_test_language']);
|
||||
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' => ['config_other_module_config_test']]);
|
||||
$this->assertEqual($e->getMessage(), 'Configuration objects provided by <em class="placeholder">config_install_dependency_test</em> have unmet dependencies: <em class="placeholder">config_test.dynamic.other_module_test_with_dependency (config_other_module_config_test)</em>');
|
||||
}
|
||||
$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->assertSame('config_install_dependency_test', $entity->getDependencies()['module'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests imported configuration entities with and without language information.
|
||||
*/
|
||||
public 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'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests installing configuration where the filename and ID do not match.
|
||||
*/
|
||||
public function testIdMisMatch() {
|
||||
$this->setExpectedException(\PHPUnit_Framework_Error_Warning::class, 'The configuration name "config_test.dynamic.no_id_match" does not match the ID "does_not_match"');
|
||||
$this->installModules(['config_test_id_mismatch']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 = ['user', 'language', 'config_test', 'system', 'field'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['config_test']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale override based on language.
|
||||
*/
|
||||
public 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', ['key' => 'original'])
|
||||
->set('label', 'Original')
|
||||
->save();
|
||||
\Drupal::languageManager()
|
||||
->getLanguageConfigOverride('de', 'config_test.foo')
|
||||
->set('value', ['key' => 'override'])
|
||||
->set('label', 'Override')
|
||||
->save();
|
||||
\Drupal::languageManager()
|
||||
->getLanguageConfigOverride('fr', 'config_test.foo')
|
||||
->set('value', ['key' => 'override'])
|
||||
->save();
|
||||
\Drupal::configFactory()->clearStaticCache();
|
||||
$config = \Drupal::config('config_test.foo');
|
||||
$this->assertIdentical($config->get('value'), ['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'), ['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'), ['key' => 'override']);
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'config_test.bar');
|
||||
$this->assertFalse($override->isNew());
|
||||
$this->assertEqual($override->get('value'), ['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'), []);
|
||||
$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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?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 = ['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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<?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 = ['system', 'config_test'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['system']);
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuration override.
|
||||
*/
|
||||
public function testConfOverride() {
|
||||
$expected_original_data = [
|
||||
'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(['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 = [
|
||||
'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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<?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 = ['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([
|
||||
'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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,746 @@
|
|||
<?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\Config\Schema\Ignore;
|
||||
use Drupal\Core\Config\Schema\Mapping;
|
||||
use Drupal\Core\Config\Schema\Undefined;
|
||||
use Drupal\Core\TypedData\Plugin\DataType\StringData;
|
||||
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 = ['system', 'language', 'field', 'image', 'config_test', 'config_schema_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['system', 'image', 'config_schema_test']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basic metadata retrieval layer.
|
||||
*/
|
||||
public function testSchemaMapping() {
|
||||
// Nonexistent configuration key will have Undefined as metadata.
|
||||
$this->assertSame(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 = [];
|
||||
$expected['label'] = 'Undefined';
|
||||
$expected['class'] = Undefined::class;
|
||||
$expected['type'] = 'undefined';
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for nonexistent configuration.');
|
||||
|
||||
// Configuration file without schema will return Undefined as well.
|
||||
$this->assertSame(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->assertSame(TRUE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'));
|
||||
$definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.someschema');
|
||||
$expected = [];
|
||||
$expected['label'] = 'Schema test data';
|
||||
$expected['class'] = Mapping::class;
|
||||
$expected['mapping']['langcode']['type'] = 'string';
|
||||
$expected['mapping']['langcode']['label'] = 'Language code';
|
||||
$expected['mapping']['_core']['type'] = '_core_config_info';
|
||||
$expected['mapping']['testitem'] = ['label' => 'Test item'];
|
||||
$expected['mapping']['testlist'] = ['label' => 'Test list'];
|
||||
$expected['type'] = 'config_schema_test.someschema';
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$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 = [];
|
||||
$expected['label'] = 'Test item';
|
||||
$expected['class'] = Undefined::class;
|
||||
$expected['type'] = 'undefined';
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$this->assertEqual($definition, $expected, 'Automatic type detected for a scalar is undefined.');
|
||||
$definition = $config->get('testlist')->getDataDefinition()->toArray();
|
||||
$expected = [];
|
||||
$expected['label'] = 'Test list';
|
||||
$expected['class'] = Undefined::class;
|
||||
$expected['type'] = 'undefined';
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$this->assertEqual($definition, $expected, 'Automatic type detected for a list is undefined.');
|
||||
$definition = $config->get('testnoschema')->getDataDefinition()->toArray();
|
||||
$expected = [];
|
||||
$expected['label'] = 'Undefined';
|
||||
$expected['class'] = Undefined::class;
|
||||
$expected['type'] = 'undefined';
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$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 = [];
|
||||
$expected['label'] = 'Maintenance mode';
|
||||
$expected['class'] = Mapping::class;
|
||||
$expected['mapping']['message'] = [
|
||||
'label' => 'Message to display when in maintenance mode',
|
||||
'type' => 'text',
|
||||
];
|
||||
$expected['mapping']['langcode'] = [
|
||||
'label' => 'Language code',
|
||||
'type' => 'string',
|
||||
];
|
||||
$expected['mapping']['_core']['type'] = '_core_config_info';
|
||||
$expected['type'] = 'system.maintenance';
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$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 = [];
|
||||
$expected['label'] = 'Ignore test';
|
||||
$expected['class'] = Mapping::class;
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
|
||||
$expected['mapping']['langcode'] = [
|
||||
'type' => 'string',
|
||||
'label' => 'Language code',
|
||||
];
|
||||
$expected['mapping']['_core']['type'] = '_core_config_info';
|
||||
$expected['mapping']['label'] = [
|
||||
'label' => 'Label',
|
||||
'type' => 'label',
|
||||
];
|
||||
$expected['mapping']['irrelevant'] = [
|
||||
'label' => 'Irrelevant',
|
||||
'type' => 'ignore',
|
||||
];
|
||||
$expected['mapping']['indescribable'] = [
|
||||
'label' => 'Indescribable',
|
||||
'type' => 'ignore',
|
||||
];
|
||||
$expected['mapping']['weight'] = [
|
||||
'label' => 'Weight',
|
||||
'type' => 'integer',
|
||||
];
|
||||
$expected['type'] = 'config_schema_test.ignore';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
|
||||
$this->assertEqual($definition, $expected);
|
||||
|
||||
// The ignore elements themselves.
|
||||
$definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('irrelevant')->getDataDefinition()->toArray();
|
||||
$expected = [];
|
||||
$expected['type'] = 'ignore';
|
||||
$expected['label'] = 'Irrelevant';
|
||||
$expected['class'] = Ignore::class;
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$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 = [];
|
||||
$expected['label'] = 'Image style';
|
||||
$expected['class'] = Mapping::class;
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$expected['mapping']['name']['type'] = 'string';
|
||||
$expected['mapping']['uuid']['type'] = 'uuid';
|
||||
$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'] = 'uuid';
|
||||
$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 = [];
|
||||
$expected['label'] = 'Image scale';
|
||||
$expected['class'] = Mapping::class;
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$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 = [];
|
||||
$expected['type'] = 'config_test.dynamic.*.third_party.config_schema_test';
|
||||
$expected['label'] = 'Mapping';
|
||||
$expected['class'] = Mapping::class;
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$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 = [];
|
||||
$expected['label'] = 'Schema multiple filesystem marker test';
|
||||
$expected['class'] = Mapping::class;
|
||||
$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';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
|
||||
$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.
|
||||
*/
|
||||
public 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 = [
|
||||
'type' => 'config_schema_test.someschema.with_parents.key_1',
|
||||
'label' => 'Test item nested one level',
|
||||
'class' => StringData::class,
|
||||
'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
|
||||
'unwrap_for_canonical_representation' => TRUE,
|
||||
];
|
||||
$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 = [
|
||||
'type' => 'config_schema_test.someschema.with_parents.key_2',
|
||||
'label' => 'Test item nested two levels',
|
||||
'class' => StringData::class,
|
||||
'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
|
||||
'unwrap_for_canonical_representation' => TRUE,
|
||||
];
|
||||
$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 = [
|
||||
'type' => 'config_schema_test.someschema.with_parents.key_3',
|
||||
'label' => 'Test item nested three levels',
|
||||
'class' => StringData::class,
|
||||
'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
|
||||
'unwrap_for_canonical_representation' => TRUE,
|
||||
];
|
||||
$this->assertEqual($definition, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests metadata applied to configuration objects.
|
||||
*/
|
||||
public 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 = [
|
||||
'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' => [
|
||||
'string' => 1,
|
||||
],
|
||||
'float' => '3.14',
|
||||
'null_float' => '',
|
||||
'sequence' => [1, 0, 1],
|
||||
'sequence_bc' => [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 = [
|
||||
'string' => '1',
|
||||
'empty_string' => '',
|
||||
'null_string' => NULL,
|
||||
'integer' => 100,
|
||||
'null_integer' => NULL,
|
||||
'boolean' => TRUE,
|
||||
'no_type' => 1,
|
||||
'mapping' => [
|
||||
'string' => '1',
|
||||
],
|
||||
'float' => 3.14,
|
||||
'null_float' => NULL,
|
||||
'sequence' => [TRUE, FALSE, TRUE],
|
||||
'sequence_bc' => [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 configuration sequence sorting using schemas.
|
||||
*/
|
||||
public function testConfigSaveWithSequenceSorting() {
|
||||
$data = [
|
||||
'keyed_sort' => [
|
||||
'b' => '1',
|
||||
'a' => '2',
|
||||
],
|
||||
'no_sort' => [
|
||||
'b' => '2',
|
||||
'a' => '1',
|
||||
],
|
||||
];
|
||||
// Save config which has a schema that enforces sorting.
|
||||
$this->config('config_schema_test.schema_sequence_sort')
|
||||
->setData($data)
|
||||
->save();
|
||||
$this->assertSame(['a' => '2', 'b' => '1'], $this->config('config_schema_test.schema_sequence_sort')->get('keyed_sort'));
|
||||
$this->assertSame(['b' => '2', 'a' => '1'], $this->config('config_schema_test.schema_sequence_sort')->get('no_sort'));
|
||||
|
||||
$data = [
|
||||
'value_sort' => ['b', 'a'],
|
||||
'no_sort' => ['b', 'a'],
|
||||
];
|
||||
// Save config which has a schema that enforces sorting.
|
||||
$this->config('config_schema_test.schema_sequence_sort')
|
||||
->setData($data)
|
||||
->save();
|
||||
|
||||
$this->assertSame(['a', 'b'], $this->config('config_schema_test.schema_sequence_sort')->get('value_sort'));
|
||||
$this->assertSame(['b', 'a'], $this->config('config_schema_test.schema_sequence_sort')->get('no_sort'));
|
||||
|
||||
// Value sort does not preserve keys - this is intentional.
|
||||
$data = [
|
||||
'value_sort' => [1 => 'b', 2 => 'a'],
|
||||
'no_sort' => [1 => 'b', 2 => 'a'],
|
||||
];
|
||||
// Save config which has a schema that enforces sorting.
|
||||
$this->config('config_schema_test.schema_sequence_sort')
|
||||
->setData($data)
|
||||
->save();
|
||||
|
||||
$this->assertSame(['a', 'b'], $this->config('config_schema_test.schema_sequence_sort')->get('value_sort'));
|
||||
$this->assertSame([1 => 'b', 2 => 'a'], $this->config('config_schema_test.schema_sequence_sort')->get('no_sort'));
|
||||
|
||||
// Test sorts do not destroy complex values.
|
||||
$data = [
|
||||
'complex_sort_value' => [['foo' => 'b', 'bar' => 'b'] , ['foo' => 'a', 'bar' => 'a']],
|
||||
'complex_sort_key' => ['b' => ['foo' => '1', 'bar' => '1'] , 'a' => ['foo' => '2', 'bar' => '2']],
|
||||
];
|
||||
$this->config('config_schema_test.schema_sequence_sort')
|
||||
->setData($data)
|
||||
->save();
|
||||
$this->assertSame([['foo' => 'a', 'bar' => 'a'], ['foo' => 'b', 'bar' => 'b']], $this->config('config_schema_test.schema_sequence_sort')->get('complex_sort_value'));
|
||||
$this->assertSame(['a' => ['foo' => '2', 'bar' => '2'], 'b' => ['foo' => '1', 'bar' => '1']], $this->config('config_schema_test.schema_sequence_sort')->get('complex_sort_key'));
|
||||
|
||||
// Swap the previous test scenario around.
|
||||
$data = [
|
||||
'complex_sort_value' => ['b' => ['foo' => '1', 'bar' => '1'] , 'a' => ['foo' => '2', 'bar' => '2']],
|
||||
'complex_sort_key' => [['foo' => 'b', 'bar' => 'b'] , ['foo' => 'a', 'bar' => 'a']],
|
||||
];
|
||||
$this->config('config_schema_test.schema_sequence_sort')
|
||||
->setData($data)
|
||||
->save();
|
||||
$this->assertSame([['foo' => '1', 'bar' => '1'], ['foo' => '2', 'bar' => '2']], $this->config('config_schema_test.schema_sequence_sort')->get('complex_sort_value'));
|
||||
$this->assertSame([['foo' => 'b', 'bar' => 'b'], ['foo' => 'a', 'bar' => 'a']], $this->config('config_schema_test.schema_sequence_sort')->get('complex_sort_key'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests fallback to a greedy wildcard.
|
||||
*/
|
||||
public 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 = [];
|
||||
$expected['label'] = 'Schema wildcard fallback test';
|
||||
$expected['class'] = Mapping::class;
|
||||
$expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
|
||||
$expected['unwrap_for_canonical_representation'] = TRUE;
|
||||
$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->assertSame($definition, $definition2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests use of colons in schema type determination.
|
||||
*
|
||||
* @see \Drupal\Core\Config\TypedConfigManager::getFallbackName()
|
||||
*/
|
||||
public 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');
|
||||
|
||||
$typed_values = [
|
||||
'tests' => [
|
||||
[
|
||||
'id' => 'cat:persion.dog',
|
||||
'foo' => 'cat',
|
||||
'bar' => 'dog',
|
||||
'breed' => 'persion',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.other_double_brackets')
|
||||
->setData($typed_values)
|
||||
->save();
|
||||
$tests = \Drupal::service('config.typed')->get('wrapping.config_schema_test.other_double_brackets')->get('tests')->getElements();
|
||||
$definition = $tests[0]->getDataDefinition()->toArray();
|
||||
// Check that definition type is a merge of the expected types.
|
||||
$this->assertEqual($definition['type'], 'wrapping.test.other_double_brackets.*||test.double_brackets.cat:*.*');
|
||||
// Check that breed was inherited from parent definition.
|
||||
$this->assertEqual($definition['mapping']['breed'], ['type' => 'string']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?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 = ['config_test', 'system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['system']);
|
||||
// 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.
|
||||
*/
|
||||
public 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(['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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config;
|
||||
|
||||
use Drupal\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 = ['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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Config\Entity\ConfigEntityUpdater.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Core\Config\Entity\ConfigEntityUpdater
|
||||
* @group config
|
||||
*/
|
||||
class ConfigEntityUpdaterTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['config_test'];
|
||||
|
||||
/**
|
||||
* @covers ::update
|
||||
*/
|
||||
public function testUpdate() {
|
||||
// Create some entities to update.
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('config_test');
|
||||
for ($i = 0; $i < 15; $i++) {
|
||||
$entity_id = 'config_test_' . $i;
|
||||
$storage->create(['id' => $entity_id, 'label' => $entity_id])->save();
|
||||
}
|
||||
|
||||
// Set up the updater.
|
||||
$sandbox = [];
|
||||
$settings = Settings::getInstance() ? Settings::getAll() : [];
|
||||
$settings['entity_update_batch_size'] = 10;
|
||||
new Settings($settings);
|
||||
$updater = $this->container->get('class_resolver')->getInstanceFromDefinition(ConfigEntityUpdater::class);
|
||||
|
||||
$callback = function ($config_entity) {
|
||||
/** @var \Drupal\config_test\Entity\ConfigTest $config_entity */
|
||||
$number = (int) str_replace('config_test_', '', $config_entity->id());
|
||||
// Only update even numbered entities.
|
||||
if ($number % 2 == 0) {
|
||||
$config_entity->set('label', $config_entity->label . ' (updated)');
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
};
|
||||
|
||||
// This should run against the first 10 entities. The even numbered labels
|
||||
// will have been updated.
|
||||
$updater->update($sandbox, 'config_test', $callback);
|
||||
$entities = $storage->loadMultiple();
|
||||
$this->assertEquals('config_test_8 (updated)', $entities['config_test_8']->label());
|
||||
$this->assertEquals('config_test_9', $entities['config_test_9']->label());
|
||||
$this->assertEquals('config_test_10', $entities['config_test_10']->label());
|
||||
$this->assertEquals('config_test_14', $entities['config_test_14']->label());
|
||||
$this->assertEquals(15, $sandbox['config_entity_updater:config_test']['count']);
|
||||
$this->assertCount(5, $sandbox['config_entity_updater:config_test']['entities']);
|
||||
$this->assertEquals(10 / 15, $sandbox['#finished']);
|
||||
|
||||
// Update the rest.
|
||||
$updater->update($sandbox, 'config_test', $callback);
|
||||
$entities = $storage->loadMultiple();
|
||||
$this->assertEquals('config_test_8 (updated)', $entities['config_test_8']->label());
|
||||
$this->assertEquals('config_test_9', $entities['config_test_9']->label());
|
||||
$this->assertEquals('config_test_10 (updated)', $entities['config_test_10']->label());
|
||||
$this->assertEquals('config_test_14 (updated)', $entities['config_test_14']->label());
|
||||
$this->assertEquals(1, $sandbox['#finished']);
|
||||
$this->assertCount(0, $sandbox['config_entity_updater:config_test']['entities']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::update
|
||||
*/
|
||||
public function testUpdateDefaultCallback() {
|
||||
// Create some entities to update.
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('config_test');
|
||||
for ($i = 0; $i < 15; $i++) {
|
||||
$entity_id = 'config_test_' . $i;
|
||||
$storage->create(['id' => $entity_id, 'label' => $entity_id])->save();
|
||||
}
|
||||
|
||||
// Set up the updater.
|
||||
$sandbox = [];
|
||||
$settings = Settings::getInstance() ? Settings::getAll() : [];
|
||||
$settings['entity_update_batch_size'] = 9;
|
||||
new Settings($settings);
|
||||
$updater = $this->container->get('class_resolver')->getInstanceFromDefinition(ConfigEntityUpdater::class);
|
||||
// Cause a dependency to be added during an update.
|
||||
\Drupal::state()->set('config_test_new_dependency', 'added_dependency');
|
||||
|
||||
// This should run against the first 10 entities.
|
||||
$updater->update($sandbox, 'config_test');
|
||||
$entities = $storage->loadMultiple();
|
||||
$this->assertEquals(['added_dependency'], $entities['config_test_7']->getDependencies()['module']);
|
||||
$this->assertEquals(['added_dependency'], $entities['config_test_8']->getDependencies()['module']);
|
||||
$this->assertEquals([], $entities['config_test_9']->getDependencies());
|
||||
$this->assertEquals([], $entities['config_test_14']->getDependencies());
|
||||
$this->assertEquals(15, $sandbox['config_entity_updater:config_test']['count']);
|
||||
$this->assertCount(6, $sandbox['config_entity_updater:config_test']['entities']);
|
||||
$this->assertEquals(9 / 15, $sandbox['#finished']);
|
||||
|
||||
// Update the rest.
|
||||
$updater->update($sandbox, 'config_test');
|
||||
$entities = $storage->loadMultiple();
|
||||
$this->assertEquals(['added_dependency'], $entities['config_test_9']->getDependencies()['module']);
|
||||
$this->assertEquals(['added_dependency'], $entities['config_test_14']->getDependencies()['module']);
|
||||
$this->assertEquals(1, $sandbox['#finished']);
|
||||
$this->assertCount(0, $sandbox['config_entity_updater:config_test']['entities']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::update
|
||||
*/
|
||||
public function testUpdateException() {
|
||||
$this->enableModules(['entity_test']);
|
||||
$this->setExpectedException(\InvalidArgumentException::class, 'The provided entity type ID \'entity_test_mul_changed\' is not a configuration entity type');
|
||||
$updater = $this->container->get('class_resolver')->getInstanceFromDefinition(ConfigEntityUpdater::class);
|
||||
$sandbox = [];
|
||||
$updater->update($sandbox, 'entity_test_mul_changed');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?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 = ['config_test', 'config_schema_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(['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 = ['new_key' => 'new_value', 'new_array' => []] + $config_data;
|
||||
$config_data['boolean'] = [];
|
||||
$ret = $this->checkConfigSchema($this->typedConfig, 'config_test.types', $config_data);
|
||||
$expected = [
|
||||
'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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\Traits\Core\Config\SchemaConfigListenerTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the functionality of ConfigSchemaChecker in KernelTestBase tests.
|
||||
*
|
||||
* @group config
|
||||
*/
|
||||
class SchemaConfigListenerTest extends KernelTestBase {
|
||||
|
||||
use SchemaConfigListenerTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['config_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Install configuration provided by the module so that the order of the
|
||||
// config keys is the same as
|
||||
// \Drupal\FunctionalTests\Core\Config\SchemaConfigListenerTest.
|
||||
$this->installConfig(['config_test']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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 = ['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 = [
|
||||
'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, []);
|
||||
|
||||
// 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 = ['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, []);
|
||||
|
||||
// Getting all collections on a non-existing storage bin return an empty
|
||||
// array.
|
||||
$this->assertSame([], $this->invalidStorage->getAllCollectionNames());
|
||||
|
||||
// Writing to a non-existing storage bin creates the bin.
|
||||
$this->invalidStorage->write($name, ['foo' => 'bar']);
|
||||
$result = $this->invalidStorage->read($name);
|
||||
$this->assertIdentical($result, ['foo' => 'bar']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests storage writing and reading data preserving data type.
|
||||
*/
|
||||
public function testDataTypes() {
|
||||
$name = 'config_test.types';
|
||||
$data = [
|
||||
'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 = ['foo' => 'bar'];
|
||||
$result = $this->storage->write($name, $data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertSame($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([], $new_storage->listAll());
|
||||
$new_storage->write($name, $data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertSame($data, $new_storage->read($name));
|
||||
$this->assertEqual([$name], $new_storage->listAll());
|
||||
$this->assertTrue($new_storage->exists($name));
|
||||
$new_data = ['foo' => 'baz'];
|
||||
$new_storage->write($name, $new_data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertSame($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([], $another_storage->listAll());
|
||||
$another_storage->write($name, $new_data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertSame($new_data, $another_storage->read($name));
|
||||
$this->assertEqual([$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->assertSame($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->assertSame($data, $this->storage->read($name));
|
||||
|
||||
// Check that the getAllCollectionNames() method works.
|
||||
$this->assertSame(['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->assertSame(['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([], $parent_storage->listAll());
|
||||
$parent_storage->write($name, $new_data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertSame($new_data, $parent_storage->read($name));
|
||||
$this->assertEqual([$name], $parent_storage->listAll());
|
||||
$this->assertTrue($parent_storage->exists($name));
|
||||
$this->assertSame(['collection', 'collection.sub.another', 'collection.sub.new'], $this->storage->getAllCollectionNames());
|
||||
$parent_storage->deleteAll();
|
||||
$this->assertSame(['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->assertSame($data, $this->storage->read($name));
|
||||
$this->storage->delete($name);
|
||||
$this->assertSame(['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);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?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', []);
|
||||
}
|
||||
|
||||
protected function read($name) {
|
||||
$data = db_query('SELECT data FROM {config} WHERE name = :name', [':name' => $name])->fetchField();
|
||||
return unserialize($data);
|
||||
}
|
||||
|
||||
protected function insert($name, $data) {
|
||||
db_insert('config')->fields(['name' => $name, 'data' => $data])->execute();
|
||||
}
|
||||
|
||||
protected function update($name, $data) {
|
||||
db_update('config')->fields(['data' => $data])->condition('name', $name)->execute();
|
||||
}
|
||||
|
||||
protected function delete($name) {
|
||||
db_delete('config')->condition('name', $name)->execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config\Storage;
|
||||
|
||||
use Drupal\Core\Config\FileStorage;
|
||||
use Drupal\Core\Config\UnsupportedDataTypeConfigException;
|
||||
use Drupal\Core\Serialization\Yaml;
|
||||
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', ['module' => []]);
|
||||
}
|
||||
|
||||
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 = [
|
||||
'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->assertSame(['system.performance'], $this->storage->listAll('system'), 'The FileStorage::listAll() with prefix works.');
|
||||
$this->assertSame([], $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.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Config\Storage;
|
||||
|
||||
use Drupal\config\StorageReplaceDataWrapper;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
|
||||
/**
|
||||
* Tests StorageReplaceDataWrapper operations.
|
||||
*
|
||||
* @group config
|
||||
*/
|
||||
class StorageReplaceDataWrapperTest extends ConfigStorageTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->storage = new StorageReplaceDataWrapper($this->container->get('config.storage'));
|
||||
// ::listAll() verifications require other configuration data to exist.
|
||||
$this->storage->write('system.performance', []);
|
||||
$this->storage->replaceData('system.performance', ['foo' => 'bar']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function read($name) {
|
||||
return $this->storage->read($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function insert($name, $data) {
|
||||
$this->storage->write($name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function update($name, $data) {
|
||||
$this->storage->write($name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function delete($name) {
|
||||
$this->storage->delete($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testInvalidStorage() {
|
||||
// No-op as this test does not make sense.
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if new collections created correctly.
|
||||
*
|
||||
* @param string $collection
|
||||
* The collection name.
|
||||
*
|
||||
* @dataProvider providerCollections
|
||||
*/
|
||||
public function testCreateCollection($collection) {
|
||||
$initial_collection_name = $this->storage->getCollectionName();
|
||||
|
||||
// Create new storage with given collection and check it is set correctly.
|
||||
$new_storage = $this->storage->createCollection($collection);
|
||||
$this->assertSame($collection, $new_storage->getCollectionName());
|
||||
|
||||
// Check collection not changed in the current storage instance.
|
||||
$this->assertSame($initial_collection_name, $this->storage->getCollectionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testing different collections.
|
||||
*
|
||||
* @return array
|
||||
* Returns an array of collection names.
|
||||
*/
|
||||
public function providerCollections() {
|
||||
return [
|
||||
[StorageInterface::DEFAULT_COLLECTION],
|
||||
['foo.bar'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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().
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testConcatLiterals() {
|
||||
$result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', [
|
||||
':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.
|
||||
*
|
||||
* We use 'job' and 'age' fields from the {test} table. Using the 'name' field
|
||||
* for concatenation causes issues with custom or contrib database drivers,
|
||||
* since its type 'varchar_ascii' may lead to using field-level collations not
|
||||
* compatible with the other fields.
|
||||
*/
|
||||
public function testConcatFields() {
|
||||
$result = $this->connection->query(
|
||||
'SELECT CONCAT(:a1, CONCAT(job, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', [
|
||||
':a1' => 'The age of ',
|
||||
':a2' => ' is ',
|
||||
':a3' => '.',
|
||||
':age' => 25,
|
||||
]
|
||||
);
|
||||
$this->assertSame('The age of Singer is 25.', $result->fetchField(), 'Field CONCAT works.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string concatenation with separator.
|
||||
*/
|
||||
public function testConcatWsLiterals() {
|
||||
$result = db_query("SELECT CONCAT_WS(', ', :a1, NULL, :a2, :a3, :a4)", [
|
||||
':a1' => 'Hello',
|
||||
':a2' => NULL,
|
||||
':a3' => '',
|
||||
':a4' => 'world.',
|
||||
]);
|
||||
$this->assertIdentical($result->fetchField(), 'Hello, , world.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string concatenation with separator, with field values.
|
||||
*/
|
||||
public function testConcatWsFields() {
|
||||
$result = db_query("SELECT CONCAT_WS('-', :a1, name, :a2, age) FROM {test} WHERE age = :age", [
|
||||
':a1' => 'name',
|
||||
':a2' => 'age',
|
||||
':age' => 25,
|
||||
]);
|
||||
$this->assertIdentical($result->fetchField(), 'name-John-age-25');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests escaping of LIKE wildcards.
|
||||
*/
|
||||
public function testLikeEscape() {
|
||||
db_insert('test')
|
||||
->fields([
|
||||
'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.
|
||||
*/
|
||||
public function testLikeBackslash() {
|
||||
db_insert('test')
|
||||
->fields(['name'])
|
||||
->values([
|
||||
'name' => 'abcde\f',
|
||||
])
|
||||
->values([
|
||||
'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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
/**
|
||||
* Tests handling case sensitive collation.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class CaseSensitivityTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests BINARY collation in MySQL.
|
||||
*/
|
||||
public function testCaseSensitiveInsert() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
db_insert('test')
|
||||
->fields([
|
||||
// A record already exists with name 'John'.
|
||||
'name' => 'john',
|
||||
'age' => 2,
|
||||
'job' => 'Baby',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
$this->assertSame($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'john'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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->assertSame($db1, $db1b, 'A second call to getConnection() returns the same object.');
|
||||
$this->assertSame($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->assertSame($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->assertSame($db3, $db3b, 'A second call to getConnection() returns the same object.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that connections return appropriate connection objects.
|
||||
*/
|
||||
public 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->assertSame($db1, $db2, 'Both targets refer to the same connection.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the closing of a database connection.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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');
|
||||
// Getting a driver class ensures the namespace option is set.
|
||||
$this->assertEquals($db->getDriverClass('select'), $db2->getDriverClass('select'));
|
||||
$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.', ['%word' => $word]));
|
||||
$this->assertIdentical($db->escapeField($word), $expected, format_string('The reserved word %word was correctly escaped when used as a column name.', ['%word' => $word]));
|
||||
$this->assertIdentical($db->escapeAlias($word), $expected, format_string('The reserved word %word was correctly escaped when used as an alias.', ['%word' => $word]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
<?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.', ['@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.', ['@id' => $id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Database::closeConnection() without query.
|
||||
*
|
||||
* @todo getConnectionId() executes a query.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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, [
|
||||
'fields' => [
|
||||
'name' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Execute a query.
|
||||
Database::getConnection($this->target, $this->key)->select('foo', 'f')
|
||||
->fields('f', ['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);
|
||||
// Skip this test when a database driver does not implement PDO.
|
||||
// An alternative database driver that does not implement PDO
|
||||
// should implement its own connection test.
|
||||
if (get_class($connection_property->getValue($connection)) !== 'PDO') {
|
||||
$this->markTestSkipped('Ignored PDO connection unit test for this driver because it does not implement PDO.');
|
||||
}
|
||||
$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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
/**
|
||||
* Deprecation tests cases for the database layer.
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
class DatabaseLegacyTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests the db_table_exists() function.
|
||||
*
|
||||
* @expectedDeprecation db_table_exists() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Use $injected_database->schema()->tableExists($table) instead. See https://www.drupal.org/node/2947929.
|
||||
*/
|
||||
public function testDbTableExists() {
|
||||
$this->assertTrue(db_table_exists('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the db_set_active() function.
|
||||
*
|
||||
* @expectedDeprecation db_set_active() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\Database\Database::setActiveConnection() instead. See https://www.drupal.org/node/2944084.
|
||||
*/
|
||||
public function testDbSetActive() {
|
||||
$get_active_db = $this->connection->getKey();
|
||||
$this->assert(db_set_active($get_active_db), 'Database connection is active');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the db_drop_table() function.
|
||||
*
|
||||
* @expectedDeprecation db_drop_table() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\Database\Database::getConnection()->schema()->dropTable() instead. See https://www.drupal.org/node/2987737
|
||||
*/
|
||||
public function testDbDropTable() {
|
||||
$this->assertFalse(db_drop_table('temp_test_table'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
use Drupal\Core\Database\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 = ['database_test'];
|
||||
|
||||
/**
|
||||
* The database connection for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->connection = Database::getConnection();
|
||||
$this->installSchema('database_test', [
|
||||
'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.
|
||||
*/
|
||||
public function ensureSampleDataNull() {
|
||||
$this->connection->insert('test_null')
|
||||
->fields(['name', 'age'])
|
||||
->values([
|
||||
'name' => 'Kermit',
|
||||
'age' => 25,
|
||||
])
|
||||
->values([
|
||||
'name' => 'Fozzie',
|
||||
'age' => NULL,
|
||||
])
|
||||
->values([
|
||||
'name' => 'Gonzo',
|
||||
'age' => 27,
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up our sample data.
|
||||
*/
|
||||
public static function addSampleData() {
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// We need the IDs, so we can't use a multi-insert here.
|
||||
$john = $connection->insert('test')
|
||||
->fields([
|
||||
'name' => 'John',
|
||||
'age' => 25,
|
||||
'job' => 'Singer',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$george = $connection->insert('test')
|
||||
->fields([
|
||||
'name' => 'George',
|
||||
'age' => 27,
|
||||
'job' => 'Singer',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$connection->insert('test')
|
||||
->fields([
|
||||
'name' => 'Ringo',
|
||||
'age' => 28,
|
||||
'job' => 'Drummer',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$paul = $connection->insert('test')
|
||||
->fields([
|
||||
'name' => 'Paul',
|
||||
'age' => 26,
|
||||
'job' => 'Songwriter',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$connection->insert('test_people')
|
||||
->fields([
|
||||
'name' => 'Meredith',
|
||||
'age' => 30,
|
||||
'job' => 'Speaker',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$connection->insert('test_task')
|
||||
->fields(['pid', 'task', 'priority'])
|
||||
->values([
|
||||
'pid' => $john,
|
||||
'task' => 'eat',
|
||||
'priority' => 3,
|
||||
])
|
||||
->values([
|
||||
'pid' => $john,
|
||||
'task' => 'sleep',
|
||||
'priority' => 4,
|
||||
])
|
||||
->values([
|
||||
'pid' => $john,
|
||||
'task' => 'code',
|
||||
'priority' => 1,
|
||||
])
|
||||
->values([
|
||||
'pid' => $george,
|
||||
'task' => 'sing',
|
||||
'priority' => 2,
|
||||
])
|
||||
->values([
|
||||
'pid' => $george,
|
||||
'task' => 'sleep',
|
||||
'priority' => 2,
|
||||
])
|
||||
->values([
|
||||
'pid' => $paul,
|
||||
'task' => 'found new band',
|
||||
'priority' => 1,
|
||||
])
|
||||
->values([
|
||||
'pid' => $paul,
|
||||
'task' => 'perform at superbowl',
|
||||
'priority' => 3,
|
||||
])
|
||||
->execute();
|
||||
|
||||
$connection->insert('test_special_columns')
|
||||
->fields([
|
||||
'id' => 1,
|
||||
'offset' => 'Offset value 1',
|
||||
'function' => 'Function value 1',
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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', ['id'])
|
||||
->condition('t.id', [$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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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 truncate a whole table while in transaction.
|
||||
*/
|
||||
public function testTruncateInTransaction() {
|
||||
// This test won't work right if transactions are not supported.
|
||||
if (!$this->connection->supportsTransactions()) {
|
||||
$this->markTestSkipped('The database driver does not support transactions.');
|
||||
}
|
||||
|
||||
$num_records_before = $this->connection->select('test')->countQuery()->execute()->fetchField();
|
||||
$this->assertGreaterThan(0, $num_records_before, 'The table is not empty.');
|
||||
|
||||
$transaction = $this->connection->startTransaction('test_truncate_in_transaction');
|
||||
$this->connection->insert('test')
|
||||
->fields([
|
||||
'name' => 'Freddie',
|
||||
'age' => 45,
|
||||
'job' => 'Great singer',
|
||||
])
|
||||
->execute();
|
||||
$num_records_after_insert = $this->connection->select('test')->countQuery()->execute()->fetchField();
|
||||
$this->assertEquals($num_records_before + 1, $num_records_after_insert);
|
||||
|
||||
$this->connection->truncate('test')->execute();
|
||||
|
||||
// Checks that there are no records left in the table, and transaction is
|
||||
// still active.
|
||||
$this->assertTrue($this->connection->inTransaction());
|
||||
$num_records_after = $this->connection->select('test')->countQuery()->execute()->fetchField();
|
||||
$this->assertEquals(0, $num_records_after);
|
||||
|
||||
// Close the transaction, and check that there are still no records in the
|
||||
// table.
|
||||
$transaction = NULL;
|
||||
$this->assertFalse($this->connection->inTransaction());
|
||||
$num_records_after = $this->connection->select('test')->countQuery()->execute()->fetchField();
|
||||
$this->assertEquals(0, $num_records_after);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that transaction rollback voids a truncate operation.
|
||||
*/
|
||||
public function testTruncateTransactionRollback() {
|
||||
// This test won't work right if transactions are not supported.
|
||||
if (!$this->connection->supportsTransactions()) {
|
||||
$this->markTestSkipped('The database driver does not support transactions.');
|
||||
}
|
||||
|
||||
$num_records_before = $this->connection->select('test')->countQuery()->execute()->fetchField();
|
||||
$this->assertGreaterThan(0, $num_records_before, 'The table is not empty.');
|
||||
|
||||
$transaction = $this->connection->startTransaction('test_truncate_in_transaction');
|
||||
$this->connection->insert('test')
|
||||
->fields([
|
||||
'name' => 'Freddie',
|
||||
'age' => 45,
|
||||
'job' => 'Great singer',
|
||||
])
|
||||
->execute();
|
||||
$num_records_after_insert = $this->connection->select('test')->countQuery()->execute()->fetchField();
|
||||
$this->assertEquals($num_records_before + 1, $num_records_after_insert);
|
||||
|
||||
$this->connection->truncate('test')->execute();
|
||||
|
||||
// Checks that there are no records left in the table, and transaction is
|
||||
// still active.
|
||||
$this->assertTrue($this->connection->inTransaction());
|
||||
$num_records_after = $this->connection->select('test')->countQuery()->execute()->fetchField();
|
||||
$this->assertEquals(0, $num_records_after);
|
||||
|
||||
// Roll back the transaction, and check that we are back to status before
|
||||
// insert and truncate.
|
||||
$this->connection->rollBack();
|
||||
$this->assertFalse($this->connection->inTransaction());
|
||||
$num_records_after = $this->connection->select('test')->countQuery()->execute()->fetchField();
|
||||
$this->assertEquals($num_records_before, $num_records_after);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can delete a single special column name record successfully.
|
||||
*/
|
||||
public 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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
use Drupal\Core\Database\RowCountException;
|
||||
use Drupal\Core\Database\StatementInterface;
|
||||
use Drupal\Tests\system\Functional\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.
|
||||
*/
|
||||
public function testQueryFetchDefault() {
|
||||
$records = [];
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', [':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.
|
||||
*/
|
||||
public function testQueryFetchObject() {
|
||||
$records = [];
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', [':age' => 25], ['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.
|
||||
*/
|
||||
public function testQueryFetchArray() {
|
||||
$records = [];
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', [':age' => 25], ['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
|
||||
*/
|
||||
public function testQueryFetchClass() {
|
||||
$records = [];
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', [':age' => 25], ['fetch' => FakeRecord::class]);
|
||||
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.
|
||||
*/
|
||||
public function testQueryFetchNum() {
|
||||
$records = [];
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', [':age' => 25], ['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.
|
||||
*/
|
||||
public function testQueryFetchBoth() {
|
||||
$records = [];
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', [':age' => 25], ['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 all records into an array explicitly.
|
||||
*/
|
||||
public function testQueryFetchAllColumn() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->orderBy('name');
|
||||
$query_result = $query->execute()->fetchAll(\PDO::FETCH_COLUMN);
|
||||
|
||||
$expected_result = ['George', 'John', 'Paul', 'Ringo'];
|
||||
$this->assertEqual($query_result, $expected_result, 'Returned the correct result.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can fetch an entire column of a result set at once.
|
||||
*/
|
||||
public function testQueryFetchCol() {
|
||||
$result = db_query('SELECT name FROM {test} WHERE age > :age', [':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', [':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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testDefaultInsert() {
|
||||
$query = db_insert('test')->useDefaults(['job']);
|
||||
$id = $query->execute();
|
||||
|
||||
$schema = drupal_get_module_schema('database_test', 'test');
|
||||
|
||||
$job = db_query('SELECT job FROM {test} WHERE id = :id', [':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.
|
||||
*/
|
||||
public 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->assertSame($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.
|
||||
*/
|
||||
public function testDefaultInsertWithFields() {
|
||||
$query = db_insert('test')
|
||||
->fields(['name' => 'Bob'])
|
||||
->useDefaults(['job']);
|
||||
$id = $query->execute();
|
||||
|
||||
$schema = drupal_get_module_schema('database_test', 'test');
|
||||
|
||||
$job = db_query('SELECT job FROM {test} WHERE id = :id', [':id' => $id])->fetchField();
|
||||
$this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testInsertOneBlob() {
|
||||
$data = "This is\000a test.";
|
||||
$this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.');
|
||||
$id = db_insert('test_one_blob')
|
||||
->fields(['blob1' => $data])
|
||||
->execute();
|
||||
$r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', [':id' => $id])->fetchAssoc();
|
||||
$this->assertTrue($r['blob1'] === $data, format_string('Can insert a blob: id @id, @data.', ['@id' => $id, '@data' => serialize($r)]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can insert multiple blob fields in the same query.
|
||||
*/
|
||||
public function testInsertMultipleBlob() {
|
||||
$id = db_insert('test_two_blobs')
|
||||
->fields([
|
||||
'blob1' => 'This is',
|
||||
'blob2' => 'a test',
|
||||
])
|
||||
->execute();
|
||||
$r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', [':id' => $id])->fetchAssoc();
|
||||
$this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', 'Can insert multiple blobs per row.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
/**
|
||||
* Tests the insert builder.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class InsertTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests very basic insert functionality.
|
||||
*/
|
||||
public function testSimpleInsert() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$query = db_insert('test');
|
||||
$query->fields([
|
||||
'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->assertSame($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Yoko'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '29', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can insert multiple records in one query object.
|
||||
*/
|
||||
public function testMultiInsert() {
|
||||
$num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$query = db_insert('test');
|
||||
$query->fields([
|
||||
'name' => 'Larry',
|
||||
'age' => '30',
|
||||
]);
|
||||
|
||||
// We should be able to specify values in any order if named.
|
||||
$query->values([
|
||||
'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(['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->assertSame($num_records_before + 3, $num_records_after, 'Record inserts correctly.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Larry'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Curly'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':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.
|
||||
*/
|
||||
public function testRepeatedInsert() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$query = db_insert('test');
|
||||
|
||||
$query->fields([
|
||||
'name' => 'Larry',
|
||||
'age' => '30',
|
||||
]);
|
||||
// Check how many records are queued for insertion.
|
||||
$this->assertIdentical($query->count(), 1, 'One record is queued for insertion.');
|
||||
// This should run the insert, but leave the fields intact.
|
||||
$query->execute();
|
||||
|
||||
// We should be able to specify values in any order if named.
|
||||
$query->values([
|
||||
'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(['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->assertSame((int) $num_records_before + 3, (int) $num_records_after, 'Record inserts correctly.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Larry'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Curly'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Moe'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can specify fields without values and specify values later.
|
||||
*/
|
||||
public 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(['name', 'age'])
|
||||
->values(['Larry', '30'])
|
||||
->values(['Curly', '31'])
|
||||
->values(['Moe', '32'])
|
||||
->execute();
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Larry'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Curly'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Moe'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that inserts return the proper auto-increment ID.
|
||||
*/
|
||||
public function testInsertLastInsertID() {
|
||||
$id = db_insert('test')
|
||||
->fields([
|
||||
'name' => 'Larry',
|
||||
'age' => '30',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$this->assertIdentical($id, '5', 'Auto-increment ID returned successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the INSERT INTO ... SELECT (fields) ... syntax works.
|
||||
*/
|
||||
public 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', ['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', [':name' => 'Meredith'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the INSERT INTO ... SELECT * ... syntax works.
|
||||
*/
|
||||
public 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', [':name' => 'Meredith'])->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can INSERT INTO a special named column.
|
||||
*/
|
||||
public function testSpecialColumnInsert() {
|
||||
$this->connection->insert('test_special_columns')
|
||||
->fields([
|
||||
'id' => 2,
|
||||
'offset' => 'Offset value 2',
|
||||
'function' => 'foobar',
|
||||
])
|
||||
->execute();
|
||||
$result = $this->connection->select('test_special_columns')
|
||||
->fields('test_special_columns', ['offset', 'function'])
|
||||
->condition('test_special_columns.function', 'foobar')
|
||||
->execute();
|
||||
$record = $result->fetch();
|
||||
$this->assertSame('Offset value 2', $record->offset);
|
||||
$this->assertSame('foobar', $record->function);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testInsertDuplicateData() {
|
||||
// Try to insert multiple records where at least one has bad data.
|
||||
try {
|
||||
db_insert('test')
|
||||
->fields(['name', 'age', 'job'])
|
||||
->values([
|
||||
'name' => 'Elvis',
|
||||
'age' => 63,
|
||||
'job' => 'Singer',
|
||||
])->values([
|
||||
// Duplicate value on unique field.
|
||||
'name' => 'John',
|
||||
'age' => 17,
|
||||
'job' => 'Consultant',
|
||||
])
|
||||
->values([
|
||||
'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', [':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', ['name', 'age'])
|
||||
->condition('age', [17, 75], 'IN')
|
||||
->execute()->fetchObject();
|
||||
|
||||
$this->assertFalse($record, 'The rest of the insert aborted as expected.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests inserting with invalid data from a select query.
|
||||
*/
|
||||
public function testInsertDuplicateDataFromSelect() {
|
||||
// Insert multiple records in 'test_people' where one has bad data
|
||||
// (duplicate key). A 'Meredith' record has already been inserted
|
||||
// in ::setUp.
|
||||
db_insert('test_people')
|
||||
->fields(['name', 'age', 'job'])
|
||||
->values([
|
||||
'name' => 'Elvis',
|
||||
'age' => 63,
|
||||
'job' => 'Singer',
|
||||
])->values([
|
||||
// Duplicate value on unique field 'name' for later INSERT in 'test'
|
||||
// table.
|
||||
'name' => 'John',
|
||||
'age' => 17,
|
||||
'job' => 'Consultant',
|
||||
])
|
||||
->values([
|
||||
'name' => 'Frank',
|
||||
'age' => 75,
|
||||
'job' => 'Bass',
|
||||
])
|
||||
->execute();
|
||||
|
||||
try {
|
||||
// Define the subselect query. Add ORDER BY to ensure we have consistent
|
||||
// order in results. Will return:
|
||||
// 0 => [name] => Elvis, [age] => 63, [job] => Singer
|
||||
// 1 => [name] => Frank, [age] => 75, [job] => Bass
|
||||
// 2 => [name] => John, [age] => 17, [job] => Consultant
|
||||
// 3 => [name] => Meredith, [age] => 30, [job] => Speaker
|
||||
// Records 0 and 1 should pass, record 2 should lead to integrity
|
||||
// constraint violation.
|
||||
$query = db_select('test_people', 'tp')
|
||||
->fields('tp', ['name', 'age', 'job'])
|
||||
->orderBy('name');
|
||||
|
||||
// Try inserting from the subselect.
|
||||
db_insert('test')
|
||||
->from($query)
|
||||
->execute();
|
||||
|
||||
$this->fail('Insert succeeded when it should not have.');
|
||||
}
|
||||
catch (IntegrityConstraintViolationException $e) {
|
||||
// Check if the second record was inserted.
|
||||
$name = db_query('SELECT name FROM {test} WHERE age = :age', [':age' => 75])->fetchField();
|
||||
|
||||
if ($name == 'Frank') {
|
||||
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 values for records 2 and 3 were not inserted.
|
||||
$record = db_select('test')
|
||||
->fields('test', ['name', 'age'])
|
||||
->condition('age', [17, 30], 'IN')
|
||||
->execute()->fetchObject();
|
||||
|
||||
$this->assertFalse($record, 'The rest of the insert aborted as expected.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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', [':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.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testEnableLogging() {
|
||||
Database::startLog('testing');
|
||||
|
||||
db_query('SELECT name FROM {test} WHERE age > :age', [':age' => 25])->fetchCol();
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchCol();
|
||||
|
||||
// Trigger a call that does not have file in the backtrace.
|
||||
call_user_func_array('db_query', ['SELECT age FROM {test} WHERE name = :name', [':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.
|
||||
*/
|
||||
public function testEnableMultiLogging() {
|
||||
Database::startLog('testing1');
|
||||
|
||||
db_query('SELECT name FROM {test} WHERE age > :age', [':age' => 25])->fetchCol();
|
||||
|
||||
Database::startLog('testing2');
|
||||
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', [':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.
|
||||
*/
|
||||
public 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', [':age' => 25])->fetchCol();
|
||||
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'], ['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.
|
||||
*/
|
||||
public function testEnableTargetLoggingNoTarget() {
|
||||
Database::startLog('testing1');
|
||||
|
||||
db_query('SELECT name FROM {test} WHERE age > :age', [':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', [':name' => 'Ringo'], ['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.
|
||||
*/
|
||||
public 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', [':age' => 25])->fetchCol();
|
||||
|
||||
$old_key = Database::setActiveConnection('test2');
|
||||
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'], ['target' => 'replica'])->fetchCol();
|
||||
|
||||
Database::setActiveConnection($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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that getLog with a wrong key return an empty array.
|
||||
*/
|
||||
public function testGetLoggingWrongKey() {
|
||||
$result = Database::getLog('wrong');
|
||||
|
||||
$this->assertEqual($result, [], 'The function getLog with a wrong key returns an empty array.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testMergeInsert() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
$result = db_merge('test_people')
|
||||
->key('job', 'Presenter')
|
||||
->fields([
|
||||
'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', [':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.
|
||||
*/
|
||||
public function testMergeUpdate() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
$result = db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->fields([
|
||||
'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', [':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.
|
||||
*/
|
||||
public function testMergeUpdateExcept() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->insertFields(['age' => 31])
|
||||
->updateFields(['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', [':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.
|
||||
*/
|
||||
public function testMergeUpdateExplicit() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->insertFields([
|
||||
'age' => 31,
|
||||
'name' => 'Tiffany',
|
||||
])
|
||||
->updateFields([
|
||||
'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', [':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.
|
||||
*/
|
||||
public 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', [':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(['name' => 'Tiffany'])
|
||||
->insertFields(['age' => 31])
|
||||
->expression('age', 'age + :age', [':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', [':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.
|
||||
*/
|
||||
public 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', [':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.
|
||||
*/
|
||||
public 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', [':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(['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', [':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.
|
||||
*/
|
||||
public 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([
|
||||
'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([
|
||||
'age' => 31,
|
||||
'name' => 'Tiffany',
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
catch (InvalidMergeQueryException $e) {
|
||||
$this->pass('InvalidMergeQueryException thrown for invalid query.');
|
||||
return;
|
||||
}
|
||||
$this->fail('No InvalidMergeQueryException thrown');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?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 = ['system'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', 'sequences');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the sequences API works.
|
||||
*/
|
||||
public 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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
/**
|
||||
* Tests that the prefix info for a database schema is correct.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class PrefixInfoTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests that DatabaseSchema::getPrefixInfo() returns the right database.
|
||||
*
|
||||
* We are testing if the return array of the method
|
||||
* \Drupal\Core\Database\Driver\mysql\Schema::getPrefixInfo(). This return
|
||||
* array is a keyed array with info about amongst other things the database.
|
||||
* The other two by Drupal core supported databases do not have this variable
|
||||
* set in the return array.
|
||||
*/
|
||||
public function testGetPrefixInfo() {
|
||||
$connection_info = Database::getConnectionInfo('default');
|
||||
if ($connection_info['default']['driver'] == 'mysql') {
|
||||
// Copy the default connection info to the 'extra' key.
|
||||
Database::addConnectionInfo('extra', 'default', $connection_info['default']);
|
||||
|
||||
$db1_connection = Database::getConnection('default', 'default');
|
||||
$db1_schema = $db1_connection->schema();
|
||||
$db2_connection = Database::getConnection('default', 'extra');
|
||||
|
||||
// Get the prefix info for the first database.
|
||||
$method = new \ReflectionMethod($db1_schema, 'getPrefixInfo');
|
||||
$method->setAccessible(TRUE);
|
||||
$db1_info = $method->invoke($db1_schema);
|
||||
|
||||
// We change the database after opening the connection, so as to prevent
|
||||
// connecting to a non-existent database.
|
||||
$reflection = new \ReflectionObject($db2_connection);
|
||||
$property = $reflection->getProperty('connectionOptions');
|
||||
$property->setAccessible(TRUE);
|
||||
$connection_info['default']['database'] = 'foobar';
|
||||
$property->setValue($db2_connection, $connection_info['default']);
|
||||
|
||||
// For testing purposes, we also change the database info.
|
||||
$reflection_class = new \ReflectionClass('Drupal\Core\Database\Database');
|
||||
$property = $reflection_class->getProperty('databaseInfo');
|
||||
$property->setAccessible(TRUE);
|
||||
$info = $property->getValue();
|
||||
$info['extra']['default']['database'] = 'foobar';
|
||||
$property->setValue(NULL, $info);
|
||||
|
||||
$extra_info = Database::getConnectionInfo('extra');
|
||||
$this->assertSame($extra_info['default']['database'], 'foobar');
|
||||
$db2_schema = $db2_connection->schema();
|
||||
$db2_info = $method->invoke($db2_schema);
|
||||
|
||||
$this->assertNotSame($db2_info['database'], $db1_info['database'], 'Each target connection has a different database.');
|
||||
$this->assertSame($db2_info['database'], 'foobar', 'The new profile has a different database.');
|
||||
|
||||
Database::removeConnection('extra');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testArraySubstitution() {
|
||||
$names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', [':ages[]' => [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', [':ages[]' => [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.
|
||||
*/
|
||||
public function testScalarSubstitution() {
|
||||
try {
|
||||
$names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', [':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 = [
|
||||
"1 ;INSERT INTO {test} (name) VALUES ('test12345678'); -- " => '',
|
||||
'1' => '',
|
||||
];
|
||||
try {
|
||||
db_query("SELECT * FROM {test} WHERE name = :name", [':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'); -- ";
|
||||
|
||||
$previous_error_handler = set_error_handler(function ($severity, $message, $filename, $lineno, $context) use (&$previous_error_handler) {
|
||||
// Normalize the filename to use UNIX directory separators.
|
||||
if (preg_match('@core/lib/Drupal/Core/Database/Query/Condition.php$@', str_replace(DIRECTORY_SEPARATOR, '/', $filename))) {
|
||||
// Convert errors to exceptions for testing purposes below.
|
||||
throw new \ErrorException($message, 0, $severity, $filename, $lineno);
|
||||
}
|
||||
if ($previous_error_handler) {
|
||||
return $previous_error_handler($severity, $message, $filename, $lineno, $context);
|
||||
}
|
||||
});
|
||||
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', ['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', ['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}', [
|
||||
':count' => 3,
|
||||
])->fetchField();
|
||||
$this->assertEqual((bool) $count, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
/**
|
||||
* Tests the Range query functionality.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class RangeQueryTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that range queries work and return the correct result.
|
||||
*/
|
||||
public 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?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 = ['node', 'user'];
|
||||
|
||||
/**
|
||||
* Ensures that non-ASCII UTF-8 data is stored in the database properly.
|
||||
*/
|
||||
public function testRegression_310447() {
|
||||
// That's a 255 character UTF-8 string.
|
||||
$job = str_repeat("é", 255);
|
||||
db_insert('test')
|
||||
->fields([
|
||||
'name' => $this->randomMachineName(),
|
||||
'age' => 20,
|
||||
'job' => $job,
|
||||
])->execute();
|
||||
|
||||
$from_database = db_query('SELECT job FROM {test} WHERE job = :job', [':job' => $job])->fetchField();
|
||||
$this->assertSame($job, $from_database, 'The database handles UTF-8 characters cleanly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the db_table_exists() function.
|
||||
*/
|
||||
public function testDBTableExists() {
|
||||
$this->assertSame(TRUE, $this->connection->schema()->tableExists('test'), 'Returns true for existent table.');
|
||||
$this->assertSame(FALSE, $this->connection->schema()->tableExists('nosuchtable'), 'Returns false for nonexistent table.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the db_field_exists() function.
|
||||
*/
|
||||
public function testDBFieldExists() {
|
||||
$this->assertSame(TRUE, db_field_exists('test', 'name'), 'Returns true for existent column.');
|
||||
$this->assertSame(FALSE, db_field_exists('test', 'nosuchcolumn'), 'Returns false for nonexistent column.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the db_index_exists() function.
|
||||
*/
|
||||
public function testDBIndexExists() {
|
||||
$this->assertSame(TRUE, db_index_exists('test', 'ages'), 'Returns true for existent index.');
|
||||
$this->assertSame(FALSE, db_index_exists('test', 'nosuchindex'), 'Returns false for nonexistent index.');
|
||||
}
|
||||
|
||||
}
|
||||
1192
2017/web/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
Normal file
1192
2017/web/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,68 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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 have a different unique identifier.
|
||||
$this->assertNotEquals($query->uniqueIdentifier(), $clone->uniqueIdentifier());
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that nested SELECT queries are cloned properly.
|
||||
*/
|
||||
public function testNestedQueryCloning() {
|
||||
$sub_query = $this->connection->select('test', 't');
|
||||
$sub_query->addField('t', 'id', 'id');
|
||||
$sub_query->condition('age', 28, '<');
|
||||
|
||||
$query = $this->connection->select($sub_query, 't');
|
||||
|
||||
$clone = clone $query;
|
||||
|
||||
// Cloned query should have a different unique identifier.
|
||||
$this->assertNotEquals($query->uniqueIdentifier(), $clone->uniqueIdentifier());
|
||||
|
||||
// Cloned query should not be altered by the following modification
|
||||
// happening on original query.
|
||||
$sub_query->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->assertEquals(3, $clone_result, 'The cloned query returns the expected number of rows');
|
||||
$this->assertEquals(2, $query_result, 'The query returns the expected number of rows');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,438 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
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 = ['system', 'user', 'node_access_test', 'field'];
|
||||
|
||||
/**
|
||||
* Tests simple JOIN statements.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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 = [];
|
||||
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 = [
|
||||
'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.", ['@task' => $task]));
|
||||
}
|
||||
|
||||
$this->assertEqual($num_records, 6, 'Returned the correct number of total rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests GROUP BY and HAVING clauses together.
|
||||
*/
|
||||
public 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 = [];
|
||||
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 = [
|
||||
'sleep' => 2,
|
||||
];
|
||||
|
||||
foreach ($correct_results as $task => $count) {
|
||||
$this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", ['@task' => $task]));
|
||||
}
|
||||
|
||||
$this->assertEqual($num_records, 1, 'Returned the correct number of total rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests range queries.
|
||||
*
|
||||
* The SQL clause varies with the database.
|
||||
*/
|
||||
public 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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the range property of a select clause can be undone.
|
||||
*/
|
||||
public function testRangeUndo() {
|
||||
$query = db_select('test');
|
||||
$name_field = $query->addField('test', 'name');
|
||||
$age_field = $query->addField('test', 'age', 'age');
|
||||
$query->range(0, 2);
|
||||
$query->range(NULL, NULL);
|
||||
$query_result = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($query_result, 4, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests distinct queries.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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', ['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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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((new Condition('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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public function testJoinSubquery() {
|
||||
$this->installSchema('system', 'sequences');
|
||||
|
||||
$account = User::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'mail' => $this->randomMachineName() . '@example.com',
|
||||
]);
|
||||
|
||||
$query = db_select('test_task', 'tt', ['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.
|
||||
*/
|
||||
public 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that join conditions can use Condition objects.
|
||||
*/
|
||||
public function testJoinConditionObject() {
|
||||
// Same test as testDefaultJoin, but with a Condition object.
|
||||
$query = db_select('test_task', 't');
|
||||
$join_cond = (new Condition('AND'))->where('t.pid = p.id');
|
||||
$people_alias = $query->join('test', 'p', $join_cond);
|
||||
$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.');
|
||||
|
||||
// Test a condition object that creates placeholders.
|
||||
$t1_name = 'John';
|
||||
$t2_name = 'George';
|
||||
$join_cond = (new Condition('AND'))
|
||||
->condition('t1.name', $t1_name)
|
||||
->condition('t2.name', $t2_name);
|
||||
$query = db_select('test', 't1');
|
||||
$query->innerJoin('test', 't2', $join_cond);
|
||||
$query->addField('t1', 'name', 't1_name');
|
||||
$query->addField('t2', 'name', 't2_name');
|
||||
|
||||
$num_records = $query->countQuery()->execute()->fetchField();
|
||||
$this->assertEqual($num_records, 1, 'Query expected to return 1 row. Actual: ' . $num_records);
|
||||
if ($num_records == 1) {
|
||||
$record = $query->execute()->fetchObject();
|
||||
$this->assertEqual($record->t1_name, $t1_name, 'Query expected to retrieve name ' . $t1_name . ' from table t1. Actual: ' . $record->t1_name);
|
||||
$this->assertEqual($record->t2_name, $t2_name, 'Query expected to retrieve name ' . $t2_name . ' from table t2. Actual: ' . $record->t2_name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
/**
|
||||
* Tests the Select query builder.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectOrderedTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests basic ORDER BY.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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 = [
|
||||
['Ringo', 28, 'Drummer'],
|
||||
['John', 25, 'Singer'],
|
||||
['George', 27, 'Singer'],
|
||||
['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.
|
||||
*/
|
||||
public 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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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->assertCount(1, $people, 'Returned the correct number of rows.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can use a subquery in a FROM clause with a LIMIT.
|
||||
*/
|
||||
public 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->assertCount(1, $people, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can use a subquery with an IN operator in a WHERE clause.
|
||||
*/
|
||||
public 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->assertCount(5, $people, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can use a subquery with a relational operator in a WHERE clause.
|
||||
*/
|
||||
public function testConditionSubquerySelect2() {
|
||||
// Create a subquery, which is just a normal query object.
|
||||
$subquery = db_select('test', 't2');
|
||||
$subquery->addExpression('AVG(t2.age)');
|
||||
|
||||
// Create another query that adds a clause using the subquery.
|
||||
$select = db_select('test', 't');
|
||||
$select->addField('t', 'name');
|
||||
$select->condition('t.age', $subquery, '<');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// SELECT t.name
|
||||
// FROM test t
|
||||
// WHERE t.age < (SELECT AVG(t2.age) FROM test t2)
|
||||
$people = $select->execute()->fetchCol();
|
||||
$this->assertEquals(['John', 'Paul'], $people, 'Returned Paul and John.', 0.0, 10, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can use 2 subqueries with a relational operator in a WHERE clause.
|
||||
*/
|
||||
public function testConditionSubquerySelect3() {
|
||||
// Create subquery 1, which is just a normal query object.
|
||||
$subquery1 = db_select('test_task', 'tt');
|
||||
$subquery1->addExpression('AVG(tt.priority)');
|
||||
$subquery1->where('tt.pid = t.id');
|
||||
|
||||
// Create subquery 2, which is just a normal query object.
|
||||
$subquery2 = db_select('test_task', 'tt2');
|
||||
$subquery2->addExpression('AVG(tt2.priority)');
|
||||
|
||||
// Create another query that adds a clause using the subqueries.
|
||||
$select = db_select('test', 't');
|
||||
$select->addField('t', 'name');
|
||||
$select->condition($subquery1, $subquery2, '>');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// SELECT t.name
|
||||
// FROM test t
|
||||
// WHERE (SELECT AVG(tt.priority) FROM test_task tt WHERE tt.pid = t.id) > (SELECT AVG(tt2.priority) FROM test_task tt2)
|
||||
$people = $select->execute()->fetchCol();
|
||||
$this->assertEquals(['John'], $people, 'Returned John.', 0.0, 10, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can use multiple subqueries.
|
||||
*
|
||||
* This test uses a subquery at the left hand side and multiple subqueries at
|
||||
* the right hand side. The test query may not be that logical but that's due
|
||||
* to the limited amount of data and tables. 'Valid' use cases do exist :)
|
||||
*/
|
||||
public function testConditionSubquerySelect4() {
|
||||
// Create subquery 1, which is just a normal query object.
|
||||
$subquery1 = db_select('test_task', 'tt');
|
||||
$subquery1->addExpression('AVG(tt.priority)');
|
||||
$subquery1->where('tt.pid = t.id');
|
||||
|
||||
// Create subquery 2, which is just a normal query object.
|
||||
$subquery2 = db_select('test_task', 'tt2');
|
||||
$subquery2->addExpression('MIN(tt2.priority)');
|
||||
$subquery2->where('tt2.pid <> t.id');
|
||||
|
||||
// Create subquery 3, which is just a normal query object.
|
||||
$subquery3 = db_select('test_task', 'tt3');
|
||||
$subquery3->addExpression('AVG(tt3.priority)');
|
||||
$subquery3->where('tt3.pid <> t.id');
|
||||
|
||||
// Create another query that adds a clause using the subqueries.
|
||||
$select = db_select('test', 't');
|
||||
$select->addField('t', 'name');
|
||||
$select->condition($subquery1, [$subquery2, $subquery3], 'BETWEEN');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// SELECT t.name AS name
|
||||
// FROM {test} t
|
||||
// WHERE (SELECT AVG(tt.priority) AS expression FROM {test_task} tt WHERE (tt.pid = t.id))
|
||||
// BETWEEN (SELECT MIN(tt2.priority) AS expression FROM {test_task} tt2 WHERE (tt2.pid <> t.id))
|
||||
// AND (SELECT AVG(tt3.priority) AS expression FROM {test_task} tt3 WHERE (tt3.pid <> t.id));
|
||||
$people = $select->execute()->fetchCol();
|
||||
$this->assertEquals(['George', 'Paul'], $people, 'Returned George and Paul.', 0.0, 10, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can use a subquery in a JOIN clause.
|
||||
*/
|
||||
public 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->assertCount(2, $people, '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.
|
||||
*/
|
||||
public function testExistsSubquerySelect() {
|
||||
// Put George into {test_people}.
|
||||
db_insert('test_people')
|
||||
->fields([
|
||||
'name' => 'George',
|
||||
'age' => 27,
|
||||
'job' => 'Singer',
|
||||
])
|
||||
->execute();
|
||||
// Base query to {test}.
|
||||
$query = db_select('test', 't')
|
||||
->fields('t', ['name']);
|
||||
// Subquery to {test_people}.
|
||||
$subquery = db_select('test_people', 'tp')
|
||||
->fields('tp', ['name'])
|
||||
->where('tp.name = t.name');
|
||||
$query->exists($subquery);
|
||||
$result = $query->execute();
|
||||
|
||||
// Ensure that we got the right record.
|
||||
$record = $result->fetch();
|
||||
$this->assertEquals('George', $record->name, '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.
|
||||
*/
|
||||
public function testNotExistsSubquerySelect() {
|
||||
// Put George into {test_people}.
|
||||
db_insert('test_people')
|
||||
->fields([
|
||||
'name' => 'George',
|
||||
'age' => 27,
|
||||
'job' => 'Singer',
|
||||
])
|
||||
->execute();
|
||||
|
||||
// Base query to {test}.
|
||||
$query = db_select('test', 't')
|
||||
->fields('t', ['name']);
|
||||
// Subquery to {test_people}.
|
||||
$subquery = db_select('test_people', 'tp')
|
||||
->fields('tp', ['name'])
|
||||
->where('tp.name = t.name');
|
||||
$query->notExists($subquery);
|
||||
|
||||
// Ensure that we got the right number of records.
|
||||
$people = $query->execute()->fetchCol();
|
||||
$this->assertCount(3, $people, 'NOT EXISTS query returned the correct results.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,594 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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().
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public function testSimpleSelectMultipleFields() {
|
||||
$record = db_select('test')
|
||||
->fields('test', ['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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public function testNullCondition() {
|
||||
$this->ensureSampleDataNull();
|
||||
|
||||
$names = db_select('test_null', 'tn')
|
||||
->fields('tn', ['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.
|
||||
*/
|
||||
public function testIsNullCondition() {
|
||||
$this->ensureSampleDataNull();
|
||||
|
||||
$names = db_select('test_null', 'tn')
|
||||
->fields('tn', ['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.
|
||||
*/
|
||||
public function testIsNotNullCondition() {
|
||||
$this->ensureSampleDataNull();
|
||||
|
||||
$names = db_select('test_null', 'tn')
|
||||
->fields('tn', ['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.
|
||||
*/
|
||||
public function testUnion() {
|
||||
$query_1 = db_select('test', 't')
|
||||
->fields('t', ['name'])
|
||||
->condition('age', [27, 28], 'IN');
|
||||
|
||||
$query_2 = db_select('test', 't')
|
||||
->fields('t', ['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.
|
||||
*/
|
||||
public function testUnionAll() {
|
||||
$query_1 = db_select('test', 't')
|
||||
->fields('t', ['name'])
|
||||
->condition('age', [27, 28], 'IN');
|
||||
|
||||
$query_2 = db_select('test', 't')
|
||||
->fields('t', ['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.
|
||||
*/
|
||||
public function testUnionCount() {
|
||||
$query_1 = db_select('test', 't')
|
||||
->fields('t', ['name', 'age'])
|
||||
->condition('age', [27, 28], 'IN');
|
||||
|
||||
$query_2 = db_select('test', 't')
|
||||
->fields('t', ['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 we can UNION multiple Select queries together and set the ORDER.
|
||||
*/
|
||||
public function testUnionOrder() {
|
||||
// This gives George and Ringo.
|
||||
$query_1 = db_select('test', 't')
|
||||
->fields('t', ['name'])
|
||||
->condition('age', [27, 28], 'IN');
|
||||
|
||||
// This gives Paul.
|
||||
$query_2 = db_select('test', 't')
|
||||
->fields('t', ['name'])
|
||||
->condition('age', 26);
|
||||
|
||||
$query_1->union($query_2);
|
||||
$query_1->orderBy('name', 'DESC');
|
||||
|
||||
$names = $query_1->execute()->fetchCol();
|
||||
|
||||
// Ensure we get all 3 records.
|
||||
$this->assertEqual(count($names), 3, 'UNION returned rows from both queries.');
|
||||
|
||||
// Ensure that the names are in the correct reverse alphabetical order,
|
||||
// regardless of which query they came from.
|
||||
$this->assertEqual($names[0], 'Ringo', 'First query returned correct name.');
|
||||
$this->assertEqual($names[1], 'Paul', 'Second query returned correct name.');
|
||||
$this->assertEqual($names[2], 'George', 'Third query returned correct name.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can UNION multiple Select queries together with and a LIMIT.
|
||||
*/
|
||||
public function testUnionOrderLimit() {
|
||||
// This gives George and Ringo.
|
||||
$query_1 = db_select('test', 't')
|
||||
->fields('t', ['name'])
|
||||
->condition('age', [27, 28], 'IN');
|
||||
|
||||
// This gives Paul.
|
||||
$query_2 = db_select('test', 't')
|
||||
->fields('t', ['name'])
|
||||
->condition('age', 26);
|
||||
|
||||
$query_1->union($query_2);
|
||||
$query_1->orderBy('name', 'DESC');
|
||||
$query_1->range(0, 2);
|
||||
|
||||
$names = $query_1->execute()->fetchCol();
|
||||
|
||||
// Ensure we get all only 2 of the 3 records.
|
||||
$this->assertEqual(count($names), 2, 'UNION with a limit returned rows from both queries.');
|
||||
|
||||
// Ensure that the names are in the correct reverse alphabetical order,
|
||||
// regardless of which query they came from.
|
||||
$this->assertEqual($names[0], 'Ringo', 'First query returned correct name.');
|
||||
$this->assertEqual($names[1], 'Paul', 'Second query returned correct name.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public 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(['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', ['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', ['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', ['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[] = [
|
||||
'regex' => 'hn$',
|
||||
'expected' => [
|
||||
'John',
|
||||
],
|
||||
];
|
||||
$test_groups[] = [
|
||||
'regex' => '^Pau',
|
||||
'expected' => [
|
||||
'Paul',
|
||||
],
|
||||
];
|
||||
$test_groups[] = [
|
||||
'regex' => 'Ringo|George',
|
||||
'expected' => [
|
||||
'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([
|
||||
'name' => 'Pete',
|
||||
'age' => 26,
|
||||
'job' => '#Drummer',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$test_groups = [];
|
||||
$test_groups[] = [
|
||||
'regex' => '#Drummer',
|
||||
'expected' => [
|
||||
'Pete',
|
||||
],
|
||||
];
|
||||
$test_groups[] = [
|
||||
'regex' => '#Singer',
|
||||
'expected' => [],
|
||||
];
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
// Ensure that REGEXP filter still works with no-string type field.
|
||||
$query = $database->select('test', 't');
|
||||
$query->addField('t', 'age');
|
||||
$query->condition('t.age', '2[6]', 'REGEXP');
|
||||
$result = $query->execute()->fetchField();
|
||||
$this->assertEquals($result, '26', 'Regexp with number type.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that aliases are renamed when they are duplicates.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public function testEmptyInCondition() {
|
||||
try {
|
||||
db_select('test', 't')
|
||||
->fields('t')
|
||||
->condition('age', [], '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', [], 'NOT IN')
|
||||
->execute();
|
||||
|
||||
$this->fail('Expected exception not thrown');
|
||||
}
|
||||
catch (InvalidQueryException $e) {
|
||||
$this->assertEqual("Query condition 'age NOT IN ()' cannot be empty.", $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<?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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public function testMetaData() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
|
||||
$data = [
|
||||
'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.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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([
|
||||
'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([
|
||||
'name' => 'Daniel' . $suffix,
|
||||
'age' => '19',
|
||||
])
|
||||
->execute();
|
||||
|
||||
$this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
|
||||
|
||||
if ($ddl_statement) {
|
||||
$table = [
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
],
|
||||
],
|
||||
'primary key' => ['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.
|
||||
*/
|
||||
public 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', [':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', [':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.
|
||||
*/
|
||||
public 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', [':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', [':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.
|
||||
*/
|
||||
public 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', [':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', [':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.
|
||||
*/
|
||||
public 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([
|
||||
'name' => $name,
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a DDL statement.
|
||||
*/
|
||||
protected function executeDDLStatement() {
|
||||
static $count = 0;
|
||||
$table = [
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
],
|
||||
],
|
||||
'primary key' => ['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.
|
||||
*/
|
||||
public function assertRowPresent($name, $message = NULL) {
|
||||
if (!isset($message)) {
|
||||
$message = format_string('Row %name is present.', ['%name' => $name]);
|
||||
}
|
||||
$present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', [':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.
|
||||
*/
|
||||
public function assertRowAbsent($name, $message = NULL) {
|
||||
if (!isset($message)) {
|
||||
$message = format_string('Row %name is absent.', ['%name' => $name]);
|
||||
}
|
||||
$present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', [':name' => $name])->fetchField();
|
||||
return $this->assertFalse($present, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests transaction stacking, commit, and rollback.
|
||||
*/
|
||||
public 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', [':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([
|
||||
'name' => 'David',
|
||||
'age' => '24',
|
||||
])
|
||||
->execute();
|
||||
|
||||
// Commit the transaction.
|
||||
unset($transaction);
|
||||
|
||||
$saved_age = $connection->query('SELECT age FROM {test} WHERE name = :name', [':name' => 'David'])->fetchField();
|
||||
$this->assertEqual('24', $saved_age);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Database;
|
||||
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
|
||||
/**
|
||||
* Tests the Update query builder, complex queries.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class UpdateComplexTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests updates with OR conditionals.
|
||||
*/
|
||||
public function testOrConditionUpdate() {
|
||||
$update = db_update('test')
|
||||
->fields(['job' => 'Musician'])
|
||||
->condition((new Condition('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', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests WHERE IN clauses.
|
||||
*/
|
||||
public function testInConditionUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(['job' => 'Musician'])
|
||||
->condition('name', ['John', 'Paul'], 'IN')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests WHERE NOT IN clauses.
|
||||
*/
|
||||
public 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(['job' => 'Musician'])
|
||||
->condition('name', ['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', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests BETWEEN conditional clauses.
|
||||
*/
|
||||
public function testBetweenConditionUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(['job' => 'Musician'])
|
||||
->condition('age', [25, 26], 'BETWEEN')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests LIKE conditionals.
|
||||
*/
|
||||
public function testLikeConditionUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(['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', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UPDATE with expression values.
|
||||
*/
|
||||
public function testUpdateExpression() {
|
||||
$before_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchField();
|
||||
$num_updated = db_update('test')
|
||||
->condition('name', 'Ringo')
|
||||
->fields(['job' => 'Musician'])
|
||||
->expression('age', 'age + :age', [':age' => 4])
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test} WHERE name = :name', [':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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UPDATE with only expression values.
|
||||
*/
|
||||
public function testUpdateOnlyExpression() {
|
||||
$before_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchField();
|
||||
$num_updated = db_update('test')
|
||||
->condition('name', 'Ringo')
|
||||
->expression('age', 'age + :age', [':age' => 4])
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$after_age = db_query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchField();
|
||||
$this->assertEqual($before_age + 4, $after_age, 'Age updated correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test UPDATE with a subselect value.
|
||||
*/
|
||||
public function testSubSelectUpdate() {
|
||||
$subselect = db_select('test_task', 't');
|
||||
$subselect->addExpression('MAX(priority) + :increment', 'max_priority', [':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', [':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.'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testUpdateOneBlob() {
|
||||
$data = "This is\000a test.";
|
||||
$this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.');
|
||||
$id = db_insert('test_one_blob')
|
||||
->fields(['blob1' => $data])
|
||||
->execute();
|
||||
|
||||
$data .= $data;
|
||||
db_update('test_one_blob')
|
||||
->condition('id', $id)
|
||||
->fields(['blob1' => $data])
|
||||
->execute();
|
||||
|
||||
$r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', [':id' => $id])->fetchAssoc();
|
||||
$this->assertTrue($r['blob1'] === $data, format_string('Can update a blob: id @id, @data.', ['@id' => $id, '@data' => serialize($r)]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can update two blob columns in the same table.
|
||||
*/
|
||||
public function testUpdateMultipleBlob() {
|
||||
$id = db_insert('test_two_blobs')
|
||||
->fields([
|
||||
'blob1' => 'This is',
|
||||
'blob2' => 'a test',
|
||||
])
|
||||
->execute();
|
||||
|
||||
db_update('test_two_blobs')
|
||||
->condition('id', $id)
|
||||
->fields(['blob1' => 'and so', 'blob2' => 'is this'])
|
||||
->execute();
|
||||
|
||||
$r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', [':id' => $id])->fetchAssoc();
|
||||
$this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', 'Can update multiple blobs per row.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
<?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.
|
||||
*/
|
||||
public function testSimpleUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(['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', [':id' => 1])->fetchField();
|
||||
$this->assertIdentical($saved_name, 'Tiffany', 'Updated name successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms updating to NULL.
|
||||
*/
|
||||
public function testSimpleNullUpdate() {
|
||||
$this->ensureSampleDataNull();
|
||||
$num_updated = db_update('test_null')
|
||||
->fields(['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', [':name' => 'Kermit'])->fetchField();
|
||||
$this->assertNull($saved_age, 'Updated name successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can update multiple records successfully.
|
||||
*/
|
||||
public function testMultiUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(['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', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can update multiple records with a non-equality condition.
|
||||
*/
|
||||
public function testMultiGTUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(['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', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can update multiple records with a where call.
|
||||
*/
|
||||
public function testWhereUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(['job' => 'Musician'])
|
||||
->where('age > :age', [':age' => 26])
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can stack condition and where calls.
|
||||
*/
|
||||
public function testWhereAndConditionUpdate() {
|
||||
$update = db_update('test')
|
||||
->fields(['job' => 'Musician'])
|
||||
->where('age > :age', [':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', [':job' => 'Musician'])->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating with expressions.
|
||||
*/
|
||||
public 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', [':age' => pow(26, 2)])->fetchField();
|
||||
$this->assertIdentical($saved_name, 'Paul', 'Successfully updated values using an algebraic expression.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests return value on update.
|
||||
*/
|
||||
public 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(['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.
|
||||
*/
|
||||
public function testPrimaryKeyUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(['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', [':id' => 42])->fetchField();
|
||||
$this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that we can update values in a column with special name.
|
||||
*/
|
||||
public function testSpecialColumnUpdate() {
|
||||
$num_updated = db_update('test_special_columns')
|
||||
->fields(['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', [':id' => 1])->fetchField();
|
||||
$this->assertIdentical($saved_value, 'New offset value', 'Updated special column name value successfully.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?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', [':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', [':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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can upsert records with a special named column.
|
||||
*/
|
||||
public function testSpecialColumnUpsert() {
|
||||
$num_records_before = $this->connection->query('SELECT COUNT(*) FROM {test_special_columns}')->fetchField();
|
||||
$upsert = $this->connection->upsert('test_special_columns')
|
||||
->key('id')
|
||||
->fields(['id', 'offset', 'function']);
|
||||
|
||||
// Add a new row.
|
||||
$upsert->values([
|
||||
'id' => 2,
|
||||
'offset' => 'Offset 2',
|
||||
'function' => 'Function 2',
|
||||
]);
|
||||
|
||||
// Update an existing row.
|
||||
$upsert->values([
|
||||
'id' => 1,
|
||||
'offset' => 'Offset 1 updated',
|
||||
'function' => 'Function 1 updated',
|
||||
]);
|
||||
|
||||
$upsert->execute();
|
||||
$num_records_after = $this->connection->query('SELECT COUNT(*) FROM {test_special_columns}')->fetchField();
|
||||
$this->assertEquals($num_records_before + 1, $num_records_after, 'Rows were inserted and updated properly.');
|
||||
|
||||
$record = $this->connection->query('SELECT * FROM {test_special_columns} WHERE id = :id', [':id' => 1])->fetch();
|
||||
$this->assertEquals($record->offset, 'Offset 1 updated');
|
||||
$this->assertEquals($record->function, 'Function 1 updated');
|
||||
|
||||
$record = $this->connection->query('SELECT * FROM {test_special_columns} WHERE id = :id', [':id' => 2])->fetch();
|
||||
$this->assertEquals($record->offset, 'Offset 2');
|
||||
$this->assertEquals($record->function, 'Function 2');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Datetime;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests date formatting.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class FormatDateTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['language', 'system'];
|
||||
|
||||
/**
|
||||
* Arbitrary langcode for a custom language.
|
||||
*/
|
||||
const LANGCODE = 'xx';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['system']);
|
||||
|
||||
$this->setSetting('locale_custom_strings_' . self::LANGCODE, [
|
||||
'' => ['Sunday' => 'domingo'],
|
||||
'Long month name' => ['March' => 'marzo'],
|
||||
]);
|
||||
|
||||
$formats = $this->container->get('entity.manager')
|
||||
->getStorage('date_format')
|
||||
->loadMultiple(['long', 'medium', 'short']);
|
||||
$formats['long']->setPattern('l, j. F Y - G:i')->save();
|
||||
$formats['medium']->setPattern('j. F Y - G:i')->save();
|
||||
$formats['short']->setPattern('Y M j - g:ia')->save();
|
||||
|
||||
ConfigurableLanguage::createFromLangcode(static::LANGCODE)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the format_date() function.
|
||||
*/
|
||||
public function testFormatDate() {
|
||||
/** @var \Drupal\Core\Datetime\DateFormatterInterface $formatter */
|
||||
$formatter = $this->container->get('date.formatter');
|
||||
/** @var \Drupal\Core\Language\LanguageManagerInterface $language_manager */
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
|
||||
$timestamp = strtotime('2007-03-26T00:00:00+00:00');
|
||||
$this->assertSame('Sunday, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Test all parameters.');
|
||||
$this->assertSame('domingo, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'Test translated format.');
|
||||
$this->assertSame('l, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'Test an escaped format string.');
|
||||
$this->assertSame('\\domingo, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'Test format containing backslash character.');
|
||||
$this->assertSame('\\l, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'Test format containing backslash followed by escaped format string.');
|
||||
$this->assertSame('Monday, 26-Mar-07 01:00:00 BST', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Test a different time zone.');
|
||||
$this->assertSame('Thu, 01/01/1970 - 00:00', $formatter->format(0, 'custom', '', 'UTC', 'en'), 'Test custom format with empty string.');
|
||||
|
||||
// Make sure we didn't change the configuration override language.
|
||||
$this->assertSame('en', $language_manager->getConfigOverrideLanguage()->getId(), 'Configuration override language not disturbed,');
|
||||
|
||||
// Test bad format string will use the fallback format.
|
||||
$this->assertSame($formatter->format($timestamp, 'fallback'), $formatter->format($timestamp, 'bad_format_string'), 'Test fallback format.');
|
||||
$this->assertSame('en', $language_manager->getConfigOverrideLanguage()->getId(), 'Configuration override language not disturbed,');
|
||||
|
||||
// Change the default language and timezone.
|
||||
$this->config('system.site')->set('default_langcode', static::LANGCODE)->save();
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
// Reset the language manager so new negotiations attempts will fall back on
|
||||
// on the new language.
|
||||
$language_manager->reset();
|
||||
$this->assertSame('en', $language_manager->getConfigOverrideLanguage()->getId(), 'Configuration override language not disturbed,');
|
||||
|
||||
$this->assertSame('Sunday, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Test a different language.');
|
||||
$this->assertSame('Monday, 26-Mar-07 01:00:00 BST', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London'), 'Test a different time zone.');
|
||||
$this->assertSame('domingo, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T'), 'Test custom date format.');
|
||||
$this->assertSame('domingo, 25. marzo 2007 - 17:00', $formatter->format($timestamp, 'long'), 'Test long date format.');
|
||||
$this->assertSame('25. marzo 2007 - 17:00', $formatter->format($timestamp, 'medium'), 'Test medium date format.');
|
||||
$this->assertSame('2007 Mar 25 - 5:00pm', $formatter->format($timestamp, 'short'), 'Test short date format.');
|
||||
$this->assertSame('25. marzo 2007 - 17:00', $formatter->format($timestamp), 'Test default date format.');
|
||||
// Test HTML time element formats.
|
||||
$this->assertSame('2007-03-25T17:00:00-0700', $formatter->format($timestamp, 'html_datetime'), 'Test html_datetime date format.');
|
||||
$this->assertSame('2007-03-25', $formatter->format($timestamp, 'html_date'), 'Test html_date date format.');
|
||||
$this->assertSame('17:00:00', $formatter->format($timestamp, 'html_time'), 'Test html_time date format.');
|
||||
$this->assertSame('03-25', $formatter->format($timestamp, 'html_yearless_date'), 'Test html_yearless_date date format.');
|
||||
$this->assertSame('2007-W12', $formatter->format($timestamp, 'html_week'), 'Test html_week date format.');
|
||||
$this->assertSame('2007-03', $formatter->format($timestamp, 'html_month'), 'Test html_month date format.');
|
||||
$this->assertSame('2007', $formatter->format($timestamp, 'html_year'), 'Test html_year date format.');
|
||||
|
||||
// Make sure we didn't change the configuration override language.
|
||||
$this->assertSame('en', $language_manager->getConfigOverrideLanguage()->getId(), 'Configuration override language not disturbed,');
|
||||
|
||||
// Test bad format string will use the fallback format.
|
||||
$this->assertSame($formatter->format($timestamp, 'fallback'), $formatter->format($timestamp, 'bad_format_string'), 'Test fallback format.');
|
||||
$this->assertSame('en', $language_manager->getConfigOverrideLanguage()->getId(), 'Configuration override language not disturbed,');
|
||||
|
||||
// HTML is not escaped by the date formatter, it must be escaped later.
|
||||
$this->assertSame("<script>alert('2007');</script>", $formatter->format($timestamp, 'custom', '\<\s\c\r\i\p\t\>\a\l\e\r\t\(\'Y\'\)\;\<\/\s\c\r\i\p\t\>'), 'Script tags not removed from dates.');
|
||||
$this->assertSame('<em>2007</em>', $formatter->format($timestamp, 'custom', '\<\e\m\>Y\<\/\e\m\>'), 'Em tags are not removed from dates.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Datetime;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests timestamp schema.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class TimestampSchemaTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'field', 'field_timestamp_test'];
|
||||
|
||||
/**
|
||||
* Tests if the timestamp field schema is validated.
|
||||
*/
|
||||
public function testTimestampSchema() {
|
||||
$this->installConfig(['field_timestamp_test']);
|
||||
// Make at least an assertion.
|
||||
$this->assertTrue(TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue