diff --git a/automatic_updates_extensions/automatic_updates_extensions.services.yml b/automatic_updates_extensions/automatic_updates_extensions.services.yml
index d2c2a1305bb1e76d2e8b5a4901e89032d0f26212..8e22f97c5691cc7916e2120b0476e42c7e242b2e 100644
--- a/automatic_updates_extensions/automatic_updates_extensions.services.yml
+++ b/automatic_updates_extensions/automatic_updates_extensions.services.yml
@@ -18,14 +18,6 @@ services:
       - '@string_translation'
     tags:
       - { name: event_subscriber }
-  automatic_updates_extensions.validator.packages_type:
-    class: Drupal\automatic_updates_extensions\Validator\UpdatePackagesTypeValidator
-    arguments:
-      - '@string_translation'
-      - '@extension.list.module'
-      - '@extension.list.theme'
-    tags:
-      - { name: event_subscriber }
   automatic_updates_extensions.validator.target_release:
     class: Drupal\automatic_updates_extensions\Validator\UpdateReleaseValidator
     tags:
diff --git a/automatic_updates_extensions/src/ExtensionUpdater.php b/automatic_updates_extensions/src/ExtensionUpdater.php
index 6314ea7d00dbd98a949d35ff239e71e13347e2e8..91109fa788d9229b8436a16f2e49ee6051e09ab9 100644
--- a/automatic_updates_extensions/src/ExtensionUpdater.php
+++ b/automatic_updates_extensions/src/ExtensionUpdater.php
@@ -43,7 +43,10 @@ class ExtensionUpdater extends Stage {
       ->getPackage()
       ->getDevRequires();
     foreach ($project_versions as $project_name => $version) {
-      $package = "drupal/$project_name";
+      $package = $composer->getPackageForProject($project_name);
+      if (empty($package)) {
+        throw new \InvalidArgumentException("The project $project_name is not a Drupal project known to Composer and cannot be updated.");
+      }
       $group = array_key_exists($package, $require_dev) ? 'dev' : 'production';
       $package_versions[$group][$package] = LegacyVersionUtility::convertToSemanticVersion($version);
     }
diff --git a/automatic_updates_extensions/src/Validator/PackagesInstalledWithComposerValidator.php b/automatic_updates_extensions/src/Validator/PackagesInstalledWithComposerValidator.php
index 54af7e4dc49fc49fdb3576f1346e57a76846fc66..487b46a64ff8c997a70e43e3b0685887650a216f 100644
--- a/automatic_updates_extensions/src/Validator/PackagesInstalledWithComposerValidator.php
+++ b/automatic_updates_extensions/src/Validator/PackagesInstalledWithComposerValidator.php
@@ -7,12 +7,12 @@ use Drupal\automatic_updates_extensions\ExtensionUpdater;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\package_manager\Event\PreApplyEvent;
-use Drupal\package_manager\Event\PreCreateEvent;
-use Drupal\package_manager\Event\PreOperationStageEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
  * Validates packages are installed via Composer.
+ *
+ * @todo Remove this validator completely in https://www.drupal.org/i/3303900.
  */
 class PackagesInstalledWithComposerValidator implements EventSubscriberInterface {
 
@@ -31,10 +31,10 @@ class PackagesInstalledWithComposerValidator implements EventSubscriberInterface
   /**
    * Validates that packages are installed with composer or not.
    *
-   * @param \Drupal\package_manager\Event\PreOperationStageEvent $event
+   * @param \Drupal\package_manager\Event\PreApplyEvent $event
    *   The event object.
    */
-  public function checkPackagesInstalledWithComposer(PreOperationStageEvent $event): void {
+  public function checkPackagesInstalledWithComposer(PreApplyEvent $event): void {
     $stage = $event->getStage();
 
     if (!$stage instanceof ExtensionUpdater) {
@@ -54,7 +54,6 @@ class PackagesInstalledWithComposerValidator implements EventSubscriberInterface
    */
   public static function getSubscribedEvents() {
     return [
-      PreCreateEvent::class => 'checkPackagesInstalledWithComposer',
       PreApplyEvent::class => 'checkPackagesInstalledWithComposer',
     ];
   }
@@ -62,51 +61,37 @@ class PackagesInstalledWithComposerValidator implements EventSubscriberInterface
   /**
    * Gets the packages which aren't installed via composer.
    *
-   * @param \Drupal\package_manager\Event\PreOperationStageEvent $event
+   * @param \Drupal\package_manager\Event\PreApplyEvent $event
    *   The event object.
    *
    * @return \Composer\Package\PackageInterface[]
    *   Packages not installed via composer.
    */
-  protected function getPackagesNotInstalledWithComposer(PreOperationStageEvent $event): array {
+  protected function getPackagesNotInstalledWithComposer(PreApplyEvent $event): array {
     $stage = $event->getStage();
     $active_composer = $stage->getActiveComposer();
-    $installed_packages = $active_composer->getInstalledPackages();
 
-    $missing_packages = [];
-    // During pre-create there is no stage directory, so missing packages can be
-    // found using package versions that will be required during the update,
-    // while during pre-apply there is stage directory which will be used to
-    // find missing packages.
-    if ($event instanceof PreCreateEvent) {
-      $package_versions = $stage->getPackageVersions();
-      foreach (['production', 'dev'] as $package_group) {
-        $missing_packages = array_merge($missing_packages, array_diff_key($package_versions[$package_group], $installed_packages));
-      }
-    }
-    else {
-      $missing_packages = $stage->getStageComposer()->getPackagesNotIn($active_composer);
+    $missing_packages = $stage->getStageComposer()->getPackagesNotIn($active_composer);
 
-      // The core update system can only fetch release information for modules,
-      // themes, or profiles that are in the active code base (whether they're
-      // installed or not). If a package is not one of those types, ignore it
-      // even if its vendor namespace is `drupal`.
-      $types = [
-        'drupal-module',
-        'drupal-theme',
-        'drupal-profile',
-      ];
-      $filter = function (PackageInterface $package) use ($types): bool {
-        return in_array($package->getType(), $types, TRUE);
-      };
-      $missing_packages = array_filter($missing_packages, $filter);
+    // The core update system can only fetch release information for modules,
+    // themes, or profiles that are in the active code base (whether they're
+    // installed or not). If a package is not one of those types, ignore it
+    // even if its vendor namespace is `drupal`.
+    $types = [
+      'drupal-module',
+      'drupal-theme',
+      'drupal-profile',
+    ];
+    $filter = function (PackageInterface $package) use ($types): bool {
+      return in_array($package->getType(), $types, TRUE);
+    };
+    $missing_packages = array_filter($missing_packages, $filter);
 
-      // The core update system can only fetch release information for drupal
-      // projects, so saving only the packages whose name starts with drupal/.
-      $missing_packages = array_filter($missing_packages, function (string $key) {
-        return str_starts_with($key, 'drupal/');
-      }, ARRAY_FILTER_USE_KEY);
-    }
+    // The core update system can only fetch release information for drupal
+    // projects, so saving only the packages whose name starts with drupal/.
+    $missing_packages = array_filter($missing_packages, function (string $key) {
+      return str_starts_with($key, 'drupal/');
+    }, ARRAY_FILTER_USE_KEY);
     return $missing_packages;
   }
 
diff --git a/automatic_updates_extensions/src/Validator/UpdatePackagesTypeValidator.php b/automatic_updates_extensions/src/Validator/UpdatePackagesTypeValidator.php
deleted file mode 100644
index 9fc41b9b35cd02fcccda123d83692a2d8720719b..0000000000000000000000000000000000000000
--- a/automatic_updates_extensions/src/Validator/UpdatePackagesTypeValidator.php
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-
-namespace Drupal\automatic_updates_extensions\Validator;
-
-use Drupal\automatic_updates_extensions\ExtensionUpdater;
-use Drupal\Core\Extension\ModuleExtensionList;
-use Drupal\Core\Extension\ThemeExtensionList;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\Core\StringTranslation\TranslationInterface;
-use Drupal\Core\Utility\ProjectInfo;
-use Drupal\package_manager\Event\PreCreateEvent;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-/**
- * Validates the type of updated packages.
- */
-class UpdatePackagesTypeValidator implements EventSubscriberInterface {
-
-  use StringTranslationTrait;
-
-  /**
-   * The module list service.
-   *
-   * @var \Drupal\Core\Extension\ModuleExtensionList
-   */
-  protected $moduleList;
-
-  /**
-   * The theme list service.
-   *
-   * @var \Drupal\Core\Extension\ThemeExtensionList
-   */
-  protected $themeList;
-
-  /**
-   * Constructs a UpdatePackagesTypeValidator object.
-   *
-   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
-   *   The translation service.
-   * @param \Drupal\Core\Extension\ModuleExtensionList $module_list
-   *   The module list service.
-   * @param \Drupal\Core\Extension\ThemeExtensionList $theme_list
-   *   The theme list service.
-   */
-  public function __construct(TranslationInterface $translation, ModuleExtensionList $module_list, ThemeExtensionList $theme_list) {
-    $this->setStringTranslation($translation);
-    $this->moduleList = $module_list;
-    $this->themeList = $theme_list;
-  }
-
-  /**
-   * Validates that updated packages are only modules or themes.
-   *
-   * @param \Drupal\package_manager\Event\PreCreateEvent $event
-   *   The event object.
-   */
-  public function checkPackagesAreOnlyThemesOrModules(PreCreateEvent $event): void {
-    $stage = $event->getStage();
-    if (!$stage instanceof ExtensionUpdater) {
-      return;
-    }
-
-    $invalid_projects = [];
-    $all_projects = $this->getInstalledProjectNames();
-
-    foreach ($stage->getPackageVersions() as $group) {
-      foreach (array_keys($group) as $package) {
-        // @todo Use
-        //   \Drupal\package_manager\ComposerUtility::getProjectForPackage() to
-        //   determine the project name in https://www.drupal.org/i/3304142.
-        $update_project = str_replace('drupal/', '', $package);
-        if ($update_project === 'drupal' || !in_array($update_project, $all_projects, TRUE)) {
-          $invalid_projects[] = $update_project;
-        }
-      }
-    }
-    if ($invalid_projects) {
-      $event->addError($invalid_projects, $this->t('The following projects cannot be updated because they are not Drupal modules or themes:'));
-    }
-  }
-
-  /**
-   * Returns a list of all available modules and themes' project names.
-   *
-   * @return string[]
-   *   The project names of all available modules and themes.
-   */
-  private function getInstalledProjectNames(): array {
-    $extension_list = array_merge($this->themeList->getList(), $this->moduleList->getList());
-    $map = \Closure::fromCallable([new ProjectInfo(), 'getProjectName']);
-    return array_map($map, $extension_list);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getSubscribedEvents() {
-    return [
-      PreCreateEvent::class => 'checkPackagesAreOnlyThemesOrModules',
-    ];
-  }
-
-}
diff --git a/automatic_updates_extensions/src/Validator/UpdateReleaseValidator.php b/automatic_updates_extensions/src/Validator/UpdateReleaseValidator.php
index 945d89b5a79b894cddfa0a641c4601ecdfd426c6..e6a832537889e05be7c8b9c4641074ee9c3258dc 100644
--- a/automatic_updates_extensions/src/Validator/UpdateReleaseValidator.php
+++ b/automatic_updates_extensions/src/Validator/UpdateReleaseValidator.php
@@ -16,6 +16,8 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  * @internal
  *   This class is an internal part of the module's update handling and
  *   should not be used by external code.
+ *
+ * @todo Remove this validator completely in https://www.drupal.org/i/3307369.
  */
 final class UpdateReleaseValidator implements EventSubscriberInterface {
 
@@ -79,7 +81,7 @@ final class UpdateReleaseValidator implements EventSubscriberInterface {
         ['drupal-module', 'drupal-theme'], TRUE)) {
         continue;
       }
-      [, $project_name] = explode('/', $staged_package->getName());
+      $project_name = $staged->getProjectForPackage($staged_package->getName());
       $semantic_version = $staged_package->getPrettyVersion();
       if (!$this->isSupportedRelease($project_name, $semantic_version)) {
         $messages[] = $this->t('Project @project_name to version @version', [
@@ -115,8 +117,7 @@ final class UpdateReleaseValidator implements EventSubscriberInterface {
     $messages = [];
     foreach (['production', 'dev'] as $package_type) {
       foreach ($all_versions[$package_type] as $package_name => $sematic_version) {
-        $package_parts = explode('/', $package_name);
-        $project_name = $package_parts[1];
+        $project_name = $stage->getActiveComposer()->getProjectForPackage($package_name);
         // If the version isn't in the list of installable releases, then it
         // isn't secure and supported and the user should receive an error.
         if (!$this->isSupportedRelease($project_name, $sematic_version)) {
diff --git a/automatic_updates_extensions/tests/fixtures/fake-site/vendor/composer/installed.php b/automatic_updates_extensions/tests/fixtures/fake-site/vendor/composer/installed.php
new file mode 100644
index 0000000000000000000000000000000000000000..4f90e04078283b7bc2113891f6b518dff5a87279
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/fake-site/vendor/composer/installed.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ */
+
+$projects_dir = __DIR__ . '/../../web/projects';
+return [
+  'versions' => [
+    'drupal/my_module' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/my_module',
+    ],
+    'drupal/my_dev_module' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/my_dev_module',
+    ],
+    'drupal/semver_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/semver_test',
+    ],
+    'drupal/aaa_update_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/aaa_update_test',
+    ],
+    'drupal/aaa_automatic_updates_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/aaa_automatic_updates_test',
+    ],
+  ],
+];
diff --git a/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml.hide b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..8215de0b462a258714e2ebb3b9624da096742794
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml.hide
@@ -0,0 +1 @@
+project: aaa_automatic_updates_test
diff --git a/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/aaa_update_test/aaa_update_test.info.yml.hide b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/aaa_update_test/aaa_update_test.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..b2d1f884d4a007ebd07dc00b3e46a8c5d098918d
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/aaa_update_test/aaa_update_test.info.yml.hide
@@ -0,0 +1 @@
+project: aaa_update_test
diff --git a/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/my_dev_module/my_dev_module.info.yml.hide b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/my_dev_module/my_dev_module.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..038e544660c96601ccc767d75d191d1ed132abc3
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/my_dev_module/my_dev_module.info.yml.hide
@@ -0,0 +1 @@
+project: my_dev_module
diff --git a/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/my_module/my_module.info.yml.hide b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/my_module/my_module.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..eb1bc0fda720c2989242046e42db1097f2f8f631
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/my_module/my_module.info.yml.hide
@@ -0,0 +1 @@
+project: my_module
diff --git a/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/semver_test/semver_test.info.yml.hide b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/semver_test/semver_test.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..6175b81a041a98520a59f20ff0e1bce7b659cacd
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/fake-site/web/projects/semver_test/semver_test.info.yml.hide
@@ -0,0 +1 @@
+project: semver_test
diff --git a/automatic_updates_extensions/tests/fixtures/new_module/1.0.0/new_module.info.yml b/automatic_updates_extensions/tests/fixtures/new_module/1.0.0/new_module.info.yml.hide
similarity index 75%
rename from automatic_updates_extensions/tests/fixtures/new_module/1.0.0/new_module.info.yml
rename to automatic_updates_extensions/tests/fixtures/new_module/1.0.0/new_module.info.yml.hide
index 8ffd0cce5c6bd85945d961b8246cddc9bb77c92c..24cbeec14e6d1b8966cf9bfdeccdbee49e8c069b 100644
--- a/automatic_updates_extensions/tests/fixtures/new_module/1.0.0/new_module.info.yml
+++ b/automatic_updates_extensions/tests/fixtures/new_module/1.0.0/new_module.info.yml.hide
@@ -1,3 +1,4 @@
 name: 'New module'
 type: module
 core_version_requirement: ^9
+project: new_module
diff --git a/automatic_updates_extensions/tests/fixtures/new_module/1.1.0/new_module.info.yml b/automatic_updates_extensions/tests/fixtures/new_module/1.1.0/new_module.info.yml.hide
similarity index 75%
rename from automatic_updates_extensions/tests/fixtures/new_module/1.1.0/new_module.info.yml
rename to automatic_updates_extensions/tests/fixtures/new_module/1.1.0/new_module.info.yml.hide
index 8ffd0cce5c6bd85945d961b8246cddc9bb77c92c..24cbeec14e6d1b8966cf9bfdeccdbee49e8c069b 100644
--- a/automatic_updates_extensions/tests/fixtures/new_module/1.1.0/new_module.info.yml
+++ b/automatic_updates_extensions/tests/fixtures/new_module/1.1.0/new_module.info.yml.hide
@@ -1,3 +1,4 @@
 name: 'New module'
 type: module
 core_version_requirement: ^9
+project: new_module
diff --git a/automatic_updates_extensions/tests/fixtures/stage_composer/aaa_update_test/vendor/composer/installed.php b/automatic_updates_extensions/tests/fixtures/stage_composer/aaa_update_test/vendor/composer/installed.php
new file mode 100644
index 0000000000000000000000000000000000000000..db86abf49f27d864aca21eb9bcef1bd8b92b17ff
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/stage_composer/aaa_update_test/vendor/composer/installed.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ */
+
+$projects_dir = __DIR__ . '/../../web/projects';
+return [
+  'versions' => [
+    'drupal/test_theme' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/test_theme',
+    ],
+    'drupal/semver_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/semver_test',
+    ],
+    'drupal/aaa_update_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/aaa_update_test',
+    ],
+  ],
+];
diff --git a/automatic_updates_extensions/tests/fixtures/stage_composer/aaa_update_test/web/projects/aaa_update_test/aaa_update_test.info.yml.hide b/automatic_updates_extensions/tests/fixtures/stage_composer/aaa_update_test/web/projects/aaa_update_test/aaa_update_test.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..b2d1f884d4a007ebd07dc00b3e46a8c5d098918d
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/stage_composer/aaa_update_test/web/projects/aaa_update_test/aaa_update_test.info.yml.hide
@@ -0,0 +1 @@
+project: aaa_update_test
diff --git a/automatic_updates_extensions/tests/fixtures/stage_composer/semver_test/vendor/composer/installed.php b/automatic_updates_extensions/tests/fixtures/stage_composer/semver_test/vendor/composer/installed.php
new file mode 100644
index 0000000000000000000000000000000000000000..db86abf49f27d864aca21eb9bcef1bd8b92b17ff
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/stage_composer/semver_test/vendor/composer/installed.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ */
+
+$projects_dir = __DIR__ . '/../../web/projects';
+return [
+  'versions' => [
+    'drupal/test_theme' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/test_theme',
+    ],
+    'drupal/semver_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/semver_test',
+    ],
+    'drupal/aaa_update_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/aaa_update_test',
+    ],
+  ],
+];
diff --git a/automatic_updates_extensions/tests/fixtures/stage_composer/semver_test/web/projects/semver_test/semver_test.info.yml.hide b/automatic_updates_extensions/tests/fixtures/stage_composer/semver_test/web/projects/semver_test/semver_test.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..6175b81a041a98520a59f20ff0e1bce7b659cacd
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/stage_composer/semver_test/web/projects/semver_test/semver_test.info.yml.hide
@@ -0,0 +1 @@
+project: semver_test
diff --git a/automatic_updates_extensions/tests/fixtures/stage_composer/test_theme/vendor/composer/installed.php b/automatic_updates_extensions/tests/fixtures/stage_composer/test_theme/vendor/composer/installed.php
new file mode 100644
index 0000000000000000000000000000000000000000..db86abf49f27d864aca21eb9bcef1bd8b92b17ff
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/stage_composer/test_theme/vendor/composer/installed.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ */
+
+$projects_dir = __DIR__ . '/../../web/projects';
+return [
+  'versions' => [
+    'drupal/test_theme' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/test_theme',
+    ],
+    'drupal/semver_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/semver_test',
+    ],
+    'drupal/aaa_update_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/aaa_update_test',
+    ],
+  ],
+];
diff --git a/automatic_updates_extensions/tests/fixtures/stage_composer/test_theme/web/projects/test_theme/test_theme.info.yml.hide b/automatic_updates_extensions/tests/fixtures/stage_composer/test_theme/web/projects/test_theme/test_theme.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..95e64c8affec1370b89e6f78a7a2f7d32076afe1
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/stage_composer/test_theme/web/projects/test_theme/test_theme.info.yml.hide
@@ -0,0 +1 @@
+project: test_theme
diff --git a/automatic_updates_extensions/tests/fixtures/two_projects/vendor/composer/installed.php b/automatic_updates_extensions/tests/fixtures/two_projects/vendor/composer/installed.php
new file mode 100644
index 0000000000000000000000000000000000000000..db86abf49f27d864aca21eb9bcef1bd8b92b17ff
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/two_projects/vendor/composer/installed.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ */
+
+$projects_dir = __DIR__ . '/../../web/projects';
+return [
+  'versions' => [
+    'drupal/test_theme' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/test_theme',
+    ],
+    'drupal/semver_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/semver_test',
+    ],
+    'drupal/aaa_update_test' => [
+      'type' => 'drupal-module',
+      'install_path' => $projects_dir . '/aaa_update_test',
+    ],
+  ],
+];
diff --git a/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml.hide b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..8215de0b462a258714e2ebb3b9624da096742794
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml.hide
@@ -0,0 +1 @@
+project: aaa_automatic_updates_test
diff --git a/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/aaa_update_test/aaa_update_test.info.yml.hide b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/aaa_update_test/aaa_update_test.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..b2d1f884d4a007ebd07dc00b3e46a8c5d098918d
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/aaa_update_test/aaa_update_test.info.yml.hide
@@ -0,0 +1 @@
+project: aaa_update_test
diff --git a/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/my_dev_module/my_dev_module.info.yml.hide b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/my_dev_module/my_dev_module.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..038e544660c96601ccc767d75d191d1ed132abc3
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/my_dev_module/my_dev_module.info.yml.hide
@@ -0,0 +1 @@
+project: my_dev_module
diff --git a/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/my_module/my_module.info.yml.hide b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/my_module/my_module.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..eb1bc0fda720c2989242046e42db1097f2f8f631
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/my_module/my_module.info.yml.hide
@@ -0,0 +1 @@
+project: my_module
diff --git a/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/semver_test/semver_test.info.yml.hide b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/semver_test/semver_test.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..6175b81a041a98520a59f20ff0e1bce7b659cacd
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/semver_test/semver_test.info.yml.hide
@@ -0,0 +1 @@
+project: semver_test
diff --git a/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/test_theme/test_theme.info.yml.hide b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/test_theme/test_theme.info.yml.hide
new file mode 100644
index 0000000000000000000000000000000000000000..95e64c8affec1370b89e6f78a7a2f7d32076afe1
--- /dev/null
+++ b/automatic_updates_extensions/tests/fixtures/two_projects/web/projects/test_theme/test_theme.info.yml.hide
@@ -0,0 +1 @@
+project: test_theme
diff --git a/automatic_updates_extensions/tests/src/Build/ModuleUpdateTest.php b/automatic_updates_extensions/tests/src/Build/ModuleUpdateTest.php
index 55e122bb50011dbea91b4598fad3fa61be8488ea..b674eeb03aab9b7aca8113e468a1911e9fd6f2c5 100644
--- a/automatic_updates_extensions/tests/src/Build/ModuleUpdateTest.php
+++ b/automatic_updates_extensions/tests/src/Build/ModuleUpdateTest.php
@@ -37,12 +37,11 @@ class ModuleUpdateTest extends UpdateTestBase {
 \$config['update_test.settings']['system_info'] = $system_info;
 END;
     $this->writeSettings($code);
-
-    $this->addRepository('alpha', __DIR__ . '/../../../../package_manager/tests/fixtures/alpha/1.0.0');
+    $this->addRepository('alpha', $this->copyFixtureToTempDirectory(__DIR__ . '/../../../../package_manager/tests/fixtures/alpha/1.0.0'));
     $this->runComposer('COMPOSER_MIRROR_PATH_REPOS=1 composer require drupal/alpha --update-with-all-dependencies', 'project');
     $this->assertModuleVersion('alpha', '1.0.0');
     $fs = new SymfonyFilesystem();
-    $fs->mirror(__DIR__ . '/../../fixtures/new_module', $this->getWorkspaceDirectory() . '/project/web/modules');
+    $fs->mirror($this->copyFixtureToTempDirectory(__DIR__ . '/../../fixtures/new_module'), $this->getWorkspaceDirectory() . '/project/web/modules');
     $this->installModules([
       'automatic_updates_extensions_test_api',
       'alpha',
@@ -50,7 +49,7 @@ END;
     ]);
 
     // Change both modules' upstream version.
-    $this->addRepository('alpha', __DIR__ . '/../../../../package_manager/tests/fixtures/alpha/1.1.0');
+    $this->addRepository('alpha', $this->copyFixtureToTempDirectory(__DIR__ . '/../../../../package_manager/tests/fixtures/alpha/1.1.0'));
   }
 
   /**
@@ -72,7 +71,7 @@ END;
     $mink = $this->getMink();
     $mink->assertSession()->statusCodeEquals(500);
     $page_text = $mink->getSession()->getPage()->getText();
-    $this->assertStringContainsString('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:', $page_text);
+    $this->assertStringContainsString('The project new_module is not a Drupal project known to Composer and cannot be updated.', $page_text);
     $this->assertStringContainsString('new_module', $page_text);
     // Use the API endpoint to create a stage and update the 'alpha' module to
     // 1.1.0. We ask the API to return the contents of the module's
diff --git a/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php b/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php
index a7df5b7e14fb19a0a3f4c3ebf12ba6aa83be9261..d8c3a98bd7e77ee921efecf7738633ad1af37f3d 100644
--- a/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php
+++ b/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php
@@ -153,11 +153,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
    */
   public function testSuccessfulUpdate(bool $maintenance_mode_on, string $project_name, string $project_title, string $installed_version, string $target_version): void {
     $this->container->get('theme_installer')->install(['automatic_updates_theme_with_updates']);
-    // By default, the Update module only checks for updates of installed modules
-    // and themes. The two modules we're testing here (semver_test and aaa_update_test)
-    // are already installed by static::$modules.
+    // By default, the Update module only checks for updates of installed
+    // modules and themes. The two modules we're testing here (semver_test and
+    // aaa_update_test) are already installed by static::$modules.
     $this->container->get('theme_installer')->install(['test_theme']);
-    Stager::setFixturePath(__DIR__ . '/../../fixtures/stage_composer/' . $project_name);
+    $this->useFixtureDirectoryAsStaged(__DIR__ . '/../../fixtures/stage_composer/' . $project_name);
     $this->setReleaseMetadata(__DIR__ . '/../../../../tests/fixtures/release-history/drupal.9.8.2.xml');
     $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/' . $project_name . '.1.1.xml');
     $this->setProjectInstalledVersion([$project_name => $installed_version]);
@@ -202,7 +202,6 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
 
     $page->pressButton('Continue');
     $this->checkForMetaRefresh();
-
     $assert_session->addressEquals('/admin/reports/updates');
     // Confirm that the site was in maintenance before the update was applied.
     // @see \Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber::handleEvent()
diff --git a/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php b/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php
index f7a4b01a2467b79d4b39186465bff05008f7817c..a6357c9c77c96b512d501d919ecee10cb080e0f5 100644
--- a/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php
+++ b/automatic_updates_extensions/tests/src/Kernel/AutomaticUpdatesExtensionsKernelTestBase.php
@@ -3,18 +3,20 @@
 namespace Drupal\Tests\automatic_updates_extensions\Kernel;
 
 use Drupal\automatic_updates_extensions\ExtensionUpdater;
-
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\package_manager\Exception\StageValidationException;
 use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
 use Drupal\Tests\package_manager\Kernel\TestPathFactory;
 use Drupal\Tests\package_manager\Kernel\TestStageTrait;
+use Drupal\Tests\package_manager\Traits\InfoYmlConverterTrait;
 
 /**
  * Base class for kernel tests of the Automatic Updates Extensions module.
  */
 abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdatesKernelTestBase {
 
+  use InfoYmlConverterTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -36,6 +38,18 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates
     parent::setUp();
   }
 
+  /**
+   * Create Virtual Project.
+   *
+   * @param string|null $source_dir
+   *   Source directory.
+   */
+  protected function createVirtualProject(?string $source_dir = NULL): void {
+    $source_dir = $source_dir ?? __DIR__ . '/../../fixtures/fake-site';
+    parent::createVirtualProject($source_dir);
+    $this->renameVfsInfoYmlFiles();
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php b/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php
index 61f0845de0a71a1ac46465c857a608781990d116..b05262708476bcfd2b48e362de5a55f1e73d9644 100644
--- a/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php
+++ b/automatic_updates_extensions/tests/src/Kernel/ExtensionUpdaterTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\automatic_updates_extensions\Kernel;
 
 use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
+use Drupal\Tests\package_manager\Traits\InfoYmlConverterTrait;
 use Drupal\Tests\user\Traits\UserCreationTrait;
 
 /**
@@ -13,6 +14,7 @@ use Drupal\Tests\user\Traits\UserCreationTrait;
 class ExtensionUpdaterTest extends AutomaticUpdatesKernelTestBase {
 
   use UserCreationTrait;
+  use InfoYmlConverterTrait;
 
   /**
    * {@inheritdoc}
@@ -32,28 +34,28 @@ class ExtensionUpdaterTest extends AutomaticUpdatesKernelTestBase {
     // codebase. Therefore, we need to disable the following validators that
     // require real Drupal projects.
     $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release';
-    $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_type';
     parent::setUp();
     $this->installEntitySchema('user');
-  }
-
-  /**
-   * Tests that correct versions are staged after calling ::begin().
-   */
-  public function testCorrectVersionsStaged(): void {
 
     // Create a user who will own the stage even after the container is rebuilt.
     $user = $this->createUser([], NULL, TRUE, ['uid' => 2]);
     $this->setCurrentUser($user);
 
     $this->createVirtualProject(__DIR__ . '/../../fixtures/fake-site');
+    $this->renameVfsInfoYmlFiles();
+  }
 
+  /**
+   * Tests that correct versions are staged after calling ::begin().
+   */
+  public function testCorrectVersionsStaged(): void {
     $id = $this->container->get('automatic_updates_extensions.updater')->begin([
       'my_module' => '9.8.1',
       // Use a legacy version number to ensure they are converted to semantic
       // version numbers which will work with the drupal.org Composer facade.
       'my_dev_module' => '8.x-1.2-alpha1',
     ]);
+    $user = $this->container->get('current_user')->getAccount();
     // Rebuild the container to ensure the package versions are persisted.
     /** @var \Drupal\Core\DrupalKernel $kernel */
     $kernel = $this->container->get('kernel');
@@ -123,4 +125,15 @@ class ExtensionUpdaterTest extends AutomaticUpdatesKernelTestBase {
     $this->container->get('automatic_updates_extensions.updater')->begin([]);
   }
 
+  /**
+   * Tests exception if a Drupal project unknown to composer sent to ::begin().
+   */
+  public function testUnknownDrupalProject(): void {
+    $this->expectException('InvalidArgumentException');
+    $this->expectExceptionMessage("The project my_module_unknown is not a Drupal project known to Composer and cannot be updated.");
+    $this->container->get('automatic_updates_extensions.updater')->begin([
+      'my_module_unknown' => '9.8.1',
+    ]);
+  }
+
 }
diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
index 9d1dfa8b4256ec7449a68113e03fd31cc1faffe3..1ed0f011163a48d24d307efd006ce88b82ab92ac 100644
--- a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
+++ b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php
@@ -3,7 +3,6 @@
 namespace Drupal\Tests\automatic_updates_extensions\Kernel\Validator;
 
 use Drupal\package_manager\Event\PreApplyEvent;
-use Drupal\package_manager\Event\PreCreateEvent;
 use Drupal\package_manager\ValidationResult;
 use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsKernelTestBase;
 
@@ -23,91 +22,9 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
     // In this test, we don't care whether the updated projects are secure and
     // supported.
     $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release';
-    // We also don't care if the updated projects are themes and modules only.
-    $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_type';
     parent::setUp();
   }
 
-  /**
-   * Data provider for testPreCreateException().
-   *
-   * @return mixed[][]
-   *   The test cases.
-   */
-  public function providerPreCreateException(): array {
-    $summary = t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:');
-
-    return [
-      'module not installed via Composer' => [
-        [
-          'new_module' => '9.8.0',
-        ],
-        [
-          ValidationResult::createError(['new_module'], $summary),
-        ],
-      ],
-      'theme not installed via Composer' => [
-        [
-          'new_theme' => '9.8.0',
-        ],
-        [
-          ValidationResult::createError(['new_theme'], $summary),
-        ],
-      ],
-      'profile not installed via Composer' => [
-        [
-          'new_profile' => '9.8.0',
-        ],
-        [
-          ValidationResult::createError(['new_profile'], $summary),
-        ],
-      ],
-      'module, theme, profile, and library not installed via Composer' => [
-        [
-          'new_module' => '9.8.0',
-          'new_theme' => '9.8.0',
-          'new_profile' => '9.8.0',
-          'new_dependency' => '9.8.0',
-        ],
-        [
-          ValidationResult::createError(['new_module', 'new_theme', 'new_profile', 'new_dependency'], $summary),
-        ],
-      ],
-      'module, theme, and profile installed via Composer' => [
-        [
-          'existing_module' => '9.8.1',
-          'existing_theme' => '9.8.1',
-          'existing_profile' => '9.8.1',
-        ],
-        [],
-      ],
-      'existing module installed and new module not installed via Composer' => [
-        [
-          'existing_module' => '9.8.1',
-          'new_module' => '9.8.0',
-        ],
-        [
-          ValidationResult::createError(['new_module'], $summary),
-        ],
-      ],
-    ];
-  }
-
-  /**
-   * Tests the packages installed with Composer during pre-create.
-   *
-   * @param array $projects
-   *   The projects to install.
-   * @param array $expected_results
-   *   The expected validation results.
-   *
-   * @dataProvider providerPreCreateException
-   */
-  public function testPreCreateException(array $projects, array $expected_results): void {
-    $this->useComposerFixturesFiles(__DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active');
-    $this->assertUpdateResults($projects, $expected_results, PreCreateEvent::class);
-  }
-
   /**
    * Data provider for testPreApplyException().
    *
@@ -146,7 +63,10 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
       'module, theme, and profile not installed via Composer' => [
         "$fixtures_folder/module_theme_profile_dependency_not_installed_stage",
         [
-          ValidationResult::createError(['new_module', 'new_theme', 'new_profile'], $summary),
+          ValidationResult::createError(
+            ['new_module', 'new_theme', 'new_profile'],
+            $summary
+          ),
         ],
       ],
     ];
diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php
deleted file mode 100644
index 43284d23796a91460b33f0ac905d5f1de73569ad..0000000000000000000000000000000000000000
--- a/automatic_updates_extensions/tests/src/Kernel/Validator/UpdatePackagesTypeValidatorTest.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-namespace Drupal\Tests\automatic_updates_extensions\Kernel\Validator;
-
-use Drupal\package_manager\Event\PreCreateEvent;
-use Drupal\package_manager\ValidationResult;
-use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsKernelTestBase;
-
-/**
- * Validates the type of updated packages.
- *
- * @coversDefaultClass \Drupal\automatic_updates_extensions\Validator\UpdatePackagesTypeValidator
- *
- * @group automatic_updates_extensions
- */
-class UpdatePackagesTypeValidatorTest extends AutomaticUpdatesExtensionsKernelTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    // In this test, we don't focus on validating that the updated projects are
-    // secure and supported. Therefore, we need to disable the update release
-    // validator that validates updated projects are secure and supported.
-    $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release';
-    $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_installed_with_composer';
-    parent::setUp();
-  }
-
-  /**
-   * Data provider for testUpdatePackagesAreOnlyThemesOrModules().
-   *
-   * @return mixed[][]
-   *   The test cases.
-   */
-  public function providerUpdatePackagesAreOnlyThemesOrModules(): array {
-    $summary = t('The following projects cannot be updated because they are not Drupal modules or themes:');
-
-    return [
-      'non existing project updated' => [
-        [
-          'non_existing_project' => '9.8.1',
-        ],
-        [
-          ValidationResult::createError(['non_existing_project'], $summary),
-        ],
-      ],
-      'non existing project, test module and test theme updated' => [
-        [
-          'non_existing_project' => '9.8.1',
-          'test_module_project' => '9.8.1',
-          'test_theme_project' => '9.8.1',
-        ],
-        [
-          ValidationResult::createError(['non_existing_project'], $summary),
-        ],
-      ],
-      'drupal updated' => [
-        [
-          'drupal' => '9.8.1',
-        ],
-        [
-          ValidationResult::createError(['drupal'], $summary),
-        ],
-      ],
-    ];
-  }
-
-  /**
-   * Tests the packages installed with composer during pre-create.
-   *
-   * @param array $projects
-   *   The projects to install.
-   * @param array $expected_results
-   *   The expected validation results.
-   *
-   * @dataProvider providerUpdatePackagesAreOnlyThemesOrModules
-   */
-  public function testUpdatePackagesAreOnlyThemesOrModules(array $projects, array $expected_results): void {
-    $this->config('update_test.settings')
-      ->set("system_info.aaa_automatic_updates_test", [
-        'project' => 'test_module_project',
-      ])
-      ->set("system_info.automatic_updates_theme", [
-        'project' => 'test_theme_project',
-      ])
-      ->save();
-    $this->assertUpdateResults($projects, $expected_results, PreCreateEvent::class);
-  }
-
-}
diff --git a/package_manager/tests/fixtures/alpha/1.0.0/alpha.info.yml b/package_manager/tests/fixtures/alpha/1.0.0/alpha.info.yml.hide
similarity index 78%
rename from package_manager/tests/fixtures/alpha/1.0.0/alpha.info.yml
rename to package_manager/tests/fixtures/alpha/1.0.0/alpha.info.yml.hide
index 565d3142d8d816bc2f401ef4a6974c7a4c1c6208..4b12c91c911ffd8506961b4e1aff65baedf28c9b 100644
--- a/package_manager/tests/fixtures/alpha/1.0.0/alpha.info.yml
+++ b/package_manager/tests/fixtures/alpha/1.0.0/alpha.info.yml.hide
@@ -1,3 +1,4 @@
 name: Alpha
 type: module
 core_version_requirement: ^9
