Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
105
core/modules/system/src/Tests/Action/ActionUnitTest.php
Normal file
105
core/modules/system/src/Tests/Action/ActionUnitTest.php
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Action\ActionUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Action;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\Core\Action\ActionInterface;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests action plugins.
|
||||
*
|
||||
* @group Action
|
||||
*/
|
||||
class ActionUnitTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('system', 'field', 'user', 'action_test');
|
||||
|
||||
/**
|
||||
* The action manager.
|
||||
*
|
||||
* @var \Drupal\Core\Action\ActionManager
|
||||
*/
|
||||
protected $actionManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->actionManager = $this->container->get('plugin.manager.action');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installSchema('system', array('sequences'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the functionality of test actions.
|
||||
*/
|
||||
public function testOperations() {
|
||||
// Test that actions can be discovered.
|
||||
$definitions = $this->actionManager->getDefinitions();
|
||||
$this->assertTrue(count($definitions) > 1, 'Action definitions are found.');
|
||||
$this->assertTrue(!empty($definitions['action_test_no_type']), 'The test action is among the definitions found.');
|
||||
|
||||
$definition = $this->actionManager->getDefinition('action_test_no_type');
|
||||
$this->assertTrue(!empty($definition), 'The test action definition is found.');
|
||||
|
||||
$definitions = $this->actionManager->getDefinitionsByType('user');
|
||||
$this->assertTrue(empty($definitions['action_test_no_type']), 'An action with no type is not found.');
|
||||
|
||||
// Create an instance of the 'save entity' action.
|
||||
$action = $this->actionManager->createInstance('action_test_save_entity');
|
||||
$this->assertTrue($action instanceof ActionInterface, 'The action implements the correct interface.');
|
||||
|
||||
// Create a new unsaved user.
|
||||
$name = $this->randomMachineName();
|
||||
$user_storage = $this->container->get('entity.manager')->getStorage('user');
|
||||
$account = $user_storage->create(array('name' => $name, 'bundle' => 'user'));
|
||||
$loaded_accounts = $user_storage->loadMultiple();
|
||||
$this->assertEqual(count($loaded_accounts), 0);
|
||||
|
||||
// Execute the 'save entity' action.
|
||||
$action->execute($account);
|
||||
$loaded_accounts = $user_storage->loadMultiple();
|
||||
$this->assertEqual(count($loaded_accounts), 1);
|
||||
$account = reset($loaded_accounts);
|
||||
$this->assertEqual($name, $account->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the dependency calculation of actions.
|
||||
*/
|
||||
public function testDependencies() {
|
||||
// Create a new action that depends on a user role.
|
||||
$action = entity_create('action', array(
|
||||
'id' => 'user_add_role_action.' . RoleInterface::ANONYMOUS_ID,
|
||||
'type' => 'user',
|
||||
'label' => t('Add the anonymous role to the selected users'),
|
||||
'configuration' => array(
|
||||
'rid' => RoleInterface::ANONYMOUS_ID,
|
||||
),
|
||||
'plugin' => 'user_add_role_action',
|
||||
));
|
||||
$action->save();
|
||||
|
||||
$expected = array(
|
||||
'config' => array(
|
||||
'user.role.' . RoleInterface::ANONYMOUS_ID,
|
||||
),
|
||||
'module' => array(
|
||||
'user',
|
||||
),
|
||||
);
|
||||
$this->assertIdentical($expected, $action->calculateDependencies());
|
||||
}
|
||||
|
||||
}
|
||||
91
core/modules/system/src/Tests/Ajax/AjaxFormCacheTest.php
Normal file
91
core/modules/system/src/Tests/Ajax/AjaxFormCacheTest.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\AjaxFormCacheTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests the usage of form caching for AJAX forms.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class AjaxFormCacheTest extends AjaxTestBase {
|
||||
|
||||
/**
|
||||
* Tests the usage of form cache for AJAX forms.
|
||||
*/
|
||||
public function testFormCacheUsage() {
|
||||
/** @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value_expirable */
|
||||
$key_value_expirable = \Drupal::service('keyvalue.expirable')->get('form');
|
||||
$this->drupalLogin($this->rootUser);
|
||||
|
||||
// Ensure that the cache is empty.
|
||||
$this->assertEqual(0, count($key_value_expirable->getAll()));
|
||||
|
||||
// Visit an AJAX form that is not cached, 3 times.
|
||||
$uncached_form_url = Url::fromRoute('ajax_forms_test.commands_form');
|
||||
$this->drupalGet($uncached_form_url);
|
||||
$this->drupalGet($uncached_form_url);
|
||||
$this->drupalGet($uncached_form_url);
|
||||
|
||||
// The number of cache entries should not have changed.
|
||||
$this->assertEqual(0, count($key_value_expirable->getAll()));
|
||||
|
||||
// Visit a form that is explicitly cached, 3 times.
|
||||
$cached_form_url = Url::fromRoute('ajax_forms_test.cached_form');
|
||||
$this->drupalGet($cached_form_url);
|
||||
$this->drupalGet($cached_form_url);
|
||||
$this->drupalGet($cached_form_url);
|
||||
|
||||
// The number of cache entries should be exactly 3.
|
||||
$this->assertEqual(3, count($key_value_expirable->getAll()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests AJAX forms in blocks.
|
||||
*/
|
||||
public function testBlockForms() {
|
||||
$this->container->get('module_installer')->install(['block', 'search']);
|
||||
$this->rebuildContainer();
|
||||
$this->container->get('router.builder')->rebuild();
|
||||
$this->drupalLogin($this->rootUser);
|
||||
|
||||
$this->drupalPlaceBlock('search_form_block', ['weight' => -5]);
|
||||
$this->drupalPlaceBlock('ajax_forms_test_block', ['cache' => ['max_age' => 0]]);
|
||||
|
||||
$this->drupalGet('');
|
||||
$this->drupalPostAjaxForm(NULL, ['test1' => 'option1'], 'test1');
|
||||
$this->assertOptionSelectedWithDrupalSelector('edit-test1', 'option1');
|
||||
$this->assertOptionWithDrupalSelector('edit-test1', 'option3');
|
||||
$this->drupalPostForm(NULL, ['test1' => 'option1'], 'Submit');
|
||||
$this->assertText('Submission successful.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests AJAX forms on pages with a query string.
|
||||
*/
|
||||
public function testQueryString() {
|
||||
$this->container->get('module_installer')->install(['block']);
|
||||
$this->drupalLogin($this->rootUser);
|
||||
|
||||
$this->drupalPlaceBlock('ajax_forms_test_block', ['cache' => ['max_age' => 0]]);
|
||||
|
||||
$url = Url::fromRoute('entity.user.canonical', ['user' => $this->rootUser->id()], ['query' => ['foo' => 'bar']]);
|
||||
$this->drupalGet($url);
|
||||
$this->drupalPostAjaxForm(NULL, ['test1' => 'option1'], 'test1');
|
||||
$url->setOption('query', [
|
||||
'foo' => 'bar',
|
||||
FormBuilderInterface::AJAX_FORM_REQUEST => 1,
|
||||
MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax',
|
||||
]);
|
||||
$this->assertUrl($url);
|
||||
}
|
||||
|
||||
}
|
||||
107
core/modules/system/src/Tests/Ajax/AjaxFormPageCacheTest.php
Normal file
107
core/modules/system/src/Tests/Ajax/AjaxFormPageCacheTest.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\AjaxFormPageCacheTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
/**
|
||||
* Performs tests on AJAX forms in cached pages.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class AjaxFormPageCacheTest extends AjaxTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$config = $this->config('system.performance');
|
||||
$config->set('cache.page.max_age', 300);
|
||||
$config->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the build id of the current form.
|
||||
*/
|
||||
protected function getFormBuildId() {
|
||||
$build_id_fields = $this->xpath('//input[@name="form_build_id"]');
|
||||
$this->assertEqual(count($build_id_fields), 1, 'One form build id field on the page');
|
||||
return (string) $build_id_fields[0]['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple form, then POST to system/ajax to change to it.
|
||||
*/
|
||||
public function testSimpleAJAXFormValue() {
|
||||
$this->drupalGet('ajax_forms_test_get_form');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
|
||||
$build_id_initial = $this->getFormBuildId();
|
||||
|
||||
$edit = ['select' => 'green'];
|
||||
$commands = $this->drupalPostAjaxForm(NULL, $edit, 'select');
|
||||
$build_id_first_ajax = $this->getFormBuildId();
|
||||
$this->assertNotEqual($build_id_initial, $build_id_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission');
|
||||
$expected = [
|
||||
'command' => 'update_build_id',
|
||||
'old' => $build_id_initial,
|
||||
'new' => $build_id_first_ajax,
|
||||
];
|
||||
$this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission');
|
||||
|
||||
$edit = ['select' => 'red'];
|
||||
$commands = $this->drupalPostAjaxForm(NULL, $edit, 'select');
|
||||
$build_id_second_ajax = $this->getFormBuildId();
|
||||
$this->assertNotEqual($build_id_first_ajax, $build_id_second_ajax, 'Build id changes on subsequent AJAX submissions');
|
||||
$expected = [
|
||||
'command' => 'update_build_id',
|
||||
'old' => $build_id_first_ajax,
|
||||
'new' => $build_id_second_ajax,
|
||||
];
|
||||
$this->assertCommand($commands, $expected, 'Build id change command issued on subsequent AJAX submissions');
|
||||
|
||||
// Repeat the test sequence but this time with a page loaded from the cache.
|
||||
$this->drupalGet('ajax_forms_test_get_form');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
|
||||
$build_id_from_cache_initial = $this->getFormBuildId();
|
||||
$this->assertEqual($build_id_initial, $build_id_from_cache_initial, 'Build id is the same as on the first request');
|
||||
|
||||
$edit = ['select' => 'green'];
|
||||
$commands = $this->drupalPostAjaxForm(NULL, $edit, 'select');
|
||||
$build_id_from_cache_first_ajax = $this->getFormBuildId();
|
||||
$this->assertNotEqual($build_id_from_cache_initial, $build_id_from_cache_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission');
|
||||
$this->assertNotEqual($build_id_first_ajax, $build_id_from_cache_first_ajax, 'Build id from first user is not reused');
|
||||
$expected = [
|
||||
'command' => 'update_build_id',
|
||||
'old' => $build_id_from_cache_initial,
|
||||
'new' => $build_id_from_cache_first_ajax,
|
||||
];
|
||||
$this->assertCommand($commands, $expected, 'Build id change command issued on first AJAX submission');
|
||||
|
||||
$edit = ['select' => 'red'];
|
||||
$commands = $this->drupalPostAjaxForm(NULL, $edit, 'select');
|
||||
$build_id_from_cache_second_ajax = $this->getFormBuildId();
|
||||
$this->assertNotEqual($build_id_from_cache_first_ajax, $build_id_from_cache_second_ajax, 'Build id changes on subsequent AJAX submissions');
|
||||
$expected = [
|
||||
'command' => 'update_build_id',
|
||||
'old' => $build_id_from_cache_first_ajax,
|
||||
'new' => $build_id_from_cache_second_ajax,
|
||||
];
|
||||
$this->assertCommand($commands, $expected, 'Build id change command issued on subsequent AJAX submissions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a form that uses an #ajax callback.
|
||||
*
|
||||
* @see \Drupal\system\Tests\Ajax\ElementValidationTest::testAjaxElementValidation()
|
||||
*/
|
||||
public function testAjaxElementValidation() {
|
||||
$edit = ['drivertext' => t('some dumb text')];
|
||||
$this->drupalPostAjaxForm('ajax_validation_test', $edit, 'drivertext');
|
||||
}
|
||||
|
||||
}
|
||||
67
core/modules/system/src/Tests/Ajax/AjaxTestBase.php
Normal file
67
core/modules/system/src/Tests/Ajax/AjaxTestBase.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\AjaxTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Provides a base class for Ajax tests.
|
||||
*/
|
||||
abstract class AjaxTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'ajax_test', 'ajax_forms_test');
|
||||
|
||||
/**
|
||||
* 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://www.php.net/manual/language.operators.array.php).
|
||||
if (array_intersect_key($command, $needle) == $needle) {
|
||||
$found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($found, $message);
|
||||
}
|
||||
}
|
||||
163
core/modules/system/src/Tests/Ajax/CommandsTest.php
Normal file
163
core/modules/system/src/Tests/Ajax/CommandsTest.php
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\CommandsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\AddCssCommand;
|
||||
use Drupal\Core\Ajax\AfterCommand;
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\AlertCommand;
|
||||
use Drupal\Core\Ajax\AppendCommand;
|
||||
use Drupal\Core\Ajax\BeforeCommand;
|
||||
use Drupal\Core\Ajax\ChangedCommand;
|
||||
use Drupal\Core\Ajax\CssCommand;
|
||||
use Drupal\Core\Ajax\DataCommand;
|
||||
use Drupal\Core\Ajax\HtmlCommand;
|
||||
use Drupal\Core\Ajax\InvokeCommand;
|
||||
use Drupal\Core\Ajax\InsertCommand;
|
||||
use Drupal\Core\Ajax\PrependCommand;
|
||||
use Drupal\Core\Ajax\RemoveCommand;
|
||||
use Drupal\Core\Ajax\RestripeCommand;
|
||||
use Drupal\Core\Ajax\SettingsCommand;
|
||||
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
|
||||
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 AjaxTestBase {
|
||||
/**
|
||||
* Tests the various Ajax Commands.
|
||||
*/
|
||||
function testAjaxCommands() {
|
||||
$form_path = 'ajax_forms_test_ajax_commands_form';
|
||||
$web_user = $this->drupalCreateUser(array('access content'));
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$edit = array();
|
||||
|
||||
// Tests the 'add_css' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'add_css' command")));
|
||||
$expected = new AddCssCommand('my/file.css');
|
||||
$this->assertCommand($commands, $expected->render(), "'add_css' AJAX command issued with correct data.");
|
||||
|
||||
// Tests the 'after' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'After': Click to put something after the div")));
|
||||
$expected = new AfterCommand('#after_div', 'This will be placed after');
|
||||
$this->assertCommand($commands, $expected->render(), "'after' AJAX command issued with correct data.");
|
||||
|
||||
// Tests the 'alert' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'Alert': Click to alert")));
|
||||
$expected = new AlertCommand(t('Alert'));
|
||||
$this->assertCommand($commands, $expected->render(), "'alert' AJAX Command issued with correct text.");
|
||||
|
||||
// Tests the 'append' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'Append': Click to append something")));
|
||||
$expected = new AppendCommand('#append_div', 'Appended text');
|
||||
$this->assertCommand($commands, $expected->render(), "'append' AJAX command issued with correct data.");
|
||||
|
||||
// Tests the 'before' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'before': Click to put something before the div")));
|
||||
$expected = new BeforeCommand('#before_div', 'Before text');
|
||||
$this->assertCommand($commands, $expected->render(), "'before' AJAX command issued with correct data.");
|
||||
|
||||
// Tests the 'changed' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed.")));
|
||||
$expected = new ChangedCommand('#changed_div');
|
||||
$this->assertCommand($commands, $expected->render(), "'changed' AJAX command issued with correct selector.");
|
||||
|
||||
// Tests the 'changed' command using the second argument.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed with asterisk.")));
|
||||
$expected = new ChangedCommand('#changed_div', '#changed_div_mark_this');
|
||||
$this->assertCommand($commands, $expected->render(), "'changed' AJAX command (with asterisk) issued with correct selector.");
|
||||
|
||||
// Tests the 'css' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("Set the '#box' div to be blue.")));
|
||||
$expected = new CssCommand('#css_div', array('background-color' => 'blue'));
|
||||
$this->assertCommand($commands, $expected->render(), "'css' AJAX command issued with correct selector.");
|
||||
|
||||
// Tests the 'data' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX data command: Issue command.")));
|
||||
$expected = new DataCommand('#data_div', 'testkey', 'testvalue');
|
||||
$this->assertCommand($commands, $expected->render(), "'data' AJAX command issued with correct key and value.");
|
||||
|
||||
// Tests the 'html' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX html: Replace the HTML in a selector.")));
|
||||
$expected = new HtmlCommand('#html_div', 'replacement text');
|
||||
$this->assertCommand($commands, $expected->render(), "'html' AJAX command issued with correct data.");
|
||||
|
||||
// Tests the 'insert' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX insert: Let client insert based on #ajax['method'].")));
|
||||
$expected = new InsertCommand('#insert_div', 'insert replacement text');
|
||||
$this->assertCommand($commands, $expected->render(), "'insert' AJAX command issued with correct data.");
|
||||
|
||||
// Tests the 'invoke' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX invoke command: Invoke addClass() method.")));
|
||||
$expected = new InvokeCommand('#invoke_div', 'addClass', array('error'));
|
||||
$this->assertCommand($commands, $expected->render(), "'invoke' AJAX command issued with correct method and argument.");
|
||||
|
||||
// Tests the 'prepend' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something")));
|
||||
$expected = new PrependCommand('#prepend_div', 'prepended text');
|
||||
$this->assertCommand($commands, $expected->render(), "'prepend' AJAX command issued with correct data.");
|
||||
|
||||
// Tests the 'remove' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'remove': Click to remove text")));
|
||||
$expected = new RemoveCommand('#remove_text');
|
||||
$this->assertCommand($commands, $expected->render(), "'remove' AJAX command issued with correct command and selector.");
|
||||
|
||||
// Tests the 'restripe' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'restripe' command")));
|
||||
$expected = new RestripeCommand('#restripe_table');
|
||||
$this->assertCommand($commands, $expected->render(), "'restripe' AJAX command issued with correct selector.");
|
||||
|
||||
// Tests the 'settings' command.
|
||||
$commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'settings' command")));
|
||||
$expected = new SettingsCommand(array('ajax_forms_test' => array('foo' => 42)));
|
||||
$this->assertCommand($commands, $expected->render(), "'settings' AJAX command issued with correct data.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
}
|
||||
|
||||
}
|
||||
209
core/modules/system/src/Tests/Ajax/DialogTest.php
Normal file
209
core/modules/system/src/Tests/Ajax/DialogTest.php
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\DialogTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Performs tests on opening and manipulating dialogs via AJAX commands.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class DialogTest extends AjaxTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('ajax_test', 'ajax_forms_test', 'contact');
|
||||
|
||||
/**
|
||||
* Test sending non-JS and AJAX requests to open and manipulate modals.
|
||||
*/
|
||||
public function testDialog() {
|
||||
$this->drupalLogin($this->drupalCreateUser(array('administer contact forms')));
|
||||
// Ensure the elements render without notices or exceptions.
|
||||
$this->drupalGet('ajax-test/dialog');
|
||||
|
||||
// Set up variables for this test.
|
||||
$dialog_renderable = \Drupal\ajax_test\Controller\AjaxTestController::dialogContents();
|
||||
$dialog_contents = \Drupal::service('renderer')->renderRoot($dialog_renderable);
|
||||
$modal_expected_response = array(
|
||||
'command' => 'openDialog',
|
||||
'selector' => '#drupal-modal',
|
||||
'settings' => NULL,
|
||||
'data' => $dialog_contents,
|
||||
'dialogOptions' => array(
|
||||
'modal' => TRUE,
|
||||
'title' => 'AJAX Dialog contents',
|
||||
),
|
||||
);
|
||||
$form_expected_response = array(
|
||||
'command' => 'openDialog',
|
||||
'selector' => '#drupal-modal',
|
||||
'settings' => NULL,
|
||||
'dialogOptions' => array(
|
||||
'modal' => TRUE,
|
||||
'title' => 'Ajax Form contents',
|
||||
),
|
||||
);
|
||||
$entity_form_expected_response = array(
|
||||
'command' => 'openDialog',
|
||||
'selector' => '#drupal-modal',
|
||||
'settings' => NULL,
|
||||
'dialogOptions' => array(
|
||||
'modal' => TRUE,
|
||||
'title' => 'Add contact form',
|
||||
),
|
||||
);
|
||||
$normal_expected_response = array(
|
||||
'command' => 'openDialog',
|
||||
'selector' => '#ajax-test-dialog-wrapper-1',
|
||||
'settings' => NULL,
|
||||
'data' => $dialog_contents,
|
||||
'dialogOptions' => array(
|
||||
'modal' => FALSE,
|
||||
'title' => 'AJAX Dialog contents',
|
||||
),
|
||||
);
|
||||
$no_target_expected_response = array(
|
||||
'command' => 'openDialog',
|
||||
'selector' => '#drupal-dialog-ajax-testdialog-contents',
|
||||
'settings' => NULL,
|
||||
'data' => $dialog_contents,
|
||||
'dialogOptions' => array(
|
||||
'modal' => FALSE,
|
||||
'title' => 'AJAX Dialog contents',
|
||||
),
|
||||
);
|
||||
$close_expected_response = array(
|
||||
'command' => 'closeDialog',
|
||||
'selector' => '#ajax-test-dialog-wrapper-1',
|
||||
'persist' => FALSE,
|
||||
);
|
||||
|
||||
// Check that requesting a modal dialog without JS goes to a page.
|
||||
$this->drupalGet('ajax-test/dialog-contents');
|
||||
$this->assertRaw($dialog_contents, 'Non-JS modal dialog page present.');
|
||||
|
||||
// Emulate going to the JS version of the page and check the JSON response.
|
||||
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal')));
|
||||
$this->assertEqual($modal_expected_response, $ajax_result[3], 'Modal dialog JSON response matches.');
|
||||
|
||||
// Check that requesting a "normal" dialog without JS goes to a page.
|
||||
$this->drupalGet('ajax-test/dialog-contents');
|
||||
$this->assertRaw($dialog_contents, 'Non-JS normal dialog page present.');
|
||||
|
||||
// Emulate going to the JS version of the page and check the JSON response.
|
||||
// This needs to use WebTestBase::drupalPostAjaxForm() so that the correct
|
||||
// dialog options are sent.
|
||||
$ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(
|
||||
// We have to mock a form element to make drupalPost submit from a link.
|
||||
'textfield' => 'test',
|
||||
), array(), 'ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog')), array(), NULL, array(
|
||||
'submit' => array(
|
||||
'dialogOptions[target]' => 'ajax-test-dialog-wrapper-1',
|
||||
)
|
||||
));
|
||||
$this->assertEqual($normal_expected_response, $ajax_result[3], 'Normal dialog JSON response matches.');
|
||||
|
||||
// Emulate going to the JS version of the page and check the JSON response.
|
||||
// This needs to use WebTestBase::drupalPostAjaxForm() so that the correct
|
||||
// dialog options are sent.
|
||||
$ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(
|
||||
// We have to mock a form element to make drupalPost submit from a link.
|
||||
'textfield' => 'test',
|
||||
), array(), 'ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog')), array(), NULL, array(
|
||||
// Don't send a target.
|
||||
'submit' => array()
|
||||
));
|
||||
// Make sure the selector ID starts with the right string.
|
||||
$this->assert(strpos($ajax_result[3]['selector'], $no_target_expected_response['selector']) === 0, 'Selector starts with right string.');
|
||||
unset($ajax_result[3]['selector']);
|
||||
unset($no_target_expected_response['selector']);
|
||||
$this->assertEqual($no_target_expected_response, $ajax_result[3], 'Normal dialog with no target JSON response matches.');
|
||||
|
||||
// Emulate closing the dialog via an AJAX request. There is no non-JS
|
||||
// version of this test.
|
||||
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-close');
|
||||
$this->assertEqual($close_expected_response, $ajax_result[0], 'Close dialog JSON response matches.');
|
||||
|
||||
// Test submitting via a POST request through the button for modals. This
|
||||
// approach more accurately reflects the real responses by Drupal because
|
||||
// all of the necessary page variables are emulated.
|
||||
$ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(), 'button1');
|
||||
|
||||
// Check that CSS and JavaScript are "added" to the page dynamically.
|
||||
$this->assertTrue(in_array('core/drupal.dialog.ajax', explode(',', $ajax_result[0]['settings']['ajaxPageState']['libraries'])), 'core/drupal.dialog.ajax library is added to the page.');
|
||||
$dialog_css_exists = strpos($ajax_result[1]['data'], 'dialog.css') !== FALSE;
|
||||
$this->assertTrue($dialog_css_exists, 'jQuery UI dialog CSS added to the page.');
|
||||
$dialog_js_exists = strpos($ajax_result[2]['data'], 'dialog-min.js') !== FALSE;
|
||||
$this->assertTrue($dialog_js_exists, 'jQuery UI dialog JS added to the page.');
|
||||
$dialog_js_exists = strpos($ajax_result[2]['data'], 'dialog.ajax.js') !== FALSE;
|
||||
$this->assertTrue($dialog_js_exists, 'Drupal dialog JS added to the page.');
|
||||
|
||||
// Check that the response matches the expected value.
|
||||
$this->assertEqual($modal_expected_response, $ajax_result[4], 'POST request modal dialog JSON response matches.');
|
||||
|
||||
// Abbreviated test for "normal" dialogs, testing only the difference.
|
||||
$ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(), 'button2');
|
||||
$this->assertEqual($normal_expected_response, $ajax_result[4], 'POST request normal dialog JSON response matches.');
|
||||
|
||||
// Check that requesting a form dialog without JS goes to a page.
|
||||
$this->drupalGet('ajax-test/dialog-form');
|
||||
// Check we get a chunk of the code, we can't test the whole form as form
|
||||
// build id and token with be different.
|
||||
$form = $this->xpath("//form[@id='ajax-test-form']");
|
||||
$this->assertTrue(!empty($form), 'Non-JS form page present.');
|
||||
|
||||
// Emulate going to the JS version of the form and check the JSON response.
|
||||
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-form', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal')));
|
||||
$expected_ajax_settings = [
|
||||
'edit-preview' => [
|
||||
'callback' => '::preview',
|
||||
'event' => 'click',
|
||||
'url' => Url::fromRoute('ajax_test.dialog_form', [], ['query' => [
|
||||
MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal',
|
||||
FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
|
||||
]])->toString(),
|
||||
'dialogType' => 'ajax',
|
||||
'submit' => [
|
||||
'_triggering_element_name' => 'op',
|
||||
'_triggering_element_value' => 'Preview',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEqual($expected_ajax_settings, $ajax_result[0]['settings']['ajax']);
|
||||
$this->setRawContent($ajax_result[3]['data']);
|
||||
// Remove the data, the form build id and token will never match.
|
||||
unset($ajax_result[3]['data']);
|
||||
$form = $this->xpath("//form[@id='ajax-test-form']");
|
||||
$this->assertTrue(!empty($form), 'Modal dialog JSON contains form.');
|
||||
$this->assertEqual($form_expected_response, $ajax_result[3]);
|
||||
|
||||
// Check that requesting an entity form dialog without JS goes to a page.
|
||||
$this->drupalGet('admin/structure/contact/add');
|
||||
// Check we get a chunk of the code, we can't test the whole form as form
|
||||
// build id and token with be different.
|
||||
$form = $this->xpath("//form[@id='contact-form-add-form']");
|
||||
$this->assertTrue(!empty($form), 'Non-JS entity form page present.');
|
||||
|
||||
// Emulate going to the JS version of the form and check the JSON response.
|
||||
$ajax_result = $this->drupalGetAjax('admin/structure/contact/add', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal')));
|
||||
$this->setRawContent($ajax_result[3]['data']);
|
||||
// Remove the data, the form build id and token will never match.
|
||||
unset($ajax_result[3]['data']);
|
||||
$form = $this->xpath("//form[@id='contact-form-add-form']");
|
||||
$this->assertTrue(!empty($form), 'Modal dialog JSON contains entity form.');
|
||||
$this->assertEqual($entity_form_expected_response, $ajax_result[3]);
|
||||
}
|
||||
|
||||
}
|
||||
42
core/modules/system/src/Tests/Ajax/ElementValidationTest.php
Normal file
42
core/modules/system/src/Tests/Ajax/ElementValidationTest.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\ElementValidationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
/**
|
||||
* Various tests of AJAX behavior.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class ElementValidationTest extends AjaxTestBase {
|
||||
/**
|
||||
* Tries to post an Ajax change to a form that has a validated element.
|
||||
*
|
||||
* The drivertext field is Ajax-enabled. An additional field is not, but
|
||||
* is set to be a required field. In this test the required field is not
|
||||
* filled in, and we want to see if the activation of the "drivertext"
|
||||
* Ajax-enabled field fails due to the required field being empty.
|
||||
*/
|
||||
function testAjaxElementValidation() {
|
||||
$edit = array('drivertext' => t('some dumb text'));
|
||||
|
||||
// Post with 'drivertext' as the triggering element.
|
||||
$this->drupalPostAjaxForm('ajax_validation_test', $edit, 'drivertext');
|
||||
// Look for a validation failure in the resultant JSON.
|
||||
$this->assertNoText(t('Error message'), 'No error message in resultant JSON');
|
||||
$this->assertText('ajax_forms_test_validation_form_callback invoked', 'The correct callback was invoked');
|
||||
|
||||
$this->drupalGet('ajax_validation_test');
|
||||
$edit = array('drivernumber' => 12345);
|
||||
|
||||
// Post with 'drivernumber' as the triggering element.
|
||||
$this->drupalPostAjaxForm('ajax_validation_test', $edit, 'drivernumber');
|
||||
// Look for a validation failure in the resultant JSON.
|
||||
$this->assertNoText(t('Error message'), 'No error message in resultant JSON');
|
||||
$this->assertText('ajax_forms_test_validation_number_form_callback invoked', 'The correct callback was invoked');
|
||||
}
|
||||
}
|
||||
63
core/modules/system/src/Tests/Ajax/FormValuesTest.php
Normal file
63
core/modules/system/src/Tests/Ajax/FormValuesTest.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\FormValuesTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\DataCommand;
|
||||
|
||||
/**
|
||||
* Tests that form values are properly delivered to AJAX callbacks.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class FormValuesTest extends AjaxTestBase {
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser(array('access content')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits forms with select and checkbox elements via Ajax.
|
||||
*/
|
||||
function testSimpleAjaxFormValue() {
|
||||
// Verify form values of a select element.
|
||||
foreach (array('red', 'green', 'blue') as $item) {
|
||||
$edit = array(
|
||||
'select' => $item,
|
||||
);
|
||||
$commands = $this->drupalPostAjaxForm('ajax_forms_test_get_form', $edit, 'select');
|
||||
$expected = new DataCommand('#ajax_selected_color', 'form_state_value_select', $item);
|
||||
$this->assertCommand($commands, $expected->render(), 'Verification of AJAX form values from a selectbox issued with a correct value.');
|
||||
}
|
||||
|
||||
// Verify form values of a checkbox element.
|
||||
foreach (array(FALSE, TRUE) as $item) {
|
||||
$edit = array(
|
||||
'checkbox' => $item,
|
||||
);
|
||||
$commands = $this->drupalPostAjaxForm('ajax_forms_test_get_form', $edit, 'checkbox');
|
||||
$expected = new DataCommand('#ajax_checkbox_value', 'form_state_value_select', (int) $item);
|
||||
$this->assertCommand($commands, $expected->render(), 'Verification of AJAX form values from a checkbox issued with a correct value.');
|
||||
}
|
||||
|
||||
// Verify that AJAX elements with invalid callbacks return error code 500.
|
||||
// Ensure the test error log is empty before these tests.
|
||||
$this->assertNoErrorsLogged();
|
||||
foreach (array('null', 'empty', 'nonexistent') as $key) {
|
||||
$element_name = 'select_' . $key . '_callback';
|
||||
$edit = array(
|
||||
$element_name => 'red',
|
||||
);
|
||||
$commands = $this->drupalPostAjaxForm('ajax_forms_test_get_form', $edit, $element_name);
|
||||
$this->assertResponse(500);
|
||||
}
|
||||
// The exceptions are expected. Do not interpret them as a test failure.
|
||||
// Not using File API; a potential error must trigger a PHP warning.
|
||||
unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
|
||||
}
|
||||
}
|
||||
219
core/modules/system/src/Tests/Ajax/FrameworkTest.php
Normal file
219
core/modules/system/src/Tests/Ajax/FrameworkTest.php
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\FrameworkTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\AddCssCommand;
|
||||
use Drupal\Core\Ajax\AlertCommand;
|
||||
use Drupal\Core\Ajax\AppendCommand;
|
||||
use Drupal\Core\Ajax\HtmlCommand;
|
||||
use Drupal\Core\Ajax\PrependCommand;
|
||||
use Drupal\Core\Ajax\SettingsCommand;
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
|
||||
/**
|
||||
* Performs tests on AJAX framework functions.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class FrameworkTest extends AjaxTestBase {
|
||||
/**
|
||||
* Ensures \Drupal\Core\Ajax\AjaxResponse::ajaxRender() returns JavaScript settings from the page request.
|
||||
*/
|
||||
public function testAJAXRender() {
|
||||
// Verify that settings command is generated if JavaScript settings exist.
|
||||
$commands = $this->drupalGetAjax('ajax-test/render');
|
||||
$expected = new SettingsCommand(array('ajax' => 'test'), TRUE);
|
||||
$this->assertCommand($commands, $expected->render(), '\Drupal\Core\Ajax\AjaxResponse::ajaxRender() loads JavaScript settings.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests AjaxResponse::prepare() AJAX commands ordering.
|
||||
*/
|
||||
public function testOrder() {
|
||||
$expected_commands = array();
|
||||
|
||||
// Expected commands, in a very specific order.
|
||||
$asset_resolver = \Drupal::service('asset.resolver');
|
||||
$css_collection_renderer = \Drupal::service('asset.css.collection_renderer');
|
||||
$js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
|
||||
$renderer = \Drupal::service('renderer');
|
||||
$expected_commands[0] = new SettingsCommand(array('ajax' => 'test'), TRUE);
|
||||
$build['#attached']['library'][] = 'ajax_test/order-css-command';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
$css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE));
|
||||
$expected_commands[1] = new AddCssCommand($renderer->renderRoot($css_render_array));
|
||||
$build['#attached']['library'][] = 'ajax_test/order-header-js-command';
|
||||
$build['#attached']['library'][] = 'ajax_test/order-footer-js-command';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, FALSE);
|
||||
$js_header_render_array = $js_collection_renderer->render($js_assets_header);
|
||||
$js_footer_render_array = $js_collection_renderer->render($js_assets_footer);
|
||||
$expected_commands[2] = new PrependCommand('head', $js_header_render_array);
|
||||
$expected_commands[3] = new AppendCommand('body', $js_footer_render_array);
|
||||
$expected_commands[4] = new HtmlCommand('body', 'Hello, world!');
|
||||
|
||||
// Load any page with at least one CSS file, at least one JavaScript file
|
||||
// and at least one #ajax-powered element. The latter is an assumption of
|
||||
// drupalPostAjaxForm(), the two former are assumptions of
|
||||
// AjaxResponse::ajaxRender().
|
||||
// @todo refactor AJAX Framework + tests to make less assumptions.
|
||||
$this->drupalGet('ajax_forms_test_lazy_load_form');
|
||||
|
||||
// Verify AJAX command order — this should always be the order:
|
||||
// 1. JavaScript settings
|
||||
// 2. CSS files
|
||||
// 3. JavaScript files in the header
|
||||
// 4. JavaScript files in the footer
|
||||
// 5. Any other AJAX commands, in whatever order they were added.
|
||||
$commands = $this->drupalPostAjaxForm(NULL, array(), NULL, 'ajax-test/order', array(), array(), NULL, array());
|
||||
$this->assertCommand(array_slice($commands, 0, 1), $expected_commands[0]->render(), 'Settings command is first.');
|
||||
$this->assertCommand(array_slice($commands, 1, 1), $expected_commands[1]->render(), 'CSS command is second (and CSS files are ordered correctly).');
|
||||
$this->assertCommand(array_slice($commands, 2, 1), $expected_commands[2]->render(), 'Header JS command is third.');
|
||||
$this->assertCommand(array_slice($commands, 3, 1), $expected_commands[3]->render(), 'Footer JS command is fourth.');
|
||||
$this->assertCommand(array_slice($commands, 4, 1), $expected_commands[4]->render(), 'HTML command is fifth.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of an error alert command.
|
||||
*/
|
||||
public function testAJAXRenderError() {
|
||||
// Verify custom error message.
|
||||
$edit = array(
|
||||
'message' => 'Custom error message.',
|
||||
);
|
||||
$commands = $this->drupalGetAjax('ajax-test/render-error', array('query' => $edit));
|
||||
$expected = new AlertCommand($edit['message']);
|
||||
$this->assertCommand($commands, $expected->render(), 'Custom error message is output.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request.
|
||||
*/
|
||||
public function testLazyLoad() {
|
||||
$asset_resolver = \Drupal::service('asset.resolver');
|
||||
$css_collection_renderer = \Drupal::service('asset.css.collection_renderer');
|
||||
$js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$expected = array(
|
||||
'setting_name' => 'ajax_forms_test_lazy_load_form_submit',
|
||||
'setting_value' => 'executed',
|
||||
'library_1' => 'system/admin',
|
||||
'library_2' => 'system/drupal.system',
|
||||
);
|
||||
|
||||
// Get the base page.
|
||||
$this->drupalGet('ajax_forms_test_lazy_load_form');
|
||||
$original_settings = $this->getDrupalSettings();
|
||||
$original_libraries = explode(',', $original_settings['ajaxPageState']['libraries']);
|
||||
|
||||
// Verify that the base page doesn't have the settings and files that are to
|
||||
// be lazy loaded as part of the next requests.
|
||||
$this->assertTrue(!isset($original_settings[$expected['setting_name']]), format_string('Page originally lacks the %setting, as expected.', array('%setting' => $expected['setting_name'])));
|
||||
$this->assertTrue(!in_array($expected['library_1'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', array('%library' => $expected['library_1'])));
|
||||
$this->assertTrue(!in_array($expected['library_2'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', array('%library' => $expected['library_2'])));
|
||||
|
||||
// Calculate the expected CSS and JS.
|
||||
$assets = new AttachedAssets();
|
||||
$assets->setLibraries([$expected['library_1']])
|
||||
->setAlreadyLoadedLibraries($original_libraries);
|
||||
$css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE));
|
||||
$expected_css_html = $renderer->renderRoot($css_render_array);
|
||||
|
||||
$assets->setLibraries([$expected['library_2']])
|
||||
->setAlreadyLoadedLibraries($original_libraries);
|
||||
$js_assets = $asset_resolver->getJsAssets($assets, FALSE)[1];
|
||||
unset($js_assets['drupalSettings']);
|
||||
$js_render_array = $js_collection_renderer->render($js_assets);
|
||||
$expected_js_html = $renderer->renderRoot($js_render_array);
|
||||
|
||||
// Submit the AJAX request without triggering files getting added.
|
||||
$commands = $this->drupalPostAjaxForm(NULL, array('add_files' => FALSE), array('op' => t('Submit')));
|
||||
$new_settings = $this->getDrupalSettings();
|
||||
$new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']);
|
||||
|
||||
// Verify the setting was not added when not expected.
|
||||
$this->assertTrue(!isset($new_settings[$expected['setting_name']]), format_string('Page still lacks the %setting, as expected.', array('%setting' => $expected['setting_name'])));
|
||||
$this->assertTrue(!in_array($expected['library_1'], $new_libraries), format_string('Page still lacks the %library library, as expected.', array('%library' => $expected['library_1'])));
|
||||
$this->assertTrue(!in_array($expected['library_2'], $new_libraries), format_string('Page still lacks the %library library, as expected.', array('%library' => $expected['library_2'])));
|
||||
// Verify a settings command does not add CSS or scripts to drupalSettings
|
||||
// and no command inserts the corresponding tags on the page.
|
||||
$found_settings_command = FALSE;
|
||||
$found_markup_command = FALSE;
|
||||
foreach ($commands as $command) {
|
||||
if ($command['command'] == 'settings' && (array_key_exists('css', $command['settings']['ajaxPageState']) || array_key_exists('js', $command['settings']['ajaxPageState']))) {
|
||||
$found_settings_command = TRUE;
|
||||
}
|
||||
if (isset($command['data']) && ($command['data'] == $expected_js_html || $command['data'] == $expected_css_html)) {
|
||||
$found_markup_command = TRUE;
|
||||
}
|
||||
}
|
||||
$this->assertFalse($found_settings_command, format_string('Page state still lacks the %library_1 and %library_2 libraries, as expected.', array('%library_1' => $expected['library_1'], '%library_2' => $expected['library_2'])));
|
||||
$this->assertFalse($found_markup_command, format_string('Page still lacks the %library_1 and %library_2 libraries, as expected.', array('%library_1' => $expected['library_1'], '%library_2' => $expected['library_2'])));
|
||||
|
||||
// Submit the AJAX request and trigger adding files.
|
||||
$commands = $this->drupalPostAjaxForm(NULL, array('add_files' => TRUE), array('op' => t('Submit')));
|
||||
$new_settings = $this->getDrupalSettings();
|
||||
$new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']);
|
||||
|
||||
// Verify the expected setting was added, both to drupalSettings, and as
|
||||
// the first AJAX command.
|
||||
$this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', array('%setting' => $expected['setting_name'])));
|
||||
$expected_command = new SettingsCommand(array($expected['setting_name'] => $expected['setting_value']), TRUE);
|
||||
$this->assertCommand(array_slice($commands, 0, 1), $expected_command->render(), format_string('The settings command was first.'));
|
||||
|
||||
// Verify the expected CSS file was added, both to drupalSettings, and as
|
||||
// the second AJAX command for inclusion into the HTML.
|
||||
$this->assertTrue(in_array($expected['library_1'], $new_libraries), format_string('Page state now has the %library library.', array('%library' => $expected['library_1'])));
|
||||
$this->assertCommand(array_slice($commands, 1, 1), array('data' => $expected_css_html), format_string('Page now has the %library library.', array('%library' => $expected['library_1'])));
|
||||
|
||||
// Verify the expected JS file was added, both to drupalSettings, and as
|
||||
// the third AJAX command for inclusion into the HTML. By testing for an
|
||||
// exact HTML string containing the SCRIPT tag, we also ensure that
|
||||
// unexpected JavaScript code, such as a jQuery.extend() that would
|
||||
// potentially clobber rather than properly merge settings, didn't
|
||||
// accidentally get added.
|
||||
$this->assertTrue(in_array($expected['library_2'], $new_libraries), format_string('Page state now has the %library library.', array('%library' => $expected['library_2'])));
|
||||
$this->assertCommand(array_slice($commands, 2, 1), array('data' => $expected_js_html), format_string('Page now has the %library library.', array('%library' => $expected['library_2'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that drupalSettings.currentPath is not updated on AJAX requests.
|
||||
*/
|
||||
public function testCurrentPathChange() {
|
||||
$commands = $this->drupalPostAjaxForm('ajax_forms_test_lazy_load_form', array('add_files' => FALSE), array('op' => t('Submit')));
|
||||
foreach ($commands as $command) {
|
||||
if ($command['command'] == 'settings') {
|
||||
$this->assertFalse(isset($command['settings']['currentPath']), 'Value of drupalSettings.currentPath is not updated after an AJAX request.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that overridden CSS files are not added during lazy load.
|
||||
*/
|
||||
public function testLazyLoadOverriddenCSS() {
|
||||
// The test theme overrides system.module.css without an implementation,
|
||||
// thereby removing it.
|
||||
\Drupal::service('theme_handler')->install(array('test_theme'));
|
||||
$this->config('system.theme')
|
||||
->set('default', 'test_theme')
|
||||
->save();
|
||||
|
||||
// This gets the form, and emulates an Ajax submission on it, including
|
||||
// adding markup to the HEAD and BODY for any lazy loaded JS/CSS files.
|
||||
$this->drupalPostAjaxForm('ajax_forms_test_lazy_load_form', array('add_files' => TRUE), array('op' => t('Submit')));
|
||||
|
||||
// Verify that the resulting HTML does not load the overridden CSS file.
|
||||
// We add a "?" to the assertion, because drupalSettings may include
|
||||
// information about the file; we only really care about whether it appears
|
||||
// in a LINK or STYLE tag, for which Drupal always adds a query string for
|
||||
// cache control.
|
||||
$this->assertNoText('system.module.css?', 'Ajax lazy loading does not add overridden CSS files.');
|
||||
}
|
||||
}
|
||||
100
core/modules/system/src/Tests/Ajax/MultiFormTest.php
Normal file
100
core/modules/system/src/Tests/Ajax/MultiFormTest.php
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Ajax\MultiFormTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Ajax;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Tests that AJAX-enabled forms work when multiple instances of the same form
|
||||
* are on a page.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class MultiFormTest extends AjaxTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('form_test');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Page'));
|
||||
|
||||
// Create a multi-valued field for 'page' nodes to use for Ajax testing.
|
||||
$field_name = 'field_ajax_test';
|
||||
entity_create('field_storage_config', array(
|
||||
'entity_type' => 'node',
|
||||
'field_name' => $field_name,
|
||||
'type' => 'text',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
))->save();
|
||||
entity_create('field_config', array(
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'page',
|
||||
))->save();
|
||||
entity_get_form_display('node', 'page', 'default')
|
||||
->setComponent($field_name, array('type' => 'text_textfield'))
|
||||
->save();
|
||||
|
||||
// Login a user who can create 'page' nodes.
|
||||
$this->drupalLogin ($this->drupalCreateUser(array('create page content')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that pages with the 'node_page_form' included twice work correctly.
|
||||
*/
|
||||
function testMultiForm() {
|
||||
// HTML IDs for elements within the field are potentially modified with
|
||||
// each Ajax submission, but these variables are stable and help target the
|
||||
// desired elements.
|
||||
$field_name = 'field_ajax_test';
|
||||
|
||||
$form_xpath = '//form[starts-with(@id, "node-page-form")]';
|
||||
$field_xpath = '//div[contains(@class, "field-name-field-ajax-test")]';
|
||||
$button_name = $field_name . '_add_more';
|
||||
$button_value = t('Add another item');
|
||||
$button_xpath_suffix = '//input[@name="' . $button_name . '"]';
|
||||
$field_items_xpath_suffix = '//input[@type="text"]';
|
||||
|
||||
// Ensure the initial page contains both node forms and the correct number
|
||||
// of field items and "add more" button for the multi-valued field within
|
||||
// each form.
|
||||
$this->drupalGet('form-test/two-instances-of-same-form');
|
||||
|
||||
$fields = $this->xpath($form_xpath . $field_xpath);
|
||||
$this->assertEqual(count($fields), 2);
|
||||
foreach ($fields as $field) {
|
||||
$this->assertEqual(count($field->xpath('.' . $field_items_xpath_suffix)), 1, 'Found the correct number of field items on the initial page.');
|
||||
$this->assertFieldsByValue($field->xpath('.' . $button_xpath_suffix), NULL, 'Found the "add more" button on the initial page.');
|
||||
}
|
||||
|
||||
$this->assertNoDuplicateIds(t('Initial page contains unique IDs'), 'Other');
|
||||
|
||||
// Submit the "add more" button of each form twice. After each corresponding
|
||||
// page update, ensure the same as above.
|
||||
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$forms = $this->xpath($form_xpath);
|
||||
foreach ($forms as $offset => $form) {
|
||||
$form_html_id = (string) $form['id'];
|
||||
$this->drupalPostAjaxForm(NULL, array(), array($button_name => $button_value), NULL, array(), array(), $form_html_id);
|
||||
$form = $this->xpath($form_xpath)[$offset];
|
||||
$field = $form->xpath('.' . $field_xpath);
|
||||
|
||||
$this->assertEqual(count($field[0]->xpath('.' . $field_items_xpath_suffix)), $i+2, 'Found the correct number of field items after an AJAX submission.');
|
||||
$this->assertFieldsByValue($field[0]->xpath('.' . $button_xpath_suffix), NULL, 'Found the "add more" button after an AJAX submission.');
|
||||
$this->assertNoDuplicateIds(t('Updated page contains unique IDs'), 'Other');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Asset\LibraryDiscoveryIntegrationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Asset;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the element info.
|
||||
*
|
||||
* @group Render
|
||||
*/
|
||||
class LibraryDiscoveryIntegrationTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->container->get('theme_handler')->install(['test_theme', 'classy']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the element info can be altered by themes.
|
||||
*/
|
||||
public function testElementInfoByTheme() {
|
||||
/** @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');
|
||||
|
||||
/** @var \Drupal\Core\Render\ElementInfoManagerInterface $element_info */
|
||||
$library_discovery = $this->container->get('library.discovery');
|
||||
|
||||
$theme_manager->setActiveTheme($theme_initializer->getActiveThemeByName('test_theme'));
|
||||
$this->assertTrue($library_discovery->getLibraryByName('test_theme', 'kitten'));
|
||||
}
|
||||
|
||||
}
|
||||
64
core/modules/system/src/Tests/Batch/PageTest.php
Normal file
64
core/modules/system/src/Tests/Batch/PageTest.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Batch\PageTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Batch;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests the content of the progress page.
|
||||
*
|
||||
* @group Batch
|
||||
*/
|
||||
class PageTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('batch_test');
|
||||
|
||||
/**
|
||||
* Tests that the batch API progress page uses the correct theme.
|
||||
*/
|
||||
function testBatchProgressPageTheme() {
|
||||
// Make sure that the page which starts the batch (an administrative page)
|
||||
// is using a different theme than would normally be used by the batch API.
|
||||
$this->container->get('theme_handler')->install(array('seven', 'bartik'));
|
||||
$this->config('system.theme')
|
||||
->set('default', 'bartik')
|
||||
->set('admin', 'seven')
|
||||
->save();
|
||||
|
||||
// Log in as an administrator who can see the administrative theme.
|
||||
$admin_user = $this->drupalCreateUser(array('view the administration theme'));
|
||||
$this->drupalLogin($admin_user);
|
||||
// Visit an administrative page that runs a test batch, and check that the
|
||||
// theme that was used during batch execution (which the batch callback
|
||||
// function saved as a variable) matches the theme used on the
|
||||
// administrative page.
|
||||
$this->drupalGet('admin/batch-test/test-theme');
|
||||
// The stack should contain the name of the theme used on the progress
|
||||
// page.
|
||||
$this->assertEqual(batch_test_stack(), array('seven'), 'A progressive batch correctly uses the theme of the page that started the batch.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the batch API progress page shows the title correctly.
|
||||
*/
|
||||
function testBatchProgressPageTitle() {
|
||||
// Visit an administrative page that runs a test batch, and check that the
|
||||
// title shown during batch execution (which the batch callback function
|
||||
// saved as a variable) matches the theme used on the administrative page.
|
||||
$this->drupalGet('batch-test/test-title');
|
||||
// The stack should contain the title shown on the progress page.
|
||||
$this->assertEqual(batch_test_stack(), ['Batch Test'], 'The batch title is shown on the batch page.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
}
|
||||
|
||||
}
|
||||
279
core/modules/system/src/Tests/Batch/ProcessingTest.php
Normal file
279
core/modules/system/src/Tests/Batch/ProcessingTest.php
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Batch\ProcessingTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Batch;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests batch processing in form and non-form workflow.
|
||||
*
|
||||
* @group Batch
|
||||
*/
|
||||
class ProcessingTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('batch_test');
|
||||
|
||||
/**
|
||||
* Tests batches triggered outside of form submission.
|
||||
*/
|
||||
function testBatchNoForm() {
|
||||
// Displaying the page triggers batch 1.
|
||||
$this->drupalGet('batch-test/no-form');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_1'), 'Batch for step 2 performed successfully.');
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), 'Execution order was correct.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests batches defined in a form submit handler.
|
||||
*/
|
||||
function testBatchForm() {
|
||||
// Batch 0: no operation.
|
||||
$edit = array('batch' => 'batch_0');
|
||||
$this->drupalPostForm('batch-test', $edit, 'Submit');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_0'), 'Batch with no operation performed successfully.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
|
||||
// Batch 1: several simple operations.
|
||||
$edit = array('batch' => 'batch_1');
|
||||
$this->drupalPostForm('batch-test', $edit, 'Submit');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_1'), 'Batch with simple operations performed successfully.');
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), 'Execution order was correct.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
|
||||
// Batch 2: one multistep operation.
|
||||
$edit = array('batch' => 'batch_2');
|
||||
$this->drupalPostForm('batch-test', $edit, 'Submit');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_2'), 'Batch with multistep operation performed successfully.');
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), 'Execution order was correct.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
|
||||
// Batch 3: simple + multistep combined.
|
||||
$edit = array('batch' => 'batch_3');
|
||||
$this->drupalPostForm('batch-test', $edit, 'Submit');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_3'), 'Batch with simple and multistep operations performed successfully.');
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_3'), 'Execution order was correct.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
|
||||
// Batch 4: nested batch.
|
||||
$edit = array('batch' => 'batch_4');
|
||||
$this->drupalPostForm('batch-test', $edit, 'Submit');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_4'), 'Nested batch performed successfully.');
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_4'), 'Execution order was correct.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests batches defined in a multistep form.
|
||||
*/
|
||||
function testBatchFormMultistep() {
|
||||
$this->drupalGet('batch-test/multistep');
|
||||
$this->assertText('step 1', 'Form is displayed in step 1.');
|
||||
|
||||
// First step triggers batch 1.
|
||||
$this->drupalPostForm(NULL, array(), 'Submit');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_1'), 'Batch for step 1 performed successfully.');
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), 'Execution order was correct.');
|
||||
$this->assertText('step 2', 'Form is displayed in step 2.');
|
||||
|
||||
// Second step triggers batch 2.
|
||||
$this->drupalPostForm(NULL, array(), 'Submit');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_2'), 'Batch for step 2 performed successfully.');
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), 'Execution order was correct.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests batches defined in different submit handlers on the same form.
|
||||
*/
|
||||
function testBatchFormMultipleBatches() {
|
||||
// Batches 1, 2 and 3 are triggered in sequence by different submit
|
||||
// handlers. Each submit handler modify the submitted 'value'.
|
||||
$value = rand(0, 255);
|
||||
$edit = array('value' => $value);
|
||||
$this->drupalPostForm('batch-test/chained', $edit, 'Submit');
|
||||
// Check that result messages are present and in the correct order.
|
||||
$this->assertBatchMessages($this->_resultMessages('chained'), 'Batches defined in separate submit handlers performed successfully.');
|
||||
// The stack contains execution order of batch callbacks and submit
|
||||
// handlers and logging of corresponding $form_state->getValues().
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('chained', $value), 'Execution order was correct, and $form_state is correctly persisted.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests batches defined in a programmatically submitted form.
|
||||
*
|
||||
* Same as above, but the form is submitted through drupal_form_execute().
|
||||
*/
|
||||
function testBatchFormProgrammatic() {
|
||||
// Batches 1, 2 and 3 are triggered in sequence by different submit
|
||||
// handlers. Each submit handler modify the submitted 'value'.
|
||||
$value = rand(0, 255);
|
||||
$this->drupalGet('batch-test/programmatic/' . $value);
|
||||
// Check that result messages are present and in the correct order.
|
||||
$this->assertBatchMessages($this->_resultMessages('chained'), 'Batches defined in separate submit handlers performed successfully.');
|
||||
// The stack contains execution order of batch callbacks and submit
|
||||
// handlers and logging of corresponding $form_state->getValues().
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('chained', $value), 'Execution order was correct, and $form_state is correctly persisted.');
|
||||
$this->assertText('Got out of a programmatic batched form.', 'Page execution continues normally.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test form submission during a batch operation.
|
||||
*/
|
||||
function testDrupalFormSubmitInBatch() {
|
||||
// Displaying the page triggers a batch that programmatically submits a
|
||||
// form.
|
||||
$value = rand(0, 255);
|
||||
$this->drupalGet('batch-test/nested-programmatic/' . $value);
|
||||
$this->assertEqual(batch_test_stack(), array('mock form submitted with value = ' . $value), '\Drupal::formBuilder()->submitForm() ran successfully within a batch operation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests batches that return $context['finished'] > 1 do in fact complete.
|
||||
*
|
||||
* @see https://www.drupal.org/node/600836
|
||||
*/
|
||||
function testBatchLargePercentage() {
|
||||
// Displaying the page triggers batch 5.
|
||||
$this->drupalGet('batch-test/large-percentage');
|
||||
$this->assertBatchMessages($this->_resultMessages('batch_5'), 'Batch for step 2 performed successfully.');
|
||||
$this->assertEqual(batch_test_stack(), $this->_resultStack('batch_5'), 'Execution order was correct.');
|
||||
$this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers a pass if the texts were found in order in the raw content.
|
||||
*
|
||||
* @param $texts
|
||||
* Array of raw strings to look for .
|
||||
* @param $message
|
||||
* Message to display.
|
||||
*
|
||||
* @return
|
||||
* TRUE on pass, FALSE on fail.
|
||||
*/
|
||||
function assertBatchMessages($texts, $message) {
|
||||
$pattern = '|' . implode('.*', $texts) .'|s';
|
||||
return $this->assertPattern($pattern, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expected execution stacks for the test batches.
|
||||
*/
|
||||
function _resultStack($id, $value = 0) {
|
||||
$stack = array();
|
||||
switch ($id) {
|
||||
case 'batch_1':
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$stack[] = "op 1 id $i";
|
||||
}
|
||||
break;
|
||||
|
||||
case 'batch_2':
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$stack[] = "op 2 id $i";
|
||||
}
|
||||
break;
|
||||
|
||||
case 'batch_3':
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$stack[] = "op 1 id $i";
|
||||
}
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$stack[] = "op 2 id $i";
|
||||
}
|
||||
for ($i = 6; $i <= 10; $i++) {
|
||||
$stack[] = "op 1 id $i";
|
||||
}
|
||||
for ($i = 6; $i <= 10; $i++) {
|
||||
$stack[] = "op 2 id $i";
|
||||
}
|
||||
break;
|
||||
|
||||
case 'batch_4':
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$stack[] = "op 1 id $i";
|
||||
}
|
||||
$stack[] = 'setting up batch 2';
|
||||
for ($i = 6; $i <= 10; $i++) {
|
||||
$stack[] = "op 1 id $i";
|
||||
}
|
||||
$stack = array_merge($stack, $this->_resultStack('batch_2'));
|
||||
break;
|
||||
|
||||
case 'batch_5':
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$stack[] = "op 5 id $i";
|
||||
}
|
||||
break;
|
||||
|
||||
case 'chained':
|
||||
$stack[] = 'submit handler 1';
|
||||
$stack[] = 'value = ' . $value;
|
||||
$stack = array_merge($stack, $this->_resultStack('batch_1'));
|
||||
$stack[] = 'submit handler 2';
|
||||
$stack[] = 'value = ' . ($value + 1);
|
||||
$stack = array_merge($stack, $this->_resultStack('batch_2'));
|
||||
$stack[] = 'submit handler 3';
|
||||
$stack[] = 'value = ' . ($value + 2);
|
||||
$stack[] = 'submit handler 4';
|
||||
$stack[] = 'value = ' . ($value + 3);
|
||||
$stack = array_merge($stack, $this->_resultStack('batch_3'));
|
||||
break;
|
||||
}
|
||||
return $stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expected result messages for the test batches.
|
||||
*/
|
||||
function _resultMessages($id) {
|
||||
$messages = array();
|
||||
|
||||
switch ($id) {
|
||||
case 'batch_0':
|
||||
$messages[] = 'results for batch 0<br>none';
|
||||
break;
|
||||
|
||||
case 'batch_1':
|
||||
$messages[] = 'results for batch 1<br>op 1: processed 10 elements';
|
||||
break;
|
||||
|
||||
case 'batch_2':
|
||||
$messages[] = 'results for batch 2<br>op 2: processed 10 elements';
|
||||
break;
|
||||
|
||||
case 'batch_3':
|
||||
$messages[] = 'results for batch 3<br>op 1: processed 10 elements<br>op 2: processed 10 elements';
|
||||
break;
|
||||
|
||||
case 'batch_4':
|
||||
$messages[] = 'results for batch 4<br>op 1: processed 10 elements';
|
||||
$messages = array_merge($messages, $this->_resultMessages('batch_2'));
|
||||
break;
|
||||
|
||||
case 'batch_5':
|
||||
$messages[] = 'results for batch 5<br>op 5: processed 10 elements';
|
||||
break;
|
||||
|
||||
case 'chained':
|
||||
$messages = array_merge($messages, $this->_resultMessages('batch_1'));
|
||||
$messages = array_merge($messages, $this->_resultMessages('batch_2'));
|
||||
$messages = array_merge($messages, $this->_resultMessages('batch_3'));
|
||||
break;
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
}
|
||||
314
core/modules/system/src/Tests/Block/SystemMenuBlockTest.php
Normal file
314
core/modules/system/src/Tests/Block/SystemMenuBlockTest.php
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Block\SystemMenuBlockTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Block;
|
||||
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\system\Tests\Routing\MockRouteProvider;
|
||||
use Drupal\Tests\Core\Menu\MenuLinkMock;
|
||||
use Drupal\user\Entity\User;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\system\Plugin\Block\SystemMenuBlock.
|
||||
*
|
||||
* @group Block
|
||||
* @todo Expand test coverage to all SystemMenuBlock functionality, including
|
||||
* block_menu_delete().
|
||||
*
|
||||
* @see \Drupal\system\Plugin\Derivative\SystemMenuBlock
|
||||
* @see \Drupal\system\Plugin\Block\SystemMenuBlock
|
||||
*/
|
||||
class SystemMenuBlockTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array(
|
||||
'system',
|
||||
'block',
|
||||
'menu_test',
|
||||
'menu_link_content',
|
||||
'field',
|
||||
'user',
|
||||
'link',
|
||||
);
|
||||
|
||||
/**
|
||||
* The block under test.
|
||||
*
|
||||
* @var \Drupal\system\Plugin\Block\SystemMenuBlock
|
||||
*/
|
||||
protected $block;
|
||||
|
||||
/**
|
||||
* The menu for testing.
|
||||
*
|
||||
* @var \Drupal\system\MenuInterface
|
||||
*/
|
||||
protected $menu;
|
||||
|
||||
/**
|
||||
* The menu link tree service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkTree
|
||||
*/
|
||||
protected $linkTree;
|
||||
|
||||
/**
|
||||
* The menu link plugin manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
|
||||
*/
|
||||
protected $menuLinkManager;
|
||||
|
||||
/**
|
||||
* The block manager service.
|
||||
*
|
||||
* @var \Drupal\Core\block\BlockManagerInterface
|
||||
*/
|
||||
protected $blockManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', 'sequences');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installSchema('system', array('router'));
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
|
||||
$account = User::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'status' => 1,
|
||||
]);
|
||||
$account->save();
|
||||
$this->container->get('current_user')->setAccount($account);
|
||||
|
||||
$this->menuLinkManager = $this->container->get('plugin.manager.menu.link');
|
||||
$this->linkTree = $this->container->get('menu.link_tree');
|
||||
$this->blockManager = $this->container->get('plugin.manager.block');
|
||||
|
||||
$routes = new RouteCollection();
|
||||
$requirements = array('_access' => 'TRUE');
|
||||
$options = array('_access_checks' => array('access_check.default'));
|
||||
$routes->add('example1', new Route('/example1', array(), $requirements, $options));
|
||||
$routes->add('example2', new Route('/example2', array(), $requirements, $options));
|
||||
$routes->add('example3', new Route('/example3', array(), $requirements, $options));
|
||||
$routes->add('example4', new Route('/example4', array(), $requirements, $options));
|
||||
$routes->add('example5', new Route('/example5', array(), $requirements, $options));
|
||||
$routes->add('example6', new Route('/example6', array(), $requirements, $options));
|
||||
$routes->add('example7', new Route('/example7', array(), $requirements, $options));
|
||||
$routes->add('example8', new Route('/example8', array(), $requirements, $options));
|
||||
|
||||
$mock_route_provider = new MockRouteProvider($routes);
|
||||
$this->container->set('router.route_provider', $mock_route_provider);
|
||||
|
||||
// Add a new custom menu.
|
||||
$menu_name = 'mock';
|
||||
$label = $this->randomMachineName(16);
|
||||
|
||||
$this->menu = entity_create('menu', array(
|
||||
'id' => $menu_name,
|
||||
'label' => $label,
|
||||
'description' => 'Description text',
|
||||
));
|
||||
$this->menu->save();
|
||||
|
||||
// This creates a tree with the following structure:
|
||||
// - 1
|
||||
// - 2
|
||||
// - 3
|
||||
// - 4
|
||||
// - 5
|
||||
// - 7
|
||||
// - 6
|
||||
// - 8
|
||||
// With link 6 being the only external link.
|
||||
$links = array(
|
||||
1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '', 'weight' => 0)),
|
||||
2 => MenuLinkMock::create(array('id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => '', 'route_parameters' => array('foo' => 'bar'), 'weight' => 1)),
|
||||
3 => MenuLinkMock::create(array('id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'weight' => 2)),
|
||||
4 => MenuLinkMock::create(array('id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3', 'weight' => 3)),
|
||||
5 => MenuLinkMock::create(array('id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '', 'expanded' => TRUE, 'weight' => 4)),
|
||||
6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '', 'weight' => 5)),
|
||||
7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => 'test.example5', 'weight' => 6)),
|
||||
8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '', 'weight' => 7)),
|
||||
);
|
||||
foreach ($links as $instance) {
|
||||
$this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests calculation of a system menu block's configuration dependencies.
|
||||
*/
|
||||
public function testSystemMenuBlockConfigDependencies() {
|
||||
|
||||
$block = entity_create('block', array(
|
||||
'plugin' => 'system_menu_block:' . $this->menu->id(),
|
||||
'region' => 'footer',
|
||||
'id' => 'machinename',
|
||||
'theme' => 'stark',
|
||||
));
|
||||
|
||||
$dependencies = $block->calculateDependencies();
|
||||
$expected = array(
|
||||
'config' => array(
|
||||
'system.menu.' . $this->menu->id()
|
||||
),
|
||||
'module' => array(
|
||||
'system'
|
||||
),
|
||||
'theme' => array(
|
||||
'stark'
|
||||
),
|
||||
);
|
||||
$this->assertIdentical($expected, $dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the config start level and depth.
|
||||
*/
|
||||
public function testConfigLevelDepth() {
|
||||
// Helper function to generate a configured block instance.
|
||||
$place_block = function ($level, $depth) {
|
||||
return $this->blockManager->createInstance('system_menu_block:' . $this->menu->id(), array(
|
||||
'region' => 'footer',
|
||||
'id' => 'machinename',
|
||||
'theme' => 'stark',
|
||||
'level' => $level,
|
||||
'depth' => $depth,
|
||||
));
|
||||
};
|
||||
|
||||
// All the different block instances we're going to test.
|
||||
$blocks = [
|
||||
'all' => $place_block(1, 0),
|
||||
'level_1_only' => $place_block(1, 1),
|
||||
'level_2_only' => $place_block(2, 1),
|
||||
'level_3_only' => $place_block(3, 1),
|
||||
'level_1_and_beyond' => $place_block(1, 0),
|
||||
'level_2_and_beyond' => $place_block(2, 0),
|
||||
'level_3_and_beyond' => $place_block(3, 0),
|
||||
];
|
||||
|
||||
// Scenario 1: test all block instances when there's no active trail.
|
||||
$no_active_trail_expectations = [];
|
||||
$no_active_trail_expectations['all'] = [
|
||||
'test.example1' => [],
|
||||
'test.example2' => [],
|
||||
'test.example5' => [
|
||||
'test.example7' => [],
|
||||
],
|
||||
'test.example6' => [],
|
||||
'test.example8' => [],
|
||||
];
|
||||
$no_active_trail_expectations['level_1_only'] = [
|
||||
'test.example1' => [],
|
||||
'test.example2' => [],
|
||||
'test.example5' => [],
|
||||
'test.example6' => [],
|
||||
'test.example8' => [],
|
||||
];
|
||||
$no_active_trail_expectations['level_2_only'] = [
|
||||
'test.example7' => [],
|
||||
];
|
||||
$no_active_trail_expectations['level_3_only'] = [];
|
||||
$no_active_trail_expectations['level_1_and_beyond'] = $no_active_trail_expectations['all'];
|
||||
$no_active_trail_expectations['level_2_and_beyond'] = $no_active_trail_expectations['level_2_only'];
|
||||
$no_active_trail_expectations['level_3_and_beyond'] = [];
|
||||
foreach ($blocks as $id => $block) {
|
||||
$block_build = $block->build();
|
||||
$items = isset($block_build['#items']) ? $block_build['#items'] : [];
|
||||
$this->assertIdentical($no_active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), format_string('Menu block %id with no active trail renders the expected tree.', ['%id' => $id]));
|
||||
}
|
||||
|
||||
// Scenario 2: test all block instances when there's an active trail.
|
||||
$route = $this->container->get('router.route_provider')->getRouteByName('example3');
|
||||
$request = new Request();
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'example3');
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $route);
|
||||
$this->container->get('request_stack')->push($request);
|
||||
// \Drupal\Core\Menu\MenuActiveTrail uses the cache collector pattern, which
|
||||
// includes static caching. Since this second scenario simulates a second
|
||||
// request, we must also simulate it for the MenuActiveTrail service, by
|
||||
// clearing the cache collector's static cache.
|
||||
\Drupal::service('menu.active_trail')->clear();
|
||||
|
||||
$active_trail_expectations = [];
|
||||
$active_trail_expectations['all'] = [
|
||||
'test.example1' => [],
|
||||
'test.example2' => [
|
||||
'test.example3' => [
|
||||
'test.example4' => [],
|
||||
]
|
||||
],
|
||||
'test.example5' => [
|
||||
'test.example7' => [],
|
||||
],
|
||||
'test.example6' => [],
|
||||
'test.example8' => [],
|
||||
];
|
||||
$active_trail_expectations['level_1_only'] = [
|
||||
'test.example1' => [],
|
||||
'test.example2' => [],
|
||||
'test.example5' => [],
|
||||
'test.example6' => [],
|
||||
'test.example8' => [],
|
||||
];
|
||||
$active_trail_expectations['level_2_only'] = [
|
||||
'test.example3' => [],
|
||||
'test.example7' => [],
|
||||
];
|
||||
$active_trail_expectations['level_3_only'] = [
|
||||
'test.example4' => [],
|
||||
];
|
||||
$active_trail_expectations['level_1_and_beyond'] = $active_trail_expectations['all'];
|
||||
$active_trail_expectations['level_2_and_beyond'] = [
|
||||
'test.example3' => [
|
||||
'test.example4' => [],
|
||||
],
|
||||
'test.example7' => [],
|
||||
];
|
||||
$active_trail_expectations['level_3_and_beyond'] = $active_trail_expectations['level_3_only'];
|
||||
foreach ($blocks as $id => $block) {
|
||||
$block_build = $block->build();
|
||||
$items = isset($block_build['#items']) ? $block_build['#items'] : [];
|
||||
$this->assertIdentical($active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), format_string('Menu block %id with an active trail renders the expected tree.', ['%id' => $id]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to allow for easy menu link tree structure assertions.
|
||||
*
|
||||
* Converts the result of MenuLinkTree::build() in a "menu link ID tree".
|
||||
*
|
||||
* @param array $build
|
||||
* The return value of of MenuLinkTree::build()
|
||||
*
|
||||
* @return array
|
||||
* The "menu link ID tree" representation of the given render array.
|
||||
*/
|
||||
protected function convertBuiltMenuToIdTree(array $build) {
|
||||
$level = [];
|
||||
foreach (Element::children($build) as $id) {
|
||||
$level[$id] = [];
|
||||
if (isset($build[$id]['below'])) {
|
||||
$level[$id] = $this->convertBuiltMenuToIdTree($build[$id]['below']);
|
||||
}
|
||||
}
|
||||
return $level;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Bootstrap\DrupalSetMessageTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Bootstrap;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests drupal_set_message() and related functions.
|
||||
*
|
||||
* @group Bootstrap
|
||||
*/
|
||||
class DrupalSetMessageTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system_test');
|
||||
|
||||
/**
|
||||
* Tests setting messages and removing one before it is displayed.
|
||||
*/
|
||||
function testSetRemoveMessages() {
|
||||
// The page at system-test/drupal-set-message sets two messages and then
|
||||
// removes the first before it is displayed.
|
||||
$this->drupalGet('system-test/drupal-set-message');
|
||||
$this->assertNoText('First message (removed).');
|
||||
$this->assertText('Second message (not removed).');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting duplicated messages.
|
||||
*/
|
||||
function testDuplicatedMessages() {
|
||||
$this->drupalGet('system-test/drupal-set-message');
|
||||
$this->assertUniqueText('Non Duplicated message');
|
||||
$this->assertNoUniqueText('Duplicated message');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Bootstrap\GetFilenameUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Bootstrap;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that drupal_get_filename() works correctly.
|
||||
*
|
||||
* @group Bootstrap
|
||||
*/
|
||||
class GetFilenameUnitTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests that drupal_get_filename() works when the file is not in database.
|
||||
*/
|
||||
function testDrupalGetFilename() {
|
||||
// drupal_get_profile() is using obtaining the profile from state if the
|
||||
// install_state global is not set.
|
||||
global $install_state;
|
||||
$install_state['parameters']['profile'] = 'testing';
|
||||
|
||||
// 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(array('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');
|
||||
|
||||
|
||||
// Generate a non-existing module name.
|
||||
$non_existing_module = uniqid("", TRUE);
|
||||
|
||||
// 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', TRUE);
|
||||
return;
|
||||
}
|
||||
throw new \ErrorException($message, 0, $severity, $file, $line);
|
||||
});
|
||||
$this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for an item that does not exist returns NULL.');
|
||||
$this->assertTrue(\Drupal::state()->get('get_filename_test_triggered_error'), 'Searching for an item that does not exist triggers an error.');
|
||||
// Restore the original error handler.
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Bootstrap\ResettableStaticUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Bootstrap;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that drupal_static() and drupal_static_reset() work.
|
||||
*
|
||||
* @group Bootstrap
|
||||
*/
|
||||
class ResettableStaticUnitTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests drupal_static() function.
|
||||
*
|
||||
* Tests that a variable reference returned by drupal_static() gets reset when
|
||||
* drupal_static_reset() is called.
|
||||
*/
|
||||
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.');
|
||||
}
|
||||
}
|
||||
47
core/modules/system/src/Tests/Cache/ApcuBackendUnitTest.php
Normal file
47
core/modules/system/src/Tests/Cache/ApcuBackendUnitTest.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\ApcuBackendUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\Core\Cache\ApcuBackend;
|
||||
|
||||
/**
|
||||
* Tests the APCu cache backend.
|
||||
*
|
||||
* @group Cache
|
||||
* @requires extension apc
|
||||
*/
|
||||
class ApcuBackendUnitTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
protected function checkRequirements() {
|
||||
$requirements = parent::checkRequirements();
|
||||
if (!extension_loaded('apc')) {
|
||||
$requirements[] = 'APC extension not found.';
|
||||
}
|
||||
else {
|
||||
if (version_compare(phpversion('apc'), '3.1.1', '<')) {
|
||||
$requirements[] = 'APC extension must be newer than 3.1.1 for APCIterator support.';
|
||||
}
|
||||
if (PHP_SAPI === 'cli' && !ini_get('apc.enable_cli')) {
|
||||
$requirements[] = 'apc.enable_cli must be enabled to run this test.';
|
||||
}
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
protected function createCacheBackend($bin) {
|
||||
return new ApcuBackend($bin, $this->databasePrefix, \Drupal::service('cache_tags.invalidator.checksum'));
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
foreach ($this->cachebackends as $bin => $cachebackend) {
|
||||
$this->cachebackends[$bin]->removeBin();
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides test assertions for testing page-level cache contexts & tags.
|
||||
*
|
||||
* Can be used by test classes that extend \Drupal\simpletest\WebTestBase.
|
||||
*/
|
||||
trait AssertPageCacheContextsAndTagsTrait {
|
||||
|
||||
/**
|
||||
* Enables page caching.
|
||||
*/
|
||||
protected function enablePageCaching() {
|
||||
$config = $this->config('system.performance');
|
||||
$config->set('cache.page.max_age', 300);
|
||||
$config->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific header value as array.
|
||||
*
|
||||
* @param string $header_name
|
||||
* The header name.
|
||||
*
|
||||
* @return string[]
|
||||
* The header value, potentially exploded by spaces.
|
||||
*/
|
||||
protected function getCacheHeaderValues($header_name) {
|
||||
$header_value = $this->drupalGetHeader($header_name);
|
||||
if (empty($header_value)) {
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
return explode(' ', $header_value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts page cache miss, then hit for the given URL; checks cache headers.
|
||||
*
|
||||
* @param \Drupal\Core\Url $url
|
||||
* The URL to test.
|
||||
* @param string[] $expected_contexts
|
||||
* The expected cache contexts for the given URL.
|
||||
* @param string[] $expected_tags
|
||||
* The expected cache tags for the given URL.
|
||||
*/
|
||||
protected function assertPageCacheContextsAndTags(Url $url, array $expected_contexts, array $expected_tags) {
|
||||
$absolute_url = $url->setAbsolute()->toString();
|
||||
sort($expected_contexts);
|
||||
sort($expected_tags);
|
||||
|
||||
// Assert cache miss + expected cache contexts + tags.
|
||||
$this->drupalGet($absolute_url);
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
|
||||
$this->assertCacheTags($expected_tags);
|
||||
$this->assertCacheContexts($expected_contexts);
|
||||
|
||||
// Assert cache hit + expected cache contexts + tags.
|
||||
$this->drupalGet($absolute_url);
|
||||
$this->assertCacheTags($expected_tags);
|
||||
$this->assertCacheContexts($expected_contexts);
|
||||
|
||||
// Assert page cache item + expected cache tags.
|
||||
$cid_parts = array($url->setAbsolute()->toString(), 'html');
|
||||
$cid = implode(':', $cid_parts);
|
||||
$cache_entry = \Drupal::cache('render')->get($cid);
|
||||
sort($cache_entry->tags);
|
||||
$this->assertEqual($cache_entry->tags, $expected_tags);
|
||||
$this->debugCacheTags($cache_entry->tags, $expected_tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides debug information for cache tags.
|
||||
*
|
||||
* @param string[] $actual_tags
|
||||
* The actual cache tags.
|
||||
* @param string[] $expected_tags
|
||||
* The expected cache tags.
|
||||
*/
|
||||
protected function debugCacheTags(array $actual_tags, array $expected_tags) {
|
||||
if ($actual_tags !== $expected_tags) {
|
||||
debug('Missing cache tags: ' . implode(',', array_diff($expected_tags, $actual_tags)));
|
||||
debug('Unwanted cache tags: ' . implode(',', array_diff($actual_tags, $expected_tags)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that some cache tags are present in the current response.
|
||||
*
|
||||
* @param string[] $expected_tags
|
||||
* The expected tags.
|
||||
*/
|
||||
protected function assertCacheTags(array $expected_tags) {
|
||||
$actual_tags = $this->getCacheHeaderValues('X-Drupal-Cache-Tags');
|
||||
sort($expected_tags);
|
||||
sort($actual_tags);
|
||||
$this->assertIdentical($actual_tags, $expected_tags);
|
||||
$this->debugCacheTags($actual_tags, $expected_tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that some cache contexts are present in the current response.
|
||||
*
|
||||
* @param string[] $expected_contexts
|
||||
* The expected cache contexts.
|
||||
* @param string $message
|
||||
* (optional) A verbose message to output.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertCacheContexts(array $expected_contexts, $message = NULL) {
|
||||
$actual_contexts = $this->getCacheHeaderValues('X-Drupal-Cache-Contexts');
|
||||
sort($expected_contexts);
|
||||
sort($actual_contexts);
|
||||
$return = $this->assertIdentical($actual_contexts, $expected_contexts, $message);
|
||||
if (!$return) {
|
||||
debug('Missing cache contexts: ' . implode(',', array_diff($actual_contexts, $expected_contexts)));
|
||||
debug('Unwanted cache contexts: ' . implode(',', array_diff($expected_contexts, $actual_contexts)));
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the max age header.
|
||||
*
|
||||
* @param int $max_age
|
||||
*/
|
||||
protected function assertCacheMaxAge($max_age) {
|
||||
$cache_control_header = $this->drupalGetHeader('Cache-Control');
|
||||
if (strpos($cache_control_header, 'max-age:' . $max_age) === FALSE) {
|
||||
debug('Expected max-age:' . $max_age . '; Response max-age:' . $cache_control_header);
|
||||
}
|
||||
$this->assertTrue(strpos($cache_control_header, 'max-age:' . $max_age));
|
||||
}
|
||||
|
||||
}
|
||||
34
core/modules/system/src/Tests/Cache/BackendChainUnitTest.php
Normal file
34
core/modules/system/src/Tests/Cache/BackendChainUnitTest.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\BackendChainUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\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 BackendChainUnitTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
protected function createCacheBackend($bin) {
|
||||
$chain = new BackendChain($bin);
|
||||
|
||||
// We need to create some various backends in the chain.
|
||||
$chain
|
||||
->appendBackend(new MemoryBackend('foo'))
|
||||
->prependBackend(new MemoryBackend('bar'))
|
||||
->appendBackend(new MemoryBackend('baz'));
|
||||
|
||||
\Drupal::service('cache_tags.invalidator')->addInvalidator($chain);
|
||||
|
||||
return $chain;
|
||||
}
|
||||
|
||||
}
|
||||
90
core/modules/system/src/Tests/Cache/CacheTestBase.php
Normal file
90
core/modules/system/src/Tests/Cache/CacheTestBase.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\CacheTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Provides helper methods for cache tests.
|
||||
*/
|
||||
abstract class CacheTestBase extends WebTestBase {
|
||||
|
||||
protected $defaultBin = 'render';
|
||||
protected $defaultCid = 'test_temporary';
|
||||
protected $defaultValue = 'CacheTest';
|
||||
|
||||
/**
|
||||
* Checks whether or not a cache entry exists.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache id.
|
||||
* @param $var
|
||||
* The variable the cache should contain.
|
||||
* @param $bin
|
||||
* The bin the cache item was stored in.
|
||||
* @return
|
||||
* TRUE on pass, FALSE on fail.
|
||||
*/
|
||||
protected function checkCacheExists($cid, $var, $bin = NULL) {
|
||||
if ($bin == NULL) {
|
||||
$bin = $this->defaultBin;
|
||||
}
|
||||
|
||||
$cached = \Drupal::cache($bin)->get($cid);
|
||||
|
||||
return isset($cached->data) && $cached->data == $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a cache entry exists.
|
||||
*
|
||||
* @param $message
|
||||
* Message to display.
|
||||
* @param $var
|
||||
* The variable the cache should contain.
|
||||
* @param $cid
|
||||
* The cache id.
|
||||
* @param $bin
|
||||
* The bin the cache item was stored in.
|
||||
*/
|
||||
protected function assertCacheExists($message, $var = NULL, $cid = NULL, $bin = NULL) {
|
||||
if ($bin == NULL) {
|
||||
$bin = $this->defaultBin;
|
||||
}
|
||||
if ($cid == NULL) {
|
||||
$cid = $this->defaultCid;
|
||||
}
|
||||
if ($var == NULL) {
|
||||
$var = $this->defaultValue;
|
||||
}
|
||||
|
||||
$this->assertTrue($this->checkCacheExists($cid, $var, $bin), $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a cache entry has been removed.
|
||||
*
|
||||
* @param $message
|
||||
* Message to display.
|
||||
* @param $cid
|
||||
* The cache id.
|
||||
* @param $bin
|
||||
* The bin the cache item was stored in.
|
||||
*/
|
||||
function assertCacheRemoved($message, $cid = NULL, $bin = NULL) {
|
||||
if ($bin == NULL) {
|
||||
$bin = $this->defaultBin;
|
||||
}
|
||||
if ($cid == NULL) {
|
||||
$cid = $this->defaultCid;
|
||||
}
|
||||
|
||||
$cached = \Drupal::cache($bin)->get($cid);
|
||||
$this->assertFalse($cached, $message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\ChainedFastBackendUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\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 ChainedFastBackendUnitTest 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);
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
||||
46
core/modules/system/src/Tests/Cache/ClearTest.php
Normal file
46
core/modules/system/src/Tests/Cache/ClearTest.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\ClearTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
/**
|
||||
* Tests our clearing is done the proper way.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
use Drupal\Core\Cache\Cache;
|
||||
|
||||
class ClearTest extends CacheTestBase {
|
||||
|
||||
protected function setUp() {
|
||||
$this->defaultBin = 'render';
|
||||
$this->defaultValue = $this->randomMachineName(10);
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests drupal_flush_all_caches().
|
||||
*/
|
||||
function testFlushAllCaches() {
|
||||
// Create cache entries for each flushed cache bin.
|
||||
$bins = Cache::getBins();
|
||||
$this->assertTrue($bins, 'Cache::getBins() returned bins to flush.');
|
||||
foreach ($bins as $bin => $cache_backend) {
|
||||
$cid = 'test_cid_clear' . $bin;
|
||||
$cache_backend->set($cid, $this->defaultValue);
|
||||
}
|
||||
|
||||
// Remove all caches then make sure that they are cleared.
|
||||
drupal_flush_all_caches();
|
||||
|
||||
foreach ($bins as $bin => $cache_backend) {
|
||||
$cid = 'test_cid_clear' . $bin;
|
||||
$this->assertFalse($this->checkCacheExists($cid, $this->defaultValue, $bin), format_string('All cache entries removed from @bin.', array('@bin' => $bin)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\DatabaseBackendTagTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\simpletest\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 = array('system');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function containerBuild(ContainerBuilder $container) {
|
||||
parent::containerBuild($container);
|
||||
// Change container to database cache backends.
|
||||
$container
|
||||
->register('cache_factory', 'Drupal\Core\Cache\CacheFactory')
|
||||
->addArgument(new Reference('settings'))
|
||||
->addMethodCall('setContainer', array(new Reference('service_container')));
|
||||
}
|
||||
|
||||
public function testTagInvalidations() {
|
||||
// Create cache entry in multiple bins.
|
||||
$tags = array('test_tag:1', 'test_tag:2', 'test_tag:3');
|
||||
$bins = array('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', array('invalidations'))->condition('tag', 'test_tag:2')->execute()->fetchField());
|
||||
Cache::invalidateTags(array('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', array('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,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\DatabaseBackendUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\Core\Cache\DatabaseBackend;
|
||||
|
||||
/**
|
||||
* Unit test of the database backend using the generic cache unit test base.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class DatabaseBackendUnitTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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->assertIdentical($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->assertIdentical($cached_value_short, $backend->get($cid_short)->data, "Backend contains the correct value for short, non-ASCII cache id.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,624 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\GenericCacheBackendUnitTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\simpletest\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.
|
||||
*/
|
||||
protected abstract 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 = array();
|
||||
$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->assertIdentical(FALSE, $backend->get('test1'), "Backend does not contain data for cache id test1.");
|
||||
$with_backslash = array('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->assertIdentical($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->assertIdentical(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2.");
|
||||
$backend->set('test2', array('value' => 3), REQUEST_TIME + 3);
|
||||
$cached = $backend->get('test2');
|
||||
$this->assert(is_object($cached), "Backend returned an object for cache id test2.");
|
||||
$this->assertIdentical(array('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->assertIdentical(FALSE, $backend->get('test4'), "Backend does not contain data for cache id test4.");
|
||||
$with_eof = array('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->assertIdentical($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->assertIdentical(FALSE, $backend->get('test5'), "Backend does not contain data for cache id test5.");
|
||||
$with_eof_and_semicolon = array('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->assertIdentical($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 = array('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->assertIdentical($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.
|
||||
try {
|
||||
$backend->set('exception_test', 'value', Cache::PERMANENT, ['node' => [3, 5, 7]]);
|
||||
$this->fail('::set() was called with invalid cache tags, no exception was thrown.');
|
||||
}
|
||||
catch (\LogicException $e) {
|
||||
$this->pass('::set() was called with invalid cache tags, an exception was thrown.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Drupal\Core\Cache\CacheBackendInterface::delete().
|
||||
*/
|
||||
public function testDelete() {
|
||||
$backend = $this->getCacheBackend();
|
||||
|
||||
$this->assertIdentical(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->assertIdentical(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->assertIdentical(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->assertIdentical(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->assertIdentical(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 = array(
|
||||
'test1' => 1,
|
||||
'test2' => '0',
|
||||
'test3' => '',
|
||||
'test4' => 12.64,
|
||||
'test5' => FALSE,
|
||||
'test6' => array(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->assertIdentical($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 = array(
|
||||
'test3',
|
||||
'test7',
|
||||
'test21', // Cid does not exist.
|
||||
'test6',
|
||||
'test19', // Cid does not exist until added before second getMultiple().
|
||||
'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 = array('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 = array(
|
||||
'cid_1' => array('data' => 1),
|
||||
'cid_2' => array('data' => 2),
|
||||
'cid_3' => array('data' => array(1, 2)),
|
||||
'cid_4' => array('data' => 1, 'expire' => $future_expiration),
|
||||
'cid_5' => array('data' => 1, 'tags' => array('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.
|
||||
try {
|
||||
$items = [
|
||||
'exception_test_1' => array('data' => 1, 'tags' => []),
|
||||
'exception_test_2' => array('data' => 2, 'tags' => ['valid']),
|
||||
'exception_test_3' => array('data' => 3, 'tags' => ['node' => [3, 5, 7]]),
|
||||
];
|
||||
$backend->setMultiple($items);
|
||||
$this->fail('::setMultiple() was called with invalid cache tags, no exception was thrown.');
|
||||
}
|
||||
catch (\LogicException $e) {
|
||||
$this->pass('::setMultiple() was called with invalid cache tags, an exception was thrown.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
$backend->delete('test23'); // Nonexistent key should not cause an error.
|
||||
$backend->deleteMultiple(array(
|
||||
'test3',
|
||||
'test5',
|
||||
'test7',
|
||||
'test19', // Nonexistent key should not cause an error.
|
||||
'test21', // Nonexistent key should not cause an error.
|
||||
));
|
||||
|
||||
// Test if expected keys have been deleted.
|
||||
$this->assertIdentical(FALSE, $backend->get('test1'), "Cache id test1 deleted.");
|
||||
$this->assertIdentical(FALSE, $backend->get('test3'), "Cache id test3 deleted.");
|
||||
$this->assertIdentical(FALSE, $backend->get('test5'), "Cache id test5 deleted.");
|
||||
$this->assertIdentical(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->assertIdentical(FALSE, $backend->get('test19'), "Cache id test19 does not exist.");
|
||||
$this->assertIdentical(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(array()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*/
|
||||
function testInvalidate() {
|
||||
$backend = $this->getCacheBackend();
|
||||
$backend->set('test1', 1);
|
||||
$backend->set('test2', 2);
|
||||
$backend->set('test3', 2);
|
||||
$backend->set('test4', 2);
|
||||
|
||||
$reference = array('test1', 'test2', 'test3', 'test4');
|
||||
|
||||
$cids = $reference;
|
||||
$ret = $backend->getMultiple($cids);
|
||||
$this->assertEqual(count($ret), 4, 'Four items returned.');
|
||||
|
||||
$backend->invalidate('test1');
|
||||
$backend->invalidateMultiple(array('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(array()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
|
||||
*/
|
||||
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, array('test_tag:2'));
|
||||
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('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(array('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, array('test_tag:1'));
|
||||
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('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(array('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, array('test_tag:1'));
|
||||
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag:2'));
|
||||
$backend->set('test_cid_invalidate3', $this->defaultValue, Cache::PERMANENT, array('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(array('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 = array('test_tag:1', 'test_tag:2', 'test_tag:3');
|
||||
$bins = array('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(array('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,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\MemoryBackendUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\Core\Cache\MemoryBackend;
|
||||
|
||||
/**
|
||||
* Unit test of the memory cache backend using the generic cache unit test base.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class MemoryBackendUnitTest extends GenericCacheBackendUnitTestBase {
|
||||
|
||||
/**
|
||||
* Creates a new instance of MemoryBackend.
|
||||
*
|
||||
* @return
|
||||
* A new MemoryBackend object.
|
||||
*/
|
||||
protected function createCacheBackend($bin) {
|
||||
$backend = new MemoryBackend($bin);
|
||||
\Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
|
||||
return $backend;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\PageCacheTagsTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Provides helper methods for page cache tags tests.
|
||||
*/
|
||||
abstract class PageCacheTagsTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Always enable header dumping in page cache tags tests, this aids debugging.
|
||||
*/
|
||||
protected $dumpHeaders = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Enable page caching.
|
||||
$config = $this->config('system.performance');
|
||||
$config->set('cache.page.max_age', 3600);
|
||||
$config->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that when loading a given page, it's a page cache hit or miss.
|
||||
*
|
||||
* @param \Drupal\Core\Url $url
|
||||
* The page for this URL will be loaded.
|
||||
* @param string $hit_or_miss
|
||||
* 'HIT' if a page cache hit is expected, 'MISS' otherwise.
|
||||
*
|
||||
* @param array|FALSE $tags
|
||||
* When expecting a page cache hit, you may optionally specify an array of
|
||||
* expected cache tags. While FALSE, the cache tags will not be verified.
|
||||
*/
|
||||
protected function verifyPageCache(Url $url, $hit_or_miss, $tags = FALSE) {
|
||||
$this->drupalGet($url);
|
||||
$message = SafeMarkup::format('Page cache @hit_or_miss for %path.', array('@hit_or_miss' => $hit_or_miss, '%path' => $url->toString()));
|
||||
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), $hit_or_miss, $message);
|
||||
|
||||
if ($hit_or_miss === 'HIT' && is_array($tags)) {
|
||||
$absolute_url = $url->setAbsolute()->toString();
|
||||
$cid_parts = array($absolute_url, 'html');
|
||||
$cid = implode(':', $cid_parts);
|
||||
$cache_entry = \Drupal::cache('render')->get($cid);
|
||||
sort($cache_entry->tags);
|
||||
$tags = array_unique($tags);
|
||||
sort($tags);
|
||||
$this->assertIdentical($cache_entry->tags, $tags);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
30
core/modules/system/src/Tests/Cache/PhpBackendUnitTest.php
Normal file
30
core/modules/system/src/Tests/Cache/PhpBackendUnitTest.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Cache\PhpBackendUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Cache;
|
||||
|
||||
use Drupal\Core\Cache\PhpBackend;
|
||||
|
||||
/**
|
||||
* Unit test of the PHP cache backend using the generic cache unit test base.
|
||||
*
|
||||
* @group Cache
|
||||
*/
|
||||
class PhpBackendUnitTest 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;
|
||||
}
|
||||
|
||||
}
|
||||
97
core/modules/system/src/Tests/Common/AddFeedTest.php
Normal file
97
core/modules/system/src/Tests/Common/AddFeedTest.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\AddFeedTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Make sure that attaching feeds works correctly with various constructs.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class AddFeedTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Tests attaching feeds with paths, URLs, and titles.
|
||||
*/
|
||||
function testBasicFeedAddNoTitle() {
|
||||
$path = $this->randomMachineName(12);
|
||||
$external_url = 'http://' . $this->randomMachineName(12) . '/' . $this->randomMachineName(12);
|
||||
$fully_qualified_local_url = Url::fromUri('base:' . $this->randomMachineName(12), array('absolute' => TRUE))->toString();
|
||||
|
||||
$path_for_title = $this->randomMachineName(12);
|
||||
$external_for_title = 'http://' . $this->randomMachineName(12) . '/' . $this->randomMachineName(12);
|
||||
$fully_qualified_for_title = Url::fromUri('base:' . $this->randomMachineName(12), array('absolute' => TRUE))->toString();
|
||||
|
||||
$urls = array(
|
||||
'path without title' => array(
|
||||
'url' => Url::fromUri('base:' . $path, array('absolute' => TRUE))->toString(),
|
||||
'title' => '',
|
||||
),
|
||||
'external URL without title' => array(
|
||||
'url' => $external_url,
|
||||
'title' => '',
|
||||
),
|
||||
'local URL without title' => array(
|
||||
'url' => $fully_qualified_local_url,
|
||||
'title' => '',
|
||||
),
|
||||
'path with title' => array(
|
||||
'url' => Url::fromUri('base:' . $path_for_title, array('absolute' => TRUE))->toString(),
|
||||
'title' => $this->randomMachineName(12),
|
||||
),
|
||||
'external URL with title' => array(
|
||||
'url' => $external_for_title,
|
||||
'title' => $this->randomMachineName(12),
|
||||
),
|
||||
'local URL with title' => array(
|
||||
'url' => $fully_qualified_for_title,
|
||||
'title' => $this->randomMachineName(12),
|
||||
),
|
||||
);
|
||||
|
||||
$build = [];
|
||||
foreach ($urls as $feed_info) {
|
||||
$build['#attached']['feed'][] = [$feed_info['url'], $feed_info['title']];
|
||||
}
|
||||
|
||||
drupal_process_attached($build);
|
||||
|
||||
$this->setRawContent(drupal_get_html_head());
|
||||
foreach ($urls as $description => $feed_info) {
|
||||
$this->assertPattern($this->urlToRSSLinkPattern($feed_info['url'], $feed_info['title']), format_string('Found correct feed header for %description', array('%description' => $description)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern representing the RSS feed in the page.
|
||||
*/
|
||||
function urlToRSSLinkPattern($url, $title = '') {
|
||||
// Escape any regular expression characters in the URL ('?' is the worst).
|
||||
$url = preg_replace('/([+?.*])/', '[$0]', $url);
|
||||
$generated_pattern = '%<link +href="' . $url . '" +rel="alternate" +title="' . $title . '" +type="application/rss.xml" */>%';
|
||||
return $generated_pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that special characters are correctly escaped.
|
||||
*
|
||||
* @see https://www.drupal.org/node/1211668
|
||||
*/
|
||||
function testFeedIconEscaping() {
|
||||
$variables = array(
|
||||
'#theme' => 'feed_icon',
|
||||
'#url' => 'node',
|
||||
'#title' => '<>&"\'',
|
||||
);
|
||||
$text = \Drupal::service('renderer')->renderRoot($variables);
|
||||
preg_match('/title="(.*?)"/', $text, $matches);
|
||||
$this->assertEqual($matches[1], 'Subscribe to &"'', 'feed_icon template escapes reserved HTML characters.');
|
||||
}
|
||||
}
|
||||
76
core/modules/system/src/Tests/Common/AlterTest.php
Normal file
76
core/modules/system/src/Tests/Common/AlterTest.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\AlterTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests alteration of arguments passed to \Drupal::moduleHandler->alter().
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class AlterTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('block', 'common_test');
|
||||
|
||||
/**
|
||||
* Tests if the theme has been altered.
|
||||
*/
|
||||
function testDrupalAlter() {
|
||||
// This test depends on Bartik, so make sure that it is always the current
|
||||
// active theme.
|
||||
\Drupal::service('theme_handler')->install(array('bartik'));
|
||||
\Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->initTheme('bartik'));
|
||||
|
||||
$array = array('foo' => 'bar');
|
||||
$entity = new \stdClass();
|
||||
$entity->foo = 'bar';
|
||||
|
||||
// Verify alteration of a single argument.
|
||||
$array_copy = $array;
|
||||
$array_expected = array('foo' => 'Drupal theme');
|
||||
\Drupal::moduleHandler()->alter('drupal_alter', $array_copy);
|
||||
\Drupal::theme()->alter('drupal_alter', $array_copy);
|
||||
$this->assertEqual($array_copy, $array_expected, 'Single array was altered.');
|
||||
|
||||
$entity_copy = clone $entity;
|
||||
$entity_expected = clone $entity;
|
||||
$entity_expected->foo = 'Drupal theme';
|
||||
\Drupal::moduleHandler()->alter('drupal_alter', $entity_copy);
|
||||
\Drupal::theme()->alter('drupal_alter', $entity_copy);
|
||||
$this->assertEqual($entity_copy, $entity_expected, 'Single object was altered.');
|
||||
|
||||
// Verify alteration of multiple arguments.
|
||||
$array_copy = $array;
|
||||
$array_expected = array('foo' => 'Drupal theme');
|
||||
$entity_copy = clone $entity;
|
||||
$entity_expected = clone $entity;
|
||||
$entity_expected->foo = 'Drupal theme';
|
||||
$array2_copy = $array;
|
||||
$array2_expected = array('foo' => 'Drupal theme');
|
||||
\Drupal::moduleHandler()->alter('drupal_alter', $array_copy, $entity_copy, $array2_copy);
|
||||
\Drupal::theme()->alter('drupal_alter', $array_copy, $entity_copy, $array2_copy);
|
||||
$this->assertEqual($array_copy, $array_expected, 'First argument to \Drupal::moduleHandler->alter() was altered.');
|
||||
$this->assertEqual($entity_copy, $entity_expected, 'Second argument to \Drupal::moduleHandler->alter() was altered.');
|
||||
$this->assertEqual($array2_copy, $array2_expected, 'Third argument to \Drupal::moduleHandler->alter() was altered.');
|
||||
|
||||
// Verify alteration order when passing an array of types to \Drupal::moduleHandler->alter().
|
||||
// common_test_module_implements_alter() places 'block' implementation after
|
||||
// other modules.
|
||||
$array_copy = $array;
|
||||
$array_expected = array('foo' => 'Drupal block theme');
|
||||
\Drupal::moduleHandler()->alter(array('drupal_alter', 'drupal_alter_foo'), $array_copy);
|
||||
\Drupal::theme()->alter(array('drupal_alter', 'drupal_alter_foo'), $array_copy);
|
||||
$this->assertEqual($array_copy, $array_expected, 'hook_TYPE_alter() implementations ran in correct order.');
|
||||
}
|
||||
}
|
||||
485
core/modules/system/src/Tests/Common/AttachedAssetsTest.php
Normal file
485
core/modules/system/src/Tests/Common/AttachedAssetsTest.php
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\AttachedAssetsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
use Drupal\simpletest\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 = array('language', 'simpletest', 'common_test', 'system');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', array('router'));
|
||||
$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.
|
||||
*/
|
||||
function testDefault() {
|
||||
$assets = new AttachedAssets();
|
||||
$this->assertEqual(array(), $this->assetResolver->getCssAssets($assets, FALSE), 'Default CSS is empty.');
|
||||
list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, FALSE);
|
||||
$this->assertEqual(array(), $js_assets_header, 'Default header JavaScript is empty.');
|
||||
$this->assertEqual(array(), $js_assets_footer, 'Default footer JavaScript is empty.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests non-existing libraries.
|
||||
*/
|
||||
function testLibraryUnknown() {
|
||||
$build['#attached']['library'][] = 'core/unknown';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$this->assertIdentical([], $this->assetResolver->getJsAssets($assets, FALSE)[0], 'Unknown library was not added to the page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a CSS and a JavaScript file.
|
||||
*/
|
||||
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_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_create_url('core/modules/system/tests/modules/common_test/foo.js') . '?' . $query_string . '"></script>'), FALSE, 'Rendering an external JavaScript file.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding JavaScript settings.
|
||||
*/
|
||||
function testAddJsSettings() {
|
||||
// Add a file in order to test default settings.
|
||||
$build['#attached']['library'][] = 'core/drupalSettings';
|
||||
$assets = AttachedAssets::createFromRenderArray($build);
|
||||
|
||||
$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.');
|
||||
|
||||
$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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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_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.
|
||||
*/
|
||||
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_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.
|
||||
*/
|
||||
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->assertTrue(
|
||||
count($rendered_footer_js) == 2
|
||||
&& substr($rendered_footer_js[0]['#value'], 0, 20) === 'var drupalSettings ='
|
||||
&& substr($rendered_footer_js[1]['#attributes']['src'], 0, 7) === 'http://',
|
||||
'There are 2 JavaScript assets in the footer: one with drupalSettings, one with the sole aggregated JavaScript asset.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests JavaScript settings.
|
||||
*/
|
||||
function testSettings() {
|
||||
$build = array();
|
||||
$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);
|
||||
$rendered_js = $this->renderer->renderPlain($js_render_array);
|
||||
|
||||
// Parse the generated drupalSettings <script> back to a PHP representation.
|
||||
$startToken = 'drupalSettings = ';
|
||||
$endToken = '}';
|
||||
$start = strpos($rendered_js, $startToken) + strlen($startToken);
|
||||
$end = strrpos($rendered_js, $endToken);
|
||||
$json = Unicode::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->assertTrue(isset($parsed_settings['path']['scriptPath']), 'drupalSettings.path.scriptPath 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.
|
||||
*/
|
||||
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_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_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_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.
|
||||
*/
|
||||
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()
|
||||
*/
|
||||
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_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_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.
|
||||
*/
|
||||
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.1.2') > 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.
|
||||
*/
|
||||
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",
|
||||
"-5_1", // The external script.
|
||||
"-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 = array();
|
||||
if (preg_match_all('/weight_([-0-9]+_[0-9]+)/', $rendered_js, $matches)) {
|
||||
$result = $matches[1];
|
||||
}
|
||||
else {
|
||||
$result = array();
|
||||
}
|
||||
$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 = array();
|
||||
if (preg_match_all('/([a-z]+)_weight_([-0-9]+_[0-9]+)/', $rendered_css, $matches)) {
|
||||
$result = $matches[0];
|
||||
}
|
||||
else {
|
||||
$result = array();
|
||||
}
|
||||
$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.
|
||||
*/
|
||||
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()
|
||||
*/
|
||||
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()
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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->assertIdentical('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->assertIdentical(['core/jquery'], $dynamic_library['dependencies']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that multiple modules can implement libraries with the same name.
|
||||
*
|
||||
* @see common_test.library.yml
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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_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_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.');
|
||||
}
|
||||
|
||||
}
|
||||
121
core/modules/system/src/Tests/Common/FormatDateTest.php
Normal file
121
core/modules/system/src/Tests/Common/FormatDateTest.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\FormatDateTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests the format_date() function.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class FormatDateTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('language');
|
||||
|
||||
/**
|
||||
* Arbitrary langcode for a custom language.
|
||||
*/
|
||||
const LANGCODE = 'xx';
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp('language');
|
||||
|
||||
$this->config('system.date')
|
||||
->set('timezone.user.configurable', 1)
|
||||
->save();
|
||||
$formats = $this->container->get('entity.manager')
|
||||
->getStorage('date_format')
|
||||
->loadMultiple(array('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();
|
||||
$this->refreshVariables();
|
||||
|
||||
$this->settingsSet('locale_custom_strings_' . self::LANGCODE, array(
|
||||
'' => array('Sunday' => 'domingo'),
|
||||
'Long month name' => array('March' => 'marzo'),
|
||||
));
|
||||
|
||||
ConfigurableLanguage::createFromLangcode(static::LANGCODE)->save();
|
||||
$this->resetAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests admin-defined formats in format_date().
|
||||
*/
|
||||
function testAdminDefinedFormatDate() {
|
||||
// Create and log in an admin user.
|
||||
$this->drupalLogin($this->drupalCreateUser(array('administer site configuration')));
|
||||
|
||||
// Add new date format.
|
||||
$edit = array(
|
||||
'id' => 'example_style',
|
||||
'label' => 'Example Style',
|
||||
'date_format_pattern' => 'j M y',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/date-time/formats/add', $edit, t('Add format'));
|
||||
|
||||
// Add a second date format with a different case than the first.
|
||||
$edit = array(
|
||||
'id' => 'example_style_uppercase',
|
||||
'label' => 'Example Style Uppercase',
|
||||
'date_format_pattern' => 'j M Y',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/date-time/formats/add', $edit, t('Add format'));
|
||||
$this->assertText(t('Custom date format added.'));
|
||||
|
||||
$timestamp = strtotime('2007-03-10T00:00:00+00:00');
|
||||
$this->assertIdentical(format_date($timestamp, 'example_style', '', 'America/Los_Angeles'), '9 Mar 07');
|
||||
$this->assertIdentical(format_date($timestamp, 'example_style_uppercase', '', 'America/Los_Angeles'), '9 Mar 2007');
|
||||
$this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'fallback'), 'Test format_date() defaulting to `fallback` when $type not found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the format_date() function.
|
||||
*/
|
||||
function testFormatDate() {
|
||||
$timestamp = strtotime('2007-03-26T00:00:00+00:00');
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test all parameters.');
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.');
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.');
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.');
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');
|
||||
|
||||
// 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.
|
||||
$this->container->get('language_manager')->reset();
|
||||
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test a different language.');
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');
|
||||
$this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T'), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test custom date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'long'), 'domingo, 25. marzo 2007 - 17:00', 'Test long date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'medium'), '25. marzo 2007 - 17:00', 'Test medium date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'short'), '2007 Mar 25 - 5:00pm', 'Test short date format.');
|
||||
$this->assertIdentical(format_date($timestamp), '25. marzo 2007 - 17:00', 'Test default date format.');
|
||||
// Test HTML time element formats.
|
||||
$this->assertIdentical(format_date($timestamp, 'html_datetime'), '2007-03-25T17:00:00-0700', 'Test html_datetime date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'html_date'), '2007-03-25', 'Test html_date date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'html_time'), '17:00:00', 'Test html_time date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'html_yearless_date'), '03-25', 'Test html_yearless_date date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'html_week'), '2007-W12', 'Test html_week date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'html_month'), '2007-03', 'Test html_month date format.');
|
||||
$this->assertIdentical(format_date($timestamp, 'html_year'), '2007', 'Test html_year date format.');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\NoJavaScriptAnonymousTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests that anonymous users are not served any JavaScript in the Standard
|
||||
* installation profile.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class NoJavaScriptAnonymousTest extends WebTestBase {
|
||||
|
||||
protected $profile = 'standard';
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Grant the anonymous user the permission to look at user profiles.
|
||||
user_role_grant_permissions('anonymous', array('access user profiles'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that anonymous users are not served any JavaScript.
|
||||
*/
|
||||
public function testNoJavaScript() {
|
||||
// Create a node that is listed on the frontpage.
|
||||
$this->drupalCreateNode(array(
|
||||
'promote' => NODE_PROMOTED,
|
||||
));
|
||||
$user = $this->drupalCreateUser();
|
||||
|
||||
// Test frontpage.
|
||||
$this->drupalGet('');
|
||||
$this->assertNoJavaScriptExceptHtml5Shiv();
|
||||
|
||||
// Test node page.
|
||||
$this->drupalGet('node/1');
|
||||
$this->assertNoJavaScriptExceptHtml5Shiv();
|
||||
|
||||
// Test user profile page.
|
||||
$this->drupalGet('user/' . $user->id());
|
||||
$this->assertNoJavaScriptExceptHtml5Shiv();
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes if no JavaScript is found on the page except the HTML5 shiv.
|
||||
*
|
||||
* The HTML5 shiv is necessary for e.g. the <article> tag which Drupal 8 uses
|
||||
* to work in older browsers like Internet Explorer 8.
|
||||
*/
|
||||
protected function assertNoJavaScriptExceptHtml5Shiv() {
|
||||
// Ensure drupalSettings is not set.
|
||||
$this->assertNoRaw('var drupalSettings = {', 'drupalSettings is not set.');
|
||||
|
||||
// Ensure the HTML5 shiv exists.
|
||||
$this->assertRaw('html5shiv/html5shiv.min.js', 'HTML5 shiv JavaScript exists.');
|
||||
|
||||
// Ensure no other JavaScript file exists on the page, while ignoring the
|
||||
// HTML5 shiv.
|
||||
$this->assertNoPattern('/(?<!html5shiv\.min)\.js/', "No other JavaScript exists.");
|
||||
}
|
||||
|
||||
}
|
||||
90
core/modules/system/src/Tests/Common/PageRenderTest.php
Normal file
90
core/modules/system/src/Tests/Common/PageRenderTest.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\PageRenderTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Core\Render\MainContent\HtmlRenderer;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Test page rendering hooks.
|
||||
*
|
||||
* @group system
|
||||
*/
|
||||
class PageRenderTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests hook_page_attachments() exceptions.
|
||||
*/
|
||||
function testHookPageAttachmentsExceptions() {
|
||||
$this->enableModules(['common_test', 'system']);
|
||||
$this->installSchema('system', 'router');
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
|
||||
$this->assertPageRenderHookExceptions('common_test', 'hook_page_attachments');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_page_attachments_alter() exceptions.
|
||||
*/
|
||||
function testHookPageAlter() {
|
||||
$this->enableModules(['common_test', 'system']);
|
||||
$this->installSchema('system', 'router');
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
|
||||
$this->assertPageRenderHookExceptions('common_test', 'hook_page_attachments_alter');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts whether expected exceptions are thrown for invalid hook implementations.
|
||||
*
|
||||
* @param string $module
|
||||
* The module whose invalid logic in its hooks to enable.
|
||||
* @param string $hook
|
||||
* The page render hook to assert expected exceptions for.
|
||||
*/
|
||||
function assertPageRenderHookExceptions($module, $hook) {
|
||||
$html_renderer = \Drupal::getContainer()->get('main_content_renderer.html');
|
||||
|
||||
// Assert a valid hook implementation doesn't trigger an exception.
|
||||
$page = [];
|
||||
$html_renderer->invokePageAttachmentHooks($page);
|
||||
|
||||
// Assert that hooks can set cache tags.
|
||||
$this->assertEqual($page['#cache']['tags'], ['example']);
|
||||
$this->assertEqual($page['#cache']['contexts'], ['user.permissions']);
|
||||
|
||||
// Assert an invalid hook implementation doesn't trigger an exception.
|
||||
\Drupal::state()->set($module . '.' . $hook . '.descendant_attached', TRUE);
|
||||
$assertion = $hook . '() implementation that sets #attached on a descendant triggers an exception';
|
||||
$page = [];
|
||||
try {
|
||||
$html_renderer->invokePageAttachmentHooks($page);
|
||||
$this->error($assertion);
|
||||
}
|
||||
catch (\LogicException $e) {
|
||||
$this->pass($assertion);
|
||||
$this->assertEqual($e->getMessage(), 'Only #attached and #cache may be set in ' . $hook . '().');
|
||||
}
|
||||
\Drupal::state()->set('bc_test.' . $hook . '.descendant_attached', FALSE);
|
||||
|
||||
// Assert an invalid hook implementation doesn't trigger an exception.
|
||||
\Drupal::state()->set('bc_test.' . $hook . '.render_array', TRUE);
|
||||
$assertion = $hook . '() implementation that sets a child render array triggers an exception';
|
||||
$page = [];
|
||||
try {
|
||||
$html_renderer->invokePageAttachmentHooks($page);
|
||||
$this->error($assertion);
|
||||
}
|
||||
catch (\LogicException $e) {
|
||||
$this->pass($assertion);
|
||||
$this->assertEqual($e->getMessage(), 'Only #attached and #cache may be set in ' . $hook . '().');
|
||||
}
|
||||
\Drupal::state()->set($module . '.' . $hook . '.render_array', FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
253
core/modules/system/src/Tests/Common/RenderElementTypesTest.php
Normal file
253
core/modules/system/src/Tests/Common/RenderElementTypesTest.php
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\RenderElementTypesTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the markup of core render element types passed to drupal_render().
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class RenderElementTypesTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'router_test');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(array('system'));
|
||||
$this->installSchema('system', array('router'));
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that an array of elements is rendered properly.
|
||||
*
|
||||
* @param array $elements
|
||||
* The render element array to test.
|
||||
* @param string $expected_html
|
||||
* The expected markup.
|
||||
* @param string $message
|
||||
* Assertion message.
|
||||
*/
|
||||
protected function assertElements(array $elements, $expected_html, $message) {
|
||||
$actual_html = \Drupal::service('renderer')->renderRoot($elements);
|
||||
|
||||
$out = '<table><tr>';
|
||||
$out .= '<td valign="top"><pre>' . SafeMarkup::checkPlain($expected_html) . '</pre></td>';
|
||||
$out .= '<td valign="top"><pre>' . SafeMarkup::checkPlain($actual_html) . '</pre></td>';
|
||||
$out .= '</tr></table>';
|
||||
$this->verbose($out);
|
||||
|
||||
$this->assertIdentical($actual_html, $expected_html, SafeMarkup::checkPlain($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests system #type 'container'.
|
||||
*/
|
||||
function testContainer() {
|
||||
// Basic container with no attributes.
|
||||
$this->assertElements(array(
|
||||
'#type' => 'container',
|
||||
'#markup' => 'foo',
|
||||
), "<div>foo</div>\n", "#type 'container' with no HTML attributes");
|
||||
|
||||
// Container with a class.
|
||||
$this->assertElements(array(
|
||||
'#type' => 'container',
|
||||
'#markup' => 'foo',
|
||||
'#attributes' => array(
|
||||
'class' => array('bar'),
|
||||
),
|
||||
), '<div class="bar">foo</div>' . "\n", "#type 'container' with a class HTML attribute");
|
||||
|
||||
// Container with children.
|
||||
$this->assertElements(array(
|
||||
'#type' => 'container',
|
||||
'child' => array(
|
||||
'#markup' => 'foo',
|
||||
),
|
||||
), "<div>foo</div>\n", "#type 'container' with child elements");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests system #type 'html_tag'.
|
||||
*/
|
||||
function testHtmlTag() {
|
||||
// Test void element.
|
||||
$this->assertElements(array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'meta',
|
||||
'#value' => 'ignored',
|
||||
'#value_prefix' => 'ignored',
|
||||
'#value_suffix' => 'ignored',
|
||||
'#attributes' => array(
|
||||
'name' => 'description',
|
||||
'content' => 'Drupal test',
|
||||
),
|
||||
), '<meta name="description" content="Drupal test" />' . "\n", "#type 'html_tag', void element renders properly");
|
||||
|
||||
// Test non-void element.
|
||||
$this->assertElements(array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'section',
|
||||
'#value' => 'value',
|
||||
'#value_prefix' => 'value_prefix|',
|
||||
'#value_suffix' => '|value_suffix',
|
||||
'#attributes' => array(
|
||||
'class' => array('unicorns'),
|
||||
),
|
||||
), '<section class="unicorns">value_prefix|value|value_suffix</section>' . "\n", "#type 'html_tag', non-void element renders properly");
|
||||
|
||||
// Test empty void element tag.
|
||||
$this->assertElements(array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'link',
|
||||
), "<link />\n", "#type 'html_tag' empty void element renders properly");
|
||||
|
||||
// Test empty non-void element tag.
|
||||
$this->assertElements(array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'section',
|
||||
), "<section></section>\n", "#type 'html_tag' empty non-void element renders properly");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests system #type 'more_link'.
|
||||
*/
|
||||
function testMoreLink() {
|
||||
$elements = array(
|
||||
array(
|
||||
'name' => "#type 'more_link' anchor tag generation without extra classes",
|
||||
'value' => array(
|
||||
'#type' => 'more_link',
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
),
|
||||
'expected' => '//div[@class="more-link"]/a[@href="https://www.drupal.org" and text()="More"]',
|
||||
),
|
||||
array(
|
||||
'name' => "#type 'more_link' anchor tag generation with different link text",
|
||||
'value' => array(
|
||||
'#type' => 'more_link',
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
'#title' => 'More Titles',
|
||||
),
|
||||
'expected' => '//div[@class="more-link"]/a[@href="https://www.drupal.org" and text()="More Titles"]',
|
||||
),
|
||||
array(
|
||||
'name' => "#type 'more_link' anchor tag generation with attributes on wrapper",
|
||||
'value' => array(
|
||||
'#type' => 'more_link',
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
'#theme_wrappers' => array(
|
||||
'container' => array(
|
||||
'#attributes' => array(
|
||||
'title' => 'description',
|
||||
'class' => array('more-link', 'drupal', 'test'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'expected' => '//div[@title="description" and contains(@class, "more-link") and contains(@class, "drupal") and contains(@class, "test")]/a[@href="https://www.drupal.org" and text()="More"]',
|
||||
),
|
||||
array(
|
||||
'name' => "#type 'more_link' anchor tag with a relative path",
|
||||
'value' => array(
|
||||
'#type' => 'more_link',
|
||||
'#url' => Url::fromRoute('router_test.1'),
|
||||
),
|
||||
'expected' => '//div[@class="more-link"]/a[@href="' . Url::fromRoute('router_test.1')->toString() . '" and text()="More"]',
|
||||
),
|
||||
array(
|
||||
'name' => "#type 'more_link' anchor tag with a route",
|
||||
'value' => array(
|
||||
'#type' => 'more_link',
|
||||
'#url' => Url::fromRoute('router_test.1'),
|
||||
),
|
||||
'expected' => '//div[@class="more-link"]/a[@href="' . \Drupal::urlGenerator()->generate('router_test.1') . '" and text()="More"]',
|
||||
),
|
||||
array(
|
||||
'name' => "#type 'more_link' anchor tag with an absolute path",
|
||||
'value' => array(
|
||||
'#type' => 'more_link',
|
||||
'#url' => Url::fromRoute('system.admin_content'),
|
||||
'#options' => array('absolute' => TRUE),
|
||||
),
|
||||
'expected' => '//div[@class="more-link"]/a[@href="' . Url::fromRoute('system.admin_content')->setAbsolute()->toString() . '" and text()="More"]',
|
||||
),
|
||||
array(
|
||||
'name' => "#type 'more_link' anchor tag to the front page",
|
||||
'value' => array(
|
||||
'#type' => 'more_link',
|
||||
'#url' => Url::fromRoute('<front>'),
|
||||
),
|
||||
'expected' => '//div[@class="more-link"]/a[@href="' . Url::fromRoute('<front>')->toString() . '" and text()="More"]',
|
||||
),
|
||||
);
|
||||
|
||||
foreach($elements as $element) {
|
||||
$xml = new \SimpleXMLElement(\Drupal::service('renderer')->renderRoot($element['value']));
|
||||
$result = $xml->xpath($element['expected']);
|
||||
$this->assertTrue($result, '"' . $element['name'] . '" input rendered correctly by drupal_render().');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests system #type 'system_compact_link'.
|
||||
*/
|
||||
function testSystemCompactLink() {
|
||||
$elements = array(
|
||||
array(
|
||||
'name' => "#type 'system_compact_link' when admin compact mode is off",
|
||||
'value' => array(
|
||||
'#type' => 'system_compact_link',
|
||||
),
|
||||
'expected' => '//div[@class="compact-link"]/a[contains(@href, "admin/compact/on?") and text()="Hide descriptions"]',
|
||||
),
|
||||
array(
|
||||
'name' => "#type 'system_compact_link' when adding extra attributes",
|
||||
'value' => array(
|
||||
'#type' => 'system_compact_link',
|
||||
'#attributes' => array(
|
||||
'class' => array('kittens-rule'),
|
||||
),
|
||||
),
|
||||
'expected' => '//div[@class="compact-link"]/a[contains(@href, "admin/compact/on?") and @class="kittens-rule" and text()="Hide descriptions"]',
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($elements as $element) {
|
||||
$xml = new \SimpleXMLElement(\Drupal::service('renderer')->renderRoot($element['value']));
|
||||
$result = $xml->xpath($element['expected']);
|
||||
$this->assertTrue($result, '"' . $element['name'] . '" is rendered correctly by drupal_render().');
|
||||
}
|
||||
|
||||
// Set admin compact mode on for additional tests.
|
||||
\Drupal::request()->cookies->set('Drupal_visitor_admin_compact_mode', TRUE);
|
||||
|
||||
$element = array(
|
||||
'name' => "#type 'system_compact_link' when admin compact mode is on",
|
||||
'value' => array(
|
||||
'#type' => 'system_compact_link',
|
||||
),
|
||||
'expected' => '//div[@class="compact-link"]/a[contains(@href, "admin/compact?") and text()="Show descriptions"]',
|
||||
);
|
||||
|
||||
$xml = new \SimpleXMLElement(\Drupal::service('renderer')->renderRoot($element['value']));
|
||||
$result = $xml->xpath($element['expected']);
|
||||
$this->assertTrue($result, '"' . $element['name'] . '" is rendered correctly by drupal_render().');
|
||||
}
|
||||
|
||||
}
|
||||
71
core/modules/system/src/Tests/Common/RenderTest.php
Normal file
71
core/modules/system/src/Tests/Common/RenderTest.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\RenderTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Performs functional tests on drupal_render().
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class RenderTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'common_test');
|
||||
|
||||
/**
|
||||
* Tests theme preprocess functions being able to attach assets.
|
||||
*/
|
||||
function testDrupalRenderThemePreprocessAttached() {
|
||||
\Drupal::state()->set('theme_preprocess_attached_test', TRUE);
|
||||
|
||||
$test_element = [
|
||||
'#theme' => 'common_test_render_element',
|
||||
'foo' => [
|
||||
'#markup' => 'Kittens!',
|
||||
],
|
||||
];
|
||||
\Drupal::service('renderer')->renderRoot($test_element);
|
||||
|
||||
$expected_attached = [
|
||||
'library' => [
|
||||
'test/generic_preprocess',
|
||||
'test/specific_preprocess',
|
||||
]
|
||||
];
|
||||
$this->assertEqual($expected_attached, $test_element['#attached'], 'All expected assets from theme preprocess hooks attached.');
|
||||
|
||||
\Drupal::state()->set('theme_preprocess_attached_test', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests drupal_process_attached().
|
||||
*/
|
||||
public function testDrupalProcessAttached() {
|
||||
// Specify invalid attachments in a render array.
|
||||
$build['#attached']['library'][] = 'core/drupal.states';
|
||||
$build['#attached']['drupal_process_states'][] = [];
|
||||
try {
|
||||
drupal_process_attached($build);
|
||||
$this->fail("Invalid #attachment 'drupal_process_states' allowed");
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->pass("Invalid #attachment 'drupal_process_states' not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
162
core/modules/system/src/Tests/Common/RenderWebTest.php
Normal file
162
core/modules/system/src/Tests/Common/RenderWebTest.php
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\RenderWebTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Performs integration tests on drupal_render().
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class RenderWebTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('common_test');
|
||||
|
||||
/**
|
||||
* Tests rendering form elements without passing through
|
||||
* \Drupal::formBuilder()->doBuildForm().
|
||||
*/
|
||||
function testDrupalRenderFormElements() {
|
||||
// Define a series of form elements.
|
||||
$element = array(
|
||||
'#type' => 'button',
|
||||
'#value' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'submit'));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#value' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'text'));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'password',
|
||||
'#title' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'password'));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#value' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//textarea');
|
||||
|
||||
$element = array(
|
||||
'#type' => 'radio',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#value' => FALSE,
|
||||
);
|
||||
$this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'radio'));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'checkbox'));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#options' => array(
|
||||
0 => $this->randomMachineName(),
|
||||
1 => $this->randomMachineName(),
|
||||
),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//select');
|
||||
|
||||
$element = array(
|
||||
'#type' => 'file',
|
||||
'#title' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'file'));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'item',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#markup' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//div[contains(@class, :class) and contains(., :markup)]/label[contains(., :label)]', array(
|
||||
':class' => 'js-form-type-item',
|
||||
':markup' => $element['#markup'],
|
||||
':label' => $element['#title'],
|
||||
));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'hidden',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#value' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//input[@type=:type]', array(':type' => 'hidden'));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#url' => Url::fromRoute('common_test.destination'),
|
||||
'#options' => array(
|
||||
'absolute' => TRUE,
|
||||
),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//a[@href=:href and contains(., :title)]', array(
|
||||
':href' => \Drupal::urlGenerator()->generateFromPath('common-test/destination', ['absolute' => TRUE]),
|
||||
':title' => $element['#title'],
|
||||
));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
'#title' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//details/summary[contains(., :title)]', array(
|
||||
':title' => $element['#title'],
|
||||
));
|
||||
|
||||
$element = array(
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
'#title' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//details');
|
||||
|
||||
$element['item'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#markup' => $this->randomMachineName(),
|
||||
);
|
||||
$this->assertRenderedElement($element, '//details/div/div[contains(@class, :class) and contains(., :markup)]', array(
|
||||
':class' => 'js-form-type-item',
|
||||
':markup' => $element['item']['#markup'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that elements are rendered properly.
|
||||
*/
|
||||
protected function assertRenderedElement(array $element, $xpath, array $xpath_args = array()) {
|
||||
$original_element = $element;
|
||||
$this->setRawContent(drupal_render_root($element));
|
||||
$this->verbose('<hr />' . $this->getRawContent());
|
||||
|
||||
// @see \Drupal\simpletest\WebTestBase::xpath()
|
||||
$xpath = $this->buildXPathQuery($xpath, $xpath_args);
|
||||
$element += array('#value' => NULL);
|
||||
$this->assertFieldByXPath($xpath, $element['#value'], format_string('#type @type was properly rendered.', array(
|
||||
'@type' => var_export($element['#type'], TRUE),
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\SimpleTestErrorCollectorTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests SimpleTest error and exception collector.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class SimpleTestErrorCollectorTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system_test', 'error_test');
|
||||
|
||||
/**
|
||||
* Errors triggered during the test.
|
||||
*
|
||||
* Errors are intercepted by the overridden implementation
|
||||
* of Drupal\simpletest\WebTestBase::error() below.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $collectedErrors = array();
|
||||
|
||||
/**
|
||||
* Tests that simpletest collects errors from the tested site.
|
||||
*/
|
||||
function testErrorCollect() {
|
||||
$this->collectedErrors = array();
|
||||
$this->drupalGet('error-test/generate-warnings-with-report');
|
||||
$this->assertEqual(count($this->collectedErrors), 3, 'Three errors were collected');
|
||||
|
||||
if (count($this->collectedErrors) == 3) {
|
||||
$this->assertError($this->collectedErrors[0], 'Notice', 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()', 'ErrorTestController.php', 'Undefined variable: bananas');
|
||||
$this->assertError($this->collectedErrors[1], 'Warning', 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()', 'ErrorTestController.php', 'Division by zero');
|
||||
$this->assertError($this->collectedErrors[2], 'User warning', 'Drupal\error_test\Controller\ErrorTestController->generateWarnings()', 'ErrorTestController.php', 'Drupal is awesome');
|
||||
}
|
||||
else {
|
||||
// Give back the errors to the log report.
|
||||
foreach ($this->collectedErrors as $error) {
|
||||
parent::error($error['message'], $error['group'], $error['caller']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores errors into an array.
|
||||
*
|
||||
* This test class is trying to verify that simpletest correctly sees errors
|
||||
* and warnings. However, it can't generate errors and warnings that
|
||||
* propagate up to the testing framework itself, or these tests would always
|
||||
* fail. So, this special copy of error() doesn't propagate the errors up
|
||||
* the class hierarchy. It just stuffs them into a protected collectedErrors
|
||||
* array for various assertions to inspect.
|
||||
*/
|
||||
protected function error($message = '', $group = 'Other', array $caller = NULL) {
|
||||
// Due to a WTF elsewhere, simpletest treats debug() and verbose()
|
||||
// messages as if they were an 'error'. But, we don't want to collect
|
||||
// those here. This function just wants to collect the real errors (PHP
|
||||
// notices, PHP fatal errors, etc.), and let all the 'errors' from the
|
||||
// 'User notice' group bubble up to the parent classes to be handled (and
|
||||
// eventually displayed) as normal.
|
||||
if ($group == 'User notice') {
|
||||
parent::error($message, $group, $caller);
|
||||
}
|
||||
// Everything else should be collected but not propagated.
|
||||
else {
|
||||
$this->collectedErrors[] = array(
|
||||
'message' => $message,
|
||||
'group' => $group,
|
||||
'caller' => $caller
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a collected error matches what we are expecting.
|
||||
*/
|
||||
function assertError($error, $group, $function, $file, $message = NULL) {
|
||||
$this->assertEqual($error['group'], $group, format_string("Group was %group", array('%group' => $group)));
|
||||
$this->assertEqual($error['caller']['function'], $function, format_string("Function was %function", array('%function' => $function)));
|
||||
$this->assertEqual(drupal_basename($error['caller']['file']), $file, format_string("File was %file", array('%file' => $file)));
|
||||
if (isset($message)) {
|
||||
$this->assertEqual($error['message'], $message, format_string("Message was %message", array('%message' => $message)));
|
||||
}
|
||||
}
|
||||
}
|
||||
73
core/modules/system/src/Tests/Common/SizeUnitTest.php
Normal file
73
core/modules/system/src/Tests/Common/SizeUnitTest.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\SizeUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Component\Utility\Bytes;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Parse a predefined amount of bytes and compare the output with the expected
|
||||
* value.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class SizeUnitTest extends KernelTestBase {
|
||||
protected $exactTestCases;
|
||||
protected $roundedTestCases;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$kb = Bytes::KILOBYTE;
|
||||
$this->exactTestCases = array(
|
||||
'1 byte' => 1,
|
||||
'1 KB' => $kb,
|
||||
'1 MB' => $kb * $kb,
|
||||
'1 GB' => $kb * $kb * $kb,
|
||||
'1 TB' => $kb * $kb * $kb * $kb,
|
||||
'1 PB' => $kb * $kb * $kb * $kb * $kb,
|
||||
'1 EB' => $kb * $kb * $kb * $kb * $kb * $kb,
|
||||
'1 ZB' => $kb * $kb * $kb * $kb * $kb * $kb * $kb,
|
||||
'1 YB' => $kb * $kb * $kb * $kb * $kb * $kb * $kb * $kb,
|
||||
);
|
||||
$this->roundedTestCases = array(
|
||||
'2 bytes' => 2,
|
||||
'1 MB' => ($kb * $kb) - 1, // rounded to 1 MB (not 1000 or 1024 kilobyte!)
|
||||
round(3623651 / ($this->exactTestCases['1 MB']), 2) . ' MB' => 3623651, // megabytes
|
||||
round(67234178751368124 / ($this->exactTestCases['1 PB']), 2) . ' PB' => 67234178751368124, // petabytes
|
||||
round(235346823821125814962843827 / ($this->exactTestCases['1 YB']), 2) . ' YB' => 235346823821125814962843827, // yottabytes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that format_size() returns the expected string.
|
||||
*/
|
||||
function testCommonFormatSize() {
|
||||
foreach (array($this->exactTestCases, $this->roundedTestCases) as $test_cases) {
|
||||
foreach ($test_cases as $expected => $input) {
|
||||
$this->assertEqual(
|
||||
($result = format_size($input, NULL)),
|
||||
$expected,
|
||||
$expected . ' == ' . $result . ' (' . $input . ' bytes)'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-tests Bytes::toInt() and format_size().
|
||||
*/
|
||||
function testCommonParseSizeFormatSize() {
|
||||
foreach ($this->exactTestCases as $size) {
|
||||
$this->assertEqual(
|
||||
$size,
|
||||
($parsed_size = Bytes::toInt($string = format_size($size, NULL))),
|
||||
$size . ' == ' . $parsed_size . ' (' . $string . ')'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
core/modules/system/src/Tests/Common/SystemListingTest.php
Normal file
58
core/modules/system/src/Tests/Common/SystemListingTest.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\SystemListingTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests scanning system directories in drupal_system_listing().
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class SystemListingTest extends KernelTestBase {
|
||||
/**
|
||||
* Tests that files in different directories take precedence as expected.
|
||||
*/
|
||||
function testDirectoryPrecedence() {
|
||||
// Define the module files we will search for, and the directory precedence
|
||||
// we expect.
|
||||
$expected_directories = array(
|
||||
// When both copies of the module are compatible with Drupal core, the
|
||||
// copy in the profile directory takes precedence.
|
||||
'drupal_system_listing_compatible_test' => array(
|
||||
'core/profiles/testing/modules',
|
||||
'core/modules/system/tests/modules',
|
||||
),
|
||||
);
|
||||
|
||||
// This test relies on two versions of the same module existing in
|
||||
// different places in the filesystem. Without that, the test has no
|
||||
// meaning, so assert their presence first.
|
||||
foreach ($expected_directories as $module => $directories) {
|
||||
foreach ($directories as $directory) {
|
||||
$filename = "$directory/$module/$module.info.yml";
|
||||
$this->assertTrue(file_exists(\Drupal::root() . '/' . $filename), format_string('@filename exists.', array('@filename' => $filename)));
|
||||
}
|
||||
}
|
||||
|
||||
// Now scan the directories and check that the files take precedence as
|
||||
// expected.
|
||||
$listing = new ExtensionDiscovery(\Drupal::root());
|
||||
$listing->setProfileDirectories(array('core/profiles/testing'));
|
||||
$files = $listing->scan('module');
|
||||
foreach ($expected_directories as $module => $directories) {
|
||||
$expected_directory = array_shift($directories);
|
||||
$expected_uri = "$expected_directory/$module/$module.info.yml";
|
||||
$this->assertEqual($files[$module]->getPathname(), $expected_uri, format_string('Module @actual was found at @expected.', array(
|
||||
'@actual' => $files[$module]->getPathname(),
|
||||
'@expected' => $expected_uri,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\TableSortExtenderUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests table sorting.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class TableSortExtenderUnitTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests tablesort_init().
|
||||
*/
|
||||
function testTableSortInit() {
|
||||
|
||||
// Test simple table headers.
|
||||
|
||||
$headers = array('foo', 'bar', 'baz');
|
||||
// Reset $request->query to prevent parameters from Simpletest and Batch API
|
||||
// ending up in $ts['query'].
|
||||
$expected_ts = array(
|
||||
'name' => 'foo',
|
||||
'sql' => '',
|
||||
'sort' => 'asc',
|
||||
'query' => array(),
|
||||
);
|
||||
$request = Request::createFromGlobals();
|
||||
$request->query->replace(array());
|
||||
\Drupal::getContainer()->get('request_stack')->push($request);
|
||||
$ts = tablesort_init($headers);
|
||||
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
|
||||
$this->assertEqual($ts, $expected_ts, 'Simple table headers sorted correctly.');
|
||||
|
||||
// Test with simple table headers plus $_GET parameters that should _not_
|
||||
// override the default.
|
||||
$request = Request::createFromGlobals();
|
||||
$request->query->replace(array(
|
||||
// This should not override the table order because only complex
|
||||
// headers are overridable.
|
||||
'order' => 'bar',
|
||||
));
|
||||
\Drupal::getContainer()->get('request_stack')->push($request);
|
||||
$ts = tablesort_init($headers);
|
||||
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
|
||||
$this->assertEqual($ts, $expected_ts, 'Simple table headers plus non-overriding $_GET parameters sorted correctly.');
|
||||
|
||||
// Test with simple table headers plus $_GET parameters that _should_
|
||||
// override the default.
|
||||
$request = Request::createFromGlobals();
|
||||
$request->query->replace(array(
|
||||
'sort' => 'DESC',
|
||||
// Add an unrelated parameter to ensure that tablesort will include
|
||||
// it in the links that it creates.
|
||||
'alpha' => 'beta',
|
||||
));
|
||||
\Drupal::getContainer()->get('request_stack')->push($request);
|
||||
$expected_ts['sort'] = 'desc';
|
||||
$expected_ts['query'] = array('alpha' => 'beta');
|
||||
$ts = tablesort_init($headers);
|
||||
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
|
||||
$this->assertEqual($ts, $expected_ts, 'Simple table headers plus $_GET parameters sorted correctly.');
|
||||
|
||||
// Test complex table headers.
|
||||
|
||||
$headers = array(
|
||||
'foo',
|
||||
array(
|
||||
'data' => '1',
|
||||
'field' => 'one',
|
||||
'sort' => 'asc',
|
||||
'colspan' => 1,
|
||||
),
|
||||
array(
|
||||
'data' => '2',
|
||||
'field' => 'two',
|
||||
'sort' => 'desc',
|
||||
),
|
||||
);
|
||||
// Reset $_GET from previous assertion.
|
||||
$request = Request::createFromGlobals();
|
||||
$request->query->replace(array(
|
||||
'order' => '2',
|
||||
));
|
||||
\Drupal::getContainer()->get('request_stack')->push($request);
|
||||
$ts = tablesort_init($headers);
|
||||
$expected_ts = array(
|
||||
'name' => '2',
|
||||
'sql' => 'two',
|
||||
'sort' => 'desc',
|
||||
'query' => array(),
|
||||
);
|
||||
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
|
||||
$this->assertEqual($ts, $expected_ts, 'Complex table headers sorted correctly.');
|
||||
|
||||
// Test complex table headers plus $_GET parameters that should _not_
|
||||
// override the default.
|
||||
$request = Request::createFromGlobals();
|
||||
$request->query->replace(array(
|
||||
// This should not override the table order because this header does not
|
||||
// exist.
|
||||
'order' => 'bar',
|
||||
));
|
||||
\Drupal::getContainer()->get('request_stack')->push($request);
|
||||
$ts = tablesort_init($headers);
|
||||
$expected_ts = array(
|
||||
'name' => '1',
|
||||
'sql' => 'one',
|
||||
'sort' => 'asc',
|
||||
'query' => array(),
|
||||
);
|
||||
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
|
||||
$this->assertEqual($ts, $expected_ts, 'Complex table headers plus non-overriding $_GET parameters sorted correctly.');
|
||||
|
||||
// Test complex table headers plus $_GET parameters that _should_
|
||||
// override the default.
|
||||
$request = Request::createFromGlobals();
|
||||
$request->query->replace(array(
|
||||
'order' => '1',
|
||||
'sort' => 'ASC',
|
||||
// Add an unrelated parameter to ensure that tablesort will include
|
||||
// it in the links that it creates.
|
||||
'alpha' => 'beta',
|
||||
));
|
||||
\Drupal::getContainer()->get('request_stack')->push($request);
|
||||
$expected_ts = array(
|
||||
'name' => '1',
|
||||
'sql' => 'one',
|
||||
'sort' => 'asc',
|
||||
'query' => array('alpha' => 'beta'),
|
||||
);
|
||||
$ts = tablesort_init($headers);
|
||||
$this->verbose(strtr('$ts: <pre>!ts</pre>', array('!ts' => SafeMarkup::checkPlain(var_export($ts, TRUE)))));
|
||||
$this->assertEqual($ts, $expected_ts, 'Complex table headers plus $_GET parameters sorted correctly.');
|
||||
}
|
||||
}
|
||||
317
core/modules/system/src/Tests/Common/UrlTest.php
Normal file
317
core/modules/system/src/Tests/Common/UrlTest.php
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\UrlTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Confirm that \Drupal\Core\Url,
|
||||
* \Drupal\Component\Utility\UrlHelper::filterQueryParameters(),
|
||||
* \Drupal\Component\Utility\UrlHelper::buildQuery(), and _l() work correctly
|
||||
* with various input.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class UrlTest extends WebTestBase {
|
||||
|
||||
public static $modules = array('common_test', 'url_alter_test');
|
||||
|
||||
/**
|
||||
* Confirms that invalid URLs are filtered in link generating functions.
|
||||
*/
|
||||
function testLinkXSS() {
|
||||
// Test \Drupal::l().
|
||||
$text = $this->randomMachineName();
|
||||
$path = "<SCRIPT>alert('XSS')</SCRIPT>";
|
||||
$link = \Drupal::l($text, Url::fromUserInput('/' . $path));
|
||||
$sanitized_path = check_url(Url::fromUri('base:' . $path)->toString());
|
||||
$this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by _l().', array('@path' => $path)));
|
||||
|
||||
// Test \Drupal\Core\Url.
|
||||
$link = Url::fromUri('base:' . $path)->toString();
|
||||
$sanitized_path = check_url(Url::fromUri('base:' . $path)->toString());
|
||||
$this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by #theme', ['@path' => $path]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that #type=link bubbles outbound route/path processors' cacheability.
|
||||
*/
|
||||
function testLinkCacheability() {
|
||||
$cases = [
|
||||
['Regular link', 'internal:/user', [], ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT]],
|
||||
['Regular link, absolute', 'internal:/user', ['absolute' => TRUE], ['contexts' => ['url.site'], 'tags' => [], 'max-age' => Cache::PERMANENT]],
|
||||
['Route processor link', 'route:system.run_cron', [], ['contexts' => [], 'tags' => [], 'max-age' => 0]],
|
||||
['Route processor link, absolute', 'route:system.run_cron', ['absolute' => TRUE], ['contexts' => ['url.site'], 'tags' => [], 'max-age' => 0]],
|
||||
['Path processor link', 'internal:/user/1', [], ['contexts' => [], 'tags' => ['user:1'], 'max-age' => Cache::PERMANENT]],
|
||||
['Path processor link, absolute', 'internal:/user/1', ['absolute' => TRUE], ['contexts' => ['url.site'], 'tags' => ['user:1'], 'max-age' => Cache::PERMANENT]],
|
||||
];
|
||||
|
||||
foreach ($cases as $case) {
|
||||
list($title, $uri, $options, $expected_cacheability) = $case;
|
||||
$expected_cacheability['contexts'] = Cache::mergeContexts($expected_cacheability['contexts'], ['languages:language_interface', 'theme']);
|
||||
$link = [
|
||||
'#type' => 'link',
|
||||
'#title' => $title,
|
||||
'#options' => $options,
|
||||
'#url' => Url::fromUri($uri),
|
||||
];
|
||||
\Drupal::service('renderer')->renderRoot($link);
|
||||
$this->pass($title);
|
||||
$this->assertEqual($expected_cacheability, $link['#cache']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that default and custom attributes are handled correctly on links.
|
||||
*/
|
||||
function testLinkAttributes() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
|
||||
// Test that hreflang is added when a link has a known language.
|
||||
$language = new Language(array('id' => 'fr', 'name' => 'French'));
|
||||
$hreflang_link = array(
|
||||
'#type' => 'link',
|
||||
'#options' => array(
|
||||
'language' => $language,
|
||||
),
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
'#title' => 'bar',
|
||||
);
|
||||
$langcode = $language->getId();
|
||||
|
||||
// Test that the default hreflang handling for links does not override a
|
||||
// hreflang attribute explicitly set in the render array.
|
||||
$hreflang_override_link = $hreflang_link;
|
||||
$hreflang_override_link['#options']['attributes']['hreflang'] = 'foo';
|
||||
|
||||
$rendered = $renderer->renderRoot($hreflang_link);
|
||||
$this->assertTrue($this->hasAttribute('hreflang', $rendered, $langcode), format_string('hreflang attribute with value @langcode is present on a rendered link when langcode is provided in the render array.', array('@langcode' => $langcode)));
|
||||
|
||||
$rendered = $renderer->renderRoot($hreflang_override_link);
|
||||
$this->assertTrue($this->hasAttribute('hreflang', $rendered, 'foo'), format_string('hreflang attribute with value @hreflang is present on a rendered link when @hreflang is provided in the render array.', array('@hreflang' => 'foo')));
|
||||
|
||||
// Test the active class in links produced by _l() and #type 'link'.
|
||||
$options_no_query = array();
|
||||
$options_query = array(
|
||||
'query' => array(
|
||||
'foo' => 'bar',
|
||||
'one' => 'two',
|
||||
),
|
||||
);
|
||||
$options_query_reverse = array(
|
||||
'query' => array(
|
||||
'one' => 'two',
|
||||
'foo' => 'bar',
|
||||
),
|
||||
);
|
||||
|
||||
// Test #type link.
|
||||
$path = 'common-test/type-link-active-class';
|
||||
|
||||
$this->drupalGet($path, $options_no_query);
|
||||
$links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_no_query)->toString(), ':class' => 'is-active'));
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page is marked active.');
|
||||
|
||||
$links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_query)->toString(), ':class' => 'is-active'));
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page with a query string when the current page has no query string is not marked active.');
|
||||
|
||||
$this->drupalGet($path, $options_query);
|
||||
$links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_query)->toString(), ':class' => 'is-active'));
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page with a query string that matches the current query string is marked active.');
|
||||
|
||||
$links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_query_reverse)->toString(), ':class' => 'is-active'));
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page with a query string that has matching parameters to the current query string but in a different order is marked active.');
|
||||
|
||||
$links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_no_query)->toString(), ':class' => 'is-active'));
|
||||
$this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page without a query string when the current page has a query string is not marked active.');
|
||||
|
||||
// Test adding a custom class in links produced by _l() and #type 'link'.
|
||||
// Test _l().
|
||||
$class_l = $this->randomMachineName();
|
||||
$link_l = \Drupal::l($this->randomMachineName(), new Url('<current>', [], ['attributes' => ['class' => [$class_l]]]));
|
||||
$this->assertTrue($this->hasAttribute('class', $link_l, $class_l), format_string('Custom class @class is present on link when requested by l()', array('@class' => $class_l)));
|
||||
|
||||
// Test #type.
|
||||
$class_theme = $this->randomMachineName();
|
||||
$type_link = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#url' => Url::fromRoute('<current>'),
|
||||
'#options' => array(
|
||||
'attributes' => array(
|
||||
'class' => array($class_theme),
|
||||
),
|
||||
),
|
||||
);
|
||||
$link_theme = $renderer->renderRoot($type_link);
|
||||
$this->assertTrue($this->hasAttribute('class', $link_theme, $class_theme), format_string('Custom class @class is present on link when requested by #type', array('@class' => $class_theme)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that link functions support render arrays as 'text'.
|
||||
*/
|
||||
function testLinkRenderArrayText() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
|
||||
// Build a link with _l() for reference.
|
||||
$l = \Drupal::l('foo', Url::fromUri('https://www.drupal.org'));
|
||||
|
||||
// Test a renderable array passed to _l().
|
||||
$renderable_text = array('#markup' => 'foo');
|
||||
$l_renderable_text = \Drupal::l($renderable_text, Url::fromUri('https://www.drupal.org'));
|
||||
$this->assertEqual($l_renderable_text, $l);
|
||||
|
||||
// Test a themed link with plain text 'text'.
|
||||
$type_link_plain_array = array(
|
||||
'#type' => 'link',
|
||||
'#title' => 'foo',
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
);
|
||||
$type_link_plain = $renderer->renderRoot($type_link_plain_array);
|
||||
$this->assertEqual($type_link_plain, $l);
|
||||
|
||||
// Build a themed link with renderable 'text'.
|
||||
$type_link_nested_array = array(
|
||||
'#type' => 'link',
|
||||
'#title' => array('#markup' => 'foo'),
|
||||
'#url' => Url::fromUri('https://www.drupal.org'),
|
||||
);
|
||||
$type_link_nested = $renderer->renderRoot($type_link_nested_array);
|
||||
$this->assertEqual($type_link_nested, $l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for class existence in link.
|
||||
*
|
||||
* @param $link
|
||||
* URL to search.
|
||||
* @param $class
|
||||
* Element class to search for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the class is found, FALSE otherwise.
|
||||
*/
|
||||
private function hasAttribute($attribute, $link, $class) {
|
||||
return preg_match('|' . $attribute . '="([^\"\s]+\s+)*' . $class . '|', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UrlHelper::filterQueryParameters().
|
||||
*/
|
||||
function testDrupalGetQueryParameters() {
|
||||
$original = array(
|
||||
'a' => 1,
|
||||
'b' => array(
|
||||
'd' => 4,
|
||||
'e' => array(
|
||||
'f' => 5,
|
||||
),
|
||||
),
|
||||
'c' => 3,
|
||||
);
|
||||
|
||||
// First-level exclusion.
|
||||
$result = $original;
|
||||
unset($result['b']);
|
||||
$this->assertEqual(UrlHelper::filterQueryParameters($original, array('b')), $result, "'b' was removed.");
|
||||
|
||||
// Second-level exclusion.
|
||||
$result = $original;
|
||||
unset($result['b']['d']);
|
||||
$this->assertEqual(UrlHelper::filterQueryParameters($original, array('b[d]')), $result, "'b[d]' was removed.");
|
||||
|
||||
// Third-level exclusion.
|
||||
$result = $original;
|
||||
unset($result['b']['e']['f']);
|
||||
$this->assertEqual(UrlHelper::filterQueryParameters($original, array('b[e][f]')), $result, "'b[e][f]' was removed.");
|
||||
|
||||
// Multiple exclusions.
|
||||
$result = $original;
|
||||
unset($result['a'], $result['b']['e'], $result['c']);
|
||||
$this->assertEqual(UrlHelper::filterQueryParameters($original, array('a', 'b[e]', 'c')), $result, "'a', 'b[e]', 'c' were removed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UrlHelper::parse().
|
||||
*/
|
||||
function testDrupalParseUrl() {
|
||||
// Relative, absolute, and external URLs, without/with explicit script path,
|
||||
// without/with Drupal path.
|
||||
foreach (array('', '/', 'https://www.drupal.org/') as $absolute) {
|
||||
foreach (array('', 'index.php/') as $script) {
|
||||
foreach (array('', 'foo/bar') as $path) {
|
||||
$url = $absolute . $script . $path . '?foo=bar&bar=baz&baz#foo';
|
||||
$expected = array(
|
||||
'path' => $absolute . $script . $path,
|
||||
'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''),
|
||||
'fragment' => 'foo',
|
||||
);
|
||||
$this->assertEqual(UrlHelper::parse($url), $expected, 'URL parsed correctly.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relative URL that is known to confuse parse_url().
|
||||
$url = 'foo/bar:1';
|
||||
$result = array(
|
||||
'path' => 'foo/bar:1',
|
||||
'query' => array(),
|
||||
'fragment' => '',
|
||||
);
|
||||
$this->assertEqual(UrlHelper::parse($url), $result, 'Relative URL parsed correctly.');
|
||||
|
||||
// Test that drupal can recognize an absolute URL. Used to prevent attack vectors.
|
||||
$url = 'https://www.drupal.org/foo/bar?foo=bar&bar=baz&baz#foo';
|
||||
$this->assertTrue(UrlHelper::isExternal($url), 'Correctly identified an external URL.');
|
||||
|
||||
// Test that UrlHelper::parse() does not allow spoofing a URL to force a malicious redirect.
|
||||
$parts = UrlHelper::parse('forged:http://cwe.mitre.org/data/definitions/601.html');
|
||||
$this->assertFalse(UrlHelper::isValid($parts['path'], TRUE), '\Drupal\Component\Utility\UrlHelper::isValid() correctly parsed a forged URL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests external URL handling.
|
||||
*/
|
||||
function testExternalUrls() {
|
||||
$test_url = 'https://www.drupal.org/';
|
||||
|
||||
// Verify external URL can contain a fragment.
|
||||
$url = $test_url . '#drupal';
|
||||
$result = Url::fromUri($url)->toString();
|
||||
$this->assertEqual($url, $result, 'External URL with fragment works without a fragment in $options.');
|
||||
|
||||
// Verify fragment can be overridden in an external URL.
|
||||
$url = $test_url . '#drupal';
|
||||
$fragment = $this->randomMachineName(10);
|
||||
$result = Url::fromUri($url, array('fragment' => $fragment))->toString();
|
||||
$this->assertEqual($test_url . '#' . $fragment, $result, 'External URL fragment is overridden with a custom fragment in $options.');
|
||||
|
||||
// Verify external URL can contain a query string.
|
||||
$url = $test_url . '?drupal=awesome';
|
||||
$result = Url::fromUri($url)->toString();
|
||||
$this->assertEqual($url, $result);
|
||||
|
||||
// Verify external URL can be extended with a query string.
|
||||
$url = $test_url;
|
||||
$query = array($this->randomMachineName(5) => $this->randomMachineName(5));
|
||||
$result = Url::fromUri($url, array('query' => $query))->toString();
|
||||
$this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, 'External URL can be extended with a query string in $options.');
|
||||
|
||||
// Verify query string can be extended in an external URL.
|
||||
$url = $test_url . '?drupal=awesome';
|
||||
$query = array($this->randomMachineName(5) => $this->randomMachineName(5));
|
||||
$result = Url::fromUri($url, array('query' => $query))->toString();
|
||||
$this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result);
|
||||
}
|
||||
}
|
||||
61
core/modules/system/src/Tests/Common/XssUnitTest.php
Normal file
61
core/modules/system/src/Tests/Common/XssUnitTest.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\XssUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\simpletest\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 = array('filter', 'system');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig(array('system'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests t() functionality.
|
||||
*/
|
||||
function testT() {
|
||||
$text = t('Simple text');
|
||||
$this->assertEqual($text, 'Simple text', 't leaves simple text alone.');
|
||||
$text = t('Escaped text: @value', array('@value' => '<script>'));
|
||||
$this->assertEqual($text, 'Escaped text: <script>', 't replaces and escapes string.');
|
||||
$text = t('Placeholder text: %value', array('%value' => '<script>'));
|
||||
$this->assertEqual($text, 'Placeholder text: <em class="placeholder"><script></em>', 't replaces, escapes and themes string.');
|
||||
$text = t('Verbatim text: !value', array('!value' => '<script>'));
|
||||
$this->assertEqual($text, 'Verbatim text: <script>', 't replaces verbatim string as-is.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that harmful protocols are stripped.
|
||||
*/
|
||||
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::stripDangerousProtocols($url), $expected_plain, '\Drupal\Component\Utility\Url::stripDangerousProtocols() filters a URL and returns plain text.');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Condition\ConditionFormTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Condition;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests that condition plugins basic form handling is working.
|
||||
*
|
||||
* Checks condition forms and submission and gives a very cursory check to make
|
||||
* sure the configuration that was submitted actually causes the condition to
|
||||
* validate correctly.
|
||||
*
|
||||
* @group Condition
|
||||
*/
|
||||
class ConditionFormTest extends WebTestBase {
|
||||
|
||||
public static $modules = array('node', 'condition_test');
|
||||
|
||||
/**
|
||||
* Submit the condition_node_type_test_form to test condition forms.
|
||||
*/
|
||||
function testConfigForm() {
|
||||
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Page'));
|
||||
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
|
||||
$article = entity_create('node', array('type' => 'article', 'title' => $this->randomMachineName()));
|
||||
$article->save();
|
||||
$this->drupalGet('condition_test');
|
||||
$this->assertField('bundles[article]', 'There is an article bundle selector.');
|
||||
$this->assertField('bundles[page]', 'There is a page bundle selector.');
|
||||
$this->drupalPostForm(NULL, array('bundles[page]' => 'page', 'bundles[article]' => 'article'), t('Submit'));
|
||||
// @see \Drupal\condition_test\FormController::submitForm()
|
||||
$this->assertText('Bundle: page');
|
||||
$this->assertText('Bundle: article');
|
||||
$this->assertText('Executed successfully.', 'The form configured condition executed properly.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Condition\CurrentThemeConditionTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Condition;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the CurrentThemeCondition plugin.
|
||||
*
|
||||
* @group Condition
|
||||
*/
|
||||
class CurrentThemeConditionTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('system', 'theme_test');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', array('router'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the current theme condition.
|
||||
*/
|
||||
public function testCurrentTheme() {
|
||||
\Drupal::service('theme_handler')->install(array('test_theme'));
|
||||
|
||||
$manager = \Drupal::service('plugin.manager.condition');
|
||||
/** @var $condition \Drupal\Core\Condition\ConditionInterface */
|
||||
$condition = $manager->createInstance('current_theme');
|
||||
$condition->setConfiguration(array('theme' => 'test_theme'));
|
||||
/** @var $condition_negated \Drupal\Core\Condition\ConditionInterface */
|
||||
$condition_negated = $manager->createInstance('current_theme');
|
||||
$condition_negated->setConfiguration(array('theme' => 'test_theme', 'negate' => TRUE));
|
||||
|
||||
$this->assertEqual($condition->summary(), SafeMarkup::format('The current theme is @theme', array('@theme' => 'test_theme')));
|
||||
$this->assertEqual($condition_negated->summary(), SafeMarkup::format('The current theme is not @theme', array('@theme' => 'test_theme')));
|
||||
|
||||
// The expected theme has not been set up yet.
|
||||
$this->assertFalse($condition->execute());
|
||||
$this->assertTrue($condition_negated->execute());
|
||||
|
||||
// Set the expected theme to be used.
|
||||
$this->config('system.theme')->set('default', 'test_theme')->save();
|
||||
\Drupal::theme()->resetActiveTheme();
|
||||
|
||||
$this->assertTrue($condition->execute());
|
||||
$this->assertFalse($condition_negated->execute());
|
||||
}
|
||||
|
||||
}
|
||||
155
core/modules/system/src/Tests/Database/AlterTest.php
Normal file
155
core/modules/system/src/Tests/Database/AlterTest.php
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\AlterTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the hook_query_alter capabilities of the Select builder.
|
||||
*
|
||||
* @group Database
|
||||
* @see database_test_query_alter()
|
||||
*/
|
||||
class AlterTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests that we can do basic alters.
|
||||
*/
|
||||
function testSimpleAlter() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
$query->addTag('database_test_alter_add_range');
|
||||
|
||||
$result = $query->execute()->fetchAll();
|
||||
|
||||
$this->assertEqual(count($result), 2, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can alter the joins on a query.
|
||||
*/
|
||||
function testAlterWithJoin() {
|
||||
$query = db_select('test_task');
|
||||
$tid_field = $query->addField('test_task', 'tid');
|
||||
$task_field = $query->addField('test_task', 'task');
|
||||
$query->orderBy($task_field);
|
||||
$query->addTag('database_test_alter_add_join');
|
||||
|
||||
$result = $query->execute();
|
||||
|
||||
$records = $result->fetchAll();
|
||||
|
||||
$this->assertEqual(count($records), 2, 'Returned the correct number of rows.');
|
||||
|
||||
$this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.');
|
||||
$this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.');
|
||||
$this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.');
|
||||
$this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.');
|
||||
$this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.');
|
||||
$this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can alter a query's conditionals.
|
||||
*/
|
||||
function testAlterChangeConditional() {
|
||||
$query = db_select('test_task');
|
||||
$tid_field = $query->addField('test_task', 'tid');
|
||||
$pid_field = $query->addField('test_task', 'pid');
|
||||
$task_field = $query->addField('test_task', 'task');
|
||||
$people_alias = $query->join('test', 'people', "test_task.pid = people.id");
|
||||
$name_field = $query->addField($people_alias, 'name', 'name');
|
||||
$query->condition('test_task.tid', '1');
|
||||
$query->orderBy($tid_field);
|
||||
$query->addTag('database_test_alter_change_conditional');
|
||||
|
||||
$result = $query->execute();
|
||||
|
||||
$records = $result->fetchAll();
|
||||
|
||||
$this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
|
||||
$this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.');
|
||||
$this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.');
|
||||
$this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.');
|
||||
$this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can alter the fields of a query.
|
||||
*/
|
||||
function testAlterChangeFields() {
|
||||
$query = db_select('test');
|
||||
$name_field = $query->addField('test', 'name');
|
||||
$age_field = $query->addField('test', 'age', 'age');
|
||||
$query->orderBy('name');
|
||||
$query->addTag('database_test_alter_change_fields');
|
||||
|
||||
$record = $query->execute()->fetch();
|
||||
$this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
|
||||
$this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can alter expressions in the query.
|
||||
*/
|
||||
function testAlterExpression() {
|
||||
$query = db_select('test');
|
||||
$name_field = $query->addField('test', 'name');
|
||||
$age_field = $query->addExpression("age*2", 'double_age');
|
||||
$query->condition('age', 27);
|
||||
$query->addTag('database_test_alter_change_expressions');
|
||||
$result = $query->execute();
|
||||
|
||||
// Ensure that we got the right record.
|
||||
$record = $result->fetch();
|
||||
|
||||
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
|
||||
$this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can remove a range() value from a query.
|
||||
*
|
||||
* This also tests hook_query_TAG_alter().
|
||||
*/
|
||||
function testAlterRemoveRange() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
$query->range(0, 2);
|
||||
$query->addTag('database_test_alter_remove_range');
|
||||
|
||||
$num_records = count($query->execute()->fetchAll());
|
||||
|
||||
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can do basic alters on subqueries.
|
||||
*/
|
||||
function testSimpleAlterSubquery() {
|
||||
// Create a sub-query with an alter tag.
|
||||
$subquery = db_select('test', 'p');
|
||||
$subquery->addField('p', 'name');
|
||||
$subquery->addField('p', 'id');
|
||||
// Pick out George.
|
||||
$subquery->condition('age', 27);
|
||||
$subquery->addExpression("age*2", 'double_age');
|
||||
// This query alter should change it to age * 3.
|
||||
$subquery->addTag('database_test_alter_change_expressions');
|
||||
|
||||
// Create a main query and join to sub-query.
|
||||
$query = db_select('test_task', 'tt');
|
||||
$query->join($subquery, 'pq', 'pq.id = tt.pid');
|
||||
$age_field = $query->addField('pq', 'double_age');
|
||||
$name_field = $query->addField('pq', 'name');
|
||||
|
||||
$record = $query->execute()->fetch();
|
||||
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
|
||||
$this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
|
||||
}
|
||||
}
|
||||
141
core/modules/system/src/Tests/Database/BasicSyntaxTest.php
Normal file
141
core/modules/system/src/Tests/Database/BasicSyntaxTest.php
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\BasicSyntaxTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests SQL syntax interpretation.
|
||||
*
|
||||
* In order to ensure consistent SQL handling throughout Drupal
|
||||
* across multiple kinds of database systems, we test that the
|
||||
* database system interprets SQL syntax in an expected fashion.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class BasicSyntaxTest extends DatabaseTestBase {
|
||||
/**
|
||||
* Tests string concatenation.
|
||||
*/
|
||||
function testConcatLiterals() {
|
||||
$result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', array(
|
||||
':a1' => 'This',
|
||||
':a2' => ' ',
|
||||
':a3' => 'is',
|
||||
':a4' => ' a ',
|
||||
':a5' => 'test.',
|
||||
));
|
||||
$this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string concatenation with field values.
|
||||
*/
|
||||
function testConcatFields() {
|
||||
$result = db_query('SELECT CONCAT(:a1, CONCAT(name, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', array(
|
||||
':a1' => 'The age of ',
|
||||
':a2' => ' is ',
|
||||
':a3' => '.',
|
||||
':age' => 25,
|
||||
));
|
||||
$this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string concatenation with separator.
|
||||
*/
|
||||
function testConcatWsLiterals() {
|
||||
$result = db_query("SELECT CONCAT_WS(', ', :a1, NULL, :a2, :a3, :a4)", array(
|
||||
':a1' => 'Hello',
|
||||
':a2' => NULL,
|
||||
':a3' => '',
|
||||
':a4' => 'world.',
|
||||
));
|
||||
$this->assertIdentical($result->fetchField(), 'Hello, , world.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string concatenation with separator, with field values.
|
||||
*/
|
||||
function testConcatWsFields() {
|
||||
$result = db_query("SELECT CONCAT_WS('-', :a1, name, :a2, age) FROM {test} WHERE age = :age", array(
|
||||
':a1' => 'name',
|
||||
':a2' => 'age',
|
||||
':age' => 25,
|
||||
));
|
||||
$this->assertIdentical($result->fetchField(), 'name-John-age-25');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests escaping of LIKE wildcards.
|
||||
*/
|
||||
function testLikeEscape() {
|
||||
db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'Ring_',
|
||||
))
|
||||
->execute();
|
||||
|
||||
// Match both "Ringo" and "Ring_".
|
||||
$num_matches = db_select('test', 't')
|
||||
->condition('name', 'Ring_', 'LIKE')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Found 2 records.');
|
||||
// Match only "Ring_" using a LIKE expression with no wildcards.
|
||||
$num_matches = db_select('test', 't')
|
||||
->condition('name', db_like('Ring_'), 'LIKE')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Found 1 record.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a LIKE query containing a backslash.
|
||||
*/
|
||||
function testLikeBackslash() {
|
||||
db_insert('test')
|
||||
->fields(array('name'))
|
||||
->values(array(
|
||||
'name' => 'abcde\f',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'abc%\_',
|
||||
))
|
||||
->execute();
|
||||
|
||||
// Match both rows using a LIKE expression with two wildcards and a verbatim
|
||||
// backslash.
|
||||
$num_matches = db_select('test', 't')
|
||||
->condition('name', 'abc%\\\\_', 'LIKE')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Found 2 records.');
|
||||
// Match only the former using a LIKE expression with no wildcards.
|
||||
$num_matches = db_select('test', 't')
|
||||
->condition('name', db_like('abc%\_'), 'LIKE')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Found 1 record.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Database\Connection::getFullQualifiedTableName().
|
||||
*/
|
||||
public function testGetFullQualifiedTableName() {
|
||||
$database = \Drupal::database();
|
||||
$num_matches = $database->select($database->getFullQualifiedTableName('test'), 't')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertIdentical($num_matches, '4', 'Found 4 records.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\CaseSensitivityTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests handling case sensitive collation.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class CaseSensitivityTest extends DatabaseTestBase {
|
||||
/**
|
||||
* Tests BINARY collation in MySQL.
|
||||
*/
|
||||
function testCaseSensitiveInsert() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'john', // <- A record already exists with name 'John'.
|
||||
'age' => 2,
|
||||
'job' => 'Baby',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
$this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.');
|
||||
}
|
||||
}
|
||||
141
core/modules/system/src/Tests/Database/ConnectionTest.php
Normal file
141
core/modules/system/src/Tests/Database/ConnectionTest.php
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\ConnectionTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
/**
|
||||
* Tests of the core database system.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class ConnectionTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests that connections return appropriate connection objects.
|
||||
*/
|
||||
function testConnectionRouting() {
|
||||
// Clone the primary credentials to a replica connection.
|
||||
// Note this will result in two independent connection objects that happen
|
||||
// to point to the same place.
|
||||
$connection_info = Database::getConnectionInfo('default');
|
||||
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
|
||||
|
||||
$db1 = Database::getConnection('default', 'default');
|
||||
$db2 = Database::getConnection('replica', 'default');
|
||||
|
||||
$this->assertNotNull($db1, 'default connection is a real connection object.');
|
||||
$this->assertNotNull($db2, 'replica connection is a real connection object.');
|
||||
$this->assertNotIdentical($db1, $db2, 'Each target refers to a different connection.');
|
||||
|
||||
// Try to open those targets another time, that should return the same objects.
|
||||
$db1b = Database::getConnection('default', 'default');
|
||||
$db2b = Database::getConnection('replica', 'default');
|
||||
$this->assertIdentical($db1, $db1b, 'A second call to getConnection() returns the same object.');
|
||||
$this->assertIdentical($db2, $db2b, 'A second call to getConnection() returns the same object.');
|
||||
|
||||
// Try to open an unknown target.
|
||||
$unknown_target = $this->randomMachineName();
|
||||
$db3 = Database::getConnection($unknown_target, 'default');
|
||||
$this->assertNotNull($db3, 'Opening an unknown target returns a real connection object.');
|
||||
$this->assertIdentical($db1, $db3, 'An unknown target opens the default connection.');
|
||||
|
||||
// Try to open that unknown target another time, that should return the same object.
|
||||
$db3b = Database::getConnection($unknown_target, 'default');
|
||||
$this->assertIdentical($db3, $db3b, 'A second call to getConnection() returns the same object.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that connections return appropriate connection objects.
|
||||
*/
|
||||
function testConnectionRoutingOverride() {
|
||||
// Clone the primary credentials to a replica connection.
|
||||
// Note this will result in two independent connection objects that happen
|
||||
// to point to the same place.
|
||||
$connection_info = Database::getConnectionInfo('default');
|
||||
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
|
||||
|
||||
Database::ignoreTarget('default', 'replica');
|
||||
|
||||
$db1 = Database::getConnection('default', 'default');
|
||||
$db2 = Database::getConnection('replica', 'default');
|
||||
|
||||
$this->assertIdentical($db1, $db2, 'Both targets refer to the same connection.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the closing of a database connection.
|
||||
*/
|
||||
function testConnectionClosing() {
|
||||
// Open the default target so we have an object to compare.
|
||||
$db1 = Database::getConnection('default', 'default');
|
||||
|
||||
// Try to close the default connection, then open a new one.
|
||||
Database::closeConnection('default', 'default');
|
||||
$db2 = Database::getConnection('default', 'default');
|
||||
|
||||
// Opening a connection after closing it should yield an object different than the original.
|
||||
$this->assertNotIdentical($db1, $db2, 'Opening the default connection after it is closed returns a new object.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the connection options of the active database.
|
||||
*/
|
||||
function testConnectionOptions() {
|
||||
$connection_info = Database::getConnectionInfo('default');
|
||||
|
||||
// Be sure we're connected to the default database.
|
||||
$db = Database::getConnection('default', 'default');
|
||||
$connectionOptions = $db->getConnectionOptions();
|
||||
|
||||
// In the MySQL driver, the port can be different, so check individual
|
||||
// options.
|
||||
$this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], 'The default connection info driver matches the current connection options driver.');
|
||||
$this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], 'The default connection info database matches the current connection options database.');
|
||||
|
||||
// Set up identical replica and confirm connection options are identical.
|
||||
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
|
||||
$db2 = Database::getConnection('replica', 'default');
|
||||
$connectionOptions2 = $db2->getConnectionOptions();
|
||||
|
||||
// Get a fresh copy of the default connection options.
|
||||
$connectionOptions = $db->getConnectionOptions();
|
||||
$this->assertIdentical($connectionOptions, $connectionOptions2, 'The default and replica connection options are identical.');
|
||||
|
||||
// Set up a new connection with different connection info.
|
||||
$test = $connection_info['default'];
|
||||
$test['database'] .= 'test';
|
||||
Database::addConnectionInfo('test', 'default', $test);
|
||||
$connection_info = Database::getConnectionInfo('test');
|
||||
|
||||
// Get a fresh copy of the default connection options.
|
||||
$connectionOptions = $db->getConnectionOptions();
|
||||
$this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that you cannot execute multiple statements on phpversion() > 5.5.21 or > 5.6.5.
|
||||
*/
|
||||
public function testMultipleStatementsForNewPhp() {
|
||||
// This just tests mysql, as other PDO integrations don't allow to disable
|
||||
// multiple statements.
|
||||
if (Database::getConnection()->databaseType() !== 'mysql' || !defined('\PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$db = Database::getConnection('default', 'default');
|
||||
try {
|
||||
$db->query('SELECT * FROM {test}; SELECT * FROM {test_people}')->execute();
|
||||
$this->fail('NO PDO exception thrown for multiple statements.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->pass('PDO exception thrown for multiple statements.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
250
core/modules/system/src/Tests/Database/ConnectionUnitTest.php
Normal file
250
core/modules/system/src/Tests/Database/ConnectionUnitTest.php
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\ConnectionUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Doctrine\Common\Reflection\StaticReflectionProperty;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\simpletest\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 integer
|
||||
*/
|
||||
protected function getConnectionID() {
|
||||
return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a connection ID exists.
|
||||
*
|
||||
* @param integer $id
|
||||
* The connection ID to verify.
|
||||
*/
|
||||
protected function assertConnection($id) {
|
||||
$list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
|
||||
return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a connection ID does not exist.
|
||||
*
|
||||
* @param integer $id
|
||||
* The connection ID to verify.
|
||||
*/
|
||||
protected function assertNoConnection($id) {
|
||||
$list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
|
||||
return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Database::closeConnection() without query.
|
||||
*
|
||||
* @todo getConnectionID() executes a query.
|
||||
*/
|
||||
function testOpenClose() {
|
||||
if ($this->skipTest) {
|
||||
return;
|
||||
}
|
||||
// Add and open a new connection.
|
||||
$this->addConnection();
|
||||
$id = $this->getConnectionID();
|
||||
Database::getConnection($this->target, $this->key);
|
||||
|
||||
// Verify that there is a new connection.
|
||||
$this->assertConnection($id);
|
||||
|
||||
// Close the connection.
|
||||
Database::closeConnection($this->target, $this->key);
|
||||
// Wait 20ms to give the database engine sufficient time to react.
|
||||
usleep(20000);
|
||||
|
||||
// Verify that we are back to the original connection count.
|
||||
$this->assertNoConnection($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Database::closeConnection() with a query.
|
||||
*/
|
||||
function testOpenQueryClose() {
|
||||
if ($this->skipTest) {
|
||||
return;
|
||||
}
|
||||
// Add and open a new connection.
|
||||
$this->addConnection();
|
||||
$id = $this->getConnectionID();
|
||||
Database::getConnection($this->target, $this->key);
|
||||
|
||||
// Verify that there is a new connection.
|
||||
$this->assertConnection($id);
|
||||
|
||||
// Execute a query.
|
||||
Database::getConnection($this->target, $this->key)->query('SHOW TABLES');
|
||||
|
||||
// Close the connection.
|
||||
Database::closeConnection($this->target, $this->key);
|
||||
// Wait 20ms to give the database engine sufficient time to react.
|
||||
usleep(20000);
|
||||
|
||||
// Verify that we are back to the original connection count.
|
||||
$this->assertNoConnection($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Database::closeConnection() with a query and custom prefetch method.
|
||||
*/
|
||||
function testOpenQueryPrefetchClose() {
|
||||
if ($this->skipTest) {
|
||||
return;
|
||||
}
|
||||
// Add and open a new connection.
|
||||
$this->addConnection();
|
||||
$id = $this->getConnectionID();
|
||||
Database::getConnection($this->target, $this->key);
|
||||
|
||||
// Verify that there is a new connection.
|
||||
$this->assertConnection($id);
|
||||
|
||||
// Execute a query.
|
||||
Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol();
|
||||
|
||||
// Close the connection.
|
||||
Database::closeConnection($this->target, $this->key);
|
||||
// Wait 20ms to give the database engine sufficient time to react.
|
||||
usleep(20000);
|
||||
|
||||
// Verify that we are back to the original connection count.
|
||||
$this->assertNoConnection($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Database::closeConnection() with a select query.
|
||||
*/
|
||||
function testOpenSelectQueryClose() {
|
||||
if ($this->skipTest) {
|
||||
return;
|
||||
}
|
||||
// Add and open a new connection.
|
||||
$this->addConnection();
|
||||
$id = $this->getConnectionID();
|
||||
Database::getConnection($this->target, $this->key);
|
||||
|
||||
// Verify that there is a new connection.
|
||||
$this->assertConnection($id);
|
||||
|
||||
// Create a table.
|
||||
$name = 'foo';
|
||||
Database::getConnection($this->target, $this->key)->schema()->createTable($name, array(
|
||||
'fields' => array(
|
||||
'name' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// Execute a query.
|
||||
Database::getConnection($this->target, $this->key)->select('foo', 'f')
|
||||
->fields('f', array('name'))
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
// Drop the table.
|
||||
Database::getConnection($this->target, $this->key)->schema()->dropTable($name);
|
||||
|
||||
// Close the connection.
|
||||
Database::closeConnection($this->target, $this->key);
|
||||
// Wait 20ms to give the database engine sufficient time to react.
|
||||
usleep(20000);
|
||||
|
||||
// Verify that we are back to the original connection count.
|
||||
$this->assertNoConnection($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests pdo options override.
|
||||
*/
|
||||
public function testConnectionOpen() {
|
||||
$connection = Database::getConnection('default');
|
||||
$reflection = new \ReflectionObject($connection);
|
||||
$connection_property = $reflection->getProperty('connection');
|
||||
$connection_property->setAccessible(TRUE);
|
||||
$error_mode = $connection_property->getValue($connection)
|
||||
->getAttribute(\PDO::ATTR_ERRMODE);
|
||||
$this->assertEqual($error_mode, \PDO::ERRMODE_EXCEPTION, 'Ensure the default error mode is set to exception.');
|
||||
|
||||
$connection = Database::getConnectionInfo('default');
|
||||
$connection['default']['pdo'][\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_SILENT;
|
||||
Database::addConnectionInfo('test', 'default', $connection['default']);
|
||||
$connection = Database::getConnection('default', 'test');
|
||||
|
||||
$reflection = new \ReflectionObject($connection);
|
||||
$connection_property = $reflection->getProperty('connection');
|
||||
$connection_property->setAccessible(TRUE);
|
||||
$error_mode = $connection_property->getValue($connection)
|
||||
->getAttribute(\PDO::ATTR_ERRMODE);
|
||||
$this->assertEqual($error_mode, \PDO::ERRMODE_SILENT, 'Ensure PDO connection options can be overridden.');
|
||||
|
||||
Database::removeConnection('test');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\DatabaseExceptionWrapperTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\DatabaseExceptionWrapper;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\simpletest\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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
142
core/modules/system/src/Tests/Database/DatabaseTestBase.php
Normal file
142
core/modules/system/src/Tests/Database/DatabaseTestBase.php
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\DatabaseTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Base class for databases database tests.
|
||||
*
|
||||
* Because all database tests share the same test data, we can centralize that
|
||||
* here.
|
||||
*/
|
||||
abstract class DatabaseTestBase extends KernelTestBase {
|
||||
|
||||
public static $modules = array('database_test');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('database_test', array(
|
||||
'test',
|
||||
'test_people',
|
||||
'test_people_copy',
|
||||
'test_one_blob',
|
||||
'test_two_blobs',
|
||||
'test_task',
|
||||
'test_null',
|
||||
'test_serialized',
|
||||
));
|
||||
self::addSampleData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up tables for NULL handling.
|
||||
*/
|
||||
function ensureSampleDataNull() {
|
||||
db_insert('test_null')
|
||||
->fields(array('name', 'age'))
|
||||
->values(array(
|
||||
'name' => 'Kermit',
|
||||
'age' => 25,
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'Fozzie',
|
||||
'age' => NULL,
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'Gonzo',
|
||||
'age' => 27,
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up our sample data.
|
||||
*/
|
||||
static function addSampleData() {
|
||||
// We need the IDs, so we can't use a multi-insert here.
|
||||
$john = db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'John',
|
||||
'age' => 25,
|
||||
'job' => 'Singer',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$george = db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'George',
|
||||
'age' => 27,
|
||||
'job' => 'Singer',
|
||||
))
|
||||
->execute();
|
||||
|
||||
db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'Ringo',
|
||||
'age' => 28,
|
||||
'job' => 'Drummer',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$paul = db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'Paul',
|
||||
'age' => 26,
|
||||
'job' => 'Songwriter',
|
||||
))
|
||||
->execute();
|
||||
|
||||
db_insert('test_people')
|
||||
->fields(array(
|
||||
'name' => 'Meredith',
|
||||
'age' => 30,
|
||||
'job' => 'Speaker',
|
||||
))
|
||||
->execute();
|
||||
|
||||
db_insert('test_task')
|
||||
->fields(array('pid', 'task', 'priority'))
|
||||
->values(array(
|
||||
'pid' => $john,
|
||||
'task' => 'eat',
|
||||
'priority' => 3,
|
||||
))
|
||||
->values(array(
|
||||
'pid' => $john,
|
||||
'task' => 'sleep',
|
||||
'priority' => 4,
|
||||
))
|
||||
->values(array(
|
||||
'pid' => $john,
|
||||
'task' => 'code',
|
||||
'priority' => 1,
|
||||
))
|
||||
->values(array(
|
||||
'pid' => $george,
|
||||
'task' => 'sing',
|
||||
'priority' => 2,
|
||||
))
|
||||
->values(array(
|
||||
'pid' => $george,
|
||||
'task' => 'sleep',
|
||||
'priority' => 2,
|
||||
))
|
||||
->values(array(
|
||||
'pid' => $paul,
|
||||
'task' => 'found new band',
|
||||
'priority' => 1,
|
||||
))
|
||||
->values(array(
|
||||
'pid' => $paul,
|
||||
'task' => 'perform at superbowl',
|
||||
'priority' => 3,
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\DatabaseWebTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Base class for databases database tests.
|
||||
*/
|
||||
abstract class DatabaseWebTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('database_test');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
DatabaseTestBase::addSampleData();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\DeleteTruncateTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests delete and truncate queries.
|
||||
*
|
||||
* The DELETE tests are not as extensive, as all of the interesting code for
|
||||
* DELETE queries is in the conditional which is identical to the UPDATE and
|
||||
* SELECT conditional handling.
|
||||
*
|
||||
* The TRUNCATE tests are not extensive either, because the behavior of
|
||||
* TRUNCATE queries is not consistent across database engines. We only test
|
||||
* that a TRUNCATE query actually deletes all rows from the target table.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class DeleteTruncateTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that we can use a subselect in a delete successfully.
|
||||
*/
|
||||
function testSubselectDelete() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
|
||||
$pid_to_delete = db_query("SELECT * FROM {test_task} WHERE task = 'sleep'")->fetchField();
|
||||
|
||||
$subquery = db_select('test', 't')
|
||||
->fields('t', array('id'))
|
||||
->condition('t.id', array($pid_to_delete), 'IN');
|
||||
$delete = db_delete('test_task')
|
||||
->condition('task', 'sleep')
|
||||
->condition('pid', $subquery, 'IN');
|
||||
|
||||
$num_deleted = $delete->execute();
|
||||
$this->assertEqual($num_deleted, 1, 'Deleted 1 record.');
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
|
||||
$this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can delete a single record successfully.
|
||||
*/
|
||||
function testSimpleDelete() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$num_deleted = db_delete('test')
|
||||
->condition('id', 1)
|
||||
->execute();
|
||||
$this->assertIdentical($num_deleted, 1, 'Deleted 1 record.');
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
$this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can truncate a whole table successfully.
|
||||
*/
|
||||
function testTruncate() {
|
||||
$num_records_before = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
|
||||
$this->assertTrue($num_records_before > 0, 'The table is not empty.');
|
||||
|
||||
db_truncate('test')->execute();
|
||||
|
||||
$num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
|
||||
$this->assertEqual(0, $num_records_after, 'Truncate really deletes everything.');
|
||||
}
|
||||
}
|
||||
17
core/modules/system/src/Tests/Database/FakeRecord.php
Normal file
17
core/modules/system/src/Tests/Database/FakeRecord.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\FakeRecord.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Fetches into a class.
|
||||
*
|
||||
* PDO supports using a new instance of an arbitrary class for records
|
||||
* rather than just a stdClass or array. This class is for testing that
|
||||
* functionality. (See testQueryFetchClass() below)
|
||||
*/
|
||||
class FakeRecord { }
|
||||
150
core/modules/system/src/Tests/Database/FetchTest.php
Normal file
150
core/modules/system/src/Tests/Database/FetchTest.php
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\FetchTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\RowCountException;
|
||||
use Drupal\Core\Database\StatementInterface;
|
||||
|
||||
/**
|
||||
* Tests the Database system's various fetch capabilities.
|
||||
*
|
||||
* We get timeout errors if we try to run too many tests at once.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class FetchTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that we can fetch a record properly in default object mode.
|
||||
*/
|
||||
function testQueryFetchDefault() {
|
||||
$records = array();
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25));
|
||||
$this->assertTrue($result instanceof StatementInterface, 'Result set is a Drupal statement object.');
|
||||
foreach ($result as $record) {
|
||||
$records[] = $record;
|
||||
$this->assertTrue(is_object($record), 'Record is an object.');
|
||||
$this->assertIdentical($record->name, 'John', '25 year old is John.');
|
||||
}
|
||||
|
||||
$this->assertIdentical(count($records), 1, 'There is only one record.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can fetch a record to an object explicitly.
|
||||
*/
|
||||
function testQueryFetchObject() {
|
||||
$records = array();
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => \PDO::FETCH_OBJ));
|
||||
foreach ($result as $record) {
|
||||
$records[] = $record;
|
||||
$this->assertTrue(is_object($record), 'Record is an object.');
|
||||
$this->assertIdentical($record->name, 'John', '25 year old is John.');
|
||||
}
|
||||
|
||||
$this->assertIdentical(count($records), 1, 'There is only one record.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can fetch a record to an associative array explicitly.
|
||||
*/
|
||||
function testQueryFetchArray() {
|
||||
$records = array();
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => \PDO::FETCH_ASSOC));
|
||||
foreach ($result as $record) {
|
||||
$records[] = $record;
|
||||
if ($this->assertTrue(is_array($record), 'Record is an array.')) {
|
||||
$this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.');
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertIdentical(count($records), 1, 'There is only one record.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can fetch a record into a new instance of a custom class.
|
||||
*
|
||||
* @see \Drupal\system\Tests\Database\FakeRecord
|
||||
*/
|
||||
function testQueryFetchClass() {
|
||||
$records = array();
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => 'Drupal\system\Tests\Database\FakeRecord'));
|
||||
foreach ($result as $record) {
|
||||
$records[] = $record;
|
||||
if ($this->assertTrue($record instanceof FakeRecord, 'Record is an object of class FakeRecord.')) {
|
||||
$this->assertIdentical($record->name, 'John', '25 year old is John.');
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertIdentical(count($records), 1, 'There is only one record.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can fetch a record into an indexed array explicitly.
|
||||
*/
|
||||
function testQueryFetchNum() {
|
||||
$records = array();
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => \PDO::FETCH_NUM));
|
||||
foreach ($result as $record) {
|
||||
$records[] = $record;
|
||||
if ($this->assertTrue(is_array($record), 'Record is an array.')) {
|
||||
$this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.');
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertIdentical(count($records), 1, 'There is only one record');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can fetch a record into a doubly-keyed array explicitly.
|
||||
*/
|
||||
function testQueryFetchBoth() {
|
||||
$records = array();
|
||||
$result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => \PDO::FETCH_BOTH));
|
||||
foreach ($result as $record) {
|
||||
$records[] = $record;
|
||||
if ($this->assertTrue(is_array($record), 'Record is an array.')) {
|
||||
$this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.');
|
||||
$this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.');
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertIdentical(count($records), 1, 'There is only one record.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can fetch an entire column of a result set at once.
|
||||
*/
|
||||
function testQueryFetchCol() {
|
||||
$result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25));
|
||||
$column = $result->fetchCol();
|
||||
$this->assertIdentical(count($column), 3, 'fetchCol() returns the right number of records.');
|
||||
|
||||
$result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25));
|
||||
$i = 0;
|
||||
foreach ($result as $record) {
|
||||
$this->assertIdentical($record->name, $column[$i++], 'Column matches direct access.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that rowCount() throws exception on SELECT query.
|
||||
*/
|
||||
public function testRowCount() {
|
||||
$result = db_query('SELECT name FROM {test}');
|
||||
try {
|
||||
$result->rowCount();
|
||||
$exception = FALSE;
|
||||
}
|
||||
catch (RowCountException $e) {
|
||||
$exception = TRUE;
|
||||
}
|
||||
$this->assertTrue($exception, 'Exception was thrown');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\InsertDefaultsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\Query\NoFieldsException;
|
||||
|
||||
/**
|
||||
* Tests the Insert query builder with default values.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class InsertDefaultsTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests that we can run a query that uses default values for everything.
|
||||
*/
|
||||
function testDefaultInsert() {
|
||||
$query = db_insert('test')->useDefaults(array('job'));
|
||||
$id = $query->execute();
|
||||
|
||||
$schema = drupal_get_module_schema('database_test', 'test');
|
||||
|
||||
$job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
|
||||
$this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that no action will be preformed if no fields are specified.
|
||||
*/
|
||||
function testDefaultEmptyInsert() {
|
||||
$num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
try {
|
||||
db_insert('test')->execute();
|
||||
// This is only executed if no exception has been thrown.
|
||||
$this->fail('Expected exception NoFieldsException has not been thrown.');
|
||||
} catch (NoFieldsException $e) {
|
||||
$this->pass('Expected exception NoFieldsException has been thrown.');
|
||||
}
|
||||
|
||||
$num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
$this->assertIdentical($num_records_before, $num_records_after, 'Do nothing as no fields are specified.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can insert fields with values and defaults in the same query.
|
||||
*/
|
||||
function testDefaultInsertWithFields() {
|
||||
$query = db_insert('test')
|
||||
->fields(array('name' => 'Bob'))
|
||||
->useDefaults(array('job'));
|
||||
$id = $query->execute();
|
||||
|
||||
$schema = drupal_get_module_schema('database_test', 'test');
|
||||
|
||||
$job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
|
||||
$this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
|
||||
}
|
||||
}
|
||||
43
core/modules/system/src/Tests/Database/InsertLobTest.php
Normal file
43
core/modules/system/src/Tests/Database/InsertLobTest.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\InsertLobTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the Insert query builder with LOB fields.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class InsertLobTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests that we can insert a single blob field successfully.
|
||||
*/
|
||||
function testInsertOneBlob() {
|
||||
$data = "This is\000a test.";
|
||||
$this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.');
|
||||
$id = db_insert('test_one_blob')
|
||||
->fields(array('blob1' => $data))
|
||||
->execute();
|
||||
$r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc();
|
||||
$this->assertTrue($r['blob1'] === $data, format_string('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can insert multiple blob fields in the same query.
|
||||
*/
|
||||
function testInsertMultipleBlob() {
|
||||
$id = db_insert('test_two_blobs')
|
||||
->fields(array(
|
||||
'blob1' => 'This is',
|
||||
'blob2' => 'a test',
|
||||
))
|
||||
->execute();
|
||||
$r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc();
|
||||
$this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', 'Can insert multiple blobs per row.');
|
||||
}
|
||||
}
|
||||
185
core/modules/system/src/Tests/Database/InsertTest.php
Normal file
185
core/modules/system/src/Tests/Database/InsertTest.php
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\InsertTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the insert builder.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class InsertTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests very basic insert functionality.
|
||||
*/
|
||||
function testSimpleInsert() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$query = db_insert('test');
|
||||
$query->fields(array(
|
||||
'name' => 'Yoko',
|
||||
'age' => '29',
|
||||
));
|
||||
$query->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
$this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Yoko'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '29', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can insert multiple records in one query object.
|
||||
*/
|
||||
function testMultiInsert() {
|
||||
$num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$query = db_insert('test');
|
||||
$query->fields(array(
|
||||
'name' => 'Larry',
|
||||
'age' => '30',
|
||||
));
|
||||
|
||||
// We should be able to specify values in any order if named.
|
||||
$query->values(array(
|
||||
'age' => '31',
|
||||
'name' => 'Curly',
|
||||
));
|
||||
|
||||
// We should be able to say "use the field order".
|
||||
// This is not the recommended mechanism for most cases, but it should work.
|
||||
$query->values(array('Moe', '32'));
|
||||
$query->execute();
|
||||
|
||||
$num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
$this->assertIdentical($num_records_before + 3, $num_records_after, 'Record inserts correctly.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an insert object can be reused with new data after it executes.
|
||||
*/
|
||||
function testRepeatedInsert() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$query = db_insert('test');
|
||||
|
||||
$query->fields(array(
|
||||
'name' => 'Larry',
|
||||
'age' => '30',
|
||||
));
|
||||
$query->execute(); // This should run the insert, but leave the fields intact.
|
||||
|
||||
// We should be able to specify values in any order if named.
|
||||
$query->values(array(
|
||||
'age' => '31',
|
||||
'name' => 'Curly',
|
||||
));
|
||||
$query->execute();
|
||||
|
||||
// We should be able to say "use the field order".
|
||||
$query->values(array('Moe', '32'));
|
||||
$query->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
$this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, 'Record inserts correctly.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can specify fields without values and specify values later.
|
||||
*/
|
||||
function testInsertFieldOnlyDefinition() {
|
||||
// This is useful for importers, when we want to create a query and define
|
||||
// its fields once, then loop over a multi-insert execution.
|
||||
db_insert('test')
|
||||
->fields(array('name', 'age'))
|
||||
->values(array('Larry', '30'))
|
||||
->values(array('Curly', '31'))
|
||||
->values(array('Moe', '32'))
|
||||
->execute();
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that inserts return the proper auto-increment ID.
|
||||
*/
|
||||
function testInsertLastInsertID() {
|
||||
$id = db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'Larry',
|
||||
'age' => '30',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$this->assertIdentical($id, '5', 'Auto-increment ID returned successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the INSERT INTO ... SELECT (fields) ... syntax works.
|
||||
*/
|
||||
function testInsertSelectFields() {
|
||||
$query = db_select('test_people', 'tp');
|
||||
// The query builder will always append expressions after fields.
|
||||
// Add the expression first to test that the insert fields are correctly
|
||||
// re-ordered.
|
||||
$query->addExpression('tp.age', 'age');
|
||||
$query
|
||||
->fields('tp', array('name','job'))
|
||||
->condition('tp.name', 'Meredith');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// INSERT INTO test (age, name, job)
|
||||
// SELECT tp.age AS age, tp.name AS name, tp.job AS job
|
||||
// FROM test_people tp
|
||||
// WHERE tp.name = 'Meredith'
|
||||
db_insert('test')
|
||||
->from($query)
|
||||
->execute();
|
||||
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Meredith'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the INSERT INTO ... SELECT * ... syntax works.
|
||||
*/
|
||||
function testInsertSelectAll() {
|
||||
$query = db_select('test_people', 'tp')
|
||||
->fields('tp')
|
||||
->condition('tp.name', 'Meredith');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// INSERT INTO test_people_copy
|
||||
// SELECT *
|
||||
// FROM test_people tp
|
||||
// WHERE tp.name = 'Meredith'
|
||||
db_insert('test_people_copy')
|
||||
->from($query)
|
||||
->execute();
|
||||
|
||||
$saved_age = db_query('SELECT age FROM {test_people_copy} WHERE name = :name', array(':name' => 'Meredith'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
|
||||
}
|
||||
|
||||
}
|
||||
73
core/modules/system/src/Tests/Database/InvalidDataTest.php
Normal file
73
core/modules/system/src/Tests/Database/InvalidDataTest.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\InvalidDataTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\IntegrityConstraintViolationException;
|
||||
|
||||
/**
|
||||
* Tests handling of some invalid data.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class InvalidDataTest extends DatabaseTestBase {
|
||||
/**
|
||||
* Tests aborting of traditional SQL database systems with invalid data.
|
||||
*/
|
||||
function testInsertDuplicateData() {
|
||||
// Try to insert multiple records where at least one has bad data.
|
||||
try {
|
||||
db_insert('test')
|
||||
->fields(array('name', 'age', 'job'))
|
||||
->values(array(
|
||||
'name' => 'Elvis',
|
||||
'age' => 63,
|
||||
'job' => 'Singer',
|
||||
))->values(array(
|
||||
'name' => 'John', // <-- Duplicate value on unique field.
|
||||
'age' => 17,
|
||||
'job' => 'Consultant',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'Frank',
|
||||
'age' => 75,
|
||||
'job' => 'Singer',
|
||||
))
|
||||
->execute();
|
||||
$this->fail('Insert succeeded when it should not have.');
|
||||
}
|
||||
catch (IntegrityConstraintViolationException $e) {
|
||||
// Check if the first record was inserted.
|
||||
$name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField();
|
||||
|
||||
if ($name == 'Elvis') {
|
||||
if (!Database::getConnection()->supportsTransactions()) {
|
||||
// This is an expected fail.
|
||||
// Database engines that don't support transactions can leave partial
|
||||
// inserts in place when an error occurs. This is the case for MySQL
|
||||
// when running on a MyISAM table.
|
||||
$this->pass("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions");
|
||||
}
|
||||
else {
|
||||
$this->fail('The whole transaction is rolled back when a duplicate key insert occurs.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->pass('The whole transaction is rolled back when a duplicate key insert occurs.');
|
||||
}
|
||||
|
||||
// Ensure the other values were not inserted.
|
||||
$record = db_select('test')
|
||||
->fields('test', array('name', 'age'))
|
||||
->condition('age', array(17, 75), 'IN')
|
||||
->execute()->fetchObject();
|
||||
|
||||
$this->assertFalse($record, 'The rest of the insert aborted as expected.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
133
core/modules/system/src/Tests/Database/LoggingTest.php
Normal file
133
core/modules/system/src/Tests/Database/LoggingTest.php
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\LoggingTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
/**
|
||||
* Tests the query logging facility.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class LoggingTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests that we can log the existence of a query.
|
||||
*/
|
||||
function testEnableLogging() {
|
||||
Database::startLog('testing');
|
||||
|
||||
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol();
|
||||
|
||||
// Trigger a call that does not have file in the backtrace.
|
||||
call_user_func_array('db_query', array('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo')))->fetchCol();
|
||||
|
||||
$queries = Database::getLog('testing', 'default');
|
||||
|
||||
$this->assertEqual(count($queries), 3, 'Correct number of queries recorded.');
|
||||
|
||||
foreach ($queries as $query) {
|
||||
$this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can run two logs in parallel.
|
||||
*/
|
||||
function testEnableMultiLogging() {
|
||||
Database::startLog('testing1');
|
||||
|
||||
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
|
||||
|
||||
Database::startLog('testing2');
|
||||
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol();
|
||||
|
||||
$queries1 = Database::getLog('testing1');
|
||||
$queries2 = Database::getLog('testing2');
|
||||
|
||||
$this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.');
|
||||
$this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests logging queries against multiple targets on the same connection.
|
||||
*/
|
||||
function testEnableTargetLogging() {
|
||||
// Clone the primary credentials to a replica connection and to another fake
|
||||
// connection.
|
||||
$connection_info = Database::getConnectionInfo('default');
|
||||
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
|
||||
|
||||
Database::startLog('testing1');
|
||||
|
||||
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
|
||||
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'replica'));//->fetchCol();
|
||||
|
||||
$queries1 = Database::getLog('testing1');
|
||||
|
||||
$this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
|
||||
$this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
|
||||
$this->assertEqual($queries1[1]['target'], 'replica', 'Second query used replica target.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that logs to separate targets use the same connection properly.
|
||||
*
|
||||
* This test is identical to the one above, except that it doesn't create
|
||||
* a fake target so the query should fall back to running on the default
|
||||
* target.
|
||||
*/
|
||||
function testEnableTargetLoggingNoTarget() {
|
||||
Database::startLog('testing1');
|
||||
|
||||
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
|
||||
|
||||
// We use "fake" here as a target because any non-existent target will do.
|
||||
// However, because all of the tests in this class share a single page
|
||||
// request there is likely to be a target of "replica" from one of the other
|
||||
// unit tests, so we use a target here that we know with absolute certainty
|
||||
// does not exist.
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'fake'))->fetchCol();
|
||||
|
||||
$queries1 = Database::getLog('testing1');
|
||||
|
||||
$this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
|
||||
$this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
|
||||
$this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can log queries separately on different connections.
|
||||
*/
|
||||
function testEnableMultiConnectionLogging() {
|
||||
// Clone the primary credentials to a fake connection.
|
||||
// That both connections point to the same physical database is irrelevant.
|
||||
$connection_info = Database::getConnectionInfo('default');
|
||||
Database::addConnectionInfo('test2', 'default', $connection_info['default']);
|
||||
|
||||
Database::startLog('testing1');
|
||||
Database::startLog('testing1', 'test2');
|
||||
|
||||
db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
|
||||
|
||||
$old_key = db_set_active('test2');
|
||||
|
||||
db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'replica'))->fetchCol();
|
||||
|
||||
db_set_active($old_key);
|
||||
|
||||
$queries1 = Database::getLog('testing1');
|
||||
$queries2 = Database::getLog('testing1', 'test2');
|
||||
|
||||
$this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.');
|
||||
$this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
|
||||
}
|
||||
}
|
||||
237
core/modules/system/src/Tests/Database/MergeTest.php
Normal file
237
core/modules/system/src/Tests/Database/MergeTest.php
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\MergeTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\Query\Merge;
|
||||
use Drupal\Core\Database\Query\InvalidMergeQueryException;
|
||||
|
||||
/**
|
||||
* Tests the MERGE query builder.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class MergeTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that we can merge-insert a record successfully.
|
||||
*/
|
||||
function testMergeInsert() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
$result = db_merge('test_people')
|
||||
->key('job', 'Presenter')
|
||||
->fields(array(
|
||||
'age' => 31,
|
||||
'name' => 'Tiffany',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$this->assertEqual($result, Merge::STATUS_INSERT, 'Insert status returned.');
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
$this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
|
||||
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
|
||||
$this->assertEqual($person->age, 31, 'Age set correctly.');
|
||||
$this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can merge-update a record successfully.
|
||||
*/
|
||||
function testMergeUpdate() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
$result = db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->fields(array(
|
||||
'age' => 31,
|
||||
'name' => 'Tiffany',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$this->assertEqual($result, Merge::STATUS_UPDATE, 'Update status returned.');
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
$this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
|
||||
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
|
||||
$this->assertEqual($person->age, 31, 'Age set correctly.');
|
||||
$this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can merge-update a record successfully.
|
||||
*
|
||||
* This test varies from the previous test because it manually defines which
|
||||
* fields are inserted, and which fields are updated.
|
||||
*/
|
||||
function testMergeUpdateExcept() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->insertFields(array('age' => 31))
|
||||
->updateFields(array('name' => 'Tiffany'))
|
||||
->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
$this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
|
||||
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
|
||||
$this->assertEqual($person->age, 30, 'Age skipped correctly.');
|
||||
$this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can merge-update a record, with alternate replacement.
|
||||
*/
|
||||
function testMergeUpdateExplicit() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->insertFields(array(
|
||||
'age' => 31,
|
||||
'name' => 'Tiffany',
|
||||
))
|
||||
->updateFields(array(
|
||||
'name' => 'Joe',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
$this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
|
||||
$this->assertEqual($person->name, 'Joe', 'Name set correctly.');
|
||||
$this->assertEqual($person->age, 30, 'Age skipped correctly.');
|
||||
$this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can merge-update a record successfully, with expressions.
|
||||
*/
|
||||
function testMergeUpdateExpression() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
$age_before = db_query('SELECT age FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetchField();
|
||||
|
||||
// This is a very contrived example, as I have no idea why you'd want to
|
||||
// change age this way, but that's beside the point.
|
||||
// Note that we are also double-setting age here, once as a literal and
|
||||
// once as an expression. This test will only pass if the expression wins,
|
||||
// which is what is supposed to happen.
|
||||
db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->fields(array('name' => 'Tiffany'))
|
||||
->insertFields(array('age' => 31))
|
||||
->expression('age', 'age + :age', array(':age' => 4))
|
||||
->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
$this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
|
||||
$this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
|
||||
$this->assertEqual($person->age, $age_before + 4, 'Age updated correctly.');
|
||||
$this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can merge-insert without any update fields.
|
||||
*/
|
||||
function testMergeInsertWithoutUpdate() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
db_merge('test_people')
|
||||
->key('job', 'Presenter')
|
||||
->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
$this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
|
||||
$this->assertEqual($person->name, '', 'Name set correctly.');
|
||||
$this->assertEqual($person->age, 0, 'Age set correctly.');
|
||||
$this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can merge-update without any update fields.
|
||||
*/
|
||||
function testMergeUpdateWithoutUpdate() {
|
||||
$num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
|
||||
db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
$this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
|
||||
$this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.');
|
||||
$this->assertEqual($person->age, 30, 'Age skipped correctly.');
|
||||
$this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
|
||||
|
||||
db_merge('test_people')
|
||||
->key('job', 'Speaker')
|
||||
->insertFields(array('age' => 31))
|
||||
->execute();
|
||||
|
||||
$num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
|
||||
$this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
|
||||
$this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.');
|
||||
$this->assertEqual($person->age, 30, 'Age skipped correctly.');
|
||||
$this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an invalid merge query throws an exception.
|
||||
*/
|
||||
function testInvalidMerge() {
|
||||
try {
|
||||
// This query will fail because there is no key field specified.
|
||||
// Normally it would throw an exception but we are suppressing it with
|
||||
// the throw_exception option.
|
||||
$options['throw_exception'] = FALSE;
|
||||
db_merge('test_people', $options)
|
||||
->fields(array(
|
||||
'age' => 31,
|
||||
'name' => 'Tiffany',
|
||||
))
|
||||
->execute();
|
||||
$this->pass('$options[\'throw_exception\'] is FALSE, no InvalidMergeQueryException thrown.');
|
||||
}
|
||||
catch (InvalidMergeQueryException $e) {
|
||||
$this->fail('$options[\'throw_exception\'] is FALSE, but InvalidMergeQueryException thrown for invalid query.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// This query will fail because there is no key field specified.
|
||||
db_merge('test_people')
|
||||
->fields(array(
|
||||
'age' => 31,
|
||||
'name' => 'Tiffany',
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
catch (InvalidMergeQueryException $e) {
|
||||
$this->pass('InvalidMergeQueryException thrown for invalid query.');
|
||||
return;
|
||||
}
|
||||
$this->fail('No InvalidMergeQueryException thrown');
|
||||
}
|
||||
}
|
||||
43
core/modules/system/src/Tests/Database/NextIdTest.php
Normal file
43
core/modules/system/src/Tests/Database/NextIdTest.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\NextIdTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the sequences API.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class NextIdTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The modules to enable.
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', 'sequences');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the sequences API works.
|
||||
*/
|
||||
function testDbNextId() {
|
||||
$first = db_next_id();
|
||||
$second = db_next_id();
|
||||
// We can test for exact increase in here because we know there is no
|
||||
// other process operating on these tables -- normally we could only
|
||||
// expect $second > $first.
|
||||
$this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.');
|
||||
$result = db_next_id(1000);
|
||||
$this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.');
|
||||
}
|
||||
}
|
||||
85
core/modules/system/src/Tests/Database/QueryTest.php
Normal file
85
core/modules/system/src/Tests/Database/QueryTest.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\QueryTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests Drupal's extended prepared statement syntax..
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class QueryTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests that we can pass an array of values directly in the query.
|
||||
*/
|
||||
function testArraySubstitution() {
|
||||
$names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', array(':ages[]' => array(25, 26, 27)))->fetchAll();
|
||||
$this->assertEqual(count($names), 3, 'Correct number of names returned');
|
||||
|
||||
$names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', array(':ages[]' => array(25)))->fetchAll();
|
||||
$this->assertEqual(count($names), 1, 'Correct number of names returned');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can not pass a scalar value when an array is expected.
|
||||
*/
|
||||
function testScalarSubstitution() {
|
||||
try {
|
||||
$names = db_query('SELECT name FROM {test} WHERE age IN ( :ages[] ) ORDER BY age', array(':ages[]' => 25))->fetchAll();
|
||||
$this->fail('Array placeholder with scalar argument should result in an exception.');
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
$this->pass('Array placeholder with scalar argument should result in an exception.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SQL injection via database query array arguments.
|
||||
*/
|
||||
public function testArrayArgumentsSQLInjection() {
|
||||
// Attempt SQL injection and verify that it does not work.
|
||||
$condition = array(
|
||||
"1 ;INSERT INTO {test} (name) VALUES ('test12345678'); -- " => '',
|
||||
'1' => '',
|
||||
);
|
||||
try {
|
||||
db_query("SELECT * FROM {test} WHERE name = :name", array(':name' => $condition))->fetchObject();
|
||||
$this->fail('SQL injection attempt via array arguments should result in a database exception.');
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
$this->pass('SQL injection attempt via array arguments should result in a database exception.');
|
||||
}
|
||||
|
||||
// Test that the insert query that was used in the SQL injection attempt did
|
||||
// not result in a row being inserted in the database.
|
||||
$result = db_select('test')
|
||||
->condition('name', 'test12345678')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests numeric query parameter expansion in expressions.
|
||||
*
|
||||
* @see \Drupal\Core\Database\Driver\sqlite\Statement::getStatement()
|
||||
* @see http://bugs.php.net/bug.php?id=45259
|
||||
*/
|
||||
public function testNumericExpressionSubstitution() {
|
||||
$count = db_query('SELECT COUNT(*) >= 3 FROM {test}')->fetchField();
|
||||
$this->assertEqual((bool) $count, TRUE);
|
||||
|
||||
$count = db_query('SELECT COUNT(*) >= :count FROM {test}', array(
|
||||
':count' => 3,
|
||||
))->fetchField();
|
||||
$this->assertEqual((bool) $count, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
37
core/modules/system/src/Tests/Database/RangeQueryTest.php
Normal file
37
core/modules/system/src/Tests/Database/RangeQueryTest.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\RangeQueryTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the Range query functionality.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class RangeQueryTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('database_test');
|
||||
|
||||
/**
|
||||
* Confirms that range queries work and return the correct result.
|
||||
*/
|
||||
function testRangeQuery() {
|
||||
// Test if return correct number of rows.
|
||||
$range_rows = db_query_range("SELECT name FROM {test} ORDER BY name", 1, 3)->fetchAll();
|
||||
$this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.');
|
||||
|
||||
// Test if return target data.
|
||||
$raw_rows = db_query('SELECT name FROM {test} ORDER BY name')->fetchAll();
|
||||
$raw_rows = array_slice($raw_rows, 1, 3);
|
||||
$this->assertEqual($range_rows, $raw_rows);
|
||||
}
|
||||
}
|
||||
64
core/modules/system/src/Tests/Database/RegressionTest.php
Normal file
64
core/modules/system/src/Tests/Database/RegressionTest.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\RegressionTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Regression tests cases for the database layer.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class RegressionTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'user');
|
||||
|
||||
/**
|
||||
* Ensures that non-ASCII UTF-8 data is stored in the database properly.
|
||||
*/
|
||||
function testRegression_310447() {
|
||||
// That's a 255 character UTF-8 string.
|
||||
$job = str_repeat("é", 255);
|
||||
db_insert('test')
|
||||
->fields(array(
|
||||
'name' => $this->randomMachineName(),
|
||||
'age' => 20,
|
||||
'job' => $job,
|
||||
))->execute();
|
||||
|
||||
$from_database = db_query('SELECT job FROM {test} WHERE job = :job', array(':job' => $job))->fetchField();
|
||||
$this->assertIdentical($job, $from_database, 'The database handles UTF-8 characters cleanly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the db_table_exists() function.
|
||||
*/
|
||||
function testDBTableExists() {
|
||||
$this->assertIdentical(TRUE, db_table_exists('test'), 'Returns true for existent table.');
|
||||
$this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the db_field_exists() function.
|
||||
*/
|
||||
function testDBFieldExists() {
|
||||
$this->assertIdentical(TRUE, db_field_exists('test', 'name'), 'Returns true for existent column.');
|
||||
$this->assertIdentical(FALSE, db_field_exists('test', 'nosuchcolumn'), 'Returns false for nonexistent column.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the db_index_exists() function.
|
||||
*/
|
||||
function testDBIndexExists() {
|
||||
$this->assertIdentical(TRUE, db_index_exists('test', 'ages'), 'Returns true for existent index.');
|
||||
$this->assertIdentical(FALSE, db_index_exists('test', 'nosuchindex'), 'Returns false for nonexistent index.');
|
||||
}
|
||||
}
|
||||
660
core/modules/system/src/Tests/Database/SchemaTest.php
Normal file
660
core/modules/system/src/Tests/Database/SchemaTest.php
Normal file
|
|
@ -0,0 +1,660 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SchemaTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
|
||||
use Drupal\Core\Database\SchemaObjectExistsException;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* Tests table creation and modification via the schema API.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SchemaTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* A global counter for table and field creation.
|
||||
*/
|
||||
protected $counter;
|
||||
|
||||
/**
|
||||
* Tests database interactions.
|
||||
*/
|
||||
function testSchema() {
|
||||
// Try creating a table.
|
||||
$table_specification = array(
|
||||
'description' => 'Schema table description may contain "quotes" and could be long—very long indeed.',
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'type' => 'int',
|
||||
'default' => NULL,
|
||||
),
|
||||
'test_field' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Schema table description may contain "quotes" and could be long—very long indeed. There could be "multiple quoted regions".',
|
||||
),
|
||||
'test_field_string' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 20,
|
||||
'not null' => TRUE,
|
||||
'default' => "'\"funky default'\"",
|
||||
'description' => 'Schema column description for string.',
|
||||
),
|
||||
'test_field_string_ascii' => array(
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 255,
|
||||
'description' => 'Schema column description for ASCII string.',
|
||||
),
|
||||
),
|
||||
);
|
||||
db_create_table('test_table', $table_specification);
|
||||
|
||||
// Assert that the table exists.
|
||||
$this->assertTrue(db_table_exists('test_table'), 'The table exists.');
|
||||
|
||||
// Assert that the table comment has been set.
|
||||
$this->checkSchemaComment($table_specification['description'], 'test_table');
|
||||
|
||||
// Assert that the column comment has been set.
|
||||
$this->checkSchemaComment($table_specification['fields']['test_field']['description'], 'test_table', 'test_field');
|
||||
|
||||
if (Database::getConnection()->databaseType() == 'mysql') {
|
||||
// Make sure that varchar fields have the correct collation.
|
||||
$columns = db_query('SHOW FULL COLUMNS FROM {test_table}');
|
||||
foreach ($columns as $column) {
|
||||
if ($column->Field == 'test_field_string') {
|
||||
$string_check = ($column->Collation == 'utf8mb4_general_ci');
|
||||
}
|
||||
if ($column->Field == 'test_field_string_ascii') {
|
||||
$string_ascii_check = ($column->Collation == 'ascii_general_ci');
|
||||
}
|
||||
}
|
||||
$this->assertTrue(!empty($string_check), 'string field has the right collation.');
|
||||
$this->assertTrue(!empty($string_ascii_check), 'ASCII string field has the right collation.');
|
||||
}
|
||||
|
||||
// An insert without a value for the column 'test_table' should fail.
|
||||
$this->assertFalse($this->tryInsert(), 'Insert without a default failed.');
|
||||
|
||||
// Add a default value to the column.
|
||||
db_field_set_default('test_table', 'test_field', 0);
|
||||
// The insert should now succeed.
|
||||
$this->assertTrue($this->tryInsert(), 'Insert with a default succeeded.');
|
||||
|
||||
// Remove the default.
|
||||
db_field_set_no_default('test_table', 'test_field');
|
||||
// The insert should fail again.
|
||||
$this->assertFalse($this->tryInsert(), 'Insert without a default failed.');
|
||||
|
||||
// Test for fake index and test for the boolean result of indexExists().
|
||||
$index_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
|
||||
$this->assertIdentical($index_exists, FALSE, 'Fake index does not exists');
|
||||
// Add index.
|
||||
db_add_index('test_table', 'test_field', array('test_field'));
|
||||
// Test for created index and test for the boolean result of indexExists().
|
||||
$index_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
|
||||
$this->assertIdentical($index_exists, TRUE, 'Index created.');
|
||||
|
||||
// Rename the table.
|
||||
db_rename_table('test_table', 'test_table2');
|
||||
|
||||
// Index should be renamed.
|
||||
$index_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
|
||||
$this->assertTrue($index_exists, 'Index was renamed.');
|
||||
|
||||
// We need the default so that we can insert after the rename.
|
||||
db_field_set_default('test_table2', 'test_field', 0);
|
||||
$this->assertFalse($this->tryInsert(), 'Insert into the old table failed.');
|
||||
$this->assertTrue($this->tryInsert('test_table2'), 'Insert into the new table succeeded.');
|
||||
|
||||
// We should have successfully inserted exactly two rows.
|
||||
$count = db_query('SELECT COUNT(*) FROM {test_table2}')->fetchField();
|
||||
$this->assertEqual($count, 2, 'Two fields were successfully inserted.');
|
||||
|
||||
// Try to drop the table.
|
||||
db_drop_table('test_table2');
|
||||
$this->assertFalse(db_table_exists('test_table2'), 'The dropped table does not exist.');
|
||||
|
||||
// Recreate the table.
|
||||
db_create_table('test_table', $table_specification);
|
||||
db_field_set_default('test_table', 'test_field', 0);
|
||||
db_add_field('test_table', 'test_serial', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => 'Added column description.'));
|
||||
|
||||
// Assert that the column comment has been set.
|
||||
$this->checkSchemaComment('Added column description.', 'test_table', 'test_serial');
|
||||
|
||||
// Change the new field to a serial column.
|
||||
db_change_field('test_table', 'test_serial', 'test_serial', array('type' => 'serial', 'not null' => TRUE, 'description' => 'Changed column description.'), array('primary key' => array('test_serial')));
|
||||
|
||||
// Assert that the column comment has been set.
|
||||
$this->checkSchemaComment('Changed column description.', 'test_table', 'test_serial');
|
||||
|
||||
$this->assertTrue($this->tryInsert(), 'Insert with a serial succeeded.');
|
||||
$max1 = db_query('SELECT MAX(test_serial) FROM {test_table}')->fetchField();
|
||||
$this->assertTrue($this->tryInsert(), 'Insert with a serial succeeded.');
|
||||
$max2 = db_query('SELECT MAX(test_serial) FROM {test_table}')->fetchField();
|
||||
$this->assertTrue($max2 > $max1, 'The serial is monotone.');
|
||||
|
||||
$count = db_query('SELECT COUNT(*) FROM {test_table}')->fetchField();
|
||||
$this->assertEqual($count, 2, 'There were two rows.');
|
||||
|
||||
// Test renaming of keys and constraints.
|
||||
db_drop_table('test_table');
|
||||
$table_specification = array(
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'test_field' => array(
|
||||
'type' => 'int',
|
||||
'default' => 0,
|
||||
),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
'unique keys' => array(
|
||||
'test_field' => array('test_field'),
|
||||
),
|
||||
);
|
||||
db_create_table('test_table', $table_specification);
|
||||
|
||||
// Tests for indexes are Database specific.
|
||||
$db_type = Database::getConnection()->databaseType();
|
||||
|
||||
// Test for existing primary and unique keys.
|
||||
switch ($db_type) {
|
||||
case 'pgsql':
|
||||
$primary_key_exists = Database::getConnection()->schema()->constraintExists('test_table', '__pkey');
|
||||
$unique_key_exists = Database::getConnection()->schema()->constraintExists('test_table', 'test_field' . '__key');
|
||||
break;
|
||||
case 'sqlite':
|
||||
// SQLite does not create a standalone index for primary keys.
|
||||
$primary_key_exists = TRUE;
|
||||
$unique_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
|
||||
break;
|
||||
default:
|
||||
$primary_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'PRIMARY');
|
||||
$unique_key_exists = Database::getConnection()->schema()->indexExists('test_table', 'test_field');
|
||||
break;
|
||||
}
|
||||
$this->assertIdentical($primary_key_exists, TRUE, 'Primary key created.');
|
||||
$this->assertIdentical($unique_key_exists, TRUE, 'Unique key created.');
|
||||
|
||||
db_rename_table('test_table', 'test_table2');
|
||||
|
||||
// Test for renamed primary and unique keys.
|
||||
switch ($db_type) {
|
||||
case 'pgsql':
|
||||
$renamed_primary_key_exists = Database::getConnection()->schema()->constraintExists('test_table2', '__pkey');
|
||||
$renamed_unique_key_exists = Database::getConnection()->schema()->constraintExists('test_table2', 'test_field' . '__key');
|
||||
break;
|
||||
case 'sqlite':
|
||||
// SQLite does not create a standalone index for primary keys.
|
||||
$renamed_primary_key_exists = TRUE;
|
||||
$renamed_unique_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
|
||||
break;
|
||||
default:
|
||||
$renamed_primary_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'PRIMARY');
|
||||
$renamed_unique_key_exists = Database::getConnection()->schema()->indexExists('test_table2', 'test_field');
|
||||
break;
|
||||
}
|
||||
$this->assertIdentical($renamed_primary_key_exists, TRUE, 'Primary key was renamed.');
|
||||
$this->assertIdentical($renamed_unique_key_exists, TRUE, 'Unique key was renamed.');
|
||||
|
||||
// For PostgreSQL check in addition that sequence was renamed.
|
||||
if ($db_type == 'pgsql') {
|
||||
// Get information about new table.
|
||||
$info = Database::getConnection()->schema()->queryTableInformation('test_table2');
|
||||
$sequence_name = Database::getConnection()->schema()->prefixNonTable('test_table2', 'id', 'seq');
|
||||
$this->assertEqual($sequence_name, current($info->sequences), 'Sequence was renamed.');
|
||||
}
|
||||
|
||||
// Use database specific data type and ensure that table is created.
|
||||
$table_specification = array(
|
||||
'description' => 'Schema table description.',
|
||||
'fields' => array(
|
||||
'timestamp' => array(
|
||||
'mysql_type' => 'timestamp',
|
||||
'pgsql_type' => 'timestamp',
|
||||
'sqlite_type' => 'datetime',
|
||||
'not null' => FALSE,
|
||||
'default' => NULL,
|
||||
),
|
||||
),
|
||||
);
|
||||
try {
|
||||
db_create_table('test_timestamp', $table_specification);
|
||||
}
|
||||
catch (\Exception $e) {}
|
||||
$this->assertTrue(db_table_exists('test_timestamp'), 'Table with database specific datatype was created.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that indexes on string fields are limited to 191 characters on MySQL.
|
||||
*
|
||||
* @see \Drupal\Core\Database\Driver\mysql\Schema::getNormalizedIndexes()
|
||||
*/
|
||||
function testIndexLength() {
|
||||
if (Database::getConnection()->databaseType() != 'mysql') {
|
||||
return;
|
||||
}
|
||||
$table_specification = array(
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'type' => 'int',
|
||||
'default' => NULL,
|
||||
),
|
||||
'test_field_text' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'test_field_string_long' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'test_field_string_ascii_long' => array(
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 255,
|
||||
),
|
||||
'test_field_string_short' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'test_regular' => array(
|
||||
'test_field_text',
|
||||
'test_field_string_long',
|
||||
'test_field_string_ascii_long',
|
||||
'test_field_string_short',
|
||||
),
|
||||
'test_length' => array(
|
||||
array('test_field_text', 128),
|
||||
array('test_field_string_long', 128),
|
||||
array('test_field_string_ascii_long', 128),
|
||||
array('test_field_string_short', 128),
|
||||
),
|
||||
'test_mixed' => array(
|
||||
array('test_field_text', 200),
|
||||
'test_field_string_long',
|
||||
array('test_field_string_ascii_long', 200),
|
||||
'test_field_string_short',
|
||||
),
|
||||
),
|
||||
);
|
||||
db_create_table('test_table_index_length', $table_specification);
|
||||
|
||||
// Get index information.
|
||||
$results = db_query('SHOW INDEX FROM {test_table_index_length}');
|
||||
$expected_lengths = array(
|
||||
'test_regular' => array(
|
||||
'test_field_text' => 191,
|
||||
'test_field_string_long' => 191,
|
||||
'test_field_string_ascii_long' => NULL,
|
||||
'test_field_string_short' => NULL,
|
||||
),
|
||||
'test_length' => array(
|
||||
'test_field_text' => 128,
|
||||
'test_field_string_long' => 128,
|
||||
'test_field_string_ascii_long' => 128,
|
||||
'test_field_string_short' => NULL,
|
||||
),
|
||||
'test_mixed' => array(
|
||||
'test_field_text' => 191,
|
||||
'test_field_string_long' => 191,
|
||||
'test_field_string_ascii_long' => 200,
|
||||
'test_field_string_short' => NULL,
|
||||
),
|
||||
);
|
||||
|
||||
// Count the number of columns defined in the indexes.
|
||||
$column_count = 0;
|
||||
foreach ($table_specification['indexes'] as $index) {
|
||||
foreach ($index as $field) {
|
||||
$column_count++;
|
||||
}
|
||||
}
|
||||
$test_count = 0;
|
||||
foreach ($results as $result) {
|
||||
$this->assertEqual($result->Sub_part, $expected_lengths[$result->Key_name][$result->Column_name], 'Index length matches expected value.');
|
||||
$test_count++;
|
||||
}
|
||||
$this->assertEqual($test_count, $column_count, 'Number of tests matches expected value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests inserting data into an existing table.
|
||||
*
|
||||
* @param $table
|
||||
* The database table to insert data into.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the insert succeeded, FALSE otherwise.
|
||||
*/
|
||||
function tryInsert($table = 'test_table') {
|
||||
try {
|
||||
db_insert($table)
|
||||
->fields(array('id' => mt_rand(10, 20)))
|
||||
->execute();
|
||||
return TRUE;
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a table or column comment matches a given description.
|
||||
*
|
||||
* @param $description
|
||||
* The asserted description.
|
||||
* @param $table
|
||||
* The table to test.
|
||||
* @param $column
|
||||
* Optional column to test.
|
||||
*/
|
||||
function checkSchemaComment($description, $table, $column = NULL) {
|
||||
if (method_exists(Database::getConnection()->schema(), 'getComment')) {
|
||||
$comment = Database::getConnection()->schema()->getComment($table, $column);
|
||||
// The schema comment truncation for mysql is different.
|
||||
if (Database::getConnection()->databaseType() == 'mysql') {
|
||||
$max_length = $column ? 255 : 60;
|
||||
$description = Unicode::truncate($description, $max_length, TRUE, TRUE);
|
||||
}
|
||||
$this->assertEqual($comment, $description, 'The comment matches the schema description.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating unsigned columns and data integrity thereof.
|
||||
*/
|
||||
function testUnsignedColumns() {
|
||||
// First create the table with just a serial column.
|
||||
$table_name = 'unsigned_table';
|
||||
$table_spec = array(
|
||||
'fields' => array('serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE)),
|
||||
'primary key' => array('serial_column'),
|
||||
);
|
||||
db_create_table($table_name, $table_spec);
|
||||
|
||||
// Now set up columns for the other types.
|
||||
$types = array('int', 'float', 'numeric');
|
||||
foreach ($types as $type) {
|
||||
$column_spec = array('type' => $type, 'unsigned'=> TRUE);
|
||||
if ($type == 'numeric') {
|
||||
$column_spec += array('precision' => 10, 'scale' => 0);
|
||||
}
|
||||
$column_name = $type . '_column';
|
||||
$table_spec['fields'][$column_name] = $column_spec;
|
||||
db_add_field($table_name, $column_name, $column_spec);
|
||||
}
|
||||
|
||||
// Finally, check each column and try to insert invalid values into them.
|
||||
foreach ($table_spec['fields'] as $column_name => $column_spec) {
|
||||
$this->assertTrue(db_field_exists($table_name, $column_name), format_string('Unsigned @type column was created.', array('@type' => $column_spec['type'])));
|
||||
$this->assertFalse($this->tryUnsignedInsert($table_name, $column_name), format_string('Unsigned @type column rejected a negative value.', array('@type' => $column_spec['type'])));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to insert a negative value into columns defined as unsigned.
|
||||
*
|
||||
* @param $table_name
|
||||
* The table to insert.
|
||||
* @param $column_name
|
||||
* The column to insert.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the insert succeeded, FALSE otherwise.
|
||||
*/
|
||||
function tryUnsignedInsert($table_name, $column_name) {
|
||||
try {
|
||||
db_insert($table_name)
|
||||
->fields(array($column_name => -1))
|
||||
->execute();
|
||||
return TRUE;
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding columns to an existing table.
|
||||
*/
|
||||
function testSchemaAddField() {
|
||||
// Test varchar types.
|
||||
foreach (array(1, 32, 128, 256, 512) as $length) {
|
||||
$base_field_spec = array(
|
||||
'type' => 'varchar',
|
||||
'length' => $length,
|
||||
);
|
||||
$variations = array(
|
||||
array('not null' => FALSE),
|
||||
array('not null' => FALSE, 'default' => '7'),
|
||||
array('not null' => FALSE, 'default' => substr('"thing"', 0, $length)),
|
||||
array('not null' => FALSE, 'default' => substr("\"'hing", 0, $length)),
|
||||
array('not null' => TRUE, 'initial' => 'd'),
|
||||
array('not null' => FALSE, 'default' => NULL),
|
||||
array('not null' => TRUE, 'initial' => 'd', 'default' => '7'),
|
||||
);
|
||||
|
||||
foreach ($variations as $variation) {
|
||||
$field_spec = $variation + $base_field_spec;
|
||||
$this->assertFieldAdditionRemoval($field_spec);
|
||||
}
|
||||
}
|
||||
|
||||
// Test int and float types.
|
||||
foreach (array('int', 'float') as $type) {
|
||||
foreach (array('tiny', 'small', 'medium', 'normal', 'big') as $size) {
|
||||
$base_field_spec = array(
|
||||
'type' => $type,
|
||||
'size' => $size,
|
||||
);
|
||||
$variations = array(
|
||||
array('not null' => FALSE),
|
||||
array('not null' => FALSE, 'default' => 7),
|
||||
array('not null' => TRUE, 'initial' => 1),
|
||||
array('not null' => TRUE, 'initial' => 1, 'default' => 7),
|
||||
);
|
||||
|
||||
foreach ($variations as $variation) {
|
||||
$field_spec = $variation + $base_field_spec;
|
||||
$this->assertFieldAdditionRemoval($field_spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test numeric types.
|
||||
foreach (array(1, 5, 10, 40, 65) as $precision) {
|
||||
foreach (array(0, 2, 10, 30) as $scale) {
|
||||
// Skip combinations where precision is smaller than scale.
|
||||
if ($precision <= $scale) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$base_field_spec = array(
|
||||
'type' => 'numeric',
|
||||
'scale' => $scale,
|
||||
'precision' => $precision,
|
||||
);
|
||||
$variations = array(
|
||||
array('not null' => FALSE),
|
||||
array('not null' => FALSE, 'default' => 7),
|
||||
array('not null' => TRUE, 'initial' => 1),
|
||||
array('not null' => TRUE, 'initial' => 1, 'default' => 7),
|
||||
);
|
||||
|
||||
foreach ($variations as $variation) {
|
||||
$field_spec = $variation + $base_field_spec;
|
||||
$this->assertFieldAdditionRemoval($field_spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a given field can be added and removed from a table.
|
||||
*
|
||||
* The addition test covers both defining a field of a given specification
|
||||
* when initially creating at table and extending an existing table.
|
||||
*
|
||||
* @param $field_spec
|
||||
* The schema specification of the field.
|
||||
*/
|
||||
protected function assertFieldAdditionRemoval($field_spec) {
|
||||
// Try creating the field on a new table.
|
||||
$table_name = 'test_table_' . ($this->counter++);
|
||||
$table_spec = array(
|
||||
'fields' => array(
|
||||
'serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
|
||||
'test_field' => $field_spec,
|
||||
),
|
||||
'primary key' => array('serial_column'),
|
||||
);
|
||||
db_create_table($table_name, $table_spec);
|
||||
$this->pass(format_string('Table %table created.', array('%table' => $table_name)));
|
||||
|
||||
// Check the characteristics of the field.
|
||||
$this->assertFieldCharacteristics($table_name, 'test_field', $field_spec);
|
||||
|
||||
// Clean-up.
|
||||
db_drop_table($table_name);
|
||||
|
||||
// Try adding a field to an existing table.
|
||||
$table_name = 'test_table_' . ($this->counter++);
|
||||
$table_spec = array(
|
||||
'fields' => array(
|
||||
'serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
|
||||
),
|
||||
'primary key' => array('serial_column'),
|
||||
);
|
||||
db_create_table($table_name, $table_spec);
|
||||
$this->pass(format_string('Table %table created.', array('%table' => $table_name)));
|
||||
|
||||
// Insert some rows to the table to test the handling of initial values.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
db_insert($table_name)
|
||||
->useDefaults(array('serial_column'))
|
||||
->execute();
|
||||
}
|
||||
|
||||
db_add_field($table_name, 'test_field', $field_spec);
|
||||
$this->pass(format_string('Column %column created.', array('%column' => 'test_field')));
|
||||
|
||||
// Check the characteristics of the field.
|
||||
$this->assertFieldCharacteristics($table_name, 'test_field', $field_spec);
|
||||
|
||||
// Clean-up.
|
||||
db_drop_field($table_name, 'test_field');
|
||||
|
||||
// Add back the field and then try to delete a field which is also a primary
|
||||
// key.
|
||||
db_add_field($table_name, 'test_field', $field_spec);
|
||||
db_drop_field($table_name, 'serial_column');
|
||||
db_drop_table($table_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a newly added field has the correct characteristics.
|
||||
*/
|
||||
protected function assertFieldCharacteristics($table_name, $field_name, $field_spec) {
|
||||
// Check that the initial value has been registered.
|
||||
if (isset($field_spec['initial'])) {
|
||||
// There should be no row with a value different then $field_spec['initial'].
|
||||
$count = db_select($table_name)
|
||||
->fields($table_name, array('serial_column'))
|
||||
->condition($field_name, $field_spec['initial'], '<>')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($count, 0, 'Initial values filled out.');
|
||||
}
|
||||
|
||||
// Check that the default value has been registered.
|
||||
if (isset($field_spec['default'])) {
|
||||
// Try inserting a row, and check the resulting value of the new column.
|
||||
$id = db_insert($table_name)
|
||||
->useDefaults(array('serial_column'))
|
||||
->execute();
|
||||
$field_value = db_select($table_name)
|
||||
->fields($table_name, array($field_name))
|
||||
->condition('serial_column', $id)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($field_value, $field_spec['default'], 'Default value registered.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing columns between numeric types.
|
||||
*/
|
||||
function testSchemaChangeField() {
|
||||
$field_specs = array(
|
||||
array('type' => 'int', 'size' => 'normal','not null' => FALSE),
|
||||
array('type' => 'int', 'size' => 'normal', 'not null' => TRUE, 'initial' => 1, 'default' => 17),
|
||||
array('type' => 'float', 'size' => 'normal', 'not null' => FALSE),
|
||||
array('type' => 'float', 'size' => 'normal', 'not null' => TRUE, 'initial' => 1, 'default' => 7.3),
|
||||
array('type' => 'numeric', 'scale' => 2, 'precision' => 10, 'not null' => FALSE),
|
||||
array('type' => 'numeric', 'scale' => 2, 'precision' => 10, 'not null' => TRUE, 'initial' => 1, 'default' => 7),
|
||||
);
|
||||
|
||||
foreach ($field_specs as $i => $old_spec) {
|
||||
foreach ($field_specs as $j => $new_spec) {
|
||||
if ($i === $j) {
|
||||
// Do not change a field into itself.
|
||||
continue;
|
||||
}
|
||||
$this->assertFieldChange($old_spec, $new_spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a field can be changed from one spec to another.
|
||||
*
|
||||
* @param $old_spec
|
||||
* The beginning field specification.
|
||||
* @param $new_spec
|
||||
* The ending field specification.
|
||||
*/
|
||||
protected function assertFieldChange($old_spec, $new_spec) {
|
||||
$table_name = 'test_table_' . ($this->counter++);
|
||||
$table_spec = array(
|
||||
'fields' => array(
|
||||
'serial_column' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
|
||||
'test_field' => $old_spec,
|
||||
),
|
||||
'primary key' => array('serial_column'),
|
||||
);
|
||||
db_create_table($table_name, $table_spec);
|
||||
$this->pass(format_string('Table %table created.', array('%table' => $table_name)));
|
||||
|
||||
// Check the characteristics of the field.
|
||||
$this->assertFieldCharacteristics($table_name, 'test_field', $old_spec);
|
||||
|
||||
// Remove inserted rows.
|
||||
db_truncate($table_name)->execute();
|
||||
|
||||
// Change the field.
|
||||
db_change_field($table_name, 'test_field', 'test_field', $new_spec);
|
||||
|
||||
// Check the field was changed.
|
||||
$this->assertFieldCharacteristics($table_name, 'test_field', $new_spec);
|
||||
|
||||
// Clean-up.
|
||||
db_drop_table($table_name);
|
||||
}
|
||||
}
|
||||
41
core/modules/system/src/Tests/Database/SelectCloneTest.php
Normal file
41
core/modules/system/src/Tests/Database/SelectCloneTest.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SelectCloneTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests cloning Select queries.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectCloneTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Test that subqueries as value within conditions are cloned properly.
|
||||
*/
|
||||
function testSelectConditionSubQueryCloning() {
|
||||
$subquery = db_select('test', 't');
|
||||
$subquery->addField('t', 'id', 'id');
|
||||
$subquery->condition('age', 28, '<');
|
||||
|
||||
$query = db_select('test', 't');
|
||||
$query->addField('t', 'name', 'name');
|
||||
$query->condition('id', $subquery, 'IN');
|
||||
|
||||
$clone = clone $query;
|
||||
// Cloned query should not be altered by the following modification
|
||||
// happening on original query.
|
||||
$subquery->condition('age', 25, '>');
|
||||
|
||||
$clone_result = $clone->countQuery()->execute()->fetchField();
|
||||
$query_result = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
// Make sure the cloned query has not been modified
|
||||
$this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows');
|
||||
$this->assertEqual(2, $query_result, 'The query returns the expected number of rows');
|
||||
}
|
||||
}
|
||||
382
core/modules/system/src/Tests/Database/SelectComplexTest.php
Normal file
382
core/modules/system/src/Tests/Database/SelectComplexTest.php
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SelectComplexTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\RowCountException;
|
||||
|
||||
/**
|
||||
* Tests the Select query builder with more complex queries.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectComplexTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'user', 'node_access_test', 'field');
|
||||
|
||||
/**
|
||||
* Tests simple JOIN statements.
|
||||
*/
|
||||
function testDefaultJoin() {
|
||||
$query = db_select('test_task', 't');
|
||||
$people_alias = $query->join('test', 'p', 't.pid = p.id');
|
||||
$name_field = $query->addField($people_alias, 'name', 'name');
|
||||
$query->addField('t', 'task', 'task');
|
||||
$priority_field = $query->addField('t', 'priority', 'priority');
|
||||
|
||||
$query->orderBy($priority_field);
|
||||
$result = $query->execute();
|
||||
|
||||
$num_records = 0;
|
||||
$last_priority = 0;
|
||||
foreach ($result as $record) {
|
||||
$num_records++;
|
||||
$this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.');
|
||||
$this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.');
|
||||
$last_priority = $record->$priority_field;
|
||||
}
|
||||
|
||||
$this->assertEqual($num_records, 7, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests LEFT OUTER joins.
|
||||
*/
|
||||
function testLeftOuterJoin() {
|
||||
$query = db_select('test', 'p');
|
||||
$people_alias = $query->leftJoin('test_task', 't', 't.pid = p.id');
|
||||
$name_field = $query->addField('p', 'name', 'name');
|
||||
$query->addField($people_alias, 'task', 'task');
|
||||
$query->addField($people_alias, 'priority', 'priority');
|
||||
|
||||
$query->orderBy($name_field);
|
||||
$result = $query->execute();
|
||||
|
||||
$num_records = 0;
|
||||
$last_name = 0;
|
||||
|
||||
foreach ($result as $record) {
|
||||
$num_records++;
|
||||
$this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.');
|
||||
}
|
||||
|
||||
$this->assertEqual($num_records, 8, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests GROUP BY clauses.
|
||||
*/
|
||||
function testGroupBy() {
|
||||
$query = db_select('test_task', 't');
|
||||
$count_field = $query->addExpression('COUNT(task)', 'num');
|
||||
$task_field = $query->addField('t', 'task');
|
||||
$query->orderBy($count_field);
|
||||
$query->groupBy($task_field);
|
||||
$result = $query->execute();
|
||||
|
||||
$num_records = 0;
|
||||
$last_count = 0;
|
||||
$records = array();
|
||||
foreach ($result as $record) {
|
||||
$num_records++;
|
||||
$this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
|
||||
$last_count = $record->$count_field;
|
||||
$records[$record->$task_field] = $record->$count_field;
|
||||
}
|
||||
|
||||
$correct_results = array(
|
||||
'eat' => 1,
|
||||
'sleep' => 2,
|
||||
'code' => 1,
|
||||
'found new band' => 1,
|
||||
'perform at superbowl' => 1,
|
||||
);
|
||||
|
||||
foreach ($correct_results as $task => $count) {
|
||||
$this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
|
||||
}
|
||||
|
||||
$this->assertEqual($num_records, 6, 'Returned the correct number of total rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests GROUP BY and HAVING clauses together.
|
||||
*/
|
||||
function testGroupByAndHaving() {
|
||||
$query = db_select('test_task', 't');
|
||||
$count_field = $query->addExpression('COUNT(task)', 'num');
|
||||
$task_field = $query->addField('t', 'task');
|
||||
$query->orderBy($count_field);
|
||||
$query->groupBy($task_field);
|
||||
$query->having('COUNT(task) >= 2');
|
||||
$result = $query->execute();
|
||||
|
||||
$num_records = 0;
|
||||
$last_count = 0;
|
||||
$records = array();
|
||||
foreach ($result as $record) {
|
||||
$num_records++;
|
||||
$this->assertTrue($record->$count_field >= 2, 'Record has the minimum count.');
|
||||
$this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
|
||||
$last_count = $record->$count_field;
|
||||
$records[$record->$task_field] = $record->$count_field;
|
||||
}
|
||||
|
||||
$correct_results = array(
|
||||
'sleep' => 2,
|
||||
);
|
||||
|
||||
foreach ($correct_results as $task => $count) {
|
||||
$this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
|
||||
}
|
||||
|
||||
$this->assertEqual($num_records, 1, 'Returned the correct number of total rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests range queries.
|
||||
*
|
||||
* The SQL clause varies with the database.
|
||||
*/
|
||||
function testRange() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
$query->range(0, 2);
|
||||
$query_result = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($query_result, 2, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests distinct queries.
|
||||
*/
|
||||
function testDistinct() {
|
||||
$query = db_select('test_task');
|
||||
$query->addField('test_task', 'task');
|
||||
$query->distinct();
|
||||
$query_result = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($query_result, 6, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can generate a count query from a built query.
|
||||
*/
|
||||
function testCountQuery() {
|
||||
$query = db_select('test');
|
||||
$name_field = $query->addField('test', 'name');
|
||||
$age_field = $query->addField('test', 'age', 'age');
|
||||
$query->orderBy('name');
|
||||
|
||||
$count = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($count, 4, 'Counted the correct number of records.');
|
||||
|
||||
// Now make sure we didn't break the original query! We should still have
|
||||
// all of the fields we asked for.
|
||||
$record = $query->execute()->fetch();
|
||||
$this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
|
||||
$this->assertEqual($record->$age_field, 27, 'Correct data retrieved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests having queries.
|
||||
*/
|
||||
function testHavingCountQuery() {
|
||||
$query = db_select('test')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->groupBy('age')
|
||||
->having('age + 1 > 0');
|
||||
$query->addField('test', 'age');
|
||||
$query->addExpression('age + 1');
|
||||
$count = count($query->execute()->fetchCol());
|
||||
$this->assertEqual($count, 4, 'Counted the correct number of records.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that countQuery removes 'all_fields' statements and ordering clauses.
|
||||
*/
|
||||
function testCountQueryRemovals() {
|
||||
$query = db_select('test');
|
||||
$query->fields('test');
|
||||
$query->orderBy('name');
|
||||
$count = $query->countQuery();
|
||||
|
||||
// Check that the 'all_fields' statement is handled properly.
|
||||
$tables = $query->getTables();
|
||||
$this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.');
|
||||
$tables = $count->getTables();
|
||||
$this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.');
|
||||
|
||||
// Check that the ordering clause is handled properly.
|
||||
$orderby = $query->getOrderBy();
|
||||
// The orderby string is different for PostgreSQL.
|
||||
// @see Drupal\Core\Database\Driver\pgsql\Select::orderBy()
|
||||
$db_type = Database::getConnection()->databaseType();
|
||||
$this->assertEqual($orderby['name'], ($db_type == 'pgsql' ? 'ASC NULLS FIRST' : 'ASC'), 'Query correctly sets ordering clause.');
|
||||
$orderby = $count->getOrderBy();
|
||||
$this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering clause.');
|
||||
|
||||
// Make sure that the count query works.
|
||||
$count = $count->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($count, 4, 'Counted the correct number of records.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that countQuery properly removes fields and expressions.
|
||||
*/
|
||||
function testCountQueryFieldRemovals() {
|
||||
// countQuery should remove all fields and expressions, so this can be
|
||||
// tested by adding a non-existent field and expression: if it ends
|
||||
// up in the query, an error will be thrown. If not, it will return the
|
||||
// number of records, which in this case happens to be 4 (there are four
|
||||
// records in the {test} table).
|
||||
$query = db_select('test');
|
||||
$query->fields('test', array('fail'));
|
||||
$this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed fields');
|
||||
|
||||
$query = db_select('test');
|
||||
$query->addExpression('fail');
|
||||
$this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can generate a count query from a query with distinct.
|
||||
*/
|
||||
function testCountQueryDistinct() {
|
||||
$query = db_select('test_task');
|
||||
$query->addField('test_task', 'task');
|
||||
$query->distinct();
|
||||
|
||||
$count = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($count, 6, 'Counted the correct number of records.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can generate a count query from a query with GROUP BY.
|
||||
*/
|
||||
function testCountQueryGroupBy() {
|
||||
$query = db_select('test_task');
|
||||
$query->addField('test_task', 'pid');
|
||||
$query->groupBy('pid');
|
||||
|
||||
$count = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($count, 3, 'Counted the correct number of records.');
|
||||
|
||||
// Use a column alias as, without one, the query can succeed for the wrong
|
||||
// reason.
|
||||
$query = db_select('test_task');
|
||||
$query->addField('test_task', 'pid', 'pid_alias');
|
||||
$query->addExpression('COUNT(test_task.task)', 'count');
|
||||
$query->groupBy('pid_alias');
|
||||
$query->orderBy('pid_alias', 'asc');
|
||||
|
||||
$count = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($count, 3, 'Counted the correct number of records.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can properly nest conditional clauses.
|
||||
*/
|
||||
function testNestedConditions() {
|
||||
// This query should translate to:
|
||||
// "SELECT job FROM {test} WHERE name = 'Paul' AND (age = 26 OR age = 27)"
|
||||
// That should find only one record. Yes it's a non-optimal way of writing
|
||||
// that query but that's not the point!
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'job');
|
||||
$query->condition('name', 'Paul');
|
||||
$query->condition(db_or()->condition('age', 26)->condition('age', 27));
|
||||
|
||||
$job = $query->execute()->fetchField();
|
||||
$this->assertEqual($job, 'Songwriter', 'Correct data retrieved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms we can join on a single table twice with a dynamic alias.
|
||||
*/
|
||||
function testJoinTwice() {
|
||||
$query = db_select('test')->fields('test');
|
||||
$alias = $query->join('test', 'test', 'test.job = %alias.job');
|
||||
$query->addField($alias, 'name', 'othername');
|
||||
$query->addField($alias, 'job', 'otherjob');
|
||||
$query->where("$alias.name <> test.name");
|
||||
$crowded_job = $query->execute()->fetch();
|
||||
$this->assertEqual($crowded_job->job, $crowded_job->otherjob, 'Correctly joined same table twice.');
|
||||
$this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can join on a query.
|
||||
*/
|
||||
function testJoinSubquery() {
|
||||
$this->installSchema('system', 'sequences');
|
||||
|
||||
$account = entity_create('user', array(
|
||||
'name' => $this->randomMachineName(),
|
||||
'mail' => $this->randomMachineName() . '@example.com',
|
||||
));
|
||||
|
||||
$query = db_select('test_task', 'tt', array('target' => 'replica'));
|
||||
$query->addExpression('tt.pid + 1', 'abc');
|
||||
$query->condition('priority', 1, '>');
|
||||
$query->condition('priority', 100, '<');
|
||||
|
||||
$subquery = db_select('test', 'tp');
|
||||
$subquery->join('test_one_blob', 'tpb', 'tp.id = tpb.id');
|
||||
$subquery->join('node', 'n', 'tp.id = n.nid');
|
||||
$subquery->addTag('node_access');
|
||||
$subquery->addMetaData('account', $account);
|
||||
$subquery->addField('tp', 'id');
|
||||
$subquery->condition('age', 5, '>');
|
||||
$subquery->condition('age', 500, '<');
|
||||
|
||||
$query->leftJoin($subquery, 'sq', 'tt.pid = sq.id');
|
||||
$query->join('test_one_blob', 'tb3', 'tt.pid = tb3.id');
|
||||
|
||||
// Construct the query string.
|
||||
// This is the same sequence that SelectQuery::execute() goes through.
|
||||
$query->preExecute();
|
||||
$query->getArguments();
|
||||
$str = (string) $query;
|
||||
|
||||
// Verify that the string only has one copy of condition placeholder 0.
|
||||
$pos = strpos($str, 'db_condition_placeholder_0', 0);
|
||||
$pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1);
|
||||
$this->assertFalse($pos2, 'Condition placeholder is not repeated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that rowCount() throws exception on SELECT query.
|
||||
*/
|
||||
function testSelectWithRowCount() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$result = $query->execute();
|
||||
try {
|
||||
$result->rowCount();
|
||||
$exception = FALSE;
|
||||
}
|
||||
catch (RowCountException $e) {
|
||||
$exception = TRUE;
|
||||
}
|
||||
$this->assertTrue($exception, 'Exception was thrown');
|
||||
}
|
||||
|
||||
}
|
||||
89
core/modules/system/src/Tests/Database/SelectOrderedTest.php
Normal file
89
core/modules/system/src/Tests/Database/SelectOrderedTest.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SelectOrderedTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the Select query builder.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectOrderedTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests basic ORDER BY.
|
||||
*/
|
||||
function testSimpleSelectOrdered() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$age_field = $query->addField('test', 'age', 'age');
|
||||
$query->orderBy($age_field);
|
||||
$result = $query->execute();
|
||||
|
||||
$num_records = 0;
|
||||
$last_age = 0;
|
||||
foreach ($result as $record) {
|
||||
$num_records++;
|
||||
$this->assertTrue($record->age >= $last_age, 'Results returned in correct order.');
|
||||
$last_age = $record->age;
|
||||
}
|
||||
|
||||
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multiple ORDER BY.
|
||||
*/
|
||||
function testSimpleSelectMultiOrdered() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$age_field = $query->addField('test', 'age', 'age');
|
||||
$job_field = $query->addField('test', 'job');
|
||||
$query->orderBy($job_field);
|
||||
$query->orderBy($age_field);
|
||||
$result = $query->execute();
|
||||
|
||||
$num_records = 0;
|
||||
$expected = array(
|
||||
array('Ringo', 28, 'Drummer'),
|
||||
array('John', 25, 'Singer'),
|
||||
array('George', 27, 'Singer'),
|
||||
array('Paul', 26, 'Songwriter'),
|
||||
);
|
||||
$results = $result->fetchAll(\PDO::FETCH_NUM);
|
||||
foreach ($expected as $k => $record) {
|
||||
$num_records++;
|
||||
foreach ($record as $kk => $col) {
|
||||
if ($expected[$k][$kk] != $results[$k][$kk]) {
|
||||
$this->assertTrue(FALSE, 'Results returned in correct order.');
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ORDER BY descending.
|
||||
*/
|
||||
function testSimpleSelectOrderedDesc() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$age_field = $query->addField('test', 'age', 'age');
|
||||
$query->orderBy($age_field, 'DESC');
|
||||
$result = $query->execute();
|
||||
|
||||
$num_records = 0;
|
||||
$last_age = 100000000;
|
||||
foreach ($result as $record) {
|
||||
$num_records++;
|
||||
$this->assertTrue($record->age <= $last_age, 'Results returned in correct order.');
|
||||
$last_age = $record->age;
|
||||
}
|
||||
|
||||
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SelectPagerDefaultTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests the pager query select extender.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectPagerDefaultTest extends DatabaseWebTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that a pager query returns the correct results.
|
||||
*
|
||||
* Note that we have to make an HTTP request to a test page handler
|
||||
* because the pager depends on GET parameters.
|
||||
*/
|
||||
function testEvenPagerQuery() {
|
||||
// To keep the test from being too brittle, we determine up front
|
||||
// what the page count should be dynamically, and pass the control
|
||||
// information forward to the actual query on the other side of the
|
||||
// HTTP request.
|
||||
$limit = 2;
|
||||
$count = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
|
||||
|
||||
$correct_number = $limit;
|
||||
$num_pages = floor($count / $limit);
|
||||
|
||||
// If there is no remainder from rounding, subtract 1 since we index from 0.
|
||||
if (!($num_pages * $limit < $count)) {
|
||||
$num_pages--;
|
||||
}
|
||||
|
||||
for ($page = 0; $page <= $num_pages; ++$page) {
|
||||
$this->drupalGet('database_test/pager_query_even/' . $limit, array('query' => array('page' => $page)));
|
||||
$data = json_decode($this->getRawContent());
|
||||
|
||||
if ($page == $num_pages) {
|
||||
$correct_number = $count - ($limit * $page);
|
||||
}
|
||||
|
||||
$this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that a pager query returns the correct results.
|
||||
*
|
||||
* Note that we have to make an HTTP request to a test page handler
|
||||
* because the pager depends on GET parameters.
|
||||
*/
|
||||
function testOddPagerQuery() {
|
||||
// To keep the test from being too brittle, we determine up front
|
||||
// what the page count should be dynamically, and pass the control
|
||||
// information forward to the actual query on the other side of the
|
||||
// HTTP request.
|
||||
$limit = 2;
|
||||
$count = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
|
||||
|
||||
$correct_number = $limit;
|
||||
$num_pages = floor($count / $limit);
|
||||
|
||||
// If there is no remainder from rounding, subtract 1 since we index from 0.
|
||||
if (!($num_pages * $limit < $count)) {
|
||||
$num_pages--;
|
||||
}
|
||||
|
||||
for ($page = 0; $page <= $num_pages; ++$page) {
|
||||
$this->drupalGet('database_test/pager_query_odd/' . $limit, array('query' => array('page' => $page)));
|
||||
$data = json_decode($this->getRawContent());
|
||||
|
||||
if ($page == $num_pages) {
|
||||
$correct_number = $count - ($limit * $page);
|
||||
}
|
||||
|
||||
$this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that a pager query results with an inner pager query are valid.
|
||||
*
|
||||
* This is a regression test for #467984.
|
||||
*/
|
||||
function testInnerPagerQuery() {
|
||||
$query = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender');
|
||||
$query
|
||||
->fields('t', array('age'))
|
||||
->orderBy('age')
|
||||
->limit(5);
|
||||
|
||||
$outer_query = db_select($query);
|
||||
$outer_query->addField('subquery', 'age');
|
||||
|
||||
$ages = $outer_query
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertEqual($ages, array(25, 26, 27, 28), 'Inner pager query returned the correct ages.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that a paging query results with a having expression are valid.
|
||||
*
|
||||
* This is a regression test for #467984.
|
||||
*/
|
||||
function testHavingPagerQuery() {
|
||||
$query = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender');
|
||||
$query
|
||||
->fields('t', array('name'))
|
||||
->orderBy('name')
|
||||
->groupBy('name')
|
||||
->having('MAX(age) > :count', array(':count' => 26))
|
||||
->limit(5);
|
||||
|
||||
$ages = $query
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertEqual($ages, array('George', 'Ringo'), 'Pager query with having expression returned the correct ages.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that every pager gets a valid, non-overlapping element ID.
|
||||
*/
|
||||
function testElementNumbers() {
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$request->query->replace(array(
|
||||
'page' => '3, 2, 1, 0',
|
||||
));
|
||||
\Drupal::getContainer()->get('request_stack')->push($request);
|
||||
|
||||
$name = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->element(2)
|
||||
->fields('t', array('name'))
|
||||
->orderBy('age')
|
||||
->limit(1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.');
|
||||
|
||||
// Setting an element smaller than the previous one
|
||||
// should not overwrite the pager $maxElement with a smaller value.
|
||||
$name = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->element(1)
|
||||
->fields('t', array('name'))
|
||||
->orderBy('age')
|
||||
->limit(1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.');
|
||||
|
||||
$name = db_select('test', 't')
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->fields('t', array('name'))
|
||||
->orderBy('age')
|
||||
->limit(1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.');
|
||||
|
||||
}
|
||||
}
|
||||
183
core/modules/system/src/Tests/Database/SelectSubqueryTest.php
Normal file
183
core/modules/system/src/Tests/Database/SelectSubqueryTest.php
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SelectSubqueryTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the Select query builder.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectSubqueryTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests that we can use a subquery in a FROM clause.
|
||||
*/
|
||||
function testFromSubquerySelect() {
|
||||
// Create a subquery, which is just a normal query object.
|
||||
$subquery = db_select('test_task', 'tt');
|
||||
$subquery->addField('tt', 'pid', 'pid');
|
||||
$subquery->addField('tt', 'task', 'task');
|
||||
$subquery->condition('priority', 1);
|
||||
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
// Create another query that joins against the virtual table resulting
|
||||
// from the subquery.
|
||||
$select = db_select($subquery, 'tt2');
|
||||
$select->join('test', 't', 't.id=tt2.pid');
|
||||
$select->addField('t', 'name');
|
||||
if ($i) {
|
||||
// Use a different number of conditions here to confuse the subquery
|
||||
// placeholder counter, testing https://www.drupal.org/node/1112854.
|
||||
$select->condition('name', 'John');
|
||||
}
|
||||
$select->condition('task', 'code');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// SELECT t.name
|
||||
// FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt
|
||||
// INNER JOIN test t ON t.id=tt.pid
|
||||
// WHERE tt.task = 'code'
|
||||
$people = $select->execute()->fetchCol();
|
||||
|
||||
$this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can use a subquery in a FROM clause with a LIMIT.
|
||||
*/
|
||||
function testFromSubquerySelectWithLimit() {
|
||||
// Create a subquery, which is just a normal query object.
|
||||
$subquery = db_select('test_task', 'tt');
|
||||
$subquery->addField('tt', 'pid', 'pid');
|
||||
$subquery->addField('tt', 'task', 'task');
|
||||
$subquery->orderBy('priority', 'DESC');
|
||||
$subquery->range(0, 1);
|
||||
|
||||
// Create another query that joins against the virtual table resulting
|
||||
// from the subquery.
|
||||
$select = db_select($subquery, 'tt2');
|
||||
$select->join('test', 't', 't.id=tt2.pid');
|
||||
$select->addField('t', 'name');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// SELECT t.name
|
||||
// FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt ORDER BY priority DESC LIMIT 1 OFFSET 0) tt
|
||||
// INNER JOIN test t ON t.id=tt.pid
|
||||
$people = $select->execute()->fetchCol();
|
||||
|
||||
$this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can use a subquery in a WHERE clause.
|
||||
*/
|
||||
function testConditionSubquerySelect() {
|
||||
// Create a subquery, which is just a normal query object.
|
||||
$subquery = db_select('test_task', 'tt');
|
||||
$subquery->addField('tt', 'pid', 'pid');
|
||||
$subquery->condition('tt.priority', 1);
|
||||
|
||||
// Create another query that joins against the virtual table resulting
|
||||
// from the subquery.
|
||||
$select = db_select('test_task', 'tt2');
|
||||
$select->addField('tt2', 'task');
|
||||
$select->condition('tt2.pid', $subquery, 'IN');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// SELECT tt2.name
|
||||
// FROM test tt2
|
||||
// WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1)
|
||||
$people = $select->execute()->fetchCol();
|
||||
$this->assertEqual(count($people), 5, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can use a subquery in a JOIN clause.
|
||||
*/
|
||||
function testJoinSubquerySelect() {
|
||||
// Create a subquery, which is just a normal query object.
|
||||
$subquery = db_select('test_task', 'tt');
|
||||
$subquery->addField('tt', 'pid', 'pid');
|
||||
$subquery->condition('priority', 1);
|
||||
|
||||
// Create another query that joins against the virtual table resulting
|
||||
// from the subquery.
|
||||
$select = db_select('test', 't');
|
||||
$select->join($subquery, 'tt', 't.id=tt.pid');
|
||||
$select->addField('t', 'name');
|
||||
|
||||
// The resulting query should be equivalent to:
|
||||
// SELECT t.name
|
||||
// FROM test t
|
||||
// INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid
|
||||
$people = $select->execute()->fetchCol();
|
||||
|
||||
$this->assertEqual(count($people), 2, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests EXISTS subquery conditionals on SELECT statements.
|
||||
*
|
||||
* We essentially select all rows from the {test} table that have matching
|
||||
* rows in the {test_people} table based on the shared name column.
|
||||
*/
|
||||
function testExistsSubquerySelect() {
|
||||
// Put George into {test_people}.
|
||||
db_insert('test_people')
|
||||
->fields(array(
|
||||
'name' => 'George',
|
||||
'age' => 27,
|
||||
'job' => 'Singer',
|
||||
))
|
||||
->execute();
|
||||
// Base query to {test}.
|
||||
$query = db_select('test', 't')
|
||||
->fields('t', array('name'));
|
||||
// Subquery to {test_people}.
|
||||
$subquery = db_select('test_people', 'tp')
|
||||
->fields('tp', array('name'))
|
||||
->where('tp.name = t.name');
|
||||
$query->exists($subquery);
|
||||
$result = $query->execute();
|
||||
|
||||
// Ensure that we got the right record.
|
||||
$record = $result->fetch();
|
||||
$this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests NOT EXISTS subquery conditionals on SELECT statements.
|
||||
*
|
||||
* We essentially select all rows from the {test} table that don't have
|
||||
* matching rows in the {test_people} table based on the shared name column.
|
||||
*/
|
||||
function testNotExistsSubquerySelect() {
|
||||
// Put George into {test_people}.
|
||||
db_insert('test_people')
|
||||
->fields(array(
|
||||
'name' => 'George',
|
||||
'age' => 27,
|
||||
'job' => 'Singer',
|
||||
))
|
||||
->execute();
|
||||
|
||||
// Base query to {test}.
|
||||
$query = db_select('test', 't')
|
||||
->fields('t', array('name'));
|
||||
// Subquery to {test_people}.
|
||||
$subquery = db_select('test_people', 'tp')
|
||||
->fields('tp', array('name'))
|
||||
->where('tp.name = t.name');
|
||||
$query->notExists($subquery);
|
||||
|
||||
// Ensure that we got the right number of records.
|
||||
$people = $query->execute()->fetchCol();
|
||||
$this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SelectTableSortDefaultTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the tablesort query extender.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectTableSortDefaultTest extends DatabaseWebTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that a tablesort query returns the correct results.
|
||||
*
|
||||
* Note that we have to make an HTTP request to a test page handler
|
||||
* because the pager depends on GET parameters.
|
||||
*/
|
||||
function testTableSortQuery() {
|
||||
$sorts = array(
|
||||
array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
|
||||
array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
|
||||
array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
|
||||
array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
|
||||
// more elements here
|
||||
|
||||
);
|
||||
|
||||
foreach ($sorts as $sort) {
|
||||
$this->drupalGet('database_test/tablesort/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
|
||||
$data = json_decode($this->getRawContent());
|
||||
|
||||
$first = array_shift($data->tasks);
|
||||
$last = array_pop($data->tasks);
|
||||
|
||||
$this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.');
|
||||
$this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms precedence of tablesorts headers.
|
||||
*
|
||||
* If a tablesort's orderByHeader is called before another orderBy, then its
|
||||
* header happens first.
|
||||
*/
|
||||
function testTableSortQueryFirst() {
|
||||
$sorts = array(
|
||||
array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
|
||||
array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
|
||||
array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
|
||||
array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
|
||||
// more elements here
|
||||
|
||||
);
|
||||
|
||||
foreach ($sorts as $sort) {
|
||||
$this->drupalGet('database_test/tablesort_first/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
|
||||
$data = json_decode($this->getRawContent());
|
||||
|
||||
$first = array_shift($data->tasks);
|
||||
$last = array_pop($data->tasks);
|
||||
|
||||
$this->assertEqual($first->task, $sort['first'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort'])));
|
||||
$this->assertEqual($last->task, $sort['last'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort'])));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that tableselect is rendered without error.
|
||||
*
|
||||
* Specifically that no sort is set in a tableselect, and that header links
|
||||
* are correct.
|
||||
*/
|
||||
function testTableSortDefaultSort() {
|
||||
$this->drupalGet('database_test/tablesort_default_sort');
|
||||
|
||||
// Verify that the table was displayed. Just the header is checked for
|
||||
// because if there were any fatal errors or exceptions in displaying the
|
||||
// sorted table, it would not print the table.
|
||||
$this->assertText(t('Username'));
|
||||
|
||||
// Verify that the header links are built properly.
|
||||
$this->assertLinkByHref('database_test/tablesort_default_sort');
|
||||
$this->assertPattern('/\<a.*title\=\"' . t('sort by Username') . '\".*\>/');
|
||||
}
|
||||
|
||||
}
|
||||
497
core/modules/system/src/Tests/Database/SelectTest.php
Normal file
497
core/modules/system/src/Tests/Database/SelectTest.php
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SelectTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
use Drupal\Core\Database\InvalidQueryException;
|
||||
|
||||
/**
|
||||
* Tests the Select query builder.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SelectTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests rudimentary SELECT statements.
|
||||
*/
|
||||
function testSimpleSelect() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
$num_records = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
$this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests rudimentary SELECT statement with a COMMENT.
|
||||
*/
|
||||
function testSimpleComment() {
|
||||
$query = db_select('test')->comment('Testing query comments');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
$result = $query->execute();
|
||||
|
||||
$records = $result->fetchAll();
|
||||
|
||||
$query = (string) $query;
|
||||
$expected = "/* Testing query comments */";
|
||||
|
||||
$this->assertEqual(count($records), 4, 'Returned the correct number of rows.');
|
||||
$this->assertNotIdentical(FALSE, strpos($query, $expected), 'The flattened query contains the comment string.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests query COMMENT system against vulnerabilities.
|
||||
*/
|
||||
function testVulnerableComment() {
|
||||
$query = db_select('test')->comment('Testing query comments */ SELECT nid FROM {node}; --');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
$result = $query->execute();
|
||||
|
||||
$records = $result->fetchAll();
|
||||
|
||||
$query = (string) $query;
|
||||
$expected = "/* Testing query comments SELECT nid FROM {node}; -- */";
|
||||
|
||||
$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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic conditionals on SELECT statements.
|
||||
*/
|
||||
function testSimpleSelectConditional() {
|
||||
$query = db_select('test');
|
||||
$name_field = $query->addField('test', 'name');
|
||||
$age_field = $query->addField('test', 'age', 'age');
|
||||
$query->condition('age', 27);
|
||||
$result = $query->execute();
|
||||
|
||||
// Check that the aliases are being created the way we want.
|
||||
$this->assertEqual($name_field, 'name', 'Name field alias is correct.');
|
||||
$this->assertEqual($age_field, 'age', 'Age field alias is correct.');
|
||||
|
||||
// Ensure that we got the right record.
|
||||
$record = $result->fetch();
|
||||
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
|
||||
$this->assertEqual($record->$age_field, 27, 'Fetched age is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SELECT statements with expressions.
|
||||
*/
|
||||
function testSimpleSelectExpression() {
|
||||
$query = db_select('test');
|
||||
$name_field = $query->addField('test', 'name');
|
||||
$age_field = $query->addExpression("age*2", 'double_age');
|
||||
$query->condition('age', 27);
|
||||
$result = $query->execute();
|
||||
|
||||
// Check that the aliases are being created the way we want.
|
||||
$this->assertEqual($name_field, 'name', 'Name field alias is correct.');
|
||||
$this->assertEqual($age_field, 'double_age', 'Age field alias is correct.');
|
||||
|
||||
// Ensure that we got the right record.
|
||||
$record = $result->fetch();
|
||||
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
|
||||
$this->assertEqual($record->$age_field, 27*2, 'Fetched age expression is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests SELECT statements with multiple expressions.
|
||||
*/
|
||||
function testSimpleSelectExpressionMultiple() {
|
||||
$query = db_select('test');
|
||||
$name_field = $query->addField('test', 'name');
|
||||
$age_double_field = $query->addExpression("age*2");
|
||||
$age_triple_field = $query->addExpression("age*3");
|
||||
$query->condition('age', 27);
|
||||
$result = $query->execute();
|
||||
|
||||
// Check that the aliases are being created the way we want.
|
||||
$this->assertEqual($age_double_field, 'expression', 'Double age field alias is correct.');
|
||||
$this->assertEqual($age_triple_field, 'expression_2', 'Triple age field alias is correct.');
|
||||
|
||||
// Ensure that we got the right record.
|
||||
$record = $result->fetch();
|
||||
$this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
|
||||
$this->assertEqual($record->$age_double_field, 27*2, 'Fetched double age expression is correct.');
|
||||
$this->assertEqual($record->$age_triple_field, 27*3, 'Fetched triple age expression is correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding multiple fields to a SELECT statement at the same time.
|
||||
*/
|
||||
function testSimpleSelectMultipleFields() {
|
||||
$record = db_select('test')
|
||||
->fields('test', array('id', 'name', 'age', 'job'))
|
||||
->condition('age', 27)
|
||||
->execute()->fetchObject();
|
||||
|
||||
// Check that all fields we asked for are present.
|
||||
$this->assertNotNull($record->id, 'ID field is present.');
|
||||
$this->assertNotNull($record->name, 'Name field is present.');
|
||||
$this->assertNotNull($record->age, 'Age field is present.');
|
||||
$this->assertNotNull($record->job, 'Job field is present.');
|
||||
|
||||
// Ensure that we got the right record.
|
||||
// Check that all fields we asked for are present.
|
||||
$this->assertEqual($record->id, 2, 'ID field has the correct value.');
|
||||
$this->assertEqual($record->name, 'George', 'Name field has the correct value.');
|
||||
$this->assertEqual($record->age, 27, 'Age field has the correct value.');
|
||||
$this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding all fields from a given table to a SELECT statement.
|
||||
*/
|
||||
function testSimpleSelectAllFields() {
|
||||
$record = db_select('test')
|
||||
->fields('test')
|
||||
->condition('age', 27)
|
||||
->execute()->fetchObject();
|
||||
|
||||
// Check that all fields we asked for are present.
|
||||
$this->assertNotNull($record->id, 'ID field is present.');
|
||||
$this->assertNotNull($record->name, 'Name field is present.');
|
||||
$this->assertNotNull($record->age, 'Age field is present.');
|
||||
$this->assertNotNull($record->job, 'Job field is present.');
|
||||
|
||||
// Ensure that we got the right record.
|
||||
// Check that all fields we asked for are present.
|
||||
$this->assertEqual($record->id, 2, 'ID field has the correct value.');
|
||||
$this->assertEqual($record->name, 'George', 'Name field has the correct value.');
|
||||
$this->assertEqual($record->age, 27, 'Age field has the correct value.');
|
||||
$this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a comparison with NULL is always FALSE.
|
||||
*/
|
||||
function testNullCondition() {
|
||||
$this->ensureSampleDataNull();
|
||||
|
||||
$names = db_select('test_null', 'tn')
|
||||
->fields('tn', array('name'))
|
||||
->condition('age', NULL)
|
||||
->execute()->fetchCol();
|
||||
|
||||
$this->assertEqual(count($names), 0, 'No records found when comparing to NULL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can find a record with a NULL value.
|
||||
*/
|
||||
function testIsNullCondition() {
|
||||
$this->ensureSampleDataNull();
|
||||
|
||||
$names = db_select('test_null', 'tn')
|
||||
->fields('tn', array('name'))
|
||||
->isNull('age')
|
||||
->execute()->fetchCol();
|
||||
|
||||
$this->assertEqual(count($names), 1, 'Correct number of records found with NULL age.');
|
||||
$this->assertEqual($names[0], 'Fozzie', 'Correct record returned for NULL age.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can find a record without a NULL value.
|
||||
*/
|
||||
function testIsNotNullCondition() {
|
||||
$this->ensureSampleDataNull();
|
||||
|
||||
$names = db_select('test_null', 'tn')
|
||||
->fields('tn', array('name'))
|
||||
->isNotNull('tn.age')
|
||||
->orderBy('name')
|
||||
->execute()->fetchCol();
|
||||
|
||||
$this->assertEqual(count($names), 2, 'Correct number of records found withNOT NULL age.');
|
||||
$this->assertEqual($names[0], 'Gonzo', 'Correct record returned for NOT NULL age.');
|
||||
$this->assertEqual($names[1], 'Kermit', 'Correct record returned for NOT NULL age.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can UNION multiple Select queries together.
|
||||
*
|
||||
* This is semantically equal to UNION DISTINCT, so we don't explicitly test
|
||||
* that.
|
||||
*/
|
||||
function testUnion() {
|
||||
$query_1 = db_select('test', 't')
|
||||
->fields('t', array('name'))
|
||||
->condition('age', array(27, 28), 'IN');
|
||||
|
||||
$query_2 = db_select('test', 't')
|
||||
->fields('t', array('name'))
|
||||
->condition('age', 28);
|
||||
|
||||
$query_1->union($query_2);
|
||||
|
||||
$names = $query_1->execute()->fetchCol();
|
||||
|
||||
// Ensure we only get 2 records.
|
||||
$this->assertEqual(count($names), 2, 'UNION correctly discarded duplicates.');
|
||||
|
||||
$this->assertEqual($names[0], 'George', 'First query returned correct name.');
|
||||
$this->assertEqual($names[1], 'Ringo', 'Second query returned correct name.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can UNION ALL multiple SELECT queries together.
|
||||
*/
|
||||
function testUnionAll() {
|
||||
$query_1 = db_select('test', 't')
|
||||
->fields('t', array('name'))
|
||||
->condition('age', array(27, 28), 'IN');
|
||||
|
||||
$query_2 = db_select('test', 't')
|
||||
->fields('t', array('name'))
|
||||
->condition('age', 28);
|
||||
|
||||
$query_1->union($query_2, 'ALL');
|
||||
|
||||
$names = $query_1->execute()->fetchCol();
|
||||
|
||||
// Ensure we get all 3 records.
|
||||
$this->assertEqual(count($names), 3, 'UNION ALL correctly preserved duplicates.');
|
||||
|
||||
$this->assertEqual($names[0], 'George', 'First query returned correct first name.');
|
||||
$this->assertEqual($names[1], 'Ringo', 'Second query returned correct second name.');
|
||||
$this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can get a count query for a UNION Select query.
|
||||
*/
|
||||
function testUnionCount() {
|
||||
$query_1 = db_select('test', 't')
|
||||
->fields('t', array('name', 'age'))
|
||||
->condition('age', array(27, 28), 'IN');
|
||||
|
||||
$query_2 = db_select('test', 't')
|
||||
->fields('t', array('name', 'age'))
|
||||
->condition('age', 28);
|
||||
|
||||
$query_1->union($query_2, 'ALL');
|
||||
$names = $query_1->execute()->fetchCol();
|
||||
|
||||
$query_3 = $query_1->countQuery();
|
||||
$count = $query_3->execute()->fetchField();
|
||||
|
||||
// Ensure the counts match.
|
||||
$this->assertEqual(count($names), $count, "The count query's result matched the number of rows in the UNION query.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that random ordering of queries works.
|
||||
*
|
||||
* We take the approach of testing the Drupal layer only, rather than trying
|
||||
* to test that the database's random number generator actually produces
|
||||
* random queries (which is very difficult to do without an unacceptable risk
|
||||
* of the test failing by accident).
|
||||
*
|
||||
* Therefore, in this test we simply run the same query twice and assert that
|
||||
* the two results are reordered versions of each other (as well as of the
|
||||
* same query without the random ordering). It is reasonable to assume that
|
||||
* if we run the same select query twice and the results are in a different
|
||||
* order each time, the only way this could happen is if we have successfully
|
||||
* triggered the database's random ordering functionality.
|
||||
*/
|
||||
function testRandomOrder() {
|
||||
// Use 52 items, so the chance that this test fails by accident will be the
|
||||
// same as the chance that a deck of cards will come out in the same order
|
||||
// after shuffling it (in other words, nearly impossible).
|
||||
$number_of_items = 52;
|
||||
while (db_query("SELECT MAX(id) FROM {test}")->fetchField() < $number_of_items) {
|
||||
db_insert('test')->fields(array('name' => $this->randomMachineName()))->execute();
|
||||
}
|
||||
|
||||
// First select the items in order and make sure we get an ordered list.
|
||||
$expected_ids = range(1, $number_of_items);
|
||||
$ordered_ids = db_select('test', 't')
|
||||
->fields('t', array('id'))
|
||||
->range(0, $number_of_items)
|
||||
->orderBy('id')
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertEqual($ordered_ids, $expected_ids, 'A query without random ordering returns IDs in the correct order.');
|
||||
|
||||
// Now perform the same query, but instead choose a random ordering. We
|
||||
// expect this to contain a differently ordered version of the original
|
||||
// result.
|
||||
$randomized_ids = db_select('test', 't')
|
||||
->fields('t', array('id'))
|
||||
->range(0, $number_of_items)
|
||||
->orderRandom()
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertNotEqual($randomized_ids, $ordered_ids, 'A query with random ordering returns an unordered set of IDs.');
|
||||
$sorted_ids = $randomized_ids;
|
||||
sort($sorted_ids);
|
||||
$this->assertEqual($sorted_ids, $ordered_ids, 'After sorting the random list, the result matches the original query.');
|
||||
|
||||
// Now perform the exact same query again, and make sure the order is
|
||||
// different.
|
||||
$randomized_ids_second_set = db_select('test', 't')
|
||||
->fields('t', array('id'))
|
||||
->range(0, $number_of_items)
|
||||
->orderRandom()
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertNotEqual($randomized_ids_second_set, $randomized_ids, 'Performing the query with random ordering a second time returns IDs in a different order.');
|
||||
$sorted_ids_second_set = $randomized_ids_second_set;
|
||||
sort($sorted_ids_second_set);
|
||||
$this->assertEqual($sorted_ids_second_set, $sorted_ids, 'After sorting the second random list, the result matches the sorted version of the first random list.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that filter by a regular expression works as expected.
|
||||
*/
|
||||
public function testRegexCondition() {
|
||||
|
||||
$test_groups[] = array(
|
||||
'regex' => 'hn$',
|
||||
'expected' => array(
|
||||
'John',
|
||||
),
|
||||
);
|
||||
$test_groups[] = array(
|
||||
'regex' => '^Pau',
|
||||
'expected' => array(
|
||||
'Paul',
|
||||
),
|
||||
);
|
||||
$test_groups[] = array(
|
||||
'regex' => 'Ringo|George',
|
||||
'expected' => array(
|
||||
'Ringo', 'George',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
$database = $this->container->get('database');
|
||||
foreach ($test_groups as $test_group) {
|
||||
$query = $database->select('test', 't');
|
||||
$query->addField('t', 'name');
|
||||
$query->condition('t.name', $test_group['regex'], 'REGEXP');
|
||||
$result = $query->execute()->fetchCol();
|
||||
|
||||
$this->assertEqual(count($result), count($test_group['expected']), 'Returns the expected number of rows.');
|
||||
$this->assertEqual(sort($result), sort($test_group['expected']), 'Returns the expected rows.');
|
||||
}
|
||||
|
||||
// Ensure that filter by "#" still works due to the quoting.
|
||||
$database->insert('test')
|
||||
->fields(array(
|
||||
'name' => 'Pete',
|
||||
'age' => 26,
|
||||
'job' => '#Drummer',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$test_groups = array();
|
||||
$test_groups[] = array(
|
||||
'regex' => '#Drummer',
|
||||
'expected' => array(
|
||||
'Pete',
|
||||
),
|
||||
);
|
||||
$test_groups[] = array(
|
||||
'regex' => '#Singer',
|
||||
'expected' => array(
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($test_groups as $test_group) {
|
||||
$query = $database->select('test', 't');
|
||||
$query->addField('t', 'name');
|
||||
$query->condition('t.job', $test_group['regex'], 'REGEXP');
|
||||
$result = $query->execute()->fetchCol();
|
||||
|
||||
$this->assertEqual(count($result), count($test_group['expected']), 'Returns the expected number of rows.');
|
||||
$this->assertEqual(sort($result), sort($test_group['expected']), 'Returns the expected rows.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that aliases are renamed when they are duplicates.
|
||||
*/
|
||||
function testSelectDuplicateAlias() {
|
||||
$query = db_select('test', 't');
|
||||
$alias1 = $query->addField('t', 'name', 'the_alias');
|
||||
$alias2 = $query->addField('t', 'age', 'the_alias');
|
||||
$this->assertNotIdentical($alias1, $alias2, 'Duplicate aliases are renamed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an invalid merge query throws an exception.
|
||||
*/
|
||||
function testInvalidSelectCount() {
|
||||
try {
|
||||
// This query will fail because the table does not exist.
|
||||
// Normally it would throw an exception but we are suppressing
|
||||
// it with the throw_exception option.
|
||||
$options['throw_exception'] = FALSE;
|
||||
db_select('some_table_that_doesnt_exist', 't', $options)
|
||||
->fields('t')
|
||||
->countQuery()
|
||||
->execute();
|
||||
|
||||
$this->pass('$options[\'throw_exception\'] is FALSE, no Exception thrown.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->fail('$options[\'throw_exception\'] is FALSE, but Exception thrown for invalid query.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// This query will fail because the table does not exist.
|
||||
db_select('some_table_that_doesnt_exist', 't')
|
||||
->fields('t')
|
||||
->countQuery()
|
||||
->execute();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->pass('Exception thrown for invalid query.');
|
||||
return;
|
||||
}
|
||||
$this->fail('No Exception thrown.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests thrown exception for IN query conditions with an empty array.
|
||||
*/
|
||||
function testEmptyInCondition() {
|
||||
try {
|
||||
db_select('test', 't')
|
||||
->fields('t')
|
||||
->condition('age', array(), 'IN')
|
||||
->execute();
|
||||
|
||||
$this->fail('Expected exception not thrown');
|
||||
}
|
||||
catch (InvalidQueryException $e) {
|
||||
$this->assertEqual("Query condition 'age IN ()' cannot be empty.", $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
db_select('test', 't')
|
||||
->fields('t')
|
||||
->condition('age', array(), 'NOT IN')
|
||||
->execute();
|
||||
|
||||
$this->fail('Expected exception not thrown');
|
||||
}
|
||||
catch (InvalidQueryException $e) {
|
||||
$this->assertEqual("Query condition 'age NOT IN ()' cannot be empty.", $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\SerializeQueryTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests serializing and unserializing a query.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class SerializeQueryTest extends DatabaseTestBase {
|
||||
/**
|
||||
* Confirms that a query can be serialized and unserialized.
|
||||
*/
|
||||
function testSerializeQuery() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'age');
|
||||
$query->condition('name', 'Ringo');
|
||||
// If this doesn't work, it will throw an exception, so no need for an
|
||||
// assertion.
|
||||
$query = unserialize(serialize($query));
|
||||
$results = $query->execute()->fetchCol();
|
||||
$this->assertEqual($results[0], 28, 'Query properly executed after unserialization.');
|
||||
}
|
||||
}
|
||||
132
core/modules/system/src/Tests/Database/TaggingTest.php
Normal file
132
core/modules/system/src/Tests/Database/TaggingTest.php
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\TaggingTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the tagging capabilities of the Select builder.
|
||||
*
|
||||
* Tags are a way to flag queries for alter hooks so they know
|
||||
* what type of query it is, such as "node_access".
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class TaggingTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that a query has a tag added to it.
|
||||
*/
|
||||
function testHasTag() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
|
||||
$query->addTag('test');
|
||||
|
||||
$this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
|
||||
$this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests query tagging "has all of these tags" functionality.
|
||||
*/
|
||||
function testHasAllTags() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
|
||||
$query->addTag('test');
|
||||
$query->addTag('other');
|
||||
|
||||
$this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
|
||||
$this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests query tagging "has at least one of these tags" functionality.
|
||||
*/
|
||||
function testHasAnyTag() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
|
||||
$query->addTag('test');
|
||||
|
||||
$this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
|
||||
$this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that an extended query has a tag added to it.
|
||||
*/
|
||||
function testExtenderHasTag() {
|
||||
$query = db_select('test')
|
||||
->extend('Drupal\Core\Database\Query\SelectExtender');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
|
||||
$query->addTag('test');
|
||||
|
||||
$this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
|
||||
$this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests extended query tagging "has all of these tags" functionality.
|
||||
*/
|
||||
function testExtenderHasAllTags() {
|
||||
$query = db_select('test')
|
||||
->extend('Drupal\Core\Database\Query\SelectExtender');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
|
||||
$query->addTag('test');
|
||||
$query->addTag('other');
|
||||
|
||||
$this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
|
||||
$this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests extended query tagging "has at least one of these tags" functionality.
|
||||
*/
|
||||
function testExtenderHasAnyTag() {
|
||||
$query = db_select('test')
|
||||
->extend('Drupal\Core\Database\Query\SelectExtender');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
|
||||
$query->addTag('test');
|
||||
|
||||
$this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
|
||||
$this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we can attach metadata to a query object.
|
||||
*
|
||||
* This is how we pass additional context to alter hooks.
|
||||
*/
|
||||
function testMetaData() {
|
||||
$query = db_select('test');
|
||||
$query->addField('test', 'name');
|
||||
$query->addField('test', 'age', 'age');
|
||||
|
||||
$data = array(
|
||||
'a' => 'A',
|
||||
'b' => 'B',
|
||||
);
|
||||
|
||||
$query->addMetaData('test', $data);
|
||||
|
||||
$return = $query->getMetaData('test');
|
||||
$this->assertEqual($data, $return, 'Correct metadata returned.');
|
||||
|
||||
$return = $query->getMetaData('nothere');
|
||||
$this->assertNull($return, 'Non-existent key returned NULL.');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\TemporaryQueryTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the temporary query functionality.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class TemporaryQueryTest extends DatabaseWebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('database_test');
|
||||
|
||||
/**
|
||||
* Returns the number of rows of a table.
|
||||
*/
|
||||
function countTableRows($table_name) {
|
||||
return db_select($table_name)->countQuery()->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that temporary tables work and are limited to one request.
|
||||
*/
|
||||
function testTemporaryQuery() {
|
||||
$this->drupalGet('database_test/db_query_temporary');
|
||||
$data = json_decode($this->getRawContent());
|
||||
if ($data) {
|
||||
$this->assertEqual($this->countTableRows('test'), $data->row_count, 'The temporary table contains the correct amount of rows.');
|
||||
$this->assertFalse(db_table_exists($data->table_name), 'The temporary table is, indeed, temporary.');
|
||||
}
|
||||
else {
|
||||
$this->fail('The creation of the temporary table failed.');
|
||||
}
|
||||
|
||||
// Now try to run two db_query_temporary() in the same request.
|
||||
$table_name_test = db_query_temporary('SELECT name FROM {test}', array());
|
||||
$table_name_task = db_query_temporary('SELECT pid FROM {test_task}', array());
|
||||
|
||||
$this->assertEqual($this->countTableRows($table_name_test), $this->countTableRows('test'), 'A temporary table was created successfully in this request.');
|
||||
$this->assertEqual($this->countTableRows($table_name_task), $this->countTableRows('test_task'), 'A second temporary table was created successfully in this request.');
|
||||
|
||||
// Check that leading whitespace and comments do not cause problems
|
||||
// in the modified query.
|
||||
$sql = "
|
||||
-- Let's select some rows into a temporary table
|
||||
SELECT name FROM {test}
|
||||
";
|
||||
$table_name_test = db_query_temporary($sql, array());
|
||||
$this->assertEqual($this->countTableRows($table_name_test), $this->countTableRows('test'), 'Leading white space and comments do not interfere with temporary table creation.');
|
||||
}
|
||||
}
|
||||
496
core/modules/system/src/Tests/Database/TransactionTest.php
Normal file
496
core/modules/system/src/Tests/Database/TransactionTest.php
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\TransactionTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\TransactionOutOfOrderException;
|
||||
use Drupal\Core\Database\TransactionNoActiveException;
|
||||
|
||||
/**
|
||||
* Tests the transaction abstraction system.
|
||||
*
|
||||
* We test nesting by having two transaction layers, an outer and inner. The
|
||||
* outer layer encapsulates the inner layer. Our transaction nesting abstraction
|
||||
* should allow the outer layer function to call any function it wants,
|
||||
* especially the inner layer that starts its own transaction, and be
|
||||
* confident that, when the function it calls returns, its own transaction
|
||||
* is still "alive."
|
||||
*
|
||||
* Call structure:
|
||||
* transactionOuterLayer()
|
||||
* Start transaction
|
||||
* transactionInnerLayer()
|
||||
* Start transaction (does nothing in database)
|
||||
* [Maybe decide to roll back]
|
||||
* Do more stuff
|
||||
* Should still be in transaction A
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class TransactionTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Encapsulates a transaction's "inner layer" with an "outer layer".
|
||||
*
|
||||
* This "outer layer" transaction starts and then encapsulates the "inner
|
||||
* layer" transaction. This nesting is used to evaluate whether the database
|
||||
* transaction API properly supports nesting. By "properly supports," we mean
|
||||
* the outer transaction continues to exist regardless of what functions are
|
||||
* called and whether those functions start their own transactions.
|
||||
*
|
||||
* In contrast, a typical database would commit the outer transaction, start
|
||||
* a new transaction for the inner layer, commit the inner layer transaction,
|
||||
* and then be confused when the outer layer transaction tries to commit its
|
||||
* transaction (which was already committed when the inner transaction
|
||||
* started).
|
||||
*
|
||||
* @param $suffix
|
||||
* Suffix to add to field values to differentiate tests.
|
||||
* @param $rollback
|
||||
* Whether or not to try rolling back the transaction when we're done.
|
||||
* @param $ddl_statement
|
||||
* Whether to execute a DDL statement during the inner transaction.
|
||||
*/
|
||||
protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
|
||||
$connection = Database::getConnection();
|
||||
$depth = $connection->transactionDepth();
|
||||
$txn = db_transaction();
|
||||
|
||||
// Insert a single row into the testing table.
|
||||
db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'David' . $suffix,
|
||||
'age' => '24',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$this->assertTrue($connection->inTransaction(), 'In transaction before calling nested transaction.');
|
||||
|
||||
// We're already in a transaction, but we call ->transactionInnerLayer
|
||||
// to nest another transaction inside the current one.
|
||||
$this->transactionInnerLayer($suffix, $rollback, $ddl_statement);
|
||||
|
||||
$this->assertTrue($connection->inTransaction(), 'In transaction after calling nested transaction.');
|
||||
|
||||
if ($rollback) {
|
||||
// Roll back the transaction, if requested.
|
||||
// This rollback should propagate to the last savepoint.
|
||||
$txn->rollback();
|
||||
$this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an "inner layer" transaction.
|
||||
*
|
||||
* This "inner layer" transaction is either used alone or nested inside of the
|
||||
* "outer layer" transaction.
|
||||
*
|
||||
* @param $suffix
|
||||
* Suffix to add to field values to differentiate tests.
|
||||
* @param $rollback
|
||||
* Whether or not to try rolling back the transaction when we're done.
|
||||
* @param $ddl_statement
|
||||
* Whether to execute a DDL statement during the transaction.
|
||||
*/
|
||||
protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
|
||||
$connection = Database::getConnection();
|
||||
|
||||
$depth = $connection->transactionDepth();
|
||||
// Start a transaction. If we're being called from ->transactionOuterLayer,
|
||||
// then we're already in a transaction. Normally, that would make starting
|
||||
// a transaction here dangerous, but the database API handles this problem
|
||||
// for us by tracking the nesting and avoiding the danger.
|
||||
$txn = db_transaction();
|
||||
|
||||
$depth2 = $connection->transactionDepth();
|
||||
$this->assertTrue($depth < $depth2, 'Transaction depth is has increased with new transaction.');
|
||||
|
||||
// Insert a single row into the testing table.
|
||||
db_insert('test')
|
||||
->fields(array(
|
||||
'name' => 'Daniel' . $suffix,
|
||||
'age' => '19',
|
||||
))
|
||||
->execute();
|
||||
|
||||
$this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
|
||||
|
||||
if ($ddl_statement) {
|
||||
$table = array(
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
);
|
||||
db_create_table('database_test_1', $table);
|
||||
|
||||
$this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
|
||||
}
|
||||
|
||||
if ($rollback) {
|
||||
// Roll back the transaction, if requested.
|
||||
// This rollback should propagate to the last savepoint.
|
||||
$txn->rollback();
|
||||
$this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests transaction rollback on a database that supports transactions.
|
||||
*
|
||||
* If the active connection does not support transactions, this test does
|
||||
* nothing.
|
||||
*/
|
||||
function testTransactionRollBackSupported() {
|
||||
// This test won't work right if transactions are not supported.
|
||||
if (!Database::getConnection()->supportsTransactions()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Create two nested transactions. Roll back from the inner one.
|
||||
$this->transactionOuterLayer('B', TRUE);
|
||||
|
||||
// Neither of the rows we inserted in the two transaction layers
|
||||
// should be present in the tables post-rollback.
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
|
||||
$this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
|
||||
$this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests transaction rollback on a database that doesn't support transactions.
|
||||
*
|
||||
* If the active driver supports transactions, this test does nothing.
|
||||
*/
|
||||
function testTransactionRollBackNotSupported() {
|
||||
// This test won't work right if transactions are supported.
|
||||
if (Database::getConnection()->supportsTransactions()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Create two nested transactions. Attempt to roll back from the inner one.
|
||||
$this->transactionOuterLayer('B', TRUE);
|
||||
|
||||
// Because our current database claims to not support transactions,
|
||||
// the inserted rows should be present despite the attempt to roll back.
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a committed transaction.
|
||||
*
|
||||
* The behavior of this test should be identical for connections that support
|
||||
* transactions and those that do not.
|
||||
*/
|
||||
function testCommittedTransaction() {
|
||||
try {
|
||||
// Create two nested transactions. The changes should be committed.
|
||||
$this->transactionOuterLayer('A');
|
||||
|
||||
// Because we committed, both of the inserted rows should be present.
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.');
|
||||
$saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField();
|
||||
$this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the compatibility of transactions with DDL statements.
|
||||
*/
|
||||
function testTransactionWithDdlStatement() {
|
||||
// First, test that a commit works normally, even with DDL statements.
|
||||
$transaction = db_transaction();
|
||||
$this->insertRow('row');
|
||||
$this->executeDDLStatement();
|
||||
unset($transaction);
|
||||
$this->assertRowPresent('row');
|
||||
|
||||
// Even in different order.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$this->executeDDLStatement();
|
||||
$this->insertRow('row');
|
||||
unset($transaction);
|
||||
$this->assertRowPresent('row');
|
||||
|
||||
// Even with stacking.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$transaction2 = db_transaction();
|
||||
$this->executeDDLStatement();
|
||||
unset($transaction2);
|
||||
$transaction3 = db_transaction();
|
||||
$this->insertRow('row');
|
||||
unset($transaction3);
|
||||
unset($transaction);
|
||||
$this->assertRowPresent('row');
|
||||
|
||||
// A transaction after a DDL statement should still work the same.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$transaction2 = db_transaction();
|
||||
$this->executeDDLStatement();
|
||||
unset($transaction2);
|
||||
$transaction3 = db_transaction();
|
||||
$this->insertRow('row');
|
||||
$transaction3->rollback();
|
||||
unset($transaction3);
|
||||
unset($transaction);
|
||||
$this->assertRowAbsent('row');
|
||||
|
||||
// The behavior of a rollback depends on the type of database server.
|
||||
if (Database::getConnection()->supportsTransactionalDDL()) {
|
||||
// For database servers that support transactional DDL, a rollback
|
||||
// of a transaction including DDL statements should be possible.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$this->insertRow('row');
|
||||
$this->executeDDLStatement();
|
||||
$transaction->rollback();
|
||||
unset($transaction);
|
||||
$this->assertRowAbsent('row');
|
||||
|
||||
// Including with stacking.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$transaction2 = db_transaction();
|
||||
$this->executeDDLStatement();
|
||||
unset($transaction2);
|
||||
$transaction3 = db_transaction();
|
||||
$this->insertRow('row');
|
||||
unset($transaction3);
|
||||
$transaction->rollback();
|
||||
unset($transaction);
|
||||
$this->assertRowAbsent('row');
|
||||
}
|
||||
else {
|
||||
// For database servers that do not support transactional DDL,
|
||||
// the DDL statement should commit the transaction stack.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$this->insertRow('row');
|
||||
$this->executeDDLStatement();
|
||||
// Rollback the outer transaction.
|
||||
try {
|
||||
$transaction->rollback();
|
||||
unset($transaction);
|
||||
// @TODO: an exception should be triggered here, but is not, because
|
||||
// "ROLLBACK" fails silently in MySQL if there is no transaction active.
|
||||
// $this->fail(t('Rolling back a transaction containing DDL should fail.'));
|
||||
}
|
||||
catch (TransactionNoActiveException $e) {
|
||||
$this->pass('Rolling back a transaction containing DDL should fail.');
|
||||
}
|
||||
$this->assertRowPresent('row');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a single row into the testing table.
|
||||
*/
|
||||
protected function insertRow($name) {
|
||||
db_insert('test')
|
||||
->fields(array(
|
||||
'name' => $name,
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a DDL statement.
|
||||
*/
|
||||
protected function executeDDLStatement() {
|
||||
static $count = 0;
|
||||
$table = array(
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
);
|
||||
db_create_table('database_test_' . ++$count, $table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts over for a new test.
|
||||
*/
|
||||
protected function cleanUp() {
|
||||
db_truncate('test')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a given row is present in the test table.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the row.
|
||||
* @param $message
|
||||
* The message to log for the assertion.
|
||||
*/
|
||||
function assertRowPresent($name, $message = NULL) {
|
||||
if (!isset($message)) {
|
||||
$message = format_string('Row %name is present.', array('%name' => $name));
|
||||
}
|
||||
$present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
|
||||
return $this->assertTrue($present, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a given row is absent from the test table.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the row.
|
||||
* @param $message
|
||||
* The message to log for the assertion.
|
||||
*/
|
||||
function assertRowAbsent($name, $message = NULL) {
|
||||
if (!isset($message)) {
|
||||
$message = format_string('Row %name is absent.', array('%name' => $name));
|
||||
}
|
||||
$present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
|
||||
return $this->assertFalse($present, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests transaction stacking, commit, and rollback.
|
||||
*/
|
||||
function testTransactionStacking() {
|
||||
// This test won't work right if transactions are not supported.
|
||||
if (!Database::getConnection()->supportsTransactions()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$database = Database::getConnection();
|
||||
|
||||
// Standard case: pop the inner transaction before the outer transaction.
|
||||
$transaction = db_transaction();
|
||||
$this->insertRow('outer');
|
||||
$transaction2 = db_transaction();
|
||||
$this->insertRow('inner');
|
||||
// Pop the inner transaction.
|
||||
unset($transaction2);
|
||||
$this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction');
|
||||
// Pop the outer transaction.
|
||||
unset($transaction);
|
||||
$this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction');
|
||||
$this->assertRowPresent('outer');
|
||||
$this->assertRowPresent('inner');
|
||||
|
||||
// Pop the transaction in a different order they have been pushed.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$this->insertRow('outer');
|
||||
$transaction2 = db_transaction();
|
||||
$this->insertRow('inner');
|
||||
// Pop the outer transaction, nothing should happen.
|
||||
unset($transaction);
|
||||
$this->insertRow('inner-after-outer-commit');
|
||||
$this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
|
||||
// Pop the inner transaction, the whole transaction should commit.
|
||||
unset($transaction2);
|
||||
$this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
|
||||
$this->assertRowPresent('outer');
|
||||
$this->assertRowPresent('inner');
|
||||
$this->assertRowPresent('inner-after-outer-commit');
|
||||
|
||||
// Rollback the inner transaction.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$this->insertRow('outer');
|
||||
$transaction2 = db_transaction();
|
||||
$this->insertRow('inner');
|
||||
// Now rollback the inner transaction.
|
||||
$transaction2->rollback();
|
||||
unset($transaction2);
|
||||
$this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
|
||||
// Pop the outer transaction, it should commit.
|
||||
$this->insertRow('outer-after-inner-rollback');
|
||||
unset($transaction);
|
||||
$this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
|
||||
$this->assertRowPresent('outer');
|
||||
$this->assertRowAbsent('inner');
|
||||
$this->assertRowPresent('outer-after-inner-rollback');
|
||||
|
||||
// Rollback the inner transaction after committing the outer one.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$this->insertRow('outer');
|
||||
$transaction2 = db_transaction();
|
||||
$this->insertRow('inner');
|
||||
// Pop the outer transaction, nothing should happen.
|
||||
unset($transaction);
|
||||
$this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
|
||||
// Now rollback the inner transaction, it should rollback.
|
||||
$transaction2->rollback();
|
||||
unset($transaction2);
|
||||
$this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
|
||||
$this->assertRowPresent('outer');
|
||||
$this->assertRowAbsent('inner');
|
||||
|
||||
// Rollback the outer transaction while the inner transaction is active.
|
||||
// In that case, an exception will be triggered because we cannot
|
||||
// ensure that the final result will have any meaning.
|
||||
$this->cleanUp();
|
||||
$transaction = db_transaction();
|
||||
$this->insertRow('outer');
|
||||
$transaction2 = db_transaction();
|
||||
$this->insertRow('inner');
|
||||
$transaction3 = db_transaction();
|
||||
$this->insertRow('inner2');
|
||||
// Rollback the outer transaction.
|
||||
try {
|
||||
$transaction->rollback();
|
||||
unset($transaction);
|
||||
$this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
|
||||
}
|
||||
catch (TransactionOutOfOrderException $e) {
|
||||
$this->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
|
||||
}
|
||||
$this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction');
|
||||
// Try to commit one inner transaction.
|
||||
unset($transaction3);
|
||||
$this->pass('Trying to commit an inner transaction resulted in an exception.');
|
||||
// Try to rollback one inner transaction.
|
||||
try {
|
||||
$transaction->rollback();
|
||||
unset($transaction2);
|
||||
$this->fail('Trying to commit an inner transaction resulted in an exception.');
|
||||
}
|
||||
catch (TransactionNoActiveException $e) {
|
||||
$this->pass('Trying to commit an inner transaction resulted in an exception.');
|
||||
}
|
||||
$this->assertRowAbsent('outer');
|
||||
$this->assertRowAbsent('inner');
|
||||
$this->assertRowAbsent('inner2');
|
||||
}
|
||||
}
|
||||
|
||||
150
core/modules/system/src/Tests/Database/UpdateComplexTest.php
Normal file
150
core/modules/system/src/Tests/Database/UpdateComplexTest.php
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\UpdateComplexTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the Update query builder, complex queries.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class UpdateComplexTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Tests updates with OR conditionals.
|
||||
*/
|
||||
function testOrConditionUpdate() {
|
||||
$update = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->condition(db_or()
|
||||
->condition('name', 'John')
|
||||
->condition('name', 'Paul')
|
||||
);
|
||||
$num_updated = $update->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests WHERE IN clauses.
|
||||
*/
|
||||
function testInConditionUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->condition('name', array('John', 'Paul'), 'IN')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests WHERE NOT IN clauses.
|
||||
*/
|
||||
function testNotInConditionUpdate() {
|
||||
// The o is lowercase in the 'NoT IN' operator, to make sure the operators
|
||||
// work in mixed case.
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->condition('name', array('John', 'Paul', 'George'), 'NoT IN')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests BETWEEN conditional clauses.
|
||||
*/
|
||||
function testBetweenConditionUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->condition('age', array(25, 26), 'BETWEEN')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests LIKE conditionals.
|
||||
*/
|
||||
function testLikeConditionUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->condition('name', '%ge%', 'LIKE')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UPDATE with expression values.
|
||||
*/
|
||||
function testUpdateExpression() {
|
||||
$before_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
|
||||
$GLOBALS['larry_test'] = 1;
|
||||
$num_updated = db_update('test')
|
||||
->condition('name', 'Ringo')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->expression('age', 'age + :age', array(':age' => 4))
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
|
||||
|
||||
$person = db_query('SELECT * FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetch();
|
||||
$this->assertEqual($person->name, 'Ringo', 'Name set correctly.');
|
||||
$this->assertEqual($person->age, $before_age + 4, 'Age set correctly.');
|
||||
$this->assertEqual($person->job, 'Musician', 'Job set correctly.');
|
||||
$GLOBALS['larry_test'] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UPDATE with only expression values.
|
||||
*/
|
||||
function testUpdateOnlyExpression() {
|
||||
$before_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
|
||||
$num_updated = db_update('test')
|
||||
->condition('name', 'Ringo')
|
||||
->expression('age', 'age + :age', array(':age' => 4))
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
|
||||
$this->assertEqual($before_age + 4, $after_age, 'Age updated correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test UPDATE with a subselect value.
|
||||
*/
|
||||
function testSubSelectUpdate() {
|
||||
$subselect = db_select('test_task', 't');
|
||||
$subselect->addExpression('MAX(priority) + :increment', 'max_priority', array(':increment' => 30));
|
||||
// Clone this to make sure we are running a different query when
|
||||
// asserting.
|
||||
$select = clone $subselect;
|
||||
$query = db_update('test')
|
||||
->expression('age', $subselect)
|
||||
->condition('name', 'Ringo');
|
||||
// Save the number of rows that updated for assertion later.
|
||||
$num_updated = $query->execute();
|
||||
$after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
|
||||
$expected_age = $select->execute()->fetchField();
|
||||
$this->assertEqual($after_age, $expected_age);
|
||||
$this->assertEqual(1, $num_updated, t('Expected 1 row to be updated in subselect update query.'));
|
||||
}
|
||||
|
||||
}
|
||||
56
core/modules/system/src/Tests/Database/UpdateLobTest.php
Normal file
56
core/modules/system/src/Tests/Database/UpdateLobTest.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\UpdateLobTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the Update query builder with LOB fields.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class UpdateLobTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that we can update a blob column.
|
||||
*/
|
||||
function testUpdateOneBlob() {
|
||||
$data = "This is\000a test.";
|
||||
$this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.');
|
||||
$id = db_insert('test_one_blob')
|
||||
->fields(array('blob1' => $data))
|
||||
->execute();
|
||||
|
||||
$data .= $data;
|
||||
db_update('test_one_blob')
|
||||
->condition('id', $id)
|
||||
->fields(array('blob1' => $data))
|
||||
->execute();
|
||||
|
||||
$r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc();
|
||||
$this->assertTrue($r['blob1'] === $data, format_string('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can update two blob columns in the same table.
|
||||
*/
|
||||
function testUpdateMultipleBlob() {
|
||||
$id = db_insert('test_two_blobs')
|
||||
->fields(array(
|
||||
'blob1' => 'This is',
|
||||
'blob2' => 'a test',
|
||||
))
|
||||
->execute();
|
||||
|
||||
db_update('test_two_blobs')
|
||||
->condition('id', $id)
|
||||
->fields(array('blob1' => 'and so', 'blob2' => 'is this'))
|
||||
->execute();
|
||||
|
||||
$r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc();
|
||||
$this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', 'Can update multiple blobs per row.');
|
||||
}
|
||||
}
|
||||
148
core/modules/system/src/Tests/Database/UpdateTest.php
Normal file
148
core/modules/system/src/Tests/Database/UpdateTest.php
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Database\UpdateTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Database;
|
||||
|
||||
/**
|
||||
* Tests the update query builder.
|
||||
*
|
||||
* @group Database
|
||||
*/
|
||||
class UpdateTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Confirms that we can update a single record successfully.
|
||||
*/
|
||||
function testSimpleUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('name' => 'Tiffany'))
|
||||
->condition('id', 1)
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$saved_name = db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 1))->fetchField();
|
||||
$this->assertIdentical($saved_name, 'Tiffany', 'Updated name successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms updating to NULL.
|
||||
*/
|
||||
function testSimpleNullUpdate() {
|
||||
$this->ensureSampleDataNull();
|
||||
$num_updated = db_update('test_null')
|
||||
->fields(array('age' => NULL))
|
||||
->condition('name', 'Kermit')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$saved_age = db_query('SELECT age FROM {test_null} WHERE name = :name', array(':name' => 'Kermit'))->fetchField();
|
||||
$this->assertNull($saved_age, 'Updated name successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can update multiple records successfully.
|
||||
*/
|
||||
function testMultiUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->condition('job', 'Singer')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can update multiple records with a non-equality condition.
|
||||
*/
|
||||
function testMultiGTUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->condition('age', 26, '>')
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can update multiple records with a where call.
|
||||
*/
|
||||
function testWhereUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->where('age > :age', array(':age' => 26))
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 2, 'Updated 2 records.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that we can stack condition and where calls.
|
||||
*/
|
||||
function testWhereAndConditionUpdate() {
|
||||
$update = db_update('test')
|
||||
->fields(array('job' => 'Musician'))
|
||||
->where('age > :age', array(':age' => 26))
|
||||
->condition('name', 'Ringo');
|
||||
$num_updated = $update->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
|
||||
$this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating with expressions.
|
||||
*/
|
||||
function testExpressionUpdate() {
|
||||
// Ensure that expressions are handled properly. This should set every
|
||||
// record's age to a square of itself.
|
||||
$num_rows = db_update('test')
|
||||
->expression('age', 'age * age')
|
||||
->execute();
|
||||
$this->assertIdentical($num_rows, 4, 'Updated 4 records.');
|
||||
|
||||
$saved_name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => pow(26, 2)))->fetchField();
|
||||
$this->assertIdentical($saved_name, 'Paul', 'Successfully updated values using an algebraic expression.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests return value on update.
|
||||
*/
|
||||
function testUpdateAffectedRows() {
|
||||
// At 5am in the morning, all band members but those with a priority 1 task
|
||||
// are sleeping. So we set their tasks to 'sleep'. 5 records match the
|
||||
// condition and therefore are affected by the query, even though two of
|
||||
// them actually don't have to be changed because their value was already
|
||||
// 'sleep'. Still, execute() should return 5 affected rows, not only 3,
|
||||
// because that's cross-db expected behavior.
|
||||
$num_rows = db_update('test_task')
|
||||
->condition('priority', 1, '<>')
|
||||
->fields(array('task' => 'sleep'))
|
||||
->execute();
|
||||
$this->assertIdentical($num_rows, 5, 'Correctly returned 5 affected rows.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that we can update the primary key of a record successfully.
|
||||
*/
|
||||
function testPrimaryKeyUpdate() {
|
||||
$num_updated = db_update('test')
|
||||
->fields(array('id' => 42, 'name' => 'John'))
|
||||
->condition('id', 1)
|
||||
->execute();
|
||||
$this->assertIdentical($num_updated, 1, 'Updated 1 record.');
|
||||
|
||||
$saved_name= db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 42))->fetchField();
|
||||
$this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.');
|
||||
}
|
||||
}
|
||||
124
core/modules/system/src/Tests/Datetime/DrupalDateTimeTest.php
Normal file
124
core/modules/system/src/Tests/Datetime/DrupalDateTimeTest.php
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Datetime\DrupalDateTimeTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Datetime;
|
||||
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests DrupalDateTime functionality.
|
||||
*
|
||||
* @group Datetime
|
||||
*/
|
||||
class DrupalDateTimeTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Set up required modules.
|
||||
*/
|
||||
public static $modules = array();
|
||||
|
||||
/**
|
||||
* Test setup.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the AJAX Timezone Callback can deal with various formats.
|
||||
*/
|
||||
public function testSystemTimezone() {
|
||||
$options = array(
|
||||
'query' => array(
|
||||
'date' => 'Tue+Sep+17+2013+21%3A35%3A31+GMT%2B0100+(BST)#',
|
||||
)
|
||||
);
|
||||
// Query the AJAX Timezone Callback with a long-format date.
|
||||
$response = $this->drupalGet('system/timezone/BST/3600/1', $options);
|
||||
$this->assertEqual($response, '"Europe\/London"', 'Timezone AJAX callback successfully identifies and responds to a long-format date.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that DrupalDateTime can detect the right timezone to use.
|
||||
* Test with a variety of less commonly used timezone names to
|
||||
* help ensure that the system timezone will be different than the
|
||||
* stated timezones.
|
||||
*/
|
||||
public function testDateTimezone() {
|
||||
$date_string = '2007-01-31 21:00:00';
|
||||
|
||||
// Make sure no site timezone has been set.
|
||||
$this->config('system.date')
|
||||
->set('timezone.user.configurable', 0)
|
||||
->set('timezone.default', NULL)
|
||||
->save();
|
||||
|
||||
// Detect the system timezone.
|
||||
$system_timezone = date_default_timezone_get();
|
||||
|
||||
// Create a date object with an unspecified timezone, which should
|
||||
// end up using the system timezone.
|
||||
$date = new DrupalDateTime($date_string);
|
||||
$timezone = $date->getTimezone()->getName();
|
||||
$this->assertTrue($timezone == $system_timezone, 'DrupalDateTime uses the system timezone when there is no site timezone.');
|
||||
|
||||
// Create a date object with a specified timezone.
|
||||
$date = new DrupalDateTime($date_string, 'America/Yellowknife');
|
||||
$timezone = $date->getTimezone()->getName();
|
||||
$this->assertTrue($timezone == 'America/Yellowknife', 'DrupalDateTime uses the specified timezone if provided.');
|
||||
|
||||
// Set a site timezone.
|
||||
$this->config('system.date')->set('timezone.default', 'Europe/Warsaw')->save();
|
||||
|
||||
// Create a date object with an unspecified timezone, which should
|
||||
// end up using the site timezone.
|
||||
$date = new DrupalDateTime($date_string);
|
||||
$timezone = $date->getTimezone()->getName();
|
||||
$this->assertTrue($timezone == 'Europe/Warsaw', 'DrupalDateTime uses the site timezone if provided.');
|
||||
|
||||
// Create user.
|
||||
$this->config('system.date')->set('timezone.user.configurable', 1)->save();
|
||||
$test_user = $this->drupalCreateUser(array());
|
||||
$this->drupalLogin($test_user);
|
||||
|
||||
// Set up the user with a different timezone than the site.
|
||||
$edit = array('mail' => $test_user->getEmail(), 'timezone' => 'Asia/Manila');
|
||||
$this->drupalPostForm('user/' . $test_user->id() . '/edit', $edit, t('Save'));
|
||||
|
||||
// Reload the user and reset the timezone in AccountProxy::setAccount().
|
||||
\Drupal::entityManager()->getStorage('user')->resetCache();
|
||||
$this->container->get('current_user')->setAccount(User::load($test_user->id()));
|
||||
|
||||
// Create a date object with an unspecified timezone, which should
|
||||
// end up using the user timezone.
|
||||
$date = new DrupalDateTime($date_string);
|
||||
$timezone = $date->getTimezone()->getName();
|
||||
$this->assertTrue($timezone == 'Asia/Manila', 'DrupalDateTime uses the user timezone, if configurable timezones are used and it is set.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the ability to override the time zone in the format method.
|
||||
*/
|
||||
function testTimezoneFormat() {
|
||||
// Create a date in UTC
|
||||
$date = DrupalDateTime::createFromTimestamp(87654321, 'UTC');
|
||||
|
||||
// Verify that the date format method displays the default time zone.
|
||||
$this->assertEqual($date->format('Y/m/d H:i:s e'), '1972/10/11 12:25:21 UTC', 'Date has default UTC time zone and correct date/time.');
|
||||
|
||||
// Verify that the format method can override the time zone.
|
||||
$this->assertEqual($date->format('Y/m/d H:i:s e', array('timezone' => 'America/New_York')), '1972/10/11 08:25:21 America/New_York', 'Date displayed overidden time zone and correct date/time');
|
||||
|
||||
// Verify that the date format method still displays the default time zone
|
||||
// for the date object.
|
||||
$this->assertEqual($date->format('Y/m/d H:i:s e'), '1972/10/11 12:25:21 UTC', 'Date still has default UTC time zone and correct date/time');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\DrupalKernel\ContainerRebuildWebTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\DrupalKernel;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Ensures that the container rebuild works as expected.
|
||||
*
|
||||
* @group DrupalKernel
|
||||
*/
|
||||
class ContainerRebuildWebTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['service_provider_test'];
|
||||
|
||||
/**
|
||||
* Sets a different deployment identifier.
|
||||
*/
|
||||
public function testSetContainerRebuildWithDifferentDeploymentIdentifier() {
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertHeader('container_rebuild_indicator', FALSE);
|
||||
|
||||
$this->writeSettings(['settings' => ['deployment_identifier' => (object) ['value' => 'new-identifier', 'required' => TRUE]]]);
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
|
||||
$this->assertHeader('container_rebuild_indicator', 'new-identifier');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\DrupalKernel\ContentNegotiationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\DrupalKernel;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests content negotiation.
|
||||
*
|
||||
* @group DrupalKernel
|
||||
*/
|
||||
class ContentNegotiationTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Verifies HTML responses for bogus Accept headers.
|
||||
*
|
||||
* Drupal does not fully support older browsers, but a page output is still
|
||||
* expected.
|
||||
*
|
||||
* @see https://www.drupal.org/node/1716790
|
||||
*/
|
||||
function testBogusAcceptHeader() {
|
||||
$tests = array(
|
||||
// See https://bugs.webkit.org/show_bug.cgi?id=27267.
|
||||
'Firefox 3.5 (2009)' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'IE8 (2009)' => 'image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-silverlight, */*',
|
||||
'Opera (2009)' => 'text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1',
|
||||
'Chrome (2009)' => 'application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
|
||||
// See https://github.com/symfony/symfony/pull/564.
|
||||
'Firefox 3.6 (2010)' => 'text/html,application/xhtml+xml,application/json,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Safari (2010)' => '*/*',
|
||||
'Opera (2010)' => 'image/jpeg,image/gif,image/x-xbitmap,text/html,image/webp,image/png,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.1',
|
||||
// See https://www.drupal.org/node/1716790.
|
||||
'Safari (2010), iOS 4.2.1 (2012)' => 'application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
|
||||
'Android #1 (2012)' => 'application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
|
||||
'Android #2 (2012)' => 'text/xml,text/html,application/xhtml+xml,image/png,text/plain,*/*;q=0.8',
|
||||
);
|
||||
foreach ($tests as $case => $header) {
|
||||
$this->drupalGet('', array(), array('Accept: ' . $header));
|
||||
$this->assertNoText('Unsupported Media Type', '"Unsupported Media Type" not found for ' . $case);
|
||||
$this->assertText(t('Log in'), '"Log in" found for ' . $case);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\DrupalKernel\DrupalKernelSiteTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\DrupalKernel;
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests site-specific service overrides.
|
||||
*
|
||||
* @group DrupalKernel
|
||||
*/
|
||||
class DrupalKernelSiteTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests services.yml in site directory.
|
||||
*/
|
||||
public function testServicesYml() {
|
||||
$container_yamls = Settings::get('container_yamls');
|
||||
$container_yamls[] = $this->siteDirectory . '/services.yml';
|
||||
$this->settingsSet('container_yamls', $container_yamls);
|
||||
$this->assertFalse($this->container->has('site.service.yml'));
|
||||
// A service provider class always has precedence over services.yml files.
|
||||
// KernelTestBase::buildContainer() swaps out many services with in-memory
|
||||
// implementations already, so those cannot be tested.
|
||||
$this->assertIdentical(get_class($this->container->get('cache.backend.database')), 'Drupal\Core\Cache\DatabaseBackendFactory');
|
||||
|
||||
$class = __CLASS__;
|
||||
$doc = <<<EOD
|
||||
services:
|
||||
# Add a new service.
|
||||
site.service.yml:
|
||||
class: $class
|
||||
# Swap out a core service.
|
||||
cache.backend.database:
|
||||
class: Drupal\Core\Cache\MemoryBackendFactory
|
||||
EOD;
|
||||
file_put_contents($this->siteDirectory . '/services.yml', $doc);
|
||||
|
||||
// Rebuild the container.
|
||||
$this->container->get('kernel')->rebuildContainer();
|
||||
|
||||
$this->assertTrue($this->container->has('site.service.yml'));
|
||||
$this->assertIdentical(get_class($this->container->get('cache.backend.database')), 'Drupal\Core\Cache\MemoryBackendFactory');
|
||||
}
|
||||
|
||||
}
|
||||
214
core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
Normal file
214
core/modules/system/src/Tests/DrupalKernel/DrupalKernelTest.php
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\DrupalKernel\DrupalKernelTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\DrupalKernel;
|
||||
|
||||
use Drupal\Core\DrupalKernel;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests DIC compilation to disk.
|
||||
*
|
||||
* @group DrupalKernel
|
||||
*/
|
||||
class DrupalKernelTest extends KernelTestBase {
|
||||
|
||||
protected function setUp() {
|
||||
// DrupalKernel relies on global $config_directories and requires those
|
||||
// directories to exist. Therefore, create the directories, but do not
|
||||
// invoke KernelTestBase::setUp(), since that would set up further
|
||||
// environment aspects, which would distort this test, because it tests
|
||||
// the DrupalKernel (re-)building itself.
|
||||
$this->prepareConfigDirectories();
|
||||
|
||||
$this->settingsSet('php_storage', array('service_container' => array(
|
||||
'bin' => 'service_container',
|
||||
'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage',
|
||||
'directory' => DRUPAL_ROOT . '/' . $this->publicFilesDirectory . '/php',
|
||||
'secret' => Settings::getHashSalt(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareConfigDirectories() {
|
||||
\Drupal::setContainer($this->originalContainer);
|
||||
parent::prepareConfigDirectories();
|
||||
\Drupal::unsetContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a kernel for testings.
|
||||
*
|
||||
* Because the bootstrap is in DrupalKernel::boot and that involved loading
|
||||
* settings from the filesystem we need to go to extra lengths to build a kernel
|
||||
* for testing.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A request object to use in booting the kernel.
|
||||
* @param array $modules_enabled
|
||||
* A list of modules to enable on the kernel.
|
||||
* @param bool $read_only
|
||||
* Build the kernel in a read only state.
|
||||
*
|
||||
* @return \Drupal\Core\DrupalKernel
|
||||
* New kernel for testing.
|
||||
*/
|
||||
protected function getTestKernel(Request $request, array $modules_enabled = NULL, $read_only = FALSE) {
|
||||
// Manually create kernel to avoid replacing settings.
|
||||
$class_loader = require DRUPAL_ROOT . '/autoload.php';
|
||||
$kernel = DrupalKernel::createFromRequest($request, $class_loader, 'testing');
|
||||
$this->settingsSet('container_yamls', []);
|
||||
$this->settingsSet('hash_salt', $this->databasePrefix);
|
||||
if (isset($modules_enabled)) {
|
||||
$kernel->updateModules($modules_enabled);
|
||||
}
|
||||
$kernel->boot();
|
||||
|
||||
if ($read_only) {
|
||||
$php_storage = Settings::get('php_storage');
|
||||
$php_storage['service_container']['class'] = 'Drupal\Component\PhpStorage\FileReadOnlyStorage';
|
||||
$this->settingsSet('php_storage', $php_storage);
|
||||
}
|
||||
return $kernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests DIC compilation.
|
||||
*/
|
||||
public function testCompileDIC() {
|
||||
// @todo: write a memory based storage backend for testing.
|
||||
$modules_enabled = array(
|
||||
'system' => 'system',
|
||||
'user' => 'user',
|
||||
);
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$this->getTestKernel($request, $modules_enabled);
|
||||
|
||||
// Instantiate it a second time and we should get the compiled Container
|
||||
// class.
|
||||
$kernel = $this->getTestKernel($request);
|
||||
$container = $kernel->getContainer();
|
||||
$refClass = new \ReflectionClass($container);
|
||||
$is_compiled_container =
|
||||
$refClass->getParentClass()->getName() == 'Drupal\Core\DependencyInjection\Container' &&
|
||||
!$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
|
||||
$this->assertTrue($is_compiled_container);
|
||||
// Verify that the list of modules is the same for the initial and the
|
||||
// compiled container.
|
||||
$module_list = array_keys($container->get('module_handler')->getModuleList());
|
||||
$this->assertEqual(array_values($modules_enabled), $module_list);
|
||||
|
||||
// Now use the read-only storage implementation, simulating a "production"
|
||||
// environment.
|
||||
$container = $this->getTestKernel($request, NULL, TRUE)
|
||||
->getContainer();
|
||||
|
||||
$refClass = new \ReflectionClass($container);
|
||||
$is_compiled_container =
|
||||
$refClass->getParentClass()->getName() == 'Drupal\Core\DependencyInjection\Container' &&
|
||||
!$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
|
||||
$this->assertTrue($is_compiled_container);
|
||||
|
||||
// Verify that the list of modules is the same for the initial and the
|
||||
// compiled container.
|
||||
$module_list = array_keys($container->get('module_handler')->getModuleList());
|
||||
$this->assertEqual(array_values($modules_enabled), $module_list);
|
||||
|
||||
// Test that our synthetic services are there.
|
||||
$class_loader = $container->get('class_loader');
|
||||
$refClass = new \ReflectionClass($class_loader);
|
||||
$this->assertTrue($refClass->hasMethod('loadClass'), 'Container has a class loader');
|
||||
|
||||
// We make this assertion here purely to show that the new container below
|
||||
// is functioning correctly, i.e. we get a brand new ContainerBuilder
|
||||
// which has the required new services, after changing the list of enabled
|
||||
// modules.
|
||||
$this->assertFalse($container->has('service_provider_test_class'));
|
||||
|
||||
// Add another module so that we can test that the new module's bundle is
|
||||
// registered to the new container.
|
||||
$modules_enabled['service_provider_test'] = 'service_provider_test';
|
||||
$this->getTestKernel($request, $modules_enabled, TRUE);
|
||||
|
||||
// Instantiate it a second time and we should still get a ContainerBuilder
|
||||
// class because we are using the read-only PHP storage.
|
||||
$kernel = $this->getTestKernel($request, $modules_enabled, TRUE);
|
||||
$container = $kernel->getContainer();
|
||||
|
||||
$refClass = new \ReflectionClass($container);
|
||||
$is_container_builder = $refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
|
||||
$this->assertTrue($is_container_builder, 'Container is a builder');
|
||||
|
||||
// Assert that the new module's bundle was registered to the new container.
|
||||
$this->assertTrue($container->has('service_provider_test_class'), 'Container has test service');
|
||||
|
||||
// Test that our synthetic services are there.
|
||||
$class_loader = $container->get('class_loader');
|
||||
$refClass = new \ReflectionClass($class_loader);
|
||||
$this->assertTrue($refClass->hasMethod('loadClass'), 'Container has a class loader');
|
||||
|
||||
// Check that the location of the new module is registered.
|
||||
$modules = $container->getParameter('container.modules');
|
||||
$this->assertEqual($modules['service_provider_test'], array(
|
||||
'type' => 'module',
|
||||
'pathname' => drupal_get_filename('module', 'service_provider_test'),
|
||||
'filename' => NULL,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests repeated loading of compiled DIC with different environment.
|
||||
*/
|
||||
public function testRepeatedBootWithDifferentEnvironment() {
|
||||
$request = Request::createFromGlobals();
|
||||
$class_loader = require DRUPAL_ROOT . '/autoload.php';
|
||||
|
||||
$environments = [
|
||||
'testing1',
|
||||
'testing1',
|
||||
'testing2',
|
||||
'testing2',
|
||||
];
|
||||
|
||||
foreach ($environments as $environment) {
|
||||
$kernel = DrupalKernel::createFromRequest($request, $class_loader, $environment);
|
||||
$this->settingsSet('container_yamls', []);
|
||||
$this->settingsSet('hash_salt', $this->databasePrefix);
|
||||
$kernel->boot();
|
||||
}
|
||||
|
||||
$this->pass('Repeatedly loaded compiled DIC with different environment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting of site path after kernel boot.
|
||||
*/
|
||||
public function testPreventChangeOfSitePath() {
|
||||
// @todo: write a memory based storage backend for testing.
|
||||
$modules_enabled = array(
|
||||
'system' => 'system',
|
||||
'user' => 'user',
|
||||
);
|
||||
|
||||
$request = Request::createFromGlobals();
|
||||
$kernel = $this->getTestKernel($request, $modules_enabled);
|
||||
$pass = FALSE;
|
||||
try {
|
||||
$kernel->setSitePath('/dev/null');
|
||||
}
|
||||
catch (\LogicException $e) {
|
||||
$pass = TRUE;
|
||||
}
|
||||
$this->assertTrue($pass, 'Throws LogicException if DrupalKernel::setSitePath() is called after boot');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\DrupalKernel\ServiceDestructionTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\DrupalKernel;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
|
||||
|
||||
/**
|
||||
* Tests that services are correctly destructed.
|
||||
*
|
||||
* @group DrupalKernel
|
||||
*/
|
||||
class ServiceDestructionTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Verifies that services are destructed when used.
|
||||
*/
|
||||
public function testDestructionUsed() {
|
||||
// Enable the test module to add it to the container.
|
||||
$this->enableModules(array('service_provider_test'));
|
||||
|
||||
$request = $this->container->get('request_stack')->getCurrentRequest();
|
||||
$kernel = $this->container->get('kernel');
|
||||
$kernel->preHandle($request);
|
||||
|
||||
// The service has not been destructed yet.
|
||||
$this->assertNull(\Drupal::state()->get('service_provider_test.destructed'));
|
||||
|
||||
// Call the class and then terminate the kernel
|
||||
$this->container->get('service_provider_test_class');
|
||||
|
||||
$response = new Response();
|
||||
$kernel->terminate($request, $response);
|
||||
$this->assertTrue(\Drupal::state()->get('service_provider_test.destructed'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that services are not unnecessarily destructed when not used.
|
||||
*/
|
||||
public function testDestructionUnused() {
|
||||
// Enable the test module to add it to the container.
|
||||
$this->enableModules(array('service_provider_test'));
|
||||
|
||||
$request = $this->container->get('request_stack')->getCurrentRequest();
|
||||
$kernel = $this->container->get('kernel');
|
||||
$kernel->preHandle($request);
|
||||
|
||||
// The service has not been destructed yet.
|
||||
$this->assertNull(\Drupal::state()->get('service_provider_test.destructed'));
|
||||
|
||||
// Terminate the kernel. The test class has not been called, so it should not
|
||||
// be destructed.
|
||||
$response = new Response();
|
||||
$kernel->terminate($request, $response);
|
||||
$this->assertNull(\Drupal::state()->get('service_provider_test.destructed'));
|
||||
}
|
||||
}
|
||||
210
core/modules/system/src/Tests/Element/PathElementFormTest.php
Normal file
210
core/modules/system/src/Tests/Element/PathElementFormTest.php
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Element\PathElementFormTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Element;
|
||||
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element\PathElement;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests PathElement validation and conversion functionality.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class PathElementFormTest extends KernelTestBase implements FormInterface {
|
||||
|
||||
/**
|
||||
* User for testing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $testUser;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'user');
|
||||
|
||||
/**
|
||||
* Sets up the test.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', ['router', 'sequences', 'key_value_expire']);
|
||||
$this->installEntitySchema('user');
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
/** @var \Drupal\user\RoleInterface $role */
|
||||
$role = Role::create(array(
|
||||
'id' => 'admin',
|
||||
'label' => 'admin',
|
||||
));
|
||||
$role->grantPermission('link to any page');
|
||||
$role->save();
|
||||
$this->testUser = User::create(array(
|
||||
'name' => 'foobar',
|
||||
'mail' => 'foobar@example.com',
|
||||
));
|
||||
$this->testUser->addRole($role->id());
|
||||
$this->testUser->save();
|
||||
\Drupal::service('current_user')->setAccount($this->testUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'test_path_element';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// A required validated path.
|
||||
$form['required_validate'] = array(
|
||||
'#type' => 'path',
|
||||
'#required' => TRUE,
|
||||
'#title' => 'required_validate',
|
||||
'#convert_path' => PathElement::CONVERT_NONE,
|
||||
);
|
||||
|
||||
// A non validated required path.
|
||||
$form['required_non_validate'] = array(
|
||||
'#type' => 'path',
|
||||
'#required' => TRUE,
|
||||
'#title' => 'required_non_validate',
|
||||
'#convert_path' => PathElement::CONVERT_NONE,
|
||||
'#validate_path' => FALSE,
|
||||
);
|
||||
|
||||
// A non required validated path.
|
||||
$form['optional_validate'] = array(
|
||||
'#type' => 'path',
|
||||
'#required' => FALSE,
|
||||
'#title' => 'optional_validate',
|
||||
'#convert_path' => PathElement::CONVERT_NONE,
|
||||
);
|
||||
|
||||
// A non required converted path.
|
||||
$form['optional_validate'] = array(
|
||||
'#type' => 'path',
|
||||
'#required' => FALSE,
|
||||
'#title' => 'optional_validate',
|
||||
'#convert_path' => PathElement::CONVERT_ROUTE,
|
||||
);
|
||||
|
||||
// A converted required validated path.
|
||||
$form['required_validate_route'] = array(
|
||||
'#type' => 'path',
|
||||
'#required' => TRUE,
|
||||
'#title' => 'required_validate_route',
|
||||
);
|
||||
|
||||
// A converted required validated path.
|
||||
$form['required_validate_url'] = array(
|
||||
'#type' => 'path',
|
||||
'#required' => TRUE,
|
||||
'#title' => 'required_validate_url',
|
||||
'#convert_path' => PathElement::CONVERT_URL,
|
||||
);
|
||||
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Submit'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {}
|
||||
|
||||
/**
|
||||
* Form validation handler.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {}
|
||||
|
||||
/**
|
||||
* Tests that default handlers are added even if custom are specified.
|
||||
*/
|
||||
public function testPathElement() {
|
||||
$form_state = (new FormState())
|
||||
->setValues([
|
||||
'required_validate' => 'user/' . $this->testUser->id(),
|
||||
'required_non_validate' => 'magic-ponies',
|
||||
'required_validate_route' => 'user/' . $this->testUser->id(),
|
||||
'required_validate_url' => 'user/' . $this->testUser->id(),
|
||||
]);
|
||||
$form_builder = $this->container->get('form_builder');
|
||||
$form_builder->submitForm($this, $form_state);
|
||||
|
||||
// Valid form state.
|
||||
$this->assertEqual(count($form_state->getErrors()), 0);
|
||||
$this->assertEqual($form_state->getValue('required_validate_route'), array(
|
||||
'route_name' => 'entity.user.canonical',
|
||||
'route_parameters' => array(
|
||||
'user' => $this->testUser->id(),
|
||||
),
|
||||
));
|
||||
/** @var \Drupal\Core\Url $url */
|
||||
$url = $form_state->getValue('required_validate_url');
|
||||
$this->assertTrue($url instanceof Url);
|
||||
$this->assertEqual($url->getRouteName(), 'entity.user.canonical');
|
||||
$this->assertEqual($url->getRouteParameters(), array(
|
||||
'user' => $this->testUser->id(),
|
||||
));
|
||||
|
||||
// Test #required.
|
||||
$form_state = (new FormState())
|
||||
->setValues([
|
||||
'required_non_validate' => 'magic-ponies',
|
||||
'required_validate_route' => 'user/' . $this->testUser->id(),
|
||||
'required_validate_url' => 'user/' . $this->testUser->id(),
|
||||
]);
|
||||
$form_builder->submitForm($this, $form_state);
|
||||
$errors = $form_state->getErrors();
|
||||
// Should be missing 'required_validate' field.
|
||||
$this->assertEqual(count($errors), 1);
|
||||
$this->assertEqual($errors, array('required_validate' => t('!name field is required.', array('!name' => 'required_validate'))));
|
||||
|
||||
// Test invalid parameters.
|
||||
$form_state = (new FormState())
|
||||
->setValues([
|
||||
'required_validate' => 'user/74',
|
||||
'required_non_validate' => 'magic-ponies',
|
||||
'required_validate_route' => 'user/74',
|
||||
'required_validate_url' => 'user/74',
|
||||
]);
|
||||
$form_builder = $this->container->get('form_builder');
|
||||
$form_builder->submitForm($this, $form_state);
|
||||
|
||||
// Valid form state.
|
||||
$errors = $form_state->getErrors();
|
||||
$this->assertEqual(count($errors), 3);
|
||||
$this->assertEqual($errors, array(
|
||||
'required_validate' => t('This path does not exist or you do not have permission to link to %path.', array('%path' => 'user/74')),
|
||||
'required_validate_route' => t('This path does not exist or you do not have permission to link to %path.', array('%path' => 'user/74')),
|
||||
'required_validate_url' => t('This path does not exist or you do not have permission to link to %path.', array('%path' => 'user/74')),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\BundleConstraintValidatorTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\system\Tests\TypedData;
|
||||
|
||||
/**
|
||||
* Tests validation constraints for BundleConstraintValidator.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class BundleConstraintValidatorTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The typed data manager to use.
|
||||
*
|
||||
* @var \Drupal\Core\TypedData\TypedDataManager
|
||||
*/
|
||||
protected $typedData;
|
||||
|
||||
public static $modules = array('node', 'field', 'text', 'user');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->typedData = $this->container->get('typed_data_manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests bundle constraint validation.
|
||||
*/
|
||||
public function testValidation() {
|
||||
// Test with multiple values.
|
||||
$this->assertValidation(array('foo', 'bar'));
|
||||
// Test with a single string value as well.
|
||||
$this->assertValidation('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the BundleConstraintValidator test for a given bundle.
|
||||
*
|
||||
* @param string|array $bundle
|
||||
* Bundle/bundles to use as constraint option.
|
||||
*/
|
||||
protected function assertValidation($bundle) {
|
||||
// Create a typed data definition with a Bundle constraint.
|
||||
$definition = DataDefinition::create('entity_reference')
|
||||
->addConstraint('Bundle', $bundle);
|
||||
|
||||
// Test the validation.
|
||||
$node = $this->container->get('entity.manager')->getStorage('node')->create(array('type' => 'foo'));
|
||||
|
||||
$typed_data = $this->typedData->create($definition, $node);
|
||||
$violations = $typed_data->validate();
|
||||
$this->assertEqual($violations->count(), 0, 'Validation passed for correct value.');
|
||||
|
||||
// Test the validation when an invalid value is passed.
|
||||
$page_node = $this->container->get('entity.manager')->getStorage('node')->create(array('type' => 'baz'));
|
||||
|
||||
$typed_data = $this->typedData->create($definition, $page_node);
|
||||
$violations = $typed_data->validate();
|
||||
$this->assertEqual($violations->count(), 1, 'Validation failed for incorrect value.');
|
||||
|
||||
// Make sure the information provided by a violation is correct.
|
||||
$violation = $violations[0];
|
||||
$this->assertEqual($violation->getMessage(), t('The entity must be of bundle %bundle.', array('%bundle' => implode(', ', (array) $bundle))), 'The message for invalid value is correct.');
|
||||
$this->assertEqual($violation->getRoot(), $typed_data, 'Violation root is correct.');
|
||||
$this->assertEqual($violation->getInvalidValue(), $page_node, 'The invalid value is set correctly in the violation.');
|
||||
}
|
||||
}
|
||||
236
core/modules/system/src/Tests/Entity/ConfigEntityImportTest.php
Normal file
236
core/modules/system/src/Tests/Entity/ConfigEntityImportTest.php
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\ConfigEntityImportTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests ConfigEntity importing.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class ConfigEntityImportTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('action', 'block', 'filter', 'image', 'search', 'search_extra_type');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs test methods for each module within a single test run.
|
||||
*/
|
||||
public function testConfigUpdateImport() {
|
||||
$this->doActionUpdate();
|
||||
$this->doBlockUpdate();
|
||||
$this->doFilterFormatUpdate();
|
||||
$this->doImageStyleUpdate();
|
||||
$this->doSearchPageUpdate();
|
||||
}
|
||||
/**
|
||||
* Tests updating a action during import.
|
||||
*/
|
||||
protected function doActionUpdate() {
|
||||
// Create a test action with a known label.
|
||||
$name = 'system.action.apple';
|
||||
$entity = entity_create('action', array(
|
||||
'id' => 'apple',
|
||||
'plugin' => 'action_message_action',
|
||||
));
|
||||
$entity->save();
|
||||
|
||||
$this->checkSinglePluginConfigSync($entity, 'configuration', 'message', '');
|
||||
|
||||
// Read the existing data, and prepare an altered version in staging.
|
||||
$custom_data = $original_data = $this->container->get('config.storage')->read($name);
|
||||
$custom_data['configuration']['message'] = 'Granny Smith';
|
||||
$this->assertConfigUpdateImport($name, $original_data, $custom_data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating a block during import.
|
||||
*/
|
||||
protected function doBlockUpdate() {
|
||||
// Create a test block with a known label.
|
||||
$name = 'block.block.apple';
|
||||
$block = $this->drupalPlaceBlock('system_powered_by_block', array(
|
||||
'id' => 'apple',
|
||||
'label' => 'Red Delicious',
|
||||
));
|
||||
|
||||
$this->checkSinglePluginConfigSync($block, 'settings', 'label', 'Red Delicious');
|
||||
|
||||
// Read the existing data, and prepare an altered version in staging.
|
||||
$custom_data = $original_data = $this->container->get('config.storage')->read($name);
|
||||
$custom_data['settings']['label'] = 'Granny Smith';
|
||||
$this->assertConfigUpdateImport($name, $original_data, $custom_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating a filter format during import.
|
||||
*/
|
||||
protected function doFilterFormatUpdate() {
|
||||
// Create a test filter format with a known label.
|
||||
$name = 'filter.format.plain_text';
|
||||
|
||||
/** @var $entity \Drupal\filter\Entity\FilterFormat */
|
||||
$entity = entity_load('filter_format', 'plain_text');
|
||||
$plugin_collection = $entity->getPluginCollections()['filters'];
|
||||
|
||||
$filters = $entity->get('filters');
|
||||
$this->assertIdentical(72, $filters['filter_url']['settings']['filter_url_length']);
|
||||
|
||||
$filters['filter_url']['settings']['filter_url_length'] = 100;
|
||||
$entity->set('filters', $filters);
|
||||
$entity->save();
|
||||
$this->assertIdentical($filters, $entity->get('filters'));
|
||||
$this->assertIdentical($filters, $plugin_collection->getConfiguration());
|
||||
|
||||
$filters['filter_url']['settings']['filter_url_length'] = -100;
|
||||
$entity->getPluginCollections()['filters']->setConfiguration($filters);
|
||||
$entity->save();
|
||||
$this->assertIdentical($filters, $entity->get('filters'));
|
||||
$this->assertIdentical($filters, $plugin_collection->getConfiguration());
|
||||
|
||||
// Read the existing data, and prepare an altered version in staging.
|
||||
$custom_data = $original_data = $this->container->get('config.storage')->read($name);
|
||||
$custom_data['filters']['filter_url']['settings']['filter_url_length'] = 100;
|
||||
$this->assertConfigUpdateImport($name, $original_data, $custom_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating an image style during import.
|
||||
*/
|
||||
protected function doImageStyleUpdate() {
|
||||
// Create a test image style with a known label.
|
||||
$name = 'image.style.thumbnail';
|
||||
|
||||
/** @var $entity \Drupal\image\Entity\ImageStyle */
|
||||
$entity = ImageStyle::load('thumbnail');
|
||||
$plugin_collection = $entity->getPluginCollections()['effects'];
|
||||
|
||||
$effects = $entity->get('effects');
|
||||
$effect_id = key($effects);
|
||||
$this->assertIdentical(100, $effects[$effect_id]['data']['height']);
|
||||
|
||||
$effects[$effect_id]['data']['height'] = 50;
|
||||
$entity->set('effects', $effects);
|
||||
$entity->save();
|
||||
// Ensure the entity and plugin have the correct configuration.
|
||||
$this->assertIdentical($effects, $entity->get('effects'));
|
||||
$this->assertIdentical($effects, $plugin_collection->getConfiguration());
|
||||
|
||||
$effects[$effect_id]['data']['height'] = -50;
|
||||
$entity->getPluginCollections()['effects']->setConfiguration($effects);
|
||||
$entity->save();
|
||||
// Ensure the entity and plugin have the correct configuration.
|
||||
$this->assertIdentical($effects, $entity->get('effects'));
|
||||
$this->assertIdentical($effects, $plugin_collection->getConfiguration());
|
||||
|
||||
// Read the existing data, and prepare an altered version in staging.
|
||||
$custom_data = $original_data = $this->container->get('config.storage')->read($name);
|
||||
$effect_name = key($original_data['effects']);
|
||||
|
||||
$custom_data['effects'][$effect_name]['data']['upscale'] = FALSE;
|
||||
$this->assertConfigUpdateImport($name, $original_data, $custom_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating a search page during import.
|
||||
*/
|
||||
protected function doSearchPageUpdate() {
|
||||
// Create a test search page with a known label.
|
||||
$name = 'search.page.apple';
|
||||
$entity = entity_create('search_page', array(
|
||||
'id' => 'apple',
|
||||
'plugin' => 'search_extra_type_search',
|
||||
));
|
||||
$entity->save();
|
||||
|
||||
$this->checkSinglePluginConfigSync($entity, 'configuration', 'boost', 'bi');
|
||||
|
||||
// Read the existing data, and prepare an altered version in staging.
|
||||
$custom_data = $original_data = $this->container->get('config.storage')->read($name);
|
||||
$custom_data['configuration']['boost'] = 'asdf';
|
||||
$this->assertConfigUpdateImport($name, $original_data, $custom_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a single set of plugin config stays in sync.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityWithPluginCollectionInterface $entity
|
||||
* The entity.
|
||||
* @param string $config_key
|
||||
* Where the plugin config is stored.
|
||||
* @param string $setting_key
|
||||
* The setting within the plugin config to change.
|
||||
* @param mixed $expected
|
||||
* The expected default value of the plugin config setting.
|
||||
*/
|
||||
protected function checkSinglePluginConfigSync(EntityWithPluginCollectionInterface $entity, $config_key, $setting_key, $expected) {
|
||||
$plugin_collection = $entity->getPluginCollections()[$config_key];
|
||||
$settings = $entity->get($config_key);
|
||||
|
||||
// Ensure the default config exists.
|
||||
$this->assertIdentical($expected, $settings[$setting_key]);
|
||||
|
||||
// Change the plugin config by setting it on the entity.
|
||||
$settings[$setting_key] = $this->randomString();
|
||||
$entity->set($config_key, $settings);
|
||||
$entity->save();
|
||||
$this->assertIdentical($settings, $entity->get($config_key));
|
||||
$this->assertIdentical($settings, $plugin_collection->getConfiguration());
|
||||
|
||||
// Change the plugin config by setting it on the plugin.
|
||||
$settings[$setting_key] = $this->randomString();
|
||||
$plugin_collection->setConfiguration($settings);
|
||||
$entity->save();
|
||||
$this->assertIdentical($settings, $entity->get($config_key));
|
||||
$this->assertIdentical($settings, $plugin_collection->getConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that config entities are updated during import.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the config object.
|
||||
* @param array $original_data
|
||||
* The original data stored in the config object.
|
||||
* @param array $custom_data
|
||||
* The new data to store in the config object.
|
||||
*/
|
||||
public function assertConfigUpdateImport($name, $original_data, $custom_data) {
|
||||
$this->container->get('config.storage.staging')->write($name, $custom_data);
|
||||
|
||||
// Verify the active configuration still returns the default values.
|
||||
$config = $this->config($name);
|
||||
$this->assertIdentical($config->get(), $original_data);
|
||||
|
||||
// Import.
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Verify the values were updated.
|
||||
$this->container->get('config.factory')->reset($name);
|
||||
$config = $this->config($name);
|
||||
$this->assertIdentical($config->get(), $custom_data);
|
||||
}
|
||||
|
||||
}
|
||||
601
core/modules/system/src/Tests/Entity/ConfigEntityQueryTest.php
Normal file
601
core/modules/system/src/Tests/Entity/ConfigEntityQueryTest.php
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\ConfigEntityQueryTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\Query\QueryFactory;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests Config Entity Query functionality.
|
||||
*
|
||||
* @group Entity
|
||||
* @see \Drupal\Core\Config\Entity\Query
|
||||
*/
|
||||
class ConfigEntityQueryTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $modules = array('config_test');
|
||||
|
||||
/**
|
||||
* Stores the search results for alter comparison.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $queryResults;
|
||||
|
||||
/**
|
||||
* The query factory used to construct all queries in the test.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* Stores all config entities created for the test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entities;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->entities = array();
|
||||
$this->factory = $this->container->get('entity.query');
|
||||
|
||||
// These two are here to make sure that matchArray needs to go over several
|
||||
// non-matches on every levels.
|
||||
$array['level1']['level2a'] = 9;
|
||||
$array['level1a']['level2'] = 9;
|
||||
// The tests match array.level1.level2.
|
||||
$array['level1']['level2'] = 1;
|
||||
$entity = entity_create('config_query_test', array(
|
||||
'label' => $this->randomMachineName(),
|
||||
'id' => '1',
|
||||
'number' => 31,
|
||||
'array' => $array,
|
||||
));
|
||||
$this->entities[] = $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
|
||||
$array['level1']['level2'] = 2;
|
||||
$entity = entity_create('config_query_test', array(
|
||||
'label' => $this->randomMachineName(),
|
||||
'id' => '2',
|
||||
'number' => 41,
|
||||
'array' => $array,
|
||||
));
|
||||
$this->entities[] = $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
|
||||
$array['level1']['level2'] = 1;
|
||||
$entity = entity_create('config_query_test', array(
|
||||
'label' => 'test_prefix_' . $this->randomMachineName(),
|
||||
'id' => '3',
|
||||
'number' => 59,
|
||||
'array' => $array,
|
||||
));
|
||||
$this->entities[] = $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
|
||||
$array['level1']['level2'] = 2;
|
||||
$entity = entity_create('config_query_test', array(
|
||||
'label' => $this->randomMachineName() . '_test_suffix',
|
||||
'id' => '4',
|
||||
'number' => 26,
|
||||
'array' => $array,
|
||||
));
|
||||
$this->entities[] = $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
|
||||
$array['level1']['level2'] = 3;
|
||||
$entity = entity_create('config_query_test', array(
|
||||
'label' => $this->randomMachineName() . '_TEST_contains_' . $this->randomMachineName(),
|
||||
'id' => '5',
|
||||
'number' => 53,
|
||||
'array' => $array,
|
||||
));
|
||||
$this->entities[] = $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic functionality.
|
||||
*/
|
||||
public function testConfigEntityQuery() {
|
||||
// Run a test without any condition.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2', '3', '4', '5'));
|
||||
// No conditions, OR.
|
||||
$this->queryResults = $this->factory->get('config_query_test', 'OR')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2', '3', '4', '5'));
|
||||
|
||||
// Filter by ID with equality.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '3')
|
||||
->execute();
|
||||
$this->assertResults(array('3'));
|
||||
|
||||
// Filter by label with a known prefix.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('label', 'test_prefix', 'STARTS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array('3'));
|
||||
|
||||
// Filter by label with a known suffix.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('label', 'test_suffix', 'ENDS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array('4'));
|
||||
|
||||
// Filter by label with a known containing word.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('label', 'test_contains', 'CONTAINS')
|
||||
->execute();
|
||||
$this->assertResults(array('5'));
|
||||
|
||||
// Filter by ID with the IN operator.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', array('2', '3'), 'IN')
|
||||
->execute();
|
||||
$this->assertResults(array('2', '3'));
|
||||
|
||||
// Filter by ID with the implicit IN operator.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', array('2', '3'))
|
||||
->execute();
|
||||
$this->assertResults(array('2', '3'));
|
||||
|
||||
// Filter by ID with the > operator.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '3', '>')
|
||||
->execute();
|
||||
$this->assertResults(array('4', '5'));
|
||||
|
||||
// Filter by ID with the >= operator.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '3', '>=')
|
||||
->execute();
|
||||
$this->assertResults(array('3', '4', '5'));
|
||||
|
||||
// Filter by ID with the <> operator.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '3', '<>')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2', '4', '5'));
|
||||
|
||||
// Filter by ID with the < operator.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '3', '<')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2'));
|
||||
|
||||
// Filter by ID with the <= operator.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '3', '<=')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2', '3'));
|
||||
|
||||
// Filter by two conditions on the same field.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('label', 'test_pref', 'STARTS_WITH')
|
||||
->condition('label', 'test_prefix', 'STARTS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array('3'));
|
||||
|
||||
// Filter by two conditions on different fields. The first query matches for
|
||||
// a different ID, so the result is empty.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('label', 'test_prefix', 'STARTS_WITH')
|
||||
->condition('id', '5')
|
||||
->execute();
|
||||
$this->assertResults(array());
|
||||
|
||||
// Filter by two different conditions on different fields. This time the
|
||||
// first condition matches on one item, but the second one does as well.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('label', 'test_prefix', 'STARTS_WITH')
|
||||
->condition('id', '3')
|
||||
->execute();
|
||||
$this->assertResults(array('3'));
|
||||
|
||||
// Filter by two different conditions, of which the first one matches for
|
||||
// every entry, the second one as well, but just the third one filters so
|
||||
// that just two are left.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '1', '>=')
|
||||
->condition('number', 10, '>=')
|
||||
->condition('number', 50, '>=')
|
||||
->execute();
|
||||
$this->assertResults(array('3', '5'));
|
||||
|
||||
// Filter with an OR condition group.
|
||||
$this->queryResults = $this->factory->get('config_query_test', 'OR')
|
||||
->condition('id', 1)
|
||||
->condition('id', '2')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2'));
|
||||
|
||||
// Simplify it with IN.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', array('1', '2'))
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2'));
|
||||
// Try explicit IN.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', array('1', '2'), 'IN')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2'));
|
||||
// Try not IN.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', array('1', '2'), 'NOT IN')
|
||||
->execute();
|
||||
$this->assertResults(array('3', '4', '5'));
|
||||
|
||||
// Filter with an OR condition group on different fields.
|
||||
$this->queryResults = $this->factory->get('config_query_test', 'OR')
|
||||
->condition('id', 1)
|
||||
->condition('number', 41)
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2'));
|
||||
|
||||
// Filter with an OR condition group on different fields but matching on the
|
||||
// same entity.
|
||||
$this->queryResults = $this->factory->get('config_query_test', 'OR')
|
||||
->condition('id', 1)
|
||||
->condition('number', 31)
|
||||
->execute();
|
||||
$this->assertResults(array('1'));
|
||||
|
||||
// NO simple conditions, YES complex conditions, 'AND'.
|
||||
$query = $this->factory->get('config_query_test', 'AND');
|
||||
$and_condition_1 = $query->orConditionGroup()
|
||||
->condition('id', '2')
|
||||
->condition('label', $this->entities[0]->label);
|
||||
$and_condition_2 = $query->orConditionGroup()
|
||||
->condition('id', 1)
|
||||
->condition('label', $this->entities[3]->label);
|
||||
$this->queryResults = $query
|
||||
->condition($and_condition_1)
|
||||
->condition($and_condition_2)
|
||||
->execute();
|
||||
$this->assertResults(array('1'));
|
||||
|
||||
// NO simple conditions, YES complex conditions, 'OR'.
|
||||
$query = $this->factory->get('config_query_test', 'OR');
|
||||
$and_condition_1 = $query->andConditionGroup()
|
||||
->condition('id', 1)
|
||||
->condition('label', $this->entities[0]->label);
|
||||
$and_condition_2 = $query->andConditionGroup()
|
||||
->condition('id', '2')
|
||||
->condition('label', $this->entities[1]->label);
|
||||
$this->queryResults = $query
|
||||
->condition($and_condition_1)
|
||||
->condition($and_condition_2)
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2'));
|
||||
|
||||
// YES simple conditions, YES complex conditions, 'AND'.
|
||||
$query = $this->factory->get('config_query_test', 'AND');
|
||||
$and_condition_1 = $query->orConditionGroup()
|
||||
->condition('id', '2')
|
||||
->condition('label', $this->entities[0]->label);
|
||||
$and_condition_2 = $query->orConditionGroup()
|
||||
->condition('id', 1)
|
||||
->condition('label', $this->entities[3]->label);
|
||||
$this->queryResults = $query
|
||||
->condition('number', 31)
|
||||
->condition($and_condition_1)
|
||||
->condition($and_condition_2)
|
||||
->execute();
|
||||
$this->assertResults(array('1'));
|
||||
|
||||
// YES simple conditions, YES complex conditions, 'OR'.
|
||||
$query = $this->factory->get('config_query_test', 'OR');
|
||||
$and_condition_1 = $query->orConditionGroup()
|
||||
->condition('id', '2')
|
||||
->condition('label', $this->entities[0]->label);
|
||||
$and_condition_2 = $query->orConditionGroup()
|
||||
->condition('id', 1)
|
||||
->condition('label', $this->entities[3]->label);
|
||||
$this->queryResults = $query
|
||||
->condition('number', 53)
|
||||
->condition($and_condition_1)
|
||||
->condition($and_condition_2)
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2', '4', '5'));
|
||||
|
||||
// Test the exists and notExists conditions.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->exists('id')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2', '3', '4', '5'));
|
||||
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->exists('non-existent')
|
||||
->execute();
|
||||
$this->assertResults(array());
|
||||
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->notExists('id')
|
||||
->execute();
|
||||
$this->assertResults(array());
|
||||
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->notExists('non-existent')
|
||||
->execute();
|
||||
$this->assertResults(array('1', '2', '3', '4', '5'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ID conditions.
|
||||
*/
|
||||
public function testStringIdConditions() {
|
||||
// We need an entity with a non-numeric ID.
|
||||
$entity = entity_create('config_query_test', array(
|
||||
'label' => $this->randomMachineName(),
|
||||
'id' => 'foo.bar',
|
||||
));
|
||||
$this->entities[] = $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
|
||||
// Test 'STARTS_WITH' condition.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'foo.bar', 'STARTS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array('foo.bar'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'f', 'STARTS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array('foo.bar'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'miss', 'STARTS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array());
|
||||
|
||||
// Test 'CONTAINS' condition.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'foo.bar', 'CONTAINS')
|
||||
->execute();
|
||||
$this->assertResults(array('foo.bar'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'oo.ba', 'CONTAINS')
|
||||
->execute();
|
||||
$this->assertResults(array('foo.bar'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'miss', 'CONTAINS')
|
||||
->execute();
|
||||
$this->assertResults(array());
|
||||
|
||||
// Test 'ENDS_WITH' condition.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'foo.bar', 'ENDS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array('foo.bar'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'r', 'ENDS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array('foo.bar'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', 'miss', 'ENDS_WITH')
|
||||
->execute();
|
||||
$this->assertResults(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests count query.
|
||||
*/
|
||||
public function testCount() {
|
||||
// Test count on no conditions.
|
||||
$count = $this->factory->get('config_query_test')
|
||||
->count()
|
||||
->execute();
|
||||
$this->assertIdentical($count, count($this->entities));
|
||||
|
||||
// Test count on a complex query.
|
||||
$query = $this->factory->get('config_query_test', 'OR');
|
||||
$and_condition_1 = $query->andConditionGroup()
|
||||
->condition('id', 1)
|
||||
->condition('label', $this->entities[0]->label);
|
||||
$and_condition_2 = $query->andConditionGroup()
|
||||
->condition('id', '2')
|
||||
->condition('label', $this->entities[1]->label);
|
||||
$count = $query
|
||||
->condition($and_condition_1)
|
||||
->condition($and_condition_2)
|
||||
->count()
|
||||
->execute();
|
||||
$this->assertIdentical($count, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests sorting and range on config entity queries.
|
||||
*/
|
||||
public function testSortRange() {
|
||||
// Sort by simple ascending/descending.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->sort('number', 'DESC')
|
||||
->execute();
|
||||
$this->assertIdentical(array_values($this->queryResults), array('3', '5', '2', '1', '4'));
|
||||
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->sort('number', 'ASC')
|
||||
->execute();
|
||||
$this->assertIdentical(array_values($this->queryResults), array('4', '1', '2', '5', '3'));
|
||||
|
||||
// Apply some filters and sort.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '3', '>')
|
||||
->sort('number', 'DESC')
|
||||
->execute();
|
||||
$this->assertIdentical(array_values($this->queryResults), array('5', '4'));
|
||||
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('id', '3', '>')
|
||||
->sort('number', 'ASC')
|
||||
->execute();
|
||||
$this->assertIdentical(array_values($this->queryResults), array('4', '5'));
|
||||
|
||||
// Apply a pager and sort.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->sort('number', 'DESC')
|
||||
->range('2', '2')
|
||||
->execute();
|
||||
$this->assertIdentical(array_values($this->queryResults), array('2', '1'));
|
||||
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->sort('number', 'ASC')
|
||||
->range('2', '2')
|
||||
->execute();
|
||||
$this->assertIdentical(array_values($this->queryResults), array('2', '5'));
|
||||
|
||||
// Add a range to a query without a start parameter.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->range(0, '3')
|
||||
->sort('id', 'ASC')
|
||||
->execute();
|
||||
$this->assertIdentical(array_values($this->queryResults), array('1', '2', '3'));
|
||||
|
||||
// Apply a pager with limit 4.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->pager('4', 0)
|
||||
->sort('id', 'ASC')
|
||||
->execute();
|
||||
$this->assertIdentical(array_values($this->queryResults), array('1', '2', '3', '4'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests dotted path matching.
|
||||
*/
|
||||
public function testDotted() {
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('array.level1.*', 1)
|
||||
->execute();
|
||||
$this->assertResults(array('1', '3'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('*.level1.level2', 2)
|
||||
->execute();
|
||||
$this->assertResults(array('2', '4'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('array.level1.*', 3)
|
||||
->execute();
|
||||
$this->assertResults(array('5'));
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('array.level1.level2', 3)
|
||||
->execute();
|
||||
$this->assertResults(array('5'));
|
||||
// Make sure that values on the wildcard level do not match if if there are
|
||||
// sub-keys defined. This must not find anything even if entity 2 has a
|
||||
// top-level key number with value 41.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('*.level1.level2', 41)
|
||||
->execute();
|
||||
$this->assertResults(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests case sensitivity.
|
||||
*/
|
||||
public function testCaseSensitivity() {
|
||||
// Filter by label with a known containing case-sensitive word.
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('label', 'TEST', 'CONTAINS')
|
||||
->execute();
|
||||
$this->assertResults(array('3', '4', '5'));
|
||||
|
||||
$this->queryResults = $this->factory->get('config_query_test')
|
||||
->condition('label', 'test', 'CONTAINS')
|
||||
->execute();
|
||||
$this->assertResults(array('3', '4', '5'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests lookup keys are added to the key value store.
|
||||
*/
|
||||
public function testLookupKeys() {
|
||||
\Drupal::service('state')->set('config_test.lookup_keys', TRUE);
|
||||
\Drupal::entityManager()->clearCachedDefinitions();
|
||||
$key_value = $this->container->get('keyvalue')->get(QueryFactory::CONFIG_LOOKUP_PREFIX . 'config_test');
|
||||
|
||||
$test_entities = [];
|
||||
$entity = entity_create('config_test', array(
|
||||
'label' => $this->randomMachineName(),
|
||||
'id' => '1',
|
||||
'style' => 'test',
|
||||
));
|
||||
$test_entities[$entity->getConfigDependencyName()] = $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
|
||||
|
||||
$expected[] = $entity->getConfigDependencyName();
|
||||
$this->assertEqual($expected, $key_value->get('style:test'));
|
||||
|
||||
$entity = entity_create('config_test', array(
|
||||
'label' => $this->randomMachineName(),
|
||||
'id' => '2',
|
||||
'style' => 'test',
|
||||
));
|
||||
$test_entities[$entity->getConfigDependencyName()] = $entity;
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
$expected[] = $entity->getConfigDependencyName();
|
||||
$this->assertEqual($expected, $key_value->get('style:test'));
|
||||
|
||||
$entity = entity_create('config_test', array(
|
||||
'label' => $this->randomMachineName(),
|
||||
'id' => '3',
|
||||
'style' => 'blah',
|
||||
));
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
// Do not add this entity to the list of expected result as it has a
|
||||
// different value.
|
||||
$this->assertEqual($expected, $key_value->get('style:test'));
|
||||
$this->assertEqual([$entity->getConfigDependencyName()], $key_value->get('style:blah'));
|
||||
|
||||
// Ensure that a delete clears a key.
|
||||
$entity->delete();
|
||||
$this->assertEqual([], $key_value->get('style:blah'));
|
||||
|
||||
// Ensure that delete only clears one key.
|
||||
$entity_id = array_pop($expected);
|
||||
$test_entities[$entity_id]->delete();
|
||||
$this->assertEqual($expected, $key_value->get('style:test'));
|
||||
$entity_id = array_pop($expected);
|
||||
$test_entities[$entity_id]->delete();
|
||||
$this->assertEqual($expected, $key_value->get('style:test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the results as expected regardless of order.
|
||||
*
|
||||
* @param array $expected
|
||||
* Array of expected entity IDs.
|
||||
*/
|
||||
protected function assertResults($expected) {
|
||||
$this->assertIdentical(count($this->queryResults), count($expected));
|
||||
foreach ($expected as $value) {
|
||||
// This also tests whether $this->queryResults[$value] is even set at all.
|
||||
$this->assertIdentical($this->queryResults[$value], $value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,545 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\ContentEntityChangedTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestMulChanged;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevChanged;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests basic EntityChangedInterface functionality.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class ContentEntityChangedTest extends EntityUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['language', 'user', 'system', 'field', 'text', 'filter', 'entity_test'];
|
||||
|
||||
/**
|
||||
* The EntityTestMulChanged entity type storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $mulChangedStorage;
|
||||
|
||||
/**
|
||||
* The EntityTestMulRevChanged entity type storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $mulRevChangedStorage;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Enable an additional language.
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
|
||||
$this->installEntitySchema('entity_test_mul_changed');
|
||||
$this->installEntitySchema('entity_test_mulrev_changed');
|
||||
|
||||
$this->mulChangedStorage = $this->entityManager->getStorage('entity_test_mul_changed');
|
||||
$this->mulRevChangedStorage = $this->entityManager->getStorage('entity_test_mulrev_changed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic EntityChangedInterface functionality.
|
||||
*/
|
||||
public function testChanged() {
|
||||
$user1 = $this->createUser();
|
||||
$user2 = $this->createUser();
|
||||
|
||||
// Create a test entity.
|
||||
$entity = EntityTestMulChanged::create(array(
|
||||
'name' => $this->randomString(),
|
||||
'user_id' => $user1->id(),
|
||||
'language' => 'en',
|
||||
));
|
||||
$entity->save();
|
||||
|
||||
$this->assertTrue(
|
||||
$entity->getChangedTime() >= REQUEST_TIME,
|
||||
'Changed time of original language is valid.'
|
||||
);
|
||||
|
||||
// We can't assert equality here because the created time is set to the
|
||||
// request time, while instances of ChangedTestItem use the current
|
||||
// timestamp every time. Therefor we check if the changed timestamp is
|
||||
// between the created time and now.
|
||||
$this->assertTrue(
|
||||
($entity->getChangedTime() >= $entity->get('created')->value) &&
|
||||
(($entity->getChangedTime() - $entity->get('created')->value) <= time() - REQUEST_TIME),
|
||||
'Changed and created time of original language can be assumed to be identical.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
|
||||
'Changed time of original language is the same as changed time across all translations.'
|
||||
);
|
||||
|
||||
$changed_en = $entity->getChangedTime();
|
||||
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestMulRevChanged $german */
|
||||
$german = $entity->addTranslation('de');
|
||||
|
||||
$entity->save();
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $changed_en,
|
||||
'Changed time of original language did not change.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$german->getChangedTime() > $entity->getChangedTime(),
|
||||
'Changed time of the German translation is newer then the original language.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
|
||||
'Changed time of the German translation is the newest time across all translations.'
|
||||
);
|
||||
|
||||
$changed_de = $german->getChangedTime();
|
||||
|
||||
$entity->save();
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $changed_en,
|
||||
'Changed time of original language did not change.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$german->getChangedTime(), $changed_de,
|
||||
'Changed time of the German translation did not change.'
|
||||
);
|
||||
|
||||
$entity->setOwner($user2);
|
||||
|
||||
$entity->save();
|
||||
|
||||
$this->assertTrue(
|
||||
$entity->getChangedTime() > $changed_en,
|
||||
'Changed time of original language did change.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$german->getChangedTime(), $changed_de,
|
||||
'Changed time of the German translation did not change.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$entity->getChangedTime() > $german->getChangedTime(),
|
||||
'Changed time of original language is newer then the German translation.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
|
||||
'Changed time of the original language is the newest time across all translations.'
|
||||
);
|
||||
|
||||
$changed_en = $entity->getChangedTime();
|
||||
|
||||
// Save entity without any changes.
|
||||
$entity->save();
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $changed_en,
|
||||
'Changed time of original language did not change.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$german->getChangedTime(), $changed_de,
|
||||
'Changed time of the German translation did not change.'
|
||||
);
|
||||
|
||||
// At this point the changed time of the original language (en) is newer
|
||||
// than the changed time of the German translation. Now test that entity
|
||||
// queries work as expected.
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_en)->execute();
|
||||
|
||||
$this->assertEqual(
|
||||
reset($ids), $entity->id(),
|
||||
'Entity query can access changed time of original language.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_en, '=', 'en')->execute();
|
||||
|
||||
$this->assertEqual(
|
||||
reset($ids), $entity->id(),
|
||||
'Entity query can access changed time of original language by setting the original language as condition.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_de, '=', 'en')->execute();
|
||||
|
||||
$this->assertFalse(
|
||||
$ids,
|
||||
'There\'s no original entity stored having the changed time of the German translation.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_en)->condition('default_langcode', '1')->execute();
|
||||
|
||||
$this->assertEqual(
|
||||
reset($ids), $entity->id(),
|
||||
'Entity query can access changed time of default language.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_de)->condition('default_langcode', '1')->execute();
|
||||
|
||||
$this->assertFalse(
|
||||
$ids,
|
||||
'There\'s no entity stored using the default language having the changed time of the German translation.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_de)->execute();
|
||||
|
||||
$this->assertEqual(
|
||||
reset($ids), $entity->id(),
|
||||
'Entity query can access changed time of the German translation.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_de, '=', 'de')->execute();
|
||||
|
||||
$this->assertEqual(
|
||||
reset($ids), $entity->id(),
|
||||
'Entity query can access changed time of the German translation.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_en, '=', 'de')->execute();
|
||||
|
||||
$this->assertFalse(
|
||||
$ids,
|
||||
'There\'s no German translation stored having the changed time of the original language.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_de, '>')->execute();
|
||||
|
||||
$this->assertEqual(
|
||||
reset($ids), $entity->id(),
|
||||
'Entity query can access changed time regardless of translation.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_en, '<')->execute();
|
||||
|
||||
$this->assertEqual(
|
||||
reset($ids), $entity->id(),
|
||||
'Entity query can access changed time regardless of translation.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', 0, '>')->execute();
|
||||
|
||||
$this->assertEqual(
|
||||
reset($ids), $entity->id(),
|
||||
'Entity query can access changed time regardless of translation.'
|
||||
);
|
||||
|
||||
$query = $this->mulChangedStorage->getQuery();
|
||||
$ids = $query->condition('changed', $changed_en, '>')->execute();
|
||||
|
||||
$this->assertFalse(
|
||||
$ids,
|
||||
'Entity query can access changed time regardless of translation.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests revisionable EntityChangedInterface functionality.
|
||||
*/
|
||||
public function testRevisionChanged() {
|
||||
$user1 = $this->createUser();
|
||||
$user2 = $this->createUser();
|
||||
|
||||
// Create a test entity.
|
||||
$entity = EntityTestMulRevChanged::create(array(
|
||||
'name' => $this->randomString(),
|
||||
'user_id' => $user1->id(),
|
||||
'language' => 'en',
|
||||
));
|
||||
$entity->save();
|
||||
|
||||
$this->assertTrue(
|
||||
$entity->getChangedTime() >= REQUEST_TIME,
|
||||
'Changed time of original language is valid.'
|
||||
);
|
||||
|
||||
// We can't assert equality here because the created time is set to the
|
||||
// request time while instances of ChangedTestItem use the current
|
||||
// timestamp every time.
|
||||
$this->assertTrue(
|
||||
($entity->getChangedTime() >= $entity->get('created')->value) &&
|
||||
(($entity->getChangedTime() - $entity->get('created')->value) <= time() - REQUEST_TIME),
|
||||
'Changed and created time of original language can be assumed to be identical.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
|
||||
'Changed time of original language is the same as changed time across all translations.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$this->getRevisionTranslationAffectedFlag($entity),
|
||||
'Changed flag of original language is set for a new entity.'
|
||||
);
|
||||
|
||||
$changed_en = $entity->getChangedTime();
|
||||
|
||||
$entity->setNewRevision();
|
||||
// Save entity without any changes but create new revision.
|
||||
$entity->save();
|
||||
// A new revision without any changes should not set a new changed time.
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $changed_en,
|
||||
'Changed time of original language did not change.'
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($entity),
|
||||
'Changed flag of original language is not set for new revision without changes.'
|
||||
);
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->setOwner($user2);
|
||||
$entity->save();
|
||||
|
||||
$this->assertTrue(
|
||||
$entity->getChangedTime() > $changed_en,
|
||||
'Changed time of original language has been updated by new revision.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$this->getRevisionTranslationAffectedFlag($entity),
|
||||
'Changed flag of original language is set for new revision with changes.'
|
||||
);
|
||||
|
||||
$changed_en = $entity->getChangedTime();
|
||||
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestMulRevChanged $german */
|
||||
$german = $entity->addTranslation('de');
|
||||
|
||||
$entity->save();
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $changed_en,
|
||||
'Changed time of original language did not change.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$german->getChangedTime() > $entity->getChangedTime(),
|
||||
'Changed time of the German translation is newer then the original language.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
|
||||
'Changed time of the German translation is the newest time across all translations.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$this->getRevisionTranslationAffectedFlag($entity),
|
||||
'Changed flag of original language is not reset by adding a new translation.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$this->getRevisionTranslationAffectedFlag($german),
|
||||
'Changed flag of German translation is set when adding the translation.'
|
||||
);
|
||||
|
||||
$changed_de = $german->getChangedTime();
|
||||
|
||||
$entity->setNewRevision();
|
||||
// Save entity without any changes but create new revision.
|
||||
$entity->save();
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $changed_en,
|
||||
'Changed time of original language did not change.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$german->getChangedTime(), $changed_de,
|
||||
'Changed time of the German translation did not change.'
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($entity),
|
||||
'Changed flag of original language is not set for new revision without changes.'
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($german),
|
||||
'Changed flag of of the German translation is not set for new revision without changes.'
|
||||
);
|
||||
|
||||
$entity->setNewRevision();
|
||||
$german->setOwner($user2);
|
||||
$entity->save();
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $changed_en,
|
||||
'Changed time of original language did not change.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$german->getChangedTime() > $changed_de,
|
||||
'Changed time of the German translation did change.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
|
||||
'Changed time of the German translation is the newest time across all translations.'
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($entity),
|
||||
'Changed flag of original language is not set when changing the German Translation.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$this->getRevisionTranslationAffectedFlag($german),
|
||||
'Changed flag of German translation is set when changing the German translation.'
|
||||
);
|
||||
|
||||
$french = $entity->getTranslation('fr');
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->assertEqual(
|
||||
$entity->getChangedTime(), $changed_en,
|
||||
'Changed time of original language did not change.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$french->getChangedTime() > $entity->getChangedTime(),
|
||||
'Changed time of the French translation is newer then the original language.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$french->getChangedTime() > $entity->getChangedTime(),
|
||||
'Changed time of the French translation is newer then the German translation.'
|
||||
);
|
||||
|
||||
$this->assertEqual(
|
||||
$french->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
|
||||
'Changed time of the French translation is the newest time across all translations.'
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($entity),
|
||||
'Changed flag of original language is reset by adding a new translation and a new revision.'
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($german),
|
||||
'Changed flag of German translation is reset by adding a new translation and a new revision.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$this->getRevisionTranslationAffectedFlag($french),
|
||||
'Changed flag of French translation is set when adding the translation and a new revision.'
|
||||
);
|
||||
|
||||
$entity->removeTranslation('fr');
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
// This block simulates exactly the flow of a node form submission of a new
|
||||
// translation and a new revision.
|
||||
$form_entity_builder_entity = EntityTestMulRevChanged::load($entity->id());
|
||||
// ContentTranslationController::prepareTranslation().
|
||||
$form_entity_builder_entity = $form_entity_builder_entity->addTranslation('fr', $form_entity_builder_entity->toArray());
|
||||
// EntityForm::buildEntity() during form submit.
|
||||
$form_entity_builder_clone = clone $form_entity_builder_entity;
|
||||
// NodeForm::submitForm().
|
||||
$form_entity_builder_clone->setNewRevision();
|
||||
// EntityForm::save().
|
||||
$form_entity_builder_clone->save();
|
||||
|
||||
// The assertion fails unless https://www.drupal.org/node/2513094 is
|
||||
// committed.
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($entity),
|
||||
'Changed flag of original language is reset by adding a new translation and a new revision.'
|
||||
);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($german),
|
||||
'Changed flag of German translation is reset by adding a new translation and a new revision.'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$this->getRevisionTranslationAffectedFlag($french),
|
||||
'Changed flag of French translation is set when adding the translation and a new revision.'
|
||||
);
|
||||
|
||||
$german->setOwner($user1);
|
||||
$german->setRevisionTranslationAffected(FALSE);
|
||||
$entity->save();
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($german),
|
||||
'German translation changed but the changed flag is reset manually.'
|
||||
);
|
||||
|
||||
$entity->setNewRevision();
|
||||
$german->setRevisionTranslationAffected(TRUE);
|
||||
$entity->save();
|
||||
|
||||
$this->assertTrue(
|
||||
$this->getRevisionTranslationAffectedFlag($german),
|
||||
'German translation is not changed and a new revision is created but the changed flag is set manually.'
|
||||
);
|
||||
|
||||
$german->setOwner($user2);
|
||||
$entity->setNewRevision();
|
||||
$german->setRevisionTranslationAffected(FALSE);
|
||||
$entity->save();
|
||||
|
||||
$this->assertFalse(
|
||||
$this->getRevisionTranslationAffectedFlag($german),
|
||||
'German translation changed and a new revision is created but the changed flag is reset manually.'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the revision translation affected flag value.
|
||||
*
|
||||
* @param \Drupal\entity_test\Entity\EntityTestMulRevChanged $entity
|
||||
* The entity object to be checked.
|
||||
*
|
||||
* @return bool
|
||||
* The flag value.
|
||||
*/
|
||||
protected function getRevisionTranslationAffectedFlag(EntityTestMulRevChanged $entity) {
|
||||
$query = $this->mulRevChangedStorage->getQuery();
|
||||
$ids = $query->condition('revision_translation_affected', 1, '=', $entity->language()->getId())->execute();
|
||||
$id = reset($ids);
|
||||
return (bool) ($id == $entity->id());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\ContentEntityNullStorageTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\contact\Entity\ContactForm;
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\StorageComparer;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests ContentEntityNullStorage entity query support.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\ContentEntityNullStorage
|
||||
* @see \Drupal\Core\Entity\Query\Null\Query
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class ContentEntityNullStorageTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'contact', 'user');
|
||||
|
||||
/**
|
||||
* Tests using entity query with ContentEntityNullStorage.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Query\Null\Query
|
||||
*/
|
||||
public function testEntityQuery() {
|
||||
$this->assertIdentical(0, \Drupal::entityQuery('contact_message')->count()->execute(), 'Counting a null storage returns 0.');
|
||||
$this->assertIdentical([], \Drupal::entityQuery('contact_message')->execute(), 'Querying a null storage returns an empty array.');
|
||||
$this->assertIdentical([], \Drupal::entityQuery('contact_message')->condition('contact_form', 'test')->execute(), 'Querying a null storage returns an empty array and conditions are ignored.');
|
||||
$this->assertIdentical([], \Drupal::entityQueryAggregate('contact_message')->aggregate('name', 'AVG')->execute(), 'Aggregate querying a null storage returns an empty array');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting a contact form entity via a configuration import.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Event\BundleConfigImportValidate
|
||||
*/
|
||||
public function testDeleteThroughImport() {
|
||||
$contact_form = ContactForm::create(['id' => 'test']);
|
||||
$contact_form->save();
|
||||
|
||||
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
|
||||
|
||||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.staging'),
|
||||
$this->container->get('config.storage'),
|
||||
$this->container->get('config.manager')
|
||||
);
|
||||
$config_importer = new ConfigImporter(
|
||||
$storage_comparer->createChangelist(),
|
||||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('config.manager'),
|
||||
$this->container->get('lock'),
|
||||
$this->container->get('config.typed'),
|
||||
$this->container->get('module_handler'),
|
||||
$this->container->get('module_installer'),
|
||||
$this->container->get('theme_handler'),
|
||||
$this->container->get('string_translation')
|
||||
);
|
||||
|
||||
// Delete the contact message in staging.
|
||||
$staging = $this->container->get('config.storage.staging');
|
||||
$staging->delete($contact_form->getConfigDependencyName());
|
||||
|
||||
// Import.
|
||||
$config_importer->reset()->import();
|
||||
$this->assertNull(ContactForm::load($contact_form->id()), 'The contact form has been deleted.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\Element\EntityAutocompleteElementFormTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity\Element;
|
||||
|
||||
use Drupal\Core\Entity\Element\EntityAutocomplete;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\system\Tests\Entity\EntityUnitTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Tests the EntityAutocomplete Form API element.
|
||||
*
|
||||
* @group Form
|
||||
*/
|
||||
class EntityAutocompleteElementFormTest extends EntityUnitTestBase implements FormInterface {
|
||||
|
||||
/**
|
||||
* User for testing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $testUser;
|
||||
|
||||
/**
|
||||
* User for autocreate testing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $testAutocreateUser;
|
||||
|
||||
/**
|
||||
* An array of entities to be referenced in this test.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface[]
|
||||
*/
|
||||
protected $referencedEntities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installSchema('system', ['router', 'key_value_expire']);
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
|
||||
$this->testUser = User::create(array(
|
||||
'name' => 'foobar1',
|
||||
'mail' => 'foobar1@example.com',
|
||||
));
|
||||
$this->testUser->save();
|
||||
\Drupal::service('current_user')->setAccount($this->testUser);
|
||||
|
||||
$this->testAutocreateUser = User::create(array(
|
||||
'name' => 'foobar2',
|
||||
'mail' => 'foobar2@example.com',
|
||||
));
|
||||
$this->testAutocreateUser->save();
|
||||
|
||||
for ($i = 1; $i < 3; $i++) {
|
||||
$entity = EntityTest::create(array(
|
||||
'name' => $this->randomMachineName()
|
||||
));
|
||||
$entity->save();
|
||||
$this->referencedEntities[] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'test_entity_autocomplete';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['single'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
);
|
||||
$form['single_autocreate'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#autocreate' => array(
|
||||
'bundle' => 'entity_test',
|
||||
),
|
||||
);
|
||||
$form['single_autocreate_specific_uid'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#autocreate' => array(
|
||||
'bundle' => 'entity_test',
|
||||
'uid' => $this->testAutocreateUser->id(),
|
||||
),
|
||||
);
|
||||
|
||||
$form['tags'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#tags' => TRUE,
|
||||
);
|
||||
$form['tags_autocreate'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#tags' => TRUE,
|
||||
'#autocreate' => array(
|
||||
'bundle' => 'entity_test',
|
||||
),
|
||||
);
|
||||
$form['tags_autocreate_specific_uid'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#tags' => TRUE,
|
||||
'#autocreate' => array(
|
||||
'bundle' => 'entity_test',
|
||||
'uid' => $this->testAutocreateUser->id(),
|
||||
),
|
||||
);
|
||||
|
||||
$form['single_no_validate'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#validate_reference' => FALSE,
|
||||
);
|
||||
$form['single_autocreate_no_validate'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#autocreate' => array(
|
||||
'bundle' => 'entity_test',
|
||||
),
|
||||
);
|
||||
|
||||
$form['single_access'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#default_value' => $this->referencedEntities[0],
|
||||
);
|
||||
$form['tags_access'] = array(
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => 'entity_test',
|
||||
'#tags' => TRUE,
|
||||
'#default_value' => array($this->referencedEntities[0], $this->referencedEntities[1]),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) { }
|
||||
|
||||
/**
|
||||
* Tests valid entries in the EntityAutocomplete Form API element.
|
||||
*/
|
||||
public function testValidEntityAutocompleteElement() {
|
||||
$form_state = (new FormState())
|
||||
->setValues([
|
||||
'single' => $this->getAutocompleteInput($this->referencedEntities[0]),
|
||||
'single_autocreate' => 'single - autocreated entity label',
|
||||
'single_autocreate_specific_uid' => 'single - autocreated entity label with specific uid',
|
||||
'tags' => $this->getAutocompleteInput($this->referencedEntities[0]) . ', ' . $this->getAutocompleteInput($this->referencedEntities[1]),
|
||||
'tags_autocreate' =>
|
||||
$this->getAutocompleteInput($this->referencedEntities[0])
|
||||
. ', tags - autocreated entity label, '
|
||||
. $this->getAutocompleteInput($this->referencedEntities[1]),
|
||||
'tags_autocreate_specific_uid' =>
|
||||
$this->getAutocompleteInput($this->referencedEntities[0])
|
||||
. ', tags - autocreated entity label with specific uid, '
|
||||
. $this->getAutocompleteInput($this->referencedEntities[1]),
|
||||
]);
|
||||
$form_builder = $this->container->get('form_builder');
|
||||
$form_builder->submitForm($this, $form_state);
|
||||
|
||||
// Valid form state.
|
||||
$this->assertEqual(count($form_state->getErrors()), 0);
|
||||
|
||||
// Test the 'single' element.
|
||||
$this->assertEqual($form_state->getValue('single'), $this->referencedEntities[0]->id());
|
||||
|
||||
// Test the 'single_autocreate' element.
|
||||
$value = $form_state->getValue('single_autocreate');
|
||||
$this->assertEqual($value['entity']->label(), 'single - autocreated entity label');
|
||||
$this->assertEqual($value['entity']->bundle(), 'entity_test');
|
||||
$this->assertEqual($value['entity']->getOwnerId(), $this->testUser->id());
|
||||
|
||||
// Test the 'single_autocreate_specific_uid' element.
|
||||
$value = $form_state->getValue('single_autocreate_specific_uid');
|
||||
$this->assertEqual($value['entity']->label(), 'single - autocreated entity label with specific uid');
|
||||
$this->assertEqual($value['entity']->bundle(), 'entity_test');
|
||||
$this->assertEqual($value['entity']->getOwnerId(), $this->testAutocreateUser->id());
|
||||
|
||||
// Test the 'tags' element.
|
||||
$expected = array(
|
||||
array('target_id' => $this->referencedEntities[0]->id()),
|
||||
array('target_id' => $this->referencedEntities[1]->id()),
|
||||
);
|
||||
$this->assertEqual($form_state->getValue('tags'), $expected);
|
||||
|
||||
// Test the 'single_autocreate' element.
|
||||
$value = $form_state->getValue('tags_autocreate');
|
||||
// First value is an existing entity.
|
||||
$this->assertEqual($value[0]['target_id'], $this->referencedEntities[0]->id());
|
||||
// Second value is an autocreated entity.
|
||||
$this->assertTrue(!isset($value[1]['target_id']));
|
||||
$this->assertEqual($value[1]['entity']->label(), 'tags - autocreated entity label');
|
||||
$this->assertEqual($value[1]['entity']->getOwnerId(), $this->testUser->id());
|
||||
// Third value is an existing entity.
|
||||
$this->assertEqual($value[2]['target_id'], $this->referencedEntities[1]->id());
|
||||
|
||||
// Test the 'tags_autocreate_specific_uid' element.
|
||||
$value = $form_state->getValue('tags_autocreate_specific_uid');
|
||||
// First value is an existing entity.
|
||||
$this->assertEqual($value[0]['target_id'], $this->referencedEntities[0]->id());
|
||||
// Second value is an autocreated entity.
|
||||
$this->assertTrue(!isset($value[1]['target_id']));
|
||||
$this->assertEqual($value[1]['entity']->label(), 'tags - autocreated entity label with specific uid');
|
||||
$this->assertEqual($value[1]['entity']->getOwnerId(), $this->testAutocreateUser->id());
|
||||
// Third value is an existing entity.
|
||||
$this->assertEqual($value[2]['target_id'], $this->referencedEntities[1]->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests invalid entries in the EntityAutocomplete Form API element.
|
||||
*/
|
||||
public function testInvalidEntityAutocompleteElement() {
|
||||
$form_builder = $this->container->get('form_builder');
|
||||
|
||||
// Test 'single' with a entity label that doesn't exist
|
||||
$form_state = (new FormState())
|
||||
->setValues([
|
||||
'single' => 'single - non-existent label',
|
||||
]);
|
||||
$form_builder->submitForm($this, $form_state);
|
||||
$this->assertEqual(count($form_state->getErrors()), 1);
|
||||
$this->assertEqual($form_state->getErrors()['single'], t('There are no entities matching "%value".', array('%value' => 'single - non-existent label')));
|
||||
|
||||
// Test 'single' with a entity ID that doesn't exist.
|
||||
$form_state = (new FormState())
|
||||
->setValues([
|
||||
'single' => 'single - non-existent label (42)',
|
||||
]);
|
||||
$form_builder->submitForm($this, $form_state);
|
||||
$this->assertEqual(count($form_state->getErrors()), 1);
|
||||
$this->assertEqual($form_state->getErrors()['single'], t('The referenced entity (%type: %id) does not exist.', array('%type' => 'entity_test', '%id' => 42)));
|
||||
|
||||
// Do the same tests as above but on an element with '#validate_reference'
|
||||
// set to FALSE.
|
||||
$form_state = (new FormState())
|
||||
->setValues([
|
||||
'single_no_validate' => 'single - non-existent label',
|
||||
'single_autocreate_no_validate' => 'single - autocreate non-existent label'
|
||||
]);
|
||||
$form_builder->submitForm($this, $form_state);
|
||||
|
||||
// The element without 'autocreate' support still has to emit a warning when
|
||||
// the input doesn't end with an entity ID enclosed in parentheses.
|
||||
$this->assertEqual(count($form_state->getErrors()), 1);
|
||||
$this->assertEqual($form_state->getErrors()['single_no_validate'], t('There are no entities matching "%value".', array('%value' => 'single - non-existent label')));
|
||||
|
||||
$form_state = (new FormState())
|
||||
->setValues([
|
||||
'single_no_validate' => 'single - non-existent label (42)',
|
||||
'single_autocreate_no_validate' => 'single - autocreate non-existent label (43)'
|
||||
]);
|
||||
$form_builder->submitForm($this, $form_state);
|
||||
|
||||
// The input is complete (i.e. contains an entity ID at the end), no errors
|
||||
// are triggered.
|
||||
$this->assertEqual(count($form_state->getErrors()), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that access is properly checked by the EntityAutocomplete element.
|
||||
*/
|
||||
public function testEntityAutocompleteAccess() {
|
||||
$form_builder = $this->container->get('form_builder');
|
||||
$form = $form_builder->getForm($this);
|
||||
|
||||
// Check that the current user has proper access to view entity labels.
|
||||
$expected = $this->referencedEntities[0]->label() . ' (' . $this->referencedEntities[0]->id() . ')';
|
||||
$this->assertEqual($form['single_access']['#value'], $expected);
|
||||
|
||||
$expected .= ', ' . $this->referencedEntities[1]->label() . ' (' . $this->referencedEntities[1]->id() . ')';
|
||||
$this->assertEqual($form['tags_access']['#value'], $expected);
|
||||
|
||||
// Set up a non-admin user that is *not* allowed to view test entities.
|
||||
\Drupal::currentUser()->setAccount($this->createUser(array(), array()));
|
||||
|
||||
// Rebuild the form.
|
||||
$form = $form_builder->getForm($this);
|
||||
|
||||
$expected = t('- Restricted access -') . ' (' . $this->referencedEntities[0]->id() . ')';
|
||||
$this->assertEqual($form['single_access']['#value'], $expected);
|
||||
|
||||
$expected .= ', ' . t('- Restricted access -') . ' (' . $this->referencedEntities[1]->id() . ')';
|
||||
$this->assertEqual($form['tags_access']['#value'], $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an entity label in the format needed by the EntityAutocomplete
|
||||
* element.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* A Drupal entity.
|
||||
*
|
||||
* @return string
|
||||
* A string that can be used as a value for EntityAutocomplete elements.
|
||||
*/
|
||||
protected function getAutocompleteInput(EntityInterface $entity) {
|
||||
return EntityAutocomplete::getEntityLabels(array($entity));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\EntityAccessControlHandlerTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests the entity access control handler.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class EntityAccessControlHandlerTest extends EntityLanguageTestBase {
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', 'url_alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts entity access correctly grants or denies access.
|
||||
*/
|
||||
function assertEntityAccess($ops, AccessibleInterface $object, AccountInterface $account = NULL) {
|
||||
foreach ($ops as $op => $result) {
|
||||
$message = format_string("Entity access returns @result with operation '@op'.", array(
|
||||
'@result' => !isset($result) ? 'null' : ($result ? 'true' : 'false'),
|
||||
'@op' => $op,
|
||||
));
|
||||
|
||||
$this->assertEqual($result, $object->access($op, $account), $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures entity access is properly working.
|
||||
*/
|
||||
function testEntityAccess() {
|
||||
// Set up a non-admin user that is allowed to view test entities.
|
||||
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity')));
|
||||
$entity = entity_create('entity_test', array(
|
||||
'name' => 'test',
|
||||
));
|
||||
|
||||
// The current user is allowed to view entities.
|
||||
$this->assertEntityAccess(array(
|
||||
'create' => FALSE,
|
||||
'update' => FALSE,
|
||||
'delete' => FALSE,
|
||||
'view' => TRUE,
|
||||
), $entity);
|
||||
|
||||
// The custom user is not allowed to perform any operation on test entities.
|
||||
$custom_user = $this->createUser();
|
||||
$this->assertEntityAccess(array(
|
||||
'create' => FALSE,
|
||||
'update' => FALSE,
|
||||
'delete' => FALSE,
|
||||
'view' => FALSE,
|
||||
), $entity, $custom_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures default entity access is checked when necessary.
|
||||
*
|
||||
* This ensures that the default checkAccess() implementation of the
|
||||
* entity access control handler is considered if hook_entity_access() has not
|
||||
* explicitly forbidden access. Therefore the default checkAccess()
|
||||
* implementation can forbid access, even after access was already explicitly
|
||||
* allowed by hook_entity_access().
|
||||
*
|
||||
* @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
|
||||
* @see entity_test_entity_access()
|
||||
*/
|
||||
function testDefaultEntityAccess() {
|
||||
// Set up a non-admin user that is allowed to view test entities.
|
||||
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity')));
|
||||
$entity = entity_create('entity_test', array(
|
||||
'name' => 'forbid_access',
|
||||
));
|
||||
|
||||
// The user is denied access to the entity.
|
||||
$this->assertEntityAccess(array(
|
||||
'create' => FALSE,
|
||||
'update' => FALSE,
|
||||
'delete' => FALSE,
|
||||
'view' => FALSE,
|
||||
), $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the default handler is used as a fallback.
|
||||
*/
|
||||
function testEntityAccessDefaultController() {
|
||||
// The implementation requires that the global user id can be loaded.
|
||||
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2)));
|
||||
|
||||
// Check that the default access control handler is used for entities that don't
|
||||
// have a specific access control handler defined.
|
||||
$handler = $this->container->get('entity.manager')->getAccessControlHandler('entity_test_default_access');
|
||||
$this->assertTrue($handler instanceof EntityAccessControlHandler, 'The default entity handler is used for the entity_test_default_access entity type.');
|
||||
|
||||
$entity = entity_create('entity_test_default_access');
|
||||
$this->assertEntityAccess(array(
|
||||
'create' => FALSE,
|
||||
'update' => FALSE,
|
||||
'delete' => FALSE,
|
||||
'view' => FALSE,
|
||||
), $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures entity access for entity translations is properly working.
|
||||
*/
|
||||
function testEntityTranslationAccess() {
|
||||
|
||||
// Set up a non-admin user that is allowed to view test entity translations.
|
||||
\Drupal::currentUser()->setAccount($this->createUser(array('uid' => 2), array('view test entity translations')));
|
||||
|
||||
// Create two test languages.
|
||||
foreach (array('foo', 'bar') as $langcode) {
|
||||
ConfigurableLanguage::create(array(
|
||||
'id' => $langcode,
|
||||
'label' => $this->randomString(),
|
||||
))->save();
|
||||
}
|
||||
|
||||
$entity = entity_create('entity_test', array(
|
||||
'name' => 'test',
|
||||
'langcode' => 'foo',
|
||||
));
|
||||
$entity->save();
|
||||
|
||||
$translation = $entity->getTranslation('bar');
|
||||
$this->assertEntityAccess(array(
|
||||
'view' => TRUE,
|
||||
), $translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook invocations.
|
||||
*/
|
||||
public function testHooks() {
|
||||
$state = $this->container->get('state');
|
||||
$entity = entity_create('entity_test', array(
|
||||
'name' => 'test',
|
||||
));
|
||||
|
||||
// Test hook_entity_create_access() and hook_ENTITY_TYPE_create_access().
|
||||
$entity->access('create');
|
||||
$this->assertEqual($state->get('entity_test_entity_create_access'), TRUE);
|
||||
$this->assertEqual($state->get('entity_test_entity_test_create_access'), TRUE);
|
||||
|
||||
// Test hook_entity_access() and hook_ENTITY_TYPE_access().
|
||||
$entity->access('view');
|
||||
$this->assertEqual($state->get('entity_test_entity_access'), TRUE);
|
||||
$this->assertEqual($state->get('entity_test_entity_test_access'), TRUE);
|
||||
}
|
||||
}
|
||||
146
core/modules/system/src/Tests/Entity/EntityApiTest.php
Normal file
146
core/modules/system/src/Tests/Entity/EntityApiTest.php
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\EntityApiTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests basic CRUD functionality.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class EntityApiTest extends EntityUnitTestBase {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
foreach (entity_test_entity_types() as $entity_type_id) {
|
||||
// The entity_test schema is installed by the parent.
|
||||
if ($entity_type_id != 'entity_test') {
|
||||
$this->installEntitySchema($entity_type_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic CRUD functionality of the Entity API.
|
||||
*/
|
||||
public function testCRUD() {
|
||||
// All entity variations have to have the same results.
|
||||
foreach (entity_test_entity_types() as $entity_type) {
|
||||
$this->assertCRUD($entity_type, $this->createUser());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a test set for a defined entity type and user.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to run the tests with.
|
||||
* @param \Drupal\user\UserInterface $user1
|
||||
* The user to run the tests with.
|
||||
*/
|
||||
protected function assertCRUD($entity_type, UserInterface $user1) {
|
||||
// Create some test entities.
|
||||
$entity = entity_create($entity_type, array('name' => 'test', 'user_id' => $user1->id()));
|
||||
$entity->save();
|
||||
$entity = entity_create($entity_type, array('name' => 'test2', 'user_id' => $user1->id()));
|
||||
$entity->save();
|
||||
$entity = entity_create($entity_type, array('name' => 'test', 'user_id' => NULL));
|
||||
$entity->save();
|
||||
|
||||
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test')));
|
||||
$this->assertEqual($entities[0]->name->value, 'test', format_string('%entity_type: Created and loaded entity', array('%entity_type' => $entity_type)));
|
||||
$this->assertEqual($entities[1]->name->value, 'test', format_string('%entity_type: Created and loaded entity', array('%entity_type' => $entity_type)));
|
||||
|
||||
// Test loading a single entity.
|
||||
$loaded_entity = entity_load($entity_type, $entity->id());
|
||||
$this->assertEqual($loaded_entity->id(), $entity->id(), format_string('%entity_type: Loaded a single entity by id.', array('%entity_type' => $entity_type)));
|
||||
|
||||
// Test deleting an entity.
|
||||
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test2')));
|
||||
$entities[0]->delete();
|
||||
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test2')));
|
||||
$this->assertEqual($entities, array(), format_string('%entity_type: Entity deleted.', array('%entity_type' => $entity_type)));
|
||||
|
||||
// Test updating an entity.
|
||||
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test')));
|
||||
$entities[0]->name->value = 'test3';
|
||||
$entities[0]->save();
|
||||
$entity = entity_load($entity_type, $entities[0]->id());
|
||||
$this->assertEqual($entity->name->value, 'test3', format_string('%entity_type: Entity updated.', array('%entity_type' => $entity_type)));
|
||||
|
||||
// Try deleting multiple test entities by deleting all.
|
||||
$ids = array_keys(entity_load_multiple($entity_type));
|
||||
entity_delete_multiple($entity_type, $ids);
|
||||
|
||||
$all = entity_load_multiple($entity_type);
|
||||
$this->assertTrue(empty($all), format_string('%entity_type: Deleted all entities.', array('%entity_type' => $entity_type)));
|
||||
|
||||
// Verify that all data got deleted.
|
||||
$definition = \Drupal::entityManager()->getDefinition($entity_type);
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $definition->getBaseTable() . '}')->fetchField(), 'Base table was emptied');
|
||||
if ($data_table = $definition->getDataTable()) {
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $data_table . '}')->fetchField(), 'Data table was emptied');
|
||||
}
|
||||
if ($revision_table = $definition->getRevisionTable()) {
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $revision_table . '}')->fetchField(), 'Data table was emptied');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that exceptions are thrown when saving or deleting an entity.
|
||||
*/
|
||||
public function testEntityStorageExceptionHandling() {
|
||||
$entity = entity_create('entity_test', array('name' => 'test'));
|
||||
try {
|
||||
$GLOBALS['entity_test_throw_exception'] = TRUE;
|
||||
$entity->save();
|
||||
$this->fail('Entity presave EntityStorageException thrown but not caught.');
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
$this->assertEqual($e->getcode(), 1, 'Entity presave EntityStorageException caught.');
|
||||
}
|
||||
|
||||
$entity = entity_create('entity_test', array('name' => 'test2'));
|
||||
try {
|
||||
unset($GLOBALS['entity_test_throw_exception']);
|
||||
$entity->save();
|
||||
$this->pass('Exception presave not thrown and not caught.');
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
$this->assertNotEqual($e->getCode(), 1, 'Entity presave EntityStorageException caught.');
|
||||
}
|
||||
|
||||
$entity = entity_create('entity_test', array('name' => 'test3'));
|
||||
$entity->save();
|
||||
try {
|
||||
$GLOBALS['entity_test_throw_exception'] = TRUE;
|
||||
$entity->delete();
|
||||
$this->fail('Entity predelete EntityStorageException not thrown.');
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
$this->assertEqual($e->getCode(), 2, 'Entity predelete EntityStorageException caught.');
|
||||
}
|
||||
|
||||
unset($GLOBALS['entity_test_throw_exception']);
|
||||
$entity = entity_create('entity_test', array('name' => 'test4'));
|
||||
$entity->save();
|
||||
try {
|
||||
$entity->delete();
|
||||
$this->pass('Entity predelete EntityStorageException not thrown and not caught.');
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
$this->assertNotEqual($e->getCode(), 2, 'Entity predelete EntityStorageException thrown.');
|
||||
}
|
||||
}
|
||||
}
|
||||
164
core/modules/system/src/Tests/Entity/EntityAutocompleteTest.php
Normal file
164
core/modules/system/src/Tests/Entity/EntityAutocompleteTest.php
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\EntityAutocompleteTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Tags;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\system\Controller\EntityAutocompleteController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* Tests the autocomplete functionality.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class EntityAutocompleteTest extends EntityUnitTestBase {
|
||||
|
||||
/**
|
||||
* The entity type used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType = 'entity_test';
|
||||
|
||||
/**
|
||||
* The bundle used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle = 'entity_test';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', ['key_value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests autocompletion edge cases with slashes in the names.
|
||||
*/
|
||||
function testEntityReferenceAutocompletion() {
|
||||
// Add an entity with a slash in its name.
|
||||
$entity_1 = entity_create($this->entityType, array('name' => '10/16/2011'));
|
||||
$entity_1->save();
|
||||
|
||||
// Add another entity that differs after the slash character.
|
||||
$entity_2 = entity_create($this->entityType, array('name' => '10/17/2011'));
|
||||
$entity_2->save();
|
||||
|
||||
// Add another entity that has both a comma and a slash character.
|
||||
$entity_3 = entity_create($this->entityType, array('name' => 'label with, and / test'));
|
||||
$entity_3->save();
|
||||
|
||||
// Try to autocomplete a entity label that matches both entities.
|
||||
// We should get both entities in a JSON encoded string.
|
||||
$input = '10/';
|
||||
$data = $this->getAutocompleteResult($input);
|
||||
$this->assertIdentical($data[0]['label'], SafeMarkup::checkPlain($entity_1->name->value), 'Autocomplete returned the first matching entity');
|
||||
$this->assertIdentical($data[1]['label'], SafeMarkup::checkPlain($entity_2->name->value), 'Autocomplete returned the second matching entity');
|
||||
|
||||
// Try to autocomplete a entity label that matches the first entity.
|
||||
// We should only get the first entity in a JSON encoded string.
|
||||
$input = '10/16';
|
||||
$data = $this->getAutocompleteResult($input);
|
||||
$target = array(
|
||||
'value' => $entity_1->name->value . ' (1)',
|
||||
'label' => SafeMarkup::checkPlain($entity_1->name->value),
|
||||
);
|
||||
$this->assertIdentical(reset($data), $target, 'Autocomplete returns only the expected matching entity.');
|
||||
|
||||
// Try to autocomplete a entity label that matches the second entity, and
|
||||
// the first entity is already typed in the autocomplete (tags) widget.
|
||||
$input = $entity_1->name->value . ' (1), 10/17';
|
||||
$data = $this->getAutocompleteResult($input);
|
||||
$this->assertIdentical($data[0]['label'], SafeMarkup::checkPlain($entity_2->name->value), 'Autocomplete returned the second matching entity');
|
||||
|
||||
// Try to autocomplete a entity label with both a comma and a slash.
|
||||
$input = '"label with, and / t';
|
||||
$data = $this->getAutocompleteResult($input);
|
||||
$n = $entity_3->name->value . ' (3)';
|
||||
// Entity labels containing commas or quotes must be wrapped in quotes.
|
||||
$n = Tags::encode($n);
|
||||
$target = array(
|
||||
'value' => $n,
|
||||
'label' => SafeMarkup::checkPlain($entity_3->name->value),
|
||||
);
|
||||
$this->assertIdentical(reset($data), $target, 'Autocomplete returns an entity label containing a comma and a slash.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that missing or invalid selection setting key are handled correctly.
|
||||
*/
|
||||
public function testSelectionSettingsHandling() {
|
||||
$entity_reference_controller = EntityAutocompleteController::create($this->container);
|
||||
$request = Request::create('entity_reference_autocomplete/' . $this->entityType . '/default');
|
||||
$request->query->set('q', $this->randomString());
|
||||
|
||||
try {
|
||||
// Pass an invalid selection settings key (i.e. one that does not exist
|
||||
// in the key/value store).
|
||||
$selection_settings_key = $this->randomString();
|
||||
$entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key);
|
||||
|
||||
$this->fail('Non-existent selection settings key throws an exception.');
|
||||
}
|
||||
catch (AccessDeniedHttpException $e) {
|
||||
$this->pass('Non-existent selection settings key throws an exception.');
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate a valid hash key but store a modified settings array.
|
||||
$selection_settings = [];
|
||||
$selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $this->entityType . 'default', Settings::getHashSalt());
|
||||
|
||||
$selection_settings[$this->randomMachineName()] = $this->randomString();
|
||||
\Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings);
|
||||
|
||||
$entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key);
|
||||
}
|
||||
catch (AccessDeniedHttpException $e) {
|
||||
if ($e->getMessage() == 'Invalid selection settings key.') {
|
||||
$this->pass('Invalid selection settings key throws an exception.');
|
||||
}
|
||||
else {
|
||||
$this->fail('Invalid selection settings key throws an exception.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of an Entity reference autocomplete request.
|
||||
*
|
||||
* @param string $input
|
||||
* The label of the entity to query by.
|
||||
*
|
||||
* @return mixed
|
||||
* The JSON value encoded in its appropriate PHP type.
|
||||
*/
|
||||
protected function getAutocompleteResult($input) {
|
||||
$request = Request::create('entity_reference_autocomplete/' . $this->entityType . '/default');
|
||||
$request->query->set('q', $input);
|
||||
|
||||
$selection_settings = [];
|
||||
$selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $this->entityType . 'default', Settings::getHashSalt());
|
||||
\Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings);
|
||||
|
||||
$entity_reference_controller = EntityAutocompleteController::create($this->container);
|
||||
$result = $entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key)->getContent();
|
||||
|
||||
return Json::decode($result);
|
||||
}
|
||||
|
||||
}
|
||||
119
core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
Normal file
119
core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\EntityBundleFieldTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
/**
|
||||
* Tests adding a custom bundle field.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class EntityBundleFieldTest extends EntityUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('entity_schema_test');
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The database connection used.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('user', array('users_data'));
|
||||
$this->installSchema('system', array('router'));
|
||||
$this->moduleHandler = $this->container->get('module_handler');
|
||||
$this->database = $this->container->get('database');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests making use of a custom bundle field.
|
||||
*/
|
||||
public function testCustomBundleFieldUsage() {
|
||||
entity_test_create_bundle('custom');
|
||||
|
||||
// Check that an entity with bundle entity_test does not have the custom
|
||||
// field.
|
||||
$storage = $this->entityManager->getStorage('entity_test');
|
||||
$entity = $storage->create([
|
||||
'type' => 'entity_test',
|
||||
]);
|
||||
$this->assertFalse($entity->hasField('custom_bundle_field'));
|
||||
|
||||
// Check that the custom bundle has the defined custom field and check
|
||||
// saving and deleting of custom field data.
|
||||
$entity = $storage->create([
|
||||
'type' => 'custom',
|
||||
]);
|
||||
$this->assertTrue($entity->hasField('custom_bundle_field'));
|
||||
|
||||
// Ensure that the field exists in the field map.
|
||||
$field_map = \Drupal::entityManager()->getFieldMap();
|
||||
$this->assertEqual($field_map['entity_test']['custom_bundle_field'], ['type' => 'string', 'bundles' => ['custom' => 'custom']]);
|
||||
|
||||
$entity->custom_bundle_field->value = 'swanky';
|
||||
$entity->save();
|
||||
$storage->resetCache();
|
||||
$entity = $storage->load($entity->id());
|
||||
$this->assertEqual($entity->custom_bundle_field->value, 'swanky', 'Entity was saved correctly');
|
||||
|
||||
$entity->custom_bundle_field->value = 'cozy';
|
||||
$entity->save();
|
||||
$storage->resetCache();
|
||||
$entity = $storage->load($entity->id());
|
||||
$this->assertEqual($entity->custom_bundle_field->value, 'cozy', 'Entity was updated correctly.');
|
||||
|
||||
$entity->delete();
|
||||
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
|
||||
$table_mapping = $storage->getTableMapping();
|
||||
$table = $table_mapping->getDedicatedDataTableName($entity->getFieldDefinition('custom_bundle_field'));
|
||||
$result = $this->database->select($table, 'f')
|
||||
->fields('f')
|
||||
->condition('f.entity_id', $entity->id())
|
||||
->execute();
|
||||
$this->assertFalse($result->fetchAssoc(), 'Field data has been deleted');
|
||||
|
||||
// Create another entity to test that values are marked as deleted when a
|
||||
// bundle is deleted.
|
||||
$entity = $storage->create(['type' => 'custom', 'custom_bundle_field' => 'new']);
|
||||
$entity->save();
|
||||
entity_test_delete_bundle('custom');
|
||||
|
||||
$table = $table_mapping->getDedicatedDataTableName($entity->getFieldDefinition('custom_bundle_field'));
|
||||
$result = $this->database->select($table, 'f')
|
||||
->condition('f.entity_id', $entity->id())
|
||||
->condition('deleted', 1)
|
||||
->countQuery()
|
||||
->execute();
|
||||
$this->assertEqual(1, $result->fetchField(), 'Field data has been deleted');
|
||||
|
||||
// Ensure that the field no longer exists in the field map.
|
||||
$field_map = \Drupal::entityManager()->getFieldMap();
|
||||
$this->assertFalse(isset($field_map['entity_test']['custom_bundle_field']));
|
||||
|
||||
// @todo Test field purge and table deletion once supported. See
|
||||
// https://www.drupal.org/node/2282119.
|
||||
// $this->assertFalse($this->database->schema()->tableExists($table), 'Custom field table was deleted');
|
||||
}
|
||||
|
||||
}
|
||||
706
core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
Normal file
706
core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
Normal file
|
|
@ -0,0 +1,706 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Entity\EntityCacheTagsTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Entity;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Provides helper methods for Entity cache tags tests.
|
||||
*/
|
||||
abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('entity_reference', 'entity_test', 'field_test');
|
||||
|
||||
/**
|
||||
* The main entity used for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* The entity instance referencing the main entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $referencingEntity;
|
||||
|
||||
/**
|
||||
* The entity instance not referencing the main entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $nonReferencingEntity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Give anonymous users permission to view test entities, so that we can
|
||||
// verify the cache tags of cached versions of test entity pages.
|
||||
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
|
||||
$user_role->grantPermission('view test entity');
|
||||
$user_role->save();
|
||||
|
||||
// Create an entity.
|
||||
$this->entity = $this->createEntity();
|
||||
|
||||
// If this is an entity with field UI enabled, then add a configurable
|
||||
// field. We will use this configurable field in later tests to ensure that
|
||||
// field configuration invalidate render cache entries.
|
||||
if ($this->entity->getEntityType()->get('field_ui_base_route')) {
|
||||
// Add field, so we can modify the field storage and field entities to
|
||||
// verify that changes to those indeed clear cache tags.
|
||||
entity_create('field_storage_config', array(
|
||||
'field_name' => 'configurable_field',
|
||||
'entity_type' => $this->entity->getEntityTypeId(),
|
||||
'type' => 'test_field',
|
||||
'settings' => array(),
|
||||
))->save();
|
||||
entity_create('field_config', array(
|
||||
'entity_type' => $this->entity->getEntityTypeId(),
|
||||
'bundle' => $this->entity->bundle(),
|
||||
'field_name' => 'configurable_field',
|
||||
'label' => 'Configurable field',
|
||||
'settings' => array(),
|
||||
))->save();
|
||||
|
||||
// Reload the entity now that a new field has been added to it.
|
||||
$storage = $this->container
|
||||
->get('entity.manager')
|
||||
->getStorage($this->entity->getEntityTypeId());
|
||||
$storage->resetCache();
|
||||
$this->entity = $storage->load($this->entity->id());
|
||||
}
|
||||
|
||||
// Create a referencing and a non-referencing entity.
|
||||
list(
|
||||
$this->referencingEntity,
|
||||
$this->nonReferencingEntity,
|
||||
) = $this->createReferenceTestEntities($this->entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates standardized entity cache tags test info.
|
||||
*
|
||||
* @param string $entity_type_label
|
||||
* The label of the entity type whose cache tags to test.
|
||||
* @param string $group
|
||||
* The test group.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @see \Drupal\simpletest\TestBase::getInfo()
|
||||
*/
|
||||
protected static function generateStandardizedInfo($entity_type_label, $group) {
|
||||
return array(
|
||||
'name' => "$entity_type_label entity cache tags",
|
||||
'description' => "Test the $entity_type_label entity's cache tags.",
|
||||
'group' => $group,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity to be tested.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The entity to be tested.
|
||||
*/
|
||||
abstract protected function createEntity();
|
||||
|
||||
/**
|
||||
* Returns the access cache contexts for the tested entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be tested, as created by createEntity().
|
||||
*
|
||||
* @return string[]
|
||||
* An array of the additional cache contexts.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface
|
||||
*/
|
||||
protected function getAccessCacheContextsForEntity(EntityInterface $entity) {
|
||||
return ['user.permissions'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional (non-standard) cache contexts for the tested entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be tested, as created by createEntity().
|
||||
*
|
||||
* @return string[]
|
||||
* An array of the additional cache contexts.
|
||||
*
|
||||
* @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
|
||||
*/
|
||||
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional (non-standard) cache tags for the tested entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be tested, as created by createEntity().
|
||||
* @return array
|
||||
* An array of the additional cache tags.
|
||||
*
|
||||
* @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
|
||||
*/
|
||||
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional cache tags for the tested entity's listing by type.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of the additional cache contexts.
|
||||
*/
|
||||
protected function getAdditionalCacheContextsForEntityListing() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional cache tags for the tested entity's listing by type.
|
||||
*
|
||||
* Necessary when there are unavoidable default entities of this type, e.g.
|
||||
* the anonymous and administrator User entities always exist.
|
||||
*
|
||||
* @return array
|
||||
* An array of the additional cache tags.
|
||||
*/
|
||||
protected function getAdditionalCacheTagsForEntityListing() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the preferred view mode for the given entity type.
|
||||
*
|
||||
* Prefers 'full', picks the first one otherwise, and if none are available,
|
||||
* chooses 'default'.
|
||||
*/
|
||||
protected function selectViewMode($entity_type) {
|
||||
$view_modes = \Drupal::entityManager()
|
||||
->getStorage('entity_view_mode')
|
||||
->loadByProperties(array('targetEntityType' => $entity_type));
|
||||
|
||||
if (empty($view_modes)) {
|
||||
return 'default';
|
||||
}
|
||||
else {
|
||||
// Prefer the "full" display mode.
|
||||
if (isset($view_modes[$entity_type . '.full'])) {
|
||||
return 'full';
|
||||
}
|
||||
else {
|
||||
$view_modes = array_keys($view_modes);
|
||||
return substr($view_modes[0], strlen($entity_type) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a referencing and a non-referencing entity for testing purposes.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $referenced_entity
|
||||
* The entity that the referencing entity should reference.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface[]
|
||||
* An array containing a referencing entity and a non-referencing entity.
|
||||
*/
|
||||
protected function createReferenceTestEntities($referenced_entity) {
|
||||
// All referencing entities should be of the type 'entity_test'.
|
||||
$entity_type = 'entity_test';
|
||||
|
||||
// Create a "foo" bundle for the given entity type.
|
||||
$bundle = 'foo';
|
||||
entity_test_create_bundle($bundle, NULL, $entity_type);
|
||||
|
||||
// Add a field of the given type to the given entity type's "foo" bundle.
|
||||
$field_name = $referenced_entity->getEntityTypeId() . '_reference';
|
||||
entity_create('field_storage_config', array(
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => $entity_type,
|
||||
'type' => 'entity_reference',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
'settings' => array(
|
||||
'target_type' => $referenced_entity->getEntityTypeId(),
|
||||
),
|
||||
))->save();
|
||||
entity_create('field_config', array(
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'settings' => array(
|
||||
'handler' => 'default',
|
||||
'handler_settings' => array(
|
||||
'target_bundles' => array(
|
||||
$referenced_entity->bundle() => $referenced_entity->bundle(),
|
||||
),
|
||||
'sort' => array('field' => '_none'),
|
||||
'auto_create' => FALSE,
|
||||
),
|
||||
),
|
||||
))->save();
|
||||
if (!$this->entity->getEntityType()->hasHandlerClass('view_builder')) {
|
||||
entity_get_display($entity_type, $bundle, 'full')
|
||||
->setComponent($field_name, array(
|
||||
'type' => 'entity_reference_label',
|
||||
))
|
||||
->save();
|
||||
}
|
||||
else {
|
||||
$referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId());
|
||||
entity_get_display($entity_type, $bundle, 'full')
|
||||
->setComponent($field_name, array(
|
||||
'type' => 'entity_reference_entity_view',
|
||||
'settings' => array(
|
||||
'view_mode' => $referenced_entity_view_mode,
|
||||
),
|
||||
))
|
||||
->save();
|
||||
}
|
||||
|
||||
// Create an entity that does reference the entity being tested.
|
||||
$label_key = \Drupal::entityManager()->getDefinition($entity_type)->getKey('label');
|
||||
$referencing_entity = entity_create($entity_type, array(
|
||||
$label_key => 'Referencing ' . $entity_type,
|
||||
'status' => 1,
|
||||
'type' => $bundle,
|
||||
$field_name => array('target_id' => $referenced_entity->id()),
|
||||
));
|
||||
$referencing_entity->save();
|
||||
|
||||
// Create an entity that does not reference the entity being tested.
|
||||
$non_referencing_entity = entity_create($entity_type, array(
|
||||
$label_key => 'Non-referencing ' . $entity_type,
|
||||
'status' => 1,
|
||||
'type' => $bundle,
|
||||
));
|
||||
$non_referencing_entity->save();
|
||||
|
||||
return array(
|
||||
$referencing_entity,
|
||||
$non_referencing_entity,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests cache tags presence and invalidation of the entity when referenced.
|
||||
*
|
||||
* Tests the following cache tags:
|
||||
* - entity type view cache tag: "<entity type>_view"
|
||||
* - entity cache tag: "<entity type>:<entity ID>"
|
||||
* - entity type list cache tag: "<entity type>_list"
|
||||
* - referencing entity type view cache tag: "<referencing entity type>_view"
|
||||
* - referencing entity type cache tag: "<referencing entity type>:<referencing entity ID>"
|
||||
*/
|
||||
public function testReferencedEntity() {
|
||||
$entity_type = $this->entity->getEntityTypeId();
|
||||
$referencing_entity_url = $this->referencingEntity->urlInfo('canonical');
|
||||
$non_referencing_entity_url = $this->nonReferencingEntity->urlInfo('canonical');
|
||||
$listing_url = Url::fromRoute('entity.entity_test.collection_referencing_entities', [
|
||||
'entity_reference_field_name' => $entity_type . '_reference',
|
||||
'referenced_entity_type' => $entity_type,
|
||||
'referenced_entity_id' => $this->entity->id(),
|
||||
]);
|
||||
$empty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_empty', ['entity_type_id' => $entity_type]);
|
||||
$nonempty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_labels_alphabetically', ['entity_type_id' => $entity_type]);
|
||||
|
||||
// The default cache contexts for rendered entities.
|
||||
$default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'];
|
||||
$entity_cache_contexts = $default_cache_contexts;
|
||||
|
||||
// Cache tags present on every rendered page.
|
||||
$page_cache_tags = Cache::mergeTags(
|
||||
['rendered'],
|
||||
// If the block module is used, the Block page display variant is used,
|
||||
// which adds the block config entity type's list cache tags.
|
||||
\Drupal::moduleHandler()->moduleExists('block') ? ['config:block_list']: []
|
||||
);
|
||||
$page_cache_tags_referencing_entity = in_array('user.permissions', $this->getAccessCacheContextsForEntity($this->referencingEntity)) ? ['config:user.role.anonymous'] : [];
|
||||
|
||||
$view_cache_tag = array();
|
||||
if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
|
||||
$view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)
|
||||
->getCacheTags();
|
||||
}
|
||||
|
||||
// Generate the cache tags for the (non) referencing entities.
|
||||
$referencing_entity_cache_tags = Cache::mergeTags(
|
||||
$this->referencingEntity->getCacheTags(),
|
||||
\Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags(),
|
||||
// Includes the main entity's cache tags, since this entity references it.
|
||||
$this->entity->getCacheTags(),
|
||||
$this->getAdditionalCacheTagsForEntity($this->entity),
|
||||
$view_cache_tag,
|
||||
['rendered']
|
||||
);
|
||||
$non_referencing_entity_cache_tags = Cache::mergeTags(
|
||||
$this->nonReferencingEntity->getCacheTags(),
|
||||
\Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags(),
|
||||
['rendered']
|
||||
);
|
||||
|
||||
// Generate the cache tags for all two possible entity listing paths.
|
||||
// 1. list cache tag only (listing query has no match)
|
||||
// 2. list cache tag plus entity cache tag (listing query has a match)
|
||||
$empty_entity_listing_cache_tags = Cache::mergeTags(
|
||||
$this->entity->getEntityType()->getListCacheTags(),
|
||||
$page_cache_tags
|
||||
);
|
||||
$nonempty_entity_listing_cache_tags = Cache::mergeTags(
|
||||
$this->entity->getEntityType()->getListCacheTags(),
|
||||
$this->entity->getCacheTags(),
|
||||
$this->getAdditionalCacheTagsForEntityListing($this->entity),
|
||||
$page_cache_tags
|
||||
);
|
||||
|
||||
$this->pass("Test referencing entity.", 'Debug');
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
// Verify a cache hit, but also the presence of the correct cache tags.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags, $page_cache_tags_referencing_entity));
|
||||
// Also verify the existence of an entity render cache entry.
|
||||
$cache_keys = ['entity_view', 'entity_test', $this->referencingEntity->id(), 'full'];
|
||||
$cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
|
||||
$access_cache_contexts = $this->getAccessCacheContextsForEntity($this->entity);
|
||||
$redirected_cid = NULL;
|
||||
if (count($access_cache_contexts)) {
|
||||
$redirected_cid = $this->createCacheId($cache_keys, Cache::mergeContexts($entity_cache_contexts, $this->getAdditionalCacheContextsForEntity($this->referencingEntity), $access_cache_contexts));
|
||||
}
|
||||
$this->verifyRenderCache($cid, $referencing_entity_cache_tags, $redirected_cid);
|
||||
|
||||
$this->pass("Test non-referencing entity.", 'Debug');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'MISS');
|
||||
// Verify a cache hit, but also the presence of the correct cache tags.
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT', Cache::mergeTags($non_referencing_entity_cache_tags, $page_cache_tags));
|
||||
// Also verify the existence of an entity render cache entry.
|
||||
$cache_keys = ['entity_view', 'entity_test', $this->nonReferencingEntity->id(), 'full'];
|
||||
$cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
|
||||
$this->verifyRenderCache($cid, $non_referencing_entity_cache_tags);
|
||||
|
||||
|
||||
$this->pass("Test listing of referencing entities.", 'Debug');
|
||||
// Prime the page cache for the listing of referencing entities.
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
// Verify a cache hit, but also the presence of the correct cache tags.
|
||||
$this->verifyPageCache($listing_url, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags, $page_cache_tags_referencing_entity));
|
||||
|
||||
|
||||
$this->pass("Test empty listing.", 'Debug');
|
||||
// Prime the page cache for the empty listing.
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'MISS');
|
||||
// Verify a cache hit, but also the presence of the correct cache tags.
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
|
||||
// Verify the entity type's list cache contexts are present.
|
||||
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
|
||||
$this->assertEqual(Cache::mergeContexts($default_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
|
||||
|
||||
|
||||
$this->pass("Test listing containing referenced entity.", 'Debug');
|
||||
// Prime the page cache for the listing containing the referenced entity.
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS', $nonempty_entity_listing_cache_tags);
|
||||
// Verify a cache hit, but also the presence of the correct cache tags.
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
|
||||
// Verify the entity type's list cache contexts are present.
|
||||
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
|
||||
$this->assertEqual(Cache::mergeContexts($default_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
|
||||
|
||||
|
||||
// Verify that after modifying the referenced entity, there is a cache miss
|
||||
// for every route except the one for the non-referencing entity.
|
||||
$this->pass("Test modification of referenced entity.", 'Debug');
|
||||
$this->entity->save();
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'MISS');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
|
||||
// Verify that after modifying the referencing entity, there is a cache miss
|
||||
// for every route except the ones for the non-referencing entity and the
|
||||
// empty entity listing.
|
||||
$this->pass("Test modification of referencing entity.", 'Debug');
|
||||
$this->referencingEntity->save();
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
|
||||
// Verify that after modifying the non-referencing entity, there is a cache
|
||||
// miss only for the non-referencing entity route.
|
||||
$this->pass("Test modification of non-referencing entity.", 'Debug');
|
||||
$this->nonReferencingEntity->save();
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'MISS');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
|
||||
|
||||
if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
|
||||
// Verify that after modifying the entity's display, there is a cache miss
|
||||
// for both the referencing entity, and the listing of referencing
|
||||
// entities, but not for any other routes.
|
||||
$referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId());
|
||||
$this->pass("Test modification of referenced entity's '$referenced_entity_view_mode' display.", 'Debug');
|
||||
$entity_display = entity_get_display($entity_type, $this->entity->bundle(), $referenced_entity_view_mode);
|
||||
$entity_display->save();
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
}
|
||||
|
||||
|
||||
$bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType();
|
||||
if ($bundle_entity_type !== 'bundle') {
|
||||
// Verify that after modifying the corresponding bundle entity, there is a
|
||||
// cache miss for both the referencing entity, and the listing of
|
||||
// referencing entities, but not for any other routes.
|
||||
$this->pass("Test modification of referenced entity's bundle entity.", 'Debug');
|
||||
$bundle_entity = entity_load($bundle_entity_type, $this->entity->bundle());
|
||||
$bundle_entity->save();
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
// Special case: entity types may choose to use their bundle entity type
|
||||
// cache tags, to avoid having excessively granular invalidation.
|
||||
$is_special_case = $bundle_entity->getCacheTags() == $this->entity->getCacheTags() && $bundle_entity->getEntityType()->getListCacheTags() == $this->entity->getEntityType()->getListCacheTags();
|
||||
if ($is_special_case) {
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'MISS');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
|
||||
}
|
||||
else {
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
}
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
if ($is_special_case) {
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($this->entity->getEntityType()->get('field_ui_base_route')) {
|
||||
// Verify that after modifying a configurable field on the entity, there
|
||||
// is a cache miss.
|
||||
$this->pass("Test modification of referenced entity's configurable field.", 'Debug');
|
||||
$field_storage_name = $this->entity->getEntityTypeId() . '.configurable_field';
|
||||
$field_storage = FieldStorageConfig::load($field_storage_name);
|
||||
$field_storage->save();
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
|
||||
|
||||
// Verify that after modifying a configurable field on the entity, there
|
||||
// is a cache miss.
|
||||
$this->pass("Test modification of referenced entity's configurable field.", 'Debug');
|
||||
$field_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field';
|
||||
$field = FieldConfig::load($field_name);
|
||||
$field->save();
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
}
|
||||
|
||||
|
||||
// Verify that after invalidating the entity's cache tag directly, there is
|
||||
// a cache miss for every route except the ones for the non-referencing
|
||||
// entity and the empty entity listing.
|
||||
$this->pass("Test invalidation of referenced entity's cache tag.", 'Debug');
|
||||
Cache::invalidateTags($this->entity->getCacheTags());
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
// Verify that after invalidating the entity's list cache tag directly,
|
||||
// there is a cache miss for both the empty entity listing and the non-empty
|
||||
// entity listing routes, but not for other routes.
|
||||
$this->pass("Test invalidation of referenced entity's list cache tag.", 'Debug');
|
||||
Cache::invalidateTags($this->entity->getEntityType()->getListCacheTags());
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'MISS');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
|
||||
if (!empty($view_cache_tag)) {
|
||||
// Verify that after invalidating the generic entity type's view cache tag
|
||||
// directly, there is a cache miss for both the referencing entity, and the
|
||||
// listing of referencing entities, but not for other routes.
|
||||
$this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug');
|
||||
Cache::invalidateTags($view_cache_tag);
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT');
|
||||
$this->verifyPageCache($listing_url, 'HIT');
|
||||
}
|
||||
|
||||
// Verify that after deleting the entity, there is a cache miss for every
|
||||
// route except for the non-referencing entity one.
|
||||
$this->pass('Test deletion of referenced entity.', 'Debug');
|
||||
$this->entity->delete();
|
||||
$this->verifyPageCache($referencing_entity_url, 'MISS');
|
||||
$this->verifyPageCache($listing_url, 'MISS');
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'MISS');
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
|
||||
$this->verifyPageCache($non_referencing_entity_url, 'HIT');
|
||||
|
||||
// Verify cache hits.
|
||||
$referencing_entity_cache_tags = Cache::mergeTags(
|
||||
$this->referencingEntity->getCacheTags(),
|
||||
\Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags(),
|
||||
['rendered']
|
||||
);
|
||||
$this->verifyPageCache($referencing_entity_url, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
|
||||
$this->verifyPageCache($listing_url, 'HIT', $page_cache_tags);
|
||||
$this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
|
||||
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT', Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->getAdditionalCacheTagsForEntityListing(), $page_cache_tags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cache ID from a list of cache keys and a set of cache contexts.
|
||||
*
|
||||
* @param string[] $keys
|
||||
* A list of cache keys.
|
||||
* @param string[] $contexts
|
||||
* A set of cache contexts.
|
||||
*
|
||||
* @return string
|
||||
* The cache ID string.
|
||||
*/
|
||||
protected function createCacheId(array $keys, array $contexts) {
|
||||
$cid_parts = $keys;
|
||||
|
||||
$contexts = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($contexts);
|
||||
$cid_parts = array_merge($cid_parts, $contexts);
|
||||
|
||||
return implode(':', $cid_parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a given render cache entry exists, with the correct cache tags.
|
||||
*
|
||||
* @param string $cid
|
||||
* The render cache item ID.
|
||||
* @param array $tags
|
||||
* An array of expected cache tags.
|
||||
* @param string|null $redirected_cid
|
||||
* (optional) The redirected render cache item ID.
|
||||
*/
|
||||
protected function verifyRenderCache($cid, array $tags, $redirected_cid = NULL) {
|
||||
// Also verify the existence of an entity render cache entry.
|
||||
$cache_entry = \Drupal::cache('render')->get($cid);
|
||||
$this->assertTrue($cache_entry, 'A render cache entry exists.');
|
||||
sort($cache_entry->tags);
|
||||
sort($tags);
|
||||
$this->assertIdentical($cache_entry->tags, $tags);
|
||||
$is_redirecting_cache_item = isset($cache_entry->data['#cache_redirect']);
|
||||
if ($redirected_cid === NULL) {
|
||||
$this->assertFalse($is_redirecting_cache_item, 'Render cache entry is not a redirect.');
|
||||
// If this is a redirecting cache item unlike we expected, log it.
|
||||
if ($is_redirecting_cache_item) {
|
||||
debug($cache_entry->data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Verify that $cid contains a cache redirect.
|
||||
$this->assertTrue($is_redirecting_cache_item, 'Render cache entry is a redirect.');
|
||||
// If this is not a redirecting cache item unlike we expected, log it.
|
||||
if (!$is_redirecting_cache_item) {
|
||||
debug($cache_entry->data);
|
||||
}
|
||||
// Verify that the cache redirect points to the expected CID.
|
||||
$redirect_cache_metadata = $cache_entry->data['#cache'];
|
||||
$actual_redirection_cid = $this->createCacheId(
|
||||
$redirect_cache_metadata['keys'],
|
||||
$redirect_cache_metadata['contexts']
|
||||
);
|
||||
$this->assertIdentical($redirected_cid, $actual_redirection_cid);
|
||||
// Finally, verify that the redirected CID exists and has the same cache
|
||||
// tags.
|
||||
$this->verifyRenderCache($redirected_cid, $tags);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue