From ca81aef41e53bd41ab0f68917c8f188b87935c82 Mon Sep 17 00:00:00 2001 From: phenaproxima <phenaproxima@205645.no-reply.drupal.org> Date: Mon, 11 Apr 2022 17:56:25 +0000 Subject: [PATCH] Issue #3274323 by phenaproxima: Split ExcludedPathsSubscriber into multiple classes --- package_manager/package_manager.services.yml | 38 ++- .../src/Event/ExcludedPathsTrait.php | 2 +- .../ExcludedPathsSubscriber.php | 250 ------------------ .../src/PathExcluder/GitExcluder.php | 64 +++++ .../src/PathExcluder/PathExclusionsTrait.php | 64 +++++ .../SiteConfigurationExcluder.php | 88 ++++++ .../src/PathExcluder/SiteFilesExcluder.php | 87 ++++++ .../PathExcluder/SqliteDatabaseExcluder.php | 68 +++++ .../src/PathExcluder/TestSiteExcluder.php | 50 ++++ .../PathExcluder/VendorHardeningExcluder.php | 55 ++++ .../tests/src/Kernel/ExcludedPathsTest.php | 243 ----------------- .../Kernel/PathExcluder/GitExcluderTest.php | 80 ++++++ .../SiteConfigurationExcluderTest.php | 104 ++++++++ .../PathExcluder/SiteFilesExcluderTest.php | 67 +++++ .../SqliteDatabaseExcluderTest.php | 181 +++++++++++++ .../PathExcluder/TestSiteExcluderTest.php | 58 ++++ .../VendorHardeningExcluderTest.php | 59 +++++ .../StagedProjectsValidatorTest.php | 4 +- 18 files changed, 1062 insertions(+), 500 deletions(-) delete mode 100644 package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php create mode 100644 package_manager/src/PathExcluder/GitExcluder.php create mode 100644 package_manager/src/PathExcluder/PathExclusionsTrait.php create mode 100644 package_manager/src/PathExcluder/SiteConfigurationExcluder.php create mode 100644 package_manager/src/PathExcluder/SiteFilesExcluder.php create mode 100644 package_manager/src/PathExcluder/SqliteDatabaseExcluder.php create mode 100644 package_manager/src/PathExcluder/TestSiteExcluder.php create mode 100644 package_manager/src/PathExcluder/VendorHardeningExcluder.php delete mode 100644 package_manager/tests/src/Kernel/ExcludedPathsTest.php create mode 100644 package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php create mode 100644 package_manager/tests/src/Kernel/PathExcluder/SiteConfigurationExcluderTest.php create mode 100644 package_manager/tests/src/Kernel/PathExcluder/SiteFilesExcluderTest.php create mode 100644 package_manager/tests/src/Kernel/PathExcluder/SqliteDatabaseExcluderTest.php create mode 100644 package_manager/tests/src/Kernel/PathExcluder/TestSiteExcluderTest.php create mode 100644 package_manager/tests/src/Kernel/PathExcluder/VendorHardeningExcluderTest.php diff --git a/package_manager/package_manager.services.yml b/package_manager/package_manager.services.yml index 3fa0bfa707..3406f6b5d2 100644 --- a/package_manager/package_manager.services.yml +++ b/package_manager/package_manager.services.yml @@ -128,13 +128,43 @@ services: - '@string_translation' tags: - { name: event_subscriber } - package_manager.excluded_paths_subscriber: - class: Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber + package_manager.test_site_excluder: + class: Drupal\package_manager\PathExcluder\TestSiteExcluder arguments: - - '%site.path%' - - '@package_manager.symfony_file_system' + - '@package_manager.path_locator' + tags: + - { name: event_subscriber } + package_manager.vendor_hardening_excluder: + class: Drupal\package_manager\PathExcluder\VendorHardeningExcluder + arguments: + - '@package_manager.path_locator' + tags: + - { name: event_subscriber } + package_manager.site_files_excluder: + class: Drupal\package_manager\PathExcluder\SiteFilesExcluder + arguments: + - '@package_manager.path_locator' - '@stream_wrapper_manager' + - '@package_manager.symfony_file_system' + tags: + - { name: event_subscriber } + package_manager.sqlite_excluder: + class: Drupal\package_manager\PathExcluder\SqliteDatabaseExcluder + arguments: + - '@package_manager.path_locator' - '@database' + tags: + - { name: event_subscriber } + package_manager.git_excluder: + class: Drupal\package_manager\PathExcluder\GitExcluder + arguments: + - '@package_manager.path_locator' + tags: + - { name: event_subscriber } + package_manager.site_configuration_excluder: + class: Drupal\package_manager\PathExcluder\SiteConfigurationExcluder + arguments: + - '%site.path%' - '@package_manager.path_locator' tags: - { name: event_subscriber } diff --git a/package_manager/src/Event/ExcludedPathsTrait.php b/package_manager/src/Event/ExcludedPathsTrait.php index fc7e690440..6e2dfddc31 100644 --- a/package_manager/src/Event/ExcludedPathsTrait.php +++ b/package_manager/src/Event/ExcludedPathsTrait.php @@ -31,7 +31,7 @@ trait ExcludedPathsTrait { * @param string $path * The path to exclude, relative to the project root. * - * @see \Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber + * @see \Drupal\package_manager\PathExcluder\SiteConfigurationExcluder */ public function excludePath(string $path): void { $this->excludedPaths[] = $path; diff --git a/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php b/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php deleted file mode 100644 index 5eb7e08811..0000000000 --- a/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php +++ /dev/null @@ -1,250 +0,0 @@ -<?php - -namespace Drupal\package_manager\EventSubscriber; - -use Drupal\Core\Database\Connection; -use Drupal\Core\StreamWrapper\LocalStream; -use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; -use Drupal\package_manager\Event\PreApplyEvent; -use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\Event\StageEvent; -use Drupal\package_manager\PathLocator; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\Finder\Finder; - -/** - * Defines an event subscriber to exclude certain paths from staging areas. - */ -class ExcludedPathsSubscriber implements EventSubscriberInterface { - - /** - * The current site path, relative to the Drupal root. - * - * @var string - */ - protected $sitePath; - - /** - * The Symfony file system service. - * - * @var \Symfony\Component\Filesystem\Filesystem - */ - protected $fileSystem; - - /** - * The stream wrapper manager service. - * - * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface - */ - protected $streamWrapperManager; - - /** - * The database connection. - * - * @var \Drupal\Core\Database\Connection - */ - protected $database; - - /** - * The path locator service. - * - * @var \Drupal\package_manager\PathLocator - */ - protected $pathLocator; - - /** - * Constructs an ExcludedPathsSubscriber. - * - * @param string $site_path - * The current site path, relative to the Drupal root. - * @param \Symfony\Component\Filesystem\Filesystem $file_system - * The Symfony file system service. - * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager - * The stream wrapper manager service. - * @param \Drupal\Core\Database\Connection $database - * The database connection. - * @param \Drupal\package_manager\PathLocator $path_locator - * The path locator service. - */ - public function __construct(string $site_path, Filesystem $file_system, StreamWrapperManagerInterface $stream_wrapper_manager, Connection $database, PathLocator $path_locator) { - $this->sitePath = $site_path; - $this->fileSystem = $file_system; - $this->streamWrapperManager = $stream_wrapper_manager; - $this->database = $database; - $this->pathLocator = $path_locator; - } - - /** - * Flags paths to be excluded, relative to the web root. - * - * This should only be used for paths that, if they exist at all, are - * *guaranteed* to exist within the web root. - * - * @param \Drupal\package_manager\Event\PreCreateEvent|\Drupal\package_manager\Event\PreApplyEvent $event - * The event object. - * @param string[] $paths - * The paths to exclude. These should be relative to the web root, and will - * be made relative to the project root. - */ - protected function excludeInWebRoot(StageEvent $event, array $paths): void { - $web_root = $this->pathLocator->getWebRoot(); - if ($web_root) { - $web_root .= '/'; - } - - foreach ($paths as $path) { - // Make the path relative to the project root by prefixing the web root. - $event->excludePath($web_root . $path); - } - } - - /** - * Flags paths to be excluded, relative to the project root. - * - * @param \Drupal\package_manager\Event\PreCreateEvent|\Drupal\package_manager\Event\PreApplyEvent $event - * The event object. - * @param string[] $paths - * The paths to exclude. Absolute paths will be made relative to the project - * root; relative paths will be assumed to already be relative to the - * project root, and excluded as given. - */ - protected function excludeInProjectRoot(StageEvent $event, array $paths): void { - $project_root = $this->pathLocator->getProjectRoot(); - - foreach ($paths as $path) { - // Make absolute paths relative to the project root. - $path = str_replace($project_root, '', $path); - $path = ltrim($path, '/'); - $event->excludePath($path); - } - } - - /** - * Excludes common paths from staging operations. - * - * @param \Drupal\package_manager\Event\PreApplyEvent|\Drupal\package_manager\Event\PreCreateEvent $event - * The event object. - * - * @see \Drupal\package_manager\Event\ExcludedPathsTrait::excludePath() - */ - public function ignoreCommonPaths(StageEvent $event): void { - // Compile two lists of paths to exclude: paths that are relative to the - // project root, and paths that are relative to the web root. - $web = $project = []; - - // Always ignore automated test directories. If they exist, they will be in - // the web root. - $web[] = 'sites/simpletest'; - - // If the core-vendor-hardening plugin (used in the legacy-project template) - // is present, it may have written security hardening files in the vendor - // directory. They should always be ignored. - $vendor_dir = $this->pathLocator->getVendorDirectory(); - $project[] = $vendor_dir . '/web.config'; - $project[] = $vendor_dir . '/.htaccess'; - - // Ignore public and private files. These paths could be either absolute or - // relative, depending on site settings. If they are absolute, treat them - // as relative to the project root. Otherwise, treat them as relative to - // the web root. - $files = array_filter([ - $this->getFilesPath('public'), - $this->getFilesPath('private'), - ]); - foreach ($files as $path) { - if ($this->fileSystem->isAbsolutePath($path)) { - $project[] = $path; - } - else { - $web[] = $path; - } - } - - // Ignore site-specific settings files, which are always in the web root. - $settings_files = [ - 'settings.php', - 'settings.local.php', - 'services.yml', - ]; - foreach ($settings_files as $settings_file) { - $web[] = $this->sitePath . '/' . $settings_file; - $web[] = 'sites/default/' . $settings_file; - } - - // If the database is SQLite, it might be located in the active directory - // and we should ignore it. Always treat it as relative to the project root. - if ($this->database->driver() === 'sqlite') { - $options = $this->database->getConnectionOptions(); - $project[] = $options['database']; - $project[] = $options['database'] . '-shm'; - $project[] = $options['database'] . '-wal'; - } - - // Find all .git directories in the project and exclude them. We cannot do - // this with FileSystemInterface::scanDirectory() because it unconditionally - // excludes anything starting with a dot. - $finder = Finder::create() - ->in($this->pathLocator->getProjectRoot()) - ->directories() - ->name('.git') - ->ignoreVCS(FALSE) - ->ignoreDotFiles(FALSE) - ->ignoreUnreadableDirs(); - - foreach ($finder as $git_directory) { - $project[] = $git_directory->getPathname(); - } - - $this->excludeInWebRoot($event, $web); - $this->excludeInProjectRoot($event, $project); - } - - /** - * Reacts before staged changes are committed the active directory. - * - * @param \Drupal\package_manager\Event\PreApplyEvent $event - * The event object. - */ - public function preApply(PreApplyEvent $event): void { - // Don't copy anything from the staging area's sites/default. - // @todo Make this a lot smarter in https://www.drupal.org/i/3228955. - $this->excludeInWebRoot($event, ['sites/default']); - - $this->ignoreCommonPaths($event); - } - - /** - * Returns the storage path for a stream wrapper. - * - * This will only work for stream wrappers that extend - * \Drupal\Core\StreamWrapper\LocalStream, which includes the stream wrappers - * for public and private files. - * - * @param string $scheme - * The stream wrapper scheme. - * - * @return string|null - * The storage path for files using the given scheme, relative to the Drupal - * root, or NULL if the stream wrapper does not extend - * \Drupal\Core\StreamWrapper\LocalStream. - */ - private function getFilesPath(string $scheme): ?string { - $wrapper = $this->streamWrapperManager->getViaScheme($scheme); - if ($wrapper instanceof LocalStream) { - return $wrapper->getDirectoryPath(); - } - return NULL; - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - return [ - PreCreateEvent::class => 'ignoreCommonPaths', - PreApplyEvent::class => 'preApply', - ]; - } - -} diff --git a/package_manager/src/PathExcluder/GitExcluder.php b/package_manager/src/PathExcluder/GitExcluder.php new file mode 100644 index 0000000000..b684a3af56 --- /dev/null +++ b/package_manager/src/PathExcluder/GitExcluder.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\package_manager\PathExcluder; + +use Drupal\package_manager\Event\PreApplyEvent; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\PathLocator; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Finder\Finder; + +/** + * Excludes .git directories from staging operations. + */ +class GitExcluder implements EventSubscriberInterface { + + use PathExclusionsTrait; + + /** + * Constructs a GitExcluder object. + * + * @param \Drupal\package_manager\PathLocator $path_locator + * The path locator service. + */ + public function __construct(PathLocator $path_locator) { + $this->pathLocator = $path_locator; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'excludeGitDirectories', + PreApplyEvent::class => 'excludeGitDirectories', + ]; + } + + /** + * Excludes .git directories from staging operations. + * + * @param \Drupal\package_manager\Event\StageEvent $event + * The event object. + */ + public function excludeGitDirectories(StageEvent $event): void { + // Find all .git directories in the project and exclude them. We cannot do + // this with FileSystemInterface::scanDirectory() because it unconditionally + // excludes anything starting with a dot. + $finder = Finder::create() + ->in($this->pathLocator->getProjectRoot()) + ->directories() + ->name('.git') + ->ignoreVCS(FALSE) + ->ignoreDotFiles(FALSE) + ->ignoreUnreadableDirs(); + + $paths = []; + foreach ($finder as $git_directory) { + $paths[] = $git_directory->getPathname(); + } + $this->excludeInProjectRoot($event, $paths); + } + +} diff --git a/package_manager/src/PathExcluder/PathExclusionsTrait.php b/package_manager/src/PathExcluder/PathExclusionsTrait.php new file mode 100644 index 0000000000..5c841417f6 --- /dev/null +++ b/package_manager/src/PathExcluder/PathExclusionsTrait.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\package_manager\PathExcluder; + +use Drupal\package_manager\Event\StageEvent; + +/** + * Contains methods for excluding paths from staging operations. + */ +trait PathExclusionsTrait { + + /** + * The path locator service. + * + * @var \Drupal\package_manager\PathLocator + */ + protected $pathLocator; + + /** + * Flags paths to be excluded, relative to the web root. + * + * This should only be used for paths that, if they exist at all, are + * *guaranteed* to exist within the web root. + * + * @param \Drupal\package_manager\Event\PreCreateEvent|\Drupal\package_manager\Event\PreApplyEvent $event + * The event object. + * @param string[] $paths + * The paths to exclude. These should be relative to the web root, and will + * be made relative to the project root. + */ + protected function excludeInWebRoot(StageEvent $event, array $paths): void { + $web_root = $this->pathLocator->getWebRoot(); + if ($web_root) { + $web_root .= '/'; + } + + foreach ($paths as $path) { + // Make the path relative to the project root by prefixing the web root. + $event->excludePath($web_root . $path); + } + } + + /** + * Flags paths to be excluded, relative to the project root. + * + * @param \Drupal\package_manager\Event\PreCreateEvent|\Drupal\package_manager\Event\PreApplyEvent $event + * The event object. + * @param string[] $paths + * The paths to exclude. Absolute paths will be made relative to the project + * root; relative paths will be assumed to already be relative to the + * project root, and excluded as given. + */ + protected function excludeInProjectRoot(StageEvent $event, array $paths): void { + $project_root = $this->pathLocator->getProjectRoot(); + + foreach ($paths as $path) { + // Make absolute paths relative to the project root. + $path = str_replace($project_root, '', $path); + $path = ltrim($path, '/'); + $event->excludePath($path); + } + } + +} diff --git a/package_manager/src/PathExcluder/SiteConfigurationExcluder.php b/package_manager/src/PathExcluder/SiteConfigurationExcluder.php new file mode 100644 index 0000000000..4ed7a8729b --- /dev/null +++ b/package_manager/src/PathExcluder/SiteConfigurationExcluder.php @@ -0,0 +1,88 @@ +<?php + +namespace Drupal\package_manager\PathExcluder; + +use Drupal\package_manager\Event\PreApplyEvent; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\PathLocator; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Excludes site configuration files from staging areas. + */ +class SiteConfigurationExcluder implements EventSubscriberInterface { + + use PathExclusionsTrait; + + /** + * The current site path, relative to the Drupal root. + * + * @var string + */ + protected $sitePath; + + /** + * Constructs an ExcludedPathsSubscriber. + * + * @param string $site_path + * The current site path, relative to the Drupal root. + * @param \Drupal\package_manager\PathLocator $path_locator + * The path locator service. + */ + public function __construct(string $site_path, PathLocator $path_locator) { + $this->sitePath = $site_path; + $this->pathLocator = $path_locator; + } + + /** + * Excludes common paths from staging operations. + * + * @param \Drupal\package_manager\Event\PreApplyEvent|\Drupal\package_manager\Event\PreCreateEvent $event + * The event object. + * + * @see \Drupal\package_manager\Event\ExcludedPathsTrait::excludePath() + */ + public function ignoreCommonPaths(StageEvent $event): void { + // Site configuration files are always excluded relative to the web root. + $web = []; + + // Ignore site-specific settings files, which are always in the web root. + $settings_files = [ + 'settings.php', + 'settings.local.php', + 'services.yml', + ]; + foreach ($settings_files as $settings_file) { + $web[] = $this->sitePath . '/' . $settings_file; + $web[] = 'sites/default/' . $settings_file; + } + + $this->excludeInWebRoot($event, $web); + } + + /** + * Reacts before staged changes are committed the active directory. + * + * @param \Drupal\package_manager\Event\PreApplyEvent $event + * The event object. + */ + public function preApply(PreApplyEvent $event): void { + // Don't copy anything from the staging area's sites/default. + // @todo Make this a lot smarter in https://www.drupal.org/i/3228955. + $this->excludeInWebRoot($event, ['sites/default']); + + $this->ignoreCommonPaths($event); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'ignoreCommonPaths', + PreApplyEvent::class => 'preApply', + ]; + } + +} diff --git a/package_manager/src/PathExcluder/SiteFilesExcluder.php b/package_manager/src/PathExcluder/SiteFilesExcluder.php new file mode 100644 index 0000000000..b15870195c --- /dev/null +++ b/package_manager/src/PathExcluder/SiteFilesExcluder.php @@ -0,0 +1,87 @@ +<?php + +namespace Drupal\package_manager\PathExcluder; + +use Drupal\Core\StreamWrapper\LocalStream; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; +use Drupal\package_manager\Event\PreApplyEvent; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\PathLocator; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Filesystem\Filesystem; + +/** + * Excludes public and private files from staging operations. + */ +class SiteFilesExcluder implements EventSubscriberInterface { + + use PathExclusionsTrait; + + /** + * The stream wrapper manager service. + * + * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface + */ + protected $streamWrapperManager; + + /** + * The Symfony file system service. + * + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected $fileSystem; + + /** + * Constructs a SiteFilesExcluder object. + * + * @param \Drupal\package_manager\PathLocator $path_locator + * The path locator service. + * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager + * The stream wrapper manager service. + * @param \Symfony\Component\Filesystem\Filesystem $file_system + * The Symfony file system service. + */ + public function __construct(PathLocator $path_locator, StreamWrapperManagerInterface $stream_wrapper_manager, Filesystem $file_system) { + $this->pathLocator = $path_locator; + $this->streamWrapperManager = $stream_wrapper_manager; + $this->fileSystem = $file_system; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'excludeSiteFiles', + PreApplyEvent::class => 'excludeSiteFiles', + ]; + } + + /** + * Excludes public and private files from staging operations. + * + * @param \Drupal\package_manager\Event\StageEvent $event + * The event object. + */ + public function excludeSiteFiles(StageEvent $event): void { + // Ignore public and private files. These paths could be either absolute or + // relative, depending on site settings. If they are absolute, treat them + // as relative to the project root. Otherwise, treat them as relative to + // the web root. + foreach (['public', 'private'] as $scheme) { + $wrapper = $this->streamWrapperManager->getViaScheme($scheme); + if ($wrapper instanceof LocalStream) { + $path = $wrapper->getDirectoryPath(); + + if ($this->fileSystem->isAbsolutePath($path)) { + $this->excludeInProjectRoot($event, [$path]); + } + else { + $this->excludeInWebRoot($event, [$path]); + } + } + } + } + +} diff --git a/package_manager/src/PathExcluder/SqliteDatabaseExcluder.php b/package_manager/src/PathExcluder/SqliteDatabaseExcluder.php new file mode 100644 index 0000000000..e182383c51 --- /dev/null +++ b/package_manager/src/PathExcluder/SqliteDatabaseExcluder.php @@ -0,0 +1,68 @@ +<?php + +namespace Drupal\package_manager\PathExcluder; + +use Drupal\Core\Database\Connection; +use Drupal\package_manager\Event\PreApplyEvent; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\PathLocator; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Excludes SQLite database files from staging operations. + */ +class SqliteDatabaseExcluder implements EventSubscriberInterface { + + use PathExclusionsTrait; + + /** + * The database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected $database; + + /** + * Constructs a SqliteDatabaseExcluder object. + * + * @param \Drupal\package_manager\PathLocator $path_locator + * The path locator service. + * @param \Drupal\Core\Database\Connection $database + * The database connection. + */ + public function __construct(PathLocator $path_locator, Connection $database) { + $this->pathLocator = $path_locator; + $this->database = $database; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'excludeDatabaseFiles', + PreApplyEvent::class => 'excludeDatabaseFiles', + ]; + } + + /** + * Excludes SQLite database files from staging operations. + * + * @param \Drupal\package_manager\Event\StageEvent $event + * The event object. + */ + public function excludeDatabaseFiles(StageEvent $event): void { + // If the database is SQLite, it might be located in the active directory + // and we should ignore it. Always treat it as relative to the project root. + if ($this->database->driver() === 'sqlite') { + $options = $this->database->getConnectionOptions(); + $this->excludeInProjectRoot($event, [ + $options['database'], + $options['database'] . '-shm', + $options['database'] . '-wal', + ]); + } + } + +} diff --git a/package_manager/src/PathExcluder/TestSiteExcluder.php b/package_manager/src/PathExcluder/TestSiteExcluder.php new file mode 100644 index 0000000000..3fbae39016 --- /dev/null +++ b/package_manager/src/PathExcluder/TestSiteExcluder.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\package_manager\PathExcluder; + +use Drupal\package_manager\Event\PreApplyEvent; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\PathLocator; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Excludes 'sites/simpletest' from staging operations. + */ +class TestSiteExcluder implements EventSubscriberInterface { + + use PathExclusionsTrait; + + /** + * Constructs a TestSiteExcluder object. + * + * @param \Drupal\package_manager\PathLocator $path_locator + * The path locator service. + */ + public function __construct(PathLocator $path_locator) { + $this->pathLocator = $path_locator; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'excludeTestSites', + PreApplyEvent::class => 'excludeTestSites', + ]; + } + + /** + * Excludes sites/simpletest from staging operations. + * + * @param \Drupal\package_manager\Event\StageEvent $event + * The event object. + */ + public function excludeTestSites(StageEvent $event): void { + // Always ignore automated test directories. If they exist, they will be in + // the web root. + $this->excludeInWebRoot($event, ['sites/simpletest']); + } + +} diff --git a/package_manager/src/PathExcluder/VendorHardeningExcluder.php b/package_manager/src/PathExcluder/VendorHardeningExcluder.php new file mode 100644 index 0000000000..11248732bd --- /dev/null +++ b/package_manager/src/PathExcluder/VendorHardeningExcluder.php @@ -0,0 +1,55 @@ +<?php + +namespace Drupal\package_manager\PathExcluder; + +use Drupal\package_manager\Event\PreApplyEvent; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\PathLocator; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Excludes vendor hardening files from staging operations. + */ +class VendorHardeningExcluder implements EventSubscriberInterface { + + use PathExclusionsTrait; + + /** + * Constructs a VendorHardeningExcluder object. + * + * @param \Drupal\package_manager\PathLocator $path_locator + * The path locator service. + */ + public function __construct(PathLocator $path_locator) { + $this->pathLocator = $path_locator; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'excludeVendorHardeningFiles', + PreApplyEvent::class => 'excludeVendorHardeningFiles', + ]; + } + + /** + * Excludes vendor hardening files from staging operations. + * + * @param \Drupal\package_manager\Event\StageEvent $event + * The event object. + */ + public function excludeVendorHardeningFiles(StageEvent $event): void { + // If the core-vendor-hardening plugin (used in the legacy-project template) + // is present, it may have written security hardening files in the vendor + // directory. They should always be ignored. + $vendor_dir = $this->pathLocator->getVendorDirectory(); + $this->excludeInProjectRoot($event, [ + $vendor_dir . '/web.config', + $vendor_dir . '/.htaccess', + ]); + } + +} diff --git a/package_manager/tests/src/Kernel/ExcludedPathsTest.php b/package_manager/tests/src/Kernel/ExcludedPathsTest.php deleted file mode 100644 index eecde71a0a..0000000000 --- a/package_manager/tests/src/Kernel/ExcludedPathsTest.php +++ /dev/null @@ -1,243 +0,0 @@ -<?php - -namespace Drupal\Tests\package_manager\Kernel; - -use Drupal\Core\Database\Connection; -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber; - -/** - * @covers \Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber - * - * @group package_manager - */ -class ExcludedPathsTest extends PackageManagerKernelTestBase { - - /** - * {@inheritdoc} - */ - protected function setUp(): void { - // In this test, we want to disable the lock file validator because, even - // though both the active and stage directories will have a valid lock file, - // this validator will complain because they don't differ at all. - $this->disableValidators[] = 'package_manager.validator.lock_file'; - parent::setUp(); - } - - /** - * {@inheritdoc} - */ - public function register(ContainerBuilder $container) { - parent::register($container); - - $container->getDefinition('package_manager.excluded_paths_subscriber') - ->setClass(TestExcludedPathsSubscriber::class); - } - - /** - * Tests that certain paths are excluded from staging operations. - */ - public function testExcludedPaths(): void { - // The private stream wrapper is only registered if this setting is set. - // @see \Drupal\Core\CoreServiceProvider::register() - $this->setSetting('file_private_path', 'private'); - // In this test, we want to perform the actual staging operations so that we - // can be sure that files are staged as expected. This will also rebuild - // the container, enabling the private stream wrapper. - $this->container->get('module_installer')->uninstall([ - 'package_manager_bypass', - ]); - // Ensure we have an up-to-date container. - $this->container = $this->container->get('kernel')->getContainer(); - - $this->createTestProject(); - $active_dir = $this->container->get('package_manager.path_locator') - ->getProjectRoot(); - - $site_path = 'sites/example.com'; - // Ensure that we are using directories within the fake site fixture for - // public and private files. - $this->setSetting('file_public_path', "$site_path/files"); - - // Mock a SQLite database connection to a file in the active directory. The - // file should not be staged. - $database = $this->prophesize(Connection::class); - $database->driver()->willReturn('sqlite'); - $database->getConnectionOptions()->willReturn([ - 'database' => $site_path . '/db.sqlite', - ]); - - // Update the event subscriber's dependencies. - /** @var \Drupal\Tests\package_manager\Kernel\TestExcludedPathsSubscriber $subscriber */ - $subscriber = $this->container->get('package_manager.excluded_paths_subscriber'); - $subscriber->sitePath = $site_path; - $subscriber->database = $database->reveal(); - - $stage = $this->createStage(); - $stage->create(); - $stage_dir = $stage->getStageDirectory(); - - $ignore = [ - 'sites/simpletest', - 'vendor/.htaccess', - 'vendor/web.config', - "$site_path/files/ignore.txt", - 'private/ignore.txt', - "$site_path/settings.php", - "$site_path/settings.local.php", - "$site_path/services.yml", - // SQLite databases and their support files should always be ignored. - "$site_path/db.sqlite", - "$site_path/db.sqlite-shm", - "$site_path/db.sqlite-wal", - // Default site-specific settings files should be ignored. - 'sites/default/settings.php', - 'sites/default/settings.local.php', - 'sites/default/services.yml', - // No git directories should be staged. - '.git/ignore.txt', - 'modules/example/.git/ignore.txt', - ]; - foreach ($ignore as $path) { - $this->assertFileExists("$active_dir/$path"); - $this->assertFileDoesNotExist("$stage_dir/$path"); - } - // A non-excluded file in the default site directory should be staged. - $this->assertFileExists("$stage_dir/sites/default/stage.txt"); - // Regular module files should be staged. - $this->assertFileExists("$stage_dir/modules/example/example.info.yml"); - // Files that start with .git, but aren't actually .git, should be staged. - $this->assertFileExists("$stage_dir/.gitignore"); - - // A new file added to the staging area in an excluded directory, should not - // be copied to the active directory. - $file = "$stage_dir/sites/default/no-copy.txt"; - touch($file); - $this->assertFileExists($file); - $stage->apply(); - $this->assertFileDoesNotExist("$active_dir/sites/default/no-copy.txt"); - - // The ignored files should still be in the active directory. - foreach ($ignore as $path) { - $this->assertFileExists("$active_dir/$path"); - } - } - - /** - * Data provider for ::testSqliteDatabaseExcluded(). - * - * @return array[] - * Sets of arguments to pass to the test method. - */ - public function providerSqliteDatabaseExcluded(): array { - $drupal_root = $this->getDrupalRoot(); - - return [ - 'relative path, in site directory' => [ - 'sites/example.com/db.sqlite', - [ - 'sites/example.com/db.sqlite', - 'sites/example.com/db.sqlite-shm', - 'sites/example.com/db.sqlite-wal', - ], - ], - 'relative path, at root' => [ - 'db.sqlite', - [ - 'db.sqlite', - 'db.sqlite-shm', - 'db.sqlite-wal', - ], - ], - 'absolute path, in site directory' => [ - $drupal_root . '/sites/example.com/db.sqlite', - [ - 'sites/example.com/db.sqlite', - 'sites/example.com/db.sqlite-shm', - 'sites/example.com/db.sqlite-wal', - ], - ], - 'absolute path, at root' => [ - $drupal_root . '/db.sqlite', - [ - 'db.sqlite', - 'db.sqlite-shm', - 'db.sqlite-wal', - ], - ], - ]; - } - - /** - * Tests that SQLite database paths are excluded from the staging area. - * - * This test ensures that SQLite database paths are processed properly (e.g., - * converting an absolute path to a relative path) before being flagged for - * exclusion. - * - * @param string $database_path - * The path of the SQLite database, as set in the database connection - * options. - * @param string[] $expected_exclusions - * The database paths which should be flagged for exclusion. - * - * @dataProvider providerSqliteDatabaseExcluded - */ - public function testSqliteDatabaseExcluded(string $database_path, array $expected_exclusions): void { - $database = $this->prophesize(Connection::class); - $database->driver()->willReturn('sqlite'); - $database->getConnectionOptions()->willReturn([ - 'database' => $database_path, - ]); - - // Update the event subscriber to use the mocked database. - /** @var \Drupal\Tests\package_manager\Kernel\TestExcludedPathsSubscriber $subscriber */ - $subscriber = $this->container->get('package_manager.excluded_paths_subscriber'); - $subscriber->database = $database->reveal(); - - $event = new PreCreateEvent($this->createStage()); - // Invoke the event subscriber directly, so we can check that the database - // was correctly excluded. - $subscriber->ignoreCommonPaths($event); - // All of the expected exclusions should be flagged. - $this->assertEmpty(array_diff($expected_exclusions, $event->getExcludedPaths())); - } - - /** - * Tests that unreadable directories are ignored by the event subscriber. - */ - public function testUnreadableDirectoriesAreIgnored(): void { - $this->createTestProject(); - $active_dir = $this->container->get('package_manager.path_locator') - ->getProjectRoot(); - - // Create an unreadable directory within the active directory, which will - // raise an exception as the event subscriber tries to scan for .git - // directories...unless unreadable directories are being ignored, as they - // should be. - $unreadable_dir = $active_dir . '/unreadable'; - mkdir($unreadable_dir, 0000); - $this->assertDirectoryIsNotReadable($unreadable_dir); - - $this->createStage()->create(); - } - -} - -/** - * A test-only version of the excluded paths event subscriber. - */ -class TestExcludedPathsSubscriber extends ExcludedPathsSubscriber { - - /** - * {@inheritdoc} - */ - public $sitePath; - - /** - * {@inheritdoc} - */ - public $database; - -} diff --git a/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php new file mode 100644 index 0000000000..a1817cc0d0 --- /dev/null +++ b/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php @@ -0,0 +1,80 @@ +<?php + +namespace Drupal\Tests\package_manager\Kernel\PathExcluder; + +use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; + +/** + * @covers \Drupal\package_manager\PathExcluder\GitExcluder + * + * @group package_manager + */ +class GitExcluderTest extends PackageManagerKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // In this test, we want to disable the lock file validator because, even + // though both the active and stage directories will have a valid lock file, + // this validator will complain because they don't differ at all. + $this->disableValidators[] = 'package_manager.validator.lock_file'; + parent::setUp(); + } + + /** + * Tests that unreadable directories are ignored by the event subscriber. + */ + public function testUnreadableDirectoriesAreIgnored(): void { + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getProjectRoot(); + + // Create an unreadable directory within the active directory, which will + // raise an exception as the event subscriber tries to scan for .git + // directories...unless unreadable directories are being ignored, as they + // should be. + $unreadable_dir = $active_dir . '/unreadable'; + mkdir($unreadable_dir, 0000); + $this->assertDirectoryIsNotReadable($unreadable_dir); + + $this->createStage()->create(); + } + + /** + * Tests that Git directories are excluded from staging operations. + */ + public function testGitDirectoriesExcluded(): void { + // In this test, we want to perform the actual staging operations so that we + // can be sure that files are staged as expected. + $this->disableModules(['package_manager_bypass']); + // Ensure we have an up-to-date container. + $this->container = $this->container->get('kernel')->getContainer(); + + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getProjectRoot(); + + $stage = $this->createStage(); + $stage->create(); + $stage_dir = $stage->getStageDirectory(); + + $ignored = [ + '.git/ignore.txt', + 'modules/example/.git/ignore.txt', + ]; + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + $this->assertFileDoesNotExist("$stage_dir/$path"); + } + // Files that start with .git, but aren't actually .git, should be staged. + $this->assertFileExists("$stage_dir/.gitignore"); + + $stage->apply(); + // The ignored files should still be in the active directory. + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + } + } + +} diff --git a/package_manager/tests/src/Kernel/PathExcluder/SiteConfigurationExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/SiteConfigurationExcluderTest.php new file mode 100644 index 0000000000..095461dcd1 --- /dev/null +++ b/package_manager/tests/src/Kernel/PathExcluder/SiteConfigurationExcluderTest.php @@ -0,0 +1,104 @@ +<?php + +namespace Drupal\Tests\package_manager\Kernel\PathExcluder; + +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\package_manager\PathExcluder\SiteConfigurationExcluder; +use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; + +/** + * @covers \Drupal\package_manager\PathExcluder\SiteConfigurationExcluder + * + * @group package_manager + */ +class SiteConfigurationExcluderTest extends PackageManagerKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // In this test, we want to disable the lock file validator because, even + // though both the active and stage directories will have a valid lock file, + // this validator will complain because they don't differ at all. + $this->disableValidators[] = 'package_manager.validator.lock_file'; + parent::setUp(); + } + + /** + * {@inheritdoc} + */ + public function register(ContainerBuilder $container) { + parent::register($container); + + $container->getDefinition('package_manager.site_configuration_excluder') + ->setClass(TestSiteConfigurationExcluder::class); + } + + /** + * Tests that certain paths are excluded from staging operations. + */ + public function testExcludedPaths(): void { + // In this test, we want to perform the actual staging operations so that we + // can be sure that files are staged as expected. + $this->disableModules(['package_manager_bypass']); + // Ensure we have an up-to-date container. + $this->container = $this->container->get('kernel')->getContainer(); + + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getProjectRoot(); + + $site_path = 'sites/example.com'; + + // Update the event subscribers' dependencies. + $this->container->get('package_manager.site_configuration_excluder')->sitePath = $site_path; + + $stage = $this->createStage(); + $stage->create(); + $stage_dir = $stage->getStageDirectory(); + + $ignore = [ + "$site_path/settings.php", + "$site_path/settings.local.php", + "$site_path/services.yml", + // Default site-specific settings files should be ignored. + 'sites/default/settings.php', + 'sites/default/settings.local.php', + 'sites/default/services.yml', + ]; + foreach ($ignore as $path) { + $this->assertFileExists("$active_dir/$path"); + $this->assertFileDoesNotExist("$stage_dir/$path"); + } + // A non-excluded file in the default site directory should be staged. + $this->assertFileExists("$stage_dir/sites/default/stage.txt"); + // Regular module files should be staged. + $this->assertFileExists("$stage_dir/modules/example/example.info.yml"); + + // A new file added to the staging area in an excluded directory, should not + // be copied to the active directory. + $file = "$stage_dir/sites/default/no-copy.txt"; + touch($file); + $this->assertFileExists($file); + $stage->apply(); + $this->assertFileDoesNotExist("$active_dir/sites/default/no-copy.txt"); + + // The ignored files should still be in the active directory. + foreach ($ignore as $path) { + $this->assertFileExists("$active_dir/$path"); + } + } + +} + +/** + * A test version of the site configuration excluder, to expose internals. + */ +class TestSiteConfigurationExcluder extends SiteConfigurationExcluder { + + /** + * {@inheritdoc} + */ + public $sitePath; + +} diff --git a/package_manager/tests/src/Kernel/PathExcluder/SiteFilesExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/SiteFilesExcluderTest.php new file mode 100644 index 0000000000..5241397642 --- /dev/null +++ b/package_manager/tests/src/Kernel/PathExcluder/SiteFilesExcluderTest.php @@ -0,0 +1,67 @@ +<?php + +namespace Drupal\Tests\package_manager\Kernel\PathExcluder; + +use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; + +/** + * @covers \Drupal\package_manager\PathExcluder\SiteFilesExcluder + * + * @group package_manager + */ +class SiteFilesExcluderTest extends PackageManagerKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // In this test, we want to disable the lock file validator because, even + // though both the active and stage directories will have a valid lock file, + // this validator will complain because they don't differ at all. + $this->disableValidators[] = 'package_manager.validator.lock_file'; + parent::setUp(); + } + + /** + * Tests that public and private files are excluded from staging operations. + */ + public function testSiteFilesExcluded(): void { + // The private stream wrapper is only registered if this setting is set. + // @see \Drupal\Core\CoreServiceProvider::register() + $this->setSetting('file_private_path', 'private'); + // In this test, we want to perform the actual staging operations so that we + // can be sure that files are staged as expected. This will also rebuild + // the container, enabling the private stream wrapper. + $this->disableModules(['package_manager_bypass']); + // Ensure we have an up-to-date container. + $this->container = $this->container->get('kernel')->getContainer(); + + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getProjectRoot(); + + // Ensure that we are using directories within the fake site fixture for + // public and private files. + $this->setSetting('file_public_path', "sites/example.com/files"); + + $stage = $this->createStage(); + $stage->create(); + $stage_dir = $stage->getStageDirectory(); + + $ignored = [ + "sites/example.com/files/ignore.txt", + 'private/ignore.txt', + ]; + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + $this->assertFileDoesNotExist("$stage_dir/$path"); + } + + $stage->apply(); + // The ignored files should still be in the active directory. + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + } + } + +} diff --git a/package_manager/tests/src/Kernel/PathExcluder/SqliteDatabaseExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/SqliteDatabaseExcluderTest.php new file mode 100644 index 0000000000..9671134607 --- /dev/null +++ b/package_manager/tests/src/Kernel/PathExcluder/SqliteDatabaseExcluderTest.php @@ -0,0 +1,181 @@ +<?php + +namespace Drupal\Tests\package_manager\Kernel\PathExcluder; + +use Drupal\Core\Database\Connection; +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\PathExcluder\SqliteDatabaseExcluder; +use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; + +/** + * @covers \Drupal\package_manager\PathExcluder\SqliteDatabaseExcluder + * + * @group package_manager + */ +class SqliteDatabaseExcluderTest extends PackageManagerKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // In this test, we want to disable the lock file validator because, even + // though both the active and stage directories will have a valid lock file, + // this validator will complain because they don't differ at all. + $this->disableValidators[] = 'package_manager.validator.lock_file'; + parent::setUp(); + } + + /** + * {@inheritdoc} + */ + public function register(ContainerBuilder $container) { + parent::register($container); + + $container->getDefinition('package_manager.sqlite_excluder') + ->setClass(TestSqliteDatabaseExcluder::class); + } + + /** + * Mocks a SQLite database connection for the event subscriber. + * + * @param array $connection_options + * The connection options for the mocked database connection. + */ + private function mockDatabase(array $connection_options): void { + $database = $this->prophesize(Connection::class); + $database->driver()->willReturn('sqlite'); + $database->getConnectionOptions()->willReturn($connection_options); + + $this->container->get('package_manager.sqlite_excluder') + ->database = $database->reveal(); + } + + /** + * Tests that SQLite database files are excluded from staging operations. + */ + public function testSqliteDatabaseFilesExcluded(): void { + // In this test, we want to perform the actual staging operations so that we + // can be sure that files are staged as expected. + $this->disableModules(['package_manager_bypass']); + // Ensure we have an up-to-date container. + $this->container = $this->container->get('kernel')->getContainer(); + + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getProjectRoot(); + + // Mock a SQLite database connection to a file in the active directory. The + // file should not be staged. + $this->mockDatabase([ + 'database' => 'sites/example.com/db.sqlite', + ]); + + $stage = $this->createStage(); + $stage->create(); + $stage_dir = $stage->getStageDirectory(); + + $ignored = [ + "sites/example.com/db.sqlite", + "sites/example.com/db.sqlite-shm", + "sites/example.com/db.sqlite-wal", + ]; + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + $this->assertFileDoesNotExist("$stage_dir/$path"); + } + + $stage->apply(); + // The ignored files should still be in the active directory. + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + } + } + + /** + * Data provider for ::testPathProcessing(). + * + * @return array[] + * Sets of arguments to pass to the test method. + */ + public function providerPathProcessing(): array { + $drupal_root = $this->getDrupalRoot(); + + return [ + 'relative path, in site directory' => [ + 'sites/example.com/db.sqlite', + [ + 'sites/example.com/db.sqlite', + 'sites/example.com/db.sqlite-shm', + 'sites/example.com/db.sqlite-wal', + ], + ], + 'relative path, at root' => [ + 'db.sqlite', + [ + 'db.sqlite', + 'db.sqlite-shm', + 'db.sqlite-wal', + ], + ], + 'absolute path, in site directory' => [ + $drupal_root . '/sites/example.com/db.sqlite', + [ + 'sites/example.com/db.sqlite', + 'sites/example.com/db.sqlite-shm', + 'sites/example.com/db.sqlite-wal', + ], + ], + 'absolute path, at root' => [ + $drupal_root . '/db.sqlite', + [ + 'db.sqlite', + 'db.sqlite-shm', + 'db.sqlite-wal', + ], + ], + ]; + } + + /** + * Tests SQLite database path processing. + * + * This test ensures that SQLite database paths are processed properly (e.g., + * converting an absolute path to a relative path) before being flagged for + * exclusion. + * + * @param string $database_path + * The path of the SQLite database, as set in the database connection + * options. + * @param string[] $expected_exclusions + * The database paths which should be flagged for exclusion. + * + * @dataProvider providerPathProcessing + */ + public function testPathProcessing(string $database_path, array $expected_exclusions): void { + $this->mockDatabase([ + 'database' => $database_path, + ]); + + $event = new PreCreateEvent($this->createStage()); + // Invoke the event subscriber directly, so we can check that the database + // was correctly excluded. + $this->container->get('package_manager.sqlite_excluder') + ->excludeDatabaseFiles($event); + // All of the expected exclusions should be flagged. + $this->assertEmpty(array_diff($expected_exclusions, $event->getExcludedPaths())); + } + +} + +/** + * A test-only version of the SQLite database excluder, to expose internals. + */ +class TestSqliteDatabaseExcluder extends SqliteDatabaseExcluder { + + /** + * {@inheritdoc} + */ + public $database; + +} diff --git a/package_manager/tests/src/Kernel/PathExcluder/TestSiteExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/TestSiteExcluderTest.php new file mode 100644 index 0000000000..7b19c31732 --- /dev/null +++ b/package_manager/tests/src/Kernel/PathExcluder/TestSiteExcluderTest.php @@ -0,0 +1,58 @@ +<?php + +namespace Drupal\Tests\package_manager\Kernel\PathExcluder; + +use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; + +/** + * @covers \Drupal\package_manager\PathExcluder\TestSiteExcluder + * + * @group package_manager + */ +class TestSiteExcluderTest extends PackageManagerKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // In this test, we want to disable the lock file validator because, even + // though both the active and stage directories will have a valid lock file, + // this validator will complain because they don't differ at all. + $this->disableValidators[] = 'package_manager.validator.lock_file'; + parent::setUp(); + } + + /** + * Tests that test site directories are excluded from staging operations. + */ + public function testTestSitesExcluded(): void { + // In this test, we want to perform the actual staging operations so that we + // can be sure that files are staged as expected. + $this->disableModules(['package_manager_bypass']); + // Ensure we have an up-to-date container. + $this->container = $this->container->get('kernel')->getContainer(); + + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getProjectRoot(); + + $stage = $this->createStage(); + $stage->create(); + $stage_dir = $stage->getStageDirectory(); + + $ignored = [ + 'sites/simpletest', + ]; + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + $this->assertFileDoesNotExist("$stage_dir/$path"); + } + + $stage->apply(); + // The ignored files should still be in the active directory. + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + } + } + +} diff --git a/package_manager/tests/src/Kernel/PathExcluder/VendorHardeningExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/VendorHardeningExcluderTest.php new file mode 100644 index 0000000000..8d74f74daa --- /dev/null +++ b/package_manager/tests/src/Kernel/PathExcluder/VendorHardeningExcluderTest.php @@ -0,0 +1,59 @@ +<?php + +namespace Drupal\Tests\package_manager\Kernel\PathExcluder; + +use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; + +/** + * @covers \Drupal\package_manager\PathExcluder\VendorHardeningExcluder + * + * @group package_manager + */ +class VendorHardeningExcluderTest extends PackageManagerKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // In this test, we want to disable the lock file validator because, even + // though both the active and stage directories will have a valid lock file, + // this validator will complain because they don't differ at all. + $this->disableValidators[] = 'package_manager.validator.lock_file'; + parent::setUp(); + } + + /** + * Tests that vendor hardening files are excluded from staging operations. + */ + public function testVendorHardeningFilesExcluded(): void { + // In this test, we want to perform the actual staging operations so that we + // can be sure that files are staged as expected. + $this->disableModules(['package_manager_bypass']); + // Ensure we have an up-to-date container. + $this->container = $this->container->get('kernel')->getContainer(); + + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getProjectRoot(); + + $stage = $this->createStage(); + $stage->create(); + $stage_dir = $stage->getStageDirectory(); + + $ignored = [ + 'vendor/.htaccess', + 'vendor/web.config', + ]; + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + $this->assertFileDoesNotExist("$stage_dir/$path"); + } + + $stage->apply(); + // The ignored files should still be in the active directory. + foreach ($ignored as $path) { + $this->assertFileExists("$active_dir/$path"); + } + } + +} diff --git a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php index eef8b02e2e..171490c189 100644 --- a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php @@ -96,11 +96,11 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { $event_dispatcher = $this->container->get('event_dispatcher'); // Disable the disk space validator, since it doesn't work with vfsStream, - // and the excluded paths subscriber, since it won't deal with this tiny + // and the Git directory excluder, since it won't deal with this tiny // virtual file system correctly. $disable_subscribers = array_map([$this->container, 'get'], [ 'package_manager.validator.disk_space', - 'package_manager.excluded_paths_subscriber', + 'package_manager.git_excluder', ]); array_walk($disable_subscribers, [$event_dispatcher, 'removeSubscriber']); -- GitLab