+project: alpha
diff --git a/package_manager/tests/fixtures/alpha/1.1.0/alpha.info.yml b/package_manager/tests/fixtures/alpha/1.1.0/alpha.info.yml.hide
similarity index 78%
rename from package_manager/tests/fixtures/alpha/1.1.0/alpha.info.yml
rename to package_manager/tests/fixtures/alpha/1.1.0/alpha.info.yml.hide
index 565d3142d8d816bc2f401ef4a6974c7a4c1c6208..4b12c91c911ffd8506961b4e1aff65baedf28c9b 100644
--- a/package_manager/tests/fixtures/alpha/1.1.0/alpha.info.yml
+++ b/package_manager/tests/fixtures/alpha/1.1.0/alpha.info.yml.hide
@@ -1,3 +1,4 @@
 name: Alpha
 type: module
 core_version_requirement: ^9
+project: alpha
diff --git a/package_manager/tests/src/Kernel/ComposerUtilityTest.php b/package_manager/tests/src/Kernel/ComposerUtilityTest.php
index f80331b01dc407f482d05f0c95dd3562f50671ce..29023f1d4575b56aa9d6d61618e4724a3338adb6 100644
--- a/package_manager/tests/src/Kernel/ComposerUtilityTest.php
+++ b/package_manager/tests/src/Kernel/ComposerUtilityTest.php
@@ -4,10 +4,8 @@ namespace Drupal\Tests\package_manager\Kernel;
 
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\package_manager\ComposerUtility;
+use Drupal\Tests\package_manager\Traits\InfoYmlConverterTrait;
 use org\bovigo\vfs\vfsStream;
-use org\bovigo\vfs\vfsStreamDirectory;
-use org\bovigo\vfs\vfsStreamFile;
-use org\bovigo\vfs\visitor\vfsStreamAbstractVisitor;
 
 /**
  * @coversDefaultClass \Drupal\package_manager\ComposerUtility
@@ -16,6 +14,8 @@ use org\bovigo\vfs\visitor\vfsStreamAbstractVisitor;
  */
 class ComposerUtilityTest extends KernelTestBase {
 
+  use InfoYmlConverterTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -30,34 +30,7 @@ class ComposerUtilityTest extends KernelTestBase {
     $fixture = vfsStream::newDirectory('fixture');
     vfsStream::copyFromFileSystem(__DIR__ . '/../../fixtures/project_package_conversion', $fixture);
     $this->vfsRoot->addChild($fixture);
-
-    // Strip the `.hide` suffix from all `.info.yml.hide` files. Drupal's coding
-    // standards don't allow info files to have the `project` key, but we need
-    // it to be present for testing.
-    vfsStream::inspect(new class () extends vfsStreamAbstractVisitor {
-
-      /**
-       * {@inheritdoc}
-       */
-      public function visitFile(vfsStreamFile $file) {
-        $name = $file->getName();
-
-        if (str_ends_with($name, '.info.yml.hide')) {
-          $new_name = basename($name, '.hide');
-          $file->rename($new_name);
-        }
-      }
-
-      /**
-       * {@inheritdoc}
-       */
-      public function visitDirectory(vfsStreamDirectory $dir) {
-        foreach ($dir->getChildren() as $child) {
-          $this->visit($child);
-        }
-      }
-
-    });
+    $this->renameVfsInfoYmlFiles();
   }
 
   /**
diff --git a/package_manager/tests/src/Traits/InfoYmlConverterTrait.php b/package_manager/tests/src/Traits/InfoYmlConverterTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..4f186a53faf69540926627dae11ba5a70cd5518c
--- /dev/null
+++ b/package_manager/tests/src/Traits/InfoYmlConverterTrait.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\Tests\package_manager\Traits;
+
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\vfsStreamDirectory;
+use org\bovigo\vfs\vfsStreamFile;
+use org\bovigo\vfs\visitor\vfsStreamAbstractVisitor;
+
+/**
+ * Common methods to convert info.yml file that will pass core coding standards.
+ */
+trait InfoYmlConverterTrait {
+
+  /**
+   * Renames all files that end with .info.yml.hide.
+   */
+  protected function renameVfsInfoYmlFiles(): void {
+    // Strip the `.hide` suffix from all `.info.yml.hide` files. Drupal's coding
+    // standards don't allow info files to have the `project` key, but we need
+    // it to be present for testing.
+    vfsStream::inspect(new class () extends vfsStreamAbstractVisitor {
+
+      /**
+       * {@inheritdoc}
+       */
+      public function visitFile(vfsStreamFile $file) {
+        $name = $file->getName();
+
+        if (str_ends_with($name, '.info.yml.hide')) {
+          $new_name = basename($name, '.hide');
+          $file->rename($new_name);
+        }
+      }
+
+      /**
+       * {@inheritdoc}
+       */
+      public function visitDirectory(vfsStreamDirectory $dir) {
+        foreach ($dir->getChildren() as $child) {
+          $this->visit($child);
+        }
+      }
+
+    });
+  }
+
+}
diff --git a/tests/src/Build/UpdateTestBase.php b/tests/src/Build/UpdateTestBase.php
index c2ef131588fa746c24c2b047fe76aada0f94d8d1..32d754d758fef90fa281eeba770eda29362a96ef 100644
--- a/tests/src/Build/UpdateTestBase.php
+++ b/tests/src/Build/UpdateTestBase.php
@@ -4,12 +4,17 @@ namespace Drupal\Tests\automatic_updates\Build;
 
 use Drupal\Component\Utility\Html;
 use Drupal\Tests\package_manager\Build\TemplateProjectTestBase;
+use Drupal\Tests\RandomGeneratorTrait;
+use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
 
 /**
  * Base class for tests that perform in-place updates.
  */
 abstract class UpdateTestBase extends TemplateProjectTestBase {
 
+  use RandomGeneratorTrait;
+
   /**
    * A secondary server instance, to serve XML metadata about available updates.
    *
@@ -101,4 +106,40 @@ END;
     }
   }
 
+  /**
+   * Copies a fixture directory to a temporary directory and returns its path.
+   *
+   * @param string $fixture_directory
+   *   The fixture directory.
+   *
+   * @return string
+   *   The temporary directory.
+   */
+  protected function copyFixtureToTempDirectory(string $fixture_directory): string {
+    $temp_directory = $this->getWorkspaceDirectory() . '/fixtures_temp_' . $this->randomMachineName(20);
+    (new Filesystem())->mirror($fixture_directory, $temp_directory);
+    $this->assertDirectoryIsWritable($temp_directory);
+    $this->renameInfoYmlFiles($temp_directory);
+    return $temp_directory;
+  }
+
+  /**
+   * Renames all files that end with .info.yml.hide.
+   *
+   * @param string $dir
+   *   The directory to be iterated through.
+   */
+  protected function renameInfoYmlFiles(string $dir) {
+    // Construct the iterator.
+    $it = new RecursiveDirectoryIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
+
+    // Loop through files and rename them.
+    foreach (new \RecursiveIteratorIterator($it) as $file) {
+      if ($file->getExtension() == 'hide') {
+        rename($file->getPathname(), $dir . DIRECTORY_SEPARATOR .
+            $file->getRelativePath() . DIRECTORY_SEPARATOR . str_replace(".hide", "", $file->getFilename()));
+      }
+    }
+  }
+
 }
diff --git a/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php b/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php
index df4b249ab952552da937b4a512cebd9aea43d875..7b2cc2216c9e49b44932434dc771bb5398743c56 100644
--- a/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php
+++ b/tests/src/Functional/AutomaticUpdatesFunctionalTestBase.php
@@ -4,9 +4,11 @@ namespace Drupal\Tests\automatic_updates\Functional;
 
 use Drupal\Core\Site\Settings;
 use Drupal\package_manager_bypass\Beginner;
+use Drupal\package_manager_bypass\Stager;
 use Drupal\Tests\BrowserTestBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
 
 /**
  * Base class for functional tests of the Automatic Updates module.
@@ -191,6 +193,43 @@ abstract class AutomaticUpdatesFunctionalTestBase extends BrowserTestBase {
     Beginner::setFixturePath($active_dir);
     $this->container->get('package_manager.path_locator')
       ->setPaths($active_dir, $active_dir . '/vendor', '', NULL);
+
+    $this->renameInfoYmlFiles($active_dir);
+  }
+
+  /**
+   * Sets a fixture directory to use as the staged directory.
+   *
+   * @param string $fixture_directory
+   *   The fixture directory.
+   */
+  protected function useFixtureDirectoryAsStaged(string $fixture_directory): void {
+    // Create a temporary directory from our fixture directory that will be
+    // unique for each test run. This will enable changing files in the
+    // directory and not affect other tests.
+    $staged_dir = $this->copyFixtureToTempDirectory($fixture_directory);
+    Stager::setFixturePath($staged_dir);
+
+    $this->renameInfoYmlFiles($staged_dir);
+  }
+
+  /**
+   * Renames all files that end with .info.yml.hide.
+   *
+   * @param string $dir
+   *   The directory to be iterated through.
+   */
+  protected function renameInfoYmlFiles(string $dir) {
+    // Construct the iterator.
+    $it = new RecursiveDirectoryIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
+
+    // Loop through files and rename them.
+    foreach (new \RecursiveIteratorIterator($it) as $file) {
+      if ($file->getExtension() == 'hide') {
+        rename($file->getPathname(), $dir . DIRECTORY_SEPARATOR . $file->getRelativePath() . DIRECTORY_SEPARATOR . str_replace(".hide", "", $file->getFilename()));
+      }
+    }
+
   }
 
 }