Loading package_manager/package_manager.services.yml +3 −1 Original line number Diff line number Diff line Loading @@ -105,8 +105,10 @@ services: package_manager.validator.symlink: class: Drupal\package_manager\Validator\SymlinkValidator arguments: - '@module_handler' - '@package_manager.path_locator' - '@PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface' - '@PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface' - '@module_handler' tags: - { name: event_subscriber } package_manager.validator.duplicate_info_file: Loading package_manager/src/PathExcluder/GitExcluder.php +1 −2 Original line number Diff line number Diff line Loading @@ -56,8 +56,7 @@ final class GitExcluder implements EventSubscriberInterface { ->directories() ->name('.git') ->ignoreVCS(FALSE) ->ignoreDotFiles(FALSE) ->ignoreUnreadableDirs(); ->ignoreDotFiles(FALSE); $paths = []; foreach ($finder as $git_directory) { Loading package_manager/src/Validator/SymlinkValidator.php +66 −92 Original line number Diff line number Diff line Loading @@ -10,7 +10,9 @@ use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreOperationStageEvent; use Drupal\package_manager\Event\StatusCheckEvent; use Drupal\package_manager\PathLocator; use Symfony\Component\Finder\Finder; use PhpTuf\ComposerStager\Domain\Exception\PreconditionException; use PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface; use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface; /** * Flags errors if the project root or staging area contain symbolic links. Loading @@ -27,13 +29,6 @@ class SymlinkValidator implements PreOperationStageValidatorInterface { use StringTranslationTrait; /** * The module handler service. * * @var \Drupal\Core\Extension\ModuleHandlerInterface */ protected $moduleHandler; /** * The path locator service. * Loading @@ -42,105 +37,73 @@ class SymlinkValidator implements PreOperationStageValidatorInterface { protected $pathLocator; /** * Constructs a SymlinkValidator object. * The Composer Stager precondition that this validator wraps. * * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. * @param \Drupal\package_manager\PathLocator $path_locator * The path locator service. */ public function __construct(ModuleHandlerInterface $module_handler, PathLocator $path_locator) { $this->moduleHandler = $module_handler; $this->pathLocator = $path_locator; } /** * {@inheritdoc} * @var \PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface */ public function validateStagePreOperation(PreOperationStageEvent $event): void { $dir = $this->pathLocator->getProjectRoot(); if ($this->hasLinks($dir)) { $this->addError('Symbolic links were found in the active directory, which are not supported at this time.', $event); } } protected $precondition; /** * Checks if the staging area has any symbolic links. * The path factory service. * * @param \Drupal\package_manager\Event\PreApplyEvent $event * The event object. * @var \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface */ public function preApply(PreApplyEvent $event): void { $dir = $event->getStage()->getStageDirectory(); if ($this->hasLinks($dir)) { $this->addError('Symbolic links were found in the staging area, which are not supported at this time.', $event); } } protected $pathFactory; /** * Recursively checks if a directory has any symbolic links. * * @param string $dir * The path of the directory to check. * The module handler service. * * @return bool * TRUE if the directory contains any symbolic links, FALSE otherwise. * @var \Drupal\Core\Extension\ModuleHandlerInterface */ protected function hasLinks(string $dir): bool { // Finder::filter() explicitly requires a closure, so create one from // ::isLink() so that we can still override it for testing purposes. $is_link = \Closure::fromCallable([$this, 'isLink']); // Finder::hasResults() is more efficient than count() because it will // return early if there is a match. return Finder::create() ->in($dir) ->filter($is_link) ->ignoreUnreadableDirs() ->hasResults(); } protected $moduleHandler; /** * Checks if a file or directory is a symbolic link. * * @param \SplFileInfo $file * A value object for the file or directory. * Constructs a SymlinkValidator object. * * @return bool * TRUE if the file or directory is a symbolic link, FALSE otherwise. * @param \Drupal\package_manager\PathLocator $path_locator * The path locator service. * @param \PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface $precondition * The Composer Stager precondition that this validator wraps. * @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory * The path factory service. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. */ protected function isLink(\SplFileInfo $file): bool { return $file->isLink(); public function __construct(PathLocator $path_locator, CodebaseContainsNoSymlinksInterface $precondition, PathFactoryInterface $path_factory, ModuleHandlerInterface $module_handler) { $this->pathLocator = $path_locator; $this->precondition = $precondition; $this->pathFactory = $path_factory; $this->moduleHandler = $module_handler; } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [ PreCreateEvent::class => 'validateStagePreOperation', StatusCheckEvent::class => 'validateStagePreOperation', PreApplyEvent::class => [ ['validateStagePreOperation'], ['preApply'], ], ]; public function validateStagePreOperation(PreOperationStageEvent $event): void { $active_dir = $this->pathFactory->create($this->pathLocator->getProjectRoot()); // The precondition requires us to pass both an active and stage directory, // so if the stage hasn't been created or claimed yet, use the directory // that contains this file, which contains only a few files and no symlinks, // as the stage directory. The precondition itself doesn't care if the // directory actually exists or not. try { $stage_dir = $event->getStage()->getStageDirectory(); } catch (\LogicException $e) { $stage_dir = __DIR__; } $stage_dir = $this->pathFactory->create($stage_dir); /** * Adds a validation error to a given event. * * @param string $message * The error message. If the Help module is enabled, a link to Package * Manager's help page will be appended. * @param \Drupal\package_manager\Event\PreApplyEvent|\Drupal\package_manager\Event\PreOperationStageEvent $event * The event to add the error to. * * @see package_manager_help() */ protected function addError(string $message, $event): void { try { $this->precondition->assertIsFulfilled($active_dir, $stage_dir); } catch (PreconditionException $e) { $message = $e->getMessage(); // If the Help module is enabled, append a link to Package Manager's help // page. // @see package_manager_help() if ($this->moduleHandler->moduleExists('help')) { $url = Url::fromRoute('help.page', ['name' => 'package_manager']) ->setOption('fragment', 'package-manager-faq-symlinks-found') Loading @@ -151,8 +114,19 @@ class SymlinkValidator implements PreOperationStageValidatorInterface { ':package-manager-help' => $url, ]); } $event->addError([$message]); } } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [ PreCreateEvent::class => 'validateStagePreOperation', PreApplyEvent::class => 'validateStagePreOperation', StatusCheckEvent::class => 'validateStagePreOperation', ]; } } package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php +0 −24 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ namespace Drupal\Tests\package_manager\Kernel\PathExcluder; use Drupal\package_manager_bypass\Beginner; use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; /** Loading @@ -23,29 +22,6 @@ class GitExcluderTest extends PackageManagerKernelTestBase { parent::setUp(); } /** * Tests that unreadable directories are ignored by the event subscriber. */ public function testUnreadableDirectoriesAreIgnored(): void { $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); // Don't mirror the active directory into the virtual staging area, since // the active directory contains an unreadable directory which will cause // an exception. Beginner::setFixturePath(NULL); $this->createStage()->create(); } /** * Tests that Git directories are excluded from staging operations. */ Loading package_manager/tests/src/Kernel/SymlinkValidatorTest.php +51 −132 Original line number Diff line number Diff line Loading @@ -5,9 +5,10 @@ namespace Drupal\Tests\package_manager\Kernel; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Url; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\ValidationResult; use Drupal\package_manager\Validator\SymlinkValidator; use PhpTuf\ComposerStager\Domain\Exception\PreconditionException; use PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface; use Prophecy\Argument; /** * @covers \Drupal\package_manager\Validator\SymlinkValidator Loading @@ -17,144 +18,75 @@ use Drupal\package_manager\Validator\SymlinkValidator; class SymlinkValidatorTest extends PackageManagerKernelTestBase { /** * {@inheritdoc} * The mocked precondition that checks for symlinks. * * @var \PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface|\Prophecy\Prophecy\ObjectProphecy */ public function register(ContainerBuilder $container) { parent::register($container); private $precondition; $container->getDefinition('package_manager.validator.symlink') ->setClass(TestSymlinkValidator::class); /** * {@inheritdoc} */ protected function setUp(): void { $this->precondition = $this->prophesize(CodebaseContainsNoSymlinksInterface::class); parent::setUp(); } /** * Tests that a symlink in the project root raises an error. * {@inheritdoc} */ public function testSymlinkInProjectRoot(): void { $result = ValidationResult::createError([ 'Symbolic links were found in the active directory, which are not supported at this time.', ]); $active_dir = $this->container->get('package_manager.path_locator') ->getProjectRoot(); // @see \Drupal\Tests\package_manager\Kernel\TestSymlinkValidator::isLink() touch($active_dir . '/modules/a_link'); $this->assertStatusCheckResults([$result]); $this->assertResults([$result], PreCreateEvent::class); public function register(ContainerBuilder $container) { parent::register($container); $this->enableModules(['help']); $this->assertStatusCheckResults($this->addHelpTextToResults([$result])); $this->assertResultsWithHelp([$result], PreCreateEvent::class); $container->getDefinition('package_manager.validator.symlink') ->setArgument('$precondition', $this->precondition->reveal()); } /** * Tests that a symlink in the staging area raises an error. * Data provider for ::testSymlink(). * * @dataProvider providerHelpEnabledOrNot * @return array[] * The test cases. */ public function testSymlinkInStagingArea(bool $enable_help): void { $expected_results = [ValidationResult::createError([ 'Symbolic links were found in the staging area, which are not supported at this time.', ]), public function providerSymlink(): array { return [ 'no symlinks' => [FALSE], 'symlinks' => [TRUE], ]; if ($enable_help) { $this->enableModules(['help']); $expected_results = $this->addHelpTextToResults($expected_results); } $stage = $this->createStage(); $stage->create(); $stage->require(['composer/semver:^3']); // @see \Drupal\Tests\package_manager\Kernel\TestSymlinkValidator::isLink() touch($stage->getStageDirectory() . '/modules/a_link'); try { $stage->apply(); $this->fail('Expected a validation error.'); } catch (StageValidationException $e) { $this->assertValidationResultsEqual($expected_results, $e->getResults()); } } /** * Tests that symlinks in the project root and staging area raise an error. * Tests that the validator invokes Composer Stager's symlink precondition. * * @param bool $symlinks_exist * Whether or not the precondition will detect symlinks. * * @dataProvider providerHelpEnabledOrNot * @dataProvider providerSymlink */ public function testSymlinkInProjectRootAndStagingArea(bool $enable_help): void { public function testSymlink(bool $symlinks_exist): void { $arguments = Argument::cetera(); // The precondition should always be invoked. $this->precondition->assertIsFulfilled($arguments)->shouldBeCalled(); if ($symlinks_exist) { $exception = new PreconditionException($this->precondition->reveal(), 'Symlinks were found.'); $this->precondition->assertIsFulfilled($arguments)->willThrow($exception); $expected_results = [ ValidationResult::createError([ 'Symbolic links were found in the active directory, which are not supported at this time.', ]), ValidationResult::createError([ 'Symbolic links were found in the staging area, which are not supported at this time.', $exception->getMessage(), ]), ]; if ($enable_help) { $this->enableModules(['help']); $expected_results = $this->addHelpTextToResults($expected_results); } $stage = $this->createStage(); $stage->create(); $stage->require(['composer/semver:^3']); $active_dir = $this->container->get('package_manager.path_locator') ->getProjectRoot(); // @see \Drupal\Tests\package_manager\Kernel\TestSymlinkValidator::isLink() touch($active_dir . '/modules/a_link'); touch($stage->getStageDirectory() . '/modules/a_link'); try { $stage->apply(); $this->fail('Expected a validation error.'); } catch (StageValidationException $e) { $this->assertValidationResultsEqual($expected_results, $e->getResults()); } else { $expected_results = []; } /** * Data provider for test methods that test with and without the Help module. * * @return array[] * The test cases. */ public function providerHelpEnabledOrNot() { return [ 'help_module_enabled' => [TRUE], 'help_module_disabled' => [FALSE], ]; } /** * Asserts that a set of validation results link to the Package Manager help. * * @param \Drupal\package_manager\ValidationResult[] $expected_results * The expected validation results. * @param string|null $event_class * (optional) The class of the event which should return the results. Must * be passed if $expected_results is not empty. */ private function assertResultsWithHelp(array $expected_results, string $event_class = NULL): void { $expected_results = $this->addHelpTextToResults($expected_results); $this->assertStatusCheckResults($expected_results); $this->assertResults($expected_results, $event_class); } $this->assertResults($expected_results, PreCreateEvent::class); $this->enableModules(['help']); /** * Adds help text to results messages. * * @param \Drupal\package_manager\ValidationResult[] $results * The expected validation results. * * @return array * The new results. */ public function addHelpTextToResults(array $results): array { $url = Url::fromRoute('help.page', ['name' => 'package_manager']) ->setOption('fragment', 'package-manager-faq-symlinks-found') ->toString(); Loading @@ -164,25 +96,12 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase { $map = function (string $message) use ($url): string { return $message . ' See <a href="' . $url . '">the help page</a> for information on how to resolve the problem.'; }; foreach ($results as $index => $result) { foreach ($expected_results as $index => $result) { $messages = array_map($map, $result->getMessages()); $results[$index] = ValidationResult::createError($messages); } return $results; } $expected_results[$index] = ValidationResult::createError($messages); } /** * A test validator that considers anything named 'a_link' to be a symlink. */ class TestSymlinkValidator extends SymlinkValidator { /** * {@inheritdoc} */ protected function isLink(\SplFileInfo $file): bool { return $file->getBasename() === 'a_link' || parent::isLink($file); $this->assertStatusCheckResults($expected_results); $this->assertResults($expected_results, PreCreateEvent::class); } } Loading
package_manager/package_manager.services.yml +3 −1 Original line number Diff line number Diff line Loading @@ -105,8 +105,10 @@ services: package_manager.validator.symlink: class: Drupal\package_manager\Validator\SymlinkValidator arguments: - '@module_handler' - '@package_manager.path_locator' - '@PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface' - '@PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface' - '@module_handler' tags: - { name: event_subscriber } package_manager.validator.duplicate_info_file: Loading
package_manager/src/PathExcluder/GitExcluder.php +1 −2 Original line number Diff line number Diff line Loading @@ -56,8 +56,7 @@ final class GitExcluder implements EventSubscriberInterface { ->directories() ->name('.git') ->ignoreVCS(FALSE) ->ignoreDotFiles(FALSE) ->ignoreUnreadableDirs(); ->ignoreDotFiles(FALSE); $paths = []; foreach ($finder as $git_directory) { Loading
package_manager/src/Validator/SymlinkValidator.php +66 −92 Original line number Diff line number Diff line Loading @@ -10,7 +10,9 @@ use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreOperationStageEvent; use Drupal\package_manager\Event\StatusCheckEvent; use Drupal\package_manager\PathLocator; use Symfony\Component\Finder\Finder; use PhpTuf\ComposerStager\Domain\Exception\PreconditionException; use PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface; use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface; /** * Flags errors if the project root or staging area contain symbolic links. Loading @@ -27,13 +29,6 @@ class SymlinkValidator implements PreOperationStageValidatorInterface { use StringTranslationTrait; /** * The module handler service. * * @var \Drupal\Core\Extension\ModuleHandlerInterface */ protected $moduleHandler; /** * The path locator service. * Loading @@ -42,105 +37,73 @@ class SymlinkValidator implements PreOperationStageValidatorInterface { protected $pathLocator; /** * Constructs a SymlinkValidator object. * The Composer Stager precondition that this validator wraps. * * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. * @param \Drupal\package_manager\PathLocator $path_locator * The path locator service. */ public function __construct(ModuleHandlerInterface $module_handler, PathLocator $path_locator) { $this->moduleHandler = $module_handler; $this->pathLocator = $path_locator; } /** * {@inheritdoc} * @var \PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface */ public function validateStagePreOperation(PreOperationStageEvent $event): void { $dir = $this->pathLocator->getProjectRoot(); if ($this->hasLinks($dir)) { $this->addError('Symbolic links were found in the active directory, which are not supported at this time.', $event); } } protected $precondition; /** * Checks if the staging area has any symbolic links. * The path factory service. * * @param \Drupal\package_manager\Event\PreApplyEvent $event * The event object. * @var \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface */ public function preApply(PreApplyEvent $event): void { $dir = $event->getStage()->getStageDirectory(); if ($this->hasLinks($dir)) { $this->addError('Symbolic links were found in the staging area, which are not supported at this time.', $event); } } protected $pathFactory; /** * Recursively checks if a directory has any symbolic links. * * @param string $dir * The path of the directory to check. * The module handler service. * * @return bool * TRUE if the directory contains any symbolic links, FALSE otherwise. * @var \Drupal\Core\Extension\ModuleHandlerInterface */ protected function hasLinks(string $dir): bool { // Finder::filter() explicitly requires a closure, so create one from // ::isLink() so that we can still override it for testing purposes. $is_link = \Closure::fromCallable([$this, 'isLink']); // Finder::hasResults() is more efficient than count() because it will // return early if there is a match. return Finder::create() ->in($dir) ->filter($is_link) ->ignoreUnreadableDirs() ->hasResults(); } protected $moduleHandler; /** * Checks if a file or directory is a symbolic link. * * @param \SplFileInfo $file * A value object for the file or directory. * Constructs a SymlinkValidator object. * * @return bool * TRUE if the file or directory is a symbolic link, FALSE otherwise. * @param \Drupal\package_manager\PathLocator $path_locator * The path locator service. * @param \PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface $precondition * The Composer Stager precondition that this validator wraps. * @param \PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface $path_factory * The path factory service. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. */ protected function isLink(\SplFileInfo $file): bool { return $file->isLink(); public function __construct(PathLocator $path_locator, CodebaseContainsNoSymlinksInterface $precondition, PathFactoryInterface $path_factory, ModuleHandlerInterface $module_handler) { $this->pathLocator = $path_locator; $this->precondition = $precondition; $this->pathFactory = $path_factory; $this->moduleHandler = $module_handler; } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [ PreCreateEvent::class => 'validateStagePreOperation', StatusCheckEvent::class => 'validateStagePreOperation', PreApplyEvent::class => [ ['validateStagePreOperation'], ['preApply'], ], ]; public function validateStagePreOperation(PreOperationStageEvent $event): void { $active_dir = $this->pathFactory->create($this->pathLocator->getProjectRoot()); // The precondition requires us to pass both an active and stage directory, // so if the stage hasn't been created or claimed yet, use the directory // that contains this file, which contains only a few files and no symlinks, // as the stage directory. The precondition itself doesn't care if the // directory actually exists or not. try { $stage_dir = $event->getStage()->getStageDirectory(); } catch (\LogicException $e) { $stage_dir = __DIR__; } $stage_dir = $this->pathFactory->create($stage_dir); /** * Adds a validation error to a given event. * * @param string $message * The error message. If the Help module is enabled, a link to Package * Manager's help page will be appended. * @param \Drupal\package_manager\Event\PreApplyEvent|\Drupal\package_manager\Event\PreOperationStageEvent $event * The event to add the error to. * * @see package_manager_help() */ protected function addError(string $message, $event): void { try { $this->precondition->assertIsFulfilled($active_dir, $stage_dir); } catch (PreconditionException $e) { $message = $e->getMessage(); // If the Help module is enabled, append a link to Package Manager's help // page. // @see package_manager_help() if ($this->moduleHandler->moduleExists('help')) { $url = Url::fromRoute('help.page', ['name' => 'package_manager']) ->setOption('fragment', 'package-manager-faq-symlinks-found') Loading @@ -151,8 +114,19 @@ class SymlinkValidator implements PreOperationStageValidatorInterface { ':package-manager-help' => $url, ]); } $event->addError([$message]); } } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return [ PreCreateEvent::class => 'validateStagePreOperation', PreApplyEvent::class => 'validateStagePreOperation', StatusCheckEvent::class => 'validateStagePreOperation', ]; } }
package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php +0 −24 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ namespace Drupal\Tests\package_manager\Kernel\PathExcluder; use Drupal\package_manager_bypass\Beginner; use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; /** Loading @@ -23,29 +22,6 @@ class GitExcluderTest extends PackageManagerKernelTestBase { parent::setUp(); } /** * Tests that unreadable directories are ignored by the event subscriber. */ public function testUnreadableDirectoriesAreIgnored(): void { $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); // Don't mirror the active directory into the virtual staging area, since // the active directory contains an unreadable directory which will cause // an exception. Beginner::setFixturePath(NULL); $this->createStage()->create(); } /** * Tests that Git directories are excluded from staging operations. */ Loading
package_manager/tests/src/Kernel/SymlinkValidatorTest.php +51 −132 Original line number Diff line number Diff line Loading @@ -5,9 +5,10 @@ namespace Drupal\Tests\package_manager\Kernel; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Url; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\ValidationResult; use Drupal\package_manager\Validator\SymlinkValidator; use PhpTuf\ComposerStager\Domain\Exception\PreconditionException; use PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface; use Prophecy\Argument; /** * @covers \Drupal\package_manager\Validator\SymlinkValidator Loading @@ -17,144 +18,75 @@ use Drupal\package_manager\Validator\SymlinkValidator; class SymlinkValidatorTest extends PackageManagerKernelTestBase { /** * {@inheritdoc} * The mocked precondition that checks for symlinks. * * @var \PhpTuf\ComposerStager\Domain\Service\Precondition\CodebaseContainsNoSymlinksInterface|\Prophecy\Prophecy\ObjectProphecy */ public function register(ContainerBuilder $container) { parent::register($container); private $precondition; $container->getDefinition('package_manager.validator.symlink') ->setClass(TestSymlinkValidator::class); /** * {@inheritdoc} */ protected function setUp(): void { $this->precondition = $this->prophesize(CodebaseContainsNoSymlinksInterface::class); parent::setUp(); } /** * Tests that a symlink in the project root raises an error. * {@inheritdoc} */ public function testSymlinkInProjectRoot(): void { $result = ValidationResult::createError([ 'Symbolic links were found in the active directory, which are not supported at this time.', ]); $active_dir = $this->container->get('package_manager.path_locator') ->getProjectRoot(); // @see \Drupal\Tests\package_manager\Kernel\TestSymlinkValidator::isLink() touch($active_dir . '/modules/a_link'); $this->assertStatusCheckResults([$result]); $this->assertResults([$result], PreCreateEvent::class); public function register(ContainerBuilder $container) { parent::register($container); $this->enableModules(['help']); $this->assertStatusCheckResults($this->addHelpTextToResults([$result])); $this->assertResultsWithHelp([$result], PreCreateEvent::class); $container->getDefinition('package_manager.validator.symlink') ->setArgument('$precondition', $this->precondition->reveal()); } /** * Tests that a symlink in the staging area raises an error. * Data provider for ::testSymlink(). * * @dataProvider providerHelpEnabledOrNot * @return array[] * The test cases. */ public function testSymlinkInStagingArea(bool $enable_help): void { $expected_results = [ValidationResult::createError([ 'Symbolic links were found in the staging area, which are not supported at this time.', ]), public function providerSymlink(): array { return [ 'no symlinks' => [FALSE], 'symlinks' => [TRUE], ]; if ($enable_help) { $this->enableModules(['help']); $expected_results = $this->addHelpTextToResults($expected_results); } $stage = $this->createStage(); $stage->create(); $stage->require(['composer/semver:^3']); // @see \Drupal\Tests\package_manager\Kernel\TestSymlinkValidator::isLink() touch($stage->getStageDirectory() . '/modules/a_link'); try { $stage->apply(); $this->fail('Expected a validation error.'); } catch (StageValidationException $e) { $this->assertValidationResultsEqual($expected_results, $e->getResults()); } } /** * Tests that symlinks in the project root and staging area raise an error. * Tests that the validator invokes Composer Stager's symlink precondition. * * @param bool $symlinks_exist * Whether or not the precondition will detect symlinks. * * @dataProvider providerHelpEnabledOrNot * @dataProvider providerSymlink */ public function testSymlinkInProjectRootAndStagingArea(bool $enable_help): void { public function testSymlink(bool $symlinks_exist): void { $arguments = Argument::cetera(); // The precondition should always be invoked. $this->precondition->assertIsFulfilled($arguments)->shouldBeCalled(); if ($symlinks_exist) { $exception = new PreconditionException($this->precondition->reveal(), 'Symlinks were found.'); $this->precondition->assertIsFulfilled($arguments)->willThrow($exception); $expected_results = [ ValidationResult::createError([ 'Symbolic links were found in the active directory, which are not supported at this time.', ]), ValidationResult::createError([ 'Symbolic links were found in the staging area, which are not supported at this time.', $exception->getMessage(), ]), ]; if ($enable_help) { $this->enableModules(['help']); $expected_results = $this->addHelpTextToResults($expected_results); } $stage = $this->createStage(); $stage->create(); $stage->require(['composer/semver:^3']); $active_dir = $this->container->get('package_manager.path_locator') ->getProjectRoot(); // @see \Drupal\Tests\package_manager\Kernel\TestSymlinkValidator::isLink() touch($active_dir . '/modules/a_link'); touch($stage->getStageDirectory() . '/modules/a_link'); try { $stage->apply(); $this->fail('Expected a validation error.'); } catch (StageValidationException $e) { $this->assertValidationResultsEqual($expected_results, $e->getResults()); } else { $expected_results = []; } /** * Data provider for test methods that test with and without the Help module. * * @return array[] * The test cases. */ public function providerHelpEnabledOrNot() { return [ 'help_module_enabled' => [TRUE], 'help_module_disabled' => [FALSE], ]; } /** * Asserts that a set of validation results link to the Package Manager help. * * @param \Drupal\package_manager\ValidationResult[] $expected_results * The expected validation results. * @param string|null $event_class * (optional) The class of the event which should return the results. Must * be passed if $expected_results is not empty. */ private function assertResultsWithHelp(array $expected_results, string $event_class = NULL): void { $expected_results = $this->addHelpTextToResults($expected_results); $this->assertStatusCheckResults($expected_results); $this->assertResults($expected_results, $event_class); } $this->assertResults($expected_results, PreCreateEvent::class); $this->enableModules(['help']); /** * Adds help text to results messages. * * @param \Drupal\package_manager\ValidationResult[] $results * The expected validation results. * * @return array * The new results. */ public function addHelpTextToResults(array $results): array { $url = Url::fromRoute('help.page', ['name' => 'package_manager']) ->setOption('fragment', 'package-manager-faq-symlinks-found') ->toString(); Loading @@ -164,25 +96,12 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase { $map = function (string $message) use ($url): string { return $message . ' See <a href="' . $url . '">the help page</a> for information on how to resolve the problem.'; }; foreach ($results as $index => $result) { foreach ($expected_results as $index => $result) { $messages = array_map($map, $result->getMessages()); $results[$index] = ValidationResult::createError($messages); } return $results; } $expected_results[$index] = ValidationResult::createError($messages); } /** * A test validator that considers anything named 'a_link' to be a symlink. */ class TestSymlinkValidator extends SymlinkValidator { /** * {@inheritdoc} */ protected function isLink(\SplFileInfo $file): bool { return $file->getBasename() === 'a_link' || parent::isLink($file); $this->assertStatusCheckResults($expected_results); $this->assertResults($expected_results, PreCreateEvent::class); } }