From de0c72c82fe828f54f9f2931bac2b193834cdb13 Mon Sep 17 00:00:00 2001
From: Adam G-H <32250-phenaproxima@users.noreply.drupalcode.org>
Date: Sun, 4 Jun 2023 21:06:15 +0000
Subject: [PATCH] Issue #3363938 by phenaproxima, tedbow: Package Manager
 should ignore default.settings.php and default.services.yml

---
 automatic_updates.services.yml                |   5 -
 .../SiteConfigurationExcluder.php             |  99 ++++-
 .../ScaffoldFilePermissionsValidator.php      | 134 -------
 tests/src/Build/CoreUpdateTest.php            |  23 +-
 .../ScaffoldFilePermissionsValidatorTest.php  | 355 ------------------
 .../Kernel/StatusCheck/StatusCheckerTest.php  |  16 +-
 6 files changed, 110 insertions(+), 522 deletions(-)
 delete mode 100644 src/Validator/ScaffoldFilePermissionsValidator.php
 delete mode 100644 tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php

diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml
index 8e0a83cc13..9b29fe9166 100644
--- a/automatic_updates.services.yml
+++ b/automatic_updates.services.yml
@@ -79,11 +79,6 @@ services:
     tags:
       - { name: event_subscriber }
   Drupal\automatic_updates\Validator\VersionPolicyValidator: '@automatic_updates.validator.version_policy'
-  automatic_updates.validator.scaffold_file_permissions:
-    class: Drupal\automatic_updates\Validator\ScaffoldFilePermissionsValidator
-    tags:
-      - { name: event_subscriber }
-  Drupal\automatic_updates\Validator\ScaffoldFilePermissionsValidator: '@automatic_updates.validator.scaffold_file_permissions'
   automatic_updates.validator.cron_server:
     class: Drupal\automatic_updates\Validator\CronServerValidator
     tags:
diff --git a/package_manager/src/PathExcluder/SiteConfigurationExcluder.php b/package_manager/src/PathExcluder/SiteConfigurationExcluder.php
index 7a4df3b2d0..1745d92f4e 100644
--- a/package_manager/src/PathExcluder/SiteConfigurationExcluder.php
+++ b/package_manager/src/PathExcluder/SiteConfigurationExcluder.php
@@ -4,7 +4,12 @@ declare(strict_types = 1);
 
 namespace Drupal\package_manager\PathExcluder;
 
+use Drupal\Core\File\Exception\FileException;
+use Drupal\Core\File\FileSystemInterface;
 use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
+use Drupal\package_manager\Event\PostCreateEvent;
+use Drupal\package_manager\Event\PreApplyEvent;
+use Drupal\package_manager\PathLocator;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
@@ -22,8 +27,16 @@ class SiteConfigurationExcluder implements EventSubscriberInterface {
    *
    * @param string $sitePath
    *   The current site path, relative to the Drupal root.
+   * @param \Drupal\package_manager\PathLocator $pathLocator
+   *   The path locator service.
+   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
+   *   The file system service.
    */
-  public function __construct(protected string $sitePath) {}
+  public function __construct(
+    protected string $sitePath,
+    private readonly PathLocator $pathLocator,
+    private readonly FileSystemInterface $fileSystem
+  ) {}
 
   /**
    * Excludes site configuration files from stage operations.
@@ -32,8 +45,11 @@ class SiteConfigurationExcluder implements EventSubscriberInterface {
    *   The event object.
    */
   public function excludeSiteConfiguration(CollectPathsToExcludeEvent $event): void {
-    // Site configuration files are always excluded relative to the web root.
-    $paths = [];
+    // These two files are never relevant to existing sites.
+    $paths = [
+      'sites/default/default.settings.php',
+      'sites/default/default.services.yml',
+    ];
 
     // Exclude site-specific settings files, which are always in the web root.
     // By default, Drupal core will always try to write-protect these files.
@@ -46,15 +62,92 @@ class SiteConfigurationExcluder implements EventSubscriberInterface {
       $paths[] = $this->sitePath . '/' . $settings_file;
       $paths[] = 'sites/default/' . $settings_file;
     }
+    // Site configuration files are always excluded relative to the web root.
     $event->addPathsRelativeToWebRoot($paths);
   }
 
+  /**
+   * Makes the staged `sites/default` directory world-writable.
+   *
+   * This is done to allow the core scaffold plugin to make changes in
+   * `sites/default`, if necessary, without breaking if `sites/default` is not
+   * writable (this can happen because rsync preserves directory permissions,
+   * and Drupal will try to harden the site directory's permissions as much as
+   * possible). We specifically exclude the `default.settings.php` and
+   * `default.services.yml` files from Package Manager operations, so we want to
+   * allow the scaffold plugin to make whatever changes it wants to those files
+   * in the stage directory.
+   *
+   * @param \Drupal\package_manager\Event\PostCreateEvent $event
+   *   The event being handled.
+   *
+   * @see ::excludeSiteConfiguration()
+   */
+  public function makeDefaultSiteDirectoryWritable(PostCreateEvent $event): void {
+    $dir = $this->getDefaultSiteDirectoryPath($event->stage->getStageDirectory());
+    // If the directory doesn't even exist, there's nothing to do here.
+    if (!is_dir($dir)) {
+      return;
+    }
+    if (!$this->fileSystem->chmod($dir, 0777)) {
+      throw new FileException("Could not change permissions on '$dir'.");
+    }
+  }
+
+  /**
+   * Makes `sites/default` permissions the same in live and stage directories.
+   *
+   * @param \Drupal\package_manager\Event\PreApplyEvent $event
+   *   The event being handled.
+   *
+   * @throws \Drupal\Core\File\Exception\FileException
+   *   If the permissions of the live `sites/default` cannot be determined, or
+   *   cannot be changed on the staged `sites/default`.
+   */
+  public function syncDefaultSiteDirectoryPermissions(PreApplyEvent $event): void {
+    $staged_dir = $this->getDefaultSiteDirectoryPath($event->stage->getStageDirectory());
+    // If the directory doesn't even exist, there's nothing to do here.
+    if (!is_dir($staged_dir)) {
+      return;
+    }
+    $live_dir = $this->getDefaultSiteDirectoryPath($this->pathLocator->getProjectRoot());
+
+    $permissions = fileperms($live_dir);
+    if ($permissions === FALSE) {
+      throw new FileException("Could not determine permissions for '$live_dir'.");
+    }
+
+    if (!$this->fileSystem->chmod($staged_dir, $permissions)) {
+      throw new FileException("Could not change permissions on '$staged_dir'.");
+    }
+  }
+
+  /**
+   * Returns the full path to `sites/default`, relative to a root directory.
+   *
+   * @param string $root_dir
+   *   The root directory.
+   *
+   * @return string
+   *   The full path to `sites/default` within the given root directory.
+   */
+  private function getDefaultSiteDirectoryPath(string $root_dir): string {
+    $dir = [$root_dir];
+    $web_root = $this->pathLocator->getWebRoot();
+    if ($web_root) {
+      $dir[] = $web_root;
+    }
+    return implode(DIRECTORY_SEPARATOR, [...$dir, 'sites', 'default']);
+  }
+
   /**
    * {@inheritdoc}
    */
   public static function getSubscribedEvents(): array {
     return [
       CollectPathsToExcludeEvent::class => 'excludeSiteConfiguration',
+      PostCreateEvent::class => 'makeDefaultSiteDirectoryWritable',
+      PreApplyEvent::class => 'syncDefaultSiteDirectoryPermissions',
     ];
   }
 
diff --git a/src/Validator/ScaffoldFilePermissionsValidator.php b/src/Validator/ScaffoldFilePermissionsValidator.php
deleted file mode 100644
index e8d3b8639b..0000000000
--- a/src/Validator/ScaffoldFilePermissionsValidator.php
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-
-declare(strict_types = 1);
-
-namespace Drupal\automatic_updates\Validator;
-
-use Drupal\automatic_updates\UpdateStage;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\package_manager\ComposerInspector;
-use Drupal\package_manager\Event\PreApplyEvent;
-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\EventDispatcher\EventSubscriberInterface;
-
-/**
- * Validates that scaffold files have appropriate permissions.
- *
- * @internal
- *   This is an internal part of Automatic Updates and may be changed or removed
- *   at any time without warning. External code should not interact with this
- *   class.
- */
-final class ScaffoldFilePermissionsValidator implements EventSubscriberInterface {
-
-  use StringTranslationTrait;
-
-  /**
-   * Constructs a ScaffoldFilePermissionsValidator object.
-   *
-   * @param \Drupal\package_manager\ComposerInspector $composerInspector
-   *   The Composer inspector service.
-   * @param \Drupal\package_manager\PathLocator $pathLocator
-   *   The path locator service.
-   */
-  public function __construct(
-    private readonly ComposerInspector $composerInspector,
-    private readonly PathLocator $pathLocator,
-  ) {}
-
-  /**
-   * Validates that scaffold files have the appropriate permissions.
-   */
-  public function validate(PreOperationStageEvent $event): void {
-    // We only want to do this check if the stage belongs to Automatic Updates.
-    if (!$event->stage instanceof UpdateStage) {
-      return;
-    }
-    $paths = [];
-
-    // Figure out the absolute path of `sites/default`.
-    $site_dir = $this->pathLocator->getProjectRoot();
-    $web_root = $this->pathLocator->getWebRoot();
-    if ($web_root) {
-      $site_dir .= '/' . $web_root;
-    }
-    $site_dir .= '/sites/default';
-
-    $active_scaffold_files = $this->getDefaultSiteFilesFromScaffold($this->pathLocator->getProjectRoot());
-
-    // If the active directory and stage directory have different files
-    // scaffolded into `sites/default` (i.e., files were added, renamed, or
-    // deleted), the site directory itself must be writable for the changes to
-    // be applied.
-    if ($event instanceof PreApplyEvent) {
-      $staged_scaffold_files = $this->getDefaultSiteFilesFromScaffold($event->stage->getStageDirectory());
-
-      if ($active_scaffold_files !== $staged_scaffold_files) {
-        $paths[] = $site_dir;
-      }
-    }
-    // The scaffolded files themselves must be writable, so that any changes to
-    // them in the stage directory can be synced back to the active directory.
-    foreach ($active_scaffold_files as $scaffold_file) {
-      $paths[] = $site_dir . '/' . $scaffold_file;
-    }
-
-    // Flag messages about anything in $paths which exists, but isn't writable.
-    $non_writable_files = array_filter($paths, function (string $path): bool {
-      return file_exists($path) && !is_writable($path);
-    });
-    if ($non_writable_files) {
-      // Re-key the messages in order to prevent false negative comparisons in
-      // tests.
-      $non_writable_files = array_map($this->t(...), array_values($non_writable_files));
-      $event->addError($non_writable_files, $this->t('The following paths must be writable in order to update default site configuration files.'));
-    }
-  }
-
-  /**
-   * Returns the list of file names scaffolded into `sites/default`.
-   *
-   * @param string $working_dir
-   *   The directory in which to run Composer.
-   *
-   * @return string[]
-   *   The names of files that are scaffolded into `sites/default`, stripped
-   *   of the preceding path. For example,
-   *   `[web-root]/sites/default/default.settings.php` will be
-   *   `default.settings.php`. Will be sorted alphabetically. If the target
-   *   directory doesn't have the `drupal/core` package installed, the returned
-   *   array will be empty.
-   */
-  protected function getDefaultSiteFilesFromScaffold(string $working_dir): array {
-    $installed = $this->composerInspector->getInstalledPackagesList($working_dir);
-
-    if (isset($installed['drupal/core'])) {
-      // We expect Drupal core to provide a list of scaffold files.
-      $files = (array) json_decode($this->composerInspector->getConfig('extra.drupal-scaffold.file-mapping', $installed['drupal/core']->path . '/composer.json'));
-    }
-    else {
-      $files = [];
-    }
-    $files = array_keys($files);
-    $files = preg_grep('/sites\/default\//', $files);
-    $files = array_map('basename', $files);
-    sort($files);
-
-    return $files;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getSubscribedEvents(): array {
-    return [
-      PreCreateEvent::class => 'validate',
-      PreApplyEvent::class => 'validate',
-      StatusCheckEvent::class => 'validate',
-    ];
-  }
-
-}
diff --git a/tests/src/Build/CoreUpdateTest.php b/tests/src/Build/CoreUpdateTest.php
index 76073cbf0d..8b3c3d4877 100644
--- a/tests/src/Build/CoreUpdateTest.php
+++ b/tests/src/Build/CoreUpdateTest.php
@@ -89,9 +89,6 @@ class CoreUpdateTest extends UpdateTestBase {
 
     // Ensure that Drupal has write-protected the site directory.
     $this->assertDirectoryIsNotWritable($this->getWebRoot() . '/sites/default');
-    // @todo Remove this when default.settings.php and default.services.yml are
-    //   ignored by Package Manager in  in https://drupal.org/i/3363938.
-    $this->assertTrue(chmod($this->getWebRoot() . '/sites/default', 0777));
   }
 
   /**
@@ -185,9 +182,6 @@ class CoreUpdateTest extends UpdateTestBase {
     $this->installModules(['dblog']);
 
     $this->visit('/admin/reports/status');
-    // @todo Remove this line when default.settings.php and default.services.yml
-    //   are ignored by Package Manager in  in https://drupal.org/i/3363938.
-    $this->assertTrue(chmod($this->getWebRoot() . '/sites/default', 0777));
     $mink = $this->getMink();
     $page = $mink->getSession()->getPage();
     $assert_session = $mink->assertSession();
@@ -239,9 +233,6 @@ class CoreUpdateTest extends UpdateTestBase {
     $assert_session = $mink->assertSession();
     $this->coreUpdateTillUpdateReady($page);
     $this->visit('/admin/reports/status');
-    // @todo Remove this line when default.settings.php and default.services.yml
-    //   are ignored by Package Manager in https://drupal.org/i/3363938.
-    $this->assertTrue(chmod($this->getWebRoot() . '/sites/default', 0777));
     $assert_session->pageTextContains('Your site is ready for automatic updates.');
     $page->clickLink('Run cron');
     $this->assertUpdateSuccessful('9.8.1');
@@ -309,12 +300,16 @@ class CoreUpdateTest extends UpdateTestBase {
     foreach (['default.settings.php', 'default.services.yml'] as $file) {
       $file = $web_root . '/sites/default/' . $file;
       $this->assertFileIsReadable($file);
-      $this->assertStringContainsString("# This is part of Drupal $expected_version.", file_get_contents($file));
+      // The `default.settings.php` and `default.services.yml` files are
+      // explicitly excluded from Package Manager operations, since they are not
+      // relevant to existing sites. Therefore, ensure that the changes we made
+      // to the original (scaffold) versions of the files are not present in
+      // the updated site.
+      // @see \Drupal\package_manager\PathExcluder\SiteConfigurationExcluder()
+      // @see \Drupal\Tests\package_manager\Build\TemplateProjectTestBase::setUpstreamCoreVersion()
+      $this->assertStringNotContainsString("# This is part of Drupal $expected_version.", file_get_contents($file));
     }
-    // @todo Restore this line when default.settings.php and
-    //   default.services.yml are ignored by Package Manager in
-    //   https://drupal.org/i/3363938.
-    // $this->assertDirectoryIsNotWritable("$web_root/sites/default");
+    $this->assertDirectoryIsNotWritable("$web_root/sites/default");
 
     $info = $this->runComposer('composer info --self --format json', 'project', TRUE);
 
diff --git a/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php b/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php
deleted file mode 100644
index 9236e3eca5..0000000000
--- a/tests/src/Kernel/StatusCheck/ScaffoldFilePermissionsValidatorTest.php
+++ /dev/null
@@ -1,355 +0,0 @@
-<?php
-
-declare(strict_types = 1);
-
-namespace Drupal\Tests\automatic_updates\Kernel\StatusCheck;
-
-use Drupal\automatic_updates\UpdateStage;
-use Drupal\fixture_manipulator\ActiveFixtureManipulator;
-use Drupal\package_manager\Exception\ApplyFailedException;
-use Drupal\package_manager\Exception\StageEventException;
-use Drupal\package_manager\PathLocator;
-use Drupal\package_manager\ValidationResult;
-use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
-
-/**
- * @covers \Drupal\automatic_updates\Validator\ScaffoldFilePermissionsValidator
- * @group automatic_updates
- * @internal
- */
-class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = ['automatic_updates'];
-
-  /**
-   * The active directory of the test project.
-   *
-   * @var string
-   */
-  private $activeDir;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    $this->activeDir = $this->container->get(PathLocator::class)
-      ->getProjectRoot();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function assertValidationResultsEqual(array $expected_results, array $actual_results, ?PathLocator $path_locator = NULL, ?string $stage_dir = NULL): void {
-    $map = function (string $path): string {
-      return $this->activeDir . '/' . $path;
-    };
-    foreach ($expected_results as $i => $result) {
-      // Prepend the active directory to every path listed in the error result,
-      // and add the expected summary.
-      $messages = array_map($map, $result->messages);
-      $messages = array_map(t(...), $messages);
-      $expected_results[$i] = ValidationResult::createError($messages, t('The following paths must be writable in order to update default site configuration files.'));
-    }
-    parent::assertValidationResultsEqual($expected_results, $actual_results, $path_locator);
-  }
-
-  /**
-   * Write-protects a set of paths in the active directory.
-   *
-   * @param string[] $paths
-   *   The paths to write-protect, relative to the active directory.
-   */
-  private function writeProtect(array $paths): void {
-    foreach ($paths as $path) {
-      $path = $this->activeDir . '/' . $path;
-      chmod($path, 0500);
-      $this->assertFileIsNotWritable($path, "Failed to write-protect $path.");
-    }
-  }
-
-  /**
-   * Data provider for testPermissionsBeforeStart().
-   *
-   * @return mixed[][]
-   *   The test cases.
-   */
-  public function providerPermissionsBeforeStart(): array {
-    return [
-      'write-protected scaffold file, writable site directory' => [
-        ['sites/default/default.settings.php'],
-        [
-          ValidationResult::createError([t('sites/default/default.settings.php')]),
-        ],
-      ],
-      // Whether the site directory is write-protected only matters during
-      // pre-apply, because it only presents a problem if scaffold files have
-      // been added or removed in the stage directory. Which is a condition we
-      // can only detect during pre-apply.
-      'write-protected scaffold file and site directory' => [
-        [
-          'sites/default/default.settings.php',
-          'sites/default',
-        ],
-        [
-          ValidationResult::createError([t('sites/default/default.settings.php')]),
-        ],
-      ],
-      'write-protected site directory' => [
-        ['sites/default'],
-        [],
-      ],
-    ];
-  }
-
-  /**
-   * Tests that scaffold file permissions are checked before an update begins.
-   *
-   * @param string[] $write_protected_paths
-   *   A list of paths, relative to the project root, which should be write
-   *   protected before staged changes are applied.
-   * @param \Drupal\package_manager\ValidationResult[] $expected_results
-   *   The expected validation results, if any.
-   *
-   * @dataProvider providerPermissionsBeforeStart
-   */
-  public function testPermissionsBeforeStart(array $write_protected_paths, array $expected_results): void {
-    $this->writeProtect($write_protected_paths);
-    $this->assertCheckerResultsFromManager($expected_results, TRUE);
-
-    try {
-      $this->container->get(UpdateStage::class)
-        ->begin(['drupal' => '9.8.1']);
-
-      // If no exception was thrown, ensure that we weren't expecting an error.
-      $this->assertEmpty($expected_results);
-    }
-    catch (StageEventException $e) {
-      $this->assertExpectedResultsFromException($expected_results, $e);
-    }
-  }
-
-  /**
-   * Data provider for testScaffoldFilesChanged().
-   *
-   * @return mixed[][]
-   *   The test cases.
-   */
-  public function providerScaffoldFilesChanged(): array {
-    return [
-      // If no scaffold files are changed, it doesn't matter if the site
-      // directory is writable.
-      'no scaffold changes, site directory not writable' => [
-        ['sites/default'],
-        [],
-        [],
-        [],
-      ],
-      'no scaffold changes, site directory writable' => [
-        [],
-        [],
-        [],
-        [],
-      ],
-      // If scaffold files are added or deleted in the site directory, the site
-      // directory must be writable.
-      'new scaffold file added to non-writable site directory' => [
-        ['sites/default'],
-        [],
-        [
-          '[web-root]/sites/default/new.txt' => '',
-        ],
-        [
-          ValidationResult::createError([t('sites/default')]),
-        ],
-      ],
-      'new scaffold file added to writable site directory' => [
-        [],
-        [],
-        [
-          '[web-root]/sites/default/new.txt' => '',
-        ],
-        [],
-      ],
-      'writable scaffold file removed from non-writable site directory' => [
-        ['sites/default'],
-        [
-          '[web-root]/sites/default/deleted.txt' => '',
-        ],
-        [],
-        [
-          ValidationResult::createError([t('sites/default')]),
-        ],
-      ],
-      'writable scaffold file removed from writable site directory' => [
-        [],
-        [
-          '[web-root]/sites/default/deleted.txt' => '',
-        ],
-        [],
-        [],
-      ],
-      'non-writable scaffold file removed from non-writable site directory' => [
-        [
-          // The file must be made write-protected before the site directory is,
-          // or the permissions change will fail.
-          'sites/default/deleted.txt',
-          'sites/default',
-        ],
-        [
-          '[web-root]/sites/default/deleted.txt' => '',
-        ],
-        [],
-        [
-          ValidationResult::createError([
-            t('sites/default'),
-            t('sites/default/deleted.txt'),
-          ], t('I summarize thee!')),
-        ],
-      ],
-      'non-writable scaffold file removed from writable site directory' => [
-        ['sites/default/deleted.txt'],
-        [
-          '[web-root]/sites/default/deleted.txt' => '',
-        ],
-        [],
-        [
-          ValidationResult::createError([t('sites/default/deleted.txt')]),
-        ],
-      ],
-      // If only scaffold files outside the site directory changed, the
-      // validator doesn't care if the site directory is writable.
-      'new scaffold file added outside non-writable site directory' => [
-        ['sites/default'],
-        [],
-        [
-          '[web-root]/foo.html' => '',
-        ],
-        [],
-      ],
-      'new scaffold file added outside writable site directory' => [
-        [],
-        [],
-        [
-          '[web-root]/foo.html' => '',
-        ],
-        [],
-      ],
-      'writable scaffold file removed outside non-writable site directory' => [
-        ['sites/default'],
-        [
-          '[web-root]/foo.txt' => '',
-        ],
-        [],
-        [],
-      ],
-      'writable scaffold file removed outside writable site directory' => [
-        [],
-        [
-          '[web-root]/foo.txt' => '',
-        ],
-        [],
-        [],
-      ],
-      'non-writable scaffold file removed outside non-writable site directory' => [
-        [
-          'sites/default',
-          'foo.txt',
-        ],
-        [
-          '[web-root]/foo.txt' => '',
-        ],
-        [],
-        [],
-      ],
-      'non-writable scaffold file removed outside writable site directory' => [
-        ['foo.txt'],
-        [
-          '[web-root]/foo.txt' => '',
-        ],
-        [],
-        [],
-      ],
-    ];
-  }
-
-  /**
-   * Tests site directory permissions are checked before changes are applied.
-   *
-   * @param string[] $write_protected_paths
-   *   A list of paths, relative to the project root, which should be write
-   *   protected before staged changes are applied.
-   * @param string[] $active_scaffold_files
-   *   An array simulating the extra.drupal-scaffold.file-mapping section of the
-   *   active drupal/core package.
-   * @param string[] $staged_scaffold_files
-   *   An array simulating the extra.drupal-scaffold.file-mapping section of the
-   *   staged drupal/core package.
-   * @param \Drupal\package_manager\ValidationResult[] $expected_results
-   *   The expected validation results, if any.
-   *
-   * @dataProvider providerScaffoldFilesChanged
-   */
-  public function testScaffoldFilesChanged(array $write_protected_paths, array $active_scaffold_files, array $staged_scaffold_files, array $expected_results): void {
-    // Rewrite the active and staged composer.json files, inserting the given
-    // lists of scaffold files.
-    if ($active_scaffold_files) {
-      (new ActiveFixtureManipulator())
-        ->modifyPackageConfig('drupal/core', '9.8.0', [
-          'extra' => [
-            'drupal-scaffold' => [
-              'file-mapping' => $active_scaffold_files,
-            ],
-          ],
-        ])
-        ->commitChanges();
-    }
-    $stage_manipulator = $this->getStageFixtureManipulator();
-    $stage_manipulator->setVersion('drupal/core-recommended', '9.8.1');
-    $stage_manipulator->setVersion('drupal/core-dev', '9.8.1');
-    if ($staged_scaffold_files) {
-      $stage_manipulator->modifyPackageConfig('drupal/core', '9.8.1', [
-        'extra' => [
-          'drupal-scaffold' => [
-            'file-mapping' => $staged_scaffold_files,
-          ],
-        ],
-      ]);
-    }
-    else {
-      $stage_manipulator->setVersion('drupal/core', '9.8.1');
-    }
-
-    // Create fake scaffold files so we can test scenarios in which a scaffold
-    // file that exists in the active directory is deleted in the stage
-    // directory.
-    touch($this->activeDir . '/sites/default/deleted.txt');
-    touch($this->activeDir . '/foo.txt');
-
-    $stage = $this->container->get(UpdateStage::class);
-    $stage->begin(['drupal' => '9.8.1']);
-    $stage->stage();
-
-    $this->writeProtect($write_protected_paths);
-
-    try {
-      $stage->apply();
-
-      // If no exception was thrown, ensure that we weren't expecting an error.
-      $this->assertSame([], $expected_results);
-    }
-    // If we try to overwrite any write-protected paths, even if they're not
-    // scaffold files, we'll get an ApplyFailedException.
-    catch (ApplyFailedException $e) {
-      $this->assertStringStartsWith("Automatic updates failed to apply, and the site is in an indeterminate state. Consider restoring the code and database from a backup.", $e->getMessage());
-    }
-    catch (StageEventException $e) {
-      $this->assertExpectedResultsFromException($expected_results, $e);
-    }
-  }
-
-}
diff --git a/tests/src/Kernel/StatusCheck/StatusCheckerTest.php b/tests/src/Kernel/StatusCheck/StatusCheckerTest.php
index 8ebddb72ae..ba2f79a055 100644
--- a/tests/src/Kernel/StatusCheck/StatusCheckerTest.php
+++ b/tests/src/Kernel/StatusCheck/StatusCheckerTest.php
@@ -7,7 +7,6 @@ namespace Drupal\Tests\automatic_updates\Kernel\StatusCheck;
 use Drupal\automatic_updates\CronUpdateStage;
 use Drupal\automatic_updates\UpdateStage;
 use Drupal\automatic_updates\Validation\StatusChecker;
-use Drupal\automatic_updates\Validator\ScaffoldFilePermissionsValidator;
 use Drupal\automatic_updates\Validator\StagedProjectsValidator;
 use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
 use Drupal\automatic_updates_test_status_checker\EventSubscriber\TestSubscriber2;
@@ -243,16 +242,11 @@ class StatusCheckerTest extends AutomaticUpdatesKernelTestBase {
     // results should be stored.
     $this->assertValidationResultsEqual($results, $manager->getResults());
 
-    // Don't validate staged projects or scaffold file permissions because
-    // actual stage operations are bypassed by package_manager_bypass, which
-    // will make these validators complain that there is no actual Composer data
-    // for them to inspect.
-    $validators = array_map([$this->container, 'get'], [
-      StagedProjectsValidator::class,
-      ScaffoldFilePermissionsValidator::class,
-    ]);
-    $event_dispatcher = $this->container->get('event_dispatcher');
-    array_walk($validators, [$event_dispatcher, 'removeSubscriber']);
+    // Don't validate staged projects because actual stage operations are
+    // bypassed by package_manager_bypass, which will make this validator
+    // complain that there is no actual Composer data for it to inspect.
+    $validator = $this->container->get(StagedProjectsValidator::class);
+    $this->container->get('event_dispatcher')->removeSubscriber($validator);
 
     $stage = $this->container->get(UpdateStage::class);
     $stage->begin(['drupal' => '9.8.1']);
-- 
GitLab