RulesIntegrationTestBase.php
Namespace
Drupal\Tests\rules\Unit\IntegrationFile
-
tests/
src/ Unit/ Integration/ RulesIntegrationTestBase.php
View source
<?php
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\RecursiveExtensionFilterIterator;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Plugin\Context\LazyContextRepository;
use Drupal\Core\TypedData\TypedDataManager;
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 Drupal\Tests\UnitTestCase;
use Drupal\Tests\rules\Unit\TestMessenger;
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->moduleHandler
->reveal(), $uuid_service);
$this->classResolver = $this->prophesize(ClassResolverInterface::class);
$this->typedDataManager = new TypedDataManager($this->namespaces, $this->cacheBackend, $this->moduleHandler
->reveal(), $this->classResolver
->reveal());
if (version_compare(\Drupal::VERSION, '10.2') >= 0) {
// @phpcs:ignore Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing
$this->fieldTypeCategoryManager = new \Drupal\Core\Field\FieldTypeCategoryManager($this->root, $this->moduleHandler
->reveal(), $this->cacheBackend);
}
$this->rulesDataProcessorManager = new DataProcessorManager($this->namespaces, $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);
if (version_compare(\Drupal::VERSION, '10.2') >= 0) {
$container->set('plugin.manager.field.field_type_category', $this->fieldTypeCategoryManager);
}
$container->set('typed_data_manager', $this->typedDataManager);
$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($name, array $namespaces = []) {
$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 (DrupalCI) does not use same directory structure as most live
* Drupal sites, so 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($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.
// Important: Without a RecursiveFilterIterator, RecursiveDirectoryIterator
// would recurse into the entire filesystem directory tree without any kind
// of limitations.
$filter = new RecursiveExtensionFilterIterator($directory_iterator);
// Ensure we find testing modules too!
$filter->acceptTests(TRUE);
// The actual recursive filesystem scan is only invoked by instantiating the
// RecursiveIteratorIterator.
$iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::LEAVES_ONLY, \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($data_type, $value) {
$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($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;
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
RulesIntegrationTestBase | Base class for Rules integration tests. |