rules-8.x-3.x-dev/tests/src/Unit/Integration/RulesIntegrationTestBase.php

tests/src/Unit/Integration/RulesIntegrationTestBase.php
<?php

declare(strict_types=1);

namespace Drupal\Tests\rules\Unit\Integration;

use Drupal\Component\DependencyInjection\ReverseContainer;
use Drupal\Component\Uuid\Php;
use Drupal\Core\Cache\NullBackend;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\Discovery\RecursiveExtensionFilterCallback;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldTypeCategoryManager;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Plugin\Context\LazyContextRepository;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Drupal\Tests\UnitTestCase;
use Drupal\Tests\rules\Unit\TestMessenger;
use Drupal\rules\Core\ConditionManager;
use Drupal\rules\Context\DataProcessorManager;
use Drupal\rules\Core\RulesActionManager;
use Drupal\rules\Engine\ExpressionManager;
use Drupal\typed_data\DataFetcher;
use Drupal\typed_data\DataFilterManager;
use Drupal\typed_data\PlaceholderResolver;
use Prophecy\Argument;

// cspell:ignore hardwiring

/**
 * Base class for Rules integration tests.
 *
 * Rules integration tests leverage the services (plugin managers) of the Rules
 * module to test the integration of an action or condition. Dependencies on
 * other 3rd party modules or APIs can and should be mocked; e.g. the action
 * to delete an entity would mock the call to the entity API.
 */
abstract class RulesIntegrationTestBase extends UnitTestCase {

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $entityTypeManager;

  /**
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $entityFieldManager;

  /**
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $entityTypeBundleInfo;

  /**
   * @var \Drupal\Core\TypedData\TypedDataManagerInterface
   */
  protected $typedDataManager;

  /**
   * The field type category info plugin manager.
   *
   * @var \Drupal\Core\Field\FieldTypeCategoryManagerInterface
   */
  protected $fieldTypeCategoryManager;

  /**
   * @var \Drupal\rules\Core\RulesActionManagerInterface
   */
  protected $actionManager;

  /**
   * @var \Drupal\rules\Core\ConditionManager
   */
  protected $conditionManager;

  /**
   * @var \Drupal\rules\Engine\ExpressionManager
   */
  protected $rulesExpressionManager;

  /**
   * @var \Drupal\rules\Context\DataProcessorManager
   */
  protected $rulesDataProcessorManager;

  /**
   * A mocked Rules logger.channel.rules_debug service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $logger;

  /**
   * All setup'ed namespaces.
   *
   * @var \ArrayObject
   */
  protected $namespaces;

  /**
   * @var \Drupal\Core\Cache\NullBackend
   */
  protected $cacheBackend;

  /**
   * @var \Drupal\Core\Extension\ModuleHandlerInterface||\Prophecy\Prophecy\ProphecyInterface
   */
  protected $moduleHandler;

  /**
   * Array object keyed with module names and TRUE as value.
   *
   * @var \ArrayObject
   */
  protected $enabledModules;

  /**
   * The Drupal service container.
   *
   * @var \Drupal\Core\DependencyInjection\Container
   */
  protected $container;

  /**
   * The class resolver mock for the typed data manager.
   *
   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface|\Prophecy\Prophecy\ProphecyInterface
   */
  protected $classResolver;

  /**
   * The data fetcher service.
   *
   * @var \Drupal\typed_data\DataFetcher
   */
  protected $dataFetcher;

  /**
   * The placeholder resolver service.
   *
   * @var \Drupal\typed_data\PlaceholderResolver
   */
  protected $placeholderResolver;

  /**
   * The data filter manager.
   *
   * @var \Drupal\typed_data\DataFilterManager
   */
  protected $dataFilterManager;

  /**
   * The messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $container = new ContainerBuilder();
    // Register plugin managers used by Rules, but mock some unwanted
    // dependencies requiring more stuff to loaded.
    $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);

    // Set all the modules as being existent.
    $this->enabledModules = new \ArrayObject();
    $this->enabledModules['rules'] = TRUE;
    $this->enabledModules['rules_test'] = TRUE;
    $enabled_modules = $this->enabledModules;
    $this->moduleHandler->moduleExists(Argument::type('string'))
      ->will(function ($arguments) use ($enabled_modules) {
        if (isset($enabled_modules[$arguments[0]])) {
          return [$arguments[0], $enabled_modules[$arguments[0]]];
        }
        // Handle case where a plugin provider module is not enabled.
        return [$arguments[0], FALSE];
      });

    // We don't care about alter() calls on the module handler.
    $this->moduleHandler->alter(Argument::any(), Argument::any(), Argument::any(), Argument::any())
      ->willReturn(NULL);

    $this->cacheBackend = new NullBackend('rules');
    $rules_directory = __DIR__ . '/../../../..';
    $this->namespaces = new \ArrayObject([
      'Drupal\\rules' => $rules_directory . '/src',
      'Drupal\\rules_test' => $rules_directory . '/tests/modules/rules_test/src',
      'Drupal\\Core\\TypedData' => $this->root . '/core/lib/Drupal/Core/TypedData',
      'Drupal\\Core\\Validation' => $this->root . '/core/lib/Drupal/Core/Validation',
    ]);

    $this->actionManager = new RulesActionManager($this->namespaces, $this->cacheBackend, $this->moduleHandler->reveal());
    $this->conditionManager = new ConditionManager($this->namespaces, $this->cacheBackend, $this->moduleHandler->reveal());

    $uuid_service = new Php();
    $this->rulesExpressionManager = new ExpressionManager($this->namespaces, $this->cacheBackend, $this->moduleHandler->reveal(), $uuid_service);

    $this->classResolver = $this->prophesize(ClassResolverInterface::class);

    $this->typedDataManager = new TypedDataManager(
      $this->namespaces,
      $this->cacheBackend,
      $this->moduleHandler->reveal(),
      $this->classResolver->reveal()
    );

    $this->fieldTypeCategoryManager = new FieldTypeCategoryManager(
      $this->root,
      $this->moduleHandler->reveal(),
      $this->cacheBackend,
    );

    $this->rulesDataProcessorManager = new DataProcessorManager($this->namespaces, $this->cacheBackend, $this->moduleHandler->reveal());

    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
    $this->entityTypeManager->getDefinitions()->willReturn([]);

    // Setup a rules_component storage mock which returns nothing by default.
    $storage = $this->prophesize(ConfigEntityStorageInterface::class);
    $storage->loadMultiple(NULL)->willReturn([]);
    $this->entityTypeManager->getStorage('rules_component')->willReturn($storage->reveal());

    $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class);
    $this->entityFieldManager->getBaseFieldDefinitions()->willReturn([]);

    $this->entityTypeBundleInfo = $this->prophesize(EntityTypeBundleInfoInterface::class);
    $this->entityTypeBundleInfo->getBundleInfo()->willReturn([]);

    $this->dataFetcher = new DataFetcher();
    $this->messenger = new TestMessenger();

    $this->dataFilterManager = new DataFilterManager($this->namespaces, $this->cacheBackend, $this->moduleHandler->reveal());
    $this->placeholderResolver = new PlaceholderResolver($this->dataFetcher, $this->dataFilterManager);

    // Mock the Rules debug logger service and make it return our mocked logger.
    $this->logger = $this->prophesize(LoggerChannelInterface::class);

    $container->set('entity_type.manager', $this->entityTypeManager->reveal());
    $container->set('entity_field.manager', $this->entityFieldManager->reveal());
    $container->set('entity_type.bundle.info', $this->entityTypeBundleInfo->reveal());
    $container->set('context.repository', new LazyContextRepository($container, []));
    $container->set('logger.channel.rules_debug', $this->logger->reveal());
    $container->set('plugin.manager.rules_action', $this->actionManager);
    $container->set('plugin.manager.condition', $this->conditionManager);
    $container->set('plugin.manager.rules_expression', $this->rulesExpressionManager);
    $container->set('plugin.manager.rules_data_processor', $this->rulesDataProcessorManager);
    $container->set('messenger', $this->messenger);
    $container->set('typed_data_manager', $this->typedDataManager);
    $container->set('plugin.manager.field.field_type_category', $this->fieldTypeCategoryManager);
    $container->set('string_translation', $this->getStringTranslationStub());
    $container->set('uuid', $uuid_service);
    $container->set('typed_data.data_fetcher', $this->dataFetcher);
    $container->set('typed_data.placeholder_resolver', $this->placeholderResolver);

    // The new ReverseContainer service needs to be present to prevent massive
    // unit test failures.
    // @see https://www.drupal.org/project/rules/issues/3346846
    $container->set('Drupal\Component\DependencyInjection\ReverseContainer', new ReverseContainer($container));

    \Drupal::setContainer($container);
    $this->container = $container;
  }

  /**
   * Fakes the enabling of a module and adds its namespace for plugin loading.
   *
   * This method allows plugins provided by a module to be discoverable.
   *
   * @param string $name
   *   The name of the module that's going to be enabled.
   * @param array $namespaces
   *   Map of the association between module's namespaces and filesystem paths.
   */
  protected function enableModule(string $name, array $namespaces = []): void {
    $this->enabledModules[$name] = TRUE;

    if (empty($namespaces)) {
      $namespaces = ['Drupal\\' . $name => $this->root . '/' . $this->constructModulePath($name) . '/src'];
    }
    foreach ($namespaces as $namespace => $path) {
      $this->namespaces[$namespace] = $path;
    }
  }

  /**
   * Determines the path to a module's class files.
   *
   * Core modules and contributed modules are located in different places and
   * the testbot (GitLabCI) does not use same directory structure as most live
   * Drupal sites. Thus we must discover the path instead of hardwiring it.
   *
   * This method discovers modules the same way as Drupal core, so it should
   * work for core and contributed modules in all environments.
   *
   * @see \Drupal\Core\Extension\ExtensionDiscovery
   */
  protected function constructModulePath(string $module) {
    // Use Unix paths regardless of platform, skip dot directories, follow
    // symlinks (to allow extensions to be linked from elsewhere), and return
    // the RecursiveDirectoryIterator instance to have access to getSubPath(),
    // since SplFileInfo does not support relative paths.
    $flags = \FilesystemIterator::UNIX_PATHS;
    $flags |= \FilesystemIterator::SKIP_DOTS;
    $flags |= \FilesystemIterator::FOLLOW_SYMLINKS;
    $flags |= \FilesystemIterator::CURRENT_AS_SELF;
    $directory_iterator = new \RecursiveDirectoryIterator($this->root, $flags);

    // Filter the recursive scan to discover extensions only.
    // Ensure we find testing modules too!
    $callback = new RecursiveExtensionFilterCallback([], TRUE);
    $filter = new \RecursiveCallbackFilterIterator($directory_iterator, [$callback, 'accept']);

    // The actual recursive filesystem scan is only invoked by instantiating the
    // RecursiveIteratorIterator.
    $iterator = new \RecursiveIteratorIterator($filter,
      \RecursiveIteratorIterator::LEAVES_ONLY,
      // Suppress filesystem errors in case a directory cannot be accessed.
      \RecursiveIteratorIterator::CATCH_GET_CHILD

    );

    $info_files = new \RegexIterator($iterator, "/^$module.info.yml$/");
    foreach ($info_files as $file) {
      // There should only be one match.
      return $file->getSubPath();
    }
  }

  /**
   * Returns a typed data object.
   *
   * This helper for quick creation of typed data objects.
   *
   * @param string $data_type
   *   The data type to create an object for.
   * @param mixed $value
   *   The value to set.
   *
   * @return \Drupal\Core\TypedData\TypedDataInterface
   *   The created object.
   */
  protected function getTypedData(string $data_type, mixed $value): TypedDataInterface {
    $definition = $this->typedDataManager->createDataDefinition($data_type);
    $data = $this->typedDataManager->create($definition);
    $data->setValue($value);
    return $data;
  }

  /**
   * Helper method to mock irrelevant cache methods on entities.
   *
   * @param string $interface
   *   The interface that should be mocked, example: EntityInterface::class.
   *
   * @return \Drupal\Core\Entity\EntityInterface|\Prophecy\Prophecy\ProphecyInterface
   *   The mocked entity.
   */
  protected function prophesizeEntity(string $interface) {
    $entity = $this->prophesize($interface);
    // Cache methods are irrelevant for the tests but might be called.
    $entity->getCacheContexts()->willReturn([]);
    $entity->getCacheTags()->willReturn([]);
    $entity->getCacheMaxAge()->willReturn(0);
    return $entity;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc