Initial Drupal 11 with DDEV setup
This commit is contained in:
		
							
								
								
									
										95
									
								
								web/core/tests/Drupal/KernelTests/AssertConfigTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								web/core/tests/Drupal/KernelTests/AssertConfigTrait.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,95 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Diff\Diff;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Trait to help with diffing config.
 | 
			
		||||
 */
 | 
			
		||||
trait AssertConfigTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures that a specific config diff does not contain unwanted changes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Component\Diff\Diff $result
 | 
			
		||||
   *   The diff result for the passed in config name.
 | 
			
		||||
   * @param string $config_name
 | 
			
		||||
   *   The config name to check.
 | 
			
		||||
   * @param array $skipped_config
 | 
			
		||||
   *   An array of skipped config, keyed by string. If the value is TRUE, the
 | 
			
		||||
   *   entire file will be ignored, otherwise it's an array of strings which are
 | 
			
		||||
   *   ignored.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Exception
 | 
			
		||||
   *   Thrown when a configuration is different.
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertConfigDiff(Diff $result, $config_name, array $skipped_config) {
 | 
			
		||||
    foreach ($result->getEdits() as $op) {
 | 
			
		||||
      switch (get_class($op)) {
 | 
			
		||||
        case 'Drupal\Component\Diff\Engine\DiffOpCopy':
 | 
			
		||||
          // Nothing to do, a copy is what we expect.
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case 'Drupal\Component\Diff\Engine\DiffOpDelete':
 | 
			
		||||
        case 'Drupal\Component\Diff\Engine\DiffOpChange':
 | 
			
		||||
          // It is not part of the skipped config, so we can directly throw the
 | 
			
		||||
          // exception.
 | 
			
		||||
          if (!in_array($config_name, array_keys($skipped_config))) {
 | 
			
		||||
            throw new \Exception($config_name . ': ' . var_export($op, TRUE));
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // Allow to skip entire config files.
 | 
			
		||||
          if ($skipped_config[$config_name] === TRUE) {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // Allow to skip some specific lines of imported config files.
 | 
			
		||||
          // Ensure that the only changed lines are the ones we marked as
 | 
			
		||||
          // skipped.
 | 
			
		||||
          $all_skipped = TRUE;
 | 
			
		||||
 | 
			
		||||
          $changes = get_class($op) == 'Drupal\Component\Diff\Engine\DiffOpDelete' ? $op->orig : $op->closing;
 | 
			
		||||
          foreach ($changes as $closing) {
 | 
			
		||||
            // Skip some of the changes, as they are caused by module install
 | 
			
		||||
            // code.
 | 
			
		||||
            $found = FALSE;
 | 
			
		||||
            if (!empty($skipped_config[$config_name])) {
 | 
			
		||||
              foreach ($skipped_config[$config_name] as $line) {
 | 
			
		||||
                if (str_contains($closing, $line)) {
 | 
			
		||||
                  $found = TRUE;
 | 
			
		||||
                  break;
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            $all_skipped = $all_skipped && $found;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (!$all_skipped) {
 | 
			
		||||
            throw new \Exception($config_name . ': ' . var_export($op, TRUE));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case 'Drupal\Component\Diff\Engine\DiffOpAdd':
 | 
			
		||||
          // The _core property does not exist in the default config.
 | 
			
		||||
          if ($op->closing[0] === '_core:') {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          foreach ($op->closing as $closing) {
 | 
			
		||||
            // The UUIDs don't exist in the default config.
 | 
			
		||||
            if (str_starts_with($closing, 'uuid: ')) {
 | 
			
		||||
              break;
 | 
			
		||||
            }
 | 
			
		||||
            throw new \Exception($config_name . ': ' . var_export($op, TRUE));
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
          throw new \Exception($config_name . ': ' . var_export($op, TRUE));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1433
									
								
								web/core/tests/Drupal/KernelTests/AssertContentTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1433
									
								
								web/core/tests/Drupal/KernelTests/AssertContentTrait.php
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Component;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\DomCrawler\Crawler;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the correct rendering of components.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
final class ComponentRenderTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'sdc_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the CSS load order.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCssOrder(): void {
 | 
			
		||||
    $this->container->get('theme_installer')->install(['sdc_theme_test']);
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_theme_test:css-load-order',
 | 
			
		||||
      '#props' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    \Drupal::state()->set('sdc_test_component', $build);
 | 
			
		||||
 | 
			
		||||
    $request = Request::create('/sdc-test-component');
 | 
			
		||||
    $response = $this->container->get('http_kernel')->handle($request);
 | 
			
		||||
 | 
			
		||||
    $output = $response->getContent();
 | 
			
		||||
 | 
			
		||||
    $crawler = new Crawler($output);
 | 
			
		||||
    // Assert that both CSS files are attached to the page.
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('link[rel="stylesheet"][href*="css-load-order.css"]'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('link[rel="stylesheet"][href*="css-order-dependent.css"]'));
 | 
			
		||||
    $all_stylesheets = $crawler->filter('link[rel="stylesheet"]');
 | 
			
		||||
    $component_position = NULL;
 | 
			
		||||
    $dependent_position = NULL;
 | 
			
		||||
    foreach ($all_stylesheets as $index => $stylesheet) {
 | 
			
		||||
      $href = $stylesheet->attributes->getNamedItem('href')->nodeValue;
 | 
			
		||||
      if (str_contains($href, 'css-load-order.css')) {
 | 
			
		||||
        $component_position = $index;
 | 
			
		||||
      }
 | 
			
		||||
      if (str_contains($href, 'css-order-dependent.css')) {
 | 
			
		||||
        $dependent_position = $index;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This will assert that css-order-dependent.css is loaded before the
 | 
			
		||||
    // component's css-load-order.css.
 | 
			
		||||
    $this->assertGreaterThan($dependent_position, $component_position);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,141 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Component\Render;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Render\FormattableMarkup;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a test covering integration of FormattableMarkup with other systems.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Render
 | 
			
		||||
 */
 | 
			
		||||
class FormattableMarkupKernelTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets arguments for FormattableMarkup based on Url::fromUri() parameters.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $uri
 | 
			
		||||
   *   The URI of the resource.
 | 
			
		||||
   * @param array $options
 | 
			
		||||
   *   The options to pass to Url::fromUri().
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Array containing:
 | 
			
		||||
   *   - ':url': A URL string.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Component\Render\FormattableMarkup
 | 
			
		||||
   */
 | 
			
		||||
  protected static function getFormattableMarkupUriArgs($uri, $options = []) {
 | 
			
		||||
    $args[':url'] = Url::fromUri($uri, $options)->toString();
 | 
			
		||||
    return $args;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests URL ":placeholders" in \Drupal\Component\Render\FormattableMarkup.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerTestFormattableMarkupUri
 | 
			
		||||
   */
 | 
			
		||||
  public function testFormattableMarkupUri($string, $uri, $options, $expected): void {
 | 
			
		||||
    $args = self::getFormattableMarkupUriArgs($uri, $options);
 | 
			
		||||
    $this->assertSame($expected, (string) new FormattableMarkup($string, $args));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Data provider for testFormattableMarkupUri().
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerTestFormattableMarkupUri() {
 | 
			
		||||
    $data = [];
 | 
			
		||||
    $data['routed-url'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      'route:system.admin',
 | 
			
		||||
      [],
 | 
			
		||||
      'Hey giraffe <a href="/admin">example</a>',
 | 
			
		||||
    ];
 | 
			
		||||
    $data['routed-with-query'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      'route:system.admin',
 | 
			
		||||
      ['query' => ['bar' => 'baz#']],
 | 
			
		||||
      'Hey giraffe <a href="/admin?bar=baz%23">example</a>',
 | 
			
		||||
    ];
 | 
			
		||||
    $data['routed-with-fragment'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      'route:system.admin',
 | 
			
		||||
      ['fragment' => 'bar<'],
 | 
			
		||||
      'Hey giraffe <a href="/admin#bar&lt;">example</a>',
 | 
			
		||||
    ];
 | 
			
		||||
    $data['unrouted-url'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      'base://foo',
 | 
			
		||||
      [],
 | 
			
		||||
      'Hey giraffe <a href="/foo">example</a>',
 | 
			
		||||
    ];
 | 
			
		||||
    $data['unrouted-with-query'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      'base://foo',
 | 
			
		||||
      ['query' => ['bar' => 'baz#']],
 | 
			
		||||
      'Hey giraffe <a href="/foo?bar=baz%23">example</a>',
 | 
			
		||||
    ];
 | 
			
		||||
    $data['unrouted-with-fragment'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      'base://foo',
 | 
			
		||||
      ['fragment' => 'bar<'],
 | 
			
		||||
      'Hey giraffe <a href="/foo#bar&lt;">example</a>',
 | 
			
		||||
    ];
 | 
			
		||||
    $data['mailto-protocol'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      'mailto:test@example.com',
 | 
			
		||||
      [],
 | 
			
		||||
      'Hey giraffe <a href="mailto:test@example.com">example</a>',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @dataProvider providerTestFormattableMarkupUriWithException
 | 
			
		||||
   */
 | 
			
		||||
  public function testFormattableMarkupUriWithExceptionUri($string, $uri): void {
 | 
			
		||||
    // Should throw an \InvalidArgumentException, due to Uri::toString().
 | 
			
		||||
    $this->expectException(\InvalidArgumentException::class);
 | 
			
		||||
    $args = self::getFormattableMarkupUriArgs($uri);
 | 
			
		||||
 | 
			
		||||
    new FormattableMarkup($string, $args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Data provider for testFormattableMarkupUriWithExceptionUri().
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerTestFormattableMarkupUriWithException() {
 | 
			
		||||
    $data = [];
 | 
			
		||||
    $data['js-protocol'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      "javascript:alert('xss')",
 | 
			
		||||
    ];
 | 
			
		||||
    $data['js-with-fromCharCode'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      "javascript:alert(String.fromCharCode(88,83,83))",
 | 
			
		||||
    ];
 | 
			
		||||
    $data['non-url-with-colon'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      "llamas: they are not URLs",
 | 
			
		||||
    ];
 | 
			
		||||
    $data['non-url-with-html'] = [
 | 
			
		||||
      'Hey giraffe <a href=":url">example</a>',
 | 
			
		||||
      '<span>not a url</span>',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,155 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormInterface;
 | 
			
		||||
use Drupal\Core\Form\FormState;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the correct rendering of components in form.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentInFormTest extends ComponentKernelTestBase implements FormInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'sdc_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId(): string {
 | 
			
		||||
    return 'component_in_form_test';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state): array {
 | 
			
		||||
    $form['normal'] = [
 | 
			
		||||
      '#type' => 'textfield',
 | 
			
		||||
      '#title' => 'Normal form element',
 | 
			
		||||
      '#default_value' => 'fake 1',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // We want to test form elements inside a component, itself inside a
 | 
			
		||||
    // component.
 | 
			
		||||
    $form['banner'] = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-banner',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'ctaText' => 'Click me!',
 | 
			
		||||
        'ctaHref' => 'https://www.example.org',
 | 
			
		||||
        'ctaTarget' => '',
 | 
			
		||||
      ],
 | 
			
		||||
      'banner_body' => [
 | 
			
		||||
        '#type' => 'component',
 | 
			
		||||
        '#component' => 'sdc_theme_test:my-card',
 | 
			
		||||
        '#props' => [
 | 
			
		||||
          'header' => 'Card header',
 | 
			
		||||
        ],
 | 
			
		||||
        'card_body' => [
 | 
			
		||||
          'foo' => [
 | 
			
		||||
            '#type' => 'textfield',
 | 
			
		||||
            '#title' => 'Textfield in component',
 | 
			
		||||
            '#default_value' => 'fake 2',
 | 
			
		||||
          ],
 | 
			
		||||
          'bar' => [
 | 
			
		||||
            '#type' => 'select',
 | 
			
		||||
            '#title' => 'Select in component',
 | 
			
		||||
            '#options' => [
 | 
			
		||||
              'option_1' => 'Option 1',
 | 
			
		||||
              'option_2' => 'Option 2',
 | 
			
		||||
            ],
 | 
			
		||||
            '#empty_option' => 'Empty option',
 | 
			
		||||
            '#default_value' => 'option_1',
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['actions'] = [
 | 
			
		||||
      '#type' => 'actions',
 | 
			
		||||
      'submit' => [
 | 
			
		||||
        '#type' => 'submit',
 | 
			
		||||
        '#value' => 'Submit',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state): void {
 | 
			
		||||
    // Check that submitted data are present (set with #default_value).
 | 
			
		||||
    $data = [
 | 
			
		||||
      'normal' => 'fake 1',
 | 
			
		||||
      'foo' => 'fake 2',
 | 
			
		||||
      'bar' => 'option_1',
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($data as $key => $value) {
 | 
			
		||||
      $this->assertSame($value, $form_state->getValue($key));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that fields validation messages are sorted in the fields order.
 | 
			
		||||
   */
 | 
			
		||||
  public function testFormRenderingAndSubmission(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */
 | 
			
		||||
    $form_builder = \Drupal::service('form_builder');
 | 
			
		||||
    /** @var \Drupal\Core\Render\RendererInterface $renderer */
 | 
			
		||||
    $renderer = \Drupal::service('renderer');
 | 
			
		||||
    $form = $form_builder->getForm($this);
 | 
			
		||||
 | 
			
		||||
    // Test form structure after being processed.
 | 
			
		||||
    $this->assertTrue($form['normal']['#processed'], 'The normal textfield should have been processed.');
 | 
			
		||||
    $this->assertTrue($form['banner']['banner_body']['card_body']['bar']['#processed'], 'The textfield inside component should have been processed.');
 | 
			
		||||
    $this->assertTrue($form['banner']['banner_body']['card_body']['foo']['#processed'], 'The select inside component should have been processed.');
 | 
			
		||||
    $this->assertTrue($form['actions']['submit']['#processed'], 'The submit button should have been processed.');
 | 
			
		||||
 | 
			
		||||
    // Test form rendering.
 | 
			
		||||
    $markup = $renderer->renderRoot($form);
 | 
			
		||||
    $this->setRawContent($markup);
 | 
			
		||||
 | 
			
		||||
    // Ensure form elements are rendered once.
 | 
			
		||||
    $this->assertCount(1, $this->cssSelect('input[name="normal"]'), 'The normal textfield should have been rendered once.');
 | 
			
		||||
    $this->assertCount(1, $this->cssSelect('input[name="foo"]'), 'The foo textfield should have been rendered once.');
 | 
			
		||||
    $this->assertCount(1, $this->cssSelect('select[name="bar"]'), 'The bar select should have been rendered once.');
 | 
			
		||||
 | 
			
		||||
    // Check the position of the form elements in the DOM.
 | 
			
		||||
    $paths = [
 | 
			
		||||
      '//form/div[1]/input[@name="normal"]',
 | 
			
		||||
      '//form/div[2][@data-component-id="sdc_test:my-banner"]/div[2][@class="component--my-banner--body"]/div[1][@data-component-id="sdc_theme_test:my-card"]/div[1][@class="component--my-card__body"]/div[1]/input[@name="foo"]',
 | 
			
		||||
      '//form/div[2][@data-component-id="sdc_test:my-banner"]/div[2][@class="component--my-banner--body"]/div[1][@data-component-id="sdc_theme_test:my-card"]/div[1][@class="component--my-card__body"]/div[2]/select[@name="bar"]',
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($paths as $path) {
 | 
			
		||||
      $this->assertNotEmpty($this->xpath($path), 'There should be a result with the path: ' . $path . '.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test form submission. Assertions are in submitForm().
 | 
			
		||||
    $form_state = new FormState();
 | 
			
		||||
    $form_builder->submitForm($this, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\Component\Exception\IncompatibleComponentSchema;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests invalid render options for components.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentInvalidReplacementTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['sdc_test_replacements_invalid'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that component replacement validates the schema compatibility.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidDefinitionTheme(): void {
 | 
			
		||||
    $this->expectException(IncompatibleComponentSchema::class);
 | 
			
		||||
    $this->manager->getDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,102 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\BubbleableMetadata;
 | 
			
		||||
use Drupal\Core\Render\RenderContext;
 | 
			
		||||
use Drupal\Core\Theme\ComponentNegotiator;
 | 
			
		||||
use Drupal\Core\Theme\ComponentPluginManager;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\DomCrawler\Crawler;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a base class for component kernel tests.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ComponentKernelTestBase extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'user',
 | 
			
		||||
    'serialization',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Themes to install.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The component negotiator.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Theme\ComponentNegotiator
 | 
			
		||||
   */
 | 
			
		||||
  protected ComponentNegotiator $negotiator;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The component plugin manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Theme\ComponentPluginManager
 | 
			
		||||
   */
 | 
			
		||||
  protected ComponentPluginManager $manager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    if (empty(static::$themes)) {
 | 
			
		||||
      throw new \Exception('You need to set the protected static $themes property on your test class, with the first item being the default theme.');
 | 
			
		||||
    }
 | 
			
		||||
    $this->container->get('theme_installer')->install(static::$themes);
 | 
			
		||||
    $this->installConfig('system');
 | 
			
		||||
 | 
			
		||||
    $system_theme_config = $this->container->get('config.factory')->getEditable('system.theme');
 | 
			
		||||
    $theme_name = reset(static::$themes);
 | 
			
		||||
    $system_theme_config
 | 
			
		||||
      ->set('default', $theme_name)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $theme_manager = \Drupal::service('theme.manager');
 | 
			
		||||
    $active_theme = \Drupal::service('theme.initialization')->initTheme($theme_name);
 | 
			
		||||
    $theme_manager->setActiveTheme($active_theme);
 | 
			
		||||
 | 
			
		||||
    $this->negotiator = new ComponentNegotiator($theme_manager);
 | 
			
		||||
    $this->manager = \Drupal::service('plugin.manager.sdc');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders a component for testing sake.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $component
 | 
			
		||||
   *   Component render array.
 | 
			
		||||
   * @param \Drupal\Core\Render\BubbleableMetadata|null $metadata
 | 
			
		||||
   *   Bubble metadata.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\DomCrawler\Crawler
 | 
			
		||||
   *   Crawler for introspecting the rendered component.
 | 
			
		||||
   */
 | 
			
		||||
  protected function renderComponentRenderArray(array $component, ?BubbleableMetadata $metadata = NULL): Crawler {
 | 
			
		||||
    $component = [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'id' => 'sdc-wrapper',
 | 
			
		||||
      ],
 | 
			
		||||
      'component' => $component,
 | 
			
		||||
    ];
 | 
			
		||||
    $metadata = $metadata ?: new BubbleableMetadata();
 | 
			
		||||
    $context = new RenderContext();
 | 
			
		||||
    $renderer = \Drupal::service('renderer');
 | 
			
		||||
    $output = $renderer->executeInRenderContext($context, fn () => $renderer->render($component));
 | 
			
		||||
    if (!$context->isEmpty()) {
 | 
			
		||||
      $metadata->addCacheableDependency($context->pop());
 | 
			
		||||
    }
 | 
			
		||||
    return new Crawler((string) $output);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,81 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the component negotiator.
 | 
			
		||||
 *
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Theme\ComponentNegotiator
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentNegotiatorTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'sdc_test',
 | 
			
		||||
    'sdc_test_replacements',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Themes to install.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = [
 | 
			
		||||
    'sdc_theme_test_enforce_schema', 'sdc_theme_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::negotiate
 | 
			
		||||
   */
 | 
			
		||||
  public function testNegotiate(): void {
 | 
			
		||||
    $data = [
 | 
			
		||||
      ['sdc_test:my-banner', NULL],
 | 
			
		||||
      ['sdc_theme_test:my-card', 'sdc_theme_test_enforce_schema:my-card'],
 | 
			
		||||
      [
 | 
			
		||||
        'sdc_test:my-button',
 | 
			
		||||
        'sdc_test_replacements:my-button',
 | 
			
		||||
      ],
 | 
			
		||||
        ['invalid:component', NULL],
 | 
			
		||||
        ['invalid^component', NULL],
 | 
			
		||||
        ['', NULL],
 | 
			
		||||
    ];
 | 
			
		||||
    array_walk($data, function ($test_input) {
 | 
			
		||||
      [$requested_id, $expected_id] = $test_input;
 | 
			
		||||
      $negotiated_id = $this->negotiator->negotiate(
 | 
			
		||||
        $requested_id,
 | 
			
		||||
        $this->manager->getDefinitions(),
 | 
			
		||||
      );
 | 
			
		||||
      $this->assertSame($expected_id, $negotiated_id);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests rendering components with component replacement.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderWithReplacements(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{{ include('sdc_test:my-button') }}",
 | 
			
		||||
      '#context' => ['text' => 'Like!', 'iconType' => 'like'],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper button[data-component-id="sdc_test_replacements:my-button"]'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper button .sdc-id:contains("sdc_test_replacements:my-button")'));
 | 
			
		||||
 | 
			
		||||
    // Now test component replacement on themes.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test:my-card') }}",
 | 
			
		||||
      '#context' => ['header' => 'Foo bar'],
 | 
			
		||||
      '#variant' => 'horizontal',
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper .component--my-card--replaced__body'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the node visitor.
 | 
			
		||||
 *
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Template\ComponentNodeVisitor
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentNodeVisitorTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'sdc_other_node_visitor'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  const DEBUG_COMPONENT_ID_PATTERN = '/<!-- ([\n\s\S]*) Component start: ([\SA-Za-z+-:]+) -->/';
 | 
			
		||||
  const DEBUG_VARIANT_ID_PATTERN = '/<!-- [\n\s\S]* with variant: "([\SA-Za-z+-]+)" -->/';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test that other visitors can modify Twig nodes.
 | 
			
		||||
   */
 | 
			
		||||
  public function testOtherVisitorsCanModifyTwigNodes(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{% embed('sdc_theme_test_base:my-card-no-schema') %}{% block card_body %}Foo bar{% endblock %}{% endembed %}",
 | 
			
		||||
    ];
 | 
			
		||||
    $this->renderComponentRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    // If this is reached, the test passed.
 | 
			
		||||
    $this->assertTrue(TRUE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test debug output for sdc components with component id and variant.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDebugRendersComponentStartWithVariant(): void {
 | 
			
		||||
    // Enable twig theme debug to ensure that any
 | 
			
		||||
    // changes to theme debugging format force checking
 | 
			
		||||
    // that the auto paragraph filter continues to be applied
 | 
			
		||||
    // correctly.
 | 
			
		||||
    $twig = \Drupal::service('twig');
 | 
			
		||||
    $twig->enableDebug();
 | 
			
		||||
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_theme_test:my-card',
 | 
			
		||||
      '#variant' => 'vertical',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'header' => 'My header',
 | 
			
		||||
      ],
 | 
			
		||||
      '#slots' => [
 | 
			
		||||
        'card_body' => 'Foo bar',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $content = $crawler->html();
 | 
			
		||||
 | 
			
		||||
    $matches = [];
 | 
			
		||||
    \preg_match_all(self::DEBUG_COMPONENT_ID_PATTERN, $content, $matches);
 | 
			
		||||
    $this->assertSame($matches[2][0], 'sdc_theme_test:my-card');
 | 
			
		||||
 | 
			
		||||
    \preg_match_all(self::DEBUG_VARIANT_ID_PATTERN, $content, $matches);
 | 
			
		||||
    $this->assertSame($matches[1][0], 'vertical');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests discovery of components in a theme being installed or uninstalled.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentPluginManagerCachedDiscoveryTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['stark'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests cached component plugin discovery on theme install and uninstall.
 | 
			
		||||
   */
 | 
			
		||||
  public function testComponentDiscoveryOnThemeInstall(): void {
 | 
			
		||||
    // Component in sdc_theme should not be found without sdc_theme installed.
 | 
			
		||||
    $definitions = \Drupal::service('plugin.manager.sdc')->getDefinitions();
 | 
			
		||||
    $this->assertArrayNotHasKey('sdc_theme_test:bar', $definitions);
 | 
			
		||||
 | 
			
		||||
    // Component in sdc_theme should be found once sdc_theme is installed.
 | 
			
		||||
    \Drupal::service('theme_installer')->install(['sdc_theme_test']);
 | 
			
		||||
    $definitions = \Drupal::service('plugin.manager.sdc')->getDefinitions();
 | 
			
		||||
    $this->assertArrayHasKey('sdc_theme_test:bar', $definitions);
 | 
			
		||||
 | 
			
		||||
    // Component in sdc_theme should not be found once sdc_theme is uninstalled.
 | 
			
		||||
    \Drupal::service('theme_installer')->uninstall(['sdc_theme_test']);
 | 
			
		||||
    $definitions = \Drupal::service('plugin.manager.sdc')->getDefinitions();
 | 
			
		||||
    $this->assertArrayNotHasKey('sdc_theme_test:bar', $definitions);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\Component\Exception\ComponentNotFoundException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the component plugin manager.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentPluginManagerTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'sdc_test', 'sdc_test_replacements'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test that components render correctly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testFindEmptyMetadataFile(): void {
 | 
			
		||||
    // Test that empty component metadata files are valid, since there is no
 | 
			
		||||
    // required property.
 | 
			
		||||
    $this->assertNotEmpty(
 | 
			
		||||
      $this->manager->find('sdc_theme_test:bar'),
 | 
			
		||||
    );
 | 
			
		||||
    // Test that if the folder name does not match the machine name, the
 | 
			
		||||
    // component is still available.
 | 
			
		||||
    $this->assertNotEmpty(
 | 
			
		||||
      $this->manager->find('sdc_theme_test:foo'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test that the machine name is grabbed from the *.component.yml.
 | 
			
		||||
   *
 | 
			
		||||
   * And not from the enclosing directory.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMismatchingFolderName(): void {
 | 
			
		||||
    $this->expectException(ComponentNotFoundException::class);
 | 
			
		||||
    $this->manager->find('sdc_theme_test:mismatching-folder-name');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\Component\Exception\InvalidComponentException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests invalid render options for components.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentRenderInvalidTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['sdc_test_invalid'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['starterkit_theme'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that components in modules without schema fail validation.
 | 
			
		||||
   *
 | 
			
		||||
   * The module sdc_test_invalid contains the my-card-no-schema component. This
 | 
			
		||||
   * component does not have schema definitions.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidDefinitionModule(): void {
 | 
			
		||||
    $this->expectException(InvalidComponentException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The component "sdc_test_invalid:my-card-no-schema" does not provide schema information. Schema definitions are mandatory for components declared in modules. For components declared in themes, schema definitions are only mandatory if the "enforce_prop_schemas" key is set to "true" in the theme info file.');
 | 
			
		||||
    $this->manager->getDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that components in modules without schema fail validation.
 | 
			
		||||
   *
 | 
			
		||||
   * The theme sdc_theme_test_enforce_schema_invalid is set as enforcing schemas
 | 
			
		||||
   * but provides a component without schema.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidDefinitionTheme(): void {
 | 
			
		||||
    \Drupal::service('theme_installer')->install(['sdc_theme_test_enforce_schema_invalid']);
 | 
			
		||||
    $active_theme = \Drupal::service('theme.initialization')->initTheme('sdc_theme_test_enforce_schema_invalid');
 | 
			
		||||
    \Drupal::service('theme.manager')->setActiveTheme($active_theme);
 | 
			
		||||
    $this->expectException(InvalidComponentException::class);
 | 
			
		||||
    $this->manager->getDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,384 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\BubbleableMetadata;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\Core\Template\Attribute;
 | 
			
		||||
use Drupal\Core\Theme\ComponentPluginManager;
 | 
			
		||||
use Drupal\Core\Render\Component\Exception\InvalidComponentDataException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the correct rendering of components.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentRenderTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'sdc_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check using a component with an include and default context.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderIncludeDefaultContent(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{% embed('sdc_theme_test_base:my-card-no-schema') %}{% block card_body %}Foo bar{% endblock %}{% endembed %}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test_base:my-card-no-schema"] .component--my-card-no-schema__body:contains("Foo bar")'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check using a component with an include and no default context.
 | 
			
		||||
   *
 | 
			
		||||
   * This covers passing a render array to a 'string' prop, and mapping the
 | 
			
		||||
   * prop to a context variable.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderIncludeDataMapping(): void {
 | 
			
		||||
    $content = [
 | 
			
		||||
      'label' => [
 | 
			
		||||
        '#type' => 'html_tag',
 | 
			
		||||
        '#tag' => 'span',
 | 
			
		||||
        '#value' => 'Another button ç',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:my-button', { text: content.label, iconType: 'external' }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper button:contains("Another button ç")'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render a card with slots that include a CTA component.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderEmbedWithNested(): void {
 | 
			
		||||
    $content = [
 | 
			
		||||
      'heading' => [
 | 
			
		||||
        '#type' => 'html_tag',
 | 
			
		||||
        '#tag' => 'span',
 | 
			
		||||
        '#value' => 'Just a link',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{% embed 'sdc_theme_test:my-card' with { variant: 'horizontal', header: 'Card header', content: content } only %}{% block card_body %}This is a card with a CTA {{ include('sdc_test:my-cta', { text: content.heading, href: 'https://www.example.org', target: '_blank' }, with_context = false) }}{% endblock %}{% endembed %}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test:my-card"] h2.component--my-card__header:contains("Card header")'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test:my-card"] .component--my-card__body:contains("This is a card with a CTA")'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test:my-card"] .component--my-card__body a[data-component-id="sdc_test:my-cta"]:contains("Just a link")'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test:my-card"] .component--my-card__body a[data-component-id="sdc_test:my-cta"][href="https://www.example.org"][target="_blank"]'));
 | 
			
		||||
 | 
			
		||||
    // Now render a component and assert it contains the debug comments.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-banner',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'heading' => 'I am a banner',
 | 
			
		||||
        'ctaText' => 'Click me',
 | 
			
		||||
        'ctaHref' => 'https://www.example.org',
 | 
			
		||||
        'ctaTarget' => '',
 | 
			
		||||
      ],
 | 
			
		||||
      '#slots' => [
 | 
			
		||||
        'banner_body' => [
 | 
			
		||||
          '#type' => 'html_tag',
 | 
			
		||||
          '#tag' => 'p',
 | 
			
		||||
          '#value' => 'This is the contents of the banner body.',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $metadata = new BubbleableMetadata();
 | 
			
		||||
    $this->renderComponentRenderArray($build, $metadata);
 | 
			
		||||
    $this->assertEquals(['core/components.sdc_test--my-cta', 'core/components.sdc_test--my-banner'], $metadata->getAttachments()['library']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check using the libraryOverrides.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderLibraryOverrides(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test:lib-overrides') }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $metadata = new BubbleableMetadata();
 | 
			
		||||
    $this->renderComponentRenderArray($build, $metadata);
 | 
			
		||||
    $this->assertEquals(['core/components.sdc_theme_test--lib-overrides'], $metadata->getAttachments()['library']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures the schema violations are reported properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderPropValidation(): void {
 | 
			
		||||
    // 1. Violates the minLength for the text property.
 | 
			
		||||
    $content = ['label' => '1'];
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:my-button', { text: content.label, iconType: 'external' }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    try {
 | 
			
		||||
      $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->fail('Invalid prop did not cause an exception');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Throwable) {
 | 
			
		||||
      $this->addToAssertionCount(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 2. Violates the required header property.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => [],
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test:my-card', with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    try {
 | 
			
		||||
      $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->fail('Invalid prop did not cause an exception');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Throwable) {
 | 
			
		||||
      $this->addToAssertionCount(1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure fuzzy coercing of arrays and objects works properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderArrayObjectTypeCast(): void {
 | 
			
		||||
    $content = ['test' => []];
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:array-to-object', { testProp: content.test }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    try {
 | 
			
		||||
      $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->addToAssertionCount(1);
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Throwable) {
 | 
			
		||||
      $this->fail('Empty array was not converted to object');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures that including an invalid component creates an error.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderNonExistingComponent(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => [],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:INVALID', with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    try {
 | 
			
		||||
      $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->fail('Invalid prop did not cause an exception');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Throwable) {
 | 
			
		||||
      $this->addToAssertionCount(1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures the attributes are merged properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderAttributeMerging(): void {
 | 
			
		||||
    $content = ['label' => 'I am a labels'];
 | 
			
		||||
    // 1. Check that if it exists Attribute object in the 'attributes' prop, you
 | 
			
		||||
    // get them merged.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => [
 | 
			
		||||
        'content' => $content,
 | 
			
		||||
        'attributes' => new Attribute(['data-merged-attributes' => 'yes']),
 | 
			
		||||
      ],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:my-button', { text: content.label, iconType: 'external', attributes: attributes }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-merged-attributes="yes"][data-component-id="sdc_test:my-button"]'), $crawler->outerHtml());
 | 
			
		||||
    // 2. Check that if the 'attributes' exists, but there is some other data
 | 
			
		||||
    // type, then we don't touch it.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => [
 | 
			
		||||
        'content' => $content,
 | 
			
		||||
        'attributes' => 'hard-coded-attr',
 | 
			
		||||
      ],
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test_base:my-card-no-schema', { header: content.label, attributes: attributes }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    // The default data attribute should be missing.
 | 
			
		||||
    $this->assertEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test_base:my-card-no-schema"]'), $crawler->outerHtml());
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [hard-coded-attr]'), $crawler->outerHtml());
 | 
			
		||||
    // 3. Check that if the 'attributes' is empty, we get the defaults.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test_base:my-card-no-schema', { header: content.label }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    // The default data attribute should not be missing.
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test_base:my-card-no-schema"]'), $crawler->outerHtml());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures the alter callbacks work properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderElementAlters(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-banner',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'heading' => 'I am a banner',
 | 
			
		||||
        'ctaText' => 'Click me',
 | 
			
		||||
        'ctaHref' => 'https://www.example.org',
 | 
			
		||||
        'ctaTarget' => '',
 | 
			
		||||
      ],
 | 
			
		||||
      '#propsAlter' => [
 | 
			
		||||
        fn ($props) => [...$props, 'heading' => 'I am another banner'],
 | 
			
		||||
      ],
 | 
			
		||||
      '#slots' => [
 | 
			
		||||
        'banner_body' => [
 | 
			
		||||
          '#type' => 'html_tag',
 | 
			
		||||
          '#tag' => 'p',
 | 
			
		||||
          '#value' => 'This is the contents of the banner body.',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      '#slotsAlter' => [
 | 
			
		||||
        static fn ($slots) => [...$slots, 'banner_body' => ['#markup' => '<h2>Just something else.</h2>']],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_test:my-banner"] .component--my-banner--header h3:contains("I am another banner")'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_test:my-banner"] .component--my-banner--body:contains("Just something else.")'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that the slots allow a render array or a scalar when using the render element.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderSlots(): void {
 | 
			
		||||
    $slots = [
 | 
			
		||||
      'This is the contents of the banner body.',
 | 
			
		||||
      [
 | 
			
		||||
        '#plain_text' => 'This is the contents of the banner body.',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($slots as $slot) {
 | 
			
		||||
      $build = [
 | 
			
		||||
        '#type' => 'component',
 | 
			
		||||
        '#component' => 'sdc_test:my-banner',
 | 
			
		||||
        '#props' => [
 | 
			
		||||
          'heading' => 'I am a banner',
 | 
			
		||||
          'ctaText' => 'Click me',
 | 
			
		||||
          'ctaHref' => 'https://www.example.org',
 | 
			
		||||
          'ctaTarget' => '',
 | 
			
		||||
        ],
 | 
			
		||||
        '#slots' => [
 | 
			
		||||
          'banner_body' => $slot,
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
      $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_test:my-banner"] .component--my-banner--body:contains("This is the contents of the banner body.")'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that the slots throw an error for invalid slots.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderInvalidSlot(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-banner',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'heading' => 'I am a banner',
 | 
			
		||||
        'ctaText' => 'Click me',
 | 
			
		||||
        'ctaHref' => 'https://www.example.org',
 | 
			
		||||
        'ctaTarget' => '',
 | 
			
		||||
      ],
 | 
			
		||||
      '#slots' => [
 | 
			
		||||
        'banner_body' => new \stdClass(),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $this->expectException(InvalidComponentDataException::class);
 | 
			
		||||
    $this->expectExceptionMessage('Unable to render component "sdc_test:my-banner". A render array or a scalar is expected for the slot "banner_body" when using the render element with the "#slots" property');
 | 
			
		||||
    $this->renderComponentRenderArray($build);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that components can have 0 props.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderEmptyProps(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:no-props',
 | 
			
		||||
      '#props' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertEquals(
 | 
			
		||||
      $crawler->filter('#sdc-wrapper')->innerText(),
 | 
			
		||||
      'This is a test string.'
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that components variants render.
 | 
			
		||||
   */
 | 
			
		||||
  public function testVariants(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-cta',
 | 
			
		||||
      '#variant' => 'primary',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'text' => 'Test link',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper a[data-component-id="sdc_test:my-cta"][data-component-variant="primary"][class*="my-cta-primary"]'));
 | 
			
		||||
 | 
			
		||||
    // If there were an existing prop named variant, we don't override that for BC reasons.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-cta-with-variant-prop',
 | 
			
		||||
      '#variant' => 'tertiary',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'text' => 'Test link',
 | 
			
		||||
        'variant' => 'secondary',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertEmpty($crawler->filter('#sdc-wrapper a[data-component-id="sdc_test:my-cta-with-variant-prop"][data-component-variant="tertiary"]'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper a[data-component-id="sdc_test:my-cta-with-variant-prop"][data-component-variant="secondary"]'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures some key aspects of the plugin definition are correctly computed.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 | 
			
		||||
   */
 | 
			
		||||
  public function testPluginDefinition(): void {
 | 
			
		||||
    $plugin_manager = \Drupal::service('plugin.manager.sdc');
 | 
			
		||||
    assert($plugin_manager instanceof ComponentPluginManager);
 | 
			
		||||
    $definition = $plugin_manager->getDefinition('sdc_test:my-banner');
 | 
			
		||||
    $this->assertSame('my-banner', $definition['machineName']);
 | 
			
		||||
    $this->assertStringEndsWith('system/tests/modules/sdc_test/components/my-banner', $definition['path']);
 | 
			
		||||
    $this->assertEquals(['core/drupal'], $definition['library']['dependencies']);
 | 
			
		||||
    $this->assertNotEmpty($definition['library']['css']['component']);
 | 
			
		||||
    $this->assertSame('my-banner.twig', $definition['template']);
 | 
			
		||||
    $this->assertNotEmpty($definition['documentation']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										245
									
								
								web/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								web/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,245 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityDependency;
 | 
			
		||||
use Drupal\Core\Config\FileStorage;
 | 
			
		||||
use Drupal\Core\Config\InstallStorage;
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\KernelTests\AssertConfigTrait;
 | 
			
		||||
use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that the installed config matches the default config.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Config
 | 
			
		||||
 * @group #slow
 | 
			
		||||
 */
 | 
			
		||||
class DefaultConfigTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use AssertConfigTrait;
 | 
			
		||||
  use FileSystemModuleDiscoveryDataProviderTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $timeLimit = 500;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'user', 'path_alias'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The following config entries are changed on module install.
 | 
			
		||||
   *
 | 
			
		||||
   * Comparing them does not make sense.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  public static $skippedConfig = [
 | 
			
		||||
    'locale.settings' => ['path: '],
 | 
			
		||||
    'syslog.settings' => ['facility: '],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests if installed config is equal to the exported config.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider moduleListDataProvider
 | 
			
		||||
   */
 | 
			
		||||
  public function testModuleConfig(string $module): void {
 | 
			
		||||
    $this->assertExtensionConfig($module, 'module');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests if installed config is equal to the exported config.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider themeListDataProvider
 | 
			
		||||
   */
 | 
			
		||||
  public function testThemeConfig($theme): void {
 | 
			
		||||
    $this->assertExtensionConfig($theme, 'theme');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the config provided by the extension is correct.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Extension name.
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   Extension type, either 'module' or 'theme'.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertExtensionConfig(string $name, string $type): void {
 | 
			
		||||
    // Parse .info.yml file for module/theme $name. Since it's not installed at
 | 
			
		||||
    // this point we can't retrieve it from the 'module_handler' service.
 | 
			
		||||
    switch ($name) {
 | 
			
		||||
      case 'test_deprecated_theme':
 | 
			
		||||
        $file_name = DRUPAL_ROOT . '/core/modules/system/tests/themes/' . $name . '/' . $name . '.info.yml';
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'deprecated_module':
 | 
			
		||||
        $file_name = DRUPAL_ROOT . '/core/modules/system/tests/modules/' . $name . '/' . $name . '.info.yml';
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        $file_name = DRUPAL_ROOT . '/core/' . $type . 's/' . $name . '/' . $name . '.info.yml';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $info = \Drupal::service('info_parser')->parse($file_name);
 | 
			
		||||
    // Test we have a parsed info.yml file.
 | 
			
		||||
    $this->assertNotEmpty($info);
 | 
			
		||||
 | 
			
		||||
    // Skip deprecated extensions.
 | 
			
		||||
    if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])
 | 
			
		||||
      && $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
 | 
			
		||||
      $this->markTestSkipped("The $type '$name' is deprecated.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // System and user are required in order to be able to install some of the
 | 
			
		||||
    // other modules. Therefore they are put into static::$modules, which though
 | 
			
		||||
    // doesn't install config files, so import those config files explicitly. Do
 | 
			
		||||
    // this for all tests in case optional configuration depends on it.
 | 
			
		||||
    $this->installConfig(['system', 'user']);
 | 
			
		||||
 | 
			
		||||
    $extension_path = \Drupal::service('extension.path.resolver')->getPath($type, $name) . '/';
 | 
			
		||||
    $extension_config_storage = new FileStorage($extension_path . InstallStorage::CONFIG_INSTALL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION);
 | 
			
		||||
    $optional_config_storage = new FileStorage($extension_path . InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION);
 | 
			
		||||
 | 
			
		||||
    if (empty($optional_config_storage->listAll()) && empty($extension_config_storage->listAll())) {
 | 
			
		||||
      $this->markTestSkipped("$name has no configuration to test");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Work out any additional modules and themes that need installing to create
 | 
			
		||||
    // an optional config.
 | 
			
		||||
    $modules_to_install = $type !== 'theme' ? [$name] : [];
 | 
			
		||||
    $themes_to_install = $type === 'theme' ? [$name] : [];
 | 
			
		||||
    foreach ($optional_config_storage->listAll() as $config_name) {
 | 
			
		||||
      $data = $optional_config_storage->read($config_name);
 | 
			
		||||
      $dependency = new ConfigEntityDependency($config_name, $data);
 | 
			
		||||
      $modules_to_install = array_merge($modules_to_install, $dependency->getDependencies('module'));
 | 
			
		||||
      $themes_to_install = array_merge($themes_to_install, $dependency->getDependencies('theme'));
 | 
			
		||||
    }
 | 
			
		||||
    // Remove core and standard because they cannot be installed.
 | 
			
		||||
    $modules_to_install = array_diff(array_unique($modules_to_install), ['core', 'standard']);
 | 
			
		||||
    $this->container->get('module_installer')->install($modules_to_install);
 | 
			
		||||
    $this->container->get('theme_installer')->install(array_unique($themes_to_install));
 | 
			
		||||
 | 
			
		||||
    // Test configuration in the extension's config/install directory.
 | 
			
		||||
    $this->doTestsOnConfigStorage($extension_config_storage, $name, $type);
 | 
			
		||||
 | 
			
		||||
    // Test configuration in the extension's config/optional directory.
 | 
			
		||||
    $this->doTestsOnConfigStorage($optional_config_storage, $name, $type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A data provider that lists every theme in core.
 | 
			
		||||
   *
 | 
			
		||||
   * Also adds a deprecated theme with config.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[][]
 | 
			
		||||
   *   An array of theme names to test, with both key and value being the name
 | 
			
		||||
   *   of the theme.
 | 
			
		||||
   */
 | 
			
		||||
  public static function themeListDataProvider() {
 | 
			
		||||
    $prefix = dirname(__DIR__, 4) . DIRECTORY_SEPARATOR . 'themes';
 | 
			
		||||
    $theme_dirs = array_keys(iterator_to_array(new \FilesystemIterator($prefix)));
 | 
			
		||||
    $theme_names = array_map(function ($path) use ($prefix) {
 | 
			
		||||
      return str_replace($prefix . DIRECTORY_SEPARATOR, '', $path);
 | 
			
		||||
    }, $theme_dirs);
 | 
			
		||||
    $themes_keyed = array_combine($theme_names, $theme_names);
 | 
			
		||||
 | 
			
		||||
    // Engines is not a theme.
 | 
			
		||||
    unset($themes_keyed['engines']);
 | 
			
		||||
 | 
			
		||||
    // Add a deprecated theme with config.
 | 
			
		||||
    $themes_keyed['test_deprecated_theme'] = 'test_deprecated_theme';
 | 
			
		||||
 | 
			
		||||
    return array_map(function ($theme) {
 | 
			
		||||
      return [$theme];
 | 
			
		||||
    }, $themes_keyed);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A data provider that lists every module in core.
 | 
			
		||||
   *
 | 
			
		||||
   * Also adds a deprecated module with config.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[][]
 | 
			
		||||
   *   An array of module names to test, with both key and value being the name
 | 
			
		||||
   *   of the module.
 | 
			
		||||
   */
 | 
			
		||||
  public static function moduleListDataProvider(): array {
 | 
			
		||||
    $modules_keyed = self::coreModuleListDataProvider();
 | 
			
		||||
 | 
			
		||||
    // Add a deprecated module with config.
 | 
			
		||||
    $modules_keyed['deprecated_module'] = ['deprecated_module'];
 | 
			
		||||
 | 
			
		||||
    return $modules_keyed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that default config matches the installed config.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $default_config_storage
 | 
			
		||||
   *   The default config storage to test.
 | 
			
		||||
   * @param string $extension
 | 
			
		||||
   *   The extension that is being tested.
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   The extension type to test.
 | 
			
		||||
   */
 | 
			
		||||
  protected function doTestsOnConfigStorage(StorageInterface $default_config_storage, $extension, string $type = 'module'): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
 | 
			
		||||
    $config_manager = $this->container->get('config.manager');
 | 
			
		||||
 | 
			
		||||
    // Just connect directly to the config table so we don't need to worry about
 | 
			
		||||
    // the cache layer.
 | 
			
		||||
    $active_config_storage = $this->container->get('config.storage');
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
 | 
			
		||||
    $config_factory = $this->container->get('config.factory');
 | 
			
		||||
 | 
			
		||||
    foreach ($default_config_storage->listAll() as $config_name) {
 | 
			
		||||
      if ($active_config_storage->exists($config_name)) {
 | 
			
		||||
        // If it is a config entity re-save it. This ensures that any
 | 
			
		||||
        // recalculation of dependencies does not cause config change.
 | 
			
		||||
        if ($entity_type = $config_manager->getEntityTypeIdByName($config_name)) {
 | 
			
		||||
          $entity_storage = $config_manager
 | 
			
		||||
            ->getEntityTypeManager()
 | 
			
		||||
            ->getStorage($entity_type);
 | 
			
		||||
          $id = $entity_storage->getIDFromConfigName($config_name, $entity_storage->getEntityType()
 | 
			
		||||
            ->getConfigPrefix());
 | 
			
		||||
          $entity_storage->load($id)->calculateDependencies()->save();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // Ensure simple configuration is re-saved so any schema sorting is
 | 
			
		||||
          // applied.
 | 
			
		||||
          $config_factory->getEditable($config_name)->save();
 | 
			
		||||
        }
 | 
			
		||||
        $result = $config_manager->diff($default_config_storage, $active_config_storage, $config_name);
 | 
			
		||||
        // ::assertConfigDiff will throw an exception if the configuration is
 | 
			
		||||
        // different.
 | 
			
		||||
        $this->assertNull($this->assertConfigDiff($result, $config_name, static::$skippedConfig));
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $data = $default_config_storage->read($config_name);
 | 
			
		||||
        $dependency = new ConfigEntityDependency($config_name, $data);
 | 
			
		||||
        if ($dependency->hasDependency('module', 'standard')) {
 | 
			
		||||
          // Skip configuration with a dependency on the standard profile. Such
 | 
			
		||||
          // configuration has probably been removed from the standard profile
 | 
			
		||||
          // and needs its own test.
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $info = $this->container->get("extension.list.$type")->getExtensionInfo($extension);
 | 
			
		||||
        if (!isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]) || $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
          $this->fail("$config_name provided by $extension does not exist after installing all dependencies");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										564
									
								
								web/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										564
									
								
								web/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,564 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Config\Schema;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore childkey
 | 
			
		||||
 | 
			
		||||
use Drupal\block\Entity\Block;
 | 
			
		||||
use Drupal\Core\Config\Schema\Mapping;
 | 
			
		||||
use Drupal\Core\TypedData\MapDataDefinition;
 | 
			
		||||
use Drupal\editor\Entity\Editor;
 | 
			
		||||
use Drupal\field\Entity\FieldConfig;
 | 
			
		||||
use Drupal\filter\Entity\FilterFormat;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Config\Schema\Mapping
 | 
			
		||||
 * @group Config
 | 
			
		||||
 */
 | 
			
		||||
class MappingTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $configSchemaCheckerExclusions = [
 | 
			
		||||
    'config_schema_deprecated_test.settings',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @dataProvider providerMappingInterpretation
 | 
			
		||||
   */
 | 
			
		||||
  public function testMappingInterpretation(
 | 
			
		||||
    string $config_name,
 | 
			
		||||
    ?string $property_path,
 | 
			
		||||
    array $expected_valid_keys,
 | 
			
		||||
    array $expected_optional_keys,
 | 
			
		||||
    array $expected_dynamically_valid_keys,
 | 
			
		||||
  ): void {
 | 
			
		||||
    // Some config needs some dependencies installed.
 | 
			
		||||
    switch ($config_name) {
 | 
			
		||||
      case 'block.block.branding':
 | 
			
		||||
        $this->enableModules(['system', 'block']);
 | 
			
		||||
        /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
 | 
			
		||||
        $theme_installer = $this->container->get('theme_installer');
 | 
			
		||||
        $theme_installer->install(['stark']);
 | 
			
		||||
        Block::create([
 | 
			
		||||
          'id' => 'branding',
 | 
			
		||||
          'plugin' => 'system_branding_block',
 | 
			
		||||
          'theme' => 'stark',
 | 
			
		||||
          'status' => TRUE,
 | 
			
		||||
          'settings' => [
 | 
			
		||||
            'use_site_logo' => TRUE,
 | 
			
		||||
            'use_site_name' => TRUE,
 | 
			
		||||
            'use_site_slogan' => TRUE,
 | 
			
		||||
            'label_display' => FALSE,
 | 
			
		||||
            // This is inherited from `type: block_settings`.
 | 
			
		||||
            'context_mapping' => [],
 | 
			
		||||
          ],
 | 
			
		||||
        ])->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'block.block.local_tasks':
 | 
			
		||||
        $this->enableModules(['system', 'block']);
 | 
			
		||||
        /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
 | 
			
		||||
        $theme_installer = $this->container->get('theme_installer');
 | 
			
		||||
        $theme_installer->install(['stark']);
 | 
			
		||||
        Block::create([
 | 
			
		||||
          'id' => 'local_tasks',
 | 
			
		||||
          'plugin' => 'local_tasks_block',
 | 
			
		||||
          'theme' => 'stark',
 | 
			
		||||
          'status' => TRUE,
 | 
			
		||||
          'settings' => [
 | 
			
		||||
            'primary' => TRUE,
 | 
			
		||||
            'secondary' => FALSE,
 | 
			
		||||
            // This is inherited from `type: block_settings`.
 | 
			
		||||
            'context_mapping' => [],
 | 
			
		||||
          ],
 | 
			
		||||
        ])->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'block.block.positively_powered___alternate_reality_with_fallback_type___':
 | 
			
		||||
        $this->enableModules(['config_schema_add_fallback_type_test']);
 | 
			
		||||
        $id = 'positively_powered___alternate_reality_with_fallback_type___';
 | 
			
		||||
      case 'block.block.positively_powered':
 | 
			
		||||
        $this->enableModules(['system', 'block']);
 | 
			
		||||
        /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
 | 
			
		||||
        $theme_installer = $this->container->get('theme_installer');
 | 
			
		||||
        $theme_installer->install(['stark']);
 | 
			
		||||
        Block::create([
 | 
			
		||||
          'id' => $id ?? 'positively_powered',
 | 
			
		||||
          'plugin' => 'system_powered_by_block',
 | 
			
		||||
          'theme' => 'stark',
 | 
			
		||||
          'status' => TRUE,
 | 
			
		||||
          'settings' => [
 | 
			
		||||
            'label_display' => FALSE,
 | 
			
		||||
            // This is inherited from `type: block_settings`.
 | 
			
		||||
            'context_mapping' => [],
 | 
			
		||||
          ],
 | 
			
		||||
          // Avoid showing "Powered by Drupal" on 404 responses.
 | 
			
		||||
          'visibility' => [
 | 
			
		||||
            'I_CAN_CHOOSE_THIS' => [
 | 
			
		||||
              // This is what determines the
 | 
			
		||||
              'id' => 'response_status',
 | 
			
		||||
              'negate' => FALSE,
 | 
			
		||||
              'status_codes' => [
 | 
			
		||||
                404,
 | 
			
		||||
              ],
 | 
			
		||||
            ],
 | 
			
		||||
          ],
 | 
			
		||||
        ])->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'config_schema_deprecated_test.settings':
 | 
			
		||||
        $this->enableModules(['config_schema_deprecated_test']);
 | 
			
		||||
        $config = $this->config('config_schema_deprecated_test.settings');
 | 
			
		||||
        // @see \Drupal\KernelTests\Core\Config\ConfigSchemaDeprecationTest
 | 
			
		||||
        $config
 | 
			
		||||
          ->set('complex_structure_deprecated.type', 'fruits')
 | 
			
		||||
          ->set('complex_structure_deprecated.products', ['apricot', 'apple'])
 | 
			
		||||
          ->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'editor.editor.funky':
 | 
			
		||||
        $this->enableModules(['filter', 'editor', 'ckeditor5']);
 | 
			
		||||
        FilterFormat::create(['format' => 'funky', 'name' => 'Funky'])->save();
 | 
			
		||||
        Editor::create([
 | 
			
		||||
          'format' => 'funky',
 | 
			
		||||
          'editor' => 'ckeditor5',
 | 
			
		||||
          'image_upload' => [
 | 
			
		||||
            'status' => FALSE,
 | 
			
		||||
          ],
 | 
			
		||||
        ])->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'field.field.node.config_mapping_test.comment_config_mapping_test':
 | 
			
		||||
        $this->enableModules(['user', 'field', 'node', 'comment', 'taxonomy', 'config_mapping_test']);
 | 
			
		||||
        $this->installEntitySchema('user');
 | 
			
		||||
        $this->installEntitySchema('node');
 | 
			
		||||
        $this->assertNull(FieldConfig::load('node.config_mapping_test.comment_config_mapping_test'));
 | 
			
		||||
        // \Drupal\node\Entity\NodeType::$preview_mode uses DRUPAL_OPTIONAL,
 | 
			
		||||
        // which is defined in system.module.
 | 
			
		||||
        require_once 'core/modules/system/system.module';
 | 
			
		||||
        $this->installConfig(['config_mapping_test']);
 | 
			
		||||
        $this->assertNotNull(FieldConfig::load('node.config_mapping_test.comment_config_mapping_test'));
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
 | 
			
		||||
    $typed_config_manager = \Drupal::service('config.typed');
 | 
			
		||||
    $mapping = $typed_config_manager->get($config_name);
 | 
			
		||||
    if ($property_path) {
 | 
			
		||||
      $mapping = $mapping->get($property_path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assert($mapping instanceof Mapping);
 | 
			
		||||
    $expected_required_keys = array_values(array_diff($expected_valid_keys, $expected_optional_keys));
 | 
			
		||||
    $this->assertSame($expected_valid_keys, $mapping->getValidKeys());
 | 
			
		||||
    $this->assertSame($expected_required_keys, $mapping->getRequiredKeys());
 | 
			
		||||
    $this->assertSame($expected_dynamically_valid_keys, $mapping->getDynamicallyValidKeys());
 | 
			
		||||
    $this->assertSame($expected_optional_keys, $mapping->getOptionalKeys());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides test cases for all kinds of i) dynamic typing, ii) optional keys.
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://www.drupal.org/files/ConfigSchemaCheatSheet2.0.pdf
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Generator
 | 
			
		||||
   *   The test cases.
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerMappingInterpretation(): \Generator {
 | 
			
		||||
    $available_block_settings_types = [
 | 
			
		||||
      'block.settings.field_block:*:*:*' => [
 | 
			
		||||
        'formatter',
 | 
			
		||||
      ],
 | 
			
		||||
      'block.settings.extra_field_block:*:*:*' => [
 | 
			
		||||
        'formatter',
 | 
			
		||||
      ],
 | 
			
		||||
      'block.settings.system_branding_block' => [
 | 
			
		||||
        'use_site_logo',
 | 
			
		||||
        'use_site_name',
 | 
			
		||||
        'use_site_slogan',
 | 
			
		||||
      ],
 | 
			
		||||
      'block.settings.system_menu_block:*' => [
 | 
			
		||||
        'level',
 | 
			
		||||
        'depth',
 | 
			
		||||
        'expand_all_items',
 | 
			
		||||
      ],
 | 
			
		||||
      'block.settings.local_tasks_block' => [
 | 
			
		||||
        'primary',
 | 
			
		||||
        'secondary',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // A simple config often is just a single Mapping object.
 | 
			
		||||
    yield 'No dynamic type: core.extension' => [
 | 
			
		||||
      'core.extension',
 | 
			
		||||
      NULL,
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: config_object`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        '_core',
 | 
			
		||||
        'langcode',
 | 
			
		||||
        // Keys defined locally, in `type: core.extension`.
 | 
			
		||||
        // @see core/config/schema/core.extension.schema.yml
 | 
			
		||||
        'module',
 | 
			
		||||
        'theme',
 | 
			
		||||
        'profile',
 | 
			
		||||
      ],
 | 
			
		||||
      [
 | 
			
		||||
        '_core',
 | 
			
		||||
        'langcode',
 | 
			
		||||
        'profile',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Special case: deprecated  is needed for deprecated config schema:
 | 
			
		||||
    // - deprecated keys are treated as optional
 | 
			
		||||
    // - if a deprecated property path is itself a mapping, then the keys inside
 | 
			
		||||
    //   are not optional
 | 
			
		||||
    yield 'No dynamic type: config_schema_deprecated_test.settings' => [
 | 
			
		||||
      'config_schema_deprecated_test.settings',
 | 
			
		||||
      NULL,
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: config_object`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        '_core',
 | 
			
		||||
        'langcode',
 | 
			
		||||
        // Keys defined locally, in `type:
 | 
			
		||||
        // config_schema_deprecated_test.settings`.
 | 
			
		||||
        // @see core/modules/config/tests/config_schema_deprecated_test/config/schema/config_schema_deprecated_test.schema.yml
 | 
			
		||||
        'complex_structure_deprecated',
 | 
			
		||||
      ],
 | 
			
		||||
      ['_core', 'langcode', 'complex_structure_deprecated'],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
    yield 'No dynamic type: config_schema_deprecated_test.settings:complex_structure_deprecated' => [
 | 
			
		||||
      'config_schema_deprecated_test.settings',
 | 
			
		||||
      'complex_structure_deprecated',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys defined locally, in `type:
 | 
			
		||||
        // config_schema_deprecated_test.settings`.
 | 
			
		||||
        // @see core/modules/config/tests/config_schema_deprecated_test/config/schema/config_schema_deprecated_test.schema.yml
 | 
			
		||||
        'type',
 | 
			
		||||
        'products',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // A config entity is always a Mapping at the top level, but most nesting is
 | 
			
		||||
    // also using Mappings (unless the keys are free to be chosen, then a
 | 
			
		||||
    // Sequence would be used).
 | 
			
		||||
    yield 'No dynamic type: block.block.branding' => [
 | 
			
		||||
      'block.block.branding',
 | 
			
		||||
      NULL,
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: config_entity`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'uuid',
 | 
			
		||||
        'langcode',
 | 
			
		||||
        'status',
 | 
			
		||||
        'dependencies',
 | 
			
		||||
        'third_party_settings',
 | 
			
		||||
        '_core',
 | 
			
		||||
        // Keys defined locally, in `type: block.block.*`.
 | 
			
		||||
        // @see core/modules/block/config/schema/block.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'theme',
 | 
			
		||||
        'region',
 | 
			
		||||
        'weight',
 | 
			
		||||
        'provider',
 | 
			
		||||
        'plugin',
 | 
			
		||||
        'settings',
 | 
			
		||||
        'visibility',
 | 
			
		||||
      ],
 | 
			
		||||
      ['third_party_settings', '_core'],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // An example of nested Mapping objects in config entities.
 | 
			
		||||
    yield 'No dynamic type: block.block.branding:dependencies' => [
 | 
			
		||||
      'block.block.branding',
 | 
			
		||||
      'dependencies',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: config_dependencies_base`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'config',
 | 
			
		||||
        'content',
 | 
			
		||||
        'module',
 | 
			
		||||
        'theme',
 | 
			
		||||
        // Keys defined locally, in `type: config_dependencies`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'enforced',
 | 
			
		||||
      ],
 | 
			
		||||
      // All these keys are optional!
 | 
			
		||||
      ['config', 'content', 'module', 'theme', 'enforced'],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Three examples of `[%parent]`-based dynamic typing in config schema, and
 | 
			
		||||
    // the consequences on what keys are considered valid: the first 2 depend
 | 
			
		||||
    // on the block plugin being used using a single `%parent`, the third
 | 
			
		||||
    // depends on the field plugin being used using a double `%parent`.
 | 
			
		||||
    // See `type: block.block.*` which uses
 | 
			
		||||
    // `type: block.settings.[%parent.plugin]`, and `type: field_config_base`
 | 
			
		||||
    // which uses `type: field.value.[%parent.%parent.field_type]`.
 | 
			
		||||
    yield 'Dynamic type with [%parent]: block.block.branding:settings' => [
 | 
			
		||||
      'block.block.branding',
 | 
			
		||||
      'settings',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: block.settings.*`, which in turn is
 | 
			
		||||
        // inherited from `type: block_settings`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'label',
 | 
			
		||||
        'label_display',
 | 
			
		||||
        'provider',
 | 
			
		||||
        'context_mapping',
 | 
			
		||||
        // Keys defined locally, in `type:
 | 
			
		||||
        // block.settings.system_branding_block`.
 | 
			
		||||
        // @see core/modules/block/config/schema/block.schema.yml
 | 
			
		||||
        ...$available_block_settings_types['block.settings.system_branding_block'],
 | 
			
		||||
      ],
 | 
			
		||||
      // This key is optional, see `type: block_settings`.
 | 
			
		||||
      // @see core.data_types.schema.yml
 | 
			
		||||
      ['context_mapping'],
 | 
			
		||||
      $available_block_settings_types,
 | 
			
		||||
    ];
 | 
			
		||||
    yield 'Dynamic type with [%parent]: block.block.local_tasks:settings' => [
 | 
			
		||||
      'block.block.local_tasks',
 | 
			
		||||
      'settings',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: block.settings.*`, which in turn is
 | 
			
		||||
        // inherited from `type: block_settings`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'label',
 | 
			
		||||
        'label_display',
 | 
			
		||||
        'provider',
 | 
			
		||||
        'context_mapping',
 | 
			
		||||
        // Keys defined locally, in `type: block.settings.local_tasks_block`.
 | 
			
		||||
        // @see core/modules/system/config/schema/system.schema.yml
 | 
			
		||||
        ...$available_block_settings_types['block.settings.local_tasks_block'],
 | 
			
		||||
      ],
 | 
			
		||||
      // This key is optional, see `type: block_settings`.
 | 
			
		||||
      // @see core.data_types.schema.yml
 | 
			
		||||
      ['context_mapping'],
 | 
			
		||||
      $available_block_settings_types,
 | 
			
		||||
    ];
 | 
			
		||||
    yield 'Dynamic type with [%parent.%parent]: field.field.node.config_mapping_test.comment_config_mapping_test:default_value.0' => [
 | 
			
		||||
      'field.field.node.config_mapping_test.comment_config_mapping_test',
 | 
			
		||||
      'default_value.0',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys defined locally, in `type: field.value.comment`.
 | 
			
		||||
        // @see core/modules/comment/config/schema/comment.schema.yml
 | 
			
		||||
        'status',
 | 
			
		||||
        'cid',
 | 
			
		||||
        'last_comment_timestamp',
 | 
			
		||||
        'last_comment_name',
 | 
			
		||||
        'last_comment_uid',
 | 
			
		||||
        'comment_count',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      [
 | 
			
		||||
        'field.value.string' => ['value'],
 | 
			
		||||
        'field.value.string_long' => ['value'],
 | 
			
		||||
        'field.value.uri' => ['value'],
 | 
			
		||||
        'field.value.created' => ['value'],
 | 
			
		||||
        'field.value.changed' => ['value'],
 | 
			
		||||
        'field.value.entity_reference' => ['target_id', 'target_uuid'],
 | 
			
		||||
        'field.value.boolean' => ['value'],
 | 
			
		||||
        'field.value.email' => ['value'],
 | 
			
		||||
        'field.value.integer' => ['value'],
 | 
			
		||||
        'field.value.decimal' => ['value'],
 | 
			
		||||
        'field.value.float' => ['value'],
 | 
			
		||||
        'field.value.timestamp' => ['value'],
 | 
			
		||||
        'field.value.language' => ['value'],
 | 
			
		||||
        'field.value.comment' => [
 | 
			
		||||
          'status',
 | 
			
		||||
          'cid',
 | 
			
		||||
          'last_comment_timestamp',
 | 
			
		||||
          'last_comment_name',
 | 
			
		||||
          'last_comment_uid',
 | 
			
		||||
          'comment_count',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // An example of `[childkey]`-based dynamic mapping typing in config schema,
 | 
			
		||||
    // for a mapping inside a sequence: the `id` key-value pair in the mapping
 | 
			
		||||
    // determines the type of the mapping. The key in the sequence whose value
 | 
			
		||||
    // is the mapping is irrelevant, it can be arbitrarily chosen.
 | 
			
		||||
    // See `type: block.block.*` which uses `type: condition.plugin.[id]`.
 | 
			
		||||
    yield 'Dynamic type with [childkey]: block.block.positively_powered:visibility.I_CAN_CHOOSE_THIS' => [
 | 
			
		||||
      'block.block.positively_powered',
 | 
			
		||||
      'visibility.I_CAN_CHOOSE_THIS',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: condition.plugin`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'negate',
 | 
			
		||||
        'uuid',
 | 
			
		||||
        'context_mapping',
 | 
			
		||||
        // Keys defined locally, in `type: condition.plugin.response_status`.
 | 
			
		||||
        // @see core/modules/system/config/schema/system.schema.yml
 | 
			
		||||
        'status_codes',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      // Note the presence of `id`, `negate`, `uuid` and `context_mapping` here.
 | 
			
		||||
      // That's because there is no `condition.plugin.*` type that specifies
 | 
			
		||||
      // defaults. Each individual condition plugin has the freedom to deviate
 | 
			
		||||
      // from this approach!
 | 
			
		||||
      [
 | 
			
		||||
        'condition.plugin.entity_bundle:*' => [
 | 
			
		||||
          'id',
 | 
			
		||||
          'negate',
 | 
			
		||||
          'uuid',
 | 
			
		||||
          'context_mapping',
 | 
			
		||||
          'bundles',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.request_path' => [
 | 
			
		||||
          'id',
 | 
			
		||||
          'negate',
 | 
			
		||||
          'uuid',
 | 
			
		||||
          'context_mapping',
 | 
			
		||||
          'pages',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.response_status' => [
 | 
			
		||||
          'id',
 | 
			
		||||
          'negate',
 | 
			
		||||
          'uuid',
 | 
			
		||||
          'context_mapping',
 | 
			
		||||
          'status_codes',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.current_theme' => [
 | 
			
		||||
          'id',
 | 
			
		||||
          'negate',
 | 
			
		||||
          'uuid',
 | 
			
		||||
          'context_mapping',
 | 
			
		||||
          'theme',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    // Same, but what if `type: condition.plugin.*` would have existed?
 | 
			
		||||
    // @see core/modules/config/tests/config_schema_add_fallback_type_test/config/schema/config_schema_add_fallback_type_test.schema.yml
 | 
			
		||||
    yield 'Dynamic type with [childkey]: block.block.positively_powered___alternate_reality_with_fallback_type___:visibility' => [
 | 
			
		||||
      'block.block.positively_powered___alternate_reality_with_fallback_type___',
 | 
			
		||||
      'visibility.I_CAN_CHOOSE_THIS',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: condition.plugin`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'negate',
 | 
			
		||||
        'uuid',
 | 
			
		||||
        'context_mapping',
 | 
			
		||||
        // Keys defined locally, in `type: condition.plugin.response_status`.
 | 
			
		||||
        // @see core/modules/system/config/schema/system.schema.yml
 | 
			
		||||
        'status_codes',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      // Note the ABSENCE of `id`, `negate`, `uuid` and `context_mapping`
 | 
			
		||||
      // compared to the previous test case, because now the
 | 
			
		||||
      // `condition.plugin.*` type does exist.
 | 
			
		||||
      [
 | 
			
		||||
        'condition.plugin.entity_bundle:*' => [
 | 
			
		||||
          'bundles',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.request_path' => [
 | 
			
		||||
          'pages',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.response_status' => [
 | 
			
		||||
          'status_codes',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.current_theme' => [
 | 
			
		||||
          'theme',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // An example of `[%key]`-based dynamic mapping typing in config schema: the
 | 
			
		||||
    // key in the sequence determines the type of the mapping. Unlike the above
 | 
			
		||||
    // `[childkey]` example, the key has meaning here.
 | 
			
		||||
    // See `type: editor.settings.ckeditor5`, which uses
 | 
			
		||||
    // `type: ckeditor5.plugin.[%key]`.
 | 
			
		||||
    yield 'Dynamic type with [%key]: editor.editor.funky:settings.plugins.ckeditor5_heading' => [
 | 
			
		||||
      'editor.editor.funky',
 | 
			
		||||
      'settings.plugins.ckeditor5_heading',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys defined locally, in `type: ckeditor5.plugin.ckeditor5_heading`.
 | 
			
		||||
        // @see core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
 | 
			
		||||
        'enabled_headings',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      [
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_language' => ['language_list'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_heading' => ['enabled_headings'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_imageResize' => ['allow_resize'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_sourceEditing' => ['allowed_tags'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_alignment' => ['enabled_alignments'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_list' => ['properties', 'multiBlock'],
 | 
			
		||||
        'ckeditor5.plugin.media_media' => ['allow_view_mode_override'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_codeBlock' => ['languages'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_style' => ['styles'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @testWith [false, 42, "The mapping definition at `foobar` is invalid: its `invalid` key contains a integer. It must be an array."]
 | 
			
		||||
   *           [false, 10.2, "The mapping definition at `foobar` is invalid: its `invalid` key contains a double. It must be an array."]
 | 
			
		||||
   *           [false, "type", "The mapping definition at `foobar` is invalid: its `invalid` key contains a string. It must be an array."]
 | 
			
		||||
   *           [false, false, "The mapping definition at `foobar` is invalid: its `invalid` key contains a boolean. It must be an array."]
 | 
			
		||||
   *           [true, 42, "The mapping definition at `my_module.settings:foobar` is invalid: its `invalid` key contains a integer. It must be an array."]
 | 
			
		||||
   *           [true, 10.2, "The mapping definition at `my_module.settings:foobar` is invalid: its `invalid` key contains a double. It must be an array."]
 | 
			
		||||
   *           [true, "type", "The mapping definition at `my_module.settings:foobar` is invalid: its `invalid` key contains a string. It must be an array."]
 | 
			
		||||
   *           [true, false, "The mapping definition at `my_module.settings:foobar` is invalid: its `invalid` key contains a boolean. It must be an array."]
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidMappingKeyDefinition(bool $has_parent, mixed $invalid_key_definition, string $expected_message): void {
 | 
			
		||||
    $definition = new MapDataDefinition([
 | 
			
		||||
      'type' => 'mapping',
 | 
			
		||||
      'mapping' => [
 | 
			
		||||
        'valid' => [
 | 
			
		||||
          'type' => 'boolean',
 | 
			
		||||
          'label' => 'This is a valid key-value pair in this mapping',
 | 
			
		||||
        ],
 | 
			
		||||
        'invalid' => $invalid_key_definition,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $parent = NULL;
 | 
			
		||||
    if ($has_parent) {
 | 
			
		||||
      $parent = new Mapping(
 | 
			
		||||
        new MapDataDefinition(['type' => 'mapping', 'mapping' => []]),
 | 
			
		||||
        'my_module.settings',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    $this->expectException(\LogicException::class);
 | 
			
		||||
    $this->expectExceptionMessage($expected_message);
 | 
			
		||||
    new Mapping($definition, 'foobar', $parent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @testWith [true]
 | 
			
		||||
   *           [1]
 | 
			
		||||
   *           ["true"]
 | 
			
		||||
   *           [0]
 | 
			
		||||
   *           ["false"]
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidRequiredKeyFlag(mixed $required_key_flag_value): void {
 | 
			
		||||
    $this->expectException(\LogicException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The `requiredKey` flag must either be omitted or have `false` as the value.');
 | 
			
		||||
    new Mapping(new MapDataDefinition([
 | 
			
		||||
      'type' => 'mapping',
 | 
			
		||||
      'mapping' => [
 | 
			
		||||
        'something' => [
 | 
			
		||||
          'requiredKey' => $required_key_flag_value,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ]));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										231
									
								
								web/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								web/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,231 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Schema\Sequence;
 | 
			
		||||
use Drupal\Core\Config\Schema\SequenceDataDefinition;
 | 
			
		||||
use Drupal\Core\Config\Schema\TypedConfigInterface;
 | 
			
		||||
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
 | 
			
		||||
use Drupal\Core\TypedData\ComplexDataInterface;
 | 
			
		||||
use Drupal\Core\TypedData\Type\IntegerInterface;
 | 
			
		||||
use Drupal\Core\TypedData\Type\StringInterface;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore nyans
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests config validation mechanism.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Config
 | 
			
		||||
 */
 | 
			
		||||
class TypedConfigTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $configSchemaCheckerExclusions = ['config_test.validation'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Verifies that the Typed Data API is implemented correctly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTypedDataAPI(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
 | 
			
		||||
    $typed_config_manager = \Drupal::service('config.typed');
 | 
			
		||||
 | 
			
		||||
    // Test non-existent data.
 | 
			
		||||
    try {
 | 
			
		||||
      $typed_config_manager->get('config_test.non_existent');
 | 
			
		||||
      $this->fail('Expected error when trying to get non-existent typed config.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\InvalidArgumentException $e) {
 | 
			
		||||
      $this->assertEquals('Missing required data for typed configuration: config_test.non_existent', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
 | 
			
		||||
    // Test a primitive.
 | 
			
		||||
    $string_data = $typed_config->get('llama');
 | 
			
		||||
    $this->assertInstanceOf(StringInterface::class, $string_data);
 | 
			
		||||
    $this->assertEquals('llama', $string_data->getValue());
 | 
			
		||||
 | 
			
		||||
    // Test complex data.
 | 
			
		||||
    $mapping = $typed_config->get('cat');
 | 
			
		||||
    /** @var \Drupal\Core\TypedData\ComplexDataInterface $mapping */
 | 
			
		||||
    $this->assertInstanceOf(ComplexDataInterface::class, $mapping);
 | 
			
		||||
    $this->assertInstanceOf(StringInterface::class, $mapping->get('type'));
 | 
			
		||||
    $this->assertEquals('kitten', $mapping->get('type')->getValue());
 | 
			
		||||
    $this->assertInstanceOf(IntegerInterface::class, $mapping->get('count'));
 | 
			
		||||
    $this->assertEquals(2, $mapping->get('count')->getValue());
 | 
			
		||||
    // Verify the item metadata is available.
 | 
			
		||||
    $this->assertInstanceOf(ComplexDataDefinitionInterface::class, $mapping->getDataDefinition());
 | 
			
		||||
    $this->assertArrayHasKey('type', $mapping->getProperties());
 | 
			
		||||
    $this->assertArrayHasKey('count', $mapping->getProperties());
 | 
			
		||||
 | 
			
		||||
    // Test accessing sequences.
 | 
			
		||||
    $sequence = $typed_config->get('giraffe');
 | 
			
		||||
    /** @var \Drupal\Core\TypedData\ListInterface $sequence */
 | 
			
		||||
    $this->assertInstanceOf(SequenceDataDefinition::class, $sequence->getDataDefinition());
 | 
			
		||||
    $this->assertSame(Sequence::class, $sequence->getDataDefinition()->getClass());
 | 
			
		||||
    $this->assertSame('sequence', $sequence->getDataDefinition()->getDataType());
 | 
			
		||||
    $this->assertInstanceOf(ComplexDataInterface::class, $sequence);
 | 
			
		||||
    $this->assertInstanceOf(StringInterface::class, $sequence->get('hum1'));
 | 
			
		||||
    $this->assertEquals('hum1', $sequence->get('hum1')->getValue());
 | 
			
		||||
    $this->assertEquals('hum2', $sequence->get('hum2')->getValue());
 | 
			
		||||
    $this->assertCount(2, $sequence->getIterator());
 | 
			
		||||
    // Verify the item metadata is available.
 | 
			
		||||
    $this->assertInstanceOf(SequenceDataDefinition::class, $sequence->getDataDefinition());
 | 
			
		||||
 | 
			
		||||
    // Test accessing typed config objects for simple config and config
 | 
			
		||||
    // entities.
 | 
			
		||||
    $typed_config_manager = \Drupal::service('config.typed');
 | 
			
		||||
    $typed_config = $typed_config_manager->createFromNameAndData('config_test.validation', \Drupal::configFactory()->get('config_test.validation')->get());
 | 
			
		||||
    $this->assertInstanceOf(TypedConfigInterface::class, $typed_config);
 | 
			
		||||
    $this->assertEquals(['_core', 'llama', 'cat', 'giraffe', 'uuid', 'string__not_blank', 'host'], array_keys($typed_config->getElements()));
 | 
			
		||||
    $this->assertSame('config_test.validation', $typed_config->getName());
 | 
			
		||||
    $this->assertSame('config_test.validation', $typed_config->getPropertyPath());
 | 
			
		||||
    $this->assertSame('config_test.validation.llama', $typed_config->get('llama')->getPropertyPath());
 | 
			
		||||
 | 
			
		||||
    $config_test_entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([
 | 
			
		||||
      'id' => 'test',
 | 
			
		||||
      'label' => 'Test',
 | 
			
		||||
      'weight' => 11,
 | 
			
		||||
      'style' => 'test_style',
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->createFromNameAndData($config_test_entity->getConfigDependencyName(), $config_test_entity->toArray());
 | 
			
		||||
    $this->assertInstanceOf(TypedConfigInterface::class, $typed_config);
 | 
			
		||||
    $this->assertEquals(['uuid', 'langcode', 'status', 'dependencies', 'id', 'label', 'weight', 'style', 'size', 'size_value', 'protected_property'], array_keys($typed_config->getElements()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the behavior of `NotBlank` on required data.
 | 
			
		||||
   *
 | 
			
		||||
   * @testWith ["", false, "This value should not be blank."]
 | 
			
		||||
   *           ["", true, "This value should not be blank."]
 | 
			
		||||
   *           [null, false, "This value should not be blank."]
 | 
			
		||||
   *           [null, true, "This value should not be null."]
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\TypedData\DataDefinition::getConstraints()
 | 
			
		||||
   * @see \Drupal\Core\TypedData\DataDefinitionInterface::isRequired()
 | 
			
		||||
   * @see \Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraint
 | 
			
		||||
   * @see \Symfony\Component\Validator\Constraints\NotBlank::$allowNull
 | 
			
		||||
   */
 | 
			
		||||
  public function testNotBlankInteractionWithNotNull(?string $value, bool $is_required, string $expected_message): void {
 | 
			
		||||
    \Drupal::configFactory()->getEditable('config_test.validation')
 | 
			
		||||
      ->set('string__not_blank', $value)
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = \Drupal::service('config.typed')->get('config_test.validation');
 | 
			
		||||
    $typed_config->get('string__not_blank')->getDataDefinition()->setRequired($is_required);
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
 | 
			
		||||
    // Expect 1 validation error message: the one from `NotBlank` or `NotNull`.
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertSame('string__not_blank', $result->get(0)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals($expected_message, $result->get(0)->getMessage());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests config validation via the Typed Data API.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSimpleConfigValidation(): void {
 | 
			
		||||
    $config = \Drupal::configFactory()->getEditable('config_test.validation');
 | 
			
		||||
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
 | 
			
		||||
    $typed_config_manager = \Drupal::service('config.typed');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertInstanceOf(ConstraintViolationListInterface::class, $result);
 | 
			
		||||
    $this->assertEmpty($result);
 | 
			
		||||
 | 
			
		||||
    // Test constraints on primitive types.
 | 
			
		||||
    $config->set('llama', 'elephant');
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    // Its not a valid llama anymore.
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertEquals('no valid llama', $result->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Test constraints on mapping.
 | 
			
		||||
    $config->set('llama', 'llama');
 | 
			
		||||
    $config->set('cat.type', 'nyans');
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertEmpty($result);
 | 
			
		||||
 | 
			
		||||
    // Test constrains on nested mapping.
 | 
			
		||||
    $config->set('cat.type', 'tiger');
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertEquals('no valid cat', $result->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Test constrains on sequences elements.
 | 
			
		||||
    $config->set('cat.type', 'nyans');
 | 
			
		||||
    $config->set('giraffe', ['muh', 'hum2']);
 | 
			
		||||
    $config->save();
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertEquals('Giraffes just hum', $result->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Test constrains on the sequence itself.
 | 
			
		||||
    $config->set('giraffe', ['hum', 'hum2', 'invalid-key' => 'hum']);
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertEquals('giraffe', $result->get(0)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals('Invalid giraffe key.', $result->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Validates mapping.
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $value = $typed_config->getValue();
 | 
			
		||||
    unset($value['giraffe']);
 | 
			
		||||
    $value['elephant'] = 'foo';
 | 
			
		||||
    $value['zebra'] = 'foo';
 | 
			
		||||
    $typed_config->setValue($value);
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertCount(3, $result);
 | 
			
		||||
    // 2 constraint violations triggered by the default validation constraint
 | 
			
		||||
    // for `type: mapping`
 | 
			
		||||
    // @see \Drupal\Core\Validation\Plugin\Validation\Constraint\ValidKeysConstraint
 | 
			
		||||
    $this->assertSame('elephant', $result->get(0)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals("'elephant' is not a supported key.", $result->get(0)->getMessage());
 | 
			
		||||
    $this->assertSame('zebra', $result->get(1)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals("'zebra' is not a supported key.", $result->get(1)->getMessage());
 | 
			
		||||
    // 1 additional constraint violation triggered by the custom
 | 
			
		||||
    // constraint for the `config_test.validation` type, which indirectly
 | 
			
		||||
    // extends `type: mapping` (via `type: config_object`).
 | 
			
		||||
    // @see \Drupal\config_test\ConfigValidation::validateMapping()
 | 
			
		||||
    $this->assertEquals('', $result->get(2)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals('Unexpected keys: elephant, zebra', $result->get(2)->getMessage());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								web/core/tests/Drupal/KernelTests/ConfigFormTestBase.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								web/core/tests/Drupal/KernelTests/ConfigFormTestBase.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormState;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Full generic test suite for any form that data with the configuration system.
 | 
			
		||||
 *
 | 
			
		||||
 * @see UserAdminSettingsFormTest
 | 
			
		||||
 *   For a full working implementation.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ConfigFormTestBase extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Form ID to use for testing.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Form\FormInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $form;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Values to use for testing.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   * Contains details for form key, configuration object name, and config key.
 | 
			
		||||
   * Example:
 | 
			
		||||
   * @code
 | 
			
		||||
   *   [
 | 
			
		||||
   *     'user_mail_cancel_confirm_body' => [
 | 
			
		||||
   *       '#value' => $this->randomString(),
 | 
			
		||||
   *       '#config_name' => 'user.mail',
 | 
			
		||||
   *       '#config_key' => 'cancel_confirm.body',
 | 
			
		||||
   *     ],
 | 
			
		||||
   *   ];
 | 
			
		||||
   * @endcode
 | 
			
		||||
   */
 | 
			
		||||
  protected $values;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit the system_config_form ensure the configuration has expected values.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigForm(): void {
 | 
			
		||||
    // Programmatically submit the given values.
 | 
			
		||||
    $values = [];
 | 
			
		||||
    foreach ($this->values as $form_key => $data) {
 | 
			
		||||
      $values[$form_key] = $data['#value'];
 | 
			
		||||
    }
 | 
			
		||||
    $form_state = (new FormState())->setValues($values);
 | 
			
		||||
    \Drupal::formBuilder()->submitForm($this->form, $form_state);
 | 
			
		||||
 | 
			
		||||
    // Check that the form returns an error when expected, and vice versa.
 | 
			
		||||
    $errors = $form_state->getErrors();
 | 
			
		||||
    $valid_form = empty($errors);
 | 
			
		||||
    $values = print_r($values, TRUE);
 | 
			
		||||
    $errors = $valid_form ? $this->t('None') : implode(' ', $errors);
 | 
			
		||||
    $this->assertTrue($valid_form, sprintf('Input values: %s<br/>Validation handler errors: %s', $values, $errors));
 | 
			
		||||
    foreach ($this->values as $data) {
 | 
			
		||||
      $this->assertEquals($this->config($data['#config_name'])->get($data['#config_key']), $data['#value']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,97 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Action;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver;
 | 
			
		||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\system\Entity\Action;
 | 
			
		||||
use Drupal\user\Entity\User;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @group Action
 | 
			
		||||
 */
 | 
			
		||||
class DeleteActionTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The test user.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Session\AccountInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $testUser;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'entity_test', 'user'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installEntitySchema('entity_test_mulrevpub');
 | 
			
		||||
    $this->installEntitySchema('user');
 | 
			
		||||
 | 
			
		||||
    $this->testUser = User::create([
 | 
			
		||||
      'name' => 'foobar',
 | 
			
		||||
      'mail' => 'foobar@example.com',
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->testUser->save();
 | 
			
		||||
    \Drupal::service('current_user')->setAccount($this->testUser);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver::getDerivativeDefinitions
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetDerivativeDefinitions(): void {
 | 
			
		||||
    $deriver = new EntityDeleteActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation());
 | 
			
		||||
    $this->assertEquals([
 | 
			
		||||
      'entity_test_mulrevpub' => [
 | 
			
		||||
        'type' => 'entity_test_mulrevpub',
 | 
			
		||||
        'label' => 'Delete test entity - revisions, data table, and published interface',
 | 
			
		||||
        'action_label' => 'Delete',
 | 
			
		||||
        'confirm_form_route_name' => 'entity.entity_test_mulrevpub.delete_multiple_form',
 | 
			
		||||
      ],
 | 
			
		||||
      'entity_test_revpub' => [
 | 
			
		||||
        'type' => 'entity_test_revpub',
 | 
			
		||||
        'label' => 'Delete test entity - revisions and publishing status',
 | 
			
		||||
        'action_label' => 'Delete',
 | 
			
		||||
        'confirm_form_route_name' => 'entity.entity_test_revpub.delete_multiple_form',
 | 
			
		||||
      ],
 | 
			
		||||
      'entity_test_rev' => [
 | 
			
		||||
        'type' => 'entity_test_rev',
 | 
			
		||||
        'label' => 'Delete test entity - revisions',
 | 
			
		||||
        'action_label' => 'Delete',
 | 
			
		||||
        'confirm_form_route_name' => 'entity.entity_test_rev.delete_multiple_form',
 | 
			
		||||
      ],
 | 
			
		||||
    ], $deriver->getDerivativeDefinitions([
 | 
			
		||||
      'action_label' => 'Delete',
 | 
			
		||||
    ]));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Action\Plugin\Action\DeleteAction::execute
 | 
			
		||||
   */
 | 
			
		||||
  public function testDeleteAction(): void {
 | 
			
		||||
    $entity = EntityTestMulRevPub::create(['name' => 'test']);
 | 
			
		||||
    $entity->save();
 | 
			
		||||
 | 
			
		||||
    $action = Action::create([
 | 
			
		||||
      'id' => 'entity_delete_action',
 | 
			
		||||
      'plugin' => 'entity:delete_action:entity_test_mulrevpub',
 | 
			
		||||
    ]);
 | 
			
		||||
    $action->save();
 | 
			
		||||
 | 
			
		||||
    $action->execute([$entity]);
 | 
			
		||||
    $this->assertSame(['module' => ['entity_test']], $action->getDependencies());
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store */
 | 
			
		||||
    $temp_store = \Drupal::service('tempstore.private');
 | 
			
		||||
    $store_entries = $temp_store->get('entity_delete_multiple_confirm')->get($this->testUser->id() . ':entity_test_mulrevpub');
 | 
			
		||||
    $this->assertSame([$this->testUser->id() => ['en' => 'en']], $store_entries);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,69 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Action;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Test\AssertMailTrait;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests for the EmailAction plugin.
 | 
			
		||||
 *
 | 
			
		||||
 * @group action
 | 
			
		||||
 */
 | 
			
		||||
class EmailActionTest extends KernelTestBase {
 | 
			
		||||
  use AssertMailTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'user', 'dblog'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installEntitySchema('user');
 | 
			
		||||
    $this->installSchema('dblog', ['watchdog']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the email action plugin.
 | 
			
		||||
   */
 | 
			
		||||
  public function testEmailAction(): void {
 | 
			
		||||
    $this->config('system.site')->set('mail', 'test@example.com')->save();
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Action\ActionManager $plugin_manager */
 | 
			
		||||
    $plugin_manager = $this->container->get('plugin.manager.action');
 | 
			
		||||
    $configuration = [
 | 
			
		||||
      'recipient' => 'test@example.com',
 | 
			
		||||
      'subject' => 'Test subject',
 | 
			
		||||
      'message' => 'Test message',
 | 
			
		||||
    ];
 | 
			
		||||
    $plugin_manager
 | 
			
		||||
      ->createInstance('action_send_email_action', $configuration)
 | 
			
		||||
      ->execute();
 | 
			
		||||
 | 
			
		||||
    $mails = $this->getMails();
 | 
			
		||||
    $this->assertCount(1, $this->getMails());
 | 
			
		||||
    $this->assertEquals('test@example.com', $mails[0]['to']);
 | 
			
		||||
    $this->assertEquals('Test subject', $mails[0]['subject']);
 | 
			
		||||
    $this->assertEquals("Test message\n", $mails[0]['body']);
 | 
			
		||||
 | 
			
		||||
    // Ensure that the email sending is logged.
 | 
			
		||||
    $log = \Drupal::database()
 | 
			
		||||
      ->select('watchdog', 'w')
 | 
			
		||||
      ->fields('w', ['message', 'variables'])
 | 
			
		||||
      ->orderBy('wid', 'DESC')
 | 
			
		||||
      ->range(0, 1)
 | 
			
		||||
      ->execute()
 | 
			
		||||
      ->fetch();
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals('Sent email to %recipient', $log->message);
 | 
			
		||||
    $variables = unserialize($log->variables);
 | 
			
		||||
    $this->assertEquals('test@example.com', $variables['%recipient']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,81 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Action;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver;
 | 
			
		||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\system\Entity\Action;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @group Action
 | 
			
		||||
 */
 | 
			
		||||
class PublishActionTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'entity_test', 'user'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installEntitySchema('entity_test_mulrevpub');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver::getDerivativeDefinitions
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetDerivativeDefinitions(): void {
 | 
			
		||||
    $deriver = new EntityPublishedActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation());
 | 
			
		||||
    $definitions = $deriver->getDerivativeDefinitions([
 | 
			
		||||
      'action_label' => 'Save',
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->assertEquals([
 | 
			
		||||
      'type' => 'entity_test_mulrevpub',
 | 
			
		||||
      'label' => 'Save test entity - revisions, data table, and published interface',
 | 
			
		||||
      'action_label' => 'Save',
 | 
			
		||||
    ], $definitions['entity_test_mulrevpub']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Action\Plugin\Action\PublishAction::execute
 | 
			
		||||
   */
 | 
			
		||||
  public function testPublishAction(): void {
 | 
			
		||||
    $entity = EntityTestMulRevPub::create(['name' => 'test']);
 | 
			
		||||
    $entity->setUnpublished()->save();
 | 
			
		||||
 | 
			
		||||
    $action = Action::create([
 | 
			
		||||
      'id' => 'entity_publish_action',
 | 
			
		||||
      'plugin' => 'entity:publish_action:entity_test_mulrevpub',
 | 
			
		||||
    ]);
 | 
			
		||||
    $action->save();
 | 
			
		||||
    $this->assertFalse($entity->isPublished());
 | 
			
		||||
    $action->execute([$entity]);
 | 
			
		||||
    $this->assertTrue($entity->isPublished());
 | 
			
		||||
    $this->assertSame(['module' => ['entity_test']], $action->getDependencies());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Action\Plugin\Action\UnpublishAction::execute
 | 
			
		||||
   */
 | 
			
		||||
  public function testUnpublishAction(): void {
 | 
			
		||||
    $entity = EntityTestMulRevPub::create(['name' => 'test']);
 | 
			
		||||
    $entity->setPublished()->save();
 | 
			
		||||
 | 
			
		||||
    $action = Action::create([
 | 
			
		||||
      'id' => 'entity_unpublish_action',
 | 
			
		||||
      'plugin' => 'entity:unpublish_action:entity_test_mulrevpub',
 | 
			
		||||
    ]);
 | 
			
		||||
    $action->save();
 | 
			
		||||
    $this->assertTrue($entity->isPublished());
 | 
			
		||||
    $action->execute([$entity]);
 | 
			
		||||
    $this->assertFalse($entity->isPublished());
 | 
			
		||||
    $this->assertSame(['module' => ['entity_test']], $action->getDependencies());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,63 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Action;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver;
 | 
			
		||||
use Drupal\entity_test\Entity\EntityTestMulChanged;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\system\Entity\Action;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @group Action
 | 
			
		||||
 */
 | 
			
		||||
class SaveActionTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'entity_test', 'user'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installEntitySchema('entity_test_mul_changed');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver::getDerivativeDefinitions
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetDerivativeDefinitions(): void {
 | 
			
		||||
    $deriver = new EntityChangedActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation());
 | 
			
		||||
    $definitions = $deriver->getDerivativeDefinitions([
 | 
			
		||||
      'action_label' => 'Save',
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->assertEquals([
 | 
			
		||||
      'type' => 'entity_test_mul_changed',
 | 
			
		||||
      'label' => 'Save test entity - multiple changed and data table',
 | 
			
		||||
      'action_label' => 'Save',
 | 
			
		||||
    ], $definitions['entity_test_mul_changed']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Action\Plugin\Action\SaveAction::execute
 | 
			
		||||
   */
 | 
			
		||||
  public function testSaveAction(): void {
 | 
			
		||||
    $entity = EntityTestMulChanged::create(['name' => 'test']);
 | 
			
		||||
    $entity->save();
 | 
			
		||||
    $saved_time = $entity->getChangedTime();
 | 
			
		||||
 | 
			
		||||
    $action = Action::create([
 | 
			
		||||
      'id' => 'entity_save_action',
 | 
			
		||||
      'plugin' => 'entity:save_action:entity_test_mul_changed',
 | 
			
		||||
    ]);
 | 
			
		||||
    $action->save();
 | 
			
		||||
    $action->execute([$entity]);
 | 
			
		||||
    $this->assertNotSame($saved_time, $entity->getChangedTime());
 | 
			
		||||
    $this->assertSame(['module' => ['entity_test']], $action->getDependencies());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								web/core/tests/Drupal/KernelTests/Core/Ajax/CommandsTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								web/core/tests/Drupal/KernelTests/Core/Ajax/CommandsTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Ajax;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxResponse;
 | 
			
		||||
use Drupal\Core\Ajax\InsertCommand;
 | 
			
		||||
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
 | 
			
		||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Performs tests on AJAX framework commands.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Ajax
 | 
			
		||||
 */
 | 
			
		||||
class CommandsTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'node',
 | 
			
		||||
    'ajax_test',
 | 
			
		||||
    'ajax_forms_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Regression test: Settings command exists regardless of JS aggregation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAttachedSettings(): void {
 | 
			
		||||
    $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(fn() => $ajax_response_attachments_processor);
 | 
			
		||||
      $event = new ResponseEvent(
 | 
			
		||||
        \Drupal::service('http_kernel'),
 | 
			
		||||
        new Request(),
 | 
			
		||||
        HttpKernelInterface::MAIN_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.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks empty content in commands does not throw exceptions.
 | 
			
		||||
   *
 | 
			
		||||
   * @doesNotPerformAssertions
 | 
			
		||||
   */
 | 
			
		||||
  public function testEmptyInsertCommand(): void {
 | 
			
		||||
    (new InsertCommand('foobar', []))->render();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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 array $haystack
 | 
			
		||||
   *   An array of rendered Ajax commands returned by the server.
 | 
			
		||||
   * @param array $needle
 | 
			
		||||
   *   Array of info we're expecting in one of those commands.
 | 
			
		||||
   * @param string $message
 | 
			
		||||
   *   An assertion message.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertCommand(array $haystack, array $needle, string $message): void {
 | 
			
		||||
    $found = FALSE;
 | 
			
		||||
    foreach ($haystack as $command) {
 | 
			
		||||
      // If the command has additional settings that we're not testing for, do
 | 
			
		||||
      // not consider that a failure.
 | 
			
		||||
      if (isset($command['settings']) && is_array($command['settings']) && isset($needle['settings']) && is_array($needle['settings'])) {
 | 
			
		||||
        $command['settings'] = array_intersect_key($command['settings'], $needle['settings']);
 | 
			
		||||
      }
 | 
			
		||||
      // If the command has additional data that we're not testing for, do not
 | 
			
		||||
      // consider that a failure. Also, == instead of ===, because we don't
 | 
			
		||||
      // require the key/value pairs to be in any particular order
 | 
			
		||||
      // (http://php.net/manual/language.operators.array.php).
 | 
			
		||||
      if (array_intersect_key($command, $needle) == $needle) {
 | 
			
		||||
        $found = TRUE;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->assertTrue($found, $message);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Archiver;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\Core\File\FileTestBase;
 | 
			
		||||
use Drupal\Tests\TestFileCreationTrait;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides archive specific assertions and helper properties for archive tests.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ArchiverTestBase extends FileTestBase {
 | 
			
		||||
  use TestFileCreationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The archiver plugin identifier.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $archiverPluginId;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The file system service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\File\FileSystemInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $fileSystem;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->fileSystem = $this->container->get('file_system');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts an archive contains a given file.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $path
 | 
			
		||||
   *   Absolute file path to an archived file.
 | 
			
		||||
   * @param string $file
 | 
			
		||||
   *   File to assert does exist within the archived file.
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   Optional configuration to pass to the archiver plugin.
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertArchiveContainsFile($path, $file, array $configuration = []) {
 | 
			
		||||
    $configuration['filepath'] = $path;
 | 
			
		||||
    /** @var \Drupal\Core\Archiver\ArchiverManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.archiver');
 | 
			
		||||
    $archive = $manager->createInstance($this->archiverPluginId, $configuration);
 | 
			
		||||
    $this->assertContains($file, $archive->listContents(), sprintf('The "%s" archive contains the "%s" file.', $path, $file));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts an archive does not contain a given file.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $path
 | 
			
		||||
   *   Absolute file path to an archived file.
 | 
			
		||||
   * @param string $file
 | 
			
		||||
   *   File to assert does not exist within the archived file.
 | 
			
		||||
   * @param array $configuration
 | 
			
		||||
   *   Optional configuration to pass to the archiver plugin.
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertArchiveNotContainsFile($path, $file, array $configuration = []) {
 | 
			
		||||
    $configuration['filepath'] = $path;
 | 
			
		||||
    /** @var \Drupal\Core\Archiver\ArchiverManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.archiver');
 | 
			
		||||
    $archive = $manager->createInstance($this->archiverPluginId, $configuration);
 | 
			
		||||
    $this->assertNotContains($file, $archive->listContents(), sprintf('The "%s" archive does not contain the "%s" file.', $path, $file));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								web/core/tests/Drupal/KernelTests/Core/Archiver/TarTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/core/tests/Drupal/KernelTests/Core/Archiver/TarTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Archiver;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Archiver\Tar;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Archiver\Tar
 | 
			
		||||
 * @group tar
 | 
			
		||||
 */
 | 
			
		||||
class TarTest extends ArchiverTestBase {
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $archiverPluginId = 'Tar';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the Tar archive is created if it does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCreateArchive(): void {
 | 
			
		||||
    $textFile = current($this->getTestFiles('text'));
 | 
			
		||||
    $archiveFilename = $this->fileSystem->realpath('public://' . $this->randomMachineName() . '.tar');
 | 
			
		||||
    $tar = new Tar($archiveFilename);
 | 
			
		||||
    $tar->add($this->fileSystem->realPath($textFile->uri));
 | 
			
		||||
    $this->assertFileExists($archiveFilename, 'Archive is automatically created if the file does not exist.');
 | 
			
		||||
    $this->assertArchiveContainsFile($archiveFilename, $this->fileSystem->realPath($textFile->uri));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								web/core/tests/Drupal/KernelTests/Core/Archiver/ZipTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								web/core/tests/Drupal/KernelTests/Core/Archiver/ZipTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Archiver;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Archiver\Zip;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Archiver\Zip
 | 
			
		||||
 * @group zip
 | 
			
		||||
 */
 | 
			
		||||
class ZipTest extends ArchiverTestBase {
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $archiverPluginId = 'Zip';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the Zip archive is created if it does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCreateArchive(): void {
 | 
			
		||||
    $textFile = current($this->getTestFiles('text'));
 | 
			
		||||
    $archiveFilename = $this->fileSystem->realpath('public://' . $this->randomMachineName() . '.zip');
 | 
			
		||||
    $zip = new Zip($archiveFilename, [
 | 
			
		||||
      'flags' => \ZipArchive::CREATE,
 | 
			
		||||
    ]);
 | 
			
		||||
    $zip->add($this->fileSystem->realPath($textFile->uri));
 | 
			
		||||
    // Close the archive and make sure it is written to disk.
 | 
			
		||||
    $this->assertTrue($zip->getArchive()->close(), 'Successfully closed archive.');
 | 
			
		||||
    $this->assertFileExists($archiveFilename, 'Archive is automatically created if the file does not exist.');
 | 
			
		||||
    $this->assertArchiveContainsFile($archiveFilename, $this->fileSystem->realPath($textFile->uri));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the Zip archiver is created and overwritten.
 | 
			
		||||
   */
 | 
			
		||||
  public function testOverwriteArchive(): void {
 | 
			
		||||
    // Create an archive similarly to how it's done in ::testCreateArchive.
 | 
			
		||||
    $files = $this->getTestFiles('text');
 | 
			
		||||
    $textFile = current($files);
 | 
			
		||||
    $archiveFilename = $this->fileSystem->realpath('public://' . $this->randomMachineName() . '.zip');
 | 
			
		||||
    $zip = new Zip($archiveFilename, [
 | 
			
		||||
      'flags' => \ZipArchive::CREATE,
 | 
			
		||||
    ]);
 | 
			
		||||
    $zip->add($this->fileSystem->realPath($textFile->uri));
 | 
			
		||||
    $zip->getArchive()->close();
 | 
			
		||||
    $this->assertArchiveContainsFile($archiveFilename, $this->fileSystem->realPath($textFile->uri));
 | 
			
		||||
    // Overwrite the zip with just a new text file.
 | 
			
		||||
    $secondTextFile = next($files);
 | 
			
		||||
    $zip = new Zip($archiveFilename, [
 | 
			
		||||
      'flags' => \ZipArchive::OVERWRITE,
 | 
			
		||||
    ]);
 | 
			
		||||
    $zip->add($this->fileSystem->realpath($secondTextFile->uri));
 | 
			
		||||
    $zip->getArchive()->close();
 | 
			
		||||
    $this->assertArchiveNotContainsFile($archiveFilename, $this->fileSystem->realPath($textFile->uri));
 | 
			
		||||
    $this->assertArchiveContainsFile($archiveFilename, $this->fileSystem->realPath($secondTextFile->uri));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Asset;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Asset\AssetQueryString;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the asset query string functionality.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Asset
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Asset\AssetQueryString
 | 
			
		||||
 */
 | 
			
		||||
class AssetQueryStringTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::get
 | 
			
		||||
   * @covers ::reset
 | 
			
		||||
   */
 | 
			
		||||
  public function testResetGet(): void {
 | 
			
		||||
    $state = $this->container->get('state');
 | 
			
		||||
    // Return a fixed timestamp.
 | 
			
		||||
    $time = $this->createStub(TimeInterface::class);
 | 
			
		||||
    $time->method('getRequestTime')
 | 
			
		||||
      ->willReturn(1683246590);
 | 
			
		||||
 | 
			
		||||
    $queryString = new AssetQueryString($state, $time);
 | 
			
		||||
 | 
			
		||||
    $queryString->reset();
 | 
			
		||||
    $value = $queryString->get();
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals('ru5tdq', $value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,492 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Asset;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Serialization\Json;
 | 
			
		||||
use Drupal\Core\Asset\AttachedAssets;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests #attached assets: attached asset libraries and JavaScript settings.
 | 
			
		||||
 *
 | 
			
		||||
 * I.e. tests:
 | 
			
		||||
 *
 | 
			
		||||
 * @code
 | 
			
		||||
 * $build['#attached']['library'] = …
 | 
			
		||||
 * $build['#attached']['drupalSettings'] = …
 | 
			
		||||
 * @endcode
 | 
			
		||||
 *
 | 
			
		||||
 * @group Common
 | 
			
		||||
 * @group Asset
 | 
			
		||||
 */
 | 
			
		||||
class AttachedAssetsTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The asset resolver service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Asset\AssetResolverInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $assetResolver;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The renderer service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Render\RendererInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $renderer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The file URL generator.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\File\FileUrlGeneratorInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $fileUrlGenerator;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['language', 'common_test', 'system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->assetResolver = $this->container->get('asset.resolver');
 | 
			
		||||
    $this->renderer = $this->container->get('renderer');
 | 
			
		||||
    $this->fileUrlGenerator = $this->container->get('file_url_generator');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that default CSS and JavaScript is empty.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDefault(): void {
 | 
			
		||||
    $assets = new AttachedAssets();
 | 
			
		||||
    $this->assertEquals([], $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage()), 'Default CSS is empty.');
 | 
			
		||||
    [$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
 | 
			
		||||
    $this->assertEquals([], $js_assets_header, 'Default header JavaScript is empty.');
 | 
			
		||||
    $this->assertEquals([], $js_assets_footer, 'Default footer JavaScript is empty.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests non-existing libraries.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibraryUnknown(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'core/unknown';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $this->assertSame([], $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[0], 'Unknown library was not added to the page.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests adding a CSS and a JavaScript file.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAddFiles(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/files';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $css = $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertArrayHasKey('core/modules/system/tests/modules/common_test/bar.css', $css);
 | 
			
		||||
    $this->assertArrayHasKey('core/modules/system/tests/modules/common_test/foo.js', $js);
 | 
			
		||||
 | 
			
		||||
    $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_css = (string) $this->renderer->renderInIsolation($css_render_array);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $query_string = $this->container->get('asset.query_string')->get();
 | 
			
		||||
    $this->assertStringContainsString('<link rel="stylesheet" media="all" href="' . $this->fileUrlGenerator->generateString('core/modules/system/tests/modules/common_test/bar.css') . '?' . $query_string . '" />', $rendered_css, 'Rendering an external CSS file.');
 | 
			
		||||
    $this->assertStringContainsString('<script src="' . $this->fileUrlGenerator->generateString('core/modules/system/tests/modules/common_test/foo.js') . '?' . $query_string . '"></script>', $rendered_js, 'Rendering an external JavaScript file.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests adding JavaScript settings.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAddJsSettings(): void {
 | 
			
		||||
    // Add a file in order to test default settings.
 | 
			
		||||
    $build['#attached']['library'][] = 'core/drupalSettings';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals([], $assets->getSettings(), 'JavaScript settings on $assets are empty.');
 | 
			
		||||
    $javascript = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertArrayHasKey('currentPath', $javascript['drupalSettings']['data']['path']);
 | 
			
		||||
    $this->assertArrayHasKey('currentPath', $assets->getSettings()['path']);
 | 
			
		||||
 | 
			
		||||
    $assets->setSettings(['drupal' => 'rocks', 'dries' => 280342800]);
 | 
			
		||||
    $javascript = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertEquals(280342800, $javascript['drupalSettings']['data']['dries'], 'JavaScript setting is set correctly.');
 | 
			
		||||
    $this->assertEquals('rocks', $javascript['drupalSettings']['data']['drupal'], 'The other JavaScript setting is set correctly.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests adding external CSS and JavaScript files.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAddExternalFiles(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/external';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $css = $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertArrayHasKey('http://example.com/stylesheet.css', $css);
 | 
			
		||||
    $this->assertArrayHasKey('http://example.com/script.js', $js);
 | 
			
		||||
 | 
			
		||||
    $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_css = (string) $this->renderer->renderInIsolation($css_render_array);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $this->assertStringContainsString('<link rel="stylesheet" media="all" href="http://example.com/stylesheet.css" />', $rendered_css, 'Rendering an external CSS file.');
 | 
			
		||||
    $this->assertStringContainsString('<script src="http://example.com/script.js"></script>', $rendered_js, 'Rendering an external JavaScript file.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests adding JavaScript files with additional attributes.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAttributes(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/js-attributes';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
 | 
			
		||||
    $expected_2 = '<script src="' . $this->fileUrlGenerator->generateString('core/modules/system/tests/modules/common_test/deferred-internal.js') . '?v=1" defer bar="foo"></script>';
 | 
			
		||||
    $this->assertStringContainsString($expected_1, $rendered_js, 'Rendered external JavaScript with correct defer and random attributes.');
 | 
			
		||||
    $this->assertStringContainsString($expected_2, $rendered_js, 'Rendered internal JavaScript with correct defer and random attributes.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that attributes are maintained when JS aggregation is enabled.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAggregatedAttributes(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/js-attributes';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
 | 
			
		||||
    $expected_2 = '<script src="' . $this->fileUrlGenerator->generateString('core/modules/system/tests/modules/common_test/deferred-internal.js') . '?v=1" defer bar="foo"></script>';
 | 
			
		||||
    $this->assertStringContainsString($expected_1, $rendered_js, 'Rendered external JavaScript with correct defer and random attributes.');
 | 
			
		||||
    $this->assertStringContainsString($expected_2, $rendered_js, 'Rendered internal JavaScript with correct defer and random attributes.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Integration test for CSS/JS aggregation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAggregation(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'core/drupal.timezone';
 | 
			
		||||
    $build['#attached']['library'][] = 'core/drupal.vertical-tabs';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $this->assertCount(1, $this->assetResolver->getCssAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage()), 'There is a sole aggregated CSS asset.');
 | 
			
		||||
 | 
			
		||||
    [$header_js, $footer_js] = $this->assetResolver->getJsAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage());
 | 
			
		||||
    $this->assertEquals([], \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->assertCount(3, $rendered_footer_js, 'There are 3 JavaScript assets in the footer.');
 | 
			
		||||
    $this->assertEquals('drupal-settings-json', $rendered_footer_js[0]['#attributes']['data-drupal-selector'], 'The first of the two JavaScript assets in the footer has drupal settings.');
 | 
			
		||||
    $this->assertStringContainsString('jquery.min.js', $rendered_footer_js[1]['#attributes']['src'], 'The second of the two JavaScript assets in the footer is jquery.min.js.');
 | 
			
		||||
    $this->assertStringStartsWith(base_path(), $rendered_footer_js[2]['#attributes']['src'], 'The third of the two JavaScript assets in the footer has the sole aggregated JavaScript asset.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests JavaScript settings.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSettings(): void {
 | 
			
		||||
    $build = [];
 | 
			
		||||
    $build['#attached']['library'][] = 'core/drupalSettings';
 | 
			
		||||
    // Nonsensical value to verify if it's possible to override path settings.
 | 
			
		||||
    $build['#attached']['drupalSettings']['path']['pathPrefix'] = 'does_not_exist';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    // Cast to string since this returns a \Drupal\Core\Render\Markup object.
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
 | 
			
		||||
    // Parse the generated drupalSettings <script> back to a PHP representation.
 | 
			
		||||
    $startToken = '{';
 | 
			
		||||
    $endToken = '}';
 | 
			
		||||
    $start = strpos($rendered_js, $startToken);
 | 
			
		||||
    $end = strrpos($rendered_js, $endToken);
 | 
			
		||||
    // Convert to a string, as $renderer_js is a \Drupal\Core\Render\Markup
 | 
			
		||||
    // object.
 | 
			
		||||
    $json = mb_substr($rendered_js, $start, $end - $start + 1);
 | 
			
		||||
    $parsed_settings = Json::decode($json);
 | 
			
		||||
 | 
			
		||||
    // Test whether the settings for core/drupalSettings are available.
 | 
			
		||||
    $this->assertTrue(isset($parsed_settings['path']['baseUrl']), 'drupalSettings.path.baseUrl is present.');
 | 
			
		||||
    $this->assertSame('does_not_exist', $parsed_settings['path']['pathPrefix'], 'drupalSettings.path.pathPrefix is present and has the correct (overridden) value.');
 | 
			
		||||
    $this->assertSame('', $parsed_settings['path']['currentPath'], 'drupalSettings.path.currentPath is present and has the correct value.');
 | 
			
		||||
    $this->assertFalse($parsed_settings['path']['currentPathIsAdmin'], 'drupalSettings.path.currentPathIsAdmin is present and has the correct value.');
 | 
			
		||||
    $this->assertFalse($parsed_settings['path']['isFront'], 'drupalSettings.path.isFront is present and has the correct value.');
 | 
			
		||||
    $this->assertSame('en', $parsed_settings['path']['currentLanguage'], '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->assertSame('☃', $parsed_settings['pluralDelimiter']);
 | 
			
		||||
    $this->assertSame('bar', $parsed_settings['foo']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests JS assets depending on the 'core/<head>' virtual library.
 | 
			
		||||
   */
 | 
			
		||||
  public function testHeaderHTML(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/js-header';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[0];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $query_string = $this->container->get('asset.query_string')->get();
 | 
			
		||||
    $this->assertStringContainsString('<script src="' . $this->fileUrlGenerator->generateString('core/modules/system/tests/modules/common_test/header.js') . '?' . $query_string . '"></script>', $rendered_js, 'The JS asset in common_test/js-header appears in the header.');
 | 
			
		||||
    $this->assertStringContainsString('<script src="' . $this->fileUrlGenerator->generateString('core/misc/drupal.js'), $rendered_js, 'The JS asset of the direct dependency (core/drupal) of common_test/js-header appears in the header.');
 | 
			
		||||
    $this->assertStringContainsString('<script src="' . $this->fileUrlGenerator->generateString('core/misc/drupalSettingsLoader.js'), $rendered_js, 'The JS asset of the indirect dependency (core/drupalSettings) of common_test/js-header appears in the header.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that for assets with cache = FALSE, Drupal sets preprocess = FALSE.
 | 
			
		||||
   */
 | 
			
		||||
  public function testNoCache(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/no-cache';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertFalse($js['core/modules/system/tests/modules/common_test/no_cache.js']['preprocess'], 'Setting cache to FALSE sets preprocess to FALSE when adding JavaScript.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests JavaScript versioning.
 | 
			
		||||
   */
 | 
			
		||||
  public function testVersionQueryString(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'core/once';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $this->assertStringContainsString('core/assets/vendor/once/once.min.js?v=1.0.1', $rendered_js, 'JavaScript version identifiers correctly appended to URLs');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests JavaScript and CSS asset ordering.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderOrder(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/order';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    // Construct the expected result from the regex.
 | 
			
		||||
    $expected_order_js = [
 | 
			
		||||
      "-8_1",
 | 
			
		||||
      "-8_2",
 | 
			
		||||
      "-8_3",
 | 
			
		||||
      "-8_4",
 | 
			
		||||
      // The external script.
 | 
			
		||||
      "-5_1",
 | 
			
		||||
      "-3_1",
 | 
			
		||||
      "-3_2",
 | 
			
		||||
      "0_1",
 | 
			
		||||
      "0_2",
 | 
			
		||||
      "0_3",
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Retrieve the rendered JavaScript and test against the regex.
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $matches = [];
 | 
			
		||||
    if (preg_match_all('/weight_([-0-9]+_[0-9]+)/', $rendered_js, $matches)) {
 | 
			
		||||
      $result = $matches[1];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $result = [];
 | 
			
		||||
    }
 | 
			
		||||
    $this->assertSame($expected_order_js, $result, '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, \Drupal::languageManager()->getCurrentLanguage());
 | 
			
		||||
    $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
 | 
			
		||||
    $rendered_css = (string) $this->renderer->renderInIsolation($css_render_array);
 | 
			
		||||
    $matches = [];
 | 
			
		||||
    if (preg_match_all('/([a-z]+)_weight_([-0-9]+_[0-9]+)/', $rendered_css, $matches)) {
 | 
			
		||||
      $result = $matches[0];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $result = [];
 | 
			
		||||
    }
 | 
			
		||||
    $this->assertSame($expected_order_css, $result, 'CSS is added in the expected weight order.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests rendering the JavaScript with a file's weight above jQuery's.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderDifferentWeight(): void {
 | 
			
		||||
    // 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, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    // Verify that lighter CSS assets are rendered first.
 | 
			
		||||
    $this->assertLessThan(strpos($rendered_js, 'first.js'), strpos($rendered_js, 'lighter.css'));
 | 
			
		||||
    // Verify that lighter JavaScript assets are rendered first.
 | 
			
		||||
    $this->assertLessThan(strpos($rendered_js, 'first.js'), strpos($rendered_js, 'lighter.js'));
 | 
			
		||||
    // Verify that a JavaScript file is rendered before jQuery.
 | 
			
		||||
    $this->assertLessThan(strpos($rendered_js, 'core/assets/vendor/jquery/jquery.min.js'), strpos($rendered_js, 'before-jquery.js'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests altering a JavaScript's weight via hook_js_alter().
 | 
			
		||||
   *
 | 
			
		||||
   * @see common_test_js_alter()
 | 
			
		||||
   */
 | 
			
		||||
  public function testAlter(): void {
 | 
			
		||||
    // Add both tableselect.js and alter.js.
 | 
			
		||||
    $build['#attached']['library'][] = 'core/drupal.tableselect';
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/hook_js_alter';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    // Render the JavaScript, testing if alter.js was altered to be before
 | 
			
		||||
    // tableselect.js. See common_test_js_alter() to see where this alteration
 | 
			
		||||
    // takes place.
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    // Verify that JavaScript weight is correctly altered by the alter hook.
 | 
			
		||||
    $this->assertLessThan(strpos($rendered_js, 'core/misc/tableselect.js'), strpos($rendered_js, 'alter.js'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds a JavaScript library to the page and alters it.
 | 
			
		||||
   *
 | 
			
		||||
   * @see common_test_library_info_alter()
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibraryAlter(): void {
 | 
			
		||||
    // Verify that common_test altered the title of loadjs.
 | 
			
		||||
    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
 | 
			
		||||
    $library_discovery = \Drupal::service('library.discovery');
 | 
			
		||||
    $library = $library_discovery->getLibraryByName('core', 'loadjs');
 | 
			
		||||
    $this->assertEquals('0.0', $library['version'], 'Registered libraries were altered.');
 | 
			
		||||
 | 
			
		||||
    // common_test_library_info_alter() also added a dependency on jQuery Form.
 | 
			
		||||
    $build['#attached']['library'][] = 'core/loadjs';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $this->assertStringContainsString('core/misc/jquery.form.js', (string) $rendered_js, 'Altered library dependencies are added to the page.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Dynamically defines an asset library and alters it.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDynamicLibrary(): void {
 | 
			
		||||
    /** @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->clear();
 | 
			
		||||
    $dynamic_library = $library_discovery->getLibraryByName('common_test', 'dynamic_library');
 | 
			
		||||
    $this->assertIsArray($dynamic_library);
 | 
			
		||||
    $this->assertArrayHasKey('version', $dynamic_library);
 | 
			
		||||
    $this->assertSame('1.0', $dynamic_library['version']);
 | 
			
		||||
    // Make sure the dynamic library definition could be altered.
 | 
			
		||||
    // @see common_test_library_info_alter()
 | 
			
		||||
    $this->assertArrayHasKey('dependencies', $dynamic_library);
 | 
			
		||||
    $this->assertSame(['core/jquery'], $dynamic_library['dependencies']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that multiple modules can implement libraries with the same name.
 | 
			
		||||
   *
 | 
			
		||||
   * @see common_test.library.yml
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibraryNameConflicts(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
 | 
			
		||||
    $library_discovery = \Drupal::service('library.discovery');
 | 
			
		||||
    $loadjs = $library_discovery->getLibraryByName('common_test', 'loadjs');
 | 
			
		||||
    $this->assertEquals('0.1', $loadjs['version'], 'Alternative libraries can be added to the page.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests JavaScript files that have query strings attached get added right.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAddJsFileWithQueryString(): void {
 | 
			
		||||
    $build['#attached']['library'][] = 'common_test/querystring';
 | 
			
		||||
    $assets = AttachedAssets::createFromRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    $css = $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertArrayHasKey('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2', $css);
 | 
			
		||||
    $this->assertArrayHasKey('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js);
 | 
			
		||||
 | 
			
		||||
    $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
 | 
			
		||||
    $rendered_css = (string) $this->renderer->renderInIsolation($css_render_array);
 | 
			
		||||
    $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 | 
			
		||||
    $rendered_js = (string) $this->renderer->renderInIsolation($js_render_array);
 | 
			
		||||
    $query_string = $this->container->get('asset.query_string')->get();
 | 
			
		||||
    $this->assertStringContainsString('<link rel="stylesheet" media="all" href="' . str_replace('&', '&', $this->fileUrlGenerator->generateString('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2')) . '&' . $query_string . '" />', $rendered_css, 'CSS file with query string gets version query string correctly appended..');
 | 
			
		||||
    $this->assertStringContainsString('<script src="' . str_replace('&', '&', $this->fileUrlGenerator->generateString('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2')) . '&' . $query_string . '"></script>', $rendered_js, 'JavaScript file with query string gets version query string correctly appended.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test settings can be loaded even when libraries are not.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAttachedSettingsWithoutLibraries(): void {
 | 
			
		||||
    $assets = new AttachedAssets();
 | 
			
		||||
 | 
			
		||||
    // First test with no libraries will return no settings.
 | 
			
		||||
    $assets->setSettings(['test' => 'foo']);
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertArrayNotHasKey('drupalSettings', $js);
 | 
			
		||||
 | 
			
		||||
    // Second test with a warm cache.
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertArrayNotHasKey('drupalSettings', $js);
 | 
			
		||||
 | 
			
		||||
    // Now test with different settings when drupalSettings is already loaded.
 | 
			
		||||
    $assets->setSettings(['test' => 'bar']);
 | 
			
		||||
    $assets->setAlreadyLoadedLibraries(['core/drupalSettings']);
 | 
			
		||||
    $js = $this->assetResolver->getJsAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage())[1];
 | 
			
		||||
    $this->assertSame('bar', $js['drupalSettings']['data']['test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,73 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Asset;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks the status and definition contents of deprecated libraries.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Asset
 | 
			
		||||
 * @group legacy
 | 
			
		||||
 */
 | 
			
		||||
class DeprecatedAssetsTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Confirms the status and definition contents of deprecated libraries.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $extension
 | 
			
		||||
   *   The name of the extension that registered a library.
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   The name of a registered library to retrieve.
 | 
			
		||||
   * @param string $deprecation_suffix
 | 
			
		||||
   *   The part of the deprecation message after the extension/name.
 | 
			
		||||
   * @param string $expected_hashed_library_definition
 | 
			
		||||
   *   The expected MD5 hash of the library.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider deprecatedLibrariesProvider
 | 
			
		||||
   */
 | 
			
		||||
  public function testDeprecatedLibraries(string $extension, string $name, string $deprecation_suffix, string $expected_hashed_library_definition): void {
 | 
			
		||||
    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
 | 
			
		||||
    $library_discovery = $this->container->get('library.discovery');
 | 
			
		||||
 | 
			
		||||
    // DrupalCI uses a precision of 100 in certain environments which breaks
 | 
			
		||||
    // this test.
 | 
			
		||||
    ini_set('serialize_precision', -1);
 | 
			
		||||
 | 
			
		||||
    $this->expectDeprecation("The $extension/$name " . $deprecation_suffix);
 | 
			
		||||
    $library_definition = $library_discovery->getLibraryByName($extension, $name);
 | 
			
		||||
    $this->assertEquals($expected_hashed_library_definition, md5(serialize($library_definition)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The data provider for testDeprecatedLibraries.
 | 
			
		||||
   *
 | 
			
		||||
   * Returns an array in the form of
 | 
			
		||||
   * @code
 | 
			
		||||
   *  [
 | 
			
		||||
   *    (string) description => [
 | 
			
		||||
   *      (string) extension - The name of the extension that registered a library, usually 'core'
 | 
			
		||||
   *      (string) name - The name of a registered library
 | 
			
		||||
   *      (string) deprecation_suffix - The part of the deprecation message after the extension/name
 | 
			
		||||
   *      (string) expected_hashed_library_definition -  The expected MD5 hash of the library
 | 
			
		||||
   *    ]
 | 
			
		||||
   *  ]
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   See description above.
 | 
			
		||||
   */
 | 
			
		||||
  public static function deprecatedLibrariesProvider(): array {
 | 
			
		||||
    return [
 | 
			
		||||
      'Tests deprecation of library core/js-cookie' => [
 | 
			
		||||
        'core',
 | 
			
		||||
        'js-cookie',
 | 
			
		||||
        'asset library is deprecated in Drupal 10.1.0 and will be removed in Drupal 11.0.0. There is no replacement. See https://www.drupal.org/node/3322720',
 | 
			
		||||
        '5d6a84c6143d0fa766cabdb1ff0a270d',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,350 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Asset;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Asset\Exception\InvalidLibrariesExtendSpecificationException;
 | 
			
		||||
use Drupal\Core\Asset\Exception\InvalidLibrariesOverrideSpecificationException;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the library discovery and library discovery parser.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Render
 | 
			
		||||
 */
 | 
			
		||||
class LibraryDiscoveryIntegrationTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The library discovery service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Asset\LibraryDiscoveryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $libraryDiscovery;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->container->get('theme_installer')->install(['test_theme', 'starterkit_theme']);
 | 
			
		||||
    $this->libraryDiscovery = $this->container->get('library.discovery');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that hook_library_info is invoked and the cache is cleared.
 | 
			
		||||
   */
 | 
			
		||||
  public function testHookLibraryInfoByTheme(): void {
 | 
			
		||||
    // Activate test_theme and verify that the library 'kitten' is added using
 | 
			
		||||
    // hook_library_info_alter().
 | 
			
		||||
    $this->activateTheme('test_theme');
 | 
			
		||||
    $this->assertNotEmpty($this->libraryDiscovery->getLibraryByName('test_theme', 'kitten'));
 | 
			
		||||
 | 
			
		||||
    // Now make starterkit_theme the active theme and assert that library is not
 | 
			
		||||
    // added.
 | 
			
		||||
    $this->activateTheme('starterkit_theme');
 | 
			
		||||
    $this->assertFalse($this->libraryDiscovery->getLibraryByName('test_theme', 'kitten'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that libraries-override are applied to library definitions.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibrariesOverride(): void {
 | 
			
		||||
    // Assert some starterkit_theme libraries that will be overridden or
 | 
			
		||||
    // removed.
 | 
			
		||||
    $this->activateTheme('starterkit_theme');
 | 
			
		||||
    $this->assertAssetInLibrary('core/themes/starterkit_theme/css/components/button.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertAssetInLibrary('core/themes/starterkit_theme/css/components/container-inline.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertAssetInLibrary('core/themes/starterkit_theme/css/components/details.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertAssetInLibrary('core/themes/starterkit_theme/css/components/dialog.css', 'starterkit_theme', 'dialog', 'css');
 | 
			
		||||
 | 
			
		||||
    // Confirmatory assert on core library to be removed.
 | 
			
		||||
    $this->assertNotEmpty($this->libraryDiscovery->getLibraryByName('core', 'drupal.progress'), 'Confirmatory test on "core/drupal.progress"');
 | 
			
		||||
 | 
			
		||||
    // Activate test theme that defines libraries overrides.
 | 
			
		||||
    $this->activateTheme('test_theme');
 | 
			
		||||
 | 
			
		||||
    // Assert that entire library was correctly overridden.
 | 
			
		||||
    $this->assertEquals($this->libraryDiscovery->getLibraryByName('core', 'drupal.collapse'), $this->libraryDiscovery->getLibraryByName('test_theme', 'collapse'), 'Entire library correctly overridden.');
 | 
			
		||||
 | 
			
		||||
    // Assert that starterkit_theme library assets were correctly overridden or
 | 
			
		||||
    // removed.
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/themes/starterkit_theme/css/components/button.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/themes/starterkit_theme/css/components/container-inline.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/themes/starterkit_theme/css/components/details.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/themes/starterkit_theme/css/components/dialog.css', 'starterkit_theme', 'dialog', 'css');
 | 
			
		||||
 | 
			
		||||
    $this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme/css/my-button.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertAssetInLibrary('themes/my_theme/css/my-container-inline.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertAssetInLibrary('themes/my_theme/css/my-details.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
 | 
			
		||||
    // Assert that entire library was correctly removed.
 | 
			
		||||
    $this->assertFalse($this->libraryDiscovery->getLibraryByName('core', 'drupal.progress'), 'Entire library correctly removed.');
 | 
			
		||||
 | 
			
		||||
    // Assert that overridden library asset still retains attributes.
 | 
			
		||||
    $library = $this->libraryDiscovery->getLibraryByName('core', 'drupal.batch');
 | 
			
		||||
    $this->assertSame('core/modules/system/tests/themes/test_theme/js/collapse.js', $library['js'][0]['data']);
 | 
			
		||||
    $this->assertFalse($library['js'][0]['cache']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests libraries-override on drupalSettings.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibrariesOverrideDrupalSettings(): void {
 | 
			
		||||
    // Activate test theme that attempts to override drupalSettings.
 | 
			
		||||
    $this->activateTheme('test_theme_libraries_override_with_drupal_settings');
 | 
			
		||||
 | 
			
		||||
    // Assert that drupalSettings cannot be overridden and throws an exception.
 | 
			
		||||
    try {
 | 
			
		||||
      $this->libraryDiscovery->getLibraryByName('core', 'drupal.ajax');
 | 
			
		||||
      $this->fail('Throw Exception when trying to override drupalSettings');
 | 
			
		||||
    }
 | 
			
		||||
    catch (InvalidLibrariesOverrideSpecificationException $e) {
 | 
			
		||||
      $expected_message = 'drupalSettings may not be overridden in libraries-override. Trying to override core/drupal.ajax/drupalSettings. Use hook_library_info_alter() instead.';
 | 
			
		||||
      $this->assertEquals($expected_message, $e->getMessage(), 'Throw Exception when trying to override drupalSettings');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests libraries-override on malformed assets.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibrariesOverrideMalformedAsset(): void {
 | 
			
		||||
    // Activate test theme that overrides with a malformed asset.
 | 
			
		||||
    $this->activateTheme('test_theme_libraries_override_with_invalid_asset');
 | 
			
		||||
 | 
			
		||||
    // Assert that improperly formed asset "specs" throw an exception.
 | 
			
		||||
    try {
 | 
			
		||||
      $this->libraryDiscovery->getLibraryByName('core', 'drupal.dialog');
 | 
			
		||||
      $this->fail('Throw Exception when specifying invalid override');
 | 
			
		||||
    }
 | 
			
		||||
    catch (InvalidLibrariesOverrideSpecificationException $e) {
 | 
			
		||||
      $expected_message = 'Library asset core/drupal.dialog/css is not correctly specified. It should be in the form "extension/library_name/sub_key/path/to/asset.js".';
 | 
			
		||||
      $this->assertEquals($expected_message, $e->getMessage(), 'Throw Exception when specifying invalid override');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests libraries overrides with multiple parent themes.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibrariesOverridesMultiple(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
 | 
			
		||||
    $theme_installer = $this->container->get('theme_installer');
 | 
			
		||||
    $theme_installer->install(['test_base_theme']);
 | 
			
		||||
    $theme_installer->install(['test_subtheme']);
 | 
			
		||||
    $theme_installer->install(['test_subsubtheme']);
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Theme\ThemeInitializationInterface $theme_initializer */
 | 
			
		||||
    $theme_initializer = $this->container->get('theme.initialization');
 | 
			
		||||
    $active_theme = $theme_initializer->initTheme('test_subsubtheme');
 | 
			
		||||
 | 
			
		||||
    $libraries_override = $active_theme->getLibrariesOverride();
 | 
			
		||||
    $expected_order = [
 | 
			
		||||
      'core/modules/system/tests/themes/test_base_theme',
 | 
			
		||||
      'core/modules/system/tests/themes/test_subtheme',
 | 
			
		||||
      'core/modules/system/tests/themes/test_subsubtheme',
 | 
			
		||||
    ];
 | 
			
		||||
    $this->assertEquals($expected_order, array_keys($libraries_override));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests library assets with other ways for specifying paths.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibrariesOverrideOtherAssetLibraryNames(): void {
 | 
			
		||||
    // Activate a test theme that defines libraries overrides on other types of
 | 
			
		||||
    // assets.
 | 
			
		||||
    $this->activateTheme('test_theme');
 | 
			
		||||
 | 
			
		||||
    // Assert Drupal-relative paths.
 | 
			
		||||
    $this->assertAssetInLibrary('themes/my_theme/css/dropbutton.css', 'core', 'drupal.dropbutton', 'css');
 | 
			
		||||
 | 
			
		||||
    // Assert stream wrapper paths.
 | 
			
		||||
    $this->assertAssetInLibrary('public://my_css/vertical-tabs.css', 'core', 'drupal.vertical-tabs', 'css');
 | 
			
		||||
 | 
			
		||||
    // Assert a protocol-relative URI.
 | 
			
		||||
    $this->assertAssetInLibrary('//my-server/my_theme/js/overridden.js', 'core', 'drupal.displace', 'js');
 | 
			
		||||
 | 
			
		||||
    // Assert an absolute URI.
 | 
			
		||||
    $this->assertAssetInLibrary('http://example.com/my_theme/js/announce.js', 'core', 'drupal.announce', 'js');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that base theme libraries-override still apply in sub themes.
 | 
			
		||||
   */
 | 
			
		||||
  public function testBaseThemeLibrariesOverrideInSubTheme(): void {
 | 
			
		||||
    // Activate a test theme that has subthemes.
 | 
			
		||||
    $this->activateTheme('test_subtheme');
 | 
			
		||||
 | 
			
		||||
    // Assert that libraries-override specified in the base theme still applies
 | 
			
		||||
    // in the sub theme.
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/misc/dialog/dialog.js', 'core', 'drupal.dialog', 'js');
 | 
			
		||||
    $this->assertAssetInLibrary('core/modules/system/tests/themes/test_base_theme/js/loadjs.min.js', 'core', 'loadjs', 'js');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests libraries-extend.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLibrariesExtend(): void {
 | 
			
		||||
    // Simulate starterkit_theme defining the test-navigation library.
 | 
			
		||||
    // @see theme_test_library_info_alter()
 | 
			
		||||
    $this->container->get('state')
 | 
			
		||||
      ->set('theme_test_library_info_alter starterkit_theme', [
 | 
			
		||||
        'test-navigation' => [
 | 
			
		||||
          'css' => [
 | 
			
		||||
            'component' => [
 | 
			
		||||
              'css/components/test-navigation.css' => [],
 | 
			
		||||
            ],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]);
 | 
			
		||||
 | 
			
		||||
    // Activate starterkit_theme and verify the libraries are not extended.
 | 
			
		||||
    $this->activateTheme('starterkit_theme');
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_1.css', 'starterkit_theme', 'test-navigation', 'css');
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/js/extend_1.js', 'starterkit_theme', 'test-navigation', 'js');
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_2.css', 'starterkit_theme', 'test-navigation', 'css');
 | 
			
		||||
 | 
			
		||||
    // Activate the theme that extends the test-navigation library in
 | 
			
		||||
    // starterkit_theme.
 | 
			
		||||
    $this->activateTheme('test_theme_libraries_extend');
 | 
			
		||||
    $this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_1.css', 'starterkit_theme', 'test-navigation', 'css');
 | 
			
		||||
    $this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/js/extend_1.js', 'starterkit_theme', 'test-navigation', 'js');
 | 
			
		||||
    $this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_2.css', 'starterkit_theme', 'test-navigation', 'css');
 | 
			
		||||
 | 
			
		||||
    // Activate a sub theme and confirm that it inherits the library assets
 | 
			
		||||
    // extended in the base theme as well as its own.
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_base_theme/css/base-libraries-extend.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_subtheme/css/sub-libraries-extend.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->activateTheme('test_subtheme');
 | 
			
		||||
    $this->assertAssetInLibrary('core/modules/system/tests/themes/test_base_theme/css/base-libraries-extend.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
    $this->assertAssetInLibrary('core/modules/system/tests/themes/test_subtheme/css/sub-libraries-extend.css', 'starterkit_theme', 'base', 'css');
 | 
			
		||||
 | 
			
		||||
    // Activate test theme that extends with a non-existent library. An
 | 
			
		||||
    // exception should be thrown.
 | 
			
		||||
    $this->activateTheme('test_theme_libraries_extend');
 | 
			
		||||
    try {
 | 
			
		||||
      $this->libraryDiscovery->getLibraryByName('core', 'drupal.dialog');
 | 
			
		||||
      $this->fail('Throw Exception when specifying non-existent libraries-extend.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (InvalidLibrariesExtendSpecificationException $e) {
 | 
			
		||||
      $expected_message = 'The specified library "test_theme_libraries_extend/non_existent_library" does not exist.';
 | 
			
		||||
      $this->assertEquals($expected_message, $e->getMessage(), 'Throw Exception when specifying non-existent libraries-extend.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Also, test non-string libraries-extend. An exception should be thrown.
 | 
			
		||||
    $this->container->get('theme_installer')->install(['test_theme']);
 | 
			
		||||
    try {
 | 
			
		||||
      $this->libraryDiscovery->getLibraryByName('test_theme', 'collapse');
 | 
			
		||||
      $this->fail('Throw Exception when specifying non-string libraries-extend.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (InvalidLibrariesExtendSpecificationException $e) {
 | 
			
		||||
      $expected_message = 'The libraries-extend specification for each library must be a list of strings.';
 | 
			
		||||
      $this->assertEquals($expected_message, $e->getMessage(), 'Throw Exception when specifying non-string libraries-extend.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test library deprecation support.
 | 
			
		||||
   *
 | 
			
		||||
   * @group legacy
 | 
			
		||||
   */
 | 
			
		||||
  public function testDeprecatedLibrary(): void {
 | 
			
		||||
    $this->expectDeprecation('Targeting theme_test/moved_from css/foo.css from test_theme_with_deprecated_libraries library_overrides is deprecated in drupal:X.0.0 and will be removed in drupal:Y.0.0. Target theme_test/moved_to css/base-remove.css instead. See https://example.com');
 | 
			
		||||
    $this->expectDeprecation('Targeting theme_test/moved_from js/bar.js from test_theme_with_deprecated_libraries library_overrides is deprecated in drupal:X.0.0 and will be removed in drupal:Y.0.0. Target theme_test/moved_to js/foo.js instead. See https://example.com');
 | 
			
		||||
    $this->expectDeprecation('Theme "theme_test" is overriding a deprecated library. The "theme_test/deprecated_library" asset library is deprecated in drupal:X.0.0 and is removed from drupal:Y.0.0. Use another library instead. See https://www.example.com');
 | 
			
		||||
    $this->expectDeprecation('Theme "theme_test" is extending a deprecated library. The "theme_test/another_deprecated_library" asset library is deprecated in drupal:X.0.0 and is removed from drupal:Y.0.0. Use another library instead. See https://www.example.com');
 | 
			
		||||
    $this->expectDeprecation('The "theme_test/deprecated_library" asset library is deprecated in drupal:X.0.0 and is removed from drupal:Y.0.0. Use another library instead. See https://www.example.com');
 | 
			
		||||
    $this->expectDeprecation('The "theme_test/another_deprecated_library" asset library is deprecated in drupal:X.0.0 and is removed from drupal:Y.0.0. Use another library instead. See https://www.example.com');
 | 
			
		||||
    $this->activateTheme('test_theme_with_deprecated_libraries');
 | 
			
		||||
    $this->libraryDiscovery->getLibraryByName('theme_test', 'moved_to');
 | 
			
		||||
    $this->libraryDiscovery->getLibraryByName('theme_test', 'deprecated_library');
 | 
			
		||||
    $this->libraryDiscovery->getLibraryByName('theme_test', 'another_deprecated_library');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Activates a specified theme.
 | 
			
		||||
   *
 | 
			
		||||
   * Installs the theme if not already installed and makes it the active theme.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $theme_name
 | 
			
		||||
   *   The name of the theme to be activated.
 | 
			
		||||
   */
 | 
			
		||||
  protected function activateTheme($theme_name): void {
 | 
			
		||||
    $this->container->get('theme_installer')->install([$theme_name]);
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Theme\ThemeInitializationInterface $theme_initializer */
 | 
			
		||||
    $theme_initializer = $this->container->get('theme.initialization');
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Theme\ThemeManagerInterface $theme_manager */
 | 
			
		||||
    $theme_manager = $this->container->get('theme.manager');
 | 
			
		||||
 | 
			
		||||
    $theme_manager->setActiveTheme($theme_initializer->getActiveThemeByName($theme_name));
 | 
			
		||||
 | 
			
		||||
    $this->libraryDiscovery->clear();
 | 
			
		||||
 | 
			
		||||
    $this->assertSame($theme_name, $theme_manager->getActiveTheme()->getName());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts that the specified asset is in the given library.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $asset
 | 
			
		||||
   *   The asset file with the path for the file.
 | 
			
		||||
   * @param string $extension
 | 
			
		||||
   *   The extension in which the $library is defined.
 | 
			
		||||
   * @param string $library_name
 | 
			
		||||
   *   Name of the library.
 | 
			
		||||
   * @param string $sub_key
 | 
			
		||||
   *   The library sub key where the given asset is defined.
 | 
			
		||||
   * @param string $message
 | 
			
		||||
   *   (optional) A message to display with the assertion.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertAssetInLibrary(string $asset, string $extension, string $library_name, string $sub_key, ?string $message = NULL): void {
 | 
			
		||||
    if (!isset($message)) {
 | 
			
		||||
      $message = sprintf('Asset %s found in library "%s/%s"', $asset, $extension, $library_name);
 | 
			
		||||
    }
 | 
			
		||||
    $library = $this->libraryDiscovery->getLibraryByName($extension, $library_name);
 | 
			
		||||
    foreach ($library[$sub_key] as $definition) {
 | 
			
		||||
      if ($asset == $definition['data']) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->fail($message);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts that the specified asset is not in the given library.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $asset
 | 
			
		||||
   *   The asset file with the path for the file.
 | 
			
		||||
   * @param string $extension
 | 
			
		||||
   *   The extension in which the $library_name is defined.
 | 
			
		||||
   * @param string $library_name
 | 
			
		||||
   *   Name of the library.
 | 
			
		||||
   * @param string $sub_key
 | 
			
		||||
   *   The library sub key where the given asset is defined.
 | 
			
		||||
   * @param string $message
 | 
			
		||||
   *   (optional) A message to display with the assertion.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertNoAssetInLibrary(string $asset, string $extension, string $library_name, string $sub_key, ?string $message = NULL): void {
 | 
			
		||||
    if (!isset($message)) {
 | 
			
		||||
      $message = sprintf('Asset %s not found in library "%s/%s"', $asset, $extension, $library_name);
 | 
			
		||||
    }
 | 
			
		||||
    $library = $this->libraryDiscovery->getLibraryByName($extension, $library_name);
 | 
			
		||||
    foreach ($library[$sub_key] as $definition) {
 | 
			
		||||
      if ($asset == $definition['data']) {
 | 
			
		||||
        $this->fail($message);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,263 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Asset;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that the asset files for all core libraries exist.
 | 
			
		||||
 *
 | 
			
		||||
 * This test also changes the active theme to each core theme to verify
 | 
			
		||||
 * the libraries after theme-level libraries-override and libraries-extend are
 | 
			
		||||
 * applied.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Asset
 | 
			
		||||
 * @group #slow
 | 
			
		||||
 */
 | 
			
		||||
class ResolvedLibraryDefinitionsFilesMatchTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The theme handler.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\ThemeHandlerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $themeHandler;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The theme initialization.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Theme\ThemeInitializationInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $themeInitialization;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The theme manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Theme\ThemeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $themeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The library discovery service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Asset\LibraryDiscoveryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $libraryDiscovery;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A list of all core modules.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $allModules;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A list of all core themes.
 | 
			
		||||
   *
 | 
			
		||||
   * We hardcode this because test themes don't use a 'package' or 'hidden' key
 | 
			
		||||
   * so we don't have a good way of filtering to only get "real" themes.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $allThemes = [
 | 
			
		||||
    'claro',
 | 
			
		||||
    'olivero',
 | 
			
		||||
    'stable9',
 | 
			
		||||
    'stark',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A list of libraries to skip checking, in the format extension/library_name.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $librariesToSkip = [
 | 
			
		||||
    // Locale has a "dummy" library that does not actually exist.
 | 
			
		||||
    'locale/translations',
 | 
			
		||||
    // Core has a "dummy" library that does not actually exist.
 | 
			
		||||
    'core/ckeditor5.translations',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A list of all paths that have been checked.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $pathsChecked;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'user', 'path_alias'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    // Install all core themes.
 | 
			
		||||
    sort($this->allThemes);
 | 
			
		||||
    $this->container->get('theme_installer')->install($this->allThemes);
 | 
			
		||||
 | 
			
		||||
    $this->themeHandler = $this->container->get('theme_handler');
 | 
			
		||||
    $this->themeInitialization = $this->container->get('theme.initialization');
 | 
			
		||||
    $this->themeManager = $this->container->get('theme.manager');
 | 
			
		||||
    $this->libraryDiscovery = $this->container->get('library.discovery');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures that all core module and theme library files exist.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCoreLibraryCompleteness(): void {
 | 
			
		||||
    // Enable all core modules.
 | 
			
		||||
    $all_modules = $this->container->get('extension.list.module')->getList();
 | 
			
		||||
    $all_modules = array_filter($all_modules, function ($module) {
 | 
			
		||||
      // Filter contrib, hidden, already enabled modules and modules in the
 | 
			
		||||
      // Testing package.
 | 
			
		||||
      if ($module->origin !== 'core'
 | 
			
		||||
        || !empty($module->info['hidden'])
 | 
			
		||||
        || $module->status == TRUE
 | 
			
		||||
        || $module->info['package'] == 'Testing'
 | 
			
		||||
        || $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL
 | 
			
		||||
        || $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
 | 
			
		||||
        return FALSE;
 | 
			
		||||
      }
 | 
			
		||||
      return TRUE;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Install the 'user' entity schema because the workspaces module's install
 | 
			
		||||
    // hook creates a workspace with default uid of 1. Then the layout_builder
 | 
			
		||||
    // module's implementation of hook_entity_presave will cause
 | 
			
		||||
    // \Drupal\Core\TypedData\Validation\RecursiveValidator::validate() to run
 | 
			
		||||
    // on the workspace which will fail because the user table is not present.
 | 
			
		||||
    // @todo Remove this in https://www.drupal.org/node/3039217.
 | 
			
		||||
    $this->installEntitySchema('user');
 | 
			
		||||
 | 
			
		||||
    // Install the 'path_alias' entity schema because the path alias path
 | 
			
		||||
    // processor requires it.
 | 
			
		||||
    $this->installEntitySchema('path_alias');
 | 
			
		||||
 | 
			
		||||
    // Remove demo_umami_content module as its install hook creates content
 | 
			
		||||
    // that relies on the presence of entity tables and various other elements
 | 
			
		||||
    // not present in a kernel test.
 | 
			
		||||
    unset($all_modules['demo_umami_content']);
 | 
			
		||||
    $this->allModules = array_keys($all_modules);
 | 
			
		||||
    $this->allModules[] = 'system';
 | 
			
		||||
    $this->allModules[] = 'user';
 | 
			
		||||
    $this->allModules[] = 'path_alias';
 | 
			
		||||
    $database_module = \Drupal::database()->getProvider();
 | 
			
		||||
    if ($database_module !== 'core') {
 | 
			
		||||
      $this->allModules[] = $database_module;
 | 
			
		||||
    }
 | 
			
		||||
    sort($this->allModules);
 | 
			
		||||
    $this->container->get('module_installer')->install($this->allModules);
 | 
			
		||||
    // Get a library discovery from the new container.
 | 
			
		||||
    $this->libraryDiscovery = $this->container->get('library.discovery');
 | 
			
		||||
 | 
			
		||||
    $this->assertLibraries();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures that module and theme library files exist for a deprecated modules.
 | 
			
		||||
   *
 | 
			
		||||
   * @group legacy
 | 
			
		||||
   */
 | 
			
		||||
  public function testCoreLibraryCompletenessDeprecated(): void {
 | 
			
		||||
    // Find and install deprecated modules to test.
 | 
			
		||||
    $all_modules = $this->container->get('extension.list.module')->getList();
 | 
			
		||||
    $deprecated_modules_to_test = array_filter($all_modules, function ($module) {
 | 
			
		||||
      if ($module->origin == 'core'
 | 
			
		||||
        && $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
 | 
			
		||||
        return TRUE;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    $this->container->get('module_installer')->install(array_keys($deprecated_modules_to_test));
 | 
			
		||||
    $this->libraryDiscovery = $this->container->get('library.discovery');
 | 
			
		||||
    $this->allModules = array_keys(\Drupal::moduleHandler()->getModuleList());
 | 
			
		||||
 | 
			
		||||
    $this->assertLibraries();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts the libraries for modules and themes exist.
 | 
			
		||||
   */
 | 
			
		||||
  public function assertLibraries(): void {
 | 
			
		||||
    // First verify all libraries with no active theme.
 | 
			
		||||
    $this->verifyLibraryFilesExist($this->getAllLibraries());
 | 
			
		||||
 | 
			
		||||
    // Then verify all libraries for each core theme. This may seem like
 | 
			
		||||
    // overkill but themes can override and extend other extensions' libraries
 | 
			
		||||
    // and these changes are only applied for the active theme.
 | 
			
		||||
    foreach ($this->allThemes as $theme) {
 | 
			
		||||
      $this->themeManager->setActiveTheme($this->themeInitialization->getActiveThemeByName($theme));
 | 
			
		||||
      $this->libraryDiscovery->clear();
 | 
			
		||||
 | 
			
		||||
      $this->verifyLibraryFilesExist($this->getAllLibraries());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks that all the library files exist.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array[] $library_definitions
 | 
			
		||||
   *   An array of library definitions, keyed by extension, then by library, and
 | 
			
		||||
   *   so on.
 | 
			
		||||
   */
 | 
			
		||||
  protected function verifyLibraryFilesExist($library_definitions): void {
 | 
			
		||||
    foreach ($library_definitions as $extension => $libraries) {
 | 
			
		||||
      foreach ($libraries as $library_name => $library) {
 | 
			
		||||
        if (in_array("$extension/$library_name", $this->librariesToSkip)) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check that all the assets exist.
 | 
			
		||||
        foreach (['css', 'js'] as $asset_type) {
 | 
			
		||||
          foreach ($library[$asset_type] as $asset) {
 | 
			
		||||
            $file = $asset['data'];
 | 
			
		||||
            $path = $this->root . '/' . $file;
 | 
			
		||||
            // Only check and assert each file path once.
 | 
			
		||||
            if (!isset($this->pathsChecked[$path])) {
 | 
			
		||||
              $this->assertFileExists($path, "$file file referenced from the $extension/$library_name library does not exist.");
 | 
			
		||||
              $this->pathsChecked[$path] = TRUE;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets all libraries for core and all installed modules.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Extension\Extension[]
 | 
			
		||||
   *   An array of discovered libraries keyed by extension name.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getAllLibraries() {
 | 
			
		||||
    $modules = \Drupal::moduleHandler()->getModuleList();
 | 
			
		||||
    $extensions = $modules;
 | 
			
		||||
    $module_list = array_keys($modules);
 | 
			
		||||
    sort($module_list);
 | 
			
		||||
    $this->assertEquals($this->allModules, $module_list, 'All core modules are installed.');
 | 
			
		||||
 | 
			
		||||
    $themes = $this->themeHandler->listInfo();
 | 
			
		||||
    $extensions += $themes;
 | 
			
		||||
    $theme_list = array_keys($themes);
 | 
			
		||||
    sort($theme_list);
 | 
			
		||||
    $this->assertEquals($this->allThemes, $theme_list, 'All core themes are installed.');
 | 
			
		||||
 | 
			
		||||
    $libraries['core'] = $this->libraryDiscovery->getLibrariesByExtension('core');
 | 
			
		||||
 | 
			
		||||
    foreach ($extensions as $extension_name => $extension) {
 | 
			
		||||
      $library_file = $extension->getPath() . '/' . $extension_name . '.libraries.yml';
 | 
			
		||||
      if (is_file($this->root . '/' . $library_file)) {
 | 
			
		||||
        $libraries[$extension_name] = $this->libraryDiscovery->getLibrariesByExtension($extension_name);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $libraries;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Batch;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests batch functionality.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Batch
 | 
			
		||||
 */
 | 
			
		||||
class BatchKernelTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    require_once $this->root . '/core/includes/batch.inc';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests _batch_needs_update().
 | 
			
		||||
   */
 | 
			
		||||
  public function testNeedsUpdate(): void {
 | 
			
		||||
    // Before ever being called, the return value should be FALSE.
 | 
			
		||||
    $this->assertEquals(FALSE, _batch_needs_update());
 | 
			
		||||
 | 
			
		||||
    // Set the value to TRUE.
 | 
			
		||||
    $this->assertEquals(TRUE, _batch_needs_update(TRUE));
 | 
			
		||||
    // Check that without a parameter TRUE is returned.
 | 
			
		||||
    $this->assertEquals(TRUE, _batch_needs_update());
 | 
			
		||||
 | 
			
		||||
    // Set the value to FALSE.
 | 
			
		||||
    $this->assertEquals(FALSE, _batch_needs_update(FALSE));
 | 
			
		||||
    $this->assertEquals(FALSE, _batch_needs_update());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,39 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Batch;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Routing\RouteMatch;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the BatchNegotiator.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Batch
 | 
			
		||||
 */
 | 
			
		||||
class BatchNegotiatorTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test that the negotiator applies to the batch route.
 | 
			
		||||
   */
 | 
			
		||||
  public function testApplies(): void {
 | 
			
		||||
    $request = Request::create('/batch');
 | 
			
		||||
    // Use the router to enhance the object so that a RouteMatch can be created.
 | 
			
		||||
    $this->container->get('router')->matchRequest($request);
 | 
			
		||||
    $routeMatch = RouteMatch::createFromRequest($request);
 | 
			
		||||
    // The negotiator under test.
 | 
			
		||||
    $negotiator = $this->container->get('theme.negotiator.system.batch');
 | 
			
		||||
 | 
			
		||||
    $this->assertTrue($negotiator->applies($routeMatch));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Block;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormState;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the BlockBase class, base for all block plugins.
 | 
			
		||||
 *
 | 
			
		||||
 * @group block
 | 
			
		||||
 */
 | 
			
		||||
class BlockBaseTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'block', 'block_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that blocks config form have context mapping, and it is stored in configuration.
 | 
			
		||||
   */
 | 
			
		||||
  public function testContextMapping(): void {
 | 
			
		||||
    $configuration = ['label' => 'A very cool block'];
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Block\BlockManagerInterface $blockManager */
 | 
			
		||||
    $blockManager = \Drupal::service('plugin.manager.block');
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Block\BlockBase $block */
 | 
			
		||||
    $block = $blockManager->createInstance('test_block_instantiation', $configuration);
 | 
			
		||||
 | 
			
		||||
    // Check that context mapping is present in the block config form.
 | 
			
		||||
    $form = [];
 | 
			
		||||
    $form_state = new FormState();
 | 
			
		||||
    $form = $block->buildConfigurationForm($form, $form_state);
 | 
			
		||||
    $this->assertArrayHasKey('context_mapping', $form);
 | 
			
		||||
 | 
			
		||||
    // Check that context mapping is stored in block's configuration.
 | 
			
		||||
    $context_mapping = [
 | 
			
		||||
      'user' => 'current_user',
 | 
			
		||||
    ];
 | 
			
		||||
    $form_state->setValue('context_mapping', $context_mapping);
 | 
			
		||||
    $block->submitConfigurationForm($form, $form_state);
 | 
			
		||||
    $this->assertEquals($context_mapping, $block->getConfiguration()['context_mapping'] ?? NULL);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,40 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Block;
 | 
			
		||||
 | 
			
		||||
use Drupal\block_test\PluginForm\EmptyBlockForm;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that blocks can have multiple forms.
 | 
			
		||||
 *
 | 
			
		||||
 * @group block
 | 
			
		||||
 */
 | 
			
		||||
class MultipleBlockFormTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'block', 'block_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that blocks can have multiple forms.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMultipleForms(): void {
 | 
			
		||||
    $configuration = ['label' => 'A very cool block'];
 | 
			
		||||
    $block = \Drupal::service('plugin.manager.block')->createInstance('test_multiple_forms_block', $configuration);
 | 
			
		||||
 | 
			
		||||
    $form_object1 = \Drupal::service('plugin_form.factory')->createInstance($block, 'configure');
 | 
			
		||||
    $form_object2 = \Drupal::service('plugin_form.factory')->createInstance($block, 'secondary');
 | 
			
		||||
 | 
			
		||||
    // Assert that the block itself is used for the default form.
 | 
			
		||||
    $this->assertSame($block, $form_object1);
 | 
			
		||||
 | 
			
		||||
    // Ensure that EmptyBlockForm is used and the plugin is set.
 | 
			
		||||
    $this->assertInstanceOf(EmptyBlockForm::class, $form_object2);
 | 
			
		||||
    $this->assertEquals($block, $form_object2->plugin);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,116 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Bootstrap;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\Exception\UnknownExtensionException;
 | 
			
		||||
use Drupal\Core\Extension\Exception\UnknownExtensionTypeException;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionPathResolver;
 | 
			
		||||
use Drupal\Core\Extension\ModuleExtensionList;
 | 
			
		||||
use Drupal\Core\Extension\ProfileExtensionList;
 | 
			
		||||
use Drupal\Core\Extension\ThemeEngineExtensionList;
 | 
			
		||||
use Drupal\Core\Extension\ThemeExtensionList;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that extension path resolver works correctly.
 | 
			
		||||
 *
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Extension\ExtensionPathResolver
 | 
			
		||||
 *
 | 
			
		||||
 * @group Bootstrap
 | 
			
		||||
 */
 | 
			
		||||
class ExtensionPathResolverTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getPathname
 | 
			
		||||
   */
 | 
			
		||||
  public function testExtensionPathResolving(): void {
 | 
			
		||||
    // Retrieving the location of a module.
 | 
			
		||||
    $this->assertSame('core/modules/system/system.info.yml', \Drupal::service('extension.list.module')
 | 
			
		||||
      ->getPathname('system'));
 | 
			
		||||
 | 
			
		||||
    // Retrieving the location of a theme.
 | 
			
		||||
    \Drupal::service('theme_installer')->install(['stark']);
 | 
			
		||||
    $this->assertSame('core/themes/stark/stark.info.yml', \Drupal::service('extension.list.theme')
 | 
			
		||||
      ->getPathname('stark'));
 | 
			
		||||
 | 
			
		||||
    // Retrieving the location of a theme engine.
 | 
			
		||||
    $this->assertSame('core/themes/engines/twig/twig.info.yml', \Drupal::service('extension.list.theme_engine')
 | 
			
		||||
      ->getPathname('twig'));
 | 
			
		||||
 | 
			
		||||
    // Retrieving the location of a profile. Profiles are a special case with
 | 
			
		||||
    // a fixed location and naming.
 | 
			
		||||
    $this->assertSame('core/profiles/tests/testing/testing.info.yml', \Drupal::service('extension.list.profile')
 | 
			
		||||
      ->getPathname('testing'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getPath
 | 
			
		||||
   */
 | 
			
		||||
  public function testExtensionPathResolvingPath(): void {
 | 
			
		||||
    $this->assertSame('core/modules/system/tests/modules/driver_test', \Drupal::service('extension.list.module')
 | 
			
		||||
      ->getPath('driver_test'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getPathname
 | 
			
		||||
   */
 | 
			
		||||
  public function testExtensionPathResolvingWithNonExistingModule(): void {
 | 
			
		||||
    $this->expectException(UnknownExtensionException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The module there_is_a_module_for_that does not exist.');
 | 
			
		||||
    $this->assertNull(\Drupal::service('extension.list.module')
 | 
			
		||||
      ->getPathname('there_is_a_module_for_that'), 'Searching for an item that does not exist returns NULL.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getPathname
 | 
			
		||||
   */
 | 
			
		||||
  public function testExtensionPathResolvingWithNonExistingTheme(): void {
 | 
			
		||||
    $this->expectException(UnknownExtensionException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The theme there_is_a_theme_for_you does not exist.');
 | 
			
		||||
    $this->assertNull(\Drupal::service('extension.list.theme')
 | 
			
		||||
      ->getPathname('there_is_a_theme_for_you'), 'Searching for an item that does not exist returns NULL.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getPathname
 | 
			
		||||
   */
 | 
			
		||||
  public function testExtensionPathResolvingWithNonExistingProfile(): void {
 | 
			
		||||
    $this->expectException(UnknownExtensionException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The profile there_is_an_install_profile_for_you does not exist.');
 | 
			
		||||
    $this->assertNull(\Drupal::service('extension.list.profile')
 | 
			
		||||
      ->getPathname('there_is_an_install_profile_for_you'), 'Searching for an item that does not exist returns NULL.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getPathname
 | 
			
		||||
   */
 | 
			
		||||
  public function testExtensionPathResolvingWithNonExistingThemeEngine(): void {
 | 
			
		||||
    $this->expectException(UnknownExtensionException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The theme_engine there_is_an_theme_engine_for_you does not exist');
 | 
			
		||||
    $this->assertNull(\Drupal::service('extension.list.theme_engine')
 | 
			
		||||
      ->getPathname('there_is_an_theme_engine_for_you'), 'Searching for an item that does not exist returns NULL.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the getPath() method with an unknown extension.
 | 
			
		||||
   */
 | 
			
		||||
  public function testUnknownExtension(): void {
 | 
			
		||||
    $module_extension_list = $this->prophesize(ModuleExtensionList::class);
 | 
			
		||||
    $profile_extension_list = $this->prophesize(ProfileExtensionList::class);
 | 
			
		||||
    $theme_extension_list = $this->prophesize(ThemeExtensionList::class);
 | 
			
		||||
    $theme_engine_extension_list = $this->prophesize(ThemeEngineExtensionList::class);
 | 
			
		||||
    $resolver = new ExtensionPathResolver(
 | 
			
		||||
      $module_extension_list->reveal(),
 | 
			
		||||
      $profile_extension_list->reveal(),
 | 
			
		||||
      $theme_extension_list->reveal(),
 | 
			
		||||
      $theme_engine_extension_list->reveal()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $this->expectException(UnknownExtensionTypeException::class);
 | 
			
		||||
    $this->expectExceptionMessage('Extension type foo is unknown.');
 | 
			
		||||
    $resolver->getPath('foo', 'bar');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,62 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Bootstrap;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that drupal_static() and drupal_static_reset() work.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Bootstrap
 | 
			
		||||
 */
 | 
			
		||||
class ResettableStaticTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests drupal_static() function.
 | 
			
		||||
   *
 | 
			
		||||
   * Tests that a variable reference returned by drupal_static() gets reset when
 | 
			
		||||
   * drupal_static_reset() is called.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDrupalStatic(): void {
 | 
			
		||||
    $name = __CLASS__ . '_' . __METHOD__;
 | 
			
		||||
    $var = &drupal_static($name, 'foo');
 | 
			
		||||
    $this->assertEquals('foo', $var, '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->assertEquals('foo', $var, 'Variable was reset after first invocation of name-specific reset.');
 | 
			
		||||
    $var = 'bar';
 | 
			
		||||
    drupal_static_reset($name);
 | 
			
		||||
    $this->assertEquals('foo', $var, 'Variable was reset after second invocation of name-specific reset.');
 | 
			
		||||
    $var = 'bar';
 | 
			
		||||
    drupal_static_reset();
 | 
			
		||||
    $this->assertEquals('foo', $var, 'Variable was reset after first invocation of global reset.');
 | 
			
		||||
    $var = 'bar';
 | 
			
		||||
    drupal_static_reset();
 | 
			
		||||
    $this->assertEquals('foo', $var, 'Variable was reset after second invocation of global reset.');
 | 
			
		||||
 | 
			
		||||
    // Test calling drupal_static() with no arguments (empty string).
 | 
			
		||||
    $name1 = __CLASS__ . '_' . __METHOD__ . '1';
 | 
			
		||||
    $name2 = '';
 | 
			
		||||
    $var1 = &drupal_static($name1, 'initial1');
 | 
			
		||||
    $var2 = &drupal_static($name2, 'initial2');
 | 
			
		||||
    $this->assertEquals('initial1', $var1, 'Variable 1 returned by drupal_static() was set to its default.');
 | 
			
		||||
    $this->assertEquals('initial2', $var2, 'Variable 2 returned by drupal_static() was set to its default.');
 | 
			
		||||
    $var1 = 'modified1';
 | 
			
		||||
    $var2 = 'modified2';
 | 
			
		||||
    drupal_static_reset($name1);
 | 
			
		||||
    drupal_static_reset($name2);
 | 
			
		||||
    $this->assertEquals('initial1', $var1, 'Variable 1 was reset after invocation of name-specific reset.');
 | 
			
		||||
    $this->assertEquals('initial2', $var2, 'Variable 2 was reset after invocation of name-specific reset.');
 | 
			
		||||
    $var1 = 'modified1';
 | 
			
		||||
    $var2 = 'modified2';
 | 
			
		||||
    drupal_static_reset();
 | 
			
		||||
    $this->assertEquals('initial1', $var1, 'Variable 1 was reset after invocation of global reset.');
 | 
			
		||||
    $this->assertEquals('initial2', $var2, 'Variable 2 was reset after invocation of global reset.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,66 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Bootstrap;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Bootstrap
 | 
			
		||||
 */
 | 
			
		||||
class ShutdownFunctionTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Flag to indicate if ::shutdownOne() called.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $shutDownOneCalled = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Flag to indicate if ::shutdownTwo() called.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected $shutDownTwoCalled = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that shutdown functions can be added by other shutdown functions.
 | 
			
		||||
   */
 | 
			
		||||
  public function testShutdownFunctionInShutdownFunction(): void {
 | 
			
		||||
    // Ensure there are no shutdown functions registered before starting the
 | 
			
		||||
    // test.
 | 
			
		||||
    $this->assertEmpty(drupal_register_shutdown_function());
 | 
			
		||||
    // Register a shutdown function that, when called, will register another
 | 
			
		||||
    // shutdown function.
 | 
			
		||||
    drupal_register_shutdown_function([$this, 'shutdownOne']);
 | 
			
		||||
    $this->assertCount(1, drupal_register_shutdown_function());
 | 
			
		||||
 | 
			
		||||
    // Simulate the Drupal shutdown.
 | 
			
		||||
    _drupal_shutdown_function();
 | 
			
		||||
 | 
			
		||||
    // Test that the expected functions are called.
 | 
			
		||||
    $this->assertTrue($this->shutDownOneCalled);
 | 
			
		||||
    $this->assertTrue($this->shutDownTwoCalled);
 | 
			
		||||
    $this->assertCount(2, drupal_register_shutdown_function());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests shutdown functions by registering another shutdown function.
 | 
			
		||||
   */
 | 
			
		||||
  public function shutdownOne(): void {
 | 
			
		||||
    drupal_register_shutdown_function([$this, 'shutdownTwo']);
 | 
			
		||||
    $this->shutDownOneCalled = TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests shutdown functions by being registered during shutdown.
 | 
			
		||||
   */
 | 
			
		||||
  public function shutdownTwo(): void {
 | 
			
		||||
    $this->shutDownTwoCalled = TRUE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\ApcuBackend;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the APCu cache backend.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 * @requires extension apcu
 | 
			
		||||
 */
 | 
			
		||||
class ApcuBackendTest extends GenericCacheBackendUnitTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function createCacheBackend($bin) {
 | 
			
		||||
    return new ApcuBackend($bin, $this->databasePrefix, \Drupal::service('cache_tags.invalidator.checksum'), \Drupal::service(TimeInterface::class));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function tearDown(): void {
 | 
			
		||||
    foreach ($this->cacheBackends as $bin => $cache_backend) {
 | 
			
		||||
      $this->cacheBackends[$bin]->removeBin();
 | 
			
		||||
    }
 | 
			
		||||
    parent::tearDown();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function testSetGet(): void {
 | 
			
		||||
    parent::testSetGet();
 | 
			
		||||
 | 
			
		||||
    // Make sure entries are permanent (i.e. no TTL).
 | 
			
		||||
    $backend = $this->getCacheBackend($this->getTestBin());
 | 
			
		||||
    $key = $backend->getApcuKey('TEST8');
 | 
			
		||||
 | 
			
		||||
    $iterator = new \APCUIterator('/^' . $key . '/');
 | 
			
		||||
    foreach ($iterator as $item) {
 | 
			
		||||
      $this->assertEquals(0, $item['ttl']);
 | 
			
		||||
      $found = TRUE;
 | 
			
		||||
    }
 | 
			
		||||
    $this->assertTrue($found);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\BackendChain;
 | 
			
		||||
use Drupal\Core\Cache\MemoryBackend;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit test of the backend chain using the generic cache unit test base.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class BackendChainTest extends GenericCacheBackendUnitTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function createCacheBackend($bin) {
 | 
			
		||||
    $chain = new BackendChain();
 | 
			
		||||
 | 
			
		||||
    // We need to create some various backends in the chain.
 | 
			
		||||
    $time = \Drupal::service(TimeInterface::class);
 | 
			
		||||
    $chain
 | 
			
		||||
      ->appendBackend(new MemoryBackend($time))
 | 
			
		||||
      ->prependBackend(new MemoryBackend($time))
 | 
			
		||||
      ->appendBackend(new MemoryBackend($time));
 | 
			
		||||
 | 
			
		||||
    \Drupal::service('cache_tags.invalidator')->addInvalidator($chain);
 | 
			
		||||
 | 
			
		||||
    return $chain;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,67 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\Tests\Core\Cache\CacheCollectorHelper;
 | 
			
		||||
use Drupal\TestTools\Random;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Reference;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests DatabaseBackend cache tag implementation.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class CacheCollectorTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function register(ContainerBuilder $container): void {
 | 
			
		||||
    parent::register($container);
 | 
			
		||||
    // Change container to database cache backends.
 | 
			
		||||
    $container
 | 
			
		||||
      ->register('cache_factory', 'Drupal\Core\Cache\CacheFactory')
 | 
			
		||||
      ->addArgument(new Reference('settings'))
 | 
			
		||||
      ->addMethodCall('setContainer', [new Reference('service_container')]);
 | 
			
		||||
 | 
			
		||||
    // Change container to use database lock backends.
 | 
			
		||||
    $container
 | 
			
		||||
      ->register('lock', 'Drupal\Core\Lock\DatabaseLockBackend')
 | 
			
		||||
      ->addArgument(new Reference('database'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests setting and invalidating.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerTestInvalidCharacters
 | 
			
		||||
   */
 | 
			
		||||
  public function testCacheCollector($cid, $key, $value): void {
 | 
			
		||||
    $collector = new CacheCollectorHelper($cid, $this->container->get('cache.default'), $this->container->get('lock'));
 | 
			
		||||
    $this->assertNull($collector->get($key));
 | 
			
		||||
    $collector->set($key, $value);
 | 
			
		||||
    $this->assertEquals($value, $collector->get($key));
 | 
			
		||||
    $collector->destruct();
 | 
			
		||||
    // @todo Shouldn't this be empty after destruction?
 | 
			
		||||
    $this->assertEquals($value, $collector->get($key));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Data provider for ::testCacheCollector().
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerTestInvalidCharacters() {
 | 
			
		||||
    return [
 | 
			
		||||
      // Nothing special.
 | 
			
		||||
      ['foo', 'bar', 'baz'],
 | 
			
		||||
      // Invalid characters in CID.
 | 
			
		||||
      // cSpell:disable-next-line
 | 
			
		||||
      ['éøïвβ中國書۞', 'foo', 'bar'],
 | 
			
		||||
      // Really long CID.
 | 
			
		||||
      [Random::string(1024), 'foo', 'bar'],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,117 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\Tests\user\Traits\UserCreationTrait;
 | 
			
		||||
use Drupal\user\Entity\Role;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the cache context optimization.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Render
 | 
			
		||||
 */
 | 
			
		||||
class CacheContextOptimizationTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use UserCreationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['user', 'system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installEntitySchema('user');
 | 
			
		||||
    $this->installConfig(['user']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures that 'user.permissions' cache context is able to define cache tags.
 | 
			
		||||
   */
 | 
			
		||||
  public function testUserPermissionCacheContextOptimization(): void {
 | 
			
		||||
    $user1 = $this->createUser();
 | 
			
		||||
    $this->assertEquals(1, $user1->id());
 | 
			
		||||
 | 
			
		||||
    $authenticated_user = $this->createUser(['administer permissions']);
 | 
			
		||||
    $role = $authenticated_user->getRoles()[1];
 | 
			
		||||
 | 
			
		||||
    $test_element = [
 | 
			
		||||
      '#cache' => [
 | 
			
		||||
        'keys' => ['test'],
 | 
			
		||||
        'contexts' => ['user', 'user.permissions'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    \Drupal::service('account_switcher')->switchTo($authenticated_user);
 | 
			
		||||
    $element = $test_element;
 | 
			
		||||
    $element['#markup'] = 'content for authenticated users';
 | 
			
		||||
    $output = \Drupal::service('renderer')->renderRoot($element);
 | 
			
		||||
    $this->assertEquals('content for authenticated users', $output);
 | 
			
		||||
 | 
			
		||||
    // Verify that the render caching is working so that other tests can be
 | 
			
		||||
    // trusted.
 | 
			
		||||
    $element = $test_element;
 | 
			
		||||
    $element['#markup'] = 'this should not be visible';
 | 
			
		||||
    $output = \Drupal::service('renderer')->renderRoot($element);
 | 
			
		||||
    $this->assertEquals('content for authenticated users', $output);
 | 
			
		||||
 | 
			
		||||
    // Even though the cache contexts have been optimized to only include 'user'
 | 
			
		||||
    // cache context, the element should have been changed because
 | 
			
		||||
    // 'user.permissions' cache context defined a cache tags for permission
 | 
			
		||||
    // changes, which should have bubbled up for the element when it was
 | 
			
		||||
    // optimized away.
 | 
			
		||||
    Role::load($role)
 | 
			
		||||
      ->revokePermission('administer permissions')
 | 
			
		||||
      ->save();
 | 
			
		||||
    $element = $test_element;
 | 
			
		||||
    $element['#markup'] = 'this should be visible';
 | 
			
		||||
    $output = \Drupal::service('renderer')->renderRoot($element);
 | 
			
		||||
    $this->assertEquals('this should be visible', $output);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures that 'user.roles' still works when it is optimized away.
 | 
			
		||||
   */
 | 
			
		||||
  public function testUserRolesCacheContextOptimization(): void {
 | 
			
		||||
    $root_user = $this->createUser();
 | 
			
		||||
    $this->assertEquals(1, $root_user->id());
 | 
			
		||||
 | 
			
		||||
    $authenticated_user = $this->createUser(['administer permissions']);
 | 
			
		||||
    $role = $authenticated_user->getRoles()[1];
 | 
			
		||||
 | 
			
		||||
    $test_element = [
 | 
			
		||||
      '#cache' => [
 | 
			
		||||
        'keys' => ['test'],
 | 
			
		||||
        'contexts' => ['user', 'user.roles'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    \Drupal::service('account_switcher')->switchTo($authenticated_user);
 | 
			
		||||
    $element = $test_element;
 | 
			
		||||
    $element['#markup'] = 'content for authenticated users';
 | 
			
		||||
    $output = \Drupal::service('renderer')->renderRoot($element);
 | 
			
		||||
    $this->assertEquals('content for authenticated users', $output);
 | 
			
		||||
 | 
			
		||||
    // Verify that the render caching is working so that other tests can be
 | 
			
		||||
    // trusted.
 | 
			
		||||
    $element = $test_element;
 | 
			
		||||
    $element['#markup'] = 'this should not be visible';
 | 
			
		||||
    $output = \Drupal::service('renderer')->renderRoot($element);
 | 
			
		||||
    $this->assertEquals('content for authenticated users', $output);
 | 
			
		||||
 | 
			
		||||
    // Even though the cache contexts have been optimized to only include 'user'
 | 
			
		||||
    // cache context, the element should have been changed because 'user.roles'
 | 
			
		||||
    // cache context defined a cache tag for user entity changes, which should
 | 
			
		||||
    // have bubbled up for the element when it was optimized away.
 | 
			
		||||
    $authenticated_user->removeRole($role)->save();
 | 
			
		||||
    $element = $test_element;
 | 
			
		||||
    $element['#markup'] = 'this should be visible';
 | 
			
		||||
    $output = \Drupal::service('renderer')->renderRoot($element);
 | 
			
		||||
    $this->assertEquals('this should be visible', $output);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\ChainedFastBackend;
 | 
			
		||||
use Drupal\Core\Cache\DatabaseBackend;
 | 
			
		||||
use Drupal\Core\Cache\PhpBackend;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit test of the fast chained backend using the generic cache unit test base.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class ChainedFastBackendTest extends GenericCacheBackendUnitTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new instance of ChainedFastBackend.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\ChainedFastBackend
 | 
			
		||||
   *   A new ChainedFastBackend object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createCacheBackend($bin) {
 | 
			
		||||
    $consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin, \Drupal::service('serialization.phpserialize'), \Drupal::service(TimeInterface::class), 100);
 | 
			
		||||
    $fast_backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'), \Drupal::service(TimeInterface::class));
 | 
			
		||||
    $backend = new ChainedFastBackend($consistent_backend, $fast_backend, $bin);
 | 
			
		||||
    // Explicitly register the cache bin as it can not work through the
 | 
			
		||||
    // cache bin list in the container.
 | 
			
		||||
    \Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
 | 
			
		||||
    return $backend;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,96 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\Cache;
 | 
			
		||||
use Drupal\Core\Cache\CacheTagsPurgeInterface;
 | 
			
		||||
use Drupal\Core\Database\Database;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Reference;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests DatabaseBackend cache tag implementation.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class DatabaseBackendTagTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function register(ContainerBuilder $container): void {
 | 
			
		||||
    parent::register($container);
 | 
			
		||||
    // Change container to database cache backends.
 | 
			
		||||
    $container
 | 
			
		||||
      ->register('cache_factory', 'Drupal\Core\Cache\CacheFactory')
 | 
			
		||||
      ->addArgument(new Reference('settings'))
 | 
			
		||||
      ->addMethodCall('setContainer', [new Reference('service_container')]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test tag invalidation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTagInvalidations(): void {
 | 
			
		||||
    // Create cache entry in multiple bins.
 | 
			
		||||
    $tags = ['test_tag:1', 'test_tag:2', 'test_tag:3'];
 | 
			
		||||
    $bins = ['data', 'bootstrap', 'render'];
 | 
			
		||||
    foreach ($bins as $bin) {
 | 
			
		||||
      $bin = \Drupal::cache($bin);
 | 
			
		||||
      $bin->set('test', 'value', Cache::PERMANENT, $tags);
 | 
			
		||||
      $this->assertNotEmpty($bin->get('test'), 'Cache item was set in bin.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $connection = Database::getConnection();
 | 
			
		||||
    $invalidations_before = intval($connection->select('cachetags')->fields('cachetags', ['invalidations'])->condition('tag', 'test_tag:2')->execute()->fetchField());
 | 
			
		||||
    Cache::invalidateTags(['test_tag:2']);
 | 
			
		||||
 | 
			
		||||
    // Test that cache entry has been invalidated in multiple bins.
 | 
			
		||||
    foreach ($bins as $bin) {
 | 
			
		||||
      $bin = \Drupal::cache($bin);
 | 
			
		||||
      $this->assertFalse($bin->get('test'), 'Tag invalidation affected item in bin.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test that only one tag invalidation has occurred.
 | 
			
		||||
    $invalidations_after = intval($connection->select('cachetags')->fields('cachetags', ['invalidations'])->condition('tag', 'test_tag:2')->execute()->fetchField());
 | 
			
		||||
    $this->assertEquals($invalidations_before + 1, $invalidations_after, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test cache tag purging.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTagsPurge(): void {
 | 
			
		||||
    $tags = ['test_tag:1', 'test_tag:2', 'test_tag:3'];
 | 
			
		||||
    /** @var \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_invalidator */
 | 
			
		||||
    $checksum_invalidator = \Drupal::service('cache_tags.invalidator.checksum');
 | 
			
		||||
    // Assert that initial current tag checksum is 0. This also ensures that the
 | 
			
		||||
    // 'cachetags' table is created, which at this point does not exist yet.
 | 
			
		||||
    $this->assertEquals(0, $checksum_invalidator->getCurrentChecksum($tags));
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface $invalidator */
 | 
			
		||||
    $invalidator = \Drupal::service('cache_tags.invalidator');
 | 
			
		||||
    $invalidator->invalidateTags($tags);
 | 
			
		||||
    // Checksum should be incremented by 1 by the invalidation for each tag.
 | 
			
		||||
    $this->assertEquals(3, $checksum_invalidator->getCurrentChecksum($tags));
 | 
			
		||||
 | 
			
		||||
    // After purging, confirm checksum is 0 and the 'cachetags' table is empty.
 | 
			
		||||
    $this->assertInstanceOf(CacheTagsPurgeInterface::class, $invalidator);
 | 
			
		||||
    $invalidator->purge();
 | 
			
		||||
    $this->assertEquals(0, $checksum_invalidator->getCurrentChecksum($tags));
 | 
			
		||||
 | 
			
		||||
    $rows = Database::getConnection()->select('cachetags')
 | 
			
		||||
      ->fields('cachetags')
 | 
			
		||||
      ->countQuery()
 | 
			
		||||
      ->execute()
 | 
			
		||||
      ->fetchField();
 | 
			
		||||
    $this->assertEmpty($rows, 'cachetags table is empty.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,139 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\DatabaseBackend;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit test of the database backend using the generic cache unit test base.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class DatabaseBackendTest extends GenericCacheBackendUnitTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The max rows to use for test bins.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected static $maxRows = 100;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new instance of DatabaseBackend.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\DatabaseBackend
 | 
			
		||||
   *   A new DatabaseBackend object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createCacheBackend($bin) {
 | 
			
		||||
    return new DatabaseBackend(
 | 
			
		||||
      $this->container->get('database'),
 | 
			
		||||
      $this->container->get('cache_tags.invalidator.checksum'),
 | 
			
		||||
      $bin,
 | 
			
		||||
      $this->container->get('serialization.phpserialize'),
 | 
			
		||||
      \Drupal::service(TimeInterface::class),
 | 
			
		||||
      static::$maxRows,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function testSetGet(): void {
 | 
			
		||||
    parent::testSetGet();
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
 | 
			
		||||
    // Set up a cache ID that is not ASCII and longer than 255 characters so we
 | 
			
		||||
    // can test cache ID normalization.
 | 
			
		||||
    $cid_long = str_repeat('愛€', 500);
 | 
			
		||||
    $cached_value_long = $this->randomMachineName();
 | 
			
		||||
    $backend->set($cid_long, $cached_value_long);
 | 
			
		||||
    $this->assertSame($cached_value_long, $backend->get($cid_long)->data, "Backend contains the correct value for long, non-ASCII cache id.");
 | 
			
		||||
 | 
			
		||||
    $cid_short = '愛1€';
 | 
			
		||||
    $cached_value_short = $this->randomMachineName();
 | 
			
		||||
    $backend->set($cid_short, $cached_value_short);
 | 
			
		||||
    $this->assertSame($cached_value_short, $backend->get($cid_short)->data, "Backend contains the correct value for short, non-ASCII cache id.");
 | 
			
		||||
 | 
			
		||||
    // Set multiple items to test exceeding the chunk size.
 | 
			
		||||
    $backend->deleteAll();
 | 
			
		||||
    $items = [];
 | 
			
		||||
    for ($i = 0; $i <= DatabaseBackend::MAX_ITEMS_PER_CACHE_SET; $i++) {
 | 
			
		||||
      $items["test$i"]['data'] = $i;
 | 
			
		||||
    }
 | 
			
		||||
    $backend->setMultiple($items);
 | 
			
		||||
    $this->assertSame(DatabaseBackend::MAX_ITEMS_PER_CACHE_SET + 1, $this->getNumRows());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the row count limiting of cache bin database tables.
 | 
			
		||||
   */
 | 
			
		||||
  public function testGarbageCollection(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
    $max_rows = static::$maxRows;
 | 
			
		||||
 | 
			
		||||
    $this->assertSame(0, (int) $this->getNumRows());
 | 
			
		||||
 | 
			
		||||
    // Fill to just the limit.
 | 
			
		||||
    for ($i = 0; $i < $max_rows; $i++) {
 | 
			
		||||
      // Ensure that each cache item created happens in a different millisecond,
 | 
			
		||||
      // by waiting 1 ms (1000 microseconds). The garbage collection might
 | 
			
		||||
      // otherwise keep less than exactly 100 records (which is acceptable for
 | 
			
		||||
      // real-world cases, but not for this test).
 | 
			
		||||
      usleep(1000);
 | 
			
		||||
      $backend->set("test$i", $i);
 | 
			
		||||
    }
 | 
			
		||||
    $this->assertSame($max_rows, $this->getNumRows());
 | 
			
		||||
 | 
			
		||||
    // Garbage collection has no effect.
 | 
			
		||||
    $backend->garbageCollection();
 | 
			
		||||
    $this->assertSame($max_rows, $this->getNumRows());
 | 
			
		||||
 | 
			
		||||
    // Go one row beyond the limit.
 | 
			
		||||
    $backend->set('test' . ($max_rows + 1), $max_rows + 1);
 | 
			
		||||
    $this->assertSame($max_rows + 1, $this->getNumRows());
 | 
			
		||||
 | 
			
		||||
    // Garbage collection removes one row: the oldest.
 | 
			
		||||
    $backend->garbageCollection();
 | 
			
		||||
    $this->assertSame($max_rows, $this->getNumRows());
 | 
			
		||||
    $this->assertFalse($backend->get('test0'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the number of rows in the test cache bin database table.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The number of rows in the test cache bin database table.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getNumRows(): int {
 | 
			
		||||
    $table = 'cache_' . $this->testBin;
 | 
			
		||||
    $connection = $this->container->get('database');
 | 
			
		||||
    $query = $connection->select($table);
 | 
			
		||||
    $query->addExpression('COUNT([cid])', 'cid');
 | 
			
		||||
    return (int) $query->execute()->fetchField();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that "cache_tags.invalidator.checksum" is backend overridable.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCacheTagsInvalidatorChecksumIsBackendOverridable(): void {
 | 
			
		||||
    $definition = $this->container->getDefinition('cache_tags.invalidator.checksum');
 | 
			
		||||
    $this->assertTrue($definition->hasTag('backend_overridable'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test that the service "cache.backend.database" is backend overridable.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCacheBackendDatabaseIsBackendOverridable(): void {
 | 
			
		||||
    $definition = $this->container->getDefinition('cache.backend.database');
 | 
			
		||||
    $this->assertTrue($definition->hasTag('backend_overridable'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,195 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\Cache;
 | 
			
		||||
use Drupal\Core\Cache\DatabaseBackendFactory;
 | 
			
		||||
use Drupal\Core\Database\Database;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Drupal\entity_test\Entity\EntityTest;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\user\Entity\User;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Reference;
 | 
			
		||||
use Drupal\Component\Serialization\PhpSerialize;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests delaying of cache tag invalidation queries to the end of transactions.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class EndOfTransactionQueriesTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'delay_cache_tags_invalidation',
 | 
			
		||||
    'entity_test',
 | 
			
		||||
    'system',
 | 
			
		||||
    'user',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->installEntitySchema('entity_test');
 | 
			
		||||
    $this->installEntitySchema('user');
 | 
			
		||||
 | 
			
		||||
    // Ensure the cachetags table already exists.
 | 
			
		||||
    Cache::invalidateTags([$this->randomString()]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function register(ContainerBuilder $container): void {
 | 
			
		||||
    parent::register($container);
 | 
			
		||||
 | 
			
		||||
    $container->register('serializer', PhpSerialize::class);
 | 
			
		||||
    // Register a database cache backend rather than memory-based.
 | 
			
		||||
    $container->register('cache_factory', DatabaseBackendFactory::class)
 | 
			
		||||
      ->addArgument(new Reference('database'))
 | 
			
		||||
      ->addArgument(new Reference('cache_tags.invalidator.checksum'))
 | 
			
		||||
      ->addArgument(new Reference('settings'))
 | 
			
		||||
      ->addArgument(new Reference('serializer'))
 | 
			
		||||
      ->addArgument(new Reference(TimeInterface::class));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests an entity save.
 | 
			
		||||
   */
 | 
			
		||||
  public function testEntitySave(): void {
 | 
			
		||||
    \Drupal::cache()->set('test_cache_pre-transaction_foobar', 'something', Cache::PERMANENT, ['foobar']);
 | 
			
		||||
    \Drupal::cache()->set('test_cache_pre-transaction_entity_test_list', 'something', Cache::PERMANENT, ['entity_test_list']);
 | 
			
		||||
 | 
			
		||||
    $entity = EntityTest::create(['name' => $this->randomString()]);
 | 
			
		||||
 | 
			
		||||
    Database::startLog('testEntitySave');
 | 
			
		||||
    $entity->save();
 | 
			
		||||
 | 
			
		||||
    // Entity save should have deferred cache invalidation to after transaction
 | 
			
		||||
    // completion for the "entity_test_list", "entity_test_list:entity_test"
 | 
			
		||||
    // and "4xx-response" tags. Since cache invalidation is a MERGE database
 | 
			
		||||
    // operation, and in core drivers each MERGE is split in two SELECT and
 | 
			
		||||
    // INSERT|UPDATE operations, we expect the last 6 logged database queries
 | 
			
		||||
    // to be related to the {cachetags} table.
 | 
			
		||||
    $expected_tail_length = 6;
 | 
			
		||||
    $executed_statements = [];
 | 
			
		||||
    foreach (Database::getLog('testEntitySave') as $log) {
 | 
			
		||||
      // Exclude transaction related statements from the log.
 | 
			
		||||
      if (
 | 
			
		||||
        str_starts_with($log['query'], 'ROLLBACK TO SAVEPOINT ') ||
 | 
			
		||||
        str_starts_with($log['query'], 'RELEASE SAVEPOINT ') ||
 | 
			
		||||
        str_starts_with($log['query'], 'SAVEPOINT ')
 | 
			
		||||
      ) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $executed_statements[] = $log['query'];
 | 
			
		||||
    }
 | 
			
		||||
    $expected_post_transaction_statements = array_keys(array_fill(array_key_last($executed_statements) - $expected_tail_length + 1, $expected_tail_length, TRUE));
 | 
			
		||||
    $cachetag_statements = $this->getStatementsForTable($executed_statements, 'cachetags');
 | 
			
		||||
    $tail_cachetag_statements = array_keys(array_slice($cachetag_statements, count($cachetag_statements) - $expected_tail_length, $expected_tail_length, TRUE));
 | 
			
		||||
    $this->assertSame($expected_post_transaction_statements, $tail_cachetag_statements);
 | 
			
		||||
 | 
			
		||||
    // Verify that a nested entity save occurred.
 | 
			
		||||
    $this->assertSame('john doe', User::load(1)->getAccountName());
 | 
			
		||||
 | 
			
		||||
    // Cache reads occurring during a transaction that DO NOT depend on
 | 
			
		||||
    // invalidated cache tags result in cache HITs. Similarly, cache writes that
 | 
			
		||||
    // DO NOT depend on invalidated cache tags DO get written. Of course, if we
 | 
			
		||||
    // read either one now, outside of the context of the transaction, we expect
 | 
			
		||||
    // the same.
 | 
			
		||||
    $this->assertNotEmpty(\Drupal::state()->get('delay_cache_tags_invalidation_entity_test_insert__pre-transaction_foobar'));
 | 
			
		||||
    $this->assertNotEmpty(\Drupal::cache()->get('delay_cache_tags_invalidation_entity_test_insert__during_transaction_foobar'));
 | 
			
		||||
    $this->assertNotEmpty(\Drupal::state()->get('delay_cache_tags_invalidation_user_insert__during_transaction_foobar'));
 | 
			
		||||
    $this->assertNotEmpty(\Drupal::cache()->get('test_cache_pre-transaction_foobar'));
 | 
			
		||||
 | 
			
		||||
    // Cache reads occurring during a transaction that DO depend on invalidated
 | 
			
		||||
    // cache tags result in cache MISSes. Similarly, cache writes that DO depend
 | 
			
		||||
    // on invalidated cache tags DO NOT get written. Of course, if we read
 | 
			
		||||
    // either one now, outside of the context of the transaction, we expect the
 | 
			
		||||
    // same.
 | 
			
		||||
    $this->assertFalse(\Drupal::state()->get('delay_cache_tags_invalidation_entity_test_insert__pre-transaction_entity_test_list'));
 | 
			
		||||
    $this->assertFalse(\Drupal::cache()->get('delay_cache_tags_invalidation_entity_test_insert__during_transaction_entity_test_list'));
 | 
			
		||||
    $this->assertFalse(\Drupal::state()->get('delay_cache_tags_invalidation_user_insert__during_transaction_entity_test_list'));
 | 
			
		||||
    $this->assertFalse(\Drupal::cache()->get('test_cache_pre-transaction_entity_test_list'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests an entity save rollback.
 | 
			
		||||
   */
 | 
			
		||||
  public function testEntitySaveRollback(): void {
 | 
			
		||||
    \Drupal::cache()
 | 
			
		||||
      ->set('test_cache_pre-transaction_entity_test_list', 'something', Cache::PERMANENT, ['entity_test_list']);
 | 
			
		||||
    \Drupal::cache()
 | 
			
		||||
      ->set('test_cache_pre-transaction_user_list', 'something', Cache::PERMANENT, ['user_list']);
 | 
			
		||||
 | 
			
		||||
    \Drupal::state()->set('delay_cache_tags_invalidation_exception', TRUE);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      EntityTest::create(['name' => $this->randomString()])->save();
 | 
			
		||||
      $this->fail('Exception not thrown');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $e) {
 | 
			
		||||
      $this->assertEquals('Abort entity save to trigger transaction rollback.', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The cache has not been invalidated.
 | 
			
		||||
    $this->assertNotEmpty(\Drupal::cache()->get('test_cache_pre-transaction_entity_test_list'));
 | 
			
		||||
    $this->assertNotEmpty(\Drupal::cache()->get('test_cache_pre-transaction_user_list'));
 | 
			
		||||
 | 
			
		||||
    // Save a user, that should invalidate the cache tagged with user_list but
 | 
			
		||||
    // not the one with entity_test_list.
 | 
			
		||||
    User::create([
 | 
			
		||||
      'name' => 'john doe',
 | 
			
		||||
      'status' => 1,
 | 
			
		||||
    ])->save();
 | 
			
		||||
 | 
			
		||||
    $this->assertNotEmpty(\Drupal::cache()->get('test_cache_pre-transaction_entity_test_list'));
 | 
			
		||||
    $this->assertFalse(\Drupal::cache()->get('test_cache_pre-transaction_user_list'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Filters statements by table name.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string[] $statements
 | 
			
		||||
   *   A list of query statements.
 | 
			
		||||
   * @param string $table_name
 | 
			
		||||
   *   The name of the table to filter by.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   Filtered statement list.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getStatementsForTable(array $statements, $table_name): array {
 | 
			
		||||
    return array_filter($statements, function ($statement) use ($table_name) {
 | 
			
		||||
      return $this->isStatementRelatedToTable($statement, $table_name);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if a statement is relative to a specified table.
 | 
			
		||||
   *
 | 
			
		||||
   * Non-core database drivers can override this method if they have different
 | 
			
		||||
   * patterns to identify table related statements.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $statement
 | 
			
		||||
   *   The query statement.
 | 
			
		||||
   * @param string $tableName
 | 
			
		||||
   *   The table name, Drupal style, without curly brackets or prefix.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the statement is relative to the table, FALSE otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function isStatementRelatedToTable(string $statement, string $tableName): bool {
 | 
			
		||||
    $realTableIdentifier = Database::getConnection()->prefixTables('{' . $tableName . '}');
 | 
			
		||||
    $pattern = '/.*(INTO|FROM|UPDATE)( |\n)' . preg_quote($realTableIdentifier, '/') . '/';
 | 
			
		||||
    return preg_match($pattern, $statement) === 1 ? TRUE : FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,690 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\Cache;
 | 
			
		||||
use Drupal\Core\Cache\CacheBackendInterface;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests any cache backend.
 | 
			
		||||
 *
 | 
			
		||||
 * Full generic unit test suite for any cache backend. In order to use it for a
 | 
			
		||||
 * cache backend implementation, extend this class and override the
 | 
			
		||||
 * createBackendInstance() method to return an object.
 | 
			
		||||
 *
 | 
			
		||||
 * @see DatabaseBackendUnitTestCase
 | 
			
		||||
 *   For a full working implementation.
 | 
			
		||||
 */
 | 
			
		||||
abstract class GenericCacheBackendUnitTestBase extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Array of objects implementing Drupal\Core\Cache\CacheBackendInterface.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $cacheBackends;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Cache bin to use for testing.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $testBin;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Random value to use in tests.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $defaultValue;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Most cache backends ensure changes to objects do not affect the cache.
 | 
			
		||||
   *
 | 
			
		||||
   * Some caches explicitly allow this, for example,
 | 
			
		||||
   * \Drupal\Core\Cache\MemoryCache\MemoryCache.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   */
 | 
			
		||||
  protected bool $testObjectProperties = TRUE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the testing bin.
 | 
			
		||||
   *
 | 
			
		||||
   * Override this method if you want to work on a different bin than the
 | 
			
		||||
   * default one.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   Bin name.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getTestBin() {
 | 
			
		||||
    if (!isset($this->testBin)) {
 | 
			
		||||
      $this->testBin = 'page';
 | 
			
		||||
    }
 | 
			
		||||
    return $this->testBin;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a cache backend to test.
 | 
			
		||||
   *
 | 
			
		||||
   * Override this method to test a CacheBackend.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $bin
 | 
			
		||||
   *   Bin name to use for this backend instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   *   Cache backend to test.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function createCacheBackend($bin);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Allows specific implementation to change the environment before a test run.
 | 
			
		||||
   */
 | 
			
		||||
  public function setUpCacheBackend() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Allows alteration of environment after a test run but before tear down.
 | 
			
		||||
   *
 | 
			
		||||
   * Used before the real tear down because the tear down will change things
 | 
			
		||||
   * such as the database prefix.
 | 
			
		||||
   */
 | 
			
		||||
  public function tearDownCacheBackend() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a backend to test; this will get a shared instance set in the object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   *   Cache backend to test.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getCacheBackend($bin = NULL) {
 | 
			
		||||
    if (!isset($bin)) {
 | 
			
		||||
      $bin = $this->getTestBin();
 | 
			
		||||
    }
 | 
			
		||||
    if (!isset($this->cacheBackends[$bin])) {
 | 
			
		||||
      $this->cacheBackends[$bin] = $this->createCacheBackend($bin);
 | 
			
		||||
      // Ensure the backend is empty.
 | 
			
		||||
      $this->cacheBackends[$bin]->deleteAll();
 | 
			
		||||
    }
 | 
			
		||||
    return $this->cacheBackends[$bin];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    $this->cacheBackends = [];
 | 
			
		||||
    $this->defaultValue = $this->randomMachineName(10);
 | 
			
		||||
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->setUpCacheBackend();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function tearDown(): void {
 | 
			
		||||
    // 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 => $cache_backend) {
 | 
			
		||||
      $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(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
 | 
			
		||||
    $with_backslash = ['foo' => '\Drupal\foo\Bar'];
 | 
			
		||||
    $backend->set('test1', $with_backslash);
 | 
			
		||||
    $cached = $backend->get('test1');
 | 
			
		||||
    $this->assertIsObject($cached);
 | 
			
		||||
    $this->assertSame($with_backslash, $cached->data);
 | 
			
		||||
    $this->assertTrue($cached->valid, 'Item is marked as valid.');
 | 
			
		||||
    // We need to round because microtime may be rounded up in the backend.
 | 
			
		||||
    $this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $cached->created);
 | 
			
		||||
    $this->assertLessThanOrEqual(round(microtime(TRUE), 3), $cached->created);
 | 
			
		||||
    $this->assertEquals(Cache::PERMANENT, $cached->expire, 'Expire time is correct.');
 | 
			
		||||
 | 
			
		||||
    $backend->set('test2', ['value' => 3], \Drupal::time()->getRequestTime() + 3);
 | 
			
		||||
    $cached = $backend->get('test2');
 | 
			
		||||
    $this->assertIsObject($cached);
 | 
			
		||||
    $this->assertSame(['value' => 3], $cached->data);
 | 
			
		||||
    $this->assertTrue($cached->valid, 'Item is marked as valid.');
 | 
			
		||||
    $this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $cached->created);
 | 
			
		||||
    $this->assertLessThanOrEqual(round(microtime(TRUE), 3), $cached->created);
 | 
			
		||||
    $this->assertEquals(\Drupal::time()->getRequestTime() + 3, $cached->expire, 'Expire time is correct.');
 | 
			
		||||
 | 
			
		||||
    $backend->set('test3', 'foobar', \Drupal::time()->getRequestTime() - 3);
 | 
			
		||||
    $this->assertFalse($backend->get('test3'), 'Invalid item not returned.');
 | 
			
		||||
    $cached = $backend->get('test3', TRUE);
 | 
			
		||||
    $this->assertIsObject($cached);
 | 
			
		||||
    $this->assertFalse($cached->valid, 'Item is marked as valid.');
 | 
			
		||||
    $this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $cached->created);
 | 
			
		||||
    $this->assertLessThanOrEqual(round(microtime(TRUE), 3), $cached->created);
 | 
			
		||||
    $this->assertEquals(\Drupal::time()->getRequestTime() - 3, $cached->expire, 'Expire time is correct.');
 | 
			
		||||
 | 
			
		||||
    $with_eof = ['foo' => "\nEOF\ndata"];
 | 
			
		||||
    $backend->set('test4', $with_eof);
 | 
			
		||||
    $cached = $backend->get('test4');
 | 
			
		||||
    $this->assertIsObject($cached);
 | 
			
		||||
    $this->assertSame($with_eof, $cached->data);
 | 
			
		||||
    $this->assertTrue($cached->valid, 'Item is marked as valid.');
 | 
			
		||||
    $this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $cached->created);
 | 
			
		||||
    $this->assertLessThanOrEqual(round(microtime(TRUE), 3), $cached->created);
 | 
			
		||||
    $this->assertEquals(Cache::PERMANENT, $cached->expire, 'Expire time is correct.');
 | 
			
		||||
 | 
			
		||||
    $with_eof_and_semicolon = ['foo' => "\nEOF;\ndata"];
 | 
			
		||||
    $backend->set('test5', $with_eof_and_semicolon);
 | 
			
		||||
    $cached = $backend->get('test5');
 | 
			
		||||
    $this->assertIsObject($cached);
 | 
			
		||||
    $this->assertSame($with_eof_and_semicolon, $cached->data);
 | 
			
		||||
    $this->assertTrue($cached->valid, 'Item is marked as valid.');
 | 
			
		||||
    $this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $cached->created);
 | 
			
		||||
    $this->assertLessThanOrEqual(round(microtime(TRUE), 3), $cached->created);
 | 
			
		||||
    $this->assertEquals(Cache::PERMANENT, $cached->expire, 'Expire time is correct.');
 | 
			
		||||
 | 
			
		||||
    $with_variable = ['foo' => '$bar'];
 | 
			
		||||
    $backend->set('test6', $with_variable);
 | 
			
		||||
    $cached = $backend->get('test6');
 | 
			
		||||
    $this->assertIsObject($cached);
 | 
			
		||||
    $this->assertSame($with_variable, $cached->data);
 | 
			
		||||
 | 
			
		||||
    // Make sure that a cached object is not affected by changing the original.
 | 
			
		||||
    $data = new \stdClass();
 | 
			
		||||
    $data->value = 1;
 | 
			
		||||
    $data->obj = new \stdClass();
 | 
			
		||||
    $data->obj->value = 2;
 | 
			
		||||
    $backend->set('test7', $data);
 | 
			
		||||
    $expected_data = clone $data;
 | 
			
		||||
    // Add a property to the original. It should not appear in the cached data.
 | 
			
		||||
    $data->this_should_not_be_in_the_cache = TRUE;
 | 
			
		||||
    $cached = $backend->get('test7');
 | 
			
		||||
    $this->assertIsObject($cached);
 | 
			
		||||
    if ($this->testObjectProperties) {
 | 
			
		||||
      $this->assertEquals($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));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->assertSame($data, $cached->data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check with a long key.
 | 
			
		||||
    $cid = str_repeat('a', 300);
 | 
			
		||||
    $backend->set($cid, 'test');
 | 
			
		||||
    $this->assertEquals('test', $backend->get($cid)->data);
 | 
			
		||||
 | 
			
		||||
    // Check that the cache key is case sensitive.
 | 
			
		||||
    $backend->set('TEST8', 'value');
 | 
			
		||||
    $this->assertEquals('value', $backend->get('TEST8')->data);
 | 
			
		||||
    $this->assertFalse($backend->get('test8'));
 | 
			
		||||
 | 
			
		||||
    // Test a cid with and without a trailing space is treated as two different
 | 
			
		||||
    // IDs.
 | 
			
		||||
    $cid_nospace = 'trailing-space-test';
 | 
			
		||||
    $backend->set($cid_nospace, $cid_nospace);
 | 
			
		||||
    $this->assertSame($cid_nospace, $backend->get($cid_nospace)->data);
 | 
			
		||||
    $this->assertFalse($backend->get($cid_nospace . ' '));
 | 
			
		||||
 | 
			
		||||
    // Calling ::set() with invalid cache tags. This should fail an assertion.
 | 
			
		||||
    try {
 | 
			
		||||
      $backend->set('assertion_test', 'value', Cache::PERMANENT, ['node' => [3, 5, 7]]);
 | 
			
		||||
      $this->fail('::set() was called with invalid cache tags, but runtime assertion did not fail.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\AssertionError) {
 | 
			
		||||
      // Do nothing; continue testing in extending classes.
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests Drupal\Core\Cache\CacheBackendInterface::delete().
 | 
			
		||||
   */
 | 
			
		||||
  public function testDelete(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
 | 
			
		||||
    $backend->set('test1', 7);
 | 
			
		||||
    $this->assertIsObject($backend->get('test1'));
 | 
			
		||||
 | 
			
		||||
    $backend->set('test2', 3);
 | 
			
		||||
    $this->assertIsObject($backend->get('test2'));
 | 
			
		||||
 | 
			
		||||
    $backend->delete('test1');
 | 
			
		||||
    $this->assertFalse($backend->get('test1'), "Backend does not contain data for cache id test1 after deletion.");
 | 
			
		||||
 | 
			
		||||
    $this->assertIsObject($backend->get('test2'));
 | 
			
		||||
 | 
			
		||||
    $backend->delete('test2');
 | 
			
		||||
    $this->assertFalse($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->assertFalse($backend->get($long_cid), "Backend does not contain data for long cache id after deletion.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests data type preservation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testValueTypeIsKept(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
 | 
			
		||||
    $variables = [
 | 
			
		||||
      'test1' => 1,
 | 
			
		||||
      'test2' => '0',
 | 
			
		||||
      'test3' => '',
 | 
			
		||||
      'test4' => 12.64,
 | 
			
		||||
      'test5' => FALSE,
 | 
			
		||||
      'test6' => [1, 2, 3],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Create cache entries.
 | 
			
		||||
    foreach ($variables as $cid => $data) {
 | 
			
		||||
      $backend->set($cid, $data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Retrieve and test cache objects.
 | 
			
		||||
    foreach ($variables as $cid => $value) {
 | 
			
		||||
      $object = $backend->get($cid);
 | 
			
		||||
      $this->assertIsObject($object, sprintf("Backend returned an object for cache id %s.", $cid));
 | 
			
		||||
      $this->assertSame($value, $object->data, sprintf("Data of cached id %s kept is identical in type and value", $cid));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests Drupal\Core\Cache\CacheBackendInterface::getMultiple().
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetMultiple(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
 | 
			
		||||
    // Set numerous testing keys.
 | 
			
		||||
    $long_cid = str_repeat('a', 300);
 | 
			
		||||
    $backend->set('test1', 1);
 | 
			
		||||
    $backend->set('test2', 3);
 | 
			
		||||
    $backend->set('test3', 5);
 | 
			
		||||
    $backend->set('test4', 7);
 | 
			
		||||
    $backend->set('test5', 11);
 | 
			
		||||
    $backend->set('test6', 13);
 | 
			
		||||
    $backend->set('test7', 17);
 | 
			
		||||
    $backend->set($long_cid, 300);
 | 
			
		||||
 | 
			
		||||
    // Mismatch order for harder testing.
 | 
			
		||||
    $reference = [
 | 
			
		||||
      'test3',
 | 
			
		||||
      'test7',
 | 
			
		||||
      // Cid does not exist.
 | 
			
		||||
      'test21',
 | 
			
		||||
      'test6',
 | 
			
		||||
      // Cid does not exist until added before second getMultiple().
 | 
			
		||||
      'test19',
 | 
			
		||||
      'test2',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $cids = $reference;
 | 
			
		||||
    $ret = $backend->getMultiple($cids);
 | 
			
		||||
    // Test return - ensure it contains existing cache ids.
 | 
			
		||||
    $this->assertArrayHasKey('test2', $ret, "Existing cache id test2 is set.");
 | 
			
		||||
    $this->assertArrayHasKey('test3', $ret, "Existing cache id test3 is set.");
 | 
			
		||||
    $this->assertArrayHasKey('test6', $ret, "Existing cache id test6 is set.");
 | 
			
		||||
    $this->assertArrayHasKey('test7', $ret, "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->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $ret['test2']->created);
 | 
			
		||||
    $this->assertLessThanOrEqual(round(microtime(TRUE), 3), $ret['test2']->created);
 | 
			
		||||
    $this->assertEquals(Cache::PERMANENT, $ret['test2']->expire, '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->assertSame(3, $ret['test2']->data, "Existing cache id test2 has the correct value.");
 | 
			
		||||
    $this->assertSame(5, $ret['test3']->data, "Existing cache id test3 has the correct value.");
 | 
			
		||||
    $this->assertSame(13, $ret['test6']->data, "Existing cache id test6 has the correct value.");
 | 
			
		||||
    $this->assertSame(17, $ret['test7']->data, "Existing cache id test7 has the correct value.");
 | 
			
		||||
    // Test $cids array - ensure it contains cache id's that do not exist.
 | 
			
		||||
    $this->assertContains('test19', $cids, "Nonexistent cache id test19 is in cids array.");
 | 
			
		||||
    $this->assertContains('test21', $cids, "Nonexistent cache id test21 is in cids array.");
 | 
			
		||||
    // Test $cids array - ensure it does not contain cache id's that exist.
 | 
			
		||||
    $this->assertNotContains('test2', $cids, "Existing cache id test2 is not in cids array.");
 | 
			
		||||
    $this->assertNotContains('test3', $cids, "Existing cache id test3 is not in cids array.");
 | 
			
		||||
    $this->assertNotContains('test6', $cids, "Existing cache id test6 is not in cids array.");
 | 
			
		||||
    $this->assertNotContains('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->assertArrayHasKey('test2', $ret, "Existing cache id test2 is set");
 | 
			
		||||
    $this->assertArrayHasKey('test7', $ret, "Existing cache id test7 is set");
 | 
			
		||||
    $this->assertArrayHasKey('test19', $ret, "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->assertSame(3, $ret['test2']->data, "Existing cache id test2 has the correct value.");
 | 
			
		||||
    $this->assertSame(17, $ret['test7']->data, "Existing cache id test7 has the correct value.");
 | 
			
		||||
    $this->assertSame(57, $ret['test19']->data, "Added cache id test19 has the correct value.");
 | 
			
		||||
    // Test $cids array - ensure it contains cache id's that do not exist.
 | 
			
		||||
    $this->assertContains('test3', $cids, "Deleted cache id test3 is in cids array.");
 | 
			
		||||
    $this->assertContains('test6', $cids, "Deleted cache id test6 is in cids array.");
 | 
			
		||||
    $this->assertContains('test21', $cids, "Nonexistent cache id test21 is in cids array.");
 | 
			
		||||
    // Test $cids array - ensure it does not contain cache id's that exist.
 | 
			
		||||
    $this->assertNotContains('test2', $cids, "Existing cache id test2 is not in cids array.");
 | 
			
		||||
    $this->assertNotContains('test7', $cids, "Existing cache id test7 is not in cids array.");
 | 
			
		||||
    $this->assertNotContains('test19', $cids, "Added cache id test19 is not in cids array.");
 | 
			
		||||
 | 
			
		||||
    // Test with a long $cid and non-numeric array key.
 | 
			
		||||
    $cids = ['key:key' => $long_cid];
 | 
			
		||||
    $return = $backend->getMultiple($cids);
 | 
			
		||||
    $this->assertEquals(300, $return[$long_cid]->data);
 | 
			
		||||
    $this->assertEmpty($cids);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests \Drupal\Core\Cache\CacheBackendInterface::setMultiple().
 | 
			
		||||
   */
 | 
			
		||||
  public function testSetMultiple(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
 | 
			
		||||
    $future_expiration = \Drupal::time()->getRequestTime() + 100;
 | 
			
		||||
 | 
			
		||||
    // Set multiple testing keys.
 | 
			
		||||
    $backend->set('cid_1', 'Some other value');
 | 
			
		||||
    $items = [
 | 
			
		||||
      'cid_1' => ['data' => 1],
 | 
			
		||||
      'cid_2' => ['data' => 2],
 | 
			
		||||
      'cid_3' => ['data' => [1, 2]],
 | 
			
		||||
      'cid_4' => ['data' => 1, 'expire' => $future_expiration],
 | 
			
		||||
      'cid_5' => ['data' => 1, 'tags' => ['test:a', 'test:b']],
 | 
			
		||||
    ];
 | 
			
		||||
    $backend->setMultiple($items);
 | 
			
		||||
    $cids = array_keys($items);
 | 
			
		||||
    $cached = $backend->getMultiple($cids);
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($items['cid_1']['data'], $cached['cid_1']->data, 'Over-written cache item set correctly.');
 | 
			
		||||
    $this->assertTrue($cached['cid_1']->valid, 'Item is marked as valid.');
 | 
			
		||||
    $this->assertGreaterThanOrEqual(\Drupal::time()->getRequestTime(), $cached['cid_1']->created);
 | 
			
		||||
    $this->assertLessThanOrEqual(round(microtime(TRUE), 3), $cached['cid_1']->created);
 | 
			
		||||
    $this->assertEquals(CacheBackendInterface::CACHE_PERMANENT, $cached['cid_1']->expire, 'Cache expiration defaults to permanent.');
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($items['cid_2']['data'], $cached['cid_2']->data, 'New cache item set correctly.');
 | 
			
		||||
    $this->assertEquals(CacheBackendInterface::CACHE_PERMANENT, $cached['cid_2']->expire, 'Cache expiration defaults to permanent.');
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($items['cid_3']['data'], $cached['cid_3']->data, 'New cache item with serialized data set correctly.');
 | 
			
		||||
    $this->assertEquals(CacheBackendInterface::CACHE_PERMANENT, $cached['cid_3']->expire, 'Cache expiration defaults to permanent.');
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($items['cid_4']['data'], $cached['cid_4']->data, 'New cache item set correctly.');
 | 
			
		||||
    $this->assertEquals($future_expiration, $cached['cid_4']->expire, 'Cache expiration has been correctly set.');
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($items['cid_5']['data'], $cached['cid_5']->data, 'New cache item set correctly.');
 | 
			
		||||
 | 
			
		||||
    // Calling ::setMultiple() with invalid cache tags. This should fail an
 | 
			
		||||
    // assertion.
 | 
			
		||||
    try {
 | 
			
		||||
      $items = [
 | 
			
		||||
        'exception_test_1' => ['data' => 1, 'tags' => []],
 | 
			
		||||
        'exception_test_2' => ['data' => 2, 'tags' => ['valid']],
 | 
			
		||||
        'exception_test_3' => ['data' => 3, 'tags' => ['node' => [3, 5, 7]]],
 | 
			
		||||
      ];
 | 
			
		||||
      $backend->setMultiple($items);
 | 
			
		||||
      $this->fail('::setMultiple() was called with invalid cache tags, but runtime assertion did not fail.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\AssertionError) {
 | 
			
		||||
      // Do nothing; continue testing in extending classes.
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Cache\ApcuBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\BackendChain::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\ChainedFastBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\DatabaseBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\MemoryBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\PhpBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\ApcuBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\BackendChain::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\ChainedFastBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\DatabaseBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\MemoryBackend::deleteMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\PhpBackend::deleteMultiple
 | 
			
		||||
   */
 | 
			
		||||
  public function testDeleteMultiple(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
 | 
			
		||||
    // Set numerous testing keys.
 | 
			
		||||
    $backend->set('test1', 1);
 | 
			
		||||
    $backend->set('test2', 3);
 | 
			
		||||
    $backend->set('test3', 5);
 | 
			
		||||
    $backend->set('test4', 7);
 | 
			
		||||
    $backend->set('test5', 11);
 | 
			
		||||
    $backend->set('test6', 13);
 | 
			
		||||
    $backend->set('test7', 17);
 | 
			
		||||
 | 
			
		||||
    $backend->delete('test1');
 | 
			
		||||
    // Nonexistent key should not cause an error.
 | 
			
		||||
    $backend->delete('test23');
 | 
			
		||||
    $backend->deleteMultiple([
 | 
			
		||||
      'test3',
 | 
			
		||||
      'test5',
 | 
			
		||||
      'test7',
 | 
			
		||||
      // Nonexistent key should not cause an error.
 | 
			
		||||
      'test19',
 | 
			
		||||
      // Nonexistent key should not cause an error.
 | 
			
		||||
      'test21',
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // Test if expected keys have been deleted.
 | 
			
		||||
    $this->assertFalse($backend->get('test1'), "Cache id test1 deleted.");
 | 
			
		||||
    $this->assertFalse($backend->get('test3'), "Cache id test3 deleted.");
 | 
			
		||||
    $this->assertFalse($backend->get('test5'), "Cache id test5 deleted.");
 | 
			
		||||
    $this->assertFalse($backend->get('test7'), "Cache id test7 deleted.");
 | 
			
		||||
 | 
			
		||||
    // Test if expected keys exist.
 | 
			
		||||
    $this->assertNotFalse($backend->get('test2'), "Cache id test2 exists.");
 | 
			
		||||
    $this->assertNotFalse($backend->get('test4'), "Cache id test4 exists.");
 | 
			
		||||
    $this->assertNotFalse($backend->get('test6'), "Cache id test6 exists.");
 | 
			
		||||
 | 
			
		||||
    // Test if that expected keys do not exist.
 | 
			
		||||
    $this->assertFalse($backend->get('test19'), "Cache id test19 does not exist.");
 | 
			
		||||
    $this->assertFalse($backend->get('test21'), "Cache id test21 does not exist.");
 | 
			
		||||
 | 
			
		||||
    // Calling deleteMultiple() with an empty array should not cause an error.
 | 
			
		||||
    $this->assertNull($backend->deleteMultiple([]));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests Drupal\Core\Cache\CacheBackendInterface::deleteAll().
 | 
			
		||||
   */
 | 
			
		||||
  public function testDeleteAll(): void {
 | 
			
		||||
    $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->assertNotEmpty($backend_b->get('test3'), 'Item in other bin is preserved.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers \Drupal\Core\Cache\ApcuBackend::getMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\BackendChain::getMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\ChainedFastBackend::getMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\DatabaseBackend::getMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\MemoryBackend::getMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\PhpBackend::getMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\ApcuBackend::invalidateMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\BackendChain::invalidateMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\ChainedFastBackend::invalidateMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\DatabaseBackend::invalidateMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\MemoryBackend::invalidateMultiple
 | 
			
		||||
   * @covers \Drupal\Core\Cache\PhpBackend::invalidateMultiple
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidate(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
    $backend->set('test1', 1);
 | 
			
		||||
    $backend->set('test2', 2);
 | 
			
		||||
    $backend->set('test3', 2);
 | 
			
		||||
    $backend->set('test4', 2);
 | 
			
		||||
 | 
			
		||||
    $reference = ['test1', 'test2', 'test3', 'test4'];
 | 
			
		||||
 | 
			
		||||
    $cids = $reference;
 | 
			
		||||
    $ret = $backend->getMultiple($cids);
 | 
			
		||||
    $this->assertCount(4, $ret, 'Four items returned.');
 | 
			
		||||
 | 
			
		||||
    $backend->invalidate('test1');
 | 
			
		||||
    $backend->invalidateMultiple(['test2', 'test3']);
 | 
			
		||||
 | 
			
		||||
    $cids = $reference;
 | 
			
		||||
    $ret = $backend->getMultiple($cids);
 | 
			
		||||
    $this->assertCount(1, $ret, 'Only one item element returned.');
 | 
			
		||||
 | 
			
		||||
    $cids = $reference;
 | 
			
		||||
    $ret = $backend->getMultiple($cids, TRUE);
 | 
			
		||||
    $this->assertCount(4, $ret, 'Four items returned.');
 | 
			
		||||
 | 
			
		||||
    // Calling invalidateMultiple() with an empty array should not cause an
 | 
			
		||||
    // error.
 | 
			
		||||
    $this->assertNull($backend->invalidateMultiple([]));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidateTags(): void {
 | 
			
		||||
    $backend = $this->getCacheBackend();
 | 
			
		||||
 | 
			
		||||
    // Create two cache entries with the same tag and tag value.
 | 
			
		||||
    $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, ['test_tag:2']);
 | 
			
		||||
    $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, ['test_tag:2']);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate1')->data);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate2')->data);
 | 
			
		||||
 | 
			
		||||
    // Invalidate test_tag of value 1. This should invalidate both entries.
 | 
			
		||||
    Cache::invalidateTags(['test_tag:2']);
 | 
			
		||||
    $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after invalidating a cache tag.');
 | 
			
		||||
    // Verify that cache items have not been deleted after invalidation.
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate1', TRUE)->data);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate2', TRUE)->data);
 | 
			
		||||
 | 
			
		||||
    // Create two cache entries with the same tag and an array tag value.
 | 
			
		||||
    $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, ['test_tag:1']);
 | 
			
		||||
    $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, ['test_tag:1']);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate1')->data);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate2')->data);
 | 
			
		||||
 | 
			
		||||
    // Invalidate test_tag of value 1. This should invalidate both entries.
 | 
			
		||||
    Cache::invalidateTags(['test_tag:1']);
 | 
			
		||||
    $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two caches removed after invalidating a cache tag.');
 | 
			
		||||
    // Verify that cache items have not been deleted after invalidation.
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate1', TRUE)->data);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate2', TRUE)->data);
 | 
			
		||||
 | 
			
		||||
    // Create three cache entries with a mix of tags and tag values.
 | 
			
		||||
    $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, ['test_tag:1']);
 | 
			
		||||
    $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, ['test_tag:2']);
 | 
			
		||||
    $backend->set('test_cid_invalidate3', $this->defaultValue, Cache::PERMANENT, ['test_tag_foo:3']);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate1')->data);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate2')->data);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate3')->data);
 | 
			
		||||
    Cache::invalidateTags(['test_tag_foo:3']);
 | 
			
		||||
    // Verify that cache items not matching the tag were not invalidated.
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate1')->data);
 | 
			
		||||
    $this->assertSame($this->defaultValue, $backend->get('test_cid_invalidate2')->data);
 | 
			
		||||
    $this->assertFalse($backend->get('test_cid_invalidate3'), 'Cached item matching the tag was removed.');
 | 
			
		||||
 | 
			
		||||
    // Create cache entry in multiple bins. Two cache entries
 | 
			
		||||
    // (test_cid_invalidate1 and test_cid_invalidate2) still exist from previous
 | 
			
		||||
    // tests.
 | 
			
		||||
    $tags = ['test_tag:1', 'test_tag:2', 'test_tag:3'];
 | 
			
		||||
    $bins = ['path', 'bootstrap', 'page'];
 | 
			
		||||
    foreach ($bins as $bin) {
 | 
			
		||||
      $this->getCacheBackend($bin)->set('test', $this->defaultValue, Cache::PERMANENT, $tags);
 | 
			
		||||
      $this->assertNotEmpty($this->getCacheBackend($bin)->get('test'), 'Cache item was set in bin.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Cache::invalidateTags(['test_tag:2']);
 | 
			
		||||
 | 
			
		||||
    // Test that the cache entry has been invalidated in multiple bins.
 | 
			
		||||
    foreach ($bins as $bin) {
 | 
			
		||||
      $this->assertFalse($this->getCacheBackend($bin)->get('test'), 'Tag invalidation affected item in bin.');
 | 
			
		||||
    }
 | 
			
		||||
    // Test that the cache entry with a matching tag has been invalidated.
 | 
			
		||||
    $this->assertFalse($this->getCacheBackend($bin)->get('test_cid_invalidate2'), 'Cache items matching tag were invalidated.');
 | 
			
		||||
    // Test that the cache entry with without a matching tag still exists.
 | 
			
		||||
    $this->assertNotEmpty($this->getCacheBackend($bin)->get('test_cid_invalidate1'), 'Cache items not matching tag were not invalidated.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
 | 
			
		||||
   *
 | 
			
		||||
   * @group legacy
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidateAll(): void {
 | 
			
		||||
    $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);
 | 
			
		||||
 | 
			
		||||
    $this->expectDeprecation('CacheBackendInterface::invalidateAll() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use CacheBackendInterface::deleteAll() or cache tag invalidation instead. See https://www.drupal.org/node/3500622');
 | 
			
		||||
    $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->assertNotEmpty($backend_b->get('test3'), 'Item in other bin is preserved.');
 | 
			
		||||
    $this->assertNotEmpty($backend_a->get('test1', TRUE), 'First key has not been deleted.');
 | 
			
		||||
    $this->assertNotEmpty($backend_a->get('test2', TRUE), 'Second key has not been deleted.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests Drupal\Core\Cache\CacheBackendInterface::removeBin().
 | 
			
		||||
   */
 | 
			
		||||
  public function testRemoveBin(): void {
 | 
			
		||||
    $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->assertNotEmpty($backend_b->get('test3'), 'Item in other bin is preserved.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\MemoryCache\LruMemoryCache;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit test of the LRU memory cache using the generic cache unit test base.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class LruCacheGenericTest extends GenericCacheBackendUnitTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected bool $testObjectProperties = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new instance of LruMemoryCache.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   *   A new MemoryBackend object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createCacheBackend($bin) {
 | 
			
		||||
    $backend = new LruMemoryCache(\Drupal::service(TimeInterface::class), 300);
 | 
			
		||||
    \Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
 | 
			
		||||
    return $backend;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\MemoryBackend;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit test of the memory cache backend using the generic cache unit test base.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class MemoryBackendTest extends GenericCacheBackendUnitTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new instance of MemoryBackend.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   *   A new MemoryBackend object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createCacheBackend($bin) {
 | 
			
		||||
    $backend = new MemoryBackend(\Drupal::service(TimeInterface::class));
 | 
			
		||||
    \Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
 | 
			
		||||
    return $backend;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\MemoryCache\MemoryCache;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit test of the memory cache using the generic cache unit test base.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class MemoryCacheGenericTest extends GenericCacheBackendUnitTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected bool $testObjectProperties = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new instance of MemoryCache.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   *   A new MemoryBackend object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createCacheBackend($bin) {
 | 
			
		||||
    $backend = new MemoryCache(\Drupal::service(TimeInterface::class));
 | 
			
		||||
    \Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
 | 
			
		||||
    return $backend;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Cache;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Datetime\TimeInterface;
 | 
			
		||||
use Drupal\Core\Cache\PhpBackend;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit test of the PHP cache backend using the generic cache unit test base.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Cache
 | 
			
		||||
 */
 | 
			
		||||
class PhpBackendTest extends GenericCacheBackendUnitTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new instance of MemoryBackend.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   *   A new PhpBackend object.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createCacheBackend($bin) {
 | 
			
		||||
    return new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'), \Drupal::service(TimeInterface::class));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\ClassLoader;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Random;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslationWrapper;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\module_autoload_test\Foo;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass Drupal\Core\ClassLoader\BackwardsCompatibilityClassLoader
 | 
			
		||||
 * @group ClassLoader
 | 
			
		||||
 */
 | 
			
		||||
class BackwardsCompatibilityClassLoaderTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['module_autoload_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the bc layer for TranslationWrapper works.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTranslationWrapper(): void {
 | 
			
		||||
    // @phpstan-ignore class.notFound
 | 
			
		||||
    $object = new TranslationWrapper('Backward compatibility');
 | 
			
		||||
    $this->assertInstanceOf(TranslatableMarkup::class, $object);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that a moved class from a module works.
 | 
			
		||||
   *
 | 
			
		||||
   * @group legacy
 | 
			
		||||
   */
 | 
			
		||||
  public function testModuleMovedClass():  void {
 | 
			
		||||
    // @phpstan-ignore class.notFound
 | 
			
		||||
    $this->expectDeprecation('Class ' . Foo::class . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0, use Drupal\Component\Utility\Random instead. See https://www.drupal.org/project/drupal/issues/3502882');
 | 
			
		||||
    // @phpstan-ignore class.notFound
 | 
			
		||||
    $object = new Foo();
 | 
			
		||||
    $this->assertInstanceOf(Random::class, $object);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,74 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Common;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Drupal\system_test\Hook\SystemTestHooks;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @covers ::drupal_flush_all_caches
 | 
			
		||||
 * @group Common
 | 
			
		||||
 */
 | 
			
		||||
class DrupalFlushAllCachesTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stores the number of container builds.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int
 | 
			
		||||
   */
 | 
			
		||||
  protected $containerBuilds = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that drupal_flush_all_caches() uses core.extension properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDrupalFlushAllCachesModuleList(): void {
 | 
			
		||||
    $this->assertFalse(function_exists('system_test_help'));
 | 
			
		||||
    $core_extension = \Drupal::configFactory()->getEditable('core.extension');
 | 
			
		||||
    $module = $core_extension->get('module');
 | 
			
		||||
    $module['system_test'] = -10;
 | 
			
		||||
    $core_extension->set('module', module_config_sort($module))->save();
 | 
			
		||||
    $this->containerBuilds = 0;
 | 
			
		||||
    drupal_flush_all_caches();
 | 
			
		||||
    $module_list = ['system_test', 'system'];
 | 
			
		||||
    $database_module = \Drupal::database()->getProvider();
 | 
			
		||||
    if ($database_module !== 'core') {
 | 
			
		||||
      $module_list[] = $database_module;
 | 
			
		||||
    }
 | 
			
		||||
    sort($module_list);
 | 
			
		||||
    $container_modules = array_keys($this->container->getParameter('container.modules'));
 | 
			
		||||
    sort($container_modules);
 | 
			
		||||
    $this->assertSame($module_list, $container_modules);
 | 
			
		||||
    $this->assertSame(1, $this->containerBuilds);
 | 
			
		||||
    $this->assertTrue(method_exists(SystemTestHooks::class, 'help'));
 | 
			
		||||
 | 
			
		||||
    $core_extension->clear('module.system_test')->save();
 | 
			
		||||
    $this->containerBuilds = 0;
 | 
			
		||||
    drupal_flush_all_caches();
 | 
			
		||||
    $module_list = ['system'];
 | 
			
		||||
    if ($database_module !== 'core') {
 | 
			
		||||
      $module_list[] = $database_module;
 | 
			
		||||
    }
 | 
			
		||||
    sort($module_list);
 | 
			
		||||
    $container_modules = array_keys($this->container->getParameter('container.modules'));
 | 
			
		||||
    sort($container_modules);
 | 
			
		||||
    $this->assertSame($module_list, $container_modules);
 | 
			
		||||
    $this->assertSame(1, $this->containerBuilds);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function register(ContainerBuilder $container): void {
 | 
			
		||||
    parent::register($container);
 | 
			
		||||
    $this->containerBuilds++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,65 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Common;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\UrlHelper;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests XSS filtering.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\Component\Utility\Xss::filter()
 | 
			
		||||
 * @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol
 | 
			
		||||
 * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols
 | 
			
		||||
 *
 | 
			
		||||
 * @group Common
 | 
			
		||||
 */
 | 
			
		||||
class XssUnitTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['filter', 'system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests t() functionality.
 | 
			
		||||
   */
 | 
			
		||||
  public function testT(): void {
 | 
			
		||||
    $text = $this->t('Simple text');
 | 
			
		||||
    $this->assertSame('Simple text', (string) $text, 't leaves simple text alone.');
 | 
			
		||||
    $text = $this->t('Escaped text: @value', ['@value' => '<script>']);
 | 
			
		||||
    $this->assertSame('Escaped text: <script>', (string) $text, 't replaces and escapes string.');
 | 
			
		||||
    $text = $this->t('Placeholder text: %value', ['%value' => '<script>']);
 | 
			
		||||
    $this->assertSame('Placeholder text: <em class="placeholder"><script></em>', (string) $text, 't replaces, escapes and themes string.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks that harmful protocols are stripped.
 | 
			
		||||
   */
 | 
			
		||||
  public function testBadProtocolStripping(): void {
 | 
			
		||||
    // 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->assertSame($expected_html, UrlHelper::filterBadProtocol($url), '\\Drupal\\Component\\Utility\\UrlHelper::filterBadProtocol() filters a URL and encodes it for HTML.');
 | 
			
		||||
    $this->assertSame($expected_plain, UrlHelper::stripDangerousProtocols($url), '\\Drupal\\Component\\Utility\\UrlHelper::stripDangerousProtocols() filters a URL and returns plain text.');
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,60 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Condition;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Condition\ConditionPluginCollection;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Condition\ConditionPluginCollection
 | 
			
		||||
 *
 | 
			
		||||
 * @group Condition
 | 
			
		||||
 */
 | 
			
		||||
class ConditionPluginCollectionTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'user',
 | 
			
		||||
    'path_alias',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getConfiguration
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetConfiguration(): void {
 | 
			
		||||
    // Include a condition that has custom configuration and a type mismatch on
 | 
			
		||||
    // 'negate' by using 0 instead of FALSE.
 | 
			
		||||
    $configuration['request_path'] = [
 | 
			
		||||
      'id' => 'request_path',
 | 
			
		||||
      'negate' => 0,
 | 
			
		||||
      'context_mapping' => [],
 | 
			
		||||
      'pages' => '/user/*',
 | 
			
		||||
    ];
 | 
			
		||||
    // Include a condition that matches default values but with a type mismatch
 | 
			
		||||
    // on 'negate' by using 0 instead of FALSE. This condition will be removed,
 | 
			
		||||
    // because condition configurations that match default values with "=="
 | 
			
		||||
    // comparison are not saved or exported.
 | 
			
		||||
    $configuration['user_role'] = [
 | 
			
		||||
      'id' => 'user_role',
 | 
			
		||||
      'negate' => '0',
 | 
			
		||||
      'context_mapping' => [],
 | 
			
		||||
      'roles' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    $collection = new ConditionPluginCollection(\Drupal::service('plugin.manager.condition'), $configuration);
 | 
			
		||||
 | 
			
		||||
    $expected['request_path'] = [
 | 
			
		||||
      'id' => 'request_path',
 | 
			
		||||
      'negate' => 0,
 | 
			
		||||
      'context_mapping' => [],
 | 
			
		||||
      'pages' => '/user/*',
 | 
			
		||||
    ];
 | 
			
		||||
    // NB: The 'user_role' property should not exist in expected set.
 | 
			
		||||
    $this->assertSame($expected, $collection->getConfiguration());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,324 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Action;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore inflector
 | 
			
		||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 | 
			
		||||
use Drupal\Component\Uuid\Uuid;
 | 
			
		||||
use Drupal\config_test\ConfigActionErrorEntity\DuplicatePluralizedMethodName;
 | 
			
		||||
use Drupal\config_test\ConfigActionErrorEntity\DuplicatePluralizedOtherMethodName;
 | 
			
		||||
use Drupal\Core\Config\Action\ConfigActionException;
 | 
			
		||||
use Drupal\Core\Config\Action\DuplicateConfigActionIdException;
 | 
			
		||||
use Drupal\Core\Config\Action\EntityMethodException;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the config action system.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigActionTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\Plugin\ConfigAction\EntityCreate
 | 
			
		||||
   */
 | 
			
		||||
  public function testEntityCreate(): void {
 | 
			
		||||
    $this->assertCount(0, \Drupal::entityTypeManager()->getStorage('config_test')->loadMultiple(), 'There are no config_test entities');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    $manager->applyAction('entity_create:createIfNotExists', 'config_test.dynamic.action_test', ['label' => 'Action test']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest[] $config_test_entities */
 | 
			
		||||
    $config_test_entities = \Drupal::entityTypeManager()->getStorage('config_test')->loadMultiple();
 | 
			
		||||
    $this->assertCount(1, \Drupal::entityTypeManager()->getStorage('config_test')->loadMultiple(), 'There is 1 config_test entity');
 | 
			
		||||
    $this->assertSame('Action test', $config_test_entities['action_test']->label());
 | 
			
		||||
    $this->assertTrue(Uuid::isValid((string) $config_test_entities['action_test']->uuid()), 'Config entity assigned a valid UUID');
 | 
			
		||||
 | 
			
		||||
    // Calling createIfNotExists action again will not error.
 | 
			
		||||
    $manager->applyAction('entity_create:createIfNotExists', 'config_test.dynamic.action_test', ['label' => 'Action test']);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $manager->applyAction('entity_create:create', 'config_test.dynamic.action_test', ['label' => 'Action test']);
 | 
			
		||||
      $this->fail('Expected exception not thrown');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigActionException $e) {
 | 
			
		||||
      $this->assertSame('Entity config_test.dynamic.action_test exists', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\Plugin\ConfigAction\EntityMethod
 | 
			
		||||
   */
 | 
			
		||||
  public function testEntityMethod(): void {
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
    $storage = \Drupal::entityTypeManager()->getStorage('config_test');
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('Default', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    // Call a method action.
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:setProtectedProperty', 'config_test.dynamic.dotted.default', 'Test value');
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('Test value', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:setProtectedProperty', 'config_test.dynamic.dotted.default', 'Test value 2');
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('Test value 2', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:concatProtectedProperty', 'config_test.dynamic.dotted.default', ['Test value ', '3']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('Test value 3', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:concatProtectedPropertyOptional', 'config_test.dynamic.dotted.default', ['Test value ', '4']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('Test value 4', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    // Test calling an action that has 2 arguments but one is optional with an
 | 
			
		||||
    // array value.
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:concatProtectedPropertyOptional', 'config_test.dynamic.dotted.default', ['Test value 5']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('Test value 5', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    // Test calling an action that has 2 arguments but one is optional with a
 | 
			
		||||
    // non array value.
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:concatProtectedPropertyOptional', 'config_test.dynamic.dotted.default', 'Test value 6');
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('Test value 6', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    // Test calling an action that expects no arguments.
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:defaultProtectedProperty', 'config_test.dynamic.dotted.default', []);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('Set by method', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:addToArray', 'config_test.dynamic.dotted.default', 'foo');
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:addToArray', 'config_test.dynamic.dotted.default', 'bar');
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame(['foo', 'bar'], $config_test_entity->getArrayProperty());
 | 
			
		||||
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:addToArray', 'config_test.dynamic.dotted.default', ['a', 'b', 'c']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame(['foo', 'bar', ['a', 'b', 'c']], $config_test_entity->getArrayProperty());
 | 
			
		||||
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:setArray', 'config_test.dynamic.dotted.default', ['a', 'b', 'c']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame(['a', 'b', 'c'], $config_test_entity->getArrayProperty());
 | 
			
		||||
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:setArray', 'config_test.dynamic.dotted.default', [['a', 'b', 'c'], ['a']]);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame([['a', 'b', 'c'], ['a']], $config_test_entity->getArrayProperty());
 | 
			
		||||
 | 
			
		||||
    $config_test_entity->delete();
 | 
			
		||||
    try {
 | 
			
		||||
      $manager->applyAction('entity_method:config_test.dynamic:setProtectedProperty', 'config_test.dynamic.dotted.default', 'Test value');
 | 
			
		||||
      $this->fail('Expected exception not thrown');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigActionException $e) {
 | 
			
		||||
      $this->assertSame('Entity config_test.dynamic.dotted.default does not exist', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test custom and default admin labels.
 | 
			
		||||
    $this->assertSame('Test configuration append', (string) $manager->getDefinition('entity_method:config_test.dynamic:append')['admin_label']);
 | 
			
		||||
    $this->assertSame('Set default name', (string) $manager->getDefinition('entity_method:config_test.dynamic:defaultProtectedProperty')['admin_label']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\Plugin\ConfigAction\EntityMethod
 | 
			
		||||
   */
 | 
			
		||||
  public function testPluralizedEntityMethod(): void {
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
    $storage = \Drupal::entityTypeManager()->getStorage('config_test');
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    // Call a pluralized method action.
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:addToArrayMultipleTimes', 'config_test.dynamic.dotted.default', ['a', 'b', 'c', 'd']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame(['a', 'b', 'c', 'd'], $config_test_entity->getArrayProperty());
 | 
			
		||||
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:addToArrayMultipleTimes', 'config_test.dynamic.dotted.default', [['foo'], 'bar']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame(['a', 'b', 'c', 'd', ['foo'], 'bar'], $config_test_entity->getArrayProperty());
 | 
			
		||||
 | 
			
		||||
    $config_test_entity->setProtectedProperty('')->save();
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:appends', 'config_test.dynamic.dotted.default', ['1', '2', '3']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('123', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    // Test that the inflector converts to a good plural form.
 | 
			
		||||
    $config_test_entity->setProtectedProperty('')->save();
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:concatProtectedProperties', 'config_test.dynamic.dotted.default', [['1', '2'], ['3', '4']]);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('dotted.default');
 | 
			
		||||
    $this->assertSame('34', $config_test_entity->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    $this->assertTrue($manager->hasDefinition('entity_method:config_test.dynamic:setProtectedProperty'), 'The setProtectedProperty action exists');
 | 
			
		||||
    // cspell:ignore Propertys
 | 
			
		||||
    $this->assertFalse($manager->hasDefinition('entity_method:config_test.dynamic:setProtectedPropertys'), 'There is no automatically pluralized version of the setProtectedProperty action');
 | 
			
		||||
 | 
			
		||||
    // Admin label for pluralized form.
 | 
			
		||||
    $this->assertSame('Test configuration append (multiple calls)', (string) $manager->getDefinition('entity_method:config_test.dynamic:appends')['admin_label']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\Plugin\ConfigAction\EntityMethod
 | 
			
		||||
   */
 | 
			
		||||
  public function testPluralizedEntityMethodException(): void {
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    $this->expectException(EntityMethodException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The pluralized entity method config action \'entity_method:config_test.dynamic:addToArrayMultipleTimes\' requires an array value in order to call Drupal\config_test\Entity\ConfigTest::addToArray() multiple times');
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:addToArrayMultipleTimes', 'config_test.dynamic.dotted.default', 'Test value');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\Plugin\ConfigAction\Deriver\EntityMethodDeriver
 | 
			
		||||
   */
 | 
			
		||||
  public function testDuplicatePluralizedMethodNameException(): void {
 | 
			
		||||
    \Drupal::state()->set('config_test.class_override', DuplicatePluralizedMethodName::class);
 | 
			
		||||
    \Drupal::entityTypeManager()->clearCachedDefinitions();
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    $this->expectException(EntityMethodException::class);
 | 
			
		||||
    $this->expectExceptionMessage('Duplicate action can not be created for ID \'config_test.dynamic:testMethod\' for Drupal\config_test\ConfigActionErrorEntity\DuplicatePluralizedMethodName::testMethod(). The existing action is for the ::testMethod() method');
 | 
			
		||||
    $manager->getDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\Plugin\ConfigAction\Deriver\EntityMethodDeriver
 | 
			
		||||
   */
 | 
			
		||||
  public function testDuplicatePluralizedOtherMethodNameException(): void {
 | 
			
		||||
    \Drupal::state()->set('config_test.class_override', DuplicatePluralizedOtherMethodName::class);
 | 
			
		||||
    \Drupal::entityTypeManager()->clearCachedDefinitions();
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    $this->expectException(EntityMethodException::class);
 | 
			
		||||
    $this->expectExceptionMessage('Duplicate action can not be created for ID \'config_test.dynamic:testMethod2\' for Drupal\config_test\ConfigActionErrorEntity\DuplicatePluralizedOtherMethodName::testMethod2(). The existing action is for the ::testMethod() method');
 | 
			
		||||
    $manager->getDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\Plugin\ConfigAction\EntityMethod
 | 
			
		||||
   */
 | 
			
		||||
  public function testEntityMethodException(): void {
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    $this->expectException(EntityMethodException::class);
 | 
			
		||||
    $this->expectExceptionMessage('Entity method config action \'entity_method:config_test.dynamic:concatProtectedProperty\' requires an array value. The number of parameters or required parameters for Drupal\config_test\Entity\ConfigTest::concatProtectedProperty() is not 1');
 | 
			
		||||
    $manager->applyAction('entity_method:config_test.dynamic:concatProtectedProperty', 'config_test.dynamic.dotted.default', 'Test value');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\Plugin\ConfigAction\SimpleConfigUpdate
 | 
			
		||||
   */
 | 
			
		||||
  public function testSimpleConfigUpdate(): void {
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
    $this->assertSame('bar', $this->config('config_test.system')->get('foo'));
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    // Call the simple config update action.
 | 
			
		||||
    $manager->applyAction('simpleConfigUpdate', 'config_test.system', ['foo' => 'Yay!']);
 | 
			
		||||
    $this->assertSame('Yay!', $this->config('config_test.system')->get('foo'));
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $manager->applyAction('simpleConfigUpdate', 'config_test.system', 'Test');
 | 
			
		||||
      $this->fail('Expected exception not thrown');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigActionException $e) {
 | 
			
		||||
      $this->assertSame('Config config_test.system can not be updated because $value is not an array', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->config('config_test.system')->delete();
 | 
			
		||||
    try {
 | 
			
		||||
      $manager->applyAction('simpleConfigUpdate', 'config_test.system', ['foo' => 'Yay!']);
 | 
			
		||||
      $this->fail('Expected exception not thrown');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigActionException $e) {
 | 
			
		||||
      $this->assertSame('Config config_test.system does not exist so can not be updated', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\ConfigActionManager::getShorthandActionIdsForEntityType()
 | 
			
		||||
   */
 | 
			
		||||
  public function testShorthandActionIds(): void {
 | 
			
		||||
    $storage = \Drupal::entityTypeManager()->getStorage('config_test');
 | 
			
		||||
    $this->assertCount(0, $storage->loadMultiple(), 'There are no config_test entities');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    $manager->applyAction('createIfNotExists', 'config_test.dynamic.action_test', ['label' => 'Action test', 'protected_property' => '']);
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest[] $config_test_entities */
 | 
			
		||||
    $config_test_entities = $storage->loadMultiple();
 | 
			
		||||
    $this->assertCount(1, $config_test_entities, 'There is 1 config_test entity');
 | 
			
		||||
    $this->assertSame('Action test', $config_test_entities['action_test']->label());
 | 
			
		||||
 | 
			
		||||
    $this->assertSame('', $config_test_entities['action_test']->getProtectedProperty());
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    // Call a method action.
 | 
			
		||||
    $manager->applyAction('setProtectedProperty', 'config_test.dynamic.action_test', 'Test value');
 | 
			
		||||
    /** @var \Drupal\config_test\Entity\ConfigTest $config_test_entity */
 | 
			
		||||
    $config_test_entity = $storage->load('action_test');
 | 
			
		||||
    $this->assertSame('Test value', $config_test_entity->getProtectedProperty());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\ConfigActionManager::getShorthandActionIdsForEntityType()
 | 
			
		||||
   */
 | 
			
		||||
  public function testDuplicateShorthandActionIds(): void {
 | 
			
		||||
    $this->enableModules(['config_action_duplicate_test']);
 | 
			
		||||
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
 | 
			
		||||
    $manager = $this->container->get('plugin.manager.config_action');
 | 
			
		||||
    $this->expectException(DuplicateConfigActionIdException::class);
 | 
			
		||||
    $this->expectExceptionMessage("The plugins 'entity_method:config_test.dynamic:setProtectedProperty' and 'config_action_duplicate_test:config_test.dynamic:setProtectedProperty' both resolve to the same shorthand action ID for the 'config_test' entity type");
 | 
			
		||||
    $manager->applyAction('createIfNotExists', 'config_test.dynamic.action_test', ['label' => 'Action test', 'protected_property' => '']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\ConfigActionManager::getShorthandActionIdsForEntityType()
 | 
			
		||||
   */
 | 
			
		||||
  public function testParentAttributes(): void {
 | 
			
		||||
    $definitions = $this->container->get('plugin.manager.config_action')->getDefinitions();
 | 
			
		||||
    // The \Drupal\config_test\Entity\ConfigQueryTest::concatProtectedProperty()
 | 
			
		||||
    // does not have an attribute but the parent does so this is discovered.
 | 
			
		||||
    $this->assertArrayHasKey('entity_method:config_test.query:concatProtectedProperty', $definitions);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Config\Action\ConfigActionManager
 | 
			
		||||
   */
 | 
			
		||||
  public function testMissingAction(): void {
 | 
			
		||||
    $this->expectException(PluginNotFoundException::class);
 | 
			
		||||
    $this->expectExceptionMessageMatches('/^The "does_not_exist" plugin does not exist/');
 | 
			
		||||
    $this->container->get('plugin.manager.config_action')->applyAction('does_not_exist', 'config_test.system', ['foo' => 'Yay!']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,99 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\config_override_test\Cache\PirateDayCacheContext;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests if configuration overrides correctly affect cacheability metadata.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class CacheabilityMetadataConfigOverrideTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'block',
 | 
			
		||||
    'block_content',
 | 
			
		||||
    'config',
 | 
			
		||||
    'config_override_test',
 | 
			
		||||
    'field',
 | 
			
		||||
    'path_alias',
 | 
			
		||||
    'system',
 | 
			
		||||
    'text',
 | 
			
		||||
    'user',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->container->get('theme_installer')->install(['stark']);
 | 
			
		||||
    $this->installEntitySchema('block_content');
 | 
			
		||||
    $this->installConfig([
 | 
			
		||||
      'block_content',
 | 
			
		||||
      'config_override_test',
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests if config overrides correctly set cacheability metadata.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigOverride(): void {
 | 
			
		||||
    // It's pirate day today!
 | 
			
		||||
    $GLOBALS['it_is_pirate_day'] = TRUE;
 | 
			
		||||
 | 
			
		||||
    $config_factory = $this->container->get('config.factory');
 | 
			
		||||
    $config = $config_factory->get('system.theme');
 | 
			
		||||
 | 
			
		||||
    // Check that we are using the Pirate theme.
 | 
			
		||||
    $theme = $config->get('default');
 | 
			
		||||
    $this->assertEquals('pirate', $theme);
 | 
			
		||||
 | 
			
		||||
    // Check that the cacheability metadata is correct.
 | 
			
		||||
    $this->assertEquals(['pirate_day'], $config->getCacheContexts());
 | 
			
		||||
    $this->assertEquals(['config:system.theme', 'pirate-day-tag'], $config->getCacheTags());
 | 
			
		||||
    $this->assertEquals(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $config->getCacheMaxAge());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests if config overrides set cacheability metadata on config entities.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigEntityOverride(): void {
 | 
			
		||||
    // It's pirate day today!
 | 
			
		||||
    $GLOBALS['it_is_pirate_day'] = TRUE;
 | 
			
		||||
 | 
			
		||||
    // Load the User login block and check that its cacheability metadata is
 | 
			
		||||
    // overridden correctly. This verifies that the metadata is correctly
 | 
			
		||||
    // applied to config entities.
 | 
			
		||||
    /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
 | 
			
		||||
    $entity_type_manager = $this->container->get('entity_type.manager');
 | 
			
		||||
    $block = $entity_type_manager->getStorage('block')->load('call_to_action');
 | 
			
		||||
 | 
			
		||||
    // Check that our call to action message is appealing to filibusters.
 | 
			
		||||
    $this->assertEquals('Draw yer cutlasses!', $block->label());
 | 
			
		||||
 | 
			
		||||
    // Check that the cacheability metadata is correct.
 | 
			
		||||
    $this->assertEquals(['pirate_day'], $block->getCacheContexts());
 | 
			
		||||
    $this->assertEquals(['config:block.block.call_to_action', 'pirate-day-tag'], $block->getCacheTags());
 | 
			
		||||
    $this->assertEquals(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $block->getCacheMaxAge());
 | 
			
		||||
 | 
			
		||||
    // Check that duplicating a config entity does not have the original config
 | 
			
		||||
    // entity's cache tag.
 | 
			
		||||
    $this->assertEquals(['config:block.block.', 'pirate-day-tag'], $block->createDuplicate()->getCacheTags());
 | 
			
		||||
 | 
			
		||||
    // Check that renaming a config entity does not have the original config
 | 
			
		||||
    // entity's cache tag.
 | 
			
		||||
    $block->set('id', 'call_to_looting')->save();
 | 
			
		||||
    $this->assertEquals(['pirate_day'], $block->getCacheContexts());
 | 
			
		||||
    $this->assertEquals(['config:block.block.call_to_looting', 'pirate-day-tag'], $block->getCacheTags());
 | 
			
		||||
    $this->assertEquals(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $block->getCacheMaxAge());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										352
									
								
								web/core/tests/Drupal/KernelTests/Core/Config/ConfigCRUDTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										352
									
								
								web/core/tests/Drupal/KernelTests/Core/Config/ConfigCRUDTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,352 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Crypt;
 | 
			
		||||
use Drupal\Core\Config\Config;
 | 
			
		||||
use Drupal\Core\Config\ConfigNameException;
 | 
			
		||||
use Drupal\Core\Config\ConfigValueException;
 | 
			
		||||
use Drupal\Core\Config\DatabaseStorage;
 | 
			
		||||
use Drupal\Core\Config\UnsupportedDataTypeConfigException;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests CRUD operations on configuration objects.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigCRUDTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Exempt from strict schema checking.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\Development\ConfigSchemaChecker
 | 
			
		||||
   */
 | 
			
		||||
  protected $strictConfigSchema = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests CRUD operations.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCRUD(): void {
 | 
			
		||||
    $event_dispatcher = $this->container->get('event_dispatcher');
 | 
			
		||||
    $typed_config_manager = $this->container->get('config.typed');
 | 
			
		||||
 | 
			
		||||
    $storage = $this->container->get('config.storage');
 | 
			
		||||
    $collection_storage = $storage->createCollection('test_collection');
 | 
			
		||||
 | 
			
		||||
    $config_factory = $this->container->get('config.factory');
 | 
			
		||||
    $name = 'config_test.crud';
 | 
			
		||||
 | 
			
		||||
    // Create a new configuration object in the default collection.
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
    $this->assertTrue($config->isNew());
 | 
			
		||||
 | 
			
		||||
    $config->set('value', 'initial');
 | 
			
		||||
    $config->save();
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Verify the active configuration contains the saved value.
 | 
			
		||||
    $actual_data = $storage->read($name);
 | 
			
		||||
    $this->assertSame(['value' => 'initial'], $actual_data);
 | 
			
		||||
 | 
			
		||||
    // Verify the config factory contains the saved value.
 | 
			
		||||
    $actual_data = $config_factory->get($name)->getRawData();
 | 
			
		||||
    $this->assertSame(['value' => 'initial'], $actual_data);
 | 
			
		||||
 | 
			
		||||
    // Create another instance of the config object using a custom collection.
 | 
			
		||||
    $collection_config = new Config(
 | 
			
		||||
      $name,
 | 
			
		||||
      $collection_storage,
 | 
			
		||||
      $event_dispatcher,
 | 
			
		||||
      $typed_config_manager
 | 
			
		||||
    );
 | 
			
		||||
    $collection_config->set('value', 'overridden');
 | 
			
		||||
    $collection_config->save();
 | 
			
		||||
 | 
			
		||||
    // Verify that the config factory still returns the right value, from the
 | 
			
		||||
    // config instance in the default collection.
 | 
			
		||||
    $actual_data = $config_factory->get($name)->getRawData();
 | 
			
		||||
    $this->assertSame(['value' => 'initial'], $actual_data);
 | 
			
		||||
 | 
			
		||||
    // Update the configuration object instance.
 | 
			
		||||
    $config->set('value', 'instance-update');
 | 
			
		||||
    $config->save();
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Verify the active configuration contains the updated value.
 | 
			
		||||
    $actual_data = $storage->read($name);
 | 
			
		||||
    $this->assertSame(['value' => 'instance-update'], $actual_data);
 | 
			
		||||
 | 
			
		||||
    // Verify a call to $this->config() immediately returns the updated value.
 | 
			
		||||
    $new_config = $this->config($name);
 | 
			
		||||
    $this->assertSame($config->get(), $new_config->get());
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Pollute the config factory static cache.
 | 
			
		||||
    $config_factory->getEditable($name);
 | 
			
		||||
 | 
			
		||||
    // Delete the config object that uses a custom collection. This should not
 | 
			
		||||
    // affect the instance returned by the config factory which depends on the
 | 
			
		||||
    // default collection storage.
 | 
			
		||||
    $collection_config->delete();
 | 
			
		||||
    $actual_config = $config_factory->get($name);
 | 
			
		||||
    $this->assertFalse($actual_config->isNew());
 | 
			
		||||
    $this->assertSame(['value' => 'instance-update'], $actual_config->getRawData());
 | 
			
		||||
 | 
			
		||||
    // Delete the configuration object.
 | 
			
		||||
    $config->delete();
 | 
			
		||||
 | 
			
		||||
    // Verify the configuration object is empty.
 | 
			
		||||
    $this->assertSame([], $config->get());
 | 
			
		||||
    $this->assertTrue($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Verify that all copies of the configuration has been removed from the
 | 
			
		||||
    // static cache.
 | 
			
		||||
    $this->assertTrue($config_factory->getEditable($name)->isNew());
 | 
			
		||||
 | 
			
		||||
    // Verify the active configuration contains no value.
 | 
			
		||||
    $actual_data = $storage->read($name);
 | 
			
		||||
    $this->assertFalse($actual_data);
 | 
			
		||||
 | 
			
		||||
    // Verify $this->config() returns no data.
 | 
			
		||||
    $new_config = $this->config($name);
 | 
			
		||||
    $this->assertSame($config->get(), $new_config->get());
 | 
			
		||||
    $this->assertTrue($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Re-create the configuration object.
 | 
			
		||||
    $config->set('value', 're-created');
 | 
			
		||||
    $config->save();
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Verify the active configuration contains the updated value.
 | 
			
		||||
    $actual_data = $storage->read($name);
 | 
			
		||||
    $this->assertSame(['value' => 're-created'], $actual_data);
 | 
			
		||||
 | 
			
		||||
    // Verify a call to $this->config() immediately returns the updated value.
 | 
			
		||||
    $new_config = $this->config($name);
 | 
			
		||||
    $this->assertSame($config->get(), $new_config->get());
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Rename the configuration object.
 | 
			
		||||
    $new_name = 'config_test.crud_rename';
 | 
			
		||||
    $this->container->get('config.factory')->rename($name, $new_name);
 | 
			
		||||
    $renamed_config = $this->config($new_name);
 | 
			
		||||
    $this->assertSame($config->get(), $renamed_config->get());
 | 
			
		||||
    $this->assertFalse($renamed_config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Ensure that the old configuration object is removed from both the cache
 | 
			
		||||
    // and the configuration storage.
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
    $this->assertSame([], $config->get());
 | 
			
		||||
    $this->assertTrue($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Test renaming when config.factory does not have the object in its static
 | 
			
		||||
    // cache.
 | 
			
		||||
    $name = 'config_test.crud_rename';
 | 
			
		||||
    // Pollute the non-overrides static cache.
 | 
			
		||||
    $config_factory->getEditable($name);
 | 
			
		||||
    // Pollute the overrides static cache.
 | 
			
		||||
    $config = $config_factory->get($name);
 | 
			
		||||
    // Rename and ensure that happened properly.
 | 
			
		||||
    $new_name = 'config_test.crud_rename_no_cache';
 | 
			
		||||
    $config_factory->rename($name, $new_name);
 | 
			
		||||
    $renamed_config = $config_factory->get($new_name);
 | 
			
		||||
    $this->assertSame($config->get(), $renamed_config->get());
 | 
			
		||||
    $this->assertFalse($renamed_config->isNew());
 | 
			
		||||
    // Ensure the overrides static cache has been cleared.
 | 
			
		||||
    $this->assertTrue($config_factory->get($name)->isNew());
 | 
			
		||||
    // Ensure the non-overrides static cache has been cleared.
 | 
			
		||||
    $this->assertTrue($config_factory->getEditable($name)->isNew());
 | 
			
		||||
 | 
			
		||||
    // Merge data into the configuration object.
 | 
			
		||||
    $new_config = $this->config($new_name);
 | 
			
		||||
    $expected_values = [
 | 
			
		||||
      'value' => 'herp',
 | 
			
		||||
      '404' => 'foo',
 | 
			
		||||
    ];
 | 
			
		||||
    $new_config->merge($expected_values);
 | 
			
		||||
    $new_config->save();
 | 
			
		||||
    $this->assertSame($expected_values['value'], $new_config->get('value'));
 | 
			
		||||
    $this->assertSame($expected_values['404'], $new_config->get('404'));
 | 
			
		||||
 | 
			
		||||
    // Test that getMultiple() does not return new config objects that were
 | 
			
		||||
    // previously accessed with get()
 | 
			
		||||
    $new_config = $config_factory->get('non_existing_key');
 | 
			
		||||
    $this->assertTrue($new_config->isNew());
 | 
			
		||||
    $this->assertCount(0, $config_factory->loadMultiple(['non_existing_key']), 'loadMultiple() does not return new objects');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of configuration object names.
 | 
			
		||||
   */
 | 
			
		||||
  public function testNameValidation(): void {
 | 
			
		||||
    // Verify that an object name without namespace causes an exception.
 | 
			
		||||
    $name = 'no_namespace';
 | 
			
		||||
    try {
 | 
			
		||||
      $this->config($name)->save();
 | 
			
		||||
      $this->fail('Expected ConfigNameException was thrown for a name without a namespace.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $e) {
 | 
			
		||||
      $this->assertInstanceOf(ConfigNameException::class, $e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Verify that a name longer than the maximum length causes an exception.
 | 
			
		||||
    $name = 'config_test.herman_melville.dick_or_the_whale.harper_1851.now_small_fowls_flew_screaming_over_the_yet_yawning_gulf_a_sullen_white_surf_beat_against_its_steep_sides_then_all_collapsed_and_the_great_shroud_of_the_sea_rolled_on_as_it_rolled_five_thousand_years_ago';
 | 
			
		||||
    try {
 | 
			
		||||
      $this->config($name)->save();
 | 
			
		||||
      $this->fail('Expected ConfigNameException was thrown for a name longer than Config::MAX_NAME_LENGTH.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $e) {
 | 
			
		||||
      $this->assertInstanceOf(ConfigNameException::class, $e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Verify that disallowed characters in the name cause an exception.
 | 
			
		||||
    $characters = $test_characters = [':', '?', '*', '<', '>', '"', '\'', '/', '\\'];
 | 
			
		||||
    foreach ($test_characters as $i => $c) {
 | 
			
		||||
      try {
 | 
			
		||||
        $name = 'namespace.object' . $c;
 | 
			
		||||
        $config = $this->config($name);
 | 
			
		||||
        $config->save();
 | 
			
		||||
      }
 | 
			
		||||
      catch (ConfigNameException) {
 | 
			
		||||
        unset($test_characters[$i]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $this->assertEmpty($test_characters, sprintf('Expected ConfigNameException was thrown for all invalid name characters: %s', implode(' ', $characters)));
 | 
			
		||||
 | 
			
		||||
    // Verify that a valid config object name can be saved.
 | 
			
		||||
    $name = 'namespace.object';
 | 
			
		||||
    try {
 | 
			
		||||
      $config = $this->config($name);
 | 
			
		||||
      $config->save();
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigNameException) {
 | 
			
		||||
      $this->fail('ConfigNameException was not thrown for a valid object name.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of configuration object values.
 | 
			
		||||
   */
 | 
			
		||||
  public function testValueValidation(): void {
 | 
			
		||||
    // Verify that setData() will catch dotted keys.
 | 
			
		||||
    try {
 | 
			
		||||
      $this->config('namespace.object')->setData(['key.value' => 12])->save();
 | 
			
		||||
      $this->fail('Expected ConfigValueException was thrown from setData() for value with dotted keys.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $e) {
 | 
			
		||||
      $this->assertInstanceOf(ConfigValueException::class, $e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Verify that set() will catch dotted keys.
 | 
			
		||||
    try {
 | 
			
		||||
      $this->config('namespace.object')->set('foo', ['key.value' => 12])->save();
 | 
			
		||||
      $this->fail('Expected ConfigValueException was thrown from set() for value with dotted keys.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception $e) {
 | 
			
		||||
      $this->assertInstanceOf(ConfigValueException::class, $e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests data type handling.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDataTypes(): void {
 | 
			
		||||
    \Drupal::service('module_installer')->install(['config_test']);
 | 
			
		||||
    $storage = new DatabaseStorage($this->container->get('database'), 'config');
 | 
			
		||||
    $name = 'config_test.types';
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
 | 
			
		||||
    // Verify variable data types are intact.
 | 
			
		||||
    $data = [
 | 
			
		||||
      'array' => [],
 | 
			
		||||
      'boolean' => TRUE,
 | 
			
		||||
      'exp' => 1.2e+34,
 | 
			
		||||
      'float' => 3.14159,
 | 
			
		||||
      'float_as_integer' => (float) 1,
 | 
			
		||||
      'hex' => 0xC,
 | 
			
		||||
      'int' => 99,
 | 
			
		||||
      // Symfony 5.1's YAML parser issues a deprecation when reading octal with
 | 
			
		||||
      // a leading zero, to comply with YAML 1.2. However PECL YAML is still
 | 
			
		||||
      // YAML 1.1 compliant.
 | 
			
		||||
      // @todo Revisit parsing of octal once PECL YAML supports YAML 1.2.
 | 
			
		||||
      //   https://www.drupal.org/project/drupal/issues/3205480
 | 
			
		||||
      //   'octal' => 0775,
 | 
			
		||||
      'string' => 'string',
 | 
			
		||||
      'string_int' => '1',
 | 
			
		||||
      'nullable_array' => NULL,
 | 
			
		||||
      'nullable_boolean' => NULL,
 | 
			
		||||
      'nullable_exp' => NULL,
 | 
			
		||||
      'nullable_float' => NULL,
 | 
			
		||||
      'nullable_float_as_integer' => NULL,
 | 
			
		||||
      'nullable_hex' => NULL,
 | 
			
		||||
      'nullable_int' => NULL,
 | 
			
		||||
      'nullable_octal' => NULL,
 | 
			
		||||
      'nullable_string' => NULL,
 | 
			
		||||
      'nullable_string_int' => NULL,
 | 
			
		||||
      'mapping_with_only_required_keys' => [],
 | 
			
		||||
      'mapping_with_some_required_keys' => [],
 | 
			
		||||
      'mapping_with_only_optional_keys' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    $data = ['_core' => ['default_config_hash' => Crypt::hashBase64(serialize($data))]] + $data;
 | 
			
		||||
    $this->assertSame($data, $config->get());
 | 
			
		||||
 | 
			
		||||
    // Re-set each key using Config::set().
 | 
			
		||||
    foreach ($data as $key => $value) {
 | 
			
		||||
      $config->set($key, $value);
 | 
			
		||||
    }
 | 
			
		||||
    $config->save();
 | 
			
		||||
    $this->assertSame($data, $config->get());
 | 
			
		||||
    // Assert the data against the file storage.
 | 
			
		||||
    $this->assertSame($data, $storage->read($name));
 | 
			
		||||
 | 
			
		||||
    // Set data using config::setData().
 | 
			
		||||
    $config->setData($data)->save();
 | 
			
		||||
    $this->assertSame($data, $config->get());
 | 
			
		||||
    $this->assertSame($data, $storage->read($name));
 | 
			
		||||
 | 
			
		||||
    // Test that schema type enforcement can be overridden by trusting the data.
 | 
			
		||||
    $this->assertSame(99, $config->get('int'));
 | 
			
		||||
    $config->set('int', '99')->save(TRUE);
 | 
			
		||||
    $this->assertSame('99', $config->get('int'));
 | 
			
		||||
    // Test that re-saving without testing the data enforces the schema type.
 | 
			
		||||
    $config->save();
 | 
			
		||||
    $this->assertSame($data, $config->get());
 | 
			
		||||
 | 
			
		||||
    // Test that setting an unsupported type for a config object with a schema
 | 
			
		||||
    // fails.
 | 
			
		||||
    try {
 | 
			
		||||
      $config->set('stream', fopen(__FILE__, 'r'))->save();
 | 
			
		||||
      $this->fail('No Exception thrown upon saving invalid data type.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (UnsupportedDataTypeConfigException) {
 | 
			
		||||
      // Expected exception; just continue testing.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test that setting an unsupported type for a config object with no schema
 | 
			
		||||
    // also fails.
 | 
			
		||||
    $typed_config_manager = $this->container->get('config.typed');
 | 
			
		||||
    $config_name = 'config_test.no_schema';
 | 
			
		||||
    $config = $this->config($config_name);
 | 
			
		||||
    $this->assertFalse($typed_config_manager->hasConfigSchema($config_name));
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $config->set('stream', fopen(__FILE__, 'r'))->save();
 | 
			
		||||
      $this->fail('No Exception thrown upon saving invalid data type.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (UnsupportedDataTypeConfigException) {
 | 
			
		||||
      // Expected exception; just continue testing.
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,712 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\entity_test\Entity\EntityTest;
 | 
			
		||||
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
 | 
			
		||||
use Drupal\user\Entity\Role;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests for configuration dependencies.
 | 
			
		||||
 *
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Config\ConfigManager
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigDependencyTest extends EntityKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Modules to install.
 | 
			
		||||
   *
 | 
			
		||||
   * The entity_test module is enabled to provide content entity types.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test', 'entity_test', 'user', 'node', 'views'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that calculating dependencies for system module.
 | 
			
		||||
   */
 | 
			
		||||
  public function testNonEntity(): void {
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
    $config_manager = \Drupal::service('config.manager');
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependencies('module', ['system']);
 | 
			
		||||
    $this->assertTrue(isset($dependents['system.site']), 'Simple configuration system.site has a UUID key even though it is not a configuration entity and therefore is found when looking for dependencies of the System module.');
 | 
			
		||||
    // Ensure that calling
 | 
			
		||||
    // \Drupal\Core\Config\ConfigManager::findConfigEntityDependenciesAsEntities()
 | 
			
		||||
    // does not try to load system.site as an entity.
 | 
			
		||||
    $config_manager->findConfigEntityDependenciesAsEntities('module', ['system']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests creating dependencies on configuration entities.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDependencyManagement(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
 | 
			
		||||
    $config_manager = \Drupal::service('config.manager');
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')->getStorage('config_test');
 | 
			
		||||
    // Test dependencies between modules.
 | 
			
		||||
    $entity1 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity1',
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'module' => ['node'],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity1->save();
 | 
			
		||||
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependencies('module', ['node']);
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependencies('module', ['config_test']);
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the config_test module.');
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependencies('module', ['views']);
 | 
			
		||||
    $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Views module.');
 | 
			
		||||
    // Ensure that the provider of the config entity is not actually written to
 | 
			
		||||
    // the dependencies array.
 | 
			
		||||
    $raw_config = $this->config('config_test.dynamic.entity1');
 | 
			
		||||
    $root_module_dependencies = $raw_config->get('dependencies.module');
 | 
			
		||||
    $this->assertEmpty($root_module_dependencies, 'Node module is not written to the root dependencies array as it is enforced.');
 | 
			
		||||
 | 
			
		||||
    // Create additional entities to test dependencies on config entities.
 | 
			
		||||
    $entity2 = $storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]);
 | 
			
		||||
    $entity2->save();
 | 
			
		||||
    $entity3 = $storage->create(['id' => 'entity3', 'dependencies' => ['enforced' => ['config' => [$entity2->getConfigDependencyName()]]]]);
 | 
			
		||||
    $entity3->save();
 | 
			
		||||
    $entity4 = $storage->create(['id' => 'entity4', 'dependencies' => ['enforced' => ['config' => [$entity3->getConfigDependencyName()]]]]);
 | 
			
		||||
    $entity4->save();
 | 
			
		||||
 | 
			
		||||
    // Test getting $entity1's dependencies as configuration dependency objects.
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependencies('config', [$entity1->getConfigDependencyName()]);
 | 
			
		||||
    $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on itself.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
 | 
			
		||||
    // Test getting $entity2's dependencies as entities.
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependenciesAsEntities('config', [$entity2->getConfigDependencyName()]);
 | 
			
		||||
    $dependent_ids = $this->getDependentIds($dependents);
 | 
			
		||||
    $this->assertNotContains('config_test:entity1', $dependent_ids, 'config_test.dynamic.entity1 does not have a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
    $this->assertNotContains('config_test:entity2', $dependent_ids, 'config_test.dynamic.entity2 does not have a dependency on itself.');
 | 
			
		||||
    $this->assertContains('config_test:entity3', $dependent_ids, 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity2.');
 | 
			
		||||
    $this->assertContains('config_test:entity4', $dependent_ids, 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity2.');
 | 
			
		||||
 | 
			
		||||
    // Test getting node module's dependencies as configuration dependency
 | 
			
		||||
    // objects.
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependencies('module', ['node']);
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the Node module.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
 | 
			
		||||
 | 
			
		||||
    // Test getting node module's dependencies as configuration dependency
 | 
			
		||||
    // objects after making $entity3 also dependent on node module but $entity1
 | 
			
		||||
    // no longer depend on node module.
 | 
			
		||||
    $entity1->setEnforcedDependencies([])->save();
 | 
			
		||||
    $entity3->setEnforcedDependencies(['module' => ['node'], 'config' => [$entity2->getConfigDependencyName()]])->save();
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependencies('module', ['node']);
 | 
			
		||||
    $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Node module.');
 | 
			
		||||
    $this->assertFalse(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 does not have a dependency on the Node module.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
 | 
			
		||||
 | 
			
		||||
    // Test dependency on a content entity.
 | 
			
		||||
    $entity_test = EntityTest::create([
 | 
			
		||||
      'name' => $this->randomString(),
 | 
			
		||||
      'type' => 'entity_test',
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity_test->save();
 | 
			
		||||
    $entity2->setEnforcedDependencies(['config' => [$entity1->getConfigDependencyName()], 'content' => [$entity_test->getConfigDependencyName()]])->save();
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependencies('content', [$entity_test->getConfigDependencyName()]);
 | 
			
		||||
    $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the content entity.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the content entity.');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the content entity (via entity2).');
 | 
			
		||||
    $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the content entity (via entity3).');
 | 
			
		||||
 | 
			
		||||
    // Create a configuration entity of a different type with the same ID as one
 | 
			
		||||
    // of the entities already created.
 | 
			
		||||
    $alt_storage = $this->container->get('entity_type.manager')->getStorage('config_query_test');
 | 
			
		||||
    $alt_storage->create(['id' => 'entity1', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]])->save();
 | 
			
		||||
    $alt_storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['module' => ['views']]]])->save();
 | 
			
		||||
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependenciesAsEntities('config', [$entity1->getConfigDependencyName()]);
 | 
			
		||||
    $dependent_ids = $this->getDependentIds($dependents);
 | 
			
		||||
    $this->assertNotContains('config_test:entity1', $dependent_ids, 'config_test.dynamic.entity1 does not have a dependency on itself.');
 | 
			
		||||
    $this->assertContains('config_test:entity2', $dependent_ids, 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
    $this->assertContains('config_test:entity3', $dependent_ids, 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
    $this->assertContains('config_test:entity4', $dependent_ids, 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
    $this->assertContains('config_query_test:entity1', $dependent_ids, 'config_query_test.dynamic.entity1 has a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
    $this->assertNotContains('config_query_test:entity2', $dependent_ids, 'config_query_test.dynamic.entity2 does not have a dependency on config_test.dynamic.entity1.');
 | 
			
		||||
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependenciesAsEntities('module', ['node', 'views']);
 | 
			
		||||
    $dependent_ids = $this->getDependentIds($dependents);
 | 
			
		||||
    $this->assertNotContains('config_test:entity1', $dependent_ids, 'config_test.dynamic.entity1 does not have a dependency on Views or Node.');
 | 
			
		||||
    $this->assertNotContains('config_test:entity2', $dependent_ids, 'config_test.dynamic.entity2 does not have a dependency on Views or Node.');
 | 
			
		||||
    $this->assertContains('config_test:entity3', $dependent_ids, 'config_test.dynamic.entity3 has a dependency on Views or Node.');
 | 
			
		||||
    $this->assertContains('config_test:entity4', $dependent_ids, 'config_test.dynamic.entity4 has a dependency on Views or Node.');
 | 
			
		||||
    $this->assertNotContains('config_query_test:entity1', $dependent_ids, 'config_test.query.entity1 does not have a dependency on Views or Node.');
 | 
			
		||||
    $this->assertContains('config_query_test:entity2', $dependent_ids, 'config_test.query.entity2 has a dependency on Views or Node.');
 | 
			
		||||
 | 
			
		||||
    $dependents = $config_manager->findConfigEntityDependenciesAsEntities('module', ['config_test']);
 | 
			
		||||
    $dependent_ids = $this->getDependentIds($dependents);
 | 
			
		||||
    $this->assertContains('config_test:entity1', $dependent_ids, 'config_test.dynamic.entity1 has a dependency on config_test module.');
 | 
			
		||||
    $this->assertContains('config_test:entity2', $dependent_ids, 'config_test.dynamic.entity2 has a dependency on config_test module.');
 | 
			
		||||
    $this->assertContains('config_test:entity3', $dependent_ids, 'config_test.dynamic.entity3 has a dependency on config_test module.');
 | 
			
		||||
    $this->assertContains('config_test:entity4', $dependent_ids, 'config_test.dynamic.entity4 has a dependency on config_test module.');
 | 
			
		||||
    $this->assertContains('config_query_test:entity1', $dependent_ids, 'config_test.query.entity1 has a dependency on config_test module.');
 | 
			
		||||
    $this->assertContains('config_query_test:entity2', $dependent_ids, 'config_test.query.entity2 has a dependency on config_test module.');
 | 
			
		||||
 | 
			
		||||
    // Test the ability to find missing content dependencies.
 | 
			
		||||
    $missing_dependencies = $config_manager->findMissingContentDependencies();
 | 
			
		||||
    $this->assertEquals([], $missing_dependencies);
 | 
			
		||||
 | 
			
		||||
    $expected = [
 | 
			
		||||
      $entity_test->uuid() => [
 | 
			
		||||
        'entity_type' => 'entity_test',
 | 
			
		||||
        'bundle' => $entity_test->bundle(),
 | 
			
		||||
        'uuid' => $entity_test->uuid(),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    // Delete the content entity so that is it now missing.
 | 
			
		||||
    $entity_test->delete();
 | 
			
		||||
    $missing_dependencies = $config_manager->findMissingContentDependencies();
 | 
			
		||||
    $this->assertEquals($expected, $missing_dependencies);
 | 
			
		||||
 | 
			
		||||
    // Add a fake missing dependency to ensure multiple missing dependencies
 | 
			
		||||
    // work.
 | 
			
		||||
    $entity1->setEnforcedDependencies(['content' => [$entity_test->getConfigDependencyName(), 'entity_test:bundle:uuid']])->save();
 | 
			
		||||
    $expected['uuid'] = [
 | 
			
		||||
      'entity_type' => 'entity_test',
 | 
			
		||||
      'bundle' => 'bundle',
 | 
			
		||||
      'uuid' => 'uuid',
 | 
			
		||||
    ];
 | 
			
		||||
    $missing_dependencies = $config_manager->findMissingContentDependencies();
 | 
			
		||||
    $this->assertEquals($expected, $missing_dependencies);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests ConfigManager::uninstall() and config entity dependency management.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigEntityUninstall(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
 | 
			
		||||
    $config_manager = \Drupal::service('config.manager');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')
 | 
			
		||||
      ->getStorage('config_test');
 | 
			
		||||
    // Test dependencies between modules.
 | 
			
		||||
    $entity1 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity1',
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'module' => ['node', 'config_test'],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity1->save();
 | 
			
		||||
    $entity2 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity2',
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [$entity1->getConfigDependencyName()],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity2->save();
 | 
			
		||||
    // Test that doing a config uninstall of the node module deletes entity2
 | 
			
		||||
    // since it is dependent on entity1 which is dependent on the node module.
 | 
			
		||||
    $config_manager->uninstall('module', 'node');
 | 
			
		||||
    $this->assertNull($storage->load('entity1'), 'Entity 1 deleted');
 | 
			
		||||
    $this->assertNull($storage->load('entity2'), 'Entity 2 deleted');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Data provider for self::testConfigEntityUninstallComplex().
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerConfigEntityUninstallComplex() {
 | 
			
		||||
    // Ensure that alphabetical order has no influence on dependency fixing and
 | 
			
		||||
    // removal.
 | 
			
		||||
    return [
 | 
			
		||||
      [['a', 'b', 'c', 'd', 'e']],
 | 
			
		||||
      [['e', 'd', 'c', 'b', 'a']],
 | 
			
		||||
      [['e', 'c', 'd', 'a', 'b']],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests complex configuration entity dependency handling during uninstall.
 | 
			
		||||
   *
 | 
			
		||||
   * Configuration entities can be deleted or updated during module uninstall
 | 
			
		||||
   * because they have dependencies on the module.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $entity_id_suffixes
 | 
			
		||||
   *   The suffixes to add to the 4 entities created by the test.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerConfigEntityUninstallComplex
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigEntityUninstallComplex(array $entity_id_suffixes): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
 | 
			
		||||
    $config_manager = \Drupal::service('config.manager');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')
 | 
			
		||||
      ->getStorage('config_test');
 | 
			
		||||
    // Entity 1 will be deleted because it depends on node.
 | 
			
		||||
    $entity_1 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity_' . $entity_id_suffixes[0],
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'module' => ['node', 'config_test'],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity_1->save();
 | 
			
		||||
 | 
			
		||||
    // Entity 2 has a dependency on entity 1 but it can be fixed because
 | 
			
		||||
    // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
 | 
			
		||||
    // dependency before config entities are deleted.
 | 
			
		||||
    $entity_2 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity_' . $entity_id_suffixes[1],
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [$entity_1->getConfigDependencyName()],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity_2->save();
 | 
			
		||||
 | 
			
		||||
    // Entity 3 will be unchanged because it is dependent on entity 2 which can
 | 
			
		||||
    // be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
 | 
			
		||||
    // not be called for this entity.
 | 
			
		||||
    $entity_3 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity_' . $entity_id_suffixes[2],
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [$entity_2->getConfigDependencyName()],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity_3->save();
 | 
			
		||||
 | 
			
		||||
    // Entity 4's config dependency will be fixed but it will still be deleted
 | 
			
		||||
    // because it also depends on the node module.
 | 
			
		||||
    $entity_4 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity_' . $entity_id_suffixes[3],
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [$entity_1->getConfigDependencyName()],
 | 
			
		||||
            'module' => ['node', 'config_test'],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity_4->save();
 | 
			
		||||
 | 
			
		||||
    // Entity 5 will be fixed because it is dependent on entity 3, which is
 | 
			
		||||
    // unchanged, and entity 1 which will be fixed because
 | 
			
		||||
    // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
 | 
			
		||||
    // dependency.
 | 
			
		||||
    $entity_5 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity_' . $entity_id_suffixes[4],
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [
 | 
			
		||||
              $entity_1->getConfigDependencyName(),
 | 
			
		||||
              $entity_3->getConfigDependencyName(),
 | 
			
		||||
            ],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity_5->save();
 | 
			
		||||
 | 
			
		||||
    // Set a more complicated test where dependencies will be fixed.
 | 
			
		||||
    \Drupal::state()->set('config_test.fix_dependencies', [$entity_1->getConfigDependencyName()]);
 | 
			
		||||
    \Drupal::state()->set('config_test.on_dependency_removal_called', []);
 | 
			
		||||
 | 
			
		||||
    // Do a dry run using
 | 
			
		||||
    // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
 | 
			
		||||
    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
 | 
			
		||||
 | 
			
		||||
    // Assert that \Drupal\config_test\Entity\ConfigTest::onDependencyRemoval()
 | 
			
		||||
    // is called as expected and with the correct dependencies.
 | 
			
		||||
    $called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
 | 
			
		||||
    $this->assertArrayNotHasKey($entity_3->id(), $called, 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
 | 
			
		||||
    $this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id(), $entity_5->id()], array_keys($called), 'The most dependent entities have ConfigEntityInterface::onDependencyRemoval() called first.');
 | 
			
		||||
    $this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_1->id()]);
 | 
			
		||||
    $this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => [], 'theme' => []], $called[$entity_2->id()]);
 | 
			
		||||
    $this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_4->id()]);
 | 
			
		||||
    $this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => [], 'theme' => []], $called[$entity_5->id()]);
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($entity_1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
 | 
			
		||||
    $this->assertEquals($entity_2->uuid(), $config_entities['update'][0]->uuid(), 'Entity 2 will be updated.');
 | 
			
		||||
    $this->assertEquals($entity_3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
 | 
			
		||||
    $this->assertEquals($entity_4->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 4 will be deleted.');
 | 
			
		||||
    $this->assertEquals($entity_5->uuid(), $config_entities['update'][1]->uuid(), 'Entity 5 is updated.');
 | 
			
		||||
 | 
			
		||||
    // Perform the uninstall.
 | 
			
		||||
    $config_manager->uninstall('module', 'node');
 | 
			
		||||
 | 
			
		||||
    // Test that expected actions have been performed.
 | 
			
		||||
    $this->assertNull($storage->load($entity_1->id()), 'Entity 1 deleted');
 | 
			
		||||
    $entity_2 = $storage->load($entity_2->id());
 | 
			
		||||
    $this->assertNotEmpty($entity_2, 'Entity 2 not deleted');
 | 
			
		||||
    $this->assertEquals([], $entity_2->calculateDependencies()->getDependencies()['config'], 'Entity 2 dependencies updated to remove dependency on entity 1.');
 | 
			
		||||
    $entity_3 = $storage->load($entity_3->id());
 | 
			
		||||
    $this->assertNotEmpty($entity_3, 'Entity 3 not deleted');
 | 
			
		||||
    $this->assertEquals([$entity_2->getConfigDependencyName()], $entity_3->calculateDependencies()->getDependencies()['config'], 'Entity 3 still depends on entity 2.');
 | 
			
		||||
    $this->assertNull($storage->load($entity_4->id()), 'Entity 4 deleted');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::uninstall
 | 
			
		||||
   * @covers ::getConfigEntitiesToChangeOnDependencyRemoval
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigEntityUninstallThirdParty(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
 | 
			
		||||
    $config_manager = \Drupal::service('config.manager');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')
 | 
			
		||||
      ->getStorage('config_test');
 | 
			
		||||
    // Entity 1 will be fixed because it only has a dependency via third-party
 | 
			
		||||
    // settings, which are fixable.
 | 
			
		||||
    $entity_1 = $storage->create([
 | 
			
		||||
      'id' => 'entity_1',
 | 
			
		||||
      'dependencies' => [
 | 
			
		||||
        'enforced' => [
 | 
			
		||||
          'module' => ['config_test'],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'third_party_settings' => [
 | 
			
		||||
        'node' => [
 | 
			
		||||
          'foo' => 'bar',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity_1->save();
 | 
			
		||||
 | 
			
		||||
    // Entity 2 has a dependency on entity 1.
 | 
			
		||||
    $entity_2 = $storage->create([
 | 
			
		||||
      'id' => 'entity_2',
 | 
			
		||||
      'dependencies' => [
 | 
			
		||||
        'enforced' => [
 | 
			
		||||
          'config' => [$entity_1->getConfigDependencyName()],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'third_party_settings' => [
 | 
			
		||||
        'node' => [
 | 
			
		||||
          'foo' => 'bar',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity_2->save();
 | 
			
		||||
 | 
			
		||||
    // Entity 3 will be unchanged because it is dependent on entity 2 which can
 | 
			
		||||
    // be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
 | 
			
		||||
    // not be called for this entity.
 | 
			
		||||
    $entity_3 = $storage->create([
 | 
			
		||||
      'id' => 'entity_3',
 | 
			
		||||
      'dependencies' => [
 | 
			
		||||
        'enforced' => [
 | 
			
		||||
          'config' => [$entity_2->getConfigDependencyName()],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity_3->save();
 | 
			
		||||
 | 
			
		||||
    // Entity 4's config dependency will be fixed but it will still be deleted
 | 
			
		||||
    // because it also depends on the node module.
 | 
			
		||||
    $entity_4 = $storage->create([
 | 
			
		||||
      'id' => 'entity_4',
 | 
			
		||||
      'dependencies' => [
 | 
			
		||||
        'enforced' => [
 | 
			
		||||
          'config' => [$entity_1->getConfigDependencyName()],
 | 
			
		||||
          'module' => ['node', 'config_test'],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity_4->save();
 | 
			
		||||
 | 
			
		||||
    \Drupal::state()->set('config_test.fix_dependencies', []);
 | 
			
		||||
    \Drupal::state()->set('config_test.on_dependency_removal_called', []);
 | 
			
		||||
 | 
			
		||||
    // Do a dry run using
 | 
			
		||||
    // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
 | 
			
		||||
    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
 | 
			
		||||
    $config_entity_ids = [
 | 
			
		||||
      'update' => [],
 | 
			
		||||
      'delete' => [],
 | 
			
		||||
      'unchanged' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($config_entities as $type => $config_entities_by_type) {
 | 
			
		||||
      foreach ($config_entities_by_type as $config_entity) {
 | 
			
		||||
        $config_entity_ids[$type][] = $config_entity->id();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $expected = [
 | 
			
		||||
      'update' => [$entity_1->id(), $entity_2->id()],
 | 
			
		||||
      'delete' => [$entity_4->id()],
 | 
			
		||||
      'unchanged' => [$entity_3->id()],
 | 
			
		||||
    ];
 | 
			
		||||
    $this->assertSame($expected, $config_entity_ids);
 | 
			
		||||
 | 
			
		||||
    $called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
 | 
			
		||||
    $this->assertArrayNotHasKey($entity_3->id(), $called, 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
 | 
			
		||||
    $this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id()], array_keys($called), 'The most dependent entities have ConfigEntityInterface::onDependencyRemoval() called first.');
 | 
			
		||||
    $this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_1->id()]);
 | 
			
		||||
    $this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_2->id()]);
 | 
			
		||||
    $this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_4->id()]);
 | 
			
		||||
 | 
			
		||||
    // Perform the uninstall.
 | 
			
		||||
    $config_manager->uninstall('module', 'node');
 | 
			
		||||
 | 
			
		||||
    // Test that expected actions have been performed.
 | 
			
		||||
    $entity_1 = $storage->load($entity_1->id());
 | 
			
		||||
    $this->assertNotEmpty($entity_1, 'Entity 1 not deleted');
 | 
			
		||||
    $this->assertSame($entity_1->getThirdPartySettings('node'), [], 'Entity 1 third party settings updated.');
 | 
			
		||||
    $entity_2 = $storage->load($entity_2->id());
 | 
			
		||||
    $this->assertNotEmpty($entity_2, 'Entity 2 not deleted');
 | 
			
		||||
    $this->assertSame($entity_2->getThirdPartySettings('node'), [], 'Entity 2 third party settings updated.');
 | 
			
		||||
    $this->assertSame($entity_2->calculateDependencies()->getDependencies()['config'], [$entity_1->getConfigDependencyName()], 'Entity 2 still depends on entity 1.');
 | 
			
		||||
    $entity_3 = $storage->load($entity_3->id());
 | 
			
		||||
    $this->assertNotEmpty($entity_3, 'Entity 3 not deleted');
 | 
			
		||||
    $this->assertSame($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
 | 
			
		||||
    $this->assertNull($storage->load($entity_4->id()), 'Entity 4 deleted');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests deleting a configuration entity and dependency management.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigEntityDelete(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
 | 
			
		||||
    $config_manager = \Drupal::service('config.manager');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')->getStorage('config_test');
 | 
			
		||||
    // Test dependencies between configuration entities.
 | 
			
		||||
    $entity1 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity1',
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity1->save();
 | 
			
		||||
    $entity2 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity2',
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [$entity1->getConfigDependencyName()],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity2->save();
 | 
			
		||||
 | 
			
		||||
    // Do a dry run using
 | 
			
		||||
    // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
 | 
			
		||||
    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
 | 
			
		||||
    $this->assertEquals($entity2->uuid(), reset($config_entities['delete'])->uuid(), 'Entity 2 will be deleted.');
 | 
			
		||||
    $this->assertEmpty($config_entities['update'], 'No dependent configuration entities will be updated.');
 | 
			
		||||
    $this->assertEmpty($config_entities['unchanged'], 'No dependent configuration entities will be unchanged.');
 | 
			
		||||
 | 
			
		||||
    // Test that doing a delete of entity1 deletes entity2 since it is dependent
 | 
			
		||||
    // on entity1.
 | 
			
		||||
    $entity1->delete();
 | 
			
		||||
    $this->assertNull($storage->load('entity1'), 'Entity 1 deleted');
 | 
			
		||||
    $this->assertNull($storage->load('entity2'), 'Entity 2 deleted');
 | 
			
		||||
 | 
			
		||||
    // Set a more complicated test where dependencies will be fixed.
 | 
			
		||||
    \Drupal::state()->set('config_test.fix_dependencies', [$entity1->getConfigDependencyName()]);
 | 
			
		||||
 | 
			
		||||
    // Entity1 will be deleted by the test.
 | 
			
		||||
    $entity1 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity1',
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity1->save();
 | 
			
		||||
 | 
			
		||||
    // Entity2 has a dependency on Entity1 but it can be fixed because
 | 
			
		||||
    // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
 | 
			
		||||
    // dependency before config entities are deleted.
 | 
			
		||||
    $entity2 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity2',
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [$entity1->getConfigDependencyName()],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity2->save();
 | 
			
		||||
 | 
			
		||||
    // Entity3 will be unchanged because it is dependent on Entity2 which can
 | 
			
		||||
    // be fixed.
 | 
			
		||||
    $entity3 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity3',
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [$entity2->getConfigDependencyName()],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity3->save();
 | 
			
		||||
 | 
			
		||||
    // Do a dry run using
 | 
			
		||||
    // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
 | 
			
		||||
    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
 | 
			
		||||
    $this->assertEmpty($config_entities['delete'], 'No dependent configuration entities will be deleted.');
 | 
			
		||||
    $this->assertEquals($entity2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.');
 | 
			
		||||
    $this->assertEquals($entity3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
 | 
			
		||||
 | 
			
		||||
    // Perform the uninstall.
 | 
			
		||||
    $entity1->delete();
 | 
			
		||||
 | 
			
		||||
    // Test that expected actions have been performed.
 | 
			
		||||
    $this->assertNull($storage->load('entity1'), 'Entity 1 deleted');
 | 
			
		||||
    $entity2 = $storage->load('entity2');
 | 
			
		||||
    $this->assertNotEmpty($entity2, 'Entity 2 not deleted');
 | 
			
		||||
    $this->assertEquals([], $entity2->calculateDependencies()->getDependencies()['config'], 'Entity 2 dependencies updated to remove dependency on Entity1.');
 | 
			
		||||
    $entity3 = $storage->load('entity3');
 | 
			
		||||
    $this->assertNotEmpty($entity3, 'Entity 3 not deleted');
 | 
			
		||||
    $this->assertEquals($entity3->calculateDependencies()->getDependencies()['config'], [$entity2->getConfigDependencyName()], 'Entity 3 still depends on Entity 2.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests getConfigEntitiesToChangeOnDependencyRemoval() with content entities.
 | 
			
		||||
   *
 | 
			
		||||
   * At the moment there is no runtime code that calculates configuration
 | 
			
		||||
   * dependencies on content entity delete because this calculation is expensive
 | 
			
		||||
   * and all content dependencies are soft. This test ensures that the code
 | 
			
		||||
   * works for content entities.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval()
 | 
			
		||||
   */
 | 
			
		||||
  public function testContentEntityDelete(): void {
 | 
			
		||||
    $this->installEntitySchema('entity_test');
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
 | 
			
		||||
    $config_manager = \Drupal::service('config.manager');
 | 
			
		||||
 | 
			
		||||
    $content_entity = EntityTest::create();
 | 
			
		||||
    $content_entity->save();
 | 
			
		||||
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')->getStorage('config_test');
 | 
			
		||||
    $entity1 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity1',
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'content' => [$content_entity->getConfigDependencyName()],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity1->save();
 | 
			
		||||
    $entity2 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'entity2',
 | 
			
		||||
        'dependencies' => [
 | 
			
		||||
          'enforced' => [
 | 
			
		||||
            'config' => [$entity1->getConfigDependencyName()],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity2->save();
 | 
			
		||||
 | 
			
		||||
    // Create a configuration entity that is not in the dependency chain.
 | 
			
		||||
    $entity3 = $storage->create(['id' => 'entity3']);
 | 
			
		||||
    $entity3->save();
 | 
			
		||||
 | 
			
		||||
    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('content', [$content_entity->getConfigDependencyName()]);
 | 
			
		||||
    $this->assertEquals($entity1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
 | 
			
		||||
    $this->assertEquals($entity2->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 2 will be deleted.');
 | 
			
		||||
    $this->assertEmpty($config_entities['update'], 'No dependencies of the content entity will be updated.');
 | 
			
		||||
    $this->assertEmpty($config_entities['unchanged'], 'No dependencies of the content entity will be unchanged.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that config dependency ordering.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDependencyOrder(): void {
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')->getStorage('config_test');
 | 
			
		||||
    // Test dependencies between modules.
 | 
			
		||||
    $entity1 = $storage->create(['id' => 'entity1']);
 | 
			
		||||
    $entity1->save();
 | 
			
		||||
    // Create additional entities to test dependencies on config entities.
 | 
			
		||||
    $entity2 = $storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]);
 | 
			
		||||
    $entity2->save();
 | 
			
		||||
    $entity3 = $storage->create(['id' => 'entity3', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]);
 | 
			
		||||
    $entity3->save();
 | 
			
		||||
    // Include a role entity to test ordering when dependencies have multiple
 | 
			
		||||
    // entity types.
 | 
			
		||||
    $role = Role::create([
 | 
			
		||||
      'id' => 'test_role',
 | 
			
		||||
      'label' => 'Test role',
 | 
			
		||||
      // This adds an implicit dependency on $entity 2, and hence also $entity1,
 | 
			
		||||
      // to the role.
 | 
			
		||||
      'permissions' => ["permission with {$entity2->getConfigDependencyName()} dependency"],
 | 
			
		||||
    ]);
 | 
			
		||||
    $role->save();
 | 
			
		||||
    $entity4 = $storage->create([
 | 
			
		||||
      'id' => 'entity4',
 | 
			
		||||
      'dependencies' => [
 | 
			
		||||
        'enforced' => [
 | 
			
		||||
          'config' => [
 | 
			
		||||
            // Add dependencies to $entity3 and the role so that the $entity4
 | 
			
		||||
            // should be last to be processed when handling dependency removal.
 | 
			
		||||
            // The role should be processed after $entity1 and $entity2, but
 | 
			
		||||
            // before $entity4.
 | 
			
		||||
            $entity3->getConfigDependencyName(),
 | 
			
		||||
            $role->getConfigDependencyName(),
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity4->save();
 | 
			
		||||
 | 
			
		||||
    // Create scenario where entity1 is deleted, but all the config_test
 | 
			
		||||
    // entities depending on entity1 are fixed instead of being deleted. This
 | 
			
		||||
    // means that entity2 is not deleted, so the role should not lose the
 | 
			
		||||
    // permission depending on entity2.
 | 
			
		||||
    \Drupal::state()->set('config_test.fix_dependencies', ['config_test.dynamic.entity1']);
 | 
			
		||||
    $entity1->delete();
 | 
			
		||||
    $role = Role::load('test_role');
 | 
			
		||||
    $this->assertNotNull($role);
 | 
			
		||||
    $this->assertTrue($role->hasPermission("permission with {$entity2->getConfigDependencyName()} dependency"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a list of identifiers from an array of configuration entities.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependents
 | 
			
		||||
   *   An array of configuration entities.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An array with values of entity_type_id:ID
 | 
			
		||||
   */
 | 
			
		||||
  protected function getDependentIds(array $dependents): array {
 | 
			
		||||
    $dependent_ids = [];
 | 
			
		||||
    foreach ($dependents as $dependent) {
 | 
			
		||||
      $dependent_ids[] = $dependent->getEntityTypeId() . ':' . $dependent->id();
 | 
			
		||||
    }
 | 
			
		||||
    return $dependent_ids;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										190
									
								
								web/core/tests/Drupal/KernelTests/Core/Config/ConfigDiffTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								web/core/tests/Drupal/KernelTests/Core/Config/ConfigDiffTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,190 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Calculating the difference between two sets of configuration.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigDiffTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test', 'system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests calculating the difference between two sets of configuration.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDiff(): void {
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
    $config_name = 'config_test.system';
 | 
			
		||||
    $change_key = 'foo';
 | 
			
		||||
    $remove_key = '404';
 | 
			
		||||
    $add_key = 'biff';
 | 
			
		||||
    $add_data = 'bangPow';
 | 
			
		||||
    $change_data = 'foobar';
 | 
			
		||||
 | 
			
		||||
    // Install the default config.
 | 
			
		||||
    $this->installConfig(['config_test']);
 | 
			
		||||
    $original_data = \Drupal::config($config_name)->get();
 | 
			
		||||
 | 
			
		||||
    // Change a configuration value in sync.
 | 
			
		||||
    $sync_data = $original_data;
 | 
			
		||||
    $sync_data[$change_key] = $change_data;
 | 
			
		||||
    $sync_data[$add_key] = $add_data;
 | 
			
		||||
    $sync->write($config_name, $sync_data);
 | 
			
		||||
 | 
			
		||||
    // Verify that the diff reflects a change.
 | 
			
		||||
    $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
 | 
			
		||||
    $edits = $diff->getEdits();
 | 
			
		||||
    $this->assertYamlEdit($edits, $change_key, 'change',
 | 
			
		||||
      [$change_key . ': ' . $original_data[$change_key]],
 | 
			
		||||
      [$change_key . ': ' . $change_data]);
 | 
			
		||||
 | 
			
		||||
    // Reset data back to original, and remove a key
 | 
			
		||||
    $sync_data = $original_data;
 | 
			
		||||
    unset($sync_data[$remove_key]);
 | 
			
		||||
    $sync->write($config_name, $sync_data);
 | 
			
		||||
 | 
			
		||||
    // Verify that the diff reflects a removed key.
 | 
			
		||||
    $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
 | 
			
		||||
    $edits = $diff->getEdits();
 | 
			
		||||
    $this->assertYamlEdit($edits, $change_key, 'copy');
 | 
			
		||||
    $this->assertYamlEdit($edits, $remove_key, 'delete',
 | 
			
		||||
      [$remove_key . ': ' . $original_data[$remove_key]],
 | 
			
		||||
      FALSE
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Reset data back to original and add a key
 | 
			
		||||
    $sync_data = $original_data;
 | 
			
		||||
    $sync_data[$add_key] = $add_data;
 | 
			
		||||
    $sync->write($config_name, $sync_data);
 | 
			
		||||
 | 
			
		||||
    // Verify that the diff reflects an added key.
 | 
			
		||||
    $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
 | 
			
		||||
    $edits = $diff->getEdits();
 | 
			
		||||
    $this->assertYamlEdit($edits, $change_key, 'copy');
 | 
			
		||||
    $this->assertYamlEdit($edits, $add_key, 'add', FALSE, [$add_key . ': ' . $add_data]);
 | 
			
		||||
 | 
			
		||||
    // Test diffing a renamed config entity.
 | 
			
		||||
    $test_entity_id = $this->randomMachineName();
 | 
			
		||||
    $test_entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([
 | 
			
		||||
      'id' => $test_entity_id,
 | 
			
		||||
      'label' => $this->randomMachineName(),
 | 
			
		||||
    ]);
 | 
			
		||||
    $test_entity->save();
 | 
			
		||||
    $data = $active->read('config_test.dynamic.' . $test_entity_id);
 | 
			
		||||
    $sync->write('config_test.dynamic.' . $test_entity_id, $data);
 | 
			
		||||
    $config_name = 'config_test.dynamic.' . $test_entity_id;
 | 
			
		||||
    $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name, $config_name);
 | 
			
		||||
    // Prove the fields match.
 | 
			
		||||
    $edits = $diff->getEdits();
 | 
			
		||||
    $this->assertEquals('copy', $edits[0]->type, 'The first item in the diff is a copy.');
 | 
			
		||||
    $this->assertCount(1, $edits, 'There is one item in the diff');
 | 
			
		||||
 | 
			
		||||
    // Rename the entity.
 | 
			
		||||
    $new_test_entity_id = $this->randomMachineName();
 | 
			
		||||
    $test_entity->set('id', $new_test_entity_id);
 | 
			
		||||
    $test_entity->save();
 | 
			
		||||
 | 
			
		||||
    $diff = \Drupal::service('config.manager')->diff($active, $sync, 'config_test.dynamic.' . $new_test_entity_id, $config_name);
 | 
			
		||||
    $edits = $diff->getEdits();
 | 
			
		||||
    $this->assertYamlEdit($edits, 'uuid', 'copy');
 | 
			
		||||
    $this->assertYamlEdit($edits, 'id', 'change',
 | 
			
		||||
      ['id: ' . $new_test_entity_id],
 | 
			
		||||
      ['id: ' . $test_entity_id]);
 | 
			
		||||
    $this->assertYamlEdit($edits, 'label', 'copy');
 | 
			
		||||
    $this->assertEquals('copy', $edits[2]->type, 'The third item in the diff is a copy.');
 | 
			
		||||
    $this->assertCount(3, $edits, 'There are three items in the diff.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests calculating the difference between two sets of config collections.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCollectionDiff(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active */
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $sync */
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
    $active_test_collection = $active->createCollection('test');
 | 
			
		||||
    $sync_test_collection = $sync->createCollection('test');
 | 
			
		||||
 | 
			
		||||
    $config_name = 'config_test.test';
 | 
			
		||||
    $data = ['foo' => 'bar'];
 | 
			
		||||
 | 
			
		||||
    $active->write($config_name, $data);
 | 
			
		||||
    $sync->write($config_name, $data);
 | 
			
		||||
    $active_test_collection->write($config_name, $data);
 | 
			
		||||
    $sync_test_collection->write($config_name, ['foo' => 'baz']);
 | 
			
		||||
 | 
			
		||||
    // Test the fields match in the default collection diff.
 | 
			
		||||
    $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
 | 
			
		||||
    $edits = $diff->getEdits();
 | 
			
		||||
    $this->assertEquals('copy', $edits[0]->type, 'The first item in the diff is a copy.');
 | 
			
		||||
    $this->assertCount(1, $edits, 'There is one item in the diff');
 | 
			
		||||
 | 
			
		||||
    // Test that the differences are detected when diffing the collection.
 | 
			
		||||
    $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name, NULL, 'test');
 | 
			
		||||
    $edits = $diff->getEdits();
 | 
			
		||||
    $this->assertYamlEdit($edits, 'foo', 'change', ['foo: bar'], ['foo: baz']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper method to test that an edit is found in the diff of two storages.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $edits
 | 
			
		||||
   *   A list of edits.
 | 
			
		||||
   * @param string $field
 | 
			
		||||
   *   The field key that is being asserted.
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   The type of edit that is being asserted.
 | 
			
		||||
   * @param mixed $orig
 | 
			
		||||
   *   (optional) The original value of the edit. If not supplied, assertion
 | 
			
		||||
   *   is skipped.
 | 
			
		||||
   * @param mixed $closing
 | 
			
		||||
   *   (optional) The closing value of the edit. If not supplied, assertion
 | 
			
		||||
   *   is skipped.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertYamlEdit(array $edits, string $field, string $type, $orig = NULL, $closing = NULL): void {
 | 
			
		||||
    $match = FALSE;
 | 
			
		||||
    foreach ($edits as $edit) {
 | 
			
		||||
      // Choose which section to search for the field.
 | 
			
		||||
      $haystack = $type == 'add' ? $edit->closing : $edit->orig;
 | 
			
		||||
      // Look through each line and try and find the key.
 | 
			
		||||
      if (is_array($haystack)) {
 | 
			
		||||
        foreach ($haystack as $item) {
 | 
			
		||||
          if (str_starts_with($item, $field . ':')) {
 | 
			
		||||
            $match = TRUE;
 | 
			
		||||
            // Assert that the edit is of the type specified.
 | 
			
		||||
            $this->assertEquals($type, $edit->type, "The {$field} item in the diff is a {$type}");
 | 
			
		||||
            // If an original value was given, assert that it matches.
 | 
			
		||||
            if (isset($orig)) {
 | 
			
		||||
              $this->assertSame($orig, $edit->orig, "The original value for key '{$field}' is correct.");
 | 
			
		||||
            }
 | 
			
		||||
            // If a closing value was given, assert that it matches.
 | 
			
		||||
            if (isset($closing)) {
 | 
			
		||||
              $this->assertSame($closing, $edit->closing, "The closing value for key '{$field}' is correct.");
 | 
			
		||||
            }
 | 
			
		||||
            // Break out of the search entirely.
 | 
			
		||||
            break 2;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If we didn't match anything, fail.
 | 
			
		||||
    if (!$match) {
 | 
			
		||||
      $this->fail("$field edit was not matched");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the listing of configuration entities.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigEntityNormalizeTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(static::$modules);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the normalization of configuration data when saved.
 | 
			
		||||
   */
 | 
			
		||||
  public function testNormalize(): void {
 | 
			
		||||
    $config_entity = \Drupal::entityTypeManager()->getStorage('config_test')->create(['id' => 'system', 'label' => 'foobar', 'weight' => 1]);
 | 
			
		||||
    $config_entity->save();
 | 
			
		||||
 | 
			
		||||
    // Modify stored config entity, this is comparable with a schema change.
 | 
			
		||||
    $config = $this->config('config_test.dynamic.system');
 | 
			
		||||
    $data = [
 | 
			
		||||
      'label' => 'foobar',
 | 
			
		||||
      'additional_key' => TRUE,
 | 
			
		||||
    ] + $config->getRawData();
 | 
			
		||||
    $config->setData($data)->save();
 | 
			
		||||
    $this->assertNotSame($config_entity->toArray(), $config->getRawData(), 'Stored config entity is not is equivalent to config schema.');
 | 
			
		||||
    $config_entity = \Drupal::entityTypeManager()->getStorage('config_test')->load('system');
 | 
			
		||||
    $config_entity->save();
 | 
			
		||||
 | 
			
		||||
    $config = $this->config('config_test.dynamic.system');
 | 
			
		||||
    $this->assertSame($config_entity->toArray(), $config->getRawData(), 'Stored config entity is equivalent to config schema.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,121 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\config_entity_static_cache_test\ConfigOverrider;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the entity static cache when used by config entities.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigEntityStaticCacheTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'config_test',
 | 
			
		||||
    'config_entity_static_cache_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The type ID of the entity under test.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeId;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity ID of the entity under test.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityId;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->entityTypeId = 'config_test';
 | 
			
		||||
    $this->entityId = 'test_1';
 | 
			
		||||
    $this->container->get('entity_type.manager')
 | 
			
		||||
      ->getStorage($this->entityTypeId)
 | 
			
		||||
      ->create(['id' => $this->entityId, 'label' => 'Original label'])
 | 
			
		||||
      ->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the static cache is working.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCacheHit(): void {
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')
 | 
			
		||||
      ->getStorage($this->entityTypeId);
 | 
			
		||||
    $entity_1 = $storage->load($this->entityId);
 | 
			
		||||
    $entity_2 = $storage->load($this->entityId);
 | 
			
		||||
    // config_entity_static_cache_test_config_test_load() sets _loadStamp to a
 | 
			
		||||
    // random string. If they match, it means $entity_2 was retrieved from the
 | 
			
		||||
    // static cache rather than going through a separate load sequence.
 | 
			
		||||
    $this->assertSame($entity_1->_loadStamp, $entity_2->_loadStamp);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the static cache is reset on entity save and delete.
 | 
			
		||||
   */
 | 
			
		||||
  public function testReset(): void {
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')
 | 
			
		||||
      ->getStorage($this->entityTypeId);
 | 
			
		||||
    $entity = $storage->load($this->entityId);
 | 
			
		||||
 | 
			
		||||
    // Ensure loading after a save retrieves the updated entity rather than an
 | 
			
		||||
    // obsolete cached one.
 | 
			
		||||
    $entity->label = 'New label';
 | 
			
		||||
    $entity->save();
 | 
			
		||||
    $entity = $storage->load($this->entityId);
 | 
			
		||||
    $this->assertSame('New label', $entity->label);
 | 
			
		||||
 | 
			
		||||
    // Ensure loading after a delete retrieves NULL rather than an obsolete
 | 
			
		||||
    // cached one.
 | 
			
		||||
    $entity->delete();
 | 
			
		||||
    $this->assertNull($storage->load($this->entityId));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the static cache is sensitive to config overrides.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigOverride(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
 | 
			
		||||
    $storage = \Drupal::entityTypeManager()->getStorage($this->entityTypeId);
 | 
			
		||||
    // Prime the cache prior to adding a config override.
 | 
			
		||||
    $storage->load($this->entityId);
 | 
			
		||||
 | 
			
		||||
    // Add the config override, and ensure that what is loaded is correct
 | 
			
		||||
    // despite the prior cache priming.
 | 
			
		||||
    \Drupal::configFactory()->addOverride(new ConfigOverrider());
 | 
			
		||||
    $entity_override = $storage->load($this->entityId);
 | 
			
		||||
    $this->assertSame('Overridden label', $entity_override->label);
 | 
			
		||||
 | 
			
		||||
    // Load override free to ensure that loading the config entity again does
 | 
			
		||||
    // not return the overridden value.
 | 
			
		||||
    $entity_no_override = $storage->loadOverrideFree($this->entityId);
 | 
			
		||||
    $this->assertNotSame('Overridden label', $entity_no_override->label);
 | 
			
		||||
    $this->assertNotSame($entity_override->_loadStamp, $entity_no_override->_loadStamp);
 | 
			
		||||
 | 
			
		||||
    // Reload the entity and ensure the cache is used.
 | 
			
		||||
    $this->assertSame($entity_no_override->_loadStamp, $storage->loadOverrideFree($this->entityId)->_loadStamp);
 | 
			
		||||
 | 
			
		||||
    // Enable overrides and reload the entity and ensure the cache is used.
 | 
			
		||||
    $this->assertSame($entity_override->_loadStamp, $storage->load($this->entityId)->_loadStamp);
 | 
			
		||||
 | 
			
		||||
    // Reset the cache, ensure that all variations of this entity are
 | 
			
		||||
    // invalidated.
 | 
			
		||||
    $storage->resetCache([$this->entityId]);
 | 
			
		||||
    $this->assertNotSame($entity_no_override->_loadStamp, $storage->loadOverrideFree($this->entityId)->_loadStamp);
 | 
			
		||||
    $this->assertNotSame($entity_override->_loadStamp, $storage->load($this->entityId)->_loadStamp);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests configuration entity status functionality.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigEntityStatusTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the enabling/disabling of entities.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCRUD(): void {
 | 
			
		||||
    $entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([
 | 
			
		||||
      'id' => $this->randomMachineName(),
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->assertTrue($entity->status(), 'Default status is enabled.');
 | 
			
		||||
    $entity->save();
 | 
			
		||||
    $this->assertTrue($entity->status(), 'Status is enabled after saving.');
 | 
			
		||||
 | 
			
		||||
    $entity->disable()->save();
 | 
			
		||||
    $this->assertFalse($entity->status(), 'Entity is disabled after disabling.');
 | 
			
		||||
 | 
			
		||||
    $entity->enable()->save();
 | 
			
		||||
    $this->assertTrue($entity->status(), 'Entity is enabled after enabling.');
 | 
			
		||||
 | 
			
		||||
    $entity = \Drupal::entityTypeManager()->getStorage('config_test')->load($entity->id());
 | 
			
		||||
    $this->assertTrue($entity->status(), 'Status is enabled after reload.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,67 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ConfigDuplicateUUIDException;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests configuration entity storage.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigEntityStorageTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests creating configuration entities with changed UUIDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function testUUIDConflict(): void {
 | 
			
		||||
    $entity_type = 'config_test';
 | 
			
		||||
    $id = 'test_1';
 | 
			
		||||
    // Load the original configuration entity.
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')
 | 
			
		||||
      ->getStorage($entity_type);
 | 
			
		||||
    $storage->create(['id' => $id])->save();
 | 
			
		||||
    $entity = $storage->load($id);
 | 
			
		||||
 | 
			
		||||
    $original_properties = $entity->toArray();
 | 
			
		||||
 | 
			
		||||
    // Override with a new UUID and try to save.
 | 
			
		||||
    $new_uuid = $this->container->get('uuid')->generate();
 | 
			
		||||
    $entity->set('uuid', $new_uuid);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $entity->save();
 | 
			
		||||
      $this->fail('Exception thrown when attempting to save a configuration entity with a UUID that does not match the existing UUID.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigDuplicateUUIDException) {
 | 
			
		||||
      // Expected exception; just continue testing.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Ensure that the config entity was not corrupted.
 | 
			
		||||
    $entity = $storage->loadUnchanged($entity->id());
 | 
			
		||||
    $this->assertSame($original_properties, $entity->toArray());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the hasData() method for config entity storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers \Drupal\Core\Config\Entity\ConfigEntityStorage::hasData
 | 
			
		||||
   */
 | 
			
		||||
  public function testHasData(): void {
 | 
			
		||||
    $storage = \Drupal::entityTypeManager()->getStorage('config_test');
 | 
			
		||||
    $this->assertFalse($storage->hasData());
 | 
			
		||||
 | 
			
		||||
    // Add a test config entity and check again.
 | 
			
		||||
    $storage->create(['id' => $this->randomMachineName()])->save();
 | 
			
		||||
    $this->assertTrue($storage->hasData());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,111 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unit tests for configuration entity base methods.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigEntityUnitTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Exempt from strict schema checking.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\Development\ConfigSchemaChecker
 | 
			
		||||
   */
 | 
			
		||||
  protected $strictConfigSchema = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The config_test entity storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $storage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->storage = $this->container->get('entity_type.manager')->getStorage('config_test');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests storage methods.
 | 
			
		||||
   */
 | 
			
		||||
  public function testStorageMethods(): void {
 | 
			
		||||
    $entity_type = \Drupal::entityTypeManager()->getDefinition('config_test');
 | 
			
		||||
 | 
			
		||||
    // Test the static extractID() method.
 | 
			
		||||
    $expected_id = 'test_id';
 | 
			
		||||
    $config_name = $entity_type->getConfigPrefix() . '.' . $expected_id;
 | 
			
		||||
    $storage = $this->storage;
 | 
			
		||||
    $this->assertSame($expected_id, $storage::getIDFromConfigName($config_name, $entity_type->getConfigPrefix()));
 | 
			
		||||
 | 
			
		||||
    // Create three entities, two with the same style.
 | 
			
		||||
    $style = $this->randomMachineName(8);
 | 
			
		||||
    for ($i = 0; $i < 2; $i++) {
 | 
			
		||||
      $entity = $this->storage->create([
 | 
			
		||||
        'id' => $this->randomMachineName(),
 | 
			
		||||
        'label' => $this->randomString(),
 | 
			
		||||
        'style' => $style,
 | 
			
		||||
      ]);
 | 
			
		||||
      $entity->save();
 | 
			
		||||
    }
 | 
			
		||||
    $entity = $this->storage->create([
 | 
			
		||||
      'id' => $this->randomMachineName(),
 | 
			
		||||
      'label' => $this->randomString(),
 | 
			
		||||
      // Use a different length for the entity to ensure uniqueness.
 | 
			
		||||
      'style' => $this->randomMachineName(9),
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity->save();
 | 
			
		||||
 | 
			
		||||
    // Ensure that the configuration entity can be loaded by UUID.
 | 
			
		||||
    $entity_loaded_by_uuid = \Drupal::service('entity.repository')->loadEntityByUuid($entity_type->id(), $entity->uuid());
 | 
			
		||||
    if (!$entity_loaded_by_uuid) {
 | 
			
		||||
      $this->fail(sprintf("Failed to load '%s' entity ID '%s' by UUID '%s'.", $entity_type->id(), $entity->id(), $entity->uuid()));
 | 
			
		||||
    }
 | 
			
		||||
    // Compare UUIDs as the objects are not identical since
 | 
			
		||||
    // $entity->enforceIsNew is FALSE and $entity_loaded_by_uuid->enforceIsNew
 | 
			
		||||
    // is NULL.
 | 
			
		||||
    $this->assertSame($entity->uuid(), $entity_loaded_by_uuid->uuid());
 | 
			
		||||
 | 
			
		||||
    $entities = $this->storage->loadByProperties();
 | 
			
		||||
    $this->assertCount(3, $entities, 'Three entities are loaded when no properties are specified.');
 | 
			
		||||
 | 
			
		||||
    $entities = $this->storage->loadByProperties(['style' => $style]);
 | 
			
		||||
    $this->assertCount(2, $entities, 'Two entities are loaded when the style property is specified.');
 | 
			
		||||
 | 
			
		||||
    // Assert that both returned entities have a matching style property.
 | 
			
		||||
    foreach ($entities as $entity) {
 | 
			
		||||
      $this->assertSame($style, $entity->get('style'), 'The loaded entity has the correct style value specified.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test that schema type enforcement can be overridden by trusting the data.
 | 
			
		||||
    $entity = $this->storage->create([
 | 
			
		||||
      'id' => $this->randomMachineName(),
 | 
			
		||||
      'label' => $this->randomString(),
 | 
			
		||||
      'style' => 999,
 | 
			
		||||
    ]);
 | 
			
		||||
    $entity->save();
 | 
			
		||||
    $this->assertSame('999', $entity->style);
 | 
			
		||||
    $entity->style = 999;
 | 
			
		||||
    $entity->trustData()->save();
 | 
			
		||||
    $this->assertSame(999, $entity->style);
 | 
			
		||||
    $entity->save();
 | 
			
		||||
    $this->assertSame('999', $entity->style);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,736 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\NestedArray;
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
 | 
			
		||||
use Drupal\Core\Config\Schema\Mapping;
 | 
			
		||||
use Drupal\Core\Config\TypedConfigManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 | 
			
		||||
use Drupal\Core\Entity\Plugin\DataType\ConfigEntityAdapter;
 | 
			
		||||
use Drupal\Core\Language\LanguageInterface;
 | 
			
		||||
use Drupal\Core\Language\LanguageManager;
 | 
			
		||||
use Drupal\Core\TypedData\Plugin\DataType\LanguageReference;
 | 
			
		||||
use Drupal\Core\TypedData\TypedDataInterface;
 | 
			
		||||
use Drupal\Core\Validation\Plugin\Validation\Constraint\FullyValidatableConstraint;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\language\Entity\ConfigurableLanguage;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore kthxbai
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base class for testing validation of config entities.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 * @group Validation
 | 
			
		||||
 */
 | 
			
		||||
abstract class ConfigEntityValidationTestBase extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The config entity being tested.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\Entity\ConfigEntityInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected ConfigEntityInterface $entity;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether a config entity of this type has a label.
 | 
			
		||||
   *
 | 
			
		||||
   * Most config entity types ensure their entities have a label. But a few do
 | 
			
		||||
   * not, typically highly abstract/very low level config entities without a
 | 
			
		||||
   * strong UI presence. For example: REST resource configuration entities and
 | 
			
		||||
   * entity view displays.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Entity\EntityInterface::label()
 | 
			
		||||
   */
 | 
			
		||||
  protected bool $hasLabel = TRUE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The config entity mapping properties with >=1 required keys.
 | 
			
		||||
   *
 | 
			
		||||
   * All top-level properties of a config entity are guaranteed to be defined
 | 
			
		||||
   * (since they are defined as properties on the corresponding PHP class). That
 | 
			
		||||
   * is why they can never trigger "required key" validation errors. Only for
 | 
			
		||||
   * non-top-level properties can such validation errors be triggered, and hence
 | 
			
		||||
   * that is only possible on top-level properties of `type: mapping`.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   * @see \Drupal\Core\Config\Entity\ConfigEntityType::getPropertiesToExport()
 | 
			
		||||
   * @see ::testRequiredPropertyKeysMissing()
 | 
			
		||||
   * @see \Drupal\Core\Validation\Plugin\Validation\Constraint\ValidKeysConstraintValidator
 | 
			
		||||
   */
 | 
			
		||||
  protected static array $propertiesWithRequiredKeys = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The config entity properties whose values are optional (set to NULL).
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   * @see \Drupal\Core\Config\Entity\ConfigEntityTypeInterface::getPropertiesToExport()
 | 
			
		||||
   * @see ::testRequiredPropertyValuesMissing()
 | 
			
		||||
   */
 | 
			
		||||
  protected static array $propertiesWithOptionalValues = [
 | 
			
		||||
    '_core',
 | 
			
		||||
    'third_party_settings',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig('system');
 | 
			
		||||
 | 
			
		||||
    // Install Stark so we can add a legitimately installed theme to config
 | 
			
		||||
    // dependencies.
 | 
			
		||||
    $this->container->get('theme_installer')->install(['stark']);
 | 
			
		||||
    $this->container = $this->container->get('kernel')->getContainer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures that the entity created in ::setUp() has no validation errors.
 | 
			
		||||
   */
 | 
			
		||||
  public function testEntityIsValid(): void {
 | 
			
		||||
    $this->assertInstanceOf(ConfigEntityInterface::class, $this->entity);
 | 
			
		||||
    $this->assertValidationErrors([]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the validation constraints applied to the entity's ID.
 | 
			
		||||
   *
 | 
			
		||||
   * If the entity type does not define an ID key, the test will fail. If an ID
 | 
			
		||||
   * key is defined but is not using the `machine_name` data type, the test will
 | 
			
		||||
   * be skipped.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array[]
 | 
			
		||||
   *   The validation constraint configuration applied to the entity's ID.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getMachineNameConstraints(): array {
 | 
			
		||||
    $id_key = $this->entity->getEntityType()->getKey('id');
 | 
			
		||||
    $this->assertNotEmpty($id_key, "The entity under test does not define an ID key.");
 | 
			
		||||
 | 
			
		||||
    $data_definition = $this->entity->getTypedData()
 | 
			
		||||
      ->get($id_key)
 | 
			
		||||
      ->getDataDefinition();
 | 
			
		||||
    if ($data_definition->getDataType() === 'machine_name') {
 | 
			
		||||
      return $data_definition->getConstraints();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $this->markTestSkipped("The entity's ID key does not use the machine_name data type.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Data provider for ::testInvalidMachineNameCharacters().
 | 
			
		||||
   *
 | 
			
		||||
   * @return array[]
 | 
			
		||||
   *   The test cases.
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerInvalidMachineNameCharacters(): array {
 | 
			
		||||
    return [
 | 
			
		||||
      'INVALID: space separated' => ['space separated', FALSE],
 | 
			
		||||
      'INVALID: dash separated' => ['dash-separated', FALSE],
 | 
			
		||||
      'INVALID: uppercase letters' => ['Uppercase_Letters', FALSE],
 | 
			
		||||
      'INVALID: period separated' => ['period.separated', FALSE],
 | 
			
		||||
      'VALID: underscore separated' => ['underscore_separated', TRUE],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the entity's ID is tested for invalid characters.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $machine_name
 | 
			
		||||
   *   A machine name to test.
 | 
			
		||||
   * @param bool $is_expected_to_be_valid
 | 
			
		||||
   *   Whether this machine name is expected to be considered valid.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerInvalidMachineNameCharacters
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidMachineNameCharacters(string $machine_name, bool $is_expected_to_be_valid): void {
 | 
			
		||||
    $constraints = $this->getMachineNameConstraints();
 | 
			
		||||
 | 
			
		||||
    $this->assertNotEmpty($constraints['Regex']);
 | 
			
		||||
    $this->assertIsArray($constraints['Regex']);
 | 
			
		||||
    $this->assertArrayHasKey('pattern', $constraints['Regex']);
 | 
			
		||||
    $this->assertIsString($constraints['Regex']['pattern']);
 | 
			
		||||
    $this->assertArrayHasKey('message', $constraints['Regex']);
 | 
			
		||||
    $this->assertIsString($constraints['Regex']['message']);
 | 
			
		||||
 | 
			
		||||
    $id_key = $this->entity->getEntityType()->getKey('id');
 | 
			
		||||
    if ($is_expected_to_be_valid) {
 | 
			
		||||
      $expected_errors = [];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $expected_errors = [$id_key => sprintf('The <em class="placeholder">"%s"</em> machine name is not valid.', $machine_name)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Config entity IDs are immutable by default.
 | 
			
		||||
    $expected_errors[''] = "The '$id_key' property cannot be changed.";
 | 
			
		||||
 | 
			
		||||
    $this->entity->set($id_key, $machine_name);
 | 
			
		||||
    $this->assertValidationErrors($expected_errors);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the entity ID's length is validated if it is a machine name.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMachineNameLength(string $prefix = ''): void {
 | 
			
		||||
    $constraints = $this->getMachineNameConstraints();
 | 
			
		||||
 | 
			
		||||
    $max_length = $constraints['Length']['max'];
 | 
			
		||||
    $this->assertIsInt($max_length);
 | 
			
		||||
    $this->assertGreaterThan(0, $max_length);
 | 
			
		||||
 | 
			
		||||
    $id_key = $this->entity->getEntityType()->getKey('id');
 | 
			
		||||
    $expected_errors = [
 | 
			
		||||
      $id_key => 'This value is too long. It should have <em class="placeholder">' . $max_length . '</em> characters or less.',
 | 
			
		||||
      // Config entity IDs are immutable by default.
 | 
			
		||||
      '' => "The '$id_key' property cannot be changed.",
 | 
			
		||||
    ];
 | 
			
		||||
    $this->entity->set($id_key, $prefix . $this->randomMachineName($max_length + 2));
 | 
			
		||||
    $this->assertValidationErrors($expected_errors);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Data provider for ::testConfigDependenciesValidation().
 | 
			
		||||
   *
 | 
			
		||||
   * @return array[]
 | 
			
		||||
   *   The test cases.
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerConfigDependenciesValidation(): array {
 | 
			
		||||
    return [
 | 
			
		||||
      'valid dependency types' => [
 | 
			
		||||
        [
 | 
			
		||||
          'config' => ['system.site'],
 | 
			
		||||
          'content' => ['node:some-random-uuid'],
 | 
			
		||||
          'module' => ['system'],
 | 
			
		||||
          'theme' => ['stark'],
 | 
			
		||||
        ],
 | 
			
		||||
        [],
 | 
			
		||||
      ],
 | 
			
		||||
      'unknown dependency type' => [
 | 
			
		||||
        [
 | 
			
		||||
          'fun_stuff' => ['star-trek.deep-space-nine'],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.fun_stuff' => "'fun_stuff' is not a supported key.",
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'empty string in config dependencies' => [
 | 
			
		||||
        [
 | 
			
		||||
          'config' => [''],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.config.0' => [
 | 
			
		||||
            'This value should not be blank.',
 | 
			
		||||
            "The '' config does not exist.",
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'non-existent config dependency' => [
 | 
			
		||||
        [
 | 
			
		||||
          'config' => ['fake_settings'],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.config.0' => "The 'fake_settings' config does not exist.",
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'empty string in module dependencies' => [
 | 
			
		||||
        [
 | 
			
		||||
          'module' => [''],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.module.0' => [
 | 
			
		||||
            'This value should not be blank.',
 | 
			
		||||
            "Module '' is not installed.",
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'invalid module dependency' => [
 | 
			
		||||
        [
 | 
			
		||||
          'module' => ['invalid-module-name'],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.module.0' => [
 | 
			
		||||
            'This value is not a valid extension name.',
 | 
			
		||||
            "Module 'invalid-module-name' is not installed.",
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'non-installed module dependency' => [
 | 
			
		||||
        [
 | 
			
		||||
          'module' => ['bad_judgment'],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.module.0' => "Module 'bad_judgment' is not installed.",
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'empty string in theme dependencies' => [
 | 
			
		||||
        [
 | 
			
		||||
          'theme' => [''],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.theme.0' => [
 | 
			
		||||
            'This value should not be blank.',
 | 
			
		||||
            "Theme '' is not installed.",
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'invalid theme dependency' => [
 | 
			
		||||
        [
 | 
			
		||||
          'theme' => ['invalid-theme-name'],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.theme.0' => [
 | 
			
		||||
            'This value is not a valid extension name.',
 | 
			
		||||
            "Theme 'invalid-theme-name' is not installed.",
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'non-installed theme dependency' => [
 | 
			
		||||
        [
 | 
			
		||||
          'theme' => ['ugly_theme'],
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'dependencies.theme.0' => "Theme 'ugly_theme' is not installed.",
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests validation of config dependencies.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array[] $dependencies
 | 
			
		||||
   *   The dependencies that should be added to the config entity under test.
 | 
			
		||||
   * @param array<string, string|string[]> $expected_messages
 | 
			
		||||
   *   The expected validation error messages. Keys are property paths, values
 | 
			
		||||
   *   are the expected messages: a string if a single message is expected, an
 | 
			
		||||
   *   array of strings if multiple are expected.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerConfigDependenciesValidation
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigDependenciesValidation(array $dependencies, array $expected_messages): void {
 | 
			
		||||
    // Add the dependencies we were given to the dependencies that may already
 | 
			
		||||
    // exist in the entity.
 | 
			
		||||
    $dependencies = NestedArray::mergeDeep($dependencies, $this->entity->getDependencies());
 | 
			
		||||
 | 
			
		||||
    $this->entity->set('dependencies', $dependencies);
 | 
			
		||||
    $this->assertValidationErrors($expected_messages);
 | 
			
		||||
 | 
			
		||||
    // Enforce these dependencies, and ensure we get the same results.
 | 
			
		||||
    $this->entity->set('dependencies', [
 | 
			
		||||
      'enforced' => $dependencies,
 | 
			
		||||
    ]);
 | 
			
		||||
    // We now expect validation errors not at `dependencies.module.0`, but at
 | 
			
		||||
    // `dependencies.enforced.module.0`. So reuse the same messages, but perform
 | 
			
		||||
    // string replacement in the keys.
 | 
			
		||||
    $expected_enforced_messages = array_combine(
 | 
			
		||||
      str_replace('dependencies', 'dependencies.enforced', array_keys($expected_messages)),
 | 
			
		||||
      array_values($expected_messages),
 | 
			
		||||
    );
 | 
			
		||||
    $this->assertValidationErrors($expected_enforced_messages);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests validation of config entity's label.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Entity\EntityInterface::label()
 | 
			
		||||
   * @see \Drupal\Core\Entity\EntityBase::label()
 | 
			
		||||
   */
 | 
			
		||||
  public function testLabelValidation(): void {
 | 
			
		||||
    // Some entity types do not have a label.
 | 
			
		||||
    if (!$this->hasLabel) {
 | 
			
		||||
      $this->markTestSkipped();
 | 
			
		||||
    }
 | 
			
		||||
    if ($this->entity->getEntityType()->getKey('label') === $this->entity->getEntityType()->getKey('id')) {
 | 
			
		||||
      $this->markTestSkipped('This entity type uses the ID as the label; an entity without a label is hence impossible.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static::setLabel($this->entity, "Multi\nLine");
 | 
			
		||||
    $this->assertValidationErrors([$this->entity->getEntityType()->getKey('label') => "Labels are not allowed to span multiple lines or contain control characters."]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the label of the given config entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
 | 
			
		||||
   *   The config entity to modify.
 | 
			
		||||
   * @param string $label
 | 
			
		||||
   *   The label to set.
 | 
			
		||||
   *
 | 
			
		||||
   * @see ::testLabelValidation()
 | 
			
		||||
   */
 | 
			
		||||
  protected static function setLabel(ConfigEntityInterface $entity, string $label): void {
 | 
			
		||||
    $label_property = $entity->getEntityType()->getKey('label');
 | 
			
		||||
    if ($label_property === FALSE) {
 | 
			
		||||
      throw new \LogicException(sprintf('Override %s to allow testing a %s without a label.', __METHOD__, (string) $entity->getEntityType()->getSingularLabel()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity->set($label_property, $label);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts a set of validation errors is raised when the entity is validated.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array<string, string|string[]> $expected_messages
 | 
			
		||||
   *   The expected validation error messages. Keys are property paths, values
 | 
			
		||||
   *   are the expected messages: a string if a single message is expected, an
 | 
			
		||||
   *   array of strings if multiple are expected.
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertValidationErrors(array $expected_messages): void {
 | 
			
		||||
    /** @var \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data */
 | 
			
		||||
    $typed_data = $this->container->get('typed_data_manager');
 | 
			
		||||
    $definition = $typed_data->createDataDefinition('entity:' . $this->entity->getEntityTypeId());
 | 
			
		||||
    $violations = $typed_data->create($definition, $this->entity)->validate();
 | 
			
		||||
 | 
			
		||||
    $actual_messages = [];
 | 
			
		||||
    foreach ($violations as $violation) {
 | 
			
		||||
      $property_path = $violation->getPropertyPath();
 | 
			
		||||
 | 
			
		||||
      if (!isset($actual_messages[$property_path])) {
 | 
			
		||||
        $actual_messages[$property_path] = (string) $violation->getMessage();
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        // Transform value from string to array.
 | 
			
		||||
        if (is_string($actual_messages[$property_path])) {
 | 
			
		||||
          $actual_messages[$property_path] = (array) $actual_messages[$violation->getPropertyPath()];
 | 
			
		||||
        }
 | 
			
		||||
        // And append.
 | 
			
		||||
        $actual_messages[$property_path][] = (string) $violation->getMessage();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    ksort($expected_messages);
 | 
			
		||||
    ksort($actual_messages);
 | 
			
		||||
    $this->assertSame($expected_messages, $actual_messages);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the config entity's langcode is validated.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLangcode(): void {
 | 
			
		||||
    $this->entity->set('langcode', NULL);
 | 
			
		||||
    $this->assertValidationErrors([
 | 
			
		||||
      'langcode' => 'This value should not be null.',
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // A langcode from the standard list should always be acceptable.
 | 
			
		||||
    $standard_languages = LanguageManager::getStandardLanguageList();
 | 
			
		||||
    $this->assertNotEmpty($standard_languages);
 | 
			
		||||
    $this->entity->set('langcode', key($standard_languages));
 | 
			
		||||
    $this->assertValidationErrors([]);
 | 
			
		||||
 | 
			
		||||
    // All special, internal langcodes should be acceptable.
 | 
			
		||||
    $system_langcodes = [
 | 
			
		||||
      LanguageInterface::LANGCODE_NOT_SPECIFIED,
 | 
			
		||||
      LanguageInterface::LANGCODE_NOT_APPLICABLE,
 | 
			
		||||
      LanguageInterface::LANGCODE_DEFAULT,
 | 
			
		||||
      LanguageInterface::LANGCODE_SITE_DEFAULT,
 | 
			
		||||
      LanguageInterface::LANGCODE_SYSTEM,
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($system_langcodes as $langcode) {
 | 
			
		||||
      $this->entity->set('langcode', $langcode);
 | 
			
		||||
      $this->assertValidationErrors([]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // An invalid langcode should be unacceptable, even if it "looks" right.
 | 
			
		||||
    $fake_langcode = 'definitely-not-a-language';
 | 
			
		||||
    $this->assertArrayNotHasKey($fake_langcode, LanguageReference::getAllValidLangcodes());
 | 
			
		||||
    $this->entity->set('langcode', $fake_langcode);
 | 
			
		||||
    $this->assertValidationErrors([
 | 
			
		||||
      'langcode' => 'The value you selected is not a valid choice.',
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // If a new configurable language is created with a non-standard langcode,
 | 
			
		||||
    // it should be acceptable.
 | 
			
		||||
    $this->enableModules(['language']);
 | 
			
		||||
    // The language doesn't exist yet, so it shouldn't be a valid choice.
 | 
			
		||||
    $this->entity->set('langcode', 'kthxbai');
 | 
			
		||||
    $this->assertValidationErrors([
 | 
			
		||||
      'langcode' => 'The value you selected is not a valid choice.',
 | 
			
		||||
    ]);
 | 
			
		||||
    // Once we create the language, it should be a valid choice.
 | 
			
		||||
    ConfigurableLanguage::createFromLangcode('kthxbai')->save();
 | 
			
		||||
    $this->assertValidationErrors([]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that immutable properties cannot be changed.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed[] $valid_values
 | 
			
		||||
   *   (optional) The values to set for the immutable properties, keyed by name.
 | 
			
		||||
   *   This should be used if the immutable properties can only accept certain
 | 
			
		||||
   *   values, e.g. valid plugin IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function testImmutableProperties(array $valid_values = []): void {
 | 
			
		||||
    $constraints = $this->entity->getEntityType()->getConstraints();
 | 
			
		||||
    $this->assertNotEmpty($constraints['ImmutableProperties'], 'All config entities should have at least one immutable ID property.');
 | 
			
		||||
 | 
			
		||||
    foreach ($constraints['ImmutableProperties'] as $property_name) {
 | 
			
		||||
      $original_value = $this->entity->get($property_name);
 | 
			
		||||
      $this->entity->set($property_name, $valid_values[$property_name] ?? $this->randomMachineName());
 | 
			
		||||
      $this->assertValidationErrors([
 | 
			
		||||
        '' => "The '$property_name' property cannot be changed.",
 | 
			
		||||
      ]);
 | 
			
		||||
      $this->entity->set($property_name, $original_value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A property that is required must have a value (i.e. not NULL).
 | 
			
		||||
   *
 | 
			
		||||
   * @param string[]|null $additional_expected_validation_errors_when_missing
 | 
			
		||||
   *   Some required config entity properties have additional validation
 | 
			
		||||
   *   constraints that cause additional messages to appear. Keys must be
 | 
			
		||||
   *   config entity properties, values must be arrays as expected by
 | 
			
		||||
   *   ::assertValidationErrors().
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Remove this optional parameter in https://www.drupal.org/project/drupal/issues/2820364#comment-15333069
 | 
			
		||||
   *
 | 
			
		||||
   * @return void
 | 
			
		||||
   *   No return value.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRequiredPropertyKeysMissing(?array $additional_expected_validation_errors_when_missing = NULL): void {
 | 
			
		||||
    $config_entity_properties = array_keys($this->entity->getEntityType()->getPropertiesToExport());
 | 
			
		||||
 | 
			
		||||
    if (!empty(array_diff(array_keys($additional_expected_validation_errors_when_missing ?? []), $config_entity_properties))) {
 | 
			
		||||
      throw new \LogicException(sprintf('The test %s lists `%s` in $additional_expected_validation_errors_when_missing but it is not a property of the `%s` config entity type.',
 | 
			
		||||
        get_called_class(),
 | 
			
		||||
        implode(', ', array_diff(array_keys($additional_expected_validation_errors_when_missing), $config_entity_properties)),
 | 
			
		||||
        $this->entity->getEntityTypeId(),
 | 
			
		||||
      ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $mapping_properties = array_keys(array_filter(
 | 
			
		||||
      ConfigEntityAdapter::createFromEntity($this->entity)->getProperties(FALSE),
 | 
			
		||||
      fn (TypedDataInterface $v) => $v instanceof Mapping
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    $required_property_keys = $this->getRequiredPropertyKeys();
 | 
			
		||||
    if (!$this->isFullyValidatable()) {
 | 
			
		||||
      $this->assertEmpty($required_property_keys, 'No keys can be required when a config entity type is not fully validatable.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $original_entity = clone $this->entity;
 | 
			
		||||
    foreach ($mapping_properties as $property) {
 | 
			
		||||
      $this->entity = clone $original_entity;
 | 
			
		||||
      $this->entity->set($property, []);
 | 
			
		||||
      $expected_validation_errors = array_key_exists($property, $required_property_keys)
 | 
			
		||||
        ? [$property => $required_property_keys[$property]]
 | 
			
		||||
        : [];
 | 
			
		||||
      $this->assertValidationErrors(($additional_expected_validation_errors_when_missing[$property] ?? []) + $expected_validation_errors);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A property that is required must have a value (i.e. not NULL).
 | 
			
		||||
   *
 | 
			
		||||
   * @param string[]|null $additional_expected_validation_errors_when_missing
 | 
			
		||||
   *   Some required config entity properties have additional validation
 | 
			
		||||
   *   constraints that cause additional messages to appear. Keys must be
 | 
			
		||||
   *   config entity properties, values must be arrays as expected by
 | 
			
		||||
   *   ::assertValidationErrors().
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Remove this optional parameter in https://www.drupal.org/project/drupal/issues/2820364#comment-15333069
 | 
			
		||||
   *
 | 
			
		||||
   * @return void
 | 
			
		||||
   *   No return value.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRequiredPropertyValuesMissing(?array $additional_expected_validation_errors_when_missing = NULL): void {
 | 
			
		||||
    $config_entity_properties = array_keys($this->entity->getEntityType()->getPropertiesToExport());
 | 
			
		||||
 | 
			
		||||
    // Guide developers when $additional_expected_validation_errors_when_missing
 | 
			
		||||
    // does not contain sensible values.
 | 
			
		||||
    $non_existing_properties = array_diff(array_keys($additional_expected_validation_errors_when_missing ?? []), $config_entity_properties);
 | 
			
		||||
    if ($non_existing_properties) {
 | 
			
		||||
      throw new \LogicException(sprintf('The test %s lists `%s` in $additional_expected_validation_errors_when_missing but it is not a property of the `%s` config entity type.',
 | 
			
		||||
        __METHOD__,
 | 
			
		||||
        implode(', ', $non_existing_properties),
 | 
			
		||||
        $this->entity->getEntityTypeId(),
 | 
			
		||||
      ));
 | 
			
		||||
    }
 | 
			
		||||
    $properties_with_optional_values = $this->getPropertiesWithOptionalValues();
 | 
			
		||||
 | 
			
		||||
    // Get the config entity properties that are immutable.
 | 
			
		||||
    // @see ::testImmutableProperties()
 | 
			
		||||
    $immutable_properties = $this->entity->getEntityType()->getConstraints()['ImmutableProperties'];
 | 
			
		||||
 | 
			
		||||
    // Config entity properties containing plugin collections are special cases:
 | 
			
		||||
    // setting them to NULL would cause them to get out of sync with the plugin
 | 
			
		||||
    // collection.
 | 
			
		||||
    // @see \Drupal\Core\Config\Entity\ConfigEntityBase::set()
 | 
			
		||||
    // @see \Drupal\Core\Config\Entity\ConfigEntityBase::preSave()
 | 
			
		||||
    $plugin_collection_properties = $this->entity instanceof EntityWithPluginCollectionInterface
 | 
			
		||||
      ? array_keys($this->entity->getPluginCollections())
 | 
			
		||||
      : [];
 | 
			
		||||
 | 
			
		||||
    // To test properties with missing required values, $this->entity must be
 | 
			
		||||
    // modified to be able to use ::assertValidationErrors(). To allow restoring
 | 
			
		||||
    // $this->entity to its original value for each tested property, a clone of
 | 
			
		||||
    // the original entity is needed.
 | 
			
		||||
    $original_entity = clone $this->entity;
 | 
			
		||||
    foreach ($config_entity_properties as $property) {
 | 
			
		||||
      // Do not try to set immutable properties to NULL: their immutability is
 | 
			
		||||
      // already tested.
 | 
			
		||||
      // @see ::testImmutableProperties()
 | 
			
		||||
      if (in_array($property, $immutable_properties, TRUE)) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Do not try to set plugin collection properties to NULL.
 | 
			
		||||
      if (in_array($property, $plugin_collection_properties, TRUE)) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $this->entity = clone $original_entity;
 | 
			
		||||
      $this->entity->set($property, NULL);
 | 
			
		||||
      $expected_validation_errors = in_array($property, $properties_with_optional_values, TRUE)
 | 
			
		||||
        ? []
 | 
			
		||||
        : [$property => 'This value should not be null.'];
 | 
			
		||||
 | 
			
		||||
      // @see `type: required_label`
 | 
			
		||||
      // @see \Symfony\Component\Validator\Constraints\NotBlank
 | 
			
		||||
      if (!$this->isFullyValidatable() && $this->entity->getEntityType()->getKey('label') == $property) {
 | 
			
		||||
        $expected_validation_errors = [$property => 'This value should not be blank.'];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $this->assertValidationErrors(($additional_expected_validation_errors_when_missing[$property] ?? []) + $expected_validation_errors);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether the tested config entity type is fully validatable.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   Whether the tested config entity type is fully validatable.
 | 
			
		||||
   */
 | 
			
		||||
  protected function isFullyValidatable(): bool {
 | 
			
		||||
    $typed_config = $this->container->get('config.typed');
 | 
			
		||||
    assert($typed_config instanceof TypedConfigManagerInterface);
 | 
			
		||||
    // @see \Drupal\Core\Entity\Plugin\DataType\ConfigEntityAdapter::getConfigTypedData()
 | 
			
		||||
    $config_entity_type_schema_constraints = $typed_config
 | 
			
		||||
      ->createFromNameAndData(
 | 
			
		||||
        $this->entity->getConfigDependencyName(),
 | 
			
		||||
        $this->entity->toArray()
 | 
			
		||||
      )->getConstraints();
 | 
			
		||||
 | 
			
		||||
    foreach ($config_entity_type_schema_constraints as $constraint) {
 | 
			
		||||
      if ($constraint instanceof FullyValidatableConstraint) {
 | 
			
		||||
        return TRUE;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines the config entity mapping properties with required keys.
 | 
			
		||||
   *
 | 
			
		||||
   * This refers only to the top-level properties of the config entity which are
 | 
			
		||||
   * expected to be mappings, and of those mappings, only the ones which have
 | 
			
		||||
   * required keys.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   An array of key-value pairs, with:
 | 
			
		||||
   *   - keys: names of the config entity properties which are mappings that
 | 
			
		||||
   *     contain required keys.
 | 
			
		||||
   *   - values: the corresponding expected validation error message.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getRequiredPropertyKeys(): array {
 | 
			
		||||
    // If a config entity type is not fully validatable, no mapping property
 | 
			
		||||
    // keys are required.
 | 
			
		||||
    if (!$this->isFullyValidatable()) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $config_entity_properties = array_keys($this->entity->getEntityType()
 | 
			
		||||
      ->getPropertiesToExport());
 | 
			
		||||
 | 
			
		||||
    // Otherwise, all mapping property keys are required except for those marked
 | 
			
		||||
    // optional. Rather than inspecting config schema, require authors of tests
 | 
			
		||||
    // to explicitly list optional properties in a `propertiesWithRequiredKeys`
 | 
			
		||||
    // property on this class.
 | 
			
		||||
    // @see \Drupal\KernelTests\Config\Schema\MappingTest::testMappingInterpretation()
 | 
			
		||||
    $class = static::class;
 | 
			
		||||
    $properties_with_required_keys = [];
 | 
			
		||||
    while ($class) {
 | 
			
		||||
      if (property_exists($class, 'propertiesWithRequiredKeys')) {
 | 
			
		||||
        $properties_with_required_keys += $class::$propertiesWithRequiredKeys;
 | 
			
		||||
      }
 | 
			
		||||
      $class = get_parent_class($class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Guide developers when $propertiesWithRequiredKeys does not contain
 | 
			
		||||
    // sensible values.
 | 
			
		||||
    if (!empty(array_diff(array_keys($properties_with_required_keys), $config_entity_properties))) {
 | 
			
		||||
      throw new \LogicException(sprintf('The %s test class lists %s in $propertiesWithRequiredKeys but it is not a property of the %s config entity type.',
 | 
			
		||||
        get_called_class(),
 | 
			
		||||
        implode(', ', array_diff(array_keys($properties_with_required_keys), $config_entity_properties)),
 | 
			
		||||
        $this->entity->getEntityTypeId()
 | 
			
		||||
      ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $properties_with_required_keys;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines the config entity properties with optional values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   The config entity properties whose values are optional.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getPropertiesWithOptionalValues(): array {
 | 
			
		||||
    $config_entity_properties = array_keys($this->entity->getEntityType()
 | 
			
		||||
      ->getPropertiesToExport());
 | 
			
		||||
 | 
			
		||||
    // If a config entity type is not fully validatable, all properties are
 | 
			
		||||
    // optional, with the exception of `type: langcode` and
 | 
			
		||||
    // `type: required_label`.
 | 
			
		||||
    if (!$this->isFullyValidatable()) {
 | 
			
		||||
      return array_diff($config_entity_properties, [
 | 
			
		||||
        // @see `type: langcode`
 | 
			
		||||
        // @see \Symfony\Component\Validator\Constraints\NotNull
 | 
			
		||||
        'langcode',
 | 
			
		||||
        'default_langcode',
 | 
			
		||||
        // @see `type: required_label`
 | 
			
		||||
        // @see \Symfony\Component\Validator\Constraints\NotBlank
 | 
			
		||||
        $this->entity->getEntityType()->getKey('label'),
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Otherwise, all properties are required except for those marked
 | 
			
		||||
    // optional. Rather than inspecting config schema, require authors of tests
 | 
			
		||||
    // to explicitly list optional properties in a
 | 
			
		||||
    // `propertiesWithOptionalValues` property on this class.
 | 
			
		||||
    $class = static::class;
 | 
			
		||||
    $optional_properties = [];
 | 
			
		||||
    while ($class) {
 | 
			
		||||
      if (property_exists($class, 'propertiesWithOptionalValues')) {
 | 
			
		||||
        $optional_properties = array_merge($optional_properties, $class::$propertiesWithOptionalValues);
 | 
			
		||||
      }
 | 
			
		||||
      $class = get_parent_class($class);
 | 
			
		||||
    }
 | 
			
		||||
    $optional_properties = array_unique($optional_properties);
 | 
			
		||||
 | 
			
		||||
    // Guide developers when $optionalProperties does not contain sensible
 | 
			
		||||
    // values.
 | 
			
		||||
    $non_existing_properties = array_diff($optional_properties, $config_entity_properties);
 | 
			
		||||
    if ($non_existing_properties) {
 | 
			
		||||
      throw new \LogicException(sprintf('The %s test class lists %s in $optionalProperties but it is not a property of the %s config entity type.',
 | 
			
		||||
        static::class,
 | 
			
		||||
        implode(', ', $non_existing_properties),
 | 
			
		||||
        $this->entity->getEntityTypeId()
 | 
			
		||||
      ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $optional_properties;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,81 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Config;
 | 
			
		||||
use Drupal\Core\Config\ConfigEvents;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests events fired on configuration objects.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigEventsTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_events_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests configuration events.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigEvents(): void {
 | 
			
		||||
    $name = 'config_events_test.test';
 | 
			
		||||
 | 
			
		||||
    $config = new Config($name, \Drupal::service('config.storage'), \Drupal::service('event_dispatcher'), \Drupal::service('config.typed'));
 | 
			
		||||
    $config->set('key', 'initial');
 | 
			
		||||
    $this->assertSame([], \Drupal::state()->get('config_events_test.event', []), 'No events fired by creating a new configuration object');
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $event = \Drupal::state()->get('config_events_test.event', []);
 | 
			
		||||
    $this->assertSame(ConfigEvents::SAVE, $event['event_name']);
 | 
			
		||||
    $this->assertSame(['key' => 'initial'], $event['current_config_data']);
 | 
			
		||||
    $this->assertSame(['key' => 'initial'], $event['raw_config_data']);
 | 
			
		||||
    $this->assertSame([], $event['original_config_data']);
 | 
			
		||||
 | 
			
		||||
    $config->set('key', 'updated')->save();
 | 
			
		||||
    $event = \Drupal::state()->get('config_events_test.event', []);
 | 
			
		||||
    $this->assertSame(ConfigEvents::SAVE, $event['event_name']);
 | 
			
		||||
    $this->assertSame(['key' => 'updated'], $event['current_config_data']);
 | 
			
		||||
    $this->assertSame(['key' => 'updated'], $event['raw_config_data']);
 | 
			
		||||
    $this->assertSame(['key' => 'initial'], $event['original_config_data']);
 | 
			
		||||
 | 
			
		||||
    $config->delete();
 | 
			
		||||
    $event = \Drupal::state()->get('config_events_test.event', []);
 | 
			
		||||
    $this->assertSame(ConfigEvents::DELETE, $event['event_name']);
 | 
			
		||||
    $this->assertSame([], $event['current_config_data']);
 | 
			
		||||
    $this->assertSame([], $event['raw_config_data']);
 | 
			
		||||
    $this->assertSame(['key' => 'updated'], $event['original_config_data']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests configuration rename event that is fired from the ConfigFactory.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigRenameEvent(): void {
 | 
			
		||||
    $name = 'config_events_test.test';
 | 
			
		||||
    $new_name = 'config_events_test.test_rename';
 | 
			
		||||
    $GLOBALS['config'][$name] = ['key' => 'overridden'];
 | 
			
		||||
    $GLOBALS['config'][$new_name] = ['key' => 'new overridden'];
 | 
			
		||||
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
    $config->set('key', 'initial')->save();
 | 
			
		||||
    $event = \Drupal::state()->get('config_events_test.event', []);
 | 
			
		||||
    $this->assertSame(ConfigEvents::SAVE, $event['event_name']);
 | 
			
		||||
    $this->assertSame(['key' => 'initial'], $event['current_config_data']);
 | 
			
		||||
 | 
			
		||||
    // Override applies when getting runtime config.
 | 
			
		||||
    $this->assertEquals($GLOBALS['config'][$name], \Drupal::config($name)->get());
 | 
			
		||||
 | 
			
		||||
    \Drupal::configFactory()->rename($name, $new_name);
 | 
			
		||||
    $event = \Drupal::state()->get('config_events_test.event', []);
 | 
			
		||||
    $this->assertSame(ConfigEvents::RENAME, $event['event_name']);
 | 
			
		||||
    $this->assertSame(['key' => 'new overridden'], $event['current_config_data']);
 | 
			
		||||
    $this->assertSame(['key' => 'initial'], $event['raw_config_data']);
 | 
			
		||||
    $this->assertSame(['key' => 'new overridden'], $event['original_config_data']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,54 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\TypedData\DataDefinition;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the ConfigExists constraint validator.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 * @group Validation
 | 
			
		||||
 *
 | 
			
		||||
 * @covers \Drupal\Core\Config\Plugin\Validation\Constraint\ConfigExistsConstraint
 | 
			
		||||
 * @covers \Drupal\Core\Config\Plugin\Validation\Constraint\ConfigExistsConstraintValidator
 | 
			
		||||
 */
 | 
			
		||||
class ConfigExistsConstraintValidatorTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the ConfigExists constraint validator.
 | 
			
		||||
   *
 | 
			
		||||
   * @testWith [{}, "system.site", "system.site"]
 | 
			
		||||
   *           [{"prefix": "system."}, "site", "system.site"]
 | 
			
		||||
   */
 | 
			
		||||
  public function testValidation(array $constraint_options, string $value, string $expected_config_name): void {
 | 
			
		||||
    // Create a data definition that specifies the value must be a string with
 | 
			
		||||
    // the name of an existing piece of config.
 | 
			
		||||
    $definition = DataDefinition::create('string')
 | 
			
		||||
      ->addConstraint('ConfigExists', $constraint_options);
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data */
 | 
			
		||||
    $typed_data = $this->container->get('typed_data_manager');
 | 
			
		||||
    $data = $typed_data->create($definition, $value);
 | 
			
		||||
 | 
			
		||||
    $violations = $data->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame("The '$expected_config_name' config does not exist.", (string) $violations->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    $this->installConfig('system');
 | 
			
		||||
    $this->assertCount(0, $data->validate());
 | 
			
		||||
 | 
			
		||||
    // NULL should not trigger a validation error: a value may be nullable.
 | 
			
		||||
    $data->setValue(NULL);
 | 
			
		||||
    $this->assertCount(0, $data->validate());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,50 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests configuration export storage.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigExportStorageTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system', 'config_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests configuration override.
 | 
			
		||||
   */
 | 
			
		||||
  public function testExportStorage(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active */
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $export */
 | 
			
		||||
    $export = $this->container->get('config.storage.export');
 | 
			
		||||
 | 
			
		||||
    // Test that the active and the export storage contain the same config.
 | 
			
		||||
    $this->assertNotEmpty($active->listAll());
 | 
			
		||||
    $this->assertEquals($active->listAll(), $export->listAll());
 | 
			
		||||
    foreach ($active->listAll() as $name) {
 | 
			
		||||
      $this->assertEquals($active->read($name), $export->read($name));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test that the export storage is read-only.
 | 
			
		||||
    $this->expectException(\BadMethodCallException::class);
 | 
			
		||||
    $export->deleteAll();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,237 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\FileStorage;
 | 
			
		||||
use Drupal\Core\Site\Settings;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests reading and writing of configuration files.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigFileContentTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Exempt from strict schema checking.
 | 
			
		||||
   *
 | 
			
		||||
   * @var bool
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\Development\ConfigSchemaChecker
 | 
			
		||||
   */
 | 
			
		||||
  protected $strictConfigSchema = FALSE;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests setting, writing, and reading of a configuration setting.
 | 
			
		||||
   */
 | 
			
		||||
  public function testReadWriteConfig(): void {
 | 
			
		||||
    $storage = $this->container->get('config.storage');
 | 
			
		||||
 | 
			
		||||
    $name = 'foo.bar';
 | 
			
		||||
    $key = 'foo';
 | 
			
		||||
    $value = 'bar';
 | 
			
		||||
    $nested_key = 'biff.bang';
 | 
			
		||||
    $nested_value = 'pow';
 | 
			
		||||
    $array_key = 'array';
 | 
			
		||||
    $array_value = [
 | 
			
		||||
      'foo' => 'bar',
 | 
			
		||||
      'biff' => [
 | 
			
		||||
        'bang' => 'pow',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $casting_array_key = 'casting_array';
 | 
			
		||||
    $casting_array_false_value_key = 'casting_array.cast.false';
 | 
			
		||||
    $casting_array_value = [
 | 
			
		||||
      'cast' => [
 | 
			
		||||
        'false' => FALSE,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $nested_array_key = 'nested.array';
 | 
			
		||||
    $true_key = 'true';
 | 
			
		||||
    $false_key = 'false';
 | 
			
		||||
 | 
			
		||||
    // Attempt to read non-existing configuration.
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
 | 
			
		||||
    // Verify a configuration object is returned.
 | 
			
		||||
    $this->assertEquals($name, $config->getName());
 | 
			
		||||
    $this->assertNotEmpty($config, 'Config object created.');
 | 
			
		||||
 | 
			
		||||
    // Verify the configuration object is empty.
 | 
			
		||||
    $this->assertEquals([], $config->get(), 'New config object is empty.');
 | 
			
		||||
 | 
			
		||||
    // Verify nothing was saved.
 | 
			
		||||
    $data = $storage->read($name);
 | 
			
		||||
    $this->assertFalse($data);
 | 
			
		||||
 | 
			
		||||
    // Add a top level value.
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
    $config->set($key, $value);
 | 
			
		||||
 | 
			
		||||
    // Add a nested value.
 | 
			
		||||
    $config->set($nested_key, $nested_value);
 | 
			
		||||
 | 
			
		||||
    // Add an array.
 | 
			
		||||
    $config->set($array_key, $array_value);
 | 
			
		||||
 | 
			
		||||
    // Add a nested array.
 | 
			
		||||
    $config->set($nested_array_key, $array_value);
 | 
			
		||||
 | 
			
		||||
    // Add a boolean false value. Should get cast to 0.
 | 
			
		||||
    $config->set($false_key, FALSE);
 | 
			
		||||
 | 
			
		||||
    // Add a boolean true value. Should get cast to 1.
 | 
			
		||||
    $config->set($true_key, TRUE);
 | 
			
		||||
 | 
			
		||||
    // Add a null value. Should get cast to an empty string.
 | 
			
		||||
    $config->set('null', NULL);
 | 
			
		||||
 | 
			
		||||
    // Add an array with a nested boolean false that should get cast to 0.
 | 
			
		||||
    $config->set($casting_array_key, $casting_array_value);
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    // Verify the database entry exists.
 | 
			
		||||
    $data = $storage->read($name);
 | 
			
		||||
    $this->assertNotEmpty($data);
 | 
			
		||||
 | 
			
		||||
    // Read top level value.
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
    $this->assertEquals($name, $config->getName());
 | 
			
		||||
    $this->assertNotEmpty($config, 'Config object created.');
 | 
			
		||||
    $this->assertEquals('bar', $config->get($key), 'Top level configuration value found.');
 | 
			
		||||
 | 
			
		||||
    // Read nested value.
 | 
			
		||||
    $this->assertEquals($nested_value, $config->get($nested_key), 'Nested configuration value found.');
 | 
			
		||||
 | 
			
		||||
    // Read array.
 | 
			
		||||
    $this->assertEquals($array_value, $config->get($array_key), 'Top level array configuration value found.');
 | 
			
		||||
 | 
			
		||||
    // Read nested array.
 | 
			
		||||
    $this->assertEquals($array_value, $config->get($nested_array_key), 'Nested array configuration value found.');
 | 
			
		||||
 | 
			
		||||
    // Read a top level value that doesn't exist.
 | 
			
		||||
    $this->assertNull($config->get('i_do_not_exist'), 'Non-existent top level value returned NULL.');
 | 
			
		||||
 | 
			
		||||
    // Read a nested value that doesn't exist.
 | 
			
		||||
    $this->assertNull($config->get('i.do.not.exist'), 'Non-existent nested value returned NULL.');
 | 
			
		||||
 | 
			
		||||
    // Read false value.
 | 
			
		||||
    $this->assertFalse($config->get($false_key), "Boolean FALSE value returned the FALSE.");
 | 
			
		||||
 | 
			
		||||
    // Read true value.
 | 
			
		||||
    $this->assertTrue($config->get($true_key), "Boolean TRUE value returned the TRUE.");
 | 
			
		||||
 | 
			
		||||
    // Read null value.
 | 
			
		||||
    $this->assertNull($config->get('null'));
 | 
			
		||||
 | 
			
		||||
    // Read false that had been nested in an array value.
 | 
			
		||||
    $this->assertFalse($config->get($casting_array_false_value_key), "Nested boolean FALSE value returned FALSE.");
 | 
			
		||||
 | 
			
		||||
    // Unset a top level value.
 | 
			
		||||
    $config->clear($key);
 | 
			
		||||
 | 
			
		||||
    // Unset a nested value.
 | 
			
		||||
    $config->clear($nested_key);
 | 
			
		||||
    $config->save();
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
 | 
			
		||||
    // Read unset top level value.
 | 
			
		||||
    $this->assertNull($config->get($key), 'Top level value unset.');
 | 
			
		||||
 | 
			
		||||
    // Read unset nested value.
 | 
			
		||||
    $this->assertNull($config->get($nested_key), 'Nested value unset.');
 | 
			
		||||
 | 
			
		||||
    // Create two new configuration files to test listing.
 | 
			
		||||
    $config = $this->config('foo.baz');
 | 
			
		||||
    $config->set($key, $value);
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    // Test chained set()->save().
 | 
			
		||||
    $chained_name = 'biff.bang';
 | 
			
		||||
    $config = $this->config($chained_name);
 | 
			
		||||
    $config->set($key, $value)->save();
 | 
			
		||||
 | 
			
		||||
    // Verify the database entry exists from a chained save.
 | 
			
		||||
    $data = $storage->read($chained_name);
 | 
			
		||||
    $this->assertEquals($config->get(), $data);
 | 
			
		||||
 | 
			
		||||
    // Get file listing for all files starting with 'foo'. Should return
 | 
			
		||||
    // two elements.
 | 
			
		||||
    $files = $storage->listAll('foo');
 | 
			
		||||
    $this->assertCount(2, $files, 'Two files listed with the prefix \'foo\'.');
 | 
			
		||||
 | 
			
		||||
    // Get file listing for all files starting with 'biff'. Should return
 | 
			
		||||
    // one element.
 | 
			
		||||
    $files = $storage->listAll('biff');
 | 
			
		||||
    $this->assertCount(1, $files, 'One file listed with the prefix \'biff\'.');
 | 
			
		||||
 | 
			
		||||
    // Get file listing for all files starting with 'foo.bar'. Should return
 | 
			
		||||
    // one element.
 | 
			
		||||
    $files = $storage->listAll('foo.bar');
 | 
			
		||||
    $this->assertCount(1, $files, 'One file listed with the prefix \'foo.bar\'.');
 | 
			
		||||
 | 
			
		||||
    // Get file listing for all files starting with 'bar'. Should return
 | 
			
		||||
    // an empty array.
 | 
			
		||||
    $files = $storage->listAll('bar');
 | 
			
		||||
    $this->assertEquals([], $files, 'No files listed with the prefix \'bar\'.');
 | 
			
		||||
 | 
			
		||||
    // Delete the configuration.
 | 
			
		||||
    $config = $this->config($name);
 | 
			
		||||
    $config->delete();
 | 
			
		||||
 | 
			
		||||
    // Verify the database entry no longer exists.
 | 
			
		||||
    $data = $storage->read($name);
 | 
			
		||||
    $this->assertFalse($data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests serialization of configuration to file.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSerialization(): void {
 | 
			
		||||
    $name = $this->randomMachineName(10) . '.' . $this->randomMachineName(10);
 | 
			
		||||
    $config_data = [
 | 
			
		||||
      // Indexed arrays; the order of elements is essential.
 | 
			
		||||
      'numeric keys' => ['i', 'n', 'd', 'e', 'x', 'e', 'd'],
 | 
			
		||||
      // Infinitely nested keys using arbitrary element names.
 | 
			
		||||
      'nested keys' => [
 | 
			
		||||
        // HTML/XML in values.
 | 
			
		||||
        'HTML' => '<strong> <bold> <em> <blockquote>',
 | 
			
		||||
        // UTF-8 in values.
 | 
			
		||||
        // cspell:disable-next-line
 | 
			
		||||
        'UTF-8' => 'FrançAIS is ÜBER-åwesome',
 | 
			
		||||
        // Unicode in keys and values.
 | 
			
		||||
        // cSpell:disable-next-line
 | 
			
		||||
        'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ' => 'αβγδεζηθικλμνξοσὠ',
 | 
			
		||||
      ],
 | 
			
		||||
      'invalid xml' => '</title><script type="text/javascript">alert("Title XSS!");</script> & < > " \' ',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Encode and write, and reload and decode the configuration data.
 | 
			
		||||
    $file_storage = new FileStorage(Settings::get('config_sync_directory'));
 | 
			
		||||
    $file_storage->write($name, $config_data);
 | 
			
		||||
    $config_parsed = $file_storage->read($name);
 | 
			
		||||
 | 
			
		||||
    $key = 'numeric keys';
 | 
			
		||||
    $this->assertSame($config_data[$key], $config_parsed[$key]);
 | 
			
		||||
 | 
			
		||||
    $key = 'nested keys';
 | 
			
		||||
    $this->assertSame($config_data[$key], $config_parsed[$key]);
 | 
			
		||||
 | 
			
		||||
    $key = 'HTML';
 | 
			
		||||
    $this->assertSame($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
 | 
			
		||||
 | 
			
		||||
    $key = 'UTF-8';
 | 
			
		||||
    $this->assertSame($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
 | 
			
		||||
 | 
			
		||||
    // cSpell:disable-next-line
 | 
			
		||||
    $key = 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ';
 | 
			
		||||
    $this->assertSame($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
 | 
			
		||||
 | 
			
		||||
    $key = 'invalid xml';
 | 
			
		||||
    $this->assertSame($config_data[$key], $config_parsed[$key]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,111 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ConfigImporter;
 | 
			
		||||
use Drupal\Core\Config\StorageComparer;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\node\Entity\NodeType;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests importing recreated configuration entities.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigImportRecreateTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Config Importer object used for testing.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigImporter
 | 
			
		||||
   */
 | 
			
		||||
  protected $configImporter;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'field', 'text', 'user', 'node'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->installEntitySchema('node');
 | 
			
		||||
    $this->installConfig(['system', 'field', 'node']);
 | 
			
		||||
 | 
			
		||||
    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
 | 
			
		||||
 | 
			
		||||
    // Set up the ConfigImporter object for testing.
 | 
			
		||||
    $storage_comparer = new StorageComparer(
 | 
			
		||||
      $this->container->get('config.storage.sync'),
 | 
			
		||||
      $this->container->get('config.storage')
 | 
			
		||||
    );
 | 
			
		||||
    $this->configImporter = new ConfigImporter(
 | 
			
		||||
      $storage_comparer->createChangelist(),
 | 
			
		||||
      $this->container->get('event_dispatcher'),
 | 
			
		||||
      $this->container->get('config.manager'),
 | 
			
		||||
      $this->container->get('lock'),
 | 
			
		||||
      $this->container->get('config.typed'),
 | 
			
		||||
      $this->container->get('module_handler'),
 | 
			
		||||
      $this->container->get('module_installer'),
 | 
			
		||||
      $this->container->get('theme_handler'),
 | 
			
		||||
      $this->container->get('string_translation'),
 | 
			
		||||
      $this->container->get('extension.list.module'),
 | 
			
		||||
      $this->container->get('extension.list.theme')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests re-creating a config entity with the same name but different UUID.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRecreateEntity(): void {
 | 
			
		||||
    $type_name = $this->randomMachineName(16);
 | 
			
		||||
    $content_type = NodeType::create([
 | 
			
		||||
      'type' => $type_name,
 | 
			
		||||
      'name' => 'Node type one',
 | 
			
		||||
    ]);
 | 
			
		||||
    $content_type->save();
 | 
			
		||||
    node_add_body_field($content_type);
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active */
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $sync */
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
 | 
			
		||||
    $config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id();
 | 
			
		||||
    $this->copyConfig($active, $sync);
 | 
			
		||||
 | 
			
		||||
    // Delete the content type. This will also delete a field storage, a field,
 | 
			
		||||
    // an entity view display and an entity form display.
 | 
			
		||||
    $content_type->delete();
 | 
			
		||||
    $this->assertFalse($active->exists($config_name), 'Content type\'s old name does not exist active store.');
 | 
			
		||||
    // Recreate with the same type - this will have a different UUID.
 | 
			
		||||
    $content_type = NodeType::create([
 | 
			
		||||
      'type' => $type_name,
 | 
			
		||||
      'name' => 'Node type two',
 | 
			
		||||
    ]);
 | 
			
		||||
    $content_type->save();
 | 
			
		||||
    node_add_body_field($content_type);
 | 
			
		||||
 | 
			
		||||
    $this->configImporter->reset();
 | 
			
		||||
    // A node type, a field, an entity view display and an entity form display
 | 
			
		||||
    // will be recreated.
 | 
			
		||||
    $creates = $this->configImporter->getUnprocessedConfiguration('create');
 | 
			
		||||
    $deletes = $this->configImporter->getUnprocessedConfiguration('delete');
 | 
			
		||||
    $this->assertCount(5, $creates, 'There are 5 configuration items to create.');
 | 
			
		||||
    $this->assertCount(5, $deletes, 'There are 5 configuration items to delete.');
 | 
			
		||||
    $this->assertCount(0, $this->configImporter->getUnprocessedConfiguration('update'), 'There are no configuration items to update.');
 | 
			
		||||
    $this->assertSame($creates, array_reverse($deletes), 'Deletes and creates contain the same configuration names in opposite orders due to dependencies.');
 | 
			
		||||
 | 
			
		||||
    $this->configImporter->import();
 | 
			
		||||
 | 
			
		||||
    // Verify that there is nothing more to import.
 | 
			
		||||
    $this->assertFalse($this->configImporter->reset()->hasUnprocessedConfigurationChanges());
 | 
			
		||||
    $content_type = NodeType::load($type_name);
 | 
			
		||||
    $this->assertEquals('Node type one', $content_type->label());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,162 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Uuid\Php;
 | 
			
		||||
use Drupal\Core\Config\ConfigImporter;
 | 
			
		||||
use Drupal\Core\Config\ConfigImporterException;
 | 
			
		||||
use Drupal\Core\Config\StorageComparer;
 | 
			
		||||
use Drupal\node\Entity\NodeType;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests validating renamed configuration in a configuration import.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigImportRenameValidationTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Config Importer object used for testing.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigImporter
 | 
			
		||||
   */
 | 
			
		||||
  protected $configImporter;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'user',
 | 
			
		||||
    'node',
 | 
			
		||||
    'field',
 | 
			
		||||
    'text',
 | 
			
		||||
    'config_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->installEntitySchema('user');
 | 
			
		||||
    $this->installEntitySchema('node');
 | 
			
		||||
    $this->installConfig(['system', 'field']);
 | 
			
		||||
 | 
			
		||||
    // Set up the ConfigImporter object for testing.
 | 
			
		||||
    $storage_comparer = new StorageComparer(
 | 
			
		||||
      $this->container->get('config.storage.sync'),
 | 
			
		||||
      $this->container->get('config.storage')
 | 
			
		||||
    );
 | 
			
		||||
    $this->configImporter = new ConfigImporter(
 | 
			
		||||
      $storage_comparer->createChangelist(),
 | 
			
		||||
      $this->container->get('event_dispatcher'),
 | 
			
		||||
      $this->container->get('config.manager'),
 | 
			
		||||
      $this->container->get('lock.persistent'),
 | 
			
		||||
      $this->container->get('config.typed'),
 | 
			
		||||
      $this->container->get('module_handler'),
 | 
			
		||||
      $this->container->get('module_installer'),
 | 
			
		||||
      $this->container->get('theme_handler'),
 | 
			
		||||
      $this->container->get('string_translation'),
 | 
			
		||||
      $this->container->get('extension.list.module'),
 | 
			
		||||
      $this->container->get('extension.list.theme')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests configuration renaming validation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenameValidation(): void {
 | 
			
		||||
    // Create a test entity.
 | 
			
		||||
    $test_entity_id = $this->randomMachineName();
 | 
			
		||||
    $test_entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([
 | 
			
		||||
      'id' => $test_entity_id,
 | 
			
		||||
      'label' => $this->randomMachineName(),
 | 
			
		||||
    ]);
 | 
			
		||||
    $test_entity->save();
 | 
			
		||||
    $uuid = $test_entity->uuid();
 | 
			
		||||
 | 
			
		||||
    // Stage the test entity and then delete it from the active storage.
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
    $this->copyConfig($active, $sync);
 | 
			
		||||
    $test_entity->delete();
 | 
			
		||||
 | 
			
		||||
    // Create a content type with a matching UUID in the active storage.
 | 
			
		||||
    $content_type = NodeType::create([
 | 
			
		||||
      'type' => $this->randomMachineName(16),
 | 
			
		||||
      'name' => $this->randomMachineName(),
 | 
			
		||||
      'uuid' => $uuid,
 | 
			
		||||
    ]);
 | 
			
		||||
    $content_type->save();
 | 
			
		||||
 | 
			
		||||
    // Confirm that the staged configuration is detected as a rename since the
 | 
			
		||||
    // UUIDs match.
 | 
			
		||||
    $this->configImporter->reset();
 | 
			
		||||
    $expected = [
 | 
			
		||||
      'node.type.' . $content_type->id() . '::config_test.dynamic.' . $test_entity_id,
 | 
			
		||||
    ];
 | 
			
		||||
    $renames = $this->configImporter->getUnprocessedConfiguration('rename');
 | 
			
		||||
    $this->assertSame($expected, $renames);
 | 
			
		||||
 | 
			
		||||
    // Try to import the configuration. We expect an exception to be thrown
 | 
			
		||||
    // because the staged entity is of a different type.
 | 
			
		||||
    try {
 | 
			
		||||
      $this->configImporter->import();
 | 
			
		||||
      $this->fail('Expected ConfigImporterException thrown when a renamed configuration entity does not match the existing entity type.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigImporterException) {
 | 
			
		||||
      $expected = [
 | 
			
		||||
        "Entity type mismatch on rename. node_type not equal to config_test for existing configuration node.type.{$content_type->id()} and staged configuration config_test.dynamic.$test_entity_id.",
 | 
			
		||||
      ];
 | 
			
		||||
      $this->assertEquals($expected, $this->configImporter->getErrors());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests configuration renaming validation for simple configuration.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenameSimpleConfigValidation(): void {
 | 
			
		||||
    $uuid = new Php();
 | 
			
		||||
    // Create a simple configuration with a UUID.
 | 
			
		||||
    $config = $this->config('config_test.new');
 | 
			
		||||
    $uuid_value = $uuid->generate();
 | 
			
		||||
    $config->set('uuid', $uuid_value)->save();
 | 
			
		||||
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
    $this->copyConfig($active, $sync);
 | 
			
		||||
    $config->delete();
 | 
			
		||||
 | 
			
		||||
    // Create another simple configuration with the same UUID.
 | 
			
		||||
    $config = $this->config('config_test.old');
 | 
			
		||||
    $config->set('uuid', $uuid_value)->save();
 | 
			
		||||
 | 
			
		||||
    // Confirm that the staged configuration is detected as a rename since the
 | 
			
		||||
    // UUIDs match.
 | 
			
		||||
    $this->configImporter->reset();
 | 
			
		||||
    $expected = [
 | 
			
		||||
      'config_test.old::config_test.new',
 | 
			
		||||
    ];
 | 
			
		||||
    $renames = $this->configImporter->getUnprocessedConfiguration('rename');
 | 
			
		||||
    $this->assertSame($expected, $renames);
 | 
			
		||||
 | 
			
		||||
    // Try to import the configuration. We expect an exception to be thrown
 | 
			
		||||
    // because the rename is for simple configuration.
 | 
			
		||||
    try {
 | 
			
		||||
      $this->configImporter->import();
 | 
			
		||||
      $this->fail('Expected ConfigImporterException thrown when simple configuration is renamed.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigImporterException) {
 | 
			
		||||
      $expected = [
 | 
			
		||||
        'Rename operation for simple configuration. Existing configuration config_test.old and staged configuration config_test.new.',
 | 
			
		||||
      ];
 | 
			
		||||
      $this->assertEquals($expected, $this->configImporter->getErrors());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,200 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\block_content\Entity\BlockContent;
 | 
			
		||||
use Drupal\block_content\Entity\BlockContentType;
 | 
			
		||||
use Drupal\Component\Plugin\PluginBase;
 | 
			
		||||
use Drupal\Core\Block\Plugin\Block\Broken;
 | 
			
		||||
use Drupal\Core\Config\ConfigImporter;
 | 
			
		||||
use Drupal\Core\Config\StorageComparer;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Drupal\Core\Logger\RfcLoggerTrait;
 | 
			
		||||
use Drupal\entity_test\Entity\EntityTest;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\Tests\block\Traits\BlockCreationTrait;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests importing configuration which has missing content dependencies.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigImporterMissingContentTest extends KernelTestBase implements LoggerInterface {
 | 
			
		||||
  use BlockCreationTrait;
 | 
			
		||||
  use RfcLoggerTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The logged messages.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $logMessages = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Config Importer object used for testing.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigImporter
 | 
			
		||||
   */
 | 
			
		||||
  protected $configImporter;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'user',
 | 
			
		||||
    'entity_test',
 | 
			
		||||
    'config_test',
 | 
			
		||||
    'config_import_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function register(ContainerBuilder $container): void {
 | 
			
		||||
    parent::register($container);
 | 
			
		||||
    $container->register('logger.ConfigImporterMissingContentTest', __CLASS__)->addTag('logger');
 | 
			
		||||
    $container->set('logger.ConfigImporterMissingContentTest', $this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installEntitySchema('entity_test');
 | 
			
		||||
    $this->installEntitySchema('user');
 | 
			
		||||
    $this->installConfig(['system', 'config_test']);
 | 
			
		||||
    // Installing config_test's default configuration pollutes the global
 | 
			
		||||
    // variable being used for recording hook invocations by this test already,
 | 
			
		||||
    // so it has to be cleared out manually.
 | 
			
		||||
    unset($GLOBALS['hook_config_test']);
 | 
			
		||||
 | 
			
		||||
    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
 | 
			
		||||
 | 
			
		||||
    // Set up the ConfigImporter object for testing.
 | 
			
		||||
    $storage_comparer = new StorageComparer(
 | 
			
		||||
      $this->container->get('config.storage.sync'),
 | 
			
		||||
      $this->container->get('config.storage')
 | 
			
		||||
    );
 | 
			
		||||
    $this->configImporter = new ConfigImporter(
 | 
			
		||||
      $storage_comparer->createChangelist(),
 | 
			
		||||
      $this->container->get('event_dispatcher'),
 | 
			
		||||
      $this->container->get('config.manager'),
 | 
			
		||||
      $this->container->get('lock'),
 | 
			
		||||
      $this->container->get('config.typed'),
 | 
			
		||||
      $this->container->get('module_handler'),
 | 
			
		||||
      $this->container->get('module_installer'),
 | 
			
		||||
      $this->container->get('theme_handler'),
 | 
			
		||||
      $this->container->get('string_translation'),
 | 
			
		||||
      $this->container->get('extension.list.module'),
 | 
			
		||||
      $this->container->get('extension.list.theme')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the missing content event is fired.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\ConfigImporter::processMissingContent()
 | 
			
		||||
   * @see \Drupal\config_import_test\EventSubscriber
 | 
			
		||||
   */
 | 
			
		||||
  public function testMissingContent(): void {
 | 
			
		||||
    \Drupal::state()->set('config_import_test.config_import_missing_content', TRUE);
 | 
			
		||||
 | 
			
		||||
    // Update a configuration entity in the sync directory to have a dependency
 | 
			
		||||
    // on two content entities that do not exist.
 | 
			
		||||
    $storage = $this->container->get('config.storage');
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
    $entity_one = EntityTest::create(['name' => 'one']);
 | 
			
		||||
    $entity_two = EntityTest::create(['name' => 'two']);
 | 
			
		||||
    $entity_three = EntityTest::create(['name' => 'three']);
 | 
			
		||||
    $dynamic_name = 'config_test.dynamic.dotted.default';
 | 
			
		||||
    $original_dynamic_data = $storage->read($dynamic_name);
 | 
			
		||||
    // Entity one will be resolved by
 | 
			
		||||
    // \Drupal\config_import_test\EventSubscriber::onConfigImporterMissingContentOne().
 | 
			
		||||
    $original_dynamic_data['dependencies']['content'][] = $entity_one->getConfigDependencyName();
 | 
			
		||||
    // Entity two will be resolved by
 | 
			
		||||
    // \Drupal\config_import_test\EventSubscriber::onConfigImporterMissingContentTwo().
 | 
			
		||||
    $original_dynamic_data['dependencies']['content'][] = $entity_two->getConfigDependencyName();
 | 
			
		||||
    // Entity three will be resolved by
 | 
			
		||||
    // \Drupal\Core\Config\Importer\FinalMissingContentSubscriber.
 | 
			
		||||
    $original_dynamic_data['dependencies']['content'][] = $entity_three->getConfigDependencyName();
 | 
			
		||||
    $sync->write($dynamic_name, $original_dynamic_data);
 | 
			
		||||
 | 
			
		||||
    // Import.
 | 
			
		||||
    $this->configImporter->reset()->import();
 | 
			
		||||
    $this->assertEquals([], $this->configImporter->getErrors(), 'There were no errors during the import.');
 | 
			
		||||
    $this->assertEquals($entity_one->uuid(), \Drupal::state()->get('config_import_test.config_import_missing_content_one'), 'The missing content event is fired during configuration import.');
 | 
			
		||||
    $this->assertEquals($entity_two->uuid(), \Drupal::state()->get('config_import_test.config_import_missing_content_two'), 'The missing content event is fired during configuration import.');
 | 
			
		||||
    $original_dynamic_data = $storage->read($dynamic_name);
 | 
			
		||||
    $this->assertEquals([$entity_one->getConfigDependencyName(), $entity_two->getConfigDependencyName(), $entity_three->getConfigDependencyName()], $original_dynamic_data['dependencies']['content'], 'The imported configuration entity has the missing content entity dependency.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the missing content, config import and the block plugin manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\ConfigImporter::processMissingContent()
 | 
			
		||||
   * @see \Drupal\config_import_test\EventSubscriber
 | 
			
		||||
   */
 | 
			
		||||
  public function testMissingBlockContent(): void {
 | 
			
		||||
    $this->enableModules([
 | 
			
		||||
      'block',
 | 
			
		||||
      'block_content',
 | 
			
		||||
      'field',
 | 
			
		||||
      'text',
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->container->get('theme_installer')->install(['stark']);
 | 
			
		||||
    $this->installEntitySchema('block_content');
 | 
			
		||||
    $this->installConfig(['block_content']);
 | 
			
		||||
    // Create a block content type.
 | 
			
		||||
    $block_content_type = BlockContentType::create([
 | 
			
		||||
      'id' => 'test',
 | 
			
		||||
      'label' => 'Test block content',
 | 
			
		||||
      'description' => "Provides a block type",
 | 
			
		||||
    ]);
 | 
			
		||||
    $block_content_type->save();
 | 
			
		||||
    // And a block content entity.
 | 
			
		||||
    $block_content = BlockContent::create([
 | 
			
		||||
      'info' => 'Prototype',
 | 
			
		||||
      'type' => 'test',
 | 
			
		||||
      // Set the UUID to make asserting against missing test easy.
 | 
			
		||||
      'uuid' => '6376f337-fcbf-4b28-b30e-ed5b6932e692',
 | 
			
		||||
    ]);
 | 
			
		||||
    $block_content->save();
 | 
			
		||||
    $plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid();
 | 
			
		||||
    $block = $this->placeBlock($plugin_id);
 | 
			
		||||
 | 
			
		||||
    $storage = $this->container->get('config.storage');
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
 | 
			
		||||
    $this->copyConfig($storage, $sync);
 | 
			
		||||
 | 
			
		||||
    $block->delete();
 | 
			
		||||
    $block_content->delete();
 | 
			
		||||
    $block_content_type->delete();
 | 
			
		||||
 | 
			
		||||
    // Import.
 | 
			
		||||
    $this->logMessages = [];
 | 
			
		||||
    $config_importer = $this->configImporter();
 | 
			
		||||
    $config_importer->import();
 | 
			
		||||
    $this->assertNotContains('The "block_content:6376f337-fcbf-4b28-b30e-ed5b6932e692" block plugin was not found', $this->logMessages);
 | 
			
		||||
 | 
			
		||||
    // Ensure the expected message is generated when creating an instance of the
 | 
			
		||||
    // block.
 | 
			
		||||
    $instance = $this->container->get('plugin.manager.block')->createInstance($plugin_id);
 | 
			
		||||
    $this->assertContains('The "block_content:6376f337-fcbf-4b28-b30e-ed5b6932e692" block plugin was not found', $this->logMessages);
 | 
			
		||||
    $this->assertInstanceOf(Broken::class, $instance);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function log($level, $message, array $context = []): void {
 | 
			
		||||
    $message_placeholders = \Drupal::service('logger.log_message_parser')->parseMessagePlaceholders($message, $context);
 | 
			
		||||
    $this->logMessages[] = empty($message_placeholders) ? $message : strtr($message, $message_placeholders);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1064
									
								
								web/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1064
									
								
								web/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,284 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ConfigCollectionEvents;
 | 
			
		||||
use Drupal\Core\Config\InstallStorage;
 | 
			
		||||
use Drupal\Core\Config\PreExistingConfigException;
 | 
			
		||||
use Drupal\Core\Config\UnmetDependenciesException;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests installation of configuration objects in installation functionality.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 * @see \Drupal\Core\Config\ConfigInstaller
 | 
			
		||||
 */
 | 
			
		||||
class ConfigInstallTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'config_events_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    // Ensure the global variable being asserted by this test does not exist;
 | 
			
		||||
    // a previous test executed in this request/process might have set it.
 | 
			
		||||
    unset($GLOBALS['hook_config_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests module installation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testModuleInstallation(): void {
 | 
			
		||||
    $default_config = 'config_test.system';
 | 
			
		||||
    $default_configuration_entity = 'config_test.dynamic.dotted.default';
 | 
			
		||||
 | 
			
		||||
    // Verify that default module config does not exist before installation yet.
 | 
			
		||||
    $config = $this->config($default_config);
 | 
			
		||||
    $this->assertTrue($config->isNew());
 | 
			
		||||
    $config = $this->config($default_configuration_entity);
 | 
			
		||||
    $this->assertTrue($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Ensure that schema provided by modules that are not installed is not
 | 
			
		||||
    // available.
 | 
			
		||||
    $this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.some_schema'), 'Configuration schema for config_schema_test.some_schema does not exist.');
 | 
			
		||||
 | 
			
		||||
    // Install the test module.
 | 
			
		||||
    $this->installModules(['config_test']);
 | 
			
		||||
 | 
			
		||||
    // Verify that default module config exists.
 | 
			
		||||
    \Drupal::configFactory()->reset($default_config);
 | 
			
		||||
    \Drupal::configFactory()->reset($default_configuration_entity);
 | 
			
		||||
    $config = $this->config($default_config);
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $config = $this->config($default_configuration_entity);
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
 | 
			
		||||
    // Verify that config_test API hooks were invoked for the dynamic default
 | 
			
		||||
    // configuration entity.
 | 
			
		||||
    $this->assertFalse(isset($GLOBALS['hook_config_test']['load']));
 | 
			
		||||
    $this->assertTrue(isset($GLOBALS['hook_config_test']['presave']));
 | 
			
		||||
    $this->assertTrue(isset($GLOBALS['hook_config_test']['insert']));
 | 
			
		||||
    $this->assertFalse(isset($GLOBALS['hook_config_test']['update']));
 | 
			
		||||
    $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
 | 
			
		||||
    $this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
 | 
			
		||||
 | 
			
		||||
    // Install the schema test module.
 | 
			
		||||
    $this->enableModules(['config_schema_test']);
 | 
			
		||||
    $this->installConfig(['config_schema_test']);
 | 
			
		||||
 | 
			
		||||
    // After module installation the new schema should exist.
 | 
			
		||||
    $this->assertTrue(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.some_schema'), 'Configuration schema for config_schema_test.some_schema exists.');
 | 
			
		||||
 | 
			
		||||
    // Test that uninstalling configuration removes configuration schema.
 | 
			
		||||
    $this->config('core.extension')->set('module', [])->save();
 | 
			
		||||
    \Drupal::service('config.manager')->uninstall('module', 'config_test');
 | 
			
		||||
    $this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.some_schema'), 'Configuration schema for config_schema_test.some_schema does not exist.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that collections are ignored if the event does not return anything.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCollectionInstallationNoCollections(): void {
 | 
			
		||||
    // Install the test module.
 | 
			
		||||
    $this->enableModules(['config_collection_install_test']);
 | 
			
		||||
    $this->installConfig(['config_collection_install_test']);
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active_storage */
 | 
			
		||||
    $active_storage = \Drupal::service('config.storage');
 | 
			
		||||
    $this->assertEquals([], $active_storage->getAllCollectionNames());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests config objects in collections are installed as expected.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCollectionInstallationCollections(): void {
 | 
			
		||||
    $collections = [
 | 
			
		||||
      'another_collection',
 | 
			
		||||
      'collection.test1',
 | 
			
		||||
      'collection.test2',
 | 
			
		||||
    ];
 | 
			
		||||
    // Set the event listener to return three possible collections.
 | 
			
		||||
    // @see \Drupal\config_collection_install_test\EventSubscriber
 | 
			
		||||
    \Drupal::state()->set('config_collection_install_test.collection_names', $collections);
 | 
			
		||||
    // Install the test module.
 | 
			
		||||
    $this->enableModules(['config_collection_install_test']);
 | 
			
		||||
    $this->installConfig(['config_collection_install_test']);
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active_storage */
 | 
			
		||||
    $active_storage = \Drupal::service('config.storage');
 | 
			
		||||
    $this->assertEquals($collections, $active_storage->getAllCollectionNames());
 | 
			
		||||
    foreach ($collections as $collection) {
 | 
			
		||||
      $collection_storage = $active_storage->createCollection($collection);
 | 
			
		||||
      $data = $collection_storage->read('config_collection_install_test.test');
 | 
			
		||||
      $this->assertEquals($collection, $data['collection']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Tests that clashing configuration in collections is detected.
 | 
			
		||||
    try {
 | 
			
		||||
      \Drupal::service('module_installer')->install(['config_collection_clash_install_test']);
 | 
			
		||||
      $this->fail('Expected PreExistingConfigException not thrown.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (PreExistingConfigException $e) {
 | 
			
		||||
      $this->assertEquals('config_collection_clash_install_test', $e->getExtension());
 | 
			
		||||
      $this->assertEquals(['another_collection' => ['config_collection_install_test.test'], 'collection.test1' => ['config_collection_install_test.test'], 'collection.test2' => ['config_collection_install_test.test']], $e->getConfigObjects());
 | 
			
		||||
      $this->assertEquals('Configuration objects (another_collection/config_collection_install_test.test, collection/test1/config_collection_install_test.test, collection/test2/config_collection_install_test.test) provided by config_collection_clash_install_test already exist in active configuration', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test that the we can use the config installer to install all the
 | 
			
		||||
    // available default configuration in a particular collection for enabled
 | 
			
		||||
    // extensions.
 | 
			
		||||
    \Drupal::service('config.installer')->installCollectionDefaultConfig('entity');
 | 
			
		||||
    // The 'entity' collection will not exist because the 'config_test' module
 | 
			
		||||
    // is not enabled.
 | 
			
		||||
    $this->assertEquals($collections, $active_storage->getAllCollectionNames());
 | 
			
		||||
    // Enable the 'config_test' module and try again.
 | 
			
		||||
    $this->enableModules(['config_test']);
 | 
			
		||||
    \Drupal::service('config.installer')->installCollectionDefaultConfig('entity');
 | 
			
		||||
    $collections[] = 'entity';
 | 
			
		||||
    $this->assertEquals($collections, $active_storage->getAllCollectionNames());
 | 
			
		||||
    $collection_storage = $active_storage->createCollection('entity');
 | 
			
		||||
    $data = $collection_storage->read('config_test.dynamic.dotted.default');
 | 
			
		||||
    $this->assertSame(['label' => 'entity'], $data);
 | 
			
		||||
 | 
			
		||||
    // Test that the config manager uninstalls configuration from collections
 | 
			
		||||
    // as expected.
 | 
			
		||||
    \Drupal::state()->set('config_events_test.all_events', []);
 | 
			
		||||
    $this->container->get('config.manager')->uninstall('module', 'config_collection_install_test');
 | 
			
		||||
    $all_events = \Drupal::state()->get('config_events_test.all_events');
 | 
			
		||||
    $this->assertArrayHasKey(ConfigCollectionEvents::DELETE_IN_COLLECTION, $all_events);
 | 
			
		||||
    // The delete-in-collection event has been triggered 3 times.
 | 
			
		||||
    $this->assertCount(3, $all_events[ConfigCollectionEvents::DELETE_IN_COLLECTION]['config_collection_install_test.test']);
 | 
			
		||||
    $event_collections = [];
 | 
			
		||||
    foreach ($all_events[ConfigCollectionEvents::DELETE_IN_COLLECTION]['config_collection_install_test.test'] as $event) {
 | 
			
		||||
      $event_collections[] = $event['original_config_data']['collection'];
 | 
			
		||||
    }
 | 
			
		||||
    $this->assertSame(['another_collection', 'collection.test1', 'collection.test2'], $event_collections);
 | 
			
		||||
    $this->assertEquals(['entity'], $active_storage->getAllCollectionNames());
 | 
			
		||||
 | 
			
		||||
    \Drupal::state()->set('config_events_test.all_events', []);
 | 
			
		||||
    $this->container->get('config.manager')->uninstall('module', 'config_test');
 | 
			
		||||
    $this->assertEquals([], $active_storage->getAllCollectionNames());
 | 
			
		||||
    $all_events = \Drupal::state()->get('config_events_test.all_events');
 | 
			
		||||
    $this->assertArrayHasKey(ConfigCollectionEvents::DELETE_IN_COLLECTION, $all_events);
 | 
			
		||||
    $this->assertCount(1, $all_events[ConfigCollectionEvents::DELETE_IN_COLLECTION]['config_test.dynamic.dotted.default']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests collections which do not support config entities install correctly.
 | 
			
		||||
   *
 | 
			
		||||
   * Config entity detection during config installation is done by matching
 | 
			
		||||
   * config name prefixes. If a collection provides a configuration with a
 | 
			
		||||
   * matching name but does not support config entities it should be created
 | 
			
		||||
   * using simple configuration.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCollectionInstallationCollectionConfigEntity(): void {
 | 
			
		||||
    $collections = [
 | 
			
		||||
      'entity',
 | 
			
		||||
    ];
 | 
			
		||||
    \Drupal::state()->set('config_collection_install_test.collection_names', $collections);
 | 
			
		||||
    // Install the test module.
 | 
			
		||||
    $this->installModules(['config_test', 'config_collection_install_test']);
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active_storage */
 | 
			
		||||
    $active_storage = \Drupal::service('config.storage');
 | 
			
		||||
    $this->assertEquals($collections, $active_storage->getAllCollectionNames());
 | 
			
		||||
    $collection_storage = $active_storage->createCollection('entity');
 | 
			
		||||
 | 
			
		||||
    // The config_test.dynamic.dotted.default configuration object saved in the
 | 
			
		||||
    // active store should be a configuration entity complete with UUID. Because
 | 
			
		||||
    // the entity collection does not support configuration entities the
 | 
			
		||||
    // configuration object stored there with the same name should only contain
 | 
			
		||||
    // a label.
 | 
			
		||||
    $name = 'config_test.dynamic.dotted.default';
 | 
			
		||||
    $data = $active_storage->read($name);
 | 
			
		||||
    $this->assertTrue(isset($data['uuid']));
 | 
			
		||||
    $data = $collection_storage->read($name);
 | 
			
		||||
    $this->assertSame(['label' => 'entity'], $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the configuration with unmet dependencies is not installed.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDependencyChecking(): void {
 | 
			
		||||
    $this->installModules(['config_test']);
 | 
			
		||||
    try {
 | 
			
		||||
      $this->installModules(['config_install_dependency_test']);
 | 
			
		||||
      $this->fail('Expected UnmetDependenciesException not thrown.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (UnmetDependenciesException $e) {
 | 
			
		||||
      $this->assertEquals('config_install_dependency_test', $e->getExtension());
 | 
			
		||||
      $this->assertEquals(['config_test.dynamic.other_module_test_with_dependency' => ['config_other_module_config_test', 'config_test.dynamic.dotted.english']], $e->getConfigObjects());
 | 
			
		||||
      $this->assertEquals('Configuration objects provided by <em class="placeholder">config_install_dependency_test</em> have unmet dependencies: <em class="placeholder">config_test.dynamic.other_module_test_with_dependency (config_other_module_config_test, config_test.dynamic.dotted.english)</em>', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      $this->installModules(['config_install_double_dependency_test']);
 | 
			
		||||
      $this->fail('Expected UnmetDependenciesException not thrown.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (UnmetDependenciesException $e) {
 | 
			
		||||
      $this->assertEquals('config_install_double_dependency_test', $e->getExtension());
 | 
			
		||||
      $this->assertEquals(['config_test.dynamic.other_module_test_with_dependency' => ['config_other_module_config_test', 'config_test.dynamic.dotted.english']], $e->getConfigObjects());
 | 
			
		||||
      $this->assertEquals('Configuration objects provided by <em class="placeholder">config_install_double_dependency_test</em> have unmet dependencies: <em class="placeholder">config_test.dynamic.other_module_test_with_dependency (config_other_module_config_test, config_test.dynamic.dotted.english)</em>', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
    $this->installModules(['config_test_language']);
 | 
			
		||||
    try {
 | 
			
		||||
      $this->installModules(['config_install_dependency_test']);
 | 
			
		||||
      $this->fail('Expected UnmetDependenciesException not thrown.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (UnmetDependenciesException $e) {
 | 
			
		||||
      $this->assertEquals('config_install_dependency_test', $e->getExtension());
 | 
			
		||||
      $this->assertEquals(['config_test.dynamic.other_module_test_with_dependency' => ['config_other_module_config_test']], $e->getConfigObjects());
 | 
			
		||||
      $this->assertEquals('Configuration objects provided by <em class="placeholder">config_install_dependency_test</em> have unmet dependencies: <em class="placeholder">config_test.dynamic.other_module_test_with_dependency (config_other_module_config_test)</em>', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
    $this->installModules(['config_other_module_config_test']);
 | 
			
		||||
    $this->installModules(['config_install_dependency_test']);
 | 
			
		||||
    $entity = \Drupal::entityTypeManager()->getStorage('config_test')->load('other_module_test_with_dependency');
 | 
			
		||||
    $this->assertNotEmpty($entity, 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.');
 | 
			
		||||
    // Ensure that dependencies can be added during module installation by
 | 
			
		||||
    // hooks.
 | 
			
		||||
    $this->assertSame('config_install_dependency_test', $entity->getDependencies()['module'][0]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests imported configuration entities with/without language information.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLanguage(): void {
 | 
			
		||||
    $this->installModules(['config_test_language']);
 | 
			
		||||
    // Test imported configuration with implicit language code.
 | 
			
		||||
    $storage = new InstallStorage();
 | 
			
		||||
    $data = $storage->read('config_test.dynamic.dotted.english');
 | 
			
		||||
    $this->assertTrue(!isset($data['langcode']));
 | 
			
		||||
    $this->assertEquals('en', $this->config('config_test.dynamic.dotted.english')->get('langcode'));
 | 
			
		||||
 | 
			
		||||
    // Test imported configuration with explicit language code.
 | 
			
		||||
    $data = $storage->read('config_test.dynamic.dotted.french');
 | 
			
		||||
    $this->assertEquals('fr', $data['langcode']);
 | 
			
		||||
    $this->assertEquals('fr', $this->config('config_test.dynamic.dotted.french')->get('langcode'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests installing configuration where the filename and ID do not match.
 | 
			
		||||
   */
 | 
			
		||||
  public function testIdMisMatch(): void {
 | 
			
		||||
    $this->expectException(\LogicException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The configuration name "config_test.dynamic.no_id_match" does not match the ID "does_not_match"');
 | 
			
		||||
    $this->installModules(['config_test_id_mismatch']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Installs a module.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $modules
 | 
			
		||||
   *   The module names.
 | 
			
		||||
   */
 | 
			
		||||
  protected function installModules(array $modules): void {
 | 
			
		||||
    $this->container->get('module_installer')->install($modules);
 | 
			
		||||
    $this->container = \Drupal::getContainer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,129 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\language\Entity\ConfigurableLanguage;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Confirm that language overrides work.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigLanguageOverrideTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'user',
 | 
			
		||||
    'language',
 | 
			
		||||
    'config_test',
 | 
			
		||||
    'system',
 | 
			
		||||
    'field',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['config_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests locale override based on language.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigLanguageOverride(): void {
 | 
			
		||||
    // The language module implements a config factory override object that
 | 
			
		||||
    // overrides configuration when the Language module is enabled. This test
 | 
			
		||||
    // ensures that English overrides work.
 | 
			
		||||
    \Drupal::languageManager()->setConfigOverrideLanguage(\Drupal::languageManager()->getLanguage('en'));
 | 
			
		||||
    $config = \Drupal::config('config_test.system');
 | 
			
		||||
    $this->assertSame('en bar', $config->get('foo'));
 | 
			
		||||
 | 
			
		||||
    // Ensure that the raw data is not translated.
 | 
			
		||||
    $raw = $config->getRawData();
 | 
			
		||||
    $this->assertSame('bar', $raw['foo']);
 | 
			
		||||
 | 
			
		||||
    ConfigurableLanguage::createFromLangcode('fr')->save();
 | 
			
		||||
    ConfigurableLanguage::createFromLangcode('de')->save();
 | 
			
		||||
 | 
			
		||||
    \Drupal::languageManager()->setConfigOverrideLanguage(\Drupal::languageManager()->getLanguage('fr'));
 | 
			
		||||
    $config = \Drupal::config('config_test.system');
 | 
			
		||||
    $this->assertSame('fr bar', $config->get('foo'));
 | 
			
		||||
 | 
			
		||||
    \Drupal::languageManager()->setConfigOverrideLanguage(\Drupal::languageManager()->getLanguage('de'));
 | 
			
		||||
    $config = \Drupal::config('config_test.system');
 | 
			
		||||
    $this->assertSame('de bar', $config->get('foo'));
 | 
			
		||||
 | 
			
		||||
    // Test overrides of completely new configuration objects. In normal runtime
 | 
			
		||||
    // this should only happen for configuration entities as we should not be
 | 
			
		||||
    // creating simple configuration objects on the fly.
 | 
			
		||||
    \Drupal::languageManager()
 | 
			
		||||
      ->getLanguageConfigOverride('de', 'config_test.new')
 | 
			
		||||
      ->set('language', 'override')
 | 
			
		||||
      ->save();
 | 
			
		||||
    $config = \Drupal::config('config_test.new');
 | 
			
		||||
    $this->assertTrue($config->isNew(), 'The configuration object config_test.new is new');
 | 
			
		||||
    $this->assertSame('override', $config->get('language'));
 | 
			
		||||
    $this->assertNull($config->getOriginal('language', FALSE));
 | 
			
		||||
 | 
			
		||||
    // Test how overrides react to base configuration changes. Set up some base
 | 
			
		||||
    // values.
 | 
			
		||||
    \Drupal::configFactory()->getEditable('config_test.foo')
 | 
			
		||||
      ->set('value', ['key' => 'original'])
 | 
			
		||||
      ->set('label', 'Original')
 | 
			
		||||
      // `label` is translatable, hence a `langcode` is required.
 | 
			
		||||
      // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
 | 
			
		||||
      ->set('langcode', 'en')
 | 
			
		||||
      ->save();
 | 
			
		||||
    \Drupal::languageManager()
 | 
			
		||||
      ->getLanguageConfigOverride('de', 'config_test.foo')
 | 
			
		||||
      ->set('value', ['key' => 'override'])
 | 
			
		||||
      ->set('label', 'Override')
 | 
			
		||||
      ->save();
 | 
			
		||||
    \Drupal::languageManager()
 | 
			
		||||
      ->getLanguageConfigOverride('fr', 'config_test.foo')
 | 
			
		||||
      ->set('value', ['key' => 'override'])
 | 
			
		||||
      ->save();
 | 
			
		||||
    \Drupal::configFactory()->clearStaticCache();
 | 
			
		||||
    $config = \Drupal::config('config_test.foo');
 | 
			
		||||
    $this->assertSame(['key' => 'override'], $config->get('value'));
 | 
			
		||||
 | 
			
		||||
    // Ensure renaming the config will rename the override.
 | 
			
		||||
    \Drupal::languageManager()->setConfigOverrideLanguage(\Drupal::languageManager()->getLanguage('en'));
 | 
			
		||||
    \Drupal::configFactory()->rename('config_test.foo', 'config_test.bar');
 | 
			
		||||
    $config = \Drupal::config('config_test.bar');
 | 
			
		||||
    $this->assertEquals(['key' => 'original'], $config->get('value'));
 | 
			
		||||
    $override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'config_test.foo');
 | 
			
		||||
    $this->assertTrue($override->isNew());
 | 
			
		||||
    $this->assertNull($override->get('value'));
 | 
			
		||||
    $override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'config_test.bar');
 | 
			
		||||
    $this->assertFalse($override->isNew());
 | 
			
		||||
    $this->assertEquals(['key' => 'override'], $override->get('value'));
 | 
			
		||||
    $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'config_test.bar');
 | 
			
		||||
    $this->assertFalse($override->isNew());
 | 
			
		||||
    $this->assertEquals(['key' => 'override'], $override->get('value'));
 | 
			
		||||
 | 
			
		||||
    // Ensure changing data in the config will update the overrides.
 | 
			
		||||
    $config = \Drupal::configFactory()->getEditable('config_test.bar')->clear('value.key')->save();
 | 
			
		||||
    $this->assertEquals([], $config->get('value'));
 | 
			
		||||
    $override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'config_test.bar');
 | 
			
		||||
    $this->assertFalse($override->isNew());
 | 
			
		||||
    $this->assertNull($override->get('value'));
 | 
			
		||||
    // The French override will become empty and therefore removed.
 | 
			
		||||
    $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'config_test.bar');
 | 
			
		||||
    $this->assertTrue($override->isNew());
 | 
			
		||||
    $this->assertNull($override->get('value'));
 | 
			
		||||
 | 
			
		||||
    // Ensure deleting the config will delete the override.
 | 
			
		||||
    \Drupal::configFactory()->getEditable('config_test.bar')->delete();
 | 
			
		||||
    $override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'config_test.bar');
 | 
			
		||||
    $this->assertTrue($override->isNew());
 | 
			
		||||
    $this->assertNull($override->get('value'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,57 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests module overrides of configuration using event subscribers.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigModuleOverridesTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'config', 'config_override_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests simple module overrides of configuration using event subscribers.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSimpleModuleOverrides(): void {
 | 
			
		||||
    $GLOBALS['config_test_run_module_overrides'] = TRUE;
 | 
			
		||||
    $name = 'system.site';
 | 
			
		||||
    $overridden_name = 'Wow overridden site name';
 | 
			
		||||
    $non_overridden_name = 'Wow this name is on disk mkay';
 | 
			
		||||
    $overridden_slogan = 'Yay for overrides!';
 | 
			
		||||
    $non_overridden_slogan = 'Yay for defaults!';
 | 
			
		||||
    $config_factory = $this->container->get('config.factory');
 | 
			
		||||
    $config_factory
 | 
			
		||||
      ->getEditable($name)
 | 
			
		||||
      ->set('name', $non_overridden_name)
 | 
			
		||||
      ->set('slogan', $non_overridden_slogan)
 | 
			
		||||
      // `name` and `slogan` are translatable, hence a `langcode` is required.
 | 
			
		||||
      // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
 | 
			
		||||
      ->set('langcode', 'en')
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($non_overridden_name, $config_factory->get('system.site')->getOriginal('name', FALSE));
 | 
			
		||||
    $this->assertEquals($non_overridden_slogan, $config_factory->get('system.site')->getOriginal('slogan', FALSE));
 | 
			
		||||
    $this->assertEquals($overridden_name, $config_factory->get('system.site')->get('name'));
 | 
			
		||||
    $this->assertEquals($overridden_slogan, $config_factory->get('system.site')->get('slogan'));
 | 
			
		||||
 | 
			
		||||
    // Test overrides of completely new configuration objects. In normal runtime
 | 
			
		||||
    // this should only happen for configuration entities as we should not be
 | 
			
		||||
    // creating simple configuration objects on the fly.
 | 
			
		||||
    $config = $config_factory->get('config_override_test.new');
 | 
			
		||||
    $this->assertTrue($config->isNew(), 'The configuration object config_override_test.new is new');
 | 
			
		||||
    $this->assertSame('override', $config->get('module'));
 | 
			
		||||
    $this->assertNull($config->getOriginal('module', FALSE));
 | 
			
		||||
 | 
			
		||||
    unset($GLOBALS['config_test_run_module_overrides']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,142 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests configuration overrides via $config in settings.php.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigOverrideTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests configuration override.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfOverride(): void {
 | 
			
		||||
    $expected_original_data = [
 | 
			
		||||
      'foo' => 'bar',
 | 
			
		||||
      'baz' => NULL,
 | 
			
		||||
      '404' => 'herp',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Set globals before installing to prove that the installed file does not
 | 
			
		||||
    // contain these values.
 | 
			
		||||
    $overrides['config_test.system']['foo'] = 'overridden';
 | 
			
		||||
    $overrides['config_test.system']['baz'] = 'injected';
 | 
			
		||||
    $overrides['config_test.system']['404'] = 'something';
 | 
			
		||||
    $GLOBALS['config'] = $overrides;
 | 
			
		||||
 | 
			
		||||
    $this->installConfig(['config_test']);
 | 
			
		||||
 | 
			
		||||
    // Verify that the original configuration data exists. Have to read storage
 | 
			
		||||
    // directly otherwise overrides will apply.
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    $data = $active->read('config_test.system');
 | 
			
		||||
    $this->assertSame($expected_original_data['foo'], $data['foo']);
 | 
			
		||||
    $this->assertFalse(isset($data['baz']));
 | 
			
		||||
    $this->assertSame($expected_original_data['404'], $data['404']);
 | 
			
		||||
 | 
			
		||||
    // Get the configuration object with overrides.
 | 
			
		||||
    $config = \Drupal::configFactory()->get('config_test.system');
 | 
			
		||||
 | 
			
		||||
    // Verify that it contains the overridden data from $config.
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['foo'], $config->get('foo'));
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['baz'], $config->get('baz'));
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['404'], $config->get('404'));
 | 
			
		||||
 | 
			
		||||
    // Get the configuration object which does not have overrides.
 | 
			
		||||
    $config = \Drupal::configFactory()->getEditable('config_test.system');
 | 
			
		||||
 | 
			
		||||
    // Verify that it does not contains the overridden data from $config.
 | 
			
		||||
    $this->assertSame($expected_original_data['foo'], $config->get('foo'));
 | 
			
		||||
    $this->assertNull($config->get('baz'));
 | 
			
		||||
    $this->assertSame($expected_original_data['404'], $config->get('404'));
 | 
			
		||||
 | 
			
		||||
    // Set the value for 'baz' (on the original data).
 | 
			
		||||
    $expected_original_data['baz'] = 'original baz';
 | 
			
		||||
    $config->set('baz', $expected_original_data['baz']);
 | 
			
		||||
 | 
			
		||||
    // Set the value for '404' (on the original data).
 | 
			
		||||
    $expected_original_data['404'] = 'original 404';
 | 
			
		||||
    $config->set('404', $expected_original_data['404']);
 | 
			
		||||
 | 
			
		||||
    // Save the configuration object (having overrides applied).
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    // Reload it and verify that it still contains overridden data from $config.
 | 
			
		||||
    $config = \Drupal::config('config_test.system');
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['foo'], $config->get('foo'));
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['baz'], $config->get('baz'));
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['404'], $config->get('404'));
 | 
			
		||||
 | 
			
		||||
    // Verify that raw config data has changed.
 | 
			
		||||
    $this->assertSame($expected_original_data['foo'], $config->getOriginal('foo', FALSE));
 | 
			
		||||
    $this->assertSame($expected_original_data['baz'], $config->getOriginal('baz', FALSE));
 | 
			
		||||
    $this->assertSame($expected_original_data['404'], $config->getOriginal('404', FALSE));
 | 
			
		||||
 | 
			
		||||
    // Write file to sync.
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
    $expected_new_data = [
 | 
			
		||||
      'foo' => 'new_foo',
 | 
			
		||||
      '404' => 'try again',
 | 
			
		||||
    ];
 | 
			
		||||
    $sync->write('config_test.system', $expected_new_data);
 | 
			
		||||
 | 
			
		||||
    // Import changed data from sync to active.
 | 
			
		||||
    $this->configImporter()->import();
 | 
			
		||||
    $data = $active->read('config_test.system');
 | 
			
		||||
 | 
			
		||||
    // Verify that the new configuration data exists. Have to read storage
 | 
			
		||||
    // directly otherwise overrides will apply.
 | 
			
		||||
    $this->assertSame($expected_new_data['foo'], $data['foo']);
 | 
			
		||||
    $this->assertFalse(isset($data['baz']));
 | 
			
		||||
    $this->assertSame($expected_new_data['404'], $data['404']);
 | 
			
		||||
 | 
			
		||||
    // Verify that the overrides are still working.
 | 
			
		||||
    $config = \Drupal::config('config_test.system');
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['foo'], $config->get('foo'));
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['baz'], $config->get('baz'));
 | 
			
		||||
    $this->assertSame($overrides['config_test.system']['404'], $config->get('404'));
 | 
			
		||||
 | 
			
		||||
    // Test overrides of completely new configuration objects. In normal runtime
 | 
			
		||||
    // this should only happen for configuration entities as we should not be
 | 
			
		||||
    // creating simple configuration objects on the fly.
 | 
			
		||||
    $GLOBALS['config']['config_test.new']['key'] = 'override';
 | 
			
		||||
    $config = \Drupal::config('config_test.new');
 | 
			
		||||
    $this->assertTrue($config->isNew(), 'The configuration object config_test.new is new');
 | 
			
		||||
    $this->assertSame('override', $config->get('key'));
 | 
			
		||||
    $config_raw = \Drupal::configFactory()->getEditable('config_test.new');
 | 
			
		||||
    $this->assertNull($config_raw->get('key'));
 | 
			
		||||
    $config_raw
 | 
			
		||||
      ->set('key', 'raw')
 | 
			
		||||
      ->set('new_key', 'new_value')
 | 
			
		||||
      ->save();
 | 
			
		||||
    // Ensure override is preserved but all other data has been updated
 | 
			
		||||
    // accordingly.
 | 
			
		||||
    $config = \Drupal::config('config_test.new');
 | 
			
		||||
    $this->assertFalse($config->isNew(), 'The configuration object config_test.new is not new');
 | 
			
		||||
    $this->assertSame('override', $config->get('key'));
 | 
			
		||||
    $this->assertSame('new_value', $config->get('new_key'));
 | 
			
		||||
    $raw_data = $config->getRawData();
 | 
			
		||||
    $this->assertSame('raw', $raw_data['key']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,112 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Language\Language;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that config overrides are applied in the correct order.
 | 
			
		||||
 *
 | 
			
		||||
 * Overrides should be applied in the following order, from lowest priority
 | 
			
		||||
 * to highest:
 | 
			
		||||
 * - Language overrides.
 | 
			
		||||
 * - Module overrides.
 | 
			
		||||
 * - settings.php overrides.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigOverridesPriorityTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'config',
 | 
			
		||||
    'config_override_test',
 | 
			
		||||
    'language',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the order of config overrides.
 | 
			
		||||
   */
 | 
			
		||||
  public function testOverridePriorities(): void {
 | 
			
		||||
    $GLOBALS['config_test_run_module_overrides'] = FALSE;
 | 
			
		||||
 | 
			
		||||
    $non_overridden_mail = 'site@example.com';
 | 
			
		||||
    $language_overridden_mail = 'french@example.com';
 | 
			
		||||
 | 
			
		||||
    $language_overridden_name = 'French site name';
 | 
			
		||||
    $module_overridden_name = 'Wow overridden site name';
 | 
			
		||||
    $non_overridden_name = 'Wow this name is on disk mkay';
 | 
			
		||||
 | 
			
		||||
    $module_overridden_slogan = 'Yay for overrides!';
 | 
			
		||||
    $non_overridden_slogan = 'Yay for defaults!';
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
 | 
			
		||||
    $config_factory = $this->container->get('config.factory');
 | 
			
		||||
    $config_factory
 | 
			
		||||
      ->getEditable('system.site')
 | 
			
		||||
      ->set('name', $non_overridden_name)
 | 
			
		||||
      ->set('slogan', $non_overridden_slogan)
 | 
			
		||||
      ->set('mail', $non_overridden_mail)
 | 
			
		||||
      ->set('weight_select_max', 50)
 | 
			
		||||
      // `name` and `slogan` are translatable, hence a `langcode` is required.
 | 
			
		||||
      // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
 | 
			
		||||
      ->set('langcode', 'en')
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    // Ensure that no overrides are applying.
 | 
			
		||||
    $this->assertEquals($non_overridden_name, $config_factory->get('system.site')->get('name'));
 | 
			
		||||
    $this->assertEquals($non_overridden_slogan, $config_factory->get('system.site')->get('slogan'));
 | 
			
		||||
    $this->assertEquals($non_overridden_mail, $config_factory->get('system.site')->get('mail'));
 | 
			
		||||
    $this->assertEquals(50, $config_factory->get('system.site')->get('weight_select_max'));
 | 
			
		||||
 | 
			
		||||
    // Override using language.
 | 
			
		||||
    $language = new Language([
 | 
			
		||||
      'name' => 'French',
 | 
			
		||||
      'id' => 'fr',
 | 
			
		||||
    ]);
 | 
			
		||||
    \Drupal::languageManager()->setConfigOverrideLanguage($language);
 | 
			
		||||
    \Drupal::languageManager()
 | 
			
		||||
      ->getLanguageConfigOverride($language->getId(), 'system.site')
 | 
			
		||||
      ->set('name', $language_overridden_name)
 | 
			
		||||
      ->set('mail', $language_overridden_mail)
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($language_overridden_name, $config_factory->get('system.site')->get('name'));
 | 
			
		||||
    $this->assertEquals($non_overridden_slogan, $config_factory->get('system.site')->get('slogan'));
 | 
			
		||||
    $this->assertEquals($language_overridden_mail, $config_factory->get('system.site')->get('mail'));
 | 
			
		||||
    $this->assertEquals(50, $config_factory->get('system.site')->get('weight_select_max'));
 | 
			
		||||
 | 
			
		||||
    // Enable module overrides. Do not override system.site:mail to prove that
 | 
			
		||||
    // the language override still applies.
 | 
			
		||||
    $GLOBALS['config_test_run_module_overrides'] = TRUE;
 | 
			
		||||
    $config_factory->reset('system.site');
 | 
			
		||||
    $this->assertEquals($module_overridden_name, $config_factory->get('system.site')->get('name'));
 | 
			
		||||
    $this->assertEquals($module_overridden_slogan, $config_factory->get('system.site')->get('slogan'));
 | 
			
		||||
    $this->assertEquals($language_overridden_mail, $config_factory->get('system.site')->get('mail'));
 | 
			
		||||
    $this->assertEquals(50, $config_factory->get('system.site')->get('weight_select_max'));
 | 
			
		||||
 | 
			
		||||
    // Configure a global override to simulate overriding using settings.php. Do
 | 
			
		||||
    // not override system.site:mail or system.site:slogan to prove that the
 | 
			
		||||
    // language and module overrides still apply.
 | 
			
		||||
    $GLOBALS['config']['system.site']['name'] = 'Site name global conf override';
 | 
			
		||||
    $config_factory->reset('system.site');
 | 
			
		||||
    $this->assertEquals('Site name global conf override', $config_factory->get('system.site')->get('name'));
 | 
			
		||||
    $this->assertEquals($module_overridden_slogan, $config_factory->get('system.site')->get('slogan'));
 | 
			
		||||
    $this->assertEquals($language_overridden_mail, $config_factory->get('system.site')->get('mail'));
 | 
			
		||||
    $this->assertEquals(50, $config_factory->get('system.site')->get('weight_select_max'));
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($non_overridden_name, $config_factory->get('system.site')->getOriginal('name', FALSE));
 | 
			
		||||
    $this->assertEquals($non_overridden_slogan, $config_factory->get('system.site')->getOriginal('slogan', FALSE));
 | 
			
		||||
    $this->assertEquals($non_overridden_mail, $config_factory->get('system.site')->getOriginal('mail', FALSE));
 | 
			
		||||
    $this->assertEquals(50, $config_factory->get('system.site')->getOriginal('weight_select_max', FALSE));
 | 
			
		||||
 | 
			
		||||
    unset($GLOBALS['config_test_run_module_overrides']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests config schema deprecation.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 * @group legacy
 | 
			
		||||
 */
 | 
			
		||||
class ConfigSchemaDeprecationTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'config_schema_deprecated_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests config schema deprecation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigSchemaDeprecation(): void {
 | 
			
		||||
    $this->expectDeprecation('The \'complex_structure_deprecated\' config schema is deprecated in drupal:9.1.0 and is removed from drupal 10.0.0. Use the \'complex_structure\' config schema instead. See http://drupal.org/node/the-change-notice-nid.');
 | 
			
		||||
    $config = $this->config('config_schema_deprecated_test.settings');
 | 
			
		||||
    $config
 | 
			
		||||
      ->set('complex_structure_deprecated.type', 'fruits')
 | 
			
		||||
      ->set('complex_structure_deprecated.products', ['apricot', 'apple'])
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->assertSame(['type' => 'fruits', 'products' => ['apricot', 'apple']], $config->get('complex_structure_deprecated'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,880 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\FileStorage;
 | 
			
		||||
use Drupal\Core\Config\InstallStorage;
 | 
			
		||||
use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
 | 
			
		||||
use Drupal\Core\Config\Schema\Ignore;
 | 
			
		||||
use Drupal\Core\Config\Schema\Mapping;
 | 
			
		||||
use Drupal\Core\Config\Schema\Undefined;
 | 
			
		||||
use Drupal\Core\TypedData\Plugin\DataType\StringData;
 | 
			
		||||
use Drupal\Core\TypedData\Type\IntegerInterface;
 | 
			
		||||
use Drupal\Core\TypedData\Type\StringInterface;
 | 
			
		||||
use Drupal\image\ImageEffectInterface;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests schema for configuration objects.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigSchemaTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'language',
 | 
			
		||||
    'field',
 | 
			
		||||
    'image',
 | 
			
		||||
    'config_test',
 | 
			
		||||
    'config_schema_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system', 'image', 'config_schema_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the basic metadata retrieval layer.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSchemaMapping(): void {
 | 
			
		||||
    // Nonexistent configuration key will have Undefined as metadata.
 | 
			
		||||
    $this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.no_such_key'));
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.no_such_key');
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Undefined';
 | 
			
		||||
    $expected['class'] = Undefined::class;
 | 
			
		||||
    $expected['type'] = 'undefined';
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for nonexistent configuration.');
 | 
			
		||||
 | 
			
		||||
    // Configuration file without schema will return Undefined as well.
 | 
			
		||||
    $this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.no_schema'));
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.no_schema');
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for configuration with no schema.');
 | 
			
		||||
 | 
			
		||||
    // Configuration file with only some schema.
 | 
			
		||||
    $this->assertTrue(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.some_schema'));
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.some_schema');
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Schema test data';
 | 
			
		||||
    $expected['class'] = Mapping::class;
 | 
			
		||||
    $expected['mapping']['langcode']['type'] = 'langcode';
 | 
			
		||||
    $expected['mapping']['langcode']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['_core']['type'] = '_core_config_info';
 | 
			
		||||
    $expected['mapping']['_core']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['test_item'] = ['label' => 'Test item'];
 | 
			
		||||
    $expected['mapping']['test_list'] = ['label' => 'Test list'];
 | 
			
		||||
    $expected['type'] = 'config_schema_test.some_schema';
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['constraints'] = [
 | 
			
		||||
      'ValidKeys' => '<infer>',
 | 
			
		||||
      'LangcodeRequiredIfTranslatableValues' => NULL,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for configuration with only some schema.');
 | 
			
		||||
 | 
			
		||||
    // Check type detection on elements with undefined types.
 | 
			
		||||
    $config = \Drupal::service('config.typed')->get('config_schema_test.some_schema');
 | 
			
		||||
    $definition = $config->get('test_item')->getDataDefinition()->toArray();
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Test item';
 | 
			
		||||
    $expected['class'] = Undefined::class;
 | 
			
		||||
    $expected['type'] = 'undefined';
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['requiredKey'] = TRUE;
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Automatic type detected for a scalar is undefined.');
 | 
			
		||||
    $definition = $config->get('test_list')->getDataDefinition()->toArray();
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Test list';
 | 
			
		||||
    $expected['class'] = Undefined::class;
 | 
			
		||||
    $expected['type'] = 'undefined';
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['requiredKey'] = TRUE;
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Automatic type detected for a list is undefined.');
 | 
			
		||||
    $definition = $config->get('test_no_schema')->getDataDefinition()->toArray();
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Undefined';
 | 
			
		||||
    $expected['class'] = Undefined::class;
 | 
			
		||||
    $expected['type'] = 'undefined';
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Automatic type detected for an undefined integer is undefined.');
 | 
			
		||||
 | 
			
		||||
    // Simple case, straight metadata.
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('system.maintenance');
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Maintenance mode';
 | 
			
		||||
    $expected['class'] = Mapping::class;
 | 
			
		||||
    $expected['mapping']['message'] = [
 | 
			
		||||
      'label' => 'Message to display when in maintenance mode',
 | 
			
		||||
      'type' => 'text',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['mapping']['langcode'] = [
 | 
			
		||||
      'type' => 'langcode',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['mapping']['langcode']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['_core']['type'] = '_core_config_info';
 | 
			
		||||
    $expected['mapping']['_core']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['type'] = 'system.maintenance';
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['constraints'] = [
 | 
			
		||||
      'ValidKeys' => '<infer>',
 | 
			
		||||
      'FullyValidatable' => NULL,
 | 
			
		||||
      'LangcodeRequiredIfTranslatableValues' => NULL,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for system.maintenance');
 | 
			
		||||
 | 
			
		||||
    // Mixed schema with ignore elements.
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.ignore');
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Ignore test';
 | 
			
		||||
    $expected['class'] = Mapping::class;
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 | 
			
		||||
    $expected['mapping']['langcode'] = [
 | 
			
		||||
      'type' => 'langcode',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['mapping']['langcode']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['_core']['type'] = '_core_config_info';
 | 
			
		||||
    $expected['mapping']['_core']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['label'] = [
 | 
			
		||||
      'label' => 'Label',
 | 
			
		||||
      'type' => 'label',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['mapping']['irrelevant'] = [
 | 
			
		||||
      'label' => 'Irrelevant',
 | 
			
		||||
      'type' => 'ignore',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['mapping']['indescribable'] = [
 | 
			
		||||
      'label' => 'Indescribable',
 | 
			
		||||
      'type' => 'ignore',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['mapping']['weight'] = [
 | 
			
		||||
      'label' => 'Weight',
 | 
			
		||||
      'type' => 'weight',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['type'] = 'config_schema_test.ignore';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['constraints'] = [
 | 
			
		||||
      'ValidKeys' => '<infer>',
 | 
			
		||||
      'LangcodeRequiredIfTranslatableValues' => NULL,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($expected, $definition);
 | 
			
		||||
 | 
			
		||||
    // The ignore elements themselves.
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('irrelevant')->getDataDefinition()->toArray();
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['type'] = 'ignore';
 | 
			
		||||
    $expected['label'] = 'Irrelevant';
 | 
			
		||||
    $expected['class'] = Ignore::class;
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['requiredKey'] = TRUE;
 | 
			
		||||
    $this->assertEquals($expected, $definition);
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('indescribable')->getDataDefinition()->toArray();
 | 
			
		||||
    $expected['label'] = 'Indescribable';
 | 
			
		||||
    $this->assertEquals($expected, $definition);
 | 
			
		||||
 | 
			
		||||
    // More complex case, generic type. Metadata for image style.
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('image.style.large');
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Image style';
 | 
			
		||||
    $expected['class'] = Mapping::class;
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['mapping']['name']['type'] = 'machine_name';
 | 
			
		||||
    $expected['mapping']['uuid']['type'] = 'uuid';
 | 
			
		||||
    $expected['mapping']['uuid']['label'] = 'UUID';
 | 
			
		||||
    $expected['mapping']['langcode']['type'] = 'langcode';
 | 
			
		||||
    $expected['mapping']['status']['type'] = 'boolean';
 | 
			
		||||
    $expected['mapping']['status']['label'] = 'Status';
 | 
			
		||||
    $expected['mapping']['dependencies']['type'] = 'config_dependencies';
 | 
			
		||||
    $expected['mapping']['dependencies']['label'] = 'Dependencies';
 | 
			
		||||
    $expected['mapping']['label']['type'] = 'required_label';
 | 
			
		||||
    $expected['mapping']['label']['label'] = 'Label';
 | 
			
		||||
    $expected['mapping']['effects']['type'] = 'sequence';
 | 
			
		||||
    $expected['mapping']['effects']['sequence']['type'] = 'mapping';
 | 
			
		||||
    $expected['mapping']['effects']['sequence']['mapping']['id']['type'] = 'string';
 | 
			
		||||
    $expected['mapping']['effects']['sequence']['mapping']['id']['constraints'] = [
 | 
			
		||||
      'PluginExists' => [
 | 
			
		||||
        'manager' => 'plugin.manager.image.effect',
 | 
			
		||||
        'interface' => ImageEffectInterface::class,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['mapping']['effects']['sequence']['mapping']['data']['type'] = 'image.effect.[%parent.id]';
 | 
			
		||||
    $expected['mapping']['effects']['sequence']['mapping']['weight']['type'] = 'weight';
 | 
			
		||||
    $expected['mapping']['effects']['sequence']['mapping']['uuid']['type'] = 'uuid';
 | 
			
		||||
    $expected['mapping']['third_party_settings']['type'] = 'sequence';
 | 
			
		||||
    $expected['mapping']['third_party_settings']['label'] = 'Third party settings';
 | 
			
		||||
    $expected['mapping']['third_party_settings']['sequence']['type'] = '[%parent.%parent.%type].third_party.[%key]';
 | 
			
		||||
    $expected['mapping']['third_party_settings']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['_core']['type'] = '_core_config_info';
 | 
			
		||||
    $expected['mapping']['_core']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['type'] = 'image.style.*';
 | 
			
		||||
    $expected['constraints'] = [
 | 
			
		||||
      'ValidKeys' => '<infer>',
 | 
			
		||||
      'FullyValidatable' => NULL,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($expected, $definition);
 | 
			
		||||
 | 
			
		||||
    // More complex, type based on a complex one.
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('image.effect.image_scale');
 | 
			
		||||
    // This should be the schema for image.effect.image_scale.
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Image scale';
 | 
			
		||||
    $expected['class'] = Mapping::class;
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['mapping']['width']['type'] = 'integer';
 | 
			
		||||
    $expected['mapping']['width']['label'] = 'Width';
 | 
			
		||||
    $expected['mapping']['width']['nullable'] = TRUE;
 | 
			
		||||
    $expected['mapping']['width']['constraints'] = ['NotBlank' => ['allowNull' => TRUE]];
 | 
			
		||||
    $expected['mapping']['height']['type'] = 'integer';
 | 
			
		||||
    $expected['mapping']['height']['label'] = 'Height';
 | 
			
		||||
    $expected['mapping']['height']['nullable'] = TRUE;
 | 
			
		||||
    $expected['mapping']['height']['constraints'] = ['NotBlank' => ['allowNull' => TRUE]];
 | 
			
		||||
    $expected['mapping']['upscale']['type'] = 'boolean';
 | 
			
		||||
    $expected['mapping']['upscale']['label'] = 'Upscale';
 | 
			
		||||
    $expected['type'] = 'image.effect.image_scale';
 | 
			
		||||
    $expected['constraints'] = [
 | 
			
		||||
      'ValidKeys' => '<infer>',
 | 
			
		||||
      'FullyValidatable' => NULL,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for image.effect.image_scale');
 | 
			
		||||
 | 
			
		||||
    // Most complex case, get metadata for actual configuration element.
 | 
			
		||||
    $effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects');
 | 
			
		||||
    $definition = $effects->get('bddf0d06-42f9-4c75-a700-a33cafa25ea0')->get('data')->getDataDefinition()->toArray();
 | 
			
		||||
    // This should be the schema for image.effect.image_scale, reuse previous
 | 
			
		||||
    // one.
 | 
			
		||||
    $expected['type'] = 'image.effect.image_scale';
 | 
			
		||||
    $expected['mapping']['width']['requiredKey'] = TRUE;
 | 
			
		||||
    $expected['mapping']['height']['requiredKey'] = TRUE;
 | 
			
		||||
    $expected['mapping']['upscale']['requiredKey'] = TRUE;
 | 
			
		||||
    $expected['requiredKey'] = TRUE;
 | 
			
		||||
    $expected['required'] = TRUE;
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for the first effect of image.style.medium');
 | 
			
		||||
 | 
			
		||||
    $test = \Drupal::service('config.typed')->get('config_test.dynamic.third_party')->get('third_party_settings.config_schema_test');
 | 
			
		||||
    $definition = $test->getDataDefinition()->toArray();
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['type'] = 'config_test.dynamic.*.third_party.config_schema_test';
 | 
			
		||||
    $expected['label'] = 'Mapping';
 | 
			
		||||
    $expected['class'] = Mapping::class;
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['mapping'] = [
 | 
			
		||||
      'integer' => ['type' => 'integer', 'requiredKey' => TRUE],
 | 
			
		||||
      'string' => ['type' => 'string', 'requiredKey' => TRUE],
 | 
			
		||||
    ];
 | 
			
		||||
    $expected['constraints'] = ['ValidKeys' => '<infer>'];
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for config_test.dynamic.third_party:third_party_settings.config_schema_test');
 | 
			
		||||
 | 
			
		||||
    // More complex, several level deep test.
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.some_schema.some_module.section_one.subsection');
 | 
			
		||||
    // This should be the schema of
 | 
			
		||||
    // config_schema_test.some_schema.some_module.*.*.
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Schema multiple filesystem marker test';
 | 
			
		||||
    $expected['class'] = Mapping::class;
 | 
			
		||||
    $expected['mapping']['langcode']['type'] = 'langcode';
 | 
			
		||||
    $expected['mapping']['langcode']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['_core']['type'] = '_core_config_info';
 | 
			
		||||
    $expected['mapping']['_core']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['test_id']['type'] = 'string';
 | 
			
		||||
    $expected['mapping']['test_id']['label'] = 'ID';
 | 
			
		||||
    $expected['mapping']['test_description']['type'] = 'text';
 | 
			
		||||
    $expected['mapping']['test_description']['label'] = 'Description';
 | 
			
		||||
    $expected['type'] = 'config_schema_test.some_schema.some_module.*.*';
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['constraints'] = [
 | 
			
		||||
      'ValidKeys' => '<infer>',
 | 
			
		||||
      'LangcodeRequiredIfTranslatableValues' => NULL,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for config_schema_test.some_schema.some_module.section_one.subsection');
 | 
			
		||||
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.some_schema.some_module.section_two.subsection');
 | 
			
		||||
    // The other file should have the same schema.
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for config_schema_test.some_schema.some_module.section_two.subsection');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests metadata retrieval with several levels of %parent indirection.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSchemaMappingWithParents(): void {
 | 
			
		||||
    $config_data = \Drupal::service('config.typed')->get('config_schema_test.some_schema.with_parents');
 | 
			
		||||
 | 
			
		||||
    // Test fetching parent one level up.
 | 
			
		||||
    $entry = $config_data->get('one_level');
 | 
			
		||||
    $definition = $entry->get('test_item')->getDataDefinition()->toArray();
 | 
			
		||||
    $expected = [
 | 
			
		||||
      'type' => 'config_schema_test.some_schema.with_parents.key_1',
 | 
			
		||||
      'label' => 'Test item nested one level',
 | 
			
		||||
      'class' => StringData::class,
 | 
			
		||||
      'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
 | 
			
		||||
      'unwrap_for_canonical_representation' => TRUE,
 | 
			
		||||
      'requiredKey' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->assertEquals($expected, $definition);
 | 
			
		||||
 | 
			
		||||
    // Test fetching parent two levels up.
 | 
			
		||||
    $entry = $config_data->get('two_levels');
 | 
			
		||||
    $definition = $entry->get('wrapper')->get('test_item')->getDataDefinition()->toArray();
 | 
			
		||||
    $expected = [
 | 
			
		||||
      'type' => 'config_schema_test.some_schema.with_parents.key_2',
 | 
			
		||||
      'label' => 'Test item nested two levels',
 | 
			
		||||
      'class' => StringData::class,
 | 
			
		||||
      'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
 | 
			
		||||
      'unwrap_for_canonical_representation' => TRUE,
 | 
			
		||||
      'requiredKey' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->assertEquals($expected, $definition);
 | 
			
		||||
 | 
			
		||||
    // Test fetching parent three levels up.
 | 
			
		||||
    $entry = $config_data->get('three_levels');
 | 
			
		||||
    $definition = $entry->get('wrapper_1')->get('wrapper_2')->get('test_item')->getDataDefinition()->toArray();
 | 
			
		||||
    $expected = [
 | 
			
		||||
      'type' => 'config_schema_test.some_schema.with_parents.key_3',
 | 
			
		||||
      'label' => 'Test item nested three levels',
 | 
			
		||||
      'class' => StringData::class,
 | 
			
		||||
      'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
 | 
			
		||||
      'unwrap_for_canonical_representation' => TRUE,
 | 
			
		||||
      'requiredKey' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->assertEquals($expected, $definition);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests metadata applied to configuration objects.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSchemaData(): void {
 | 
			
		||||
    // Try a simple property.
 | 
			
		||||
    $meta = \Drupal::service('config.typed')->get('system.site');
 | 
			
		||||
    $property = $meta->get('page')->get('front');
 | 
			
		||||
    $this->assertInstanceOf(StringInterface::class, $property);
 | 
			
		||||
    $this->assertEquals('/user/login', $property->getValue(), 'Got the right value for page.front data.');
 | 
			
		||||
    $definition = $property->getDataDefinition();
 | 
			
		||||
    $this->assertEmpty($definition['translatable'], 'Got the right translatability setting for page.front data.');
 | 
			
		||||
 | 
			
		||||
    // Check nested array of properties.
 | 
			
		||||
    $list = $meta->get('page')->getElements();
 | 
			
		||||
    $this->assertCount(3, $list, 'Got a list with the right number of properties for site page data');
 | 
			
		||||
    $this->assertArrayHasKey('front', $list);
 | 
			
		||||
    $this->assertArrayHasKey('403', $list);
 | 
			
		||||
    $this->assertArrayHasKey('404', $list);
 | 
			
		||||
    $this->assertEquals('/user/login', $list['front']->getValue(), 'Got the right value for page.front data from the list.');
 | 
			
		||||
 | 
			
		||||
    // And test some TypedConfigInterface methods.
 | 
			
		||||
    $properties = $list;
 | 
			
		||||
    $this->assertCount(3, $properties, 'Got the right number of properties for site page.');
 | 
			
		||||
    $this->assertSame($list['front'], $properties['front']);
 | 
			
		||||
    $values = $meta->get('page')->toArray();
 | 
			
		||||
    $this->assertCount(3, $values, 'Got the right number of property values for site page.');
 | 
			
		||||
    $this->assertSame($values['front'], '/user/login');
 | 
			
		||||
 | 
			
		||||
    // Now let's try something more complex, with nested objects.
 | 
			
		||||
    $wrapper = \Drupal::service('config.typed')->get('image.style.large');
 | 
			
		||||
    $effects = $wrapper->get('effects');
 | 
			
		||||
    $this->assertCount(2, $effects->toArray(), 'Got an array with effects for image.style.large data');
 | 
			
		||||
    foreach ($effects->toArray() as $uuid => $definition) {
 | 
			
		||||
      $effect = $effects->get($uuid)->getElements();
 | 
			
		||||
      if ($definition['id'] == 'image_scale') {
 | 
			
		||||
        $this->assertFalse($effect['data']->isEmpty(), 'Got data for the image scale effect from metadata.');
 | 
			
		||||
        $this->assertSame('image_scale', $effect['id']->getValue(), 'Got data for the image scale effect from metadata.');
 | 
			
		||||
        $this->assertInstanceOf(IntegerInterface::class, $effect['data']->get('width'));
 | 
			
		||||
        $this->assertEquals(480, $effect['data']->get('width')->getValue(), 'Got the right value for the scale effect width.');
 | 
			
		||||
      }
 | 
			
		||||
      if ($definition['id'] == 'image_convert') {
 | 
			
		||||
        $this->assertFalse($effect['data']->isEmpty(), 'Got data for the image convert effect from metadata.');
 | 
			
		||||
        $this->assertSame('image_convert', $effect['id']->getValue(), 'Got data for the image convert effect from metadata.');
 | 
			
		||||
        $this->assertSame('webp', $effect['data']->get('extension')->getValue(), 'Got the right value for the convert effect extension.');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests configuration value data type enforcement using schemas.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigSaveWithSchema(): void {
 | 
			
		||||
    $untyped_values = [
 | 
			
		||||
      // Test a custom type.
 | 
			
		||||
      'config_schema_test_integer' => '1',
 | 
			
		||||
      'config_schema_test_integer_empty_string' => '',
 | 
			
		||||
      'integer' => '100',
 | 
			
		||||
      'null_integer' => '',
 | 
			
		||||
      'float' => '3.14',
 | 
			
		||||
      'null_float' => '',
 | 
			
		||||
      'string' => 1,
 | 
			
		||||
      'null_string' => NULL,
 | 
			
		||||
      'empty_string' => '',
 | 
			
		||||
      'boolean' => 1,
 | 
			
		||||
      // If the config schema doesn't have a type it shouldn't be casted.
 | 
			
		||||
      'no_type' => 1,
 | 
			
		||||
      'mapping' => [
 | 
			
		||||
        'string' => 1,
 | 
			
		||||
      ],
 | 
			
		||||
      'sequence' => [1, 0, 1],
 | 
			
		||||
      // Not in schema and therefore should be left untouched.
 | 
			
		||||
      'not_present_in_schema' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $untyped_to_typed = $untyped_values;
 | 
			
		||||
 | 
			
		||||
    $typed_values = [
 | 
			
		||||
      'config_schema_test_integer' => 1,
 | 
			
		||||
      'config_schema_test_integer_empty_string' => NULL,
 | 
			
		||||
      'integer' => 100,
 | 
			
		||||
      'null_integer' => NULL,
 | 
			
		||||
      'float' => 3.14,
 | 
			
		||||
      'null_float' => NULL,
 | 
			
		||||
      'string' => '1',
 | 
			
		||||
      'null_string' => NULL,
 | 
			
		||||
      'empty_string' => '',
 | 
			
		||||
      'boolean' => TRUE,
 | 
			
		||||
      'no_type' => 1,
 | 
			
		||||
      'mapping' => [
 | 
			
		||||
        'string' => '1',
 | 
			
		||||
      ],
 | 
			
		||||
      'sequence' => [TRUE, FALSE, TRUE],
 | 
			
		||||
      'not_present_in_schema' => TRUE,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Save config which has a schema that enforces types.
 | 
			
		||||
    $config_object = $this->config('config_schema_test.schema_data_types');
 | 
			
		||||
    $config_object
 | 
			
		||||
      ->setData($untyped_to_typed)
 | 
			
		||||
      ->save();
 | 
			
		||||
    // Ensure the schemaWrapper property is reset after saving to prevent a
 | 
			
		||||
    // memory leak.
 | 
			
		||||
    $this->assertNull((new \ReflectionObject($config_object))->getProperty('schemaWrapper')->getValue($config_object));
 | 
			
		||||
    $this->assertSame($typed_values, $this->config('config_schema_test.schema_data_types')->get());
 | 
			
		||||
 | 
			
		||||
    // Save config which does not have a schema that enforces types.
 | 
			
		||||
    $this->config('config_schema_test.no_schema_data_types')
 | 
			
		||||
      ->setData($untyped_values)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->assertSame($untyped_values, $this->config('config_schema_test.no_schema_data_types')->get());
 | 
			
		||||
 | 
			
		||||
    // Ensure that configuration objects with keys marked as ignored are not
 | 
			
		||||
    // changed when saved. The 'config_schema_test.ignore' will have been saved
 | 
			
		||||
    // during the installation of configuration in the setUp method.
 | 
			
		||||
    $extension_path = __DIR__ . '/../../../../../modules/config/tests/config_schema_test/';
 | 
			
		||||
    $install_storage = new FileStorage($extension_path . InstallStorage::CONFIG_INSTALL_DIRECTORY);
 | 
			
		||||
    $original_data = $install_storage->read('config_schema_test.ignore');
 | 
			
		||||
    $installed_data = $this->config('config_schema_test.ignore')->get();
 | 
			
		||||
    unset($installed_data['_core']);
 | 
			
		||||
    $this->assertSame($original_data, $installed_data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test configuration value data type enforcement using schemas.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigSaveMappingSort(): void {
 | 
			
		||||
    // Top level map sorting.
 | 
			
		||||
    $data = [
 | 
			
		||||
      'foo' => '1',
 | 
			
		||||
      'bar' => '2',
 | 
			
		||||
    ];
 | 
			
		||||
    // Save config which has a schema that enforces types.
 | 
			
		||||
    $this->config('config_schema_test.schema_mapping_sort')
 | 
			
		||||
      ->setData($data)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->assertSame(['bar' => '2', 'foo' => '1'], $this->config('config_schema_test.schema_mapping_sort')->get());
 | 
			
		||||
    $this->config('config_schema_test.schema_mapping_sort')->set('map', ['sub_bar' => '2', 'sub_foo' => '1'])->save();
 | 
			
		||||
    $this->assertSame(['sub_foo' => '1', 'sub_bar' => '2'], $this->config('config_schema_test.schema_mapping_sort')->get('map'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests configuration sequence sorting using schemas.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigSaveWithSequenceSorting(): void {
 | 
			
		||||
    $data = [
 | 
			
		||||
      'keyed_sort' => [
 | 
			
		||||
        'b' => '1',
 | 
			
		||||
        'a' => '2',
 | 
			
		||||
      ],
 | 
			
		||||
      'no_sort' => [
 | 
			
		||||
        'b' => '2',
 | 
			
		||||
        'a' => '1',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    // Save config which has a schema that enforces sorting.
 | 
			
		||||
    $this->config('config_schema_test.schema_sequence_sort')
 | 
			
		||||
      ->setData($data)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->assertSame(['a' => '2', 'b' => '1'], $this->config('config_schema_test.schema_sequence_sort')->get('keyed_sort'));
 | 
			
		||||
    $this->assertSame(['b' => '2', 'a' => '1'], $this->config('config_schema_test.schema_sequence_sort')->get('no_sort'));
 | 
			
		||||
 | 
			
		||||
    $data = [
 | 
			
		||||
      'value_sort' => ['b', 'a'],
 | 
			
		||||
      'no_sort' => ['b', 'a'],
 | 
			
		||||
    ];
 | 
			
		||||
    // Save config which has a schema that enforces sorting.
 | 
			
		||||
    $this->config('config_schema_test.schema_sequence_sort')
 | 
			
		||||
      ->setData($data)
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    $this->assertSame(['a', 'b'], $this->config('config_schema_test.schema_sequence_sort')->get('value_sort'));
 | 
			
		||||
    $this->assertSame(['b', 'a'], $this->config('config_schema_test.schema_sequence_sort')->get('no_sort'));
 | 
			
		||||
 | 
			
		||||
    // Value sort does not preserve keys - this is intentional.
 | 
			
		||||
    $data = [
 | 
			
		||||
      'value_sort' => [1 => 'b', 2 => 'a'],
 | 
			
		||||
      'no_sort' => [1 => 'b', 2 => 'a'],
 | 
			
		||||
    ];
 | 
			
		||||
    // Save config which has a schema that enforces sorting.
 | 
			
		||||
    $this->config('config_schema_test.schema_sequence_sort')
 | 
			
		||||
      ->setData($data)
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    $this->assertSame(['a', 'b'], $this->config('config_schema_test.schema_sequence_sort')->get('value_sort'));
 | 
			
		||||
    $this->assertSame([1 => 'b', 2 => 'a'], $this->config('config_schema_test.schema_sequence_sort')->get('no_sort'));
 | 
			
		||||
 | 
			
		||||
    // Test sorts do not destroy complex values.
 | 
			
		||||
    $data = [
 | 
			
		||||
      'complex_sort_value' => [['foo' => 'b', 'bar' => 'b'] , ['foo' => 'a', 'bar' => 'a']],
 | 
			
		||||
      'complex_sort_key' => ['b' => ['foo' => '1', 'bar' => '1'] , 'a' => ['foo' => '2', 'bar' => '2']],
 | 
			
		||||
    ];
 | 
			
		||||
    $this->config('config_schema_test.schema_sequence_sort')
 | 
			
		||||
      ->setData($data)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->assertSame([['foo' => 'a', 'bar' => 'a'], ['foo' => 'b', 'bar' => 'b']], $this->config('config_schema_test.schema_sequence_sort')->get('complex_sort_value'));
 | 
			
		||||
    $this->assertSame(['a' => ['foo' => '2', 'bar' => '2'], 'b' => ['foo' => '1', 'bar' => '1']], $this->config('config_schema_test.schema_sequence_sort')->get('complex_sort_key'));
 | 
			
		||||
 | 
			
		||||
    // Swap the previous test scenario around.
 | 
			
		||||
    $data = [
 | 
			
		||||
      'complex_sort_value' => ['b' => ['foo' => '1', 'bar' => '1'] , 'a' => ['foo' => '2', 'bar' => '2']],
 | 
			
		||||
      'complex_sort_key' => [['foo' => 'b', 'bar' => 'b'] , ['foo' => 'a', 'bar' => 'a']],
 | 
			
		||||
    ];
 | 
			
		||||
    $this->config('config_schema_test.schema_sequence_sort')
 | 
			
		||||
      ->setData($data)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->assertSame([['foo' => '1', 'bar' => '1'], ['foo' => '2', 'bar' => '2']], $this->config('config_schema_test.schema_sequence_sort')->get('complex_sort_value'));
 | 
			
		||||
    $this->assertSame([['foo' => 'b', 'bar' => 'b'], ['foo' => 'a', 'bar' => 'a']], $this->config('config_schema_test.schema_sequence_sort')->get('complex_sort_key'));
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests fallback to a greedy wildcard.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSchemaFallback(): void {
 | 
			
		||||
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.wildcard_fallback.something');
 | 
			
		||||
    // This should be the schema of config_schema_test.wildcard_fallback.*.
 | 
			
		||||
    $expected = [];
 | 
			
		||||
    $expected['label'] = 'Schema wildcard fallback test';
 | 
			
		||||
    $expected['class'] = Mapping::class;
 | 
			
		||||
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 | 
			
		||||
    $expected['unwrap_for_canonical_representation'] = TRUE;
 | 
			
		||||
    $expected['mapping']['langcode']['type'] = 'langcode';
 | 
			
		||||
    $expected['mapping']['langcode']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['_core']['type'] = '_core_config_info';
 | 
			
		||||
    $expected['mapping']['_core']['requiredKey'] = FALSE;
 | 
			
		||||
    $expected['mapping']['test_id']['type'] = 'string';
 | 
			
		||||
    $expected['mapping']['test_id']['label'] = 'ID';
 | 
			
		||||
    $expected['mapping']['test_description']['type'] = 'text';
 | 
			
		||||
    $expected['mapping']['test_description']['label'] = 'Description';
 | 
			
		||||
    $expected['type'] = 'config_schema_test.wildcard_fallback.*';
 | 
			
		||||
    $expected['constraints'] = [
 | 
			
		||||
      'ValidKeys' => '<infer>',
 | 
			
		||||
      'LangcodeRequiredIfTranslatableValues' => NULL,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($expected, $definition, 'Retrieved the right metadata for config_schema_test.wildcard_fallback.something');
 | 
			
		||||
 | 
			
		||||
    $definition2 = \Drupal::service('config.typed')->getDefinition('config_schema_test.wildcard_fallback.something.something');
 | 
			
		||||
    // This should be the schema of config_schema_test.wildcard_fallback.* as
 | 
			
		||||
    // well.
 | 
			
		||||
    $this->assertSame($definition, $definition2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests use of colons in schema type determination.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\TypedConfigManager::getFallbackName()
 | 
			
		||||
   */
 | 
			
		||||
  public function testColonsInSchemaTypeDetermination(): void {
 | 
			
		||||
    $tests = \Drupal::service('config.typed')->get('config_schema_test.plugin_types')->get('tests')->getElements();
 | 
			
		||||
    $definition = $tests[0]->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('test.plugin_types.boolean', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $definition = $tests[1]->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('test.plugin_types.boolean:*', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $definition = $tests[2]->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('test.plugin_types.*', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $definition = $tests[3]->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('test.plugin_types.*', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $tests = \Drupal::service('config.typed')->get('config_schema_test.plugin_types')->get('test_with_parents')->getElements();
 | 
			
		||||
    $definition = $tests[0]->get('settings')->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('test_with_parents.plugin_types.boolean', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $definition = $tests[1]->get('settings')->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('test_with_parents.plugin_types.boolean:*', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $definition = $tests[2]->get('settings')->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('test_with_parents.plugin_types.*', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $definition = $tests[3]->get('settings')->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('test_with_parents.plugin_types.*', $definition['type']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests hook_config_schema_info_alter().
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigSchemaInfoAlter(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config */
 | 
			
		||||
    $typed_config = \Drupal::service('config.typed');
 | 
			
		||||
    $typed_config->clearCachedDefinitions();
 | 
			
		||||
 | 
			
		||||
    // Ensure that keys can not be added or removed by
 | 
			
		||||
    // hook_config_schema_info_alter().
 | 
			
		||||
    \Drupal::state()->set('config_schema_test_exception_remove', TRUE);
 | 
			
		||||
    try {
 | 
			
		||||
      $typed_config->getDefinitions();
 | 
			
		||||
      $this->fail('Expected ConfigSchemaAlterException thrown.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigSchemaAlterException $e) {
 | 
			
		||||
      $this->assertEquals('Invoking hook_config_schema_info_alter() has removed (config_schema_test.hook) schema definitions', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    \Drupal::state()->set('config_schema_test_exception_add', TRUE);
 | 
			
		||||
    try {
 | 
			
		||||
      $typed_config->getDefinitions();
 | 
			
		||||
      $this->fail('Expected ConfigSchemaAlterException thrown.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigSchemaAlterException $e) {
 | 
			
		||||
      $this->assertEquals('Invoking hook_config_schema_info_alter() has added (config_schema_test.hook_added_definition) and removed (config_schema_test.hook) schema definitions', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    \Drupal::state()->set('config_schema_test_exception_remove', FALSE);
 | 
			
		||||
    try {
 | 
			
		||||
      $typed_config->getDefinitions();
 | 
			
		||||
      $this->fail('Expected ConfigSchemaAlterException thrown.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (ConfigSchemaAlterException $e) {
 | 
			
		||||
      $this->assertEquals('Invoking hook_config_schema_info_alter() has added (config_schema_test.hook_added_definition) schema definitions', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Tests that hook_config_schema_info_alter() can add additional metadata to
 | 
			
		||||
    // existing configuration schema.
 | 
			
		||||
    \Drupal::state()->set('config_schema_test_exception_add', FALSE);
 | 
			
		||||
    $definitions = $typed_config->getDefinitions();
 | 
			
		||||
    $this->assertEquals('new schema info', $definitions['config_schema_test.hook']['additional_metadata']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests saving config when the type is wrapped by a dynamic type.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigSaveWithWrappingSchema(): void {
 | 
			
		||||
    $untyped_values = [
 | 
			
		||||
      'tests' => [
 | 
			
		||||
        [
 | 
			
		||||
          'wrapper_value' => 'foo',
 | 
			
		||||
          'plugin_id' => 'wrapper:foo',
 | 
			
		||||
          'internal_value' => 100,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $typed_values = [
 | 
			
		||||
      'tests' => [
 | 
			
		||||
        [
 | 
			
		||||
          'plugin_id' => 'wrapper:foo',
 | 
			
		||||
          'internal_value' => '100',
 | 
			
		||||
          'wrapper_value' => 'foo',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Save config which has a schema that enforces types.
 | 
			
		||||
    \Drupal::configFactory()->getEditable('wrapping.config_schema_test.plugin_types')
 | 
			
		||||
      ->setData($untyped_values)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->assertSame($typed_values, \Drupal::config('wrapping.config_schema_test.plugin_types')->get());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests dynamic config schema type with multiple sub-key references.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigSaveWithWrappingSchemaDoubleBrackets(): void {
 | 
			
		||||
    $untyped_values = [
 | 
			
		||||
      'tests' => [
 | 
			
		||||
        [
 | 
			
		||||
          'wrapper_value' => 'foo',
 | 
			
		||||
          'foo' => 'turtle',
 | 
			
		||||
          'bar' => 'horse',
 | 
			
		||||
          // Converted to a string by 'test.double_brackets.turtle.horse'
 | 
			
		||||
          // schema.
 | 
			
		||||
          'another_key' => '100',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $typed_values = [
 | 
			
		||||
      'tests' => [
 | 
			
		||||
        [
 | 
			
		||||
          'another_key' => 100,
 | 
			
		||||
          'foo' => 'turtle',
 | 
			
		||||
          'bar' => 'horse',
 | 
			
		||||
          'wrapper_value' => 'foo',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Save config which has a schema that enforces types.
 | 
			
		||||
    \Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
 | 
			
		||||
      ->setData($untyped_values)
 | 
			
		||||
      ->save();
 | 
			
		||||
    // TRICKY: https://www.drupal.org/project/drupal/issues/2663410 introduced a
 | 
			
		||||
    // bug that made TypedConfigManager sensitive to cache pollution. Saving
 | 
			
		||||
    // config triggers validation, which in turn triggers that cache pollution
 | 
			
		||||
    // bug. This is a work-around.
 | 
			
		||||
    // @todo Remove in https://www.drupal.org/project/drupal/issues/3400181
 | 
			
		||||
    \Drupal::service('config.typed')->clearCachedDefinitions();
 | 
			
		||||
    $this->assertSame($typed_values, \Drupal::config('wrapping.config_schema_test.double_brackets')->get());
 | 
			
		||||
 | 
			
		||||
    $tests = \Drupal::service('config.typed')->get('wrapping.config_schema_test.double_brackets')->get('tests')->getElements();
 | 
			
		||||
    $definition = $tests[0]->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('wrapping.test.double_brackets.*||test.double_brackets.turtle.horse', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $untyped_values = [
 | 
			
		||||
      'tests' => [
 | 
			
		||||
        [
 | 
			
		||||
          'wrapper_value' => 'foo',
 | 
			
		||||
          'foo' => 'cat',
 | 
			
		||||
          'bar' => 'dog',
 | 
			
		||||
          // Converted to a string by 'test.double_brackets.cat.dog' schema.
 | 
			
		||||
          'another_key' => 100,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $typed_values = [
 | 
			
		||||
      'tests' => [
 | 
			
		||||
        [
 | 
			
		||||
          'another_key' => '100',
 | 
			
		||||
          'foo' => 'cat',
 | 
			
		||||
          'bar' => 'dog',
 | 
			
		||||
          'wrapper_value' => 'foo',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Save config which has a schema that enforces types.
 | 
			
		||||
    \Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
 | 
			
		||||
      ->setData($untyped_values)
 | 
			
		||||
      ->save();
 | 
			
		||||
    // TRICKY: https://www.drupal.org/project/drupal/issues/2663410 introduced a
 | 
			
		||||
    // bug that made TypedConfigManager sensitive to cache pollution. Saving
 | 
			
		||||
    // config in a test triggers the schema checking and validation logic from
 | 
			
		||||
    // \Drupal\Core\Config\Development\ConfigSchemaChecker , which in turn
 | 
			
		||||
    // triggers that cache pollution bug. This is a work-around.
 | 
			
		||||
    // @todo Remove in https://www.drupal.org/project/drupal/issues/3400181
 | 
			
		||||
    \Drupal::service('config.typed')->clearCachedDefinitions();
 | 
			
		||||
    $this->assertSame($typed_values, \Drupal::config('wrapping.config_schema_test.double_brackets')->get());
 | 
			
		||||
 | 
			
		||||
    $tests = \Drupal::service('config.typed')->get('wrapping.config_schema_test.double_brackets')->get('tests')->getElements();
 | 
			
		||||
    $definition = $tests[0]->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('wrapping.test.double_brackets.*||test.double_brackets.cat.dog', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    // Combine everything in a single save.
 | 
			
		||||
    $typed_values = [
 | 
			
		||||
      'tests' => [
 | 
			
		||||
        [
 | 
			
		||||
          'another_key' => 100,
 | 
			
		||||
          'foo' => 'cat',
 | 
			
		||||
          'bar' => 'dog',
 | 
			
		||||
          'wrapper_value' => 'foo',
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          'another_key' => '100',
 | 
			
		||||
          'foo' => 'turtle',
 | 
			
		||||
          'bar' => 'horse',
 | 
			
		||||
          'wrapper_value' => 'foo',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    \Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
 | 
			
		||||
      ->setData($typed_values)
 | 
			
		||||
      ->save();
 | 
			
		||||
    // TRICKY: https://www.drupal.org/project/drupal/issues/2663410 introduced a
 | 
			
		||||
    // bug that made TypedConfigManager sensitive to cache pollution. Saving
 | 
			
		||||
    // config in a test triggers the schema checking and validation logic from
 | 
			
		||||
    // \Drupal\Core\Config\Development\ConfigSchemaChecker , which in turn
 | 
			
		||||
    // triggers that cache pollution bug. This is a work-around.
 | 
			
		||||
    // @todo Remove in https://www.drupal.org/project/drupal/issues/3400181
 | 
			
		||||
    \Drupal::service('config.typed')->clearCachedDefinitions();
 | 
			
		||||
    $tests = \Drupal::service('config.typed')->get('wrapping.config_schema_test.double_brackets')->get('tests')->getElements();
 | 
			
		||||
    $definition = $tests[0]->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('wrapping.test.double_brackets.*||test.double_brackets.cat.dog', $definition['type']);
 | 
			
		||||
    $definition = $tests[1]->getDataDefinition()->toArray();
 | 
			
		||||
    $this->assertEquals('wrapping.test.double_brackets.*||test.double_brackets.turtle.horse', $definition['type']);
 | 
			
		||||
 | 
			
		||||
    $typed_values = [
 | 
			
		||||
      'tests' => [
 | 
			
		||||
        [
 | 
			
		||||
          'id' => 'cat:persian.dog',
 | 
			
		||||
          'foo' => 'cat',
 | 
			
		||||
          'bar' => 'dog',
 | 
			
		||||
          'breed' => 'persian',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    \Drupal::configFactory()->getEditable('wrapping.config_schema_test.other_double_brackets')
 | 
			
		||||
      ->setData($typed_values)
 | 
			
		||||
      ->save();
 | 
			
		||||
    // TRICKY: https://www.drupal.org/project/drupal/issues/2663410 introduced a
 | 
			
		||||
    // bug that made TypedConfigManager sensitive to cache pollution. Saving
 | 
			
		||||
    // config in a test triggers the schema checking and validation logic from
 | 
			
		||||
    // \Drupal\Core\Config\Development\ConfigSchemaChecker , which in turn
 | 
			
		||||
    // triggers that cache pollution bug. This is a work-around.
 | 
			
		||||
    // @todo Remove in https://www.drupal.org/project/drupal/issues/3400181
 | 
			
		||||
    \Drupal::service('config.typed')->clearCachedDefinitions();
 | 
			
		||||
    $tests = \Drupal::service('config.typed')->get('wrapping.config_schema_test.other_double_brackets')->get('tests')->getElements();
 | 
			
		||||
    $definition = $tests[0]->getDataDefinition()->toArray();
 | 
			
		||||
    // Check that definition type is a merge of the expected types.
 | 
			
		||||
    $this->assertEquals('wrapping.test.other_double_brackets.*||test.double_brackets.cat:*.*', $definition['type']);
 | 
			
		||||
    // Check that breed was inherited from parent definition.
 | 
			
		||||
    $this->assertEquals([
 | 
			
		||||
      'type' => 'string',
 | 
			
		||||
      'requiredKey' => TRUE,
 | 
			
		||||
    ], $definition['mapping']['breed']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests exception is thrown for the root object.
 | 
			
		||||
   */
 | 
			
		||||
  public function testLangcodeRequiredIfTranslatableValuesConstraintError(): void {
 | 
			
		||||
    $config = \Drupal::configFactory()->getEditable('config_test.foo');
 | 
			
		||||
 | 
			
		||||
    $this->expectException(\LogicException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The LangcodeRequiredIfTranslatableValues constraint is applied to \'config_test.foo::broken_langcode_required\'. This constraint can only operate on the root object being validated.');
 | 
			
		||||
 | 
			
		||||
    $config
 | 
			
		||||
      ->set('broken_langcode_required.foo', 'bar')
 | 
			
		||||
      ->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,86 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\StorageComparer;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests config snapshot creation and updating.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigSnapshotTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test', 'system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
    // Update the config snapshot. This allows the parent::setUp() to write
 | 
			
		||||
    // configuration files.
 | 
			
		||||
    \Drupal::service('config.manager')->createSnapshot(\Drupal::service('config.storage'), \Drupal::service('config.storage.snapshot'));
 | 
			
		||||
    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests config snapshot creation and updating.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSnapshot(): void {
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    $sync = $this->container->get('config.storage.sync');
 | 
			
		||||
    $snapshot = $this->container->get('config.storage.snapshot');
 | 
			
		||||
    $config_name = 'config_test.system';
 | 
			
		||||
    $config_key = 'foo';
 | 
			
		||||
    $new_data = 'foobar';
 | 
			
		||||
 | 
			
		||||
    $active_snapshot_comparer = new StorageComparer($active, $snapshot);
 | 
			
		||||
    $sync_snapshot_comparer = new StorageComparer($sync, $snapshot);
 | 
			
		||||
 | 
			
		||||
    // Verify that we have an initial snapshot that matches the active
 | 
			
		||||
    // configuration. This has to be true as no config should be installed.
 | 
			
		||||
    $this->assertFalse($active_snapshot_comparer->createChangelist()->hasChanges());
 | 
			
		||||
 | 
			
		||||
    // Install the default config.
 | 
			
		||||
    $this->installConfig(['config_test']);
 | 
			
		||||
    // Although we have imported config this has not affected the snapshot.
 | 
			
		||||
    $this->assertTrue($active_snapshot_comparer->reset()->hasChanges());
 | 
			
		||||
 | 
			
		||||
    // Update the config snapshot.
 | 
			
		||||
    \Drupal::service('config.manager')->createSnapshot($active, $snapshot);
 | 
			
		||||
 | 
			
		||||
    // The snapshot and active config should now contain the same config
 | 
			
		||||
    // objects.
 | 
			
		||||
    $this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
 | 
			
		||||
 | 
			
		||||
    // Change a configuration value in sync.
 | 
			
		||||
    $sync_data = $this->config($config_name)->get();
 | 
			
		||||
    $sync_data[$config_key] = $new_data;
 | 
			
		||||
    $sync->write($config_name, $sync_data);
 | 
			
		||||
 | 
			
		||||
    // Verify that active and snapshot match, and that sync doesn't match
 | 
			
		||||
    // active.
 | 
			
		||||
    $this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
 | 
			
		||||
    $this->assertTrue($sync_snapshot_comparer->createChangelist()->hasChanges());
 | 
			
		||||
 | 
			
		||||
    // Import changed data from sync to active.
 | 
			
		||||
    $this->configImporter()->import();
 | 
			
		||||
 | 
			
		||||
    // Verify changed config was properly imported.
 | 
			
		||||
    \Drupal::configFactory()->reset($config_name);
 | 
			
		||||
    $this->assertSame($new_data, $this->config($config_name)->get($config_key));
 | 
			
		||||
 | 
			
		||||
    // Verify that a new snapshot was created which and that it matches
 | 
			
		||||
    // the active config.
 | 
			
		||||
    $this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,141 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Entity;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
 | 
			
		||||
use Drupal\Core\Site\Settings;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests \Drupal\Core\Config\Entity\ConfigEntityUpdater.
 | 
			
		||||
 *
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Config\Entity\ConfigEntityUpdater
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ConfigEntityUpdaterTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test', 'system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::update
 | 
			
		||||
   */
 | 
			
		||||
  public function testUpdate(): void {
 | 
			
		||||
    // Create some entities to update.
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')->getStorage('config_test');
 | 
			
		||||
    for ($i = 0; $i < 15; $i++) {
 | 
			
		||||
      $entity_id = 'config_test_' . $i;
 | 
			
		||||
      $storage->create(['id' => $entity_id, 'label' => $entity_id])->save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up the updater.
 | 
			
		||||
    $sandbox = [];
 | 
			
		||||
    $settings = Settings::getInstance() ? Settings::getAll() : [];
 | 
			
		||||
    $settings['entity_update_batch_size'] = 10;
 | 
			
		||||
    new Settings($settings);
 | 
			
		||||
    $updater = $this->container->get('class_resolver')->getInstanceFromDefinition(ConfigEntityUpdater::class);
 | 
			
		||||
 | 
			
		||||
    $callback = function ($config_entity) {
 | 
			
		||||
      /** @var \Drupal\config_test\Entity\ConfigTest $config_entity */
 | 
			
		||||
      $number = (int) str_replace('config_test_', '', $config_entity->id());
 | 
			
		||||
      // Only update even numbered entities.
 | 
			
		||||
      if ($number % 2 == 0) {
 | 
			
		||||
        $config_entity->set('label', $config_entity->label . ' (updated)');
 | 
			
		||||
        return TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // This should run against the first 10 entities. The even numbered labels
 | 
			
		||||
    // will have been updated.
 | 
			
		||||
    $updater->update($sandbox, 'config_test', $callback);
 | 
			
		||||
    $entities = $storage->loadMultiple();
 | 
			
		||||
    $this->assertEquals('config_test_8 (updated)', $entities['config_test_8']->label());
 | 
			
		||||
    $this->assertEquals('config_test_9', $entities['config_test_9']->label());
 | 
			
		||||
    $this->assertEquals('config_test_10', $entities['config_test_10']->label());
 | 
			
		||||
    $this->assertEquals('config_test_14', $entities['config_test_14']->label());
 | 
			
		||||
    $this->assertEquals(15, $sandbox['config_entity_updater']['count']);
 | 
			
		||||
    $this->assertEquals('config_test', $sandbox['config_entity_updater']['entity_type']);
 | 
			
		||||
    $this->assertCount(5, $sandbox['config_entity_updater']['entities']);
 | 
			
		||||
    $this->assertEquals(10 / 15, $sandbox['#finished']);
 | 
			
		||||
 | 
			
		||||
    // Update the rest.
 | 
			
		||||
    $updater->update($sandbox, 'config_test', $callback);
 | 
			
		||||
    $entities = $storage->loadMultiple();
 | 
			
		||||
    $this->assertEquals('config_test_8 (updated)', $entities['config_test_8']->label());
 | 
			
		||||
    $this->assertEquals('config_test_9', $entities['config_test_9']->label());
 | 
			
		||||
    $this->assertEquals('config_test_10 (updated)', $entities['config_test_10']->label());
 | 
			
		||||
    $this->assertEquals('config_test_14 (updated)', $entities['config_test_14']->label());
 | 
			
		||||
    $this->assertEquals(1, $sandbox['#finished']);
 | 
			
		||||
    $this->assertCount(0, $sandbox['config_entity_updater']['entities']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::update
 | 
			
		||||
   */
 | 
			
		||||
  public function testUpdateDefaultCallback(): void {
 | 
			
		||||
    // Create some entities to update.
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')->getStorage('config_test');
 | 
			
		||||
    for ($i = 0; $i < 15; $i++) {
 | 
			
		||||
      $entity_id = 'config_test_' . $i;
 | 
			
		||||
      $storage->create(['id' => $entity_id, 'label' => $entity_id])->save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up the updater.
 | 
			
		||||
    $sandbox = [];
 | 
			
		||||
    $settings = Settings::getInstance() ? Settings::getAll() : [];
 | 
			
		||||
    $settings['entity_update_batch_size'] = 9;
 | 
			
		||||
    new Settings($settings);
 | 
			
		||||
    $updater = $this->container->get('class_resolver')->getInstanceFromDefinition(ConfigEntityUpdater::class);
 | 
			
		||||
    // Cause a dependency to be added during an update.
 | 
			
		||||
    \Drupal::state()->set('config_test_new_dependency', 'system');
 | 
			
		||||
 | 
			
		||||
    // This should run against the first 10 entities.
 | 
			
		||||
    $updater->update($sandbox, 'config_test');
 | 
			
		||||
    $entities = $storage->loadMultiple();
 | 
			
		||||
    $this->assertEquals(['system'], $entities['config_test_7']->getDependencies()['module']);
 | 
			
		||||
    $this->assertEquals(['system'], $entities['config_test_8']->getDependencies()['module']);
 | 
			
		||||
    $this->assertEquals([], $entities['config_test_9']->getDependencies());
 | 
			
		||||
    $this->assertEquals([], $entities['config_test_14']->getDependencies());
 | 
			
		||||
    $this->assertEquals(15, $sandbox['config_entity_updater']['count']);
 | 
			
		||||
    $this->assertCount(6, $sandbox['config_entity_updater']['entities']);
 | 
			
		||||
    $this->assertEquals(9 / 15, $sandbox['#finished']);
 | 
			
		||||
 | 
			
		||||
    // Update the rest.
 | 
			
		||||
    $updater->update($sandbox, 'config_test');
 | 
			
		||||
    $entities = $storage->loadMultiple();
 | 
			
		||||
    $this->assertEquals(['system'], $entities['config_test_9']->getDependencies()['module']);
 | 
			
		||||
    $this->assertEquals(['system'], $entities['config_test_14']->getDependencies()['module']);
 | 
			
		||||
    $this->assertEquals(1, $sandbox['#finished']);
 | 
			
		||||
    $this->assertCount(0, $sandbox['config_entity_updater']['entities']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::update
 | 
			
		||||
   */
 | 
			
		||||
  public function testUpdateException(): void {
 | 
			
		||||
    $this->enableModules(['entity_test']);
 | 
			
		||||
    $this->expectException(\InvalidArgumentException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The provided entity type ID \'entity_test_mul_changed\' is not a configuration entity type');
 | 
			
		||||
    $updater = $this->container->get('class_resolver')->getInstanceFromDefinition(ConfigEntityUpdater::class);
 | 
			
		||||
    $sandbox = [];
 | 
			
		||||
    $updater->update($sandbox, 'entity_test_mul_changed');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::update
 | 
			
		||||
   */
 | 
			
		||||
  public function testUpdateOncePerUpdateException(): void {
 | 
			
		||||
    $this->expectException(\RuntimeException::class);
 | 
			
		||||
    $this->expectExceptionMessage('Updating multiple entity types in the same update function is not supported');
 | 
			
		||||
    $updater = $this->container->get('class_resolver')->getInstanceFromDefinition(ConfigEntityUpdater::class);
 | 
			
		||||
    $sandbox = [];
 | 
			
		||||
    $updater->update($sandbox, 'config_test');
 | 
			
		||||
    $updater->update($sandbox, 'config_query_test');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,88 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests ExcludedModulesEventSubscriber.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ExcludedModulesEventSubscriberTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'config_test',
 | 
			
		||||
    'config_exclude_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system', 'config_test', 'config_exclude_test']);
 | 
			
		||||
    $this->setSetting('config_exclude_modules', ['config_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests excluding modules from the config export.
 | 
			
		||||
   */
 | 
			
		||||
  public function testExcludedModules(): void {
 | 
			
		||||
    // Assert that config_test is in the active config.
 | 
			
		||||
    $active = $this->container->get('config.storage');
 | 
			
		||||
    $this->assertNotEmpty($active->listAll('config_test.'));
 | 
			
		||||
    $this->assertNotEmpty($active->listAll('system.'));
 | 
			
		||||
    $this->assertArrayHasKey('config_test', $active->read('core.extension')['module']);
 | 
			
		||||
    $collection = $this->randomMachineName();
 | 
			
		||||
    foreach ($active->listAll() as $config) {
 | 
			
		||||
      $active->createCollection($collection)->write($config, $active->read($config));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Assert that config_test is not in the export storage.
 | 
			
		||||
    $export = $this->container->get('config.storage.export');
 | 
			
		||||
    $this->assertEmpty($export->listAll('config_test.'));
 | 
			
		||||
    $this->assertNotEmpty($export->listAll('system.'));
 | 
			
		||||
    $this->assertEmpty($export->createCollection($collection)->listAll('config_test.'));
 | 
			
		||||
    $this->assertNotEmpty($export->createCollection($collection)->listAll('system.'));
 | 
			
		||||
    $this->assertArrayNotHasKey('config_test', $export->read('core.extension')['module']);
 | 
			
		||||
    // The config_exclude_test is not excluded but the menu it installs are.
 | 
			
		||||
    $this->assertArrayHasKey('config_exclude_test', $export->read('core.extension')['module']);
 | 
			
		||||
    $this->assertFalse($export->exists('system.menu.exclude_test'));
 | 
			
		||||
    $this->assertFalse($export->exists('system.menu.indirect_exclude_test'));
 | 
			
		||||
 | 
			
		||||
    // Assert that config_test is again in the import storage.
 | 
			
		||||
    $import = $this->container->get('config.import_transformer')->transform($export);
 | 
			
		||||
    $this->assertNotEmpty($import->listAll('config_test.'));
 | 
			
		||||
    $this->assertNotEmpty($import->listAll('system.'));
 | 
			
		||||
    $this->assertNotEmpty($import->createCollection($collection)->listAll('config_test.'));
 | 
			
		||||
    $this->assertNotEmpty($import->createCollection($collection)->listAll('system.'));
 | 
			
		||||
    $this->assertArrayHasKey('config_test', $import->read('core.extension')['module']);
 | 
			
		||||
    $this->assertArrayHasKey('config_exclude_test', $import->read('core.extension')['module']);
 | 
			
		||||
    $this->assertTrue($import->exists('system.menu.exclude-test'));
 | 
			
		||||
    $this->assertTrue($import->exists('system.menu.indirect-exclude-test'));
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($active->read('core.extension'), $import->read('core.extension'));
 | 
			
		||||
    $this->assertEquals($active->listAll(), $import->listAll());
 | 
			
		||||
    foreach ($active->listAll() as $config) {
 | 
			
		||||
      $this->assertEquals($active->read($config), $import->read($config));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // When the settings are changed, the next request will get the export
 | 
			
		||||
    // storage without the config_test excluded.
 | 
			
		||||
    $this->setSetting('config_exclude_modules', []);
 | 
			
		||||
    // We rebuild the container to simulate a new request. The managed storage
 | 
			
		||||
    // gets the storage from the manager only once.
 | 
			
		||||
    $this->container->get('kernel')->rebuildContainer();
 | 
			
		||||
    $export = $this->container->get('config.storage.export');
 | 
			
		||||
 | 
			
		||||
    $this->assertArrayHasKey('config_test', $export->read('core.extension')['module']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,107 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ExportStorageManager;
 | 
			
		||||
use Drupal\Core\Config\StorageTransformerException;
 | 
			
		||||
use Drupal\Core\Lock\NullLockBackend;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore arrr
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the export storage manager.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ExportStorageManagerTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'config_transformer_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests getting the export storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetStorage(): void {
 | 
			
		||||
    // Get the raw system.site config and set it in the sync storage.
 | 
			
		||||
    $rawConfig = $this->config('system.site')->getRawData();
 | 
			
		||||
    $this->container->get('config.storage.sync')->write('system.site', $rawConfig);
 | 
			
		||||
 | 
			
		||||
    // The export storage manager under test.
 | 
			
		||||
    $manager = new ExportStorageManager(
 | 
			
		||||
      $this->container->get('config.storage'),
 | 
			
		||||
      $this->container->get('database'),
 | 
			
		||||
      $this->container->get('event_dispatcher'),
 | 
			
		||||
      new NullLockBackend()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $storage = $manager->getStorage();
 | 
			
		||||
    $exported = $storage->read('system.site');
 | 
			
		||||
    // The test subscriber adds "Arrr" to the slogan of the sync config.
 | 
			
		||||
    $this->assertEquals($rawConfig['name'], $exported['name']);
 | 
			
		||||
    $this->assertEquals($rawConfig['slogan'] . ' Arrr', $exported['slogan']);
 | 
			
		||||
 | 
			
		||||
    // Save the config to active storage so that the transformer can alter it.
 | 
			
		||||
    $this->config('system.site')
 | 
			
		||||
      ->set('name', 'New name')
 | 
			
		||||
      ->set('slogan', 'New slogan')
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    // Get the storage again.
 | 
			
		||||
    $storage = $manager->getStorage();
 | 
			
		||||
    $exported = $storage->read('system.site');
 | 
			
		||||
    // The test subscriber adds "Arrr" to the slogan of the sync config.
 | 
			
		||||
    $this->assertEquals('New name', $exported['name']);
 | 
			
		||||
    $this->assertEquals($rawConfig['slogan'] . ' Arrr', $exported['slogan']);
 | 
			
		||||
 | 
			
		||||
    // Change what the transformer does without changing anything else to assert
 | 
			
		||||
    // that the event is dispatched every time the storage is needed.
 | 
			
		||||
    $this->container->get('state')->set('config_transform_test_mail', 'config@drupal.example');
 | 
			
		||||
    $storage = $manager->getStorage();
 | 
			
		||||
    $exported = $storage->read('system.site');
 | 
			
		||||
    // The mail is still set to the value from the beginning.
 | 
			
		||||
    $this->assertEquals('config@drupal.example', $exported['mail']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the export storage when it is locked.
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetStorageLock(): void {
 | 
			
		||||
    $lock = $this->createMock('Drupal\Core\Lock\LockBackendInterface');
 | 
			
		||||
    $lock->expects($this->exactly(2))
 | 
			
		||||
      ->method('acquire')
 | 
			
		||||
      ->with(ExportStorageManager::LOCK_NAME)
 | 
			
		||||
      ->willReturn(FALSE);
 | 
			
		||||
    $lock->expects($this->once())
 | 
			
		||||
      ->method('wait')
 | 
			
		||||
      ->with(ExportStorageManager::LOCK_NAME);
 | 
			
		||||
 | 
			
		||||
    // The export storage manager under test.
 | 
			
		||||
    $manager = new ExportStorageManager(
 | 
			
		||||
      $this->container->get('config.storage'),
 | 
			
		||||
      $this->container->get('database'),
 | 
			
		||||
      $this->container->get('event_dispatcher'),
 | 
			
		||||
      $lock
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $this->expectException(StorageTransformerException::class);
 | 
			
		||||
    $this->expectExceptionMessage("Cannot acquire config export transformer lock.");
 | 
			
		||||
    $manager->getStorage();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ConfigDirectoryNotDefinedException;
 | 
			
		||||
use Drupal\Core\Config\FileStorage;
 | 
			
		||||
use Drupal\Core\Config\FileStorageFactory;
 | 
			
		||||
use Drupal\Core\Site\Settings;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Config\FileStorageFactory
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class FileStorageFactoryTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getSync
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetSync(): void {
 | 
			
		||||
 | 
			
		||||
    // Write some random data to the sync storage.
 | 
			
		||||
    $name = $this->randomMachineName();
 | 
			
		||||
    $data = (array) $this->getRandomGenerator()->object();
 | 
			
		||||
    $storage = new FileStorage(Settings::get('config_sync_directory'));
 | 
			
		||||
    $storage->write($name, $data);
 | 
			
		||||
 | 
			
		||||
    // Get the sync storage and read from it.
 | 
			
		||||
    $sync = FileStorageFactory::getSync();
 | 
			
		||||
    $this->assertEquals($data, $sync->read($name));
 | 
			
		||||
 | 
			
		||||
    // Unset the sync directory setting.
 | 
			
		||||
    $settings = Settings::getInstance() ? Settings::getAll() : [];
 | 
			
		||||
    unset($settings['config_sync_directory']);
 | 
			
		||||
    new Settings($settings);
 | 
			
		||||
 | 
			
		||||
    // On an empty settings there is an exception thrown.
 | 
			
		||||
    $this->expectException(ConfigDirectoryNotDefinedException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The config sync directory is not defined in $settings["config_sync_directory"]');
 | 
			
		||||
    FileStorageFactory::getSync();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,125 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ConfigImporter;
 | 
			
		||||
use Drupal\Core\Config\ImportStorageTransformer;
 | 
			
		||||
use Drupal\Core\Config\MemoryStorage;
 | 
			
		||||
use Drupal\Core\Config\StorageTransformerException;
 | 
			
		||||
use Drupal\Core\Lock\NullLockBackend;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore arrr
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the import storage transformer.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ImportStorageTransformerTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'config_transformer_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the import transformation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTransform(): void {
 | 
			
		||||
    // Get the raw system.site config and set it in the sync storage.
 | 
			
		||||
    $rawConfig = $this->config('system.site')->getRawData();
 | 
			
		||||
 | 
			
		||||
    $storage = new MemoryStorage();
 | 
			
		||||
    $this->copyConfig($this->container->get('config.storage'), $storage);
 | 
			
		||||
 | 
			
		||||
    $import = $this->container->get('config.import_transformer')->transform($storage);
 | 
			
		||||
    $config = $import->read('system.site');
 | 
			
		||||
    // The test subscriber always adds "Arrr" to the current site name.
 | 
			
		||||
    $this->assertEquals($rawConfig['name'] . ' Arrr', $config['name']);
 | 
			
		||||
    $this->assertEquals($rawConfig['slogan'], $config['slogan']);
 | 
			
		||||
 | 
			
		||||
    // Update the site config in the storage to test a second transformation.
 | 
			
		||||
    $config['name'] = 'New name';
 | 
			
		||||
    $config['slogan'] = 'New slogan';
 | 
			
		||||
    $storage->write('system.site', $config);
 | 
			
		||||
 | 
			
		||||
    $import = $this->container->get('config.import_transformer')->transform($storage);
 | 
			
		||||
    $config = $import->read('system.site');
 | 
			
		||||
    // The test subscriber always adds "Arrr" to the current site name.
 | 
			
		||||
    $this->assertEquals($rawConfig['name'] . ' Arrr', $config['name']);
 | 
			
		||||
    $this->assertEquals('New slogan', $config['slogan']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the import transformer throws an exception.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTransformLocked(): void {
 | 
			
		||||
    // Mock the request lock not being available.
 | 
			
		||||
    $lock = $this->createMock('Drupal\Core\Lock\LockBackendInterface');
 | 
			
		||||
    $lock->expects($this->exactly(2))
 | 
			
		||||
      ->method('acquire')
 | 
			
		||||
      ->with(ImportStorageTransformer::LOCK_NAME)
 | 
			
		||||
      ->willReturn(FALSE);
 | 
			
		||||
    $lock->expects($this->once())
 | 
			
		||||
      ->method('wait')
 | 
			
		||||
      ->with(ImportStorageTransformer::LOCK_NAME);
 | 
			
		||||
 | 
			
		||||
    // The import transformer under test.
 | 
			
		||||
    $transformer = new ImportStorageTransformer(
 | 
			
		||||
      $this->container->get('event_dispatcher'),
 | 
			
		||||
      $this->container->get('database'),
 | 
			
		||||
      $lock,
 | 
			
		||||
      new NullLockBackend()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $this->expectException(StorageTransformerException::class);
 | 
			
		||||
    $this->expectExceptionMessage("Cannot acquire config import transformer lock.");
 | 
			
		||||
    $transformer->transform(new MemoryStorage());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the import transformer during a running config import.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTransformWhileImporting(): void {
 | 
			
		||||
    // Set up the database table with the current active config.
 | 
			
		||||
    // This simulates the config importer having its transformation done.
 | 
			
		||||
    $storage = $this->container->get('config.import_transformer')->transform($this->container->get('config.storage'));
 | 
			
		||||
 | 
			
		||||
    // Mock the persistent lock being unavailable due to a config import.
 | 
			
		||||
    $lock = $this->createMock('Drupal\Core\Lock\LockBackendInterface');
 | 
			
		||||
    $lock->expects($this->once())
 | 
			
		||||
      ->method('lockMayBeAvailable')
 | 
			
		||||
      ->with(ConfigImporter::LOCK_NAME)
 | 
			
		||||
      ->willReturn(FALSE);
 | 
			
		||||
 | 
			
		||||
    // The import transformer under test.
 | 
			
		||||
    $transformer = new ImportStorageTransformer(
 | 
			
		||||
      $this->container->get('event_dispatcher'),
 | 
			
		||||
      $this->container->get('database'),
 | 
			
		||||
      new NullLockBackend(),
 | 
			
		||||
      $lock
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Transform an empty memory storage.
 | 
			
		||||
    $import = $transformer->transform(new MemoryStorage());
 | 
			
		||||
    // Assert that the transformed storage is the same as the one used to
 | 
			
		||||
    // set up the simulated config importer.
 | 
			
		||||
    $this->assertEquals($storage->listAll(), $import->listAll());
 | 
			
		||||
    $this->assertNotEmpty($import->read('system.site'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,394 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\TypedConfigManagerInterface;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests validation of mailer dsn config.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 * @group Validation
 | 
			
		||||
 */
 | 
			
		||||
class MailerDsnConfigValidationTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Config manager service.
 | 
			
		||||
   */
 | 
			
		||||
  protected TypedConfigManagerInterface $configManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig('system');
 | 
			
		||||
    $this->configManager = $this->container->get(TypedConfigManagerInterface::class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the mailer scheme.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerSchemeValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // If the scheme is NULL, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = NULL;
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.scheme', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('This value should not be null.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the scheme is blank, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = '';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.scheme', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The mailer DSN must contain a scheme.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the scheme doesn't start with a letter, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = '-unexpected-first-character';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.scheme', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The mailer DSN scheme must start with a letter followed by zero or more letters, numbers, plus (+), minus (-) or periods (.)', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the scheme contains unexpected characters, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'unexpected_underscore';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.scheme', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The mailer DSN scheme must start with a letter followed by zero or more letters, numbers, plus (+), minus (-) or periods (.)', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the scheme is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'smtp';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If the scheme is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'sendmail+smtp';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If the scheme is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'drupal.test-capture';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the mailer host.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerHostValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // If the host is NULL, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['host'] = NULL;
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.host', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('This value should not be null.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the host is blank, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['host'] = '';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.host', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The mailer DSN must contain a host (use "default" by default).', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the host contains a newline, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['host'] = "host\nwith\nnewline";
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.host', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The mailer DSN host should conform to RFC 3986 URI host component.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the host contains unexpected characters, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['host'] = "host\rwith\tcontrol-chars";
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.host', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The mailer DSN host should conform to RFC 3986 URI host component.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the host is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['host'] = 'default';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If the host is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['host'] = 'mail.example.com';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If the host is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['host'] = '127.0.0.1';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If the host is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['host'] = '[::1]';
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the password for the mailer user.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerUserPasswordValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // If the user is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['user'] = "any😎thing\ngoes";
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If the password is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['password'] = "any😎thing\ngoes";
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the port used by the mailer.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerPortValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // If the port is negative, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['port'] = -1;
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.port', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The mailer DSN port must be between 0 and 65535.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the port greater than 65535, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['port'] = 655351 + 1;
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.port', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The mailer DSN port must be between 0 and 65535.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the port is valid, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['port'] = 587;
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the default options of the mailer.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerTransportDefaultOptionsValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // Set scheme to an unknown schema.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'drupal.unknown-scheme+https';
 | 
			
		||||
 | 
			
		||||
    // If there is no more specific type for a scheme, options with any key
 | 
			
		||||
    // should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['options'] = [
 | 
			
		||||
      'any_bool' => TRUE,
 | 
			
		||||
      'any_int' => 42,
 | 
			
		||||
      'any_string' => "any😎thing\ngoes",
 | 
			
		||||
    ];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the options for the 'native' mailer scheme.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerTransportNativeOptionsValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // Set scheme to native.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'native';
 | 
			
		||||
 | 
			
		||||
    // If the options contain an invalid key, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['options'] = ['invalid_key' => 'Hello'];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.options.invalid_key', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame("'invalid_key' is not a supported key.", (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If options is an empty map, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['options'] = [];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the options for the 'null' mailer scheme.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerTransportNullOptionsValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // Set scheme to null.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'null';
 | 
			
		||||
 | 
			
		||||
    // If the options contain an invalid key, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['options'] = ['invalid_key' => 'Hello'];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.options.invalid_key', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame("'invalid_key' is not a supported key.", (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If options is an empty map, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['options'] = [];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the options for the 'sendmail' mailer scheme.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerTransportSendmailOptionsValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // Set scheme to sendmail.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'sendmail';
 | 
			
		||||
 | 
			
		||||
    // If the options contain an invalid command, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['options'] = ['command' => "sendmail\t-bs\n"];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.options.command', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The command option is not allowed to span multiple lines or contain control characters.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the options contain an invalid key, it should be an error.
 | 
			
		||||
    $data['mailer_dsn']['options'] = ['invalid_key' => 'Hello'];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.options.invalid_key', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame("'invalid_key' is not a supported key.", (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the options contain a command, it should accepted.
 | 
			
		||||
    $data['mailer_dsn']['options'] = ['command' => 'sendmail -bs'];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If options is an empty map, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['options'] = [];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the options for the 'smtps' mailer scheme.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMailerTransportSMTPOptionsValidation(): void {
 | 
			
		||||
    $config = $this->config('system.mail');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
 | 
			
		||||
    // Set scheme to smtps.
 | 
			
		||||
    $data['mailer_dsn']['scheme'] = 'smtps';
 | 
			
		||||
 | 
			
		||||
    // If the options contain an invalid peer_fingerprint, it should be an
 | 
			
		||||
    // error.
 | 
			
		||||
    $data['mailer_dsn']['options'] = [
 | 
			
		||||
      'verify_peer' => FALSE,
 | 
			
		||||
      'peer_fingerprint' => 'BE:F7:B9:CA:0F:6E:0F:29:9B:E9:B4:64:99:35:D6:27',
 | 
			
		||||
    ];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.options.peer_fingerprint', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The peer_fingerprint option requires an md5, sha1 or sha256 certificate fingerprint in hex with all separators (colons) removed.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the options contain a valid peer_fingerprint, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['options'] = [
 | 
			
		||||
      'verify_peer' => FALSE,
 | 
			
		||||
      'peer_fingerprint' => 'BEF7B9CA0F6E0F299BE9B4649935D627',
 | 
			
		||||
    ];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If the options contain a valid peer_fingerprint, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['options'] = [
 | 
			
		||||
      'verify_peer' => TRUE,
 | 
			
		||||
      'peer_fingerprint' => '87abbc4d1c3f23146362c6a1168fb7e90a56569c4c97275c69c0630dc06e526d',
 | 
			
		||||
    ];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
 | 
			
		||||
    // If the options contain a local_domain with a newline, it should be an
 | 
			
		||||
    // error.
 | 
			
		||||
    $data['mailer_dsn']['options'] = ['local_domain' => "host\nwith\nnewline"];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.options.local_domain', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The local_domain is not allowed to span multiple lines or contain control characters.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the options contain a local_domain with unexpected characters, it
 | 
			
		||||
    // should be an error.
 | 
			
		||||
    $data['mailer_dsn']['options'] = ['local_domain' => "host\rwith\tcontrol-chars"];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('mailer_dsn.options.local_domain', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('The local_domain is not allowed to span multiple lines or contain control characters.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // If the options contain a valid local_domain, it should be accepted.
 | 
			
		||||
    $data['mailer_dsn']['options'] = ['local_domain' => 'www.example.com'];
 | 
			
		||||
    $violations = $this->configManager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(0, $violations);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,209 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Schema\SchemaCheckTrait;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the functionality of SchemaCheckTrait.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class SchemaCheckTraitTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use SchemaCheckTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The typed config manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\TypedConfigManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $typedConfig;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test', 'config_schema_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['config_test', 'config_schema_test']);
 | 
			
		||||
    $this->typedConfig = \Drupal::service('config.typed');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests \Drupal\Core\Config\Schema\SchemaCheckTrait.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerCheckConfigSchema
 | 
			
		||||
   */
 | 
			
		||||
  public function testCheckConfigSchema(string $type_to_validate_against, bool $validate_constraints, array|bool $nulled_expectations, array|bool $no_data_expectations, array $expectations): void {
 | 
			
		||||
    // Test a non existing schema.
 | 
			
		||||
    $ret = $this->checkConfigSchema($this->typedConfig, 'config_schema_test.no_schema', $this->config('config_schema_test.no_schema')->get());
 | 
			
		||||
    $this->assertFalse($ret);
 | 
			
		||||
 | 
			
		||||
    // Test an existing schema with valid data.
 | 
			
		||||
    $config_data = $this->config('config_test.types')->get();
 | 
			
		||||
    $ret = $this->checkConfigSchema($this->typedConfig, 'config_test.types', $config_data);
 | 
			
		||||
    $this->assertTrue($ret);
 | 
			
		||||
 | 
			
		||||
    // Test it is possible to mark any schema type as required (not nullable).
 | 
			
		||||
    $nulled_config_data = array_fill_keys(array_keys($config_data), NULL);
 | 
			
		||||
    $ret = $this->checkConfigSchema($this->typedConfig, $type_to_validate_against, $nulled_config_data, $validate_constraints);
 | 
			
		||||
    $this->assertSame($nulled_expectations, $ret);
 | 
			
		||||
 | 
			
		||||
    // Add a new key, a new array and overwrite boolean with array to test the
 | 
			
		||||
    // error messages.
 | 
			
		||||
    $config_data = ['new_key' => 'new_value', 'new_array' => []] + $config_data;
 | 
			
		||||
    $config_data['boolean'] = [];
 | 
			
		||||
 | 
			
		||||
    $ret = $this->checkConfigSchema($this->typedConfig, $type_to_validate_against, $config_data, $validate_constraints);
 | 
			
		||||
    $this->assertEquals($expectations, $ret);
 | 
			
		||||
 | 
			
		||||
    // Omit all data, this should trigger validation errors for required keys
 | 
			
		||||
    // missing.
 | 
			
		||||
    $config_data = [];
 | 
			
		||||
    $ret = $this->checkConfigSchema($this->typedConfig, $type_to_validate_against, $config_data, $validate_constraints);
 | 
			
		||||
    $this->assertEquals($no_data_expectations, $ret);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns test data for validating configuration schema.
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerCheckConfigSchema(): array {
 | 
			
		||||
    // Storage type check errors.
 | 
			
		||||
    // @see \Drupal\Core\Config\Schema\SchemaCheckTrait::checkValue()
 | 
			
		||||
    $expected_storage_null_check_errors = [
 | 
			
		||||
      // TRICKY: `_core` is added during installation even if it is absent from
 | 
			
		||||
      // core/modules/config/tests/config_test/config/install/config_test.dynamic.dotted.default.yml.
 | 
			
		||||
      // @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
 | 
			
		||||
      'config_test.types:_core' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Mapping',
 | 
			
		||||
      'config_test.types:array' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Sequence',
 | 
			
		||||
      'config_test.types:mapping_with_only_required_keys' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Mapping',
 | 
			
		||||
      'config_test.types:mapping_with_some_required_keys' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Mapping',
 | 
			
		||||
      'config_test.types:mapping_with_only_optional_keys' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Mapping',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected_storage_type_check_errors = [
 | 
			
		||||
      'config_test.types:new_key' => 'missing schema',
 | 
			
		||||
      'config_test.types:new_array' => 'missing schema',
 | 
			
		||||
      'config_test.types:boolean' => 'non-scalar value but not defined as an array (such as mapping or sequence)',
 | 
			
		||||
    ];
 | 
			
		||||
    // Validation constraints violations.
 | 
			
		||||
    // @see \Drupal\Core\TypedData\TypedDataInterface::validate()
 | 
			
		||||
    $expected_validation_errors = [
 | 
			
		||||
      '0' => "[new_key] 'new_key' is not a supported key.",
 | 
			
		||||
      '1' => "[new_array] 'new_array' is not a supported key.",
 | 
			
		||||
      '2' => '[boolean] This value should be of the correct primitive type.',
 | 
			
		||||
    ];
 | 
			
		||||
    $basic_cases = [
 | 
			
		||||
      'config_test.types, without validation' => [
 | 
			
		||||
        'config_test.types',
 | 
			
		||||
        FALSE,
 | 
			
		||||
        $expected_storage_null_check_errors,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        $expected_storage_type_check_errors,
 | 
			
		||||
      ],
 | 
			
		||||
      'config_test.types, with validation' => [
 | 
			
		||||
        'config_test.types',
 | 
			
		||||
        TRUE,
 | 
			
		||||
        $expected_storage_null_check_errors,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        $expected_storage_type_check_errors + $expected_validation_errors,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Test that if the exact same schema is reused but now has the constraint
 | 
			
		||||
    // "FullyValidatable" specified at the top level, that:
 | 
			
		||||
    // 1. `NULL` values now trigger validation errors, except when
 | 
			
		||||
    //    `nullable: true` is set.
 | 
			
		||||
    // 2. missing required keys now trigger validation errors, except when
 | 
			
		||||
    //    `requiredKey: false` is set.
 | 
			
		||||
    // @see `type: config_test.types.fully_validatable`
 | 
			
		||||
    // @see core/modules/config/tests/config_test/config/schema/config_test.schema.yml
 | 
			
		||||
    $expected_storage_null_check_errors = [
 | 
			
		||||
      // TRICKY: `_core` is added during installation even if it is absent from
 | 
			
		||||
      // core/modules/config/tests/config_test/config/install/config_test.dynamic.dotted.default.yml.
 | 
			
		||||
      // @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
 | 
			
		||||
      'config_test.types.fully_validatable:_core' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Mapping',
 | 
			
		||||
      'config_test.types.fully_validatable:array' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Sequence',
 | 
			
		||||
      'config_test.types.fully_validatable:mapping_with_only_required_keys' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Mapping',
 | 
			
		||||
      'config_test.types.fully_validatable:mapping_with_some_required_keys' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Mapping',
 | 
			
		||||
      'config_test.types.fully_validatable:mapping_with_only_optional_keys' => 'variable type is NULL but applied schema class is Drupal\Core\Config\Schema\Mapping',
 | 
			
		||||
    ];
 | 
			
		||||
    $expected_storage_type_check_errors = [
 | 
			
		||||
      'config_test.types.fully_validatable:new_key' => 'missing schema',
 | 
			
		||||
      'config_test.types.fully_validatable:new_array' => 'missing schema',
 | 
			
		||||
      'config_test.types.fully_validatable:boolean' => 'non-scalar value but not defined as an array (such as mapping or sequence)',
 | 
			
		||||
    ];
 | 
			
		||||
    $opt_in_cases = [
 | 
			
		||||
      'config_test.types.fully_validatable, without validation' => [
 | 
			
		||||
        'config_test.types.fully_validatable',
 | 
			
		||||
        FALSE,
 | 
			
		||||
        $expected_storage_null_check_errors,
 | 
			
		||||
        TRUE,
 | 
			
		||||
        $expected_storage_type_check_errors,
 | 
			
		||||
      ],
 | 
			
		||||
      'config_test.types.fully_validatable, with validation' => [
 | 
			
		||||
        'config_test.types.fully_validatable',
 | 
			
		||||
        TRUE,
 | 
			
		||||
        $expected_storage_null_check_errors + [
 | 
			
		||||
          '[_core] This value should not be null.',
 | 
			
		||||
          '[array] This value should not be null.',
 | 
			
		||||
          '[boolean] This value should not be null.',
 | 
			
		||||
          '[exp] This value should not be null.',
 | 
			
		||||
          '[float] This value should not be null.',
 | 
			
		||||
          '[float_as_integer] This value should not be null.',
 | 
			
		||||
          '[hex] This value should not be null.',
 | 
			
		||||
          '[int] This value should not be null.',
 | 
			
		||||
          '[string] This value should not be null.',
 | 
			
		||||
          '[string_int] This value should not be null.',
 | 
			
		||||
          '[mapping_with_only_required_keys] This value should not be null.',
 | 
			
		||||
          '[mapping_with_some_required_keys] This value should not be null.',
 | 
			
		||||
          '[mapping_with_only_optional_keys] This value should not be null.',
 | 
			
		||||
        ],
 | 
			
		||||
        [
 | 
			
		||||
          "[] 'array' is a required key.",
 | 
			
		||||
          "[] 'boolean' is a required key.",
 | 
			
		||||
          "[] 'exp' is a required key.",
 | 
			
		||||
          "[] 'float' is a required key.",
 | 
			
		||||
          "[] 'float_as_integer' is a required key.",
 | 
			
		||||
          "[] 'hex' is a required key.",
 | 
			
		||||
          "[] 'int' is a required key.",
 | 
			
		||||
          "[] 'string' is a required key.",
 | 
			
		||||
          "[] 'string_int' is a required key.",
 | 
			
		||||
          "[] 'nullable_array' is a required key.",
 | 
			
		||||
          "[] 'nullable_boolean' is a required key.",
 | 
			
		||||
          "[] 'nullable_exp' is a required key.",
 | 
			
		||||
          "[] 'nullable_float' is a required key.",
 | 
			
		||||
          "[] 'nullable_float_as_integer' is a required key.",
 | 
			
		||||
          "[] 'nullable_hex' is a required key.",
 | 
			
		||||
          "[] 'nullable_int' is a required key.",
 | 
			
		||||
          "[] 'nullable_octal' is a required key.",
 | 
			
		||||
          "[] 'nullable_string' is a required key.",
 | 
			
		||||
          "[] 'nullable_string_int' is a required key.",
 | 
			
		||||
          "[] 'mapping_with_only_required_keys' is a required key.",
 | 
			
		||||
          "[] 'mapping_with_some_required_keys' is a required key.",
 | 
			
		||||
          "[] 'mapping_with_only_optional_keys' is a required key.",
 | 
			
		||||
        ],
 | 
			
		||||
        $expected_storage_type_check_errors + $expected_validation_errors + [
 | 
			
		||||
          // For `mapping_with_only_required_keys`: errors for all 4 keys.
 | 
			
		||||
          3 => "[mapping_with_only_required_keys] 'north' is a required key.",
 | 
			
		||||
          4 => "[mapping_with_only_required_keys] 'east' is a required key.",
 | 
			
		||||
          5 => "[mapping_with_only_required_keys] 'south' is a required key.",
 | 
			
		||||
          6 => "[mapping_with_only_required_keys] 'west' is a required key.",
 | 
			
		||||
          // For `mapping_with_some_required_keys`: errors for 2 required keys.
 | 
			
		||||
          7 => "[mapping_with_some_required_keys] 'north' is a required key.",
 | 
			
		||||
          8 => "[mapping_with_some_required_keys] 'south' is a required key.",
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return array_merge($basic_cases, $opt_in_cases);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\Tests\Traits\Core\Config\SchemaConfigListenerTestTrait;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the functionality of ConfigSchemaChecker in KernelTestBase tests.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class SchemaConfigListenerTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use SchemaConfigListenerTestTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    // Install configuration provided by the module so that the order of the
 | 
			
		||||
    // config keys is the same as
 | 
			
		||||
    // \Drupal\FunctionalTests\Core\Config\SchemaConfigListenerTest.
 | 
			
		||||
    $this->installConfig(['config_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,179 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests validation of certain elements common to all config.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 * @group Validation
 | 
			
		||||
 */
 | 
			
		||||
class SimpleConfigValidationTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig('system');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the validation of the default configuration hash.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDefaultConfigHashValidation(): void {
 | 
			
		||||
    $config = $this->config('system.site');
 | 
			
		||||
    $this->assertFalse($config->isNew());
 | 
			
		||||
    $data = $config->get();
 | 
			
		||||
    $original_hash = $data['_core']['default_config_hash'];
 | 
			
		||||
    $this->assertNotEmpty($original_hash);
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
 | 
			
		||||
    $typed_config_manager = $this->container->get('config.typed');
 | 
			
		||||
 | 
			
		||||
    // If the default_config_hash is NULL, it should be an error.
 | 
			
		||||
    $data['_core']['default_config_hash'] = NULL;
 | 
			
		||||
    $violations = $typed_config_manager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('_core.default_config_hash', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('This value should not be null.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Config hashes must be 43 characters long.
 | 
			
		||||
    $data['_core']['default_config_hash'] = $original_hash . '-long';
 | 
			
		||||
    $violations = $typed_config_manager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('_core.default_config_hash', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('This value should have exactly <em class="placeholder">43</em> characters.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Config hashes can only contain certain characters, and spaces aren't one
 | 
			
		||||
    // of them. If we replace the final character of the original hash with a
 | 
			
		||||
    // space, we should get an error.
 | 
			
		||||
    $data['_core']['default_config_hash'] = substr($original_hash, 0, -1) . ' ';
 | 
			
		||||
    $violations = $typed_config_manager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('_core.default_config_hash', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame('This value is not valid.', (string) $violations[0]->getMessage());
 | 
			
		||||
 | 
			
		||||
    $data['_core']['default_config_hash'] = $original_hash;
 | 
			
		||||
    $data['_core']['invalid_key'] = 'Hello';
 | 
			
		||||
    $violations = $typed_config_manager->createFromNameAndData($config->getName(), $data)
 | 
			
		||||
      ->validate();
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame('_core.invalid_key', $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame("'invalid_key' is not a supported key.", (string) $violations[0]->getMessage());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Data provider for ::testSpecialCharacters().
 | 
			
		||||
   *
 | 
			
		||||
   * @return array[]
 | 
			
		||||
   *   The test cases.
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerSpecialCharacters(): array {
 | 
			
		||||
    $data = [];
 | 
			
		||||
 | 
			
		||||
    for ($code_point = 0; $code_point < 32; $code_point++) {
 | 
			
		||||
      $data["label $code_point"] = [
 | 
			
		||||
        'system.site',
 | 
			
		||||
        'name',
 | 
			
		||||
        mb_chr($code_point),
 | 
			
		||||
        'Labels are not allowed to span multiple lines or contain control characters.',
 | 
			
		||||
      ];
 | 
			
		||||
      $data["text $code_point"] = [
 | 
			
		||||
        'system.maintenance',
 | 
			
		||||
        'message',
 | 
			
		||||
        mb_chr($code_point),
 | 
			
		||||
        'Text is not allowed to contain control characters, only visible characters.',
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    // Line feeds (ASCII 10) and carriage returns (ASCII 13) are used to create
 | 
			
		||||
    // new lines, so they are allowed in text data, along with tabs (ASCII 9).
 | 
			
		||||
    $data['text 9'][3] = $data['text 10'][3] = $data['text 13'][3] = NULL;
 | 
			
		||||
 | 
			
		||||
    // Ensure emoji are allowed.
 | 
			
		||||
    $data['emoji in label'] = [
 | 
			
		||||
      'system.site',
 | 
			
		||||
      'name',
 | 
			
		||||
      '😎',
 | 
			
		||||
      NULL,
 | 
			
		||||
    ];
 | 
			
		||||
    $data['emoji in text'] = [
 | 
			
		||||
      'system.maintenance',
 | 
			
		||||
      'message',
 | 
			
		||||
      '🤓',
 | 
			
		||||
      NULL,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that special characters are not allowed in labels or text data.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $config_name
 | 
			
		||||
   *   The name of the simple config to test with.
 | 
			
		||||
   * @param string $property
 | 
			
		||||
   *   The config property in which to embed a control character.
 | 
			
		||||
   * @param string $character
 | 
			
		||||
   *   A special character to embed.
 | 
			
		||||
   * @param string|null $expected_error_message
 | 
			
		||||
   *   The expected validation error message, if any.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerSpecialCharacters
 | 
			
		||||
   */
 | 
			
		||||
  public function testSpecialCharacters(string $config_name, string $property, string $character, ?string $expected_error_message): void {
 | 
			
		||||
    $config = $this->config($config_name)
 | 
			
		||||
      ->set($property, "This has a special character: $character");
 | 
			
		||||
 | 
			
		||||
    $violations = $this->container->get('config.typed')
 | 
			
		||||
      ->createFromNameAndData($config->getName(), $config->get())
 | 
			
		||||
      ->validate();
 | 
			
		||||
 | 
			
		||||
    if ($expected_error_message === NULL) {
 | 
			
		||||
      $this->assertCount(0, $violations);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $code_point = mb_ord($character);
 | 
			
		||||
      $this->assertCount(1, $violations, "Character $code_point did not raise a constraint violation.");
 | 
			
		||||
      $this->assertSame($property, $violations[0]->getPropertyPath());
 | 
			
		||||
      $this->assertSame($expected_error_message, (string) $violations[0]->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that plugin IDs in simple config are validated.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $config_name
 | 
			
		||||
   *   The name of the config object to validate.
 | 
			
		||||
   * @param string $property
 | 
			
		||||
   *   The property path to set. This will receive the value 'non_existent' and
 | 
			
		||||
   *   is expected to raise a "plugin does not exist" error.
 | 
			
		||||
   *
 | 
			
		||||
   * @testWith ["system.mail", "interface.0"]
 | 
			
		||||
   *   ["system.image", "toolkit"]
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidPluginId(string $config_name, string $property): void {
 | 
			
		||||
    $config = $this->config($config_name);
 | 
			
		||||
 | 
			
		||||
    $violations = $this->container->get('config.typed')
 | 
			
		||||
      ->createFromNameAndData($config_name, $config->set($property, 'non_existent')->get())
 | 
			
		||||
      ->validate();
 | 
			
		||||
 | 
			
		||||
    $this->assertCount(1, $violations);
 | 
			
		||||
    $this->assertSame($property, $violations[0]->getPropertyPath());
 | 
			
		||||
    $this->assertSame("The 'non_existent' plugin does not exist.", (string) $violations[0]->getMessage());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,84 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Storage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\FileStorage;
 | 
			
		||||
use Drupal\Core\Config\CachedStorage;
 | 
			
		||||
use Drupal\Core\StreamWrapper\PublicStream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests CachedStorage operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class CachedStorageTest extends ConfigStorageTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The cache backend the cached storage is using.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Cache\CacheBackendInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $cache;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The file storage the cached storage is using.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\FileStorage
 | 
			
		||||
   */
 | 
			
		||||
  protected $fileStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    // Create a directory.
 | 
			
		||||
    $dir = PublicStream::basePath() . '/config';
 | 
			
		||||
    $this->fileStorage = new FileStorage($dir);
 | 
			
		||||
    $this->storage = new CachedStorage($this->fileStorage, \Drupal::service('cache.config'));
 | 
			
		||||
    $this->cache = \Drupal::service('cache_factory')->get('config');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidStorage(): void {
 | 
			
		||||
    $this->markTestSkipped('No-op as this test does not make sense');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function read($name) {
 | 
			
		||||
    $data = $this->cache->get($name);
 | 
			
		||||
    // Cache misses fall through to the underlying storage.
 | 
			
		||||
    return $data ? $data->data : $this->fileStorage->read($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function insert($name, $data): void {
 | 
			
		||||
    $this->fileStorage->write($name, $data);
 | 
			
		||||
    $this->cache->set($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function update($name, $data): void {
 | 
			
		||||
    $this->fileStorage->write($name, $data);
 | 
			
		||||
    $this->cache->set($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function delete($name): void {
 | 
			
		||||
    $this->cache->delete($name);
 | 
			
		||||
    unlink($this->fileStorage->getFilePath($name));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,331 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Storage\Checkpoint;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Checkpoint\CheckpointStorageInterface;
 | 
			
		||||
use Drupal\Core\Config\ConfigImporter;
 | 
			
		||||
use Drupal\Core\Config\StorageComparer;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests CheckpointStorage operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class CheckpointStorageTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system', 'config_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the save and read operations of checkpoint storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigSaveAndRead(): void {
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
 | 
			
		||||
    $this->config('system.site')->set('name', 'Test1')->save();
 | 
			
		||||
    $check1 = $checkpoint_storage->checkpoint('A');
 | 
			
		||||
    $this->config('system.site')->set('name', 'Test2')->save();
 | 
			
		||||
    $check2 = $checkpoint_storage->checkpoint('B');
 | 
			
		||||
    $this->config('system.site')->set('name', 'Test3')->save();
 | 
			
		||||
 | 
			
		||||
    $this->assertSame('Test3', $this->config('system.site')->get('name'));
 | 
			
		||||
    $this->assertSame('Test1', $checkpoint_storage->read('system.site')['name']);
 | 
			
		||||
 | 
			
		||||
    // The config listings should be exactly the same.
 | 
			
		||||
    $this->assertSame($checkpoint_storage->listAll(), $this->container->get('config.storage')->listAll());
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check2);
 | 
			
		||||
    $this->assertSame('Test2', $checkpoint_storage->read('system.site')['name']);
 | 
			
		||||
    $this->assertSame($checkpoint_storage->listAll(), $this->container->get('config.storage')->listAll());
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check1);
 | 
			
		||||
    $this->assertSame('Test1', $checkpoint_storage->read('system.site')['name']);
 | 
			
		||||
    $this->assertSame($checkpoint_storage->listAll(), $this->container->get('config.storage')->listAll());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the delete operation of checkpoint storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigDelete(): void {
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
 | 
			
		||||
    $check1 = $checkpoint_storage->checkpoint('A');
 | 
			
		||||
    $this->config('config_test.system')->delete();
 | 
			
		||||
 | 
			
		||||
    $this->assertFalse($this->container->get('config.storage')->exists('config_test.system'));
 | 
			
		||||
    $this->assertTrue($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertSame('bar', $checkpoint_storage->read('config_test.system')['foo']);
 | 
			
		||||
 | 
			
		||||
    $this->assertContains('config_test.system', $checkpoint_storage->listAll());
 | 
			
		||||
    $this->assertContains('config_test.system', $checkpoint_storage->listAll('config_test.'));
 | 
			
		||||
    $this->assertNotContains('config_test.system', $checkpoint_storage->listAll('system.'));
 | 
			
		||||
    // Should not be part of the active storage anymore.
 | 
			
		||||
    $this->assertNotContains('config_test.system', $this->container->get('config.storage')->listAll());
 | 
			
		||||
 | 
			
		||||
    $check2 = $checkpoint_storage->checkpoint('B');
 | 
			
		||||
 | 
			
		||||
    $this->config('config_test.system')->set('foo', 'foobar')->save();
 | 
			
		||||
    $this->assertTrue($this->container->get('config.storage')->exists('config_test.system'));
 | 
			
		||||
    $this->assertTrue($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertSame('bar', $checkpoint_storage->read('config_test.system')['foo']);
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check2);
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->read('config_test.system'));
 | 
			
		||||
    $this->assertNotContains('config_test.system', $checkpoint_storage->listAll());
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check1);
 | 
			
		||||
    $this->assertTrue($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertSame('bar', $checkpoint_storage->read('config_test.system')['foo']);
 | 
			
		||||
    $this->assertContains('config_test.system', $checkpoint_storage->listAll());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the create operation of checkpoint storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigCreate(): void {
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
 | 
			
		||||
    $this->config('config_test.system')->delete();
 | 
			
		||||
    $check1 = $checkpoint_storage->checkpoint('A');
 | 
			
		||||
    $this->config('config_test.system')->set('foo', 'foobar')->save();
 | 
			
		||||
 | 
			
		||||
    $this->assertTrue($this->container->get('config.storage')->exists('config_test.system'));
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->read('config_test.system'));
 | 
			
		||||
 | 
			
		||||
    $this->assertNotContains('config_test.system', $checkpoint_storage->listAll());
 | 
			
		||||
    $this->assertNotContains('config_test.system', $checkpoint_storage->listAll('config_test.'));
 | 
			
		||||
    $this->assertContains('system.site', $checkpoint_storage->listAll('system.'));
 | 
			
		||||
    $this->assertContains('config_test.system', $this->container->get('config.storage')->listAll());
 | 
			
		||||
 | 
			
		||||
    $check2 = $checkpoint_storage->checkpoint('B');
 | 
			
		||||
    $this->config('config_test.system')->delete();
 | 
			
		||||
 | 
			
		||||
    $this->assertFalse($this->container->get('config.storage')->exists('config_test.system'));
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->read('config_test.system'));
 | 
			
		||||
 | 
			
		||||
    $this->config('config_test.system')->set('foo', 'foobar')->save();
 | 
			
		||||
    $this->assertTrue($this->container->get('config.storage')->exists('config_test.system'));
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->read('config_test.system'));
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check2);
 | 
			
		||||
    $this->assertTrue($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertSame('foobar', $checkpoint_storage->read('config_test.system')['foo']);
 | 
			
		||||
    $this->assertContains('config_test.system', $checkpoint_storage->listAll());
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check1);
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->exists('config_test.system'));
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->read('config_test.system'));
 | 
			
		||||
    $this->assertNotContains('config_test.system', $checkpoint_storage->listAll());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the rename operation of checkpoint storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function testConfigRename(): void {
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
    $check1 = $checkpoint_storage->checkpoint('A');
 | 
			
		||||
    $this->container->get('config.factory')->rename('config_test.dynamic.dotted.default', 'config_test.dynamic.renamed');
 | 
			
		||||
    $this->config('config_test.dynamic.renamed')->set('id', 'renamed')->save();
 | 
			
		||||
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->exists('config_test.dynamic.renamed'));
 | 
			
		||||
    $this->assertTrue($checkpoint_storage->exists('config_test.dynamic.dotted.default'));
 | 
			
		||||
    $this->assertSame('dotted.default', $checkpoint_storage->read('config_test.dynamic.dotted.default')['id']);
 | 
			
		||||
    $this->assertSame($checkpoint_storage->read('config_test.dynamic.dotted.default')['uuid'], $this->config('config_test.dynamic.renamed')->get('uuid'));
 | 
			
		||||
 | 
			
		||||
    $check2 = $checkpoint_storage->checkpoint('B');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
 | 
			
		||||
    $storage = $this->container->get('entity_type.manager')->getStorage('config_test');
 | 
			
		||||
    // Entity1 will be deleted by the test.
 | 
			
		||||
    $entity1 = $storage->create(
 | 
			
		||||
      [
 | 
			
		||||
        'id' => 'dotted.default',
 | 
			
		||||
        'label' => 'Another one',
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
    $entity1->save();
 | 
			
		||||
 | 
			
		||||
    $check3 = $checkpoint_storage->checkpoint('C');
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check2);
 | 
			
		||||
    $this->assertFalse($checkpoint_storage->exists('config_test.dynamic.dotted.default'));
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check3);
 | 
			
		||||
    $this->assertTrue($checkpoint_storage->exists('config_test.dynamic.dotted.default'));
 | 
			
		||||
    $this->assertNotEquals($checkpoint_storage->read('config_test.dynamic.dotted.default')['uuid'], $this->config('config_test.dynamic.renamed')->get('uuid'));
 | 
			
		||||
    $this->assertSame('Another one', $checkpoint_storage->read('config_test.dynamic.dotted.default')['label']);
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check1);
 | 
			
		||||
    $this->assertSame('Default', $checkpoint_storage->read('config_test.dynamic.dotted.default')['label']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the revert operation of checkpoint storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRevert(): void {
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
    $check1 = $checkpoint_storage->checkpoint('A');
 | 
			
		||||
    $this->assertTrue($this->container->get('module_installer')->uninstall(['config_test']));
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
    $check2 = $checkpoint_storage->checkpoint('B');
 | 
			
		||||
 | 
			
		||||
    $importer = $this->getConfigImporter($checkpoint_storage);
 | 
			
		||||
    $config_changelist = $importer->getStorageComparer()->createChangelist()->getChangelist();
 | 
			
		||||
    $this->assertContains('config_test.dynamic.dotted.default', $config_changelist['create']);
 | 
			
		||||
    $this->assertSame(['core.extension'], $config_changelist['update']);
 | 
			
		||||
    $this->assertSame([], $config_changelist['delete']);
 | 
			
		||||
    $this->assertSame([], $config_changelist['rename']);
 | 
			
		||||
 | 
			
		||||
    $importer->import();
 | 
			
		||||
    $this->assertSame([], $importer->getErrors());
 | 
			
		||||
 | 
			
		||||
    $this->assertTrue($this->container->get('module_handler')->moduleExists('config_test'));
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check2);
 | 
			
		||||
 | 
			
		||||
    $importer = $this->getConfigImporter($checkpoint_storage);
 | 
			
		||||
    $config_changelist = $importer->getStorageComparer()->createChangelist()->getChangelist();
 | 
			
		||||
    $this->assertContains('config_test.dynamic.dotted.default', $config_changelist['delete']);
 | 
			
		||||
    $this->assertSame(['core.extension'], $config_changelist['update']);
 | 
			
		||||
    $this->assertSame([], $config_changelist['create']);
 | 
			
		||||
    $this->assertSame([], $config_changelist['rename']);
 | 
			
		||||
    $importer->import();
 | 
			
		||||
    $this->assertFalse($this->container->get('module_handler')->moduleExists('config_test'));
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check1);
 | 
			
		||||
    $importer = $this->getConfigImporter($checkpoint_storage);
 | 
			
		||||
    $importer->getStorageComparer()->createChangelist();
 | 
			
		||||
    $importer->import();
 | 
			
		||||
    $this->assertTrue($this->container->get('module_handler')->moduleExists('config_test'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the rename operation of checkpoint storage with collections.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRevertWithCollections(): void {
 | 
			
		||||
    $collections = [
 | 
			
		||||
      'another_collection',
 | 
			
		||||
      'collection.test1',
 | 
			
		||||
      'collection.test2',
 | 
			
		||||
    ];
 | 
			
		||||
    // Set the event listener to return three possible collections.
 | 
			
		||||
    // @see \Drupal\config_collection_install_test\EventSubscriber
 | 
			
		||||
    \Drupal::state()->set('config_collection_install_test.collection_names', $collections);
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
    $checkpoint_storage->checkpoint('A');
 | 
			
		||||
 | 
			
		||||
    // Install the test module.
 | 
			
		||||
    $this->assertTrue($this->container->get('module_installer')->install(['config_collection_install_test']));
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active_storage */
 | 
			
		||||
    $active_storage = \Drupal::service('config.storage');
 | 
			
		||||
    $this->assertEquals($collections, $active_storage->getAllCollectionNames());
 | 
			
		||||
    foreach ($collections as $collection) {
 | 
			
		||||
      $collection_storage = $active_storage->createCollection($collection);
 | 
			
		||||
      $data = $collection_storage->read('config_collection_install_test.test');
 | 
			
		||||
      $this->assertEquals($collection, $data['collection']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $check2 = $checkpoint_storage->checkpoint('B');
 | 
			
		||||
 | 
			
		||||
    $importer = $this->getConfigImporter($checkpoint_storage);
 | 
			
		||||
    $storage_comparer = $importer->getStorageComparer();
 | 
			
		||||
    $config_changelist = $storage_comparer->createChangelist()->getChangelist();
 | 
			
		||||
    $this->assertSame([], $config_changelist['create']);
 | 
			
		||||
    $this->assertSame(['core.extension'], $config_changelist['update']);
 | 
			
		||||
    $this->assertSame([], $config_changelist['delete']);
 | 
			
		||||
    $this->assertSame([], $config_changelist['rename']);
 | 
			
		||||
    foreach ($collections as $collection) {
 | 
			
		||||
      $config_changelist = $storage_comparer->getChangelist(NULL, $collection);
 | 
			
		||||
      $this->assertSame([], $config_changelist['create']);
 | 
			
		||||
      $this->assertSame([], $config_changelist['update']);
 | 
			
		||||
      $this->assertSame(['config_collection_install_test.test'], $config_changelist['delete'], $collection);
 | 
			
		||||
      $this->assertSame([], $config_changelist['rename']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $importer->import();
 | 
			
		||||
    $this->assertSame([], $importer->getErrors());
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage = $this->container->get('config.storage.checkpoint');
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active_storage */
 | 
			
		||||
    $active_storage = \Drupal::service('config.storage');
 | 
			
		||||
    $this->assertEmpty($active_storage->getAllCollectionNames());
 | 
			
		||||
    foreach ($collections as $collection) {
 | 
			
		||||
      $collection_storage = $active_storage->createCollection($collection);
 | 
			
		||||
      $this->assertFalse($collection_storage->read('config_collection_install_test.test'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $checkpoint_storage->setCheckpointToReadFrom($check2);
 | 
			
		||||
 | 
			
		||||
    $importer = $this->getConfigImporter($checkpoint_storage);
 | 
			
		||||
 | 
			
		||||
    $storage_comparer = $importer->getStorageComparer();
 | 
			
		||||
    $config_changelist = $storage_comparer->createChangelist()->getChangelist();
 | 
			
		||||
    $this->assertSame([], $config_changelist['create']);
 | 
			
		||||
    $this->assertSame(['core.extension'], $config_changelist['update']);
 | 
			
		||||
    $this->assertSame([], $config_changelist['delete']);
 | 
			
		||||
    $this->assertSame([], $config_changelist['rename']);
 | 
			
		||||
    foreach ($collections as $collection) {
 | 
			
		||||
      $config_changelist = $storage_comparer->getChangelist(NULL, $collection);
 | 
			
		||||
      $this->assertSame(['config_collection_install_test.test'], $config_changelist['create']);
 | 
			
		||||
      $this->assertSame([], $config_changelist['update']);
 | 
			
		||||
      $this->assertSame([], $config_changelist['delete'], $collection);
 | 
			
		||||
      $this->assertSame([], $config_changelist['rename']);
 | 
			
		||||
    }
 | 
			
		||||
    $importer->import();
 | 
			
		||||
    $this->assertSame([], $importer->getErrors());
 | 
			
		||||
 | 
			
		||||
    $this->assertTrue($this->container->get('module_handler')->moduleExists('config_collection_install_test'));
 | 
			
		||||
    /** @var \Drupal\Core\Config\StorageInterface $active_storage */
 | 
			
		||||
    $active_storage = \Drupal::service('config.storage');
 | 
			
		||||
    $this->assertEquals($collections, $active_storage->getAllCollectionNames());
 | 
			
		||||
    foreach ($collections as $collection) {
 | 
			
		||||
      $collection_storage = $active_storage->createCollection($collection);
 | 
			
		||||
      $data = $collection_storage->read('config_collection_install_test.test');
 | 
			
		||||
      $this->assertEquals($collection, $data['collection']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the configuration importer.
 | 
			
		||||
   */
 | 
			
		||||
  private function getConfigImporter(CheckpointStorageInterface $storage): ConfigImporter {
 | 
			
		||||
    $storage_comparer = new StorageComparer(
 | 
			
		||||
      $storage,
 | 
			
		||||
      $this->container->get('config.storage')
 | 
			
		||||
    );
 | 
			
		||||
    return new ConfigImporter(
 | 
			
		||||
      $storage_comparer,
 | 
			
		||||
      $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'),
 | 
			
		||||
      $this->container->get('extension.list.module'),
 | 
			
		||||
      $this->container->get('extension.list.theme')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,297 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Storage;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base class for testing storage operations.
 | 
			
		||||
 *
 | 
			
		||||
 * All configuration storages are expected to behave identically in
 | 
			
		||||
 * terms of reading, writing, listing, deleting, as well as error handling.
 | 
			
		||||
 *
 | 
			
		||||
 * Therefore, storage tests use an uncommon test case class structure;
 | 
			
		||||
 * the base class defines the test method(s) to execute, which are identical
 | 
			
		||||
 * for all storages. The storage specific test case classes supply the
 | 
			
		||||
 * necessary helper methods to interact with the raw/native storage
 | 
			
		||||
 * directly.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ConfigStorageTestBase extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $storage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $invalidStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests storage CRUD operations.
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Coverage: Trigger PDOExceptions / Database exceptions.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCRUD(): void {
 | 
			
		||||
    $name = 'config_test.storage';
 | 
			
		||||
 | 
			
		||||
    // Checking whether a non-existing name exists returns FALSE.
 | 
			
		||||
    $this->assertFalse($this->storage->exists($name));
 | 
			
		||||
 | 
			
		||||
    // Checking whether readMultiple() works with empty storage.
 | 
			
		||||
    $this->assertEmpty($this->storage->readMultiple([$name]));
 | 
			
		||||
 | 
			
		||||
    // readMultiple() accepts an empty array.
 | 
			
		||||
    $this->assertSame([], $this->storage->readMultiple([]), 'Empty query should return empty array');
 | 
			
		||||
 | 
			
		||||
    // Reading a non-existing name returns FALSE.
 | 
			
		||||
    $data = $this->storage->read($name);
 | 
			
		||||
    $this->assertFalse($data);
 | 
			
		||||
 | 
			
		||||
    // Writing data returns TRUE and the data has been written.
 | 
			
		||||
    $data = ['foo' => 'bar'];
 | 
			
		||||
    $result = $this->storage->write($name, $data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
 | 
			
		||||
    $raw_data = $this->read($name);
 | 
			
		||||
    $this->assertSame($data, $raw_data);
 | 
			
		||||
 | 
			
		||||
    // Checking whether an existing name exists returns TRUE.
 | 
			
		||||
    $this->assertTrue($this->storage->exists($name));
 | 
			
		||||
 | 
			
		||||
    // Writing the identical data again still returns TRUE.
 | 
			
		||||
    $result = $this->storage->write($name, $data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
 | 
			
		||||
    // Listing all names returns all.
 | 
			
		||||
    $this->storage->write('system.performance', []);
 | 
			
		||||
    $names = $this->storage->listAll();
 | 
			
		||||
    $this->assertContains('system.performance', $names);
 | 
			
		||||
    $this->assertContains($name, $names);
 | 
			
		||||
 | 
			
		||||
    // Listing all names with prefix returns names with that prefix only.
 | 
			
		||||
    $names = $this->storage->listAll('config_test.');
 | 
			
		||||
    $this->assertNotContains('system.performance', $names);
 | 
			
		||||
    $this->assertContains($name, $names);
 | 
			
		||||
 | 
			
		||||
    // Rename the configuration storage object.
 | 
			
		||||
    $new_name = 'config_test.storage_rename';
 | 
			
		||||
    $this->storage->rename($name, $new_name);
 | 
			
		||||
    $raw_data = $this->read($new_name);
 | 
			
		||||
    $this->assertSame($data, $raw_data);
 | 
			
		||||
    // Rename it back so further tests work.
 | 
			
		||||
    $this->storage->rename($new_name, $name);
 | 
			
		||||
 | 
			
		||||
    // Deleting an existing name returns TRUE.
 | 
			
		||||
    $result = $this->storage->delete($name);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
 | 
			
		||||
    // Deleting a non-existing name returns FALSE.
 | 
			
		||||
    $result = $this->storage->delete($name);
 | 
			
		||||
    $this->assertFalse($result);
 | 
			
		||||
 | 
			
		||||
    // Deleting all names with prefix deletes the appropriate data and returns
 | 
			
		||||
    // TRUE.
 | 
			
		||||
    $files = [
 | 
			
		||||
      'config_test.test.biff',
 | 
			
		||||
      'config_test.test.bang',
 | 
			
		||||
      'config_test.test.pow',
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($files as $name) {
 | 
			
		||||
      $this->storage->write($name, $data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test that deleting a prefix that returns no configuration returns FALSE
 | 
			
		||||
    // because nothing is deleted.
 | 
			
		||||
    $this->assertFalse($this->storage->deleteAll('some_thing_that_cannot_exist'));
 | 
			
		||||
 | 
			
		||||
    $result = $this->storage->deleteAll('config_test.');
 | 
			
		||||
    $names = $this->storage->listAll('config_test.');
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
    $this->assertSame([], $names);
 | 
			
		||||
 | 
			
		||||
    // Test renaming an object that does not exist returns FALSE.
 | 
			
		||||
    $this->assertFalse($this->storage->rename('config_test.storage_does_not_exist', 'config_test.storage_does_not_exist_rename'));
 | 
			
		||||
 | 
			
		||||
    // Test renaming to an object that already returns FALSE.
 | 
			
		||||
    $data = ['foo' => 'bar'];
 | 
			
		||||
    $this->assertTrue($this->storage->write($name, $data));
 | 
			
		||||
    $this->assertFalse($this->storage->rename('config_test.storage_does_not_exist', $name));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests an invalid storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidStorage(): void {
 | 
			
		||||
    $name = 'config_test.storage';
 | 
			
		||||
 | 
			
		||||
    // Write something to the valid storage to prove that the storages do not
 | 
			
		||||
    // pollute one another.
 | 
			
		||||
    $data = ['foo' => 'bar'];
 | 
			
		||||
    $result = $this->storage->write($name, $data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
 | 
			
		||||
    $raw_data = $this->read($name);
 | 
			
		||||
    $this->assertSame($data, $raw_data);
 | 
			
		||||
 | 
			
		||||
    // Reading from a non-existing storage bin returns FALSE.
 | 
			
		||||
    $result = $this->invalidStorage->read($name);
 | 
			
		||||
    $this->assertFalse($result);
 | 
			
		||||
 | 
			
		||||
    // Deleting from a non-existing storage bin throws an exception.
 | 
			
		||||
    try {
 | 
			
		||||
      $this->invalidStorage->delete($name);
 | 
			
		||||
      $this->fail('Exception not thrown upon deleting from a non-existing storage bin.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Exception) {
 | 
			
		||||
      // An exception occurred as expected; just continue.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Listing on a non-existing storage bin returns an empty array.
 | 
			
		||||
    $result = $this->invalidStorage->listAll();
 | 
			
		||||
    $this->assertSame([], $result);
 | 
			
		||||
 | 
			
		||||
    // Getting all collections on a non-existing storage bin return an empty
 | 
			
		||||
    // array.
 | 
			
		||||
    $this->assertSame([], $this->invalidStorage->getAllCollectionNames());
 | 
			
		||||
 | 
			
		||||
    // Writing to a non-existing storage bin creates the bin.
 | 
			
		||||
    $this->invalidStorage->write($name, ['foo' => 'bar']);
 | 
			
		||||
    $result = $this->invalidStorage->read($name);
 | 
			
		||||
    $this->assertSame(['foo' => 'bar'], $result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests storage writing and reading data preserving data type.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDataTypes(): void {
 | 
			
		||||
    $name = 'config_test.types';
 | 
			
		||||
    $data = [
 | 
			
		||||
      'array' => [],
 | 
			
		||||
      'boolean' => TRUE,
 | 
			
		||||
      'exp' => 1.2e+34,
 | 
			
		||||
      'float' => 3.14159,
 | 
			
		||||
      'hex' => 0xC,
 | 
			
		||||
      'int' => 99,
 | 
			
		||||
      'octal' => 0775,
 | 
			
		||||
      'string' => 'string',
 | 
			
		||||
      'string_int' => '1',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $result = $this->storage->write($name, $data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
 | 
			
		||||
    $read_data = $this->storage->read($name);
 | 
			
		||||
    $this->assertSame($data, $read_data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the storage supports collections.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCollection(): void {
 | 
			
		||||
    $name = 'config_test.storage';
 | 
			
		||||
    $data = ['foo' => 'bar'];
 | 
			
		||||
    $result = $this->storage->write($name, $data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
    $this->assertSame($data, $this->storage->read($name));
 | 
			
		||||
 | 
			
		||||
    // Create configuration in a new collection.
 | 
			
		||||
    $new_storage = $this->storage->createCollection('collection.sub.new');
 | 
			
		||||
    $this->assertFalse($new_storage->exists($name));
 | 
			
		||||
    $this->assertEquals([], $new_storage->listAll());
 | 
			
		||||
    $this->assertFalse($new_storage->delete($name));
 | 
			
		||||
    $this->assertFalse($new_storage->deleteAll('config_test.'));
 | 
			
		||||
    $this->assertFalse($new_storage->deleteAll());
 | 
			
		||||
    $this->assertFalse($new_storage->rename($name, 'config_test.another_name'));
 | 
			
		||||
    $new_storage->write($name, $data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
    $this->assertSame($data, $new_storage->read($name));
 | 
			
		||||
    $this->assertEquals([$name], $new_storage->listAll());
 | 
			
		||||
    $this->assertTrue($new_storage->exists($name));
 | 
			
		||||
    $new_data = ['foo' => 'baz'];
 | 
			
		||||
    $new_storage->write($name, $new_data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
    $this->assertSame($new_data, $new_storage->read($name));
 | 
			
		||||
 | 
			
		||||
    // Create configuration in another collection.
 | 
			
		||||
    $another_storage = $this->storage->createCollection('collection.sub.another');
 | 
			
		||||
    $this->assertFalse($another_storage->exists($name));
 | 
			
		||||
    $this->assertEquals([], $another_storage->listAll());
 | 
			
		||||
    $another_storage->write($name, $new_data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
    $this->assertSame($new_data, $another_storage->read($name));
 | 
			
		||||
    $this->assertEquals([$name], $another_storage->listAll());
 | 
			
		||||
    $this->assertTrue($another_storage->exists($name));
 | 
			
		||||
 | 
			
		||||
    // Create configuration in yet another collection.
 | 
			
		||||
    $alt_storage = $this->storage->createCollection('alternate');
 | 
			
		||||
    $alt_storage->write($name, $new_data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
    $this->assertSame($new_data, $alt_storage->read($name));
 | 
			
		||||
 | 
			
		||||
    // Switch back to the collection-less mode and check the data still exists
 | 
			
		||||
    // add has not been touched.
 | 
			
		||||
    $this->assertSame($data, $this->storage->read($name));
 | 
			
		||||
 | 
			
		||||
    // Check that the getAllCollectionNames() method works.
 | 
			
		||||
    $this->assertSame(['alternate', 'collection.sub.another', 'collection.sub.new'], $this->storage->getAllCollectionNames());
 | 
			
		||||
 | 
			
		||||
    // Check that the collections are removed when they are empty.
 | 
			
		||||
    $alt_storage->delete($name);
 | 
			
		||||
    $this->assertSame(['collection.sub.another', 'collection.sub.new'], $this->storage->getAllCollectionNames());
 | 
			
		||||
 | 
			
		||||
    // Create configuration in collection called 'collection'. This ensures that
 | 
			
		||||
    // FileStorage's collection storage works regardless of its use of
 | 
			
		||||
    // subdirectories.
 | 
			
		||||
    $parent_storage = $this->storage->createCollection('collection');
 | 
			
		||||
    $this->assertFalse($parent_storage->exists($name));
 | 
			
		||||
    $this->assertEquals([], $parent_storage->listAll());
 | 
			
		||||
    $parent_storage->write($name, $new_data);
 | 
			
		||||
    $this->assertTrue($result);
 | 
			
		||||
    $this->assertSame($new_data, $parent_storage->read($name));
 | 
			
		||||
    $this->assertEquals([$name], $parent_storage->listAll());
 | 
			
		||||
    $this->assertTrue($parent_storage->exists($name));
 | 
			
		||||
    $this->assertSame(['collection', 'collection.sub.another', 'collection.sub.new'], $this->storage->getAllCollectionNames());
 | 
			
		||||
    $parent_storage->deleteAll();
 | 
			
		||||
    $this->assertSame(['collection.sub.another', 'collection.sub.new'], $this->storage->getAllCollectionNames());
 | 
			
		||||
 | 
			
		||||
    // Test operations on a collection emptied through deletion.
 | 
			
		||||
    $this->assertFalse($parent_storage->exists($name));
 | 
			
		||||
    $this->assertEquals([], $parent_storage->listAll());
 | 
			
		||||
    $this->assertFalse($parent_storage->delete($name));
 | 
			
		||||
    $this->assertFalse($parent_storage->deleteAll('config_test.'));
 | 
			
		||||
    $this->assertFalse($parent_storage->deleteAll());
 | 
			
		||||
    $this->assertFalse($parent_storage->rename($name, 'config_test.another_name'));
 | 
			
		||||
 | 
			
		||||
    // Check that the having an empty collection-less storage does not break
 | 
			
		||||
    // anything. Before deleting check that the previous delete did not affect
 | 
			
		||||
    // data in another collection.
 | 
			
		||||
    $this->assertSame($data, $this->storage->read($name));
 | 
			
		||||
    $this->storage->delete($name);
 | 
			
		||||
    $this->assertSame(['collection.sub.another', 'collection.sub.new'], $this->storage->getAllCollectionNames());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reads configuration data from the storage.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function read($name);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Inserts configuration data in the storage.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function insert($name, $data);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Updates configuration data in the storage.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function update($name, $data);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Deletes configuration data from the storage.
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function delete($name);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,140 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Storage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\DatabaseStorage;
 | 
			
		||||
use Drupal\Core\Database\Database;
 | 
			
		||||
use Drupal\Core\Database\DatabaseExceptionWrapper;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests DatabaseStorage operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class DatabaseStorageTest extends ConfigStorageTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->storage = new DatabaseStorage($this->container->get('database'), 'config');
 | 
			
		||||
    $this->invalidStorage = new DatabaseStorage($this->container->get('database'), 'invalid');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function read($name) {
 | 
			
		||||
    $data = Database::getConnection()->select('config', 'c')->fields('c', ['data'])->condition('name', $name)->execute()->fetchField();
 | 
			
		||||
    return unserialize($data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function insert($name, $data): void {
 | 
			
		||||
    Database::getConnection()->insert('config')->fields(['name' => $name, 'data' => $data])->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function update($name, $data): void {
 | 
			
		||||
    Database::getConnection()->update('config')->fields(['data' => $data])->condition('name', $name)->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function delete($name): void {
 | 
			
		||||
    Database::getConnection()->delete('config')->condition('name', $name)->execute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that operations throw exceptions if the query fails.
 | 
			
		||||
   */
 | 
			
		||||
  public function testExceptionIsThrownIfQueryFails(): void {
 | 
			
		||||
    $connection = Database::getConnection();
 | 
			
		||||
    if ($connection->databaseType() === 'sqlite') {
 | 
			
		||||
      // See: https://www.drupal.org/project/drupal/issues/3349286
 | 
			
		||||
      $this->markTestSkipped('SQLite cannot allow detection of exceptions due to double quoting.');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Database::getConnection()->schema()->dropTable('config');
 | 
			
		||||
    // In order to simulate database issue create a table with an incorrect
 | 
			
		||||
    // specification.
 | 
			
		||||
    $table_specification = [
 | 
			
		||||
      'fields' => [
 | 
			
		||||
        'id'  => [
 | 
			
		||||
          'type' => 'int',
 | 
			
		||||
          'default' => NULL,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    Database::getConnection()->schema()->createTable('config', $table_specification);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->storage->exists('config.settings');
 | 
			
		||||
      $this->fail('Expected exception not thrown from exists()');
 | 
			
		||||
    }
 | 
			
		||||
    catch (DatabaseExceptionWrapper) {
 | 
			
		||||
      // Exception was expected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->storage->read('config.settings');
 | 
			
		||||
      $this->fail('Expected exception not thrown from read()');
 | 
			
		||||
    }
 | 
			
		||||
    catch (DatabaseExceptionWrapper) {
 | 
			
		||||
      // Exception was expected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->storage->readMultiple(['config.settings', 'config.settings2']);
 | 
			
		||||
      $this->fail('Expected exception not thrown from readMultiple()');
 | 
			
		||||
    }
 | 
			
		||||
    catch (DatabaseExceptionWrapper) {
 | 
			
		||||
      // Exception was expected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->storage->write('config.settings', ['data' => '']);
 | 
			
		||||
      $this->fail('Expected exception not thrown from deleteAll()');
 | 
			
		||||
    }
 | 
			
		||||
    catch (DatabaseExceptionWrapper) {
 | 
			
		||||
      // Exception was expected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->storage->listAll();
 | 
			
		||||
      $this->fail('Expected exception not thrown from listAll()');
 | 
			
		||||
    }
 | 
			
		||||
    catch (DatabaseExceptionWrapper) {
 | 
			
		||||
      // Exception was expected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->storage->deleteAll();
 | 
			
		||||
      $this->fail('Expected exception not thrown from deleteAll()');
 | 
			
		||||
    }
 | 
			
		||||
    catch (DatabaseExceptionWrapper) {
 | 
			
		||||
      // Exception was expected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->storage->getAllCollectionNames();
 | 
			
		||||
      $this->fail('Expected exception not thrown from getAllCollectionNames()');
 | 
			
		||||
    }
 | 
			
		||||
    catch (DatabaseExceptionWrapper) {
 | 
			
		||||
      // Exception was expected
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $this->assertTrue(TRUE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,101 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Storage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\FileStorage;
 | 
			
		||||
use Drupal\Core\Config\UnsupportedDataTypeConfigException;
 | 
			
		||||
use Drupal\Core\Serialization\Yaml;
 | 
			
		||||
use Drupal\Core\StreamWrapper\PublicStream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests FileStorage operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class FileStorageTest extends ConfigStorageTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A directory to store configuration in.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  protected $directory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    // Create a directory.
 | 
			
		||||
    $this->directory = PublicStream::basePath() . '/config';
 | 
			
		||||
    $this->storage = new FileStorage($this->directory);
 | 
			
		||||
    $this->invalidStorage = new FileStorage($this->directory . '/nonexisting');
 | 
			
		||||
 | 
			
		||||
    // FileStorage::listAll() requires other configuration data to exist.
 | 
			
		||||
    $this->storage->write('system.performance', $this->config('system.performance')->get());
 | 
			
		||||
    $this->storage->write('core.extension', ['module' => []]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function read($name) {
 | 
			
		||||
    $data = file_get_contents($this->storage->getFilePath($name));
 | 
			
		||||
    return Yaml::decode($data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function insert($name, $data): void {
 | 
			
		||||
    file_put_contents($this->storage->getFilePath($name), $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function update($name, $data): void {
 | 
			
		||||
    file_put_contents($this->storage->getFilePath($name), $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function delete($name): void {
 | 
			
		||||
    unlink($this->storage->getFilePath($name));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the FileStorage::listAll method with a relative and absolute path.
 | 
			
		||||
   */
 | 
			
		||||
  public function testListAll(): void {
 | 
			
		||||
    $expected_files = [
 | 
			
		||||
      'core.extension',
 | 
			
		||||
      'system.performance',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $config_files = $this->storage->listAll();
 | 
			
		||||
    $this->assertSame($expected_files, $config_files, 'Relative path, two config files found.');
 | 
			
		||||
 | 
			
		||||
    // @todo https://www.drupal.org/node/2666954 FileStorage::listAll() is
 | 
			
		||||
    //   case-sensitive. However, \Drupal\Core\Config\DatabaseStorage::listAll()
 | 
			
		||||
    //   is case-insensitive.
 | 
			
		||||
    $this->assertSame(['system.performance'], $this->storage->listAll('system'), 'The FileStorage::listAll() with prefix works.');
 | 
			
		||||
    $this->assertSame([], $this->storage->listAll('System'), 'The FileStorage::listAll() is case sensitive.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests UnsupportedDataTypeConfigException.
 | 
			
		||||
   */
 | 
			
		||||
  public function testUnsupportedDataTypeConfigException(): void {
 | 
			
		||||
    $name = 'core.extension';
 | 
			
		||||
    $path = $this->storage->getFilePath($name);
 | 
			
		||||
    $this->expectException(UnsupportedDataTypeConfigException::class);
 | 
			
		||||
    $this->expectExceptionMessageMatches("@Invalid data type in config $name, found in file $path: @");
 | 
			
		||||
    file_put_contents($path, PHP_EOL . 'foo : @bar', FILE_APPEND);
 | 
			
		||||
    $this->storage->read($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,70 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Storage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\StorageManagerInterface;
 | 
			
		||||
use Drupal\Core\Config\ManagedStorage;
 | 
			
		||||
use Drupal\Core\Config\MemoryStorage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests ManagedStorage operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ManagedStorageTest extends ConfigStorageTestBase implements StorageManagerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorage() {
 | 
			
		||||
    // We return a new storage every time to make sure the managed storage
 | 
			
		||||
    // only calls this once and retains the configuration by itself.
 | 
			
		||||
    return new MemoryStorage();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->storage = new ManagedStorage($this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function read($name) {
 | 
			
		||||
    return $this->storage->read($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function insert($name, $data): void {
 | 
			
		||||
    $this->storage->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function update($name, $data): void {
 | 
			
		||||
    $this->storage->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function delete($name): void {
 | 
			
		||||
    $this->storage->delete($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidStorage(): void {
 | 
			
		||||
    $this->markTestSkipped('ManagedStorage cannot be invalid.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,59 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Storage;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\MemoryStorage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests MemoryStorage operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class MemoryStorageTest extends ConfigStorageTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->storage = new MemoryStorage();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function read($name) {
 | 
			
		||||
    return $this->storage->read($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function insert($name, $data): void {
 | 
			
		||||
    $this->storage->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function update($name, $data): void {
 | 
			
		||||
    $this->storage->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function delete($name): void {
 | 
			
		||||
    $this->storage->delete($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidStorage(): void {
 | 
			
		||||
    $this->markTestSkipped('MemoryStorage cannot be invalid.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,92 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Config\Storage;
 | 
			
		||||
 | 
			
		||||
use Drupal\config\StorageReplaceDataWrapper;
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests StorageReplaceDataWrapper operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class StorageReplaceDataWrapperTest extends ConfigStorageTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->storage = new StorageReplaceDataWrapper($this->container->get('config.storage'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function read($name) {
 | 
			
		||||
    return $this->storage->read($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function insert($name, $data): void {
 | 
			
		||||
    $this->storage->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function update($name, $data): void {
 | 
			
		||||
    $this->storage->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function delete($name): void {
 | 
			
		||||
    $this->storage->delete($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidStorage(): void {
 | 
			
		||||
    $this->markTestSkipped('No-op as this test does not make sense');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests if new collections created correctly.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $collection
 | 
			
		||||
   *   The collection name.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerCollections
 | 
			
		||||
   */
 | 
			
		||||
  public function testCreateCollection($collection): void {
 | 
			
		||||
    $initial_collection_name = $this->storage->getCollectionName();
 | 
			
		||||
 | 
			
		||||
    // Create new storage with given collection and check it is set correctly.
 | 
			
		||||
    $new_storage = $this->storage->createCollection($collection);
 | 
			
		||||
    $this->assertSame($collection, $new_storage->getCollectionName());
 | 
			
		||||
 | 
			
		||||
    // Check collection not changed in the current storage instance.
 | 
			
		||||
    $this->assertSame($initial_collection_name, $this->storage->getCollectionName());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Data provider for testing different collections.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   Returns an array of collection names.
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerCollections() {
 | 
			
		||||
    return [
 | 
			
		||||
      [StorageInterface::DEFAULT_COLLECTION],
 | 
			
		||||
      ['foo.bar'],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,70 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\dblog\Logger\DbLog;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\system_test\Controller\BrokenSystemTestController;
 | 
			
		||||
use Drupal\system_test\Controller\OptionalServiceSystemTestController;
 | 
			
		||||
use Drupal\system_test\Controller\SystemTestController;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests \Drupal\Core\Controller\ControllerBase.
 | 
			
		||||
 *
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Controller\ControllerBase
 | 
			
		||||
 * @group Controller
 | 
			
		||||
 */
 | 
			
		||||
class ControllerBaseTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system_test', 'system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::create
 | 
			
		||||
   */
 | 
			
		||||
  public function testCreate(): void {
 | 
			
		||||
    /** @var \Drupal\system_test\Controller\SystemTestController $controller */
 | 
			
		||||
    $controller = $this->container->get('class_resolver')->getInstanceFromDefinition(SystemTestController::class);
 | 
			
		||||
 | 
			
		||||
    $property = new \ReflectionProperty(SystemTestController::class, 'lock');
 | 
			
		||||
    $this->assertSame($this->container->get('lock'), $property->getValue($controller));
 | 
			
		||||
 | 
			
		||||
    $property = new \ReflectionProperty(SystemTestController::class, 'persistentLock');
 | 
			
		||||
    $this->assertSame($this->container->get('lock.persistent'), $property->getValue($controller));
 | 
			
		||||
 | 
			
		||||
    $property = new \ReflectionProperty(SystemTestController::class, 'currentUser');
 | 
			
		||||
    $this->assertSame($this->container->get('current_user'), $property->getValue($controller));
 | 
			
		||||
 | 
			
		||||
    // Test nullables types.
 | 
			
		||||
    $this->assertSame($this->container->get('page_cache_kill_switch'), $controller->killSwitch);
 | 
			
		||||
    $this->assertSame($this->container->get('page_cache_kill_switch'), $controller->killSwitch2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::create
 | 
			
		||||
   */
 | 
			
		||||
  public function testCreateException(): void {
 | 
			
		||||
    $this->expectException(AutowiringFailedException::class);
 | 
			
		||||
    $this->expectExceptionMessage('Cannot autowire service "Drupal\Core\Lock\LockBackendInterface": argument "$lock" of method "Drupal\system_test\Controller\BrokenSystemTestController::_construct()", you should configure its value explicitly.');
 | 
			
		||||
    $this->container->get('class_resolver')->getInstanceFromDefinition(BrokenSystemTestController::class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::create
 | 
			
		||||
   */
 | 
			
		||||
  public function testCreateOptional(): void {
 | 
			
		||||
    $service = $this->container->get('class_resolver')->getInstanceFromDefinition(OptionalServiceSystemTestController::class);
 | 
			
		||||
    $this->assertInstanceOf(OptionalServiceSystemTestController::class, $service);
 | 
			
		||||
    $this->assertNull($service->dbLog);
 | 
			
		||||
    $this->container->get('module_installer')->install(['dblog']);
 | 
			
		||||
    $service = $this->container->get('class_resolver')->getInstanceFromDefinition(OptionalServiceSystemTestController::class);
 | 
			
		||||
    $this->assertInstanceOf(OptionalServiceSystemTestController::class, $service);
 | 
			
		||||
    $this->assertInstanceOf(DbLog::class, $service->dbLog);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										153
									
								
								web/core/tests/Drupal/KernelTests/Core/Database/AlterTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								web/core/tests/Drupal/KernelTests/Core/Database/AlterTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Database;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the hook_query_alter capabilities of the Select builder.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Database
 | 
			
		||||
 * @see database_test_query_alter()
 | 
			
		||||
 */
 | 
			
		||||
class AlterTest extends DatabaseTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that we can do basic alters.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSimpleAlter(): void {
 | 
			
		||||
    $query = $this->connection->select('test');
 | 
			
		||||
    $query->addField('test', 'name');
 | 
			
		||||
    $query->addField('test', 'age', 'age');
 | 
			
		||||
    $query->addTag('database_test_alter_add_range');
 | 
			
		||||
 | 
			
		||||
    $result = $query->execute()->fetchAll();
 | 
			
		||||
 | 
			
		||||
    $this->assertCount(2, $result, 'Returned the correct number of rows.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that we can alter the joins on a query.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAlterWithJoin(): void {
 | 
			
		||||
    $query = $this->connection->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->assertCount(2, $records, 'Returned the correct number of rows.');
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals('George', $records[0]->name, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertEquals(4, $records[0]->{$tid_field}, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertEquals('sing', $records[0]->{$task_field}, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertEquals('George', $records[1]->name, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertEquals(5, $records[1]->{$tid_field}, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertEquals('sleep', $records[1]->{$task_field}, 'Correct data retrieved.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that we can alter a query's conditionals.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAlterChangeConditional(): void {
 | 
			
		||||
    $query = $this->connection->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->assertCount(1, $records, 'Returned the correct number of rows.');
 | 
			
		||||
    $this->assertEquals('John', $records[0]->{$name_field}, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertEquals(2, $records[0]->{$tid_field}, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertEquals(1, $records[0]->{$pid_field}, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertEquals('sleep', $records[0]->{$task_field}, 'Correct data retrieved.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that we can alter the fields of a query.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAlterChangeFields(): void {
 | 
			
		||||
    $query = $this->connection->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->assertEquals('George', $record->{$name_field}, 'Correct data retrieved.');
 | 
			
		||||
    $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that we can alter expressions in the query.
 | 
			
		||||
   */
 | 
			
		||||
  public function testAlterExpression(): void {
 | 
			
		||||
    $query = $this->connection->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->assertEquals('George', $record->{$name_field}, 'Fetched name is correct.');
 | 
			
		||||
    $this->assertEquals(27 * 3, $record->{$age_field}, 'Fetched age expression is correct.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that we can remove a range() value from a query.
 | 
			
		||||
   *
 | 
			
		||||
   * This also tests hook_query_TAG_alter().
 | 
			
		||||
   */
 | 
			
		||||
  public function testAlterRemoveRange(): void {
 | 
			
		||||
    $query = $this->connection->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->assertEquals(4, $num_records, 'Returned the correct number of rows.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that we can do basic alters on subqueries.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSimpleAlterSubquery(): void {
 | 
			
		||||
    // Create a sub-query with an alter tag.
 | 
			
		||||
    $subquery = $this->connection->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 = $this->connection->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->assertEquals('George', $record->{$name_field}, 'Fetched name is correct.');
 | 
			
		||||
    $this->assertEquals(27 * 3, $record->{$age_field}, 'Fetched age expression is correct.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user