Skip to content
Snippets Groups Projects
Commit ca81aef4 authored by Adam G-H's avatar Adam G-H
Browse files

Issue #3274323 by phenaproxima: Split ExcludedPathsSubscriber into multiple classes

parent 8bf9b100
No related branches found
No related tags found
1 merge request!263Issue #3274323: Split ExcludedPathsSubscriber into multiple classes
Showing
with 1062 additions and 257 deletions
......@@ -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 }
......
......@@ -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;
......
<?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',
];
}
}
<?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);
}
}
<?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);
}
}
}
<?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',
];
}
}
<?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]);
}
}
}
}
}
<?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',
]);
}
}
}
<?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']);
}
}
<?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',
]);
}
}
<?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");
}
}
}
<?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;
}
<?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");
}
}
}
<?php
namespace Drupal\Tests\package_manager\Kernel;
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\EventSubscriber\ExcludedPathsSubscriber;
use Drupal\package_manager\PathExcluder\SqliteDatabaseExcluder;
use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase;
/**
* @covers \Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber
* @covers \Drupal\package_manager\PathExcluder\SqliteDatabaseExcluder
*
* @group package_manager
*/
class ExcludedPathsTest extends PackageManagerKernelTestBase {
class SqliteDatabaseExcluderTest extends PackageManagerKernelTestBase {
/**
* {@inheritdoc}
......@@ -31,23 +32,32 @@ class ExcludedPathsTest extends PackageManagerKernelTestBase {
public function register(ContainerBuilder $container) {
parent::register($container);
$container->getDefinition('package_manager.excluded_paths_subscriber')
->setClass(TestExcludedPathsSubscriber::class);
$container->getDefinition('package_manager.sqlite_excluder')
->setClass(TestSqliteDatabaseExcluder::class);
}
/**
* Tests that certain paths are excluded from staging operations.
* 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 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');
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 will also rebuild
// the container, enabling the private stream wrapper.
$this->container->get('module_installer')->uninstall([
'package_manager_bypass',
]);
// 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();
......@@ -55,82 +65,40 @@ class ExcludedPathsTest extends PackageManagerKernelTestBase {
$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',
$this->mockDatabase([
'database' => 'sites/example.com/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',
$ignored = [
"sites/example.com/db.sqlite",
"sites/example.com/db.sqlite-shm",
"sites/example.com/db.sqlite-wal",
];
foreach ($ignore as $path) {
foreach ($ignored 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");
$stage->apply();
// The ignored files should still be in the active directory.
foreach ($ignore as $path) {
foreach ($ignored as $path) {
$this->assertFileExists("$active_dir/$path");
}
}
/**
* Data provider for ::testSqliteDatabaseExcluded().
* Data provider for ::testPathProcessing().
*
* @return array[]
* Sets of arguments to pass to the test method.
*/
public function providerSqliteDatabaseExcluded(): array {
public function providerPathProcessing(): array {
$drupal_root = $this->getDrupalRoot();
return [
......@@ -170,7 +138,7 @@ class ExcludedPathsTest extends PackageManagerKernelTestBase {
}
/**
* Tests that SQLite database paths are excluded from the staging area.
* 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
......@@ -182,58 +150,28 @@ class ExcludedPathsTest extends PackageManagerKernelTestBase {
* @param string[] $expected_exclusions
* The database paths which should be flagged for exclusion.
*
* @dataProvider providerSqliteDatabaseExcluded
* @dataProvider providerPathProcessing
*/
public function testSqliteDatabaseExcluded(string $database_path, array $expected_exclusions): void {
$database = $this->prophesize(Connection::class);
$database->driver()->willReturn('sqlite');
$database->getConnectionOptions()->willReturn([
public function testPathProcessing(string $database_path, array $expected_exclusions): void {
$this->mockDatabase([
'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);
$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()));
}
/**
* 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.
* A test-only version of the SQLite database excluder, to expose internals.
*/
class TestExcludedPathsSubscriber extends ExcludedPathsSubscriber {
/**
* {@inheritdoc}
*/
public $sitePath;
class TestSqliteDatabaseExcluder extends SqliteDatabaseExcluder {
/**
* {@inheritdoc}
......
<?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");
}
}
}
<?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");
}
}
}
......@@ -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']);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment