From a7dd3e6fe3cfa8c25d717b6d376904287917d349 Mon Sep 17 00:00:00 2001
From: Ted Bowman <41201-tedbow@users.noreply.drupalcode.org>
Date: Thu, 30 Mar 2023 17:45:29 +0000
Subject: [PATCH] Issue #3347165 by tedbow, kunal.sachdev, Wim Leers: Remove
 FixtureManipulatorTest's reliance on installed.json and installed.php

---
 .../src/Kernel/ComposerInspectorTest.php      |  43 ++--
 .../src/Kernel/FixtureManipulatorTest.php     | 199 ++++--------------
 .../src/Traits/InstalledPackagesListTrait.php |  46 ++++
 3 files changed, 111 insertions(+), 177 deletions(-)
 create mode 100644 package_manager/tests/src/Traits/InstalledPackagesListTrait.php

diff --git a/package_manager/tests/src/Kernel/ComposerInspectorTest.php b/package_manager/tests/src/Kernel/ComposerInspectorTest.php
index 1dc3c7c27a..e3ba3bf8f2 100644
--- a/package_manager/tests/src/Kernel/ComposerInspectorTest.php
+++ b/package_manager/tests/src/Kernel/ComposerInspectorTest.php
@@ -10,7 +10,9 @@ use Drupal\fixture_manipulator\ActiveFixtureManipulator;
 use Drupal\package_manager\ComposerInspector;
 use Drupal\package_manager\Exception\ComposerNotReadyException;
 use Drupal\package_manager\InstalledPackage;
+use Drupal\package_manager\InstalledPackagesList;
 use Drupal\package_manager\JsonProcessOutputCallback;
+use Drupal\Tests\package_manager\Traits\InstalledPackagesListTrait;
 use Drupal\package_manager\PathLocator;
 use PhpTuf\ComposerStager\Domain\Exception\PreconditionException;
 use PhpTuf\ComposerStager\Domain\Exception\RuntimeException;
@@ -26,6 +28,8 @@ use Prophecy\Argument;
  */
 class ComposerInspectorTest extends PackageManagerKernelTestBase {
 
+  use InstalledPackagesListTrait;
+
   /**
    * @covers ::getConfig
    */
@@ -98,23 +102,28 @@ class ComposerInspectorTest extends PackageManagerKernelTestBase {
     $inspector = $this->container->get(ComposerInspector::class);
     $list = $inspector->getInstalledPackagesList($project_root);
 
-    $this->assertInstanceOf(InstalledPackage::class, $list['drupal/core']);
-    $this->assertSame('drupal/core', $list['drupal/core']->name);
-    $this->assertSame('drupal-core', $list['drupal/core']->type);
-    $this->assertSame('9.8.0', $list['drupal/core']->version);
-    $this->assertSame("$project_root/vendor/drupal/core", $list['drupal/core']->path);
-
-    $this->assertInstanceOf(InstalledPackage::class, $list['drupal/core-recommended']);
-    $this->assertSame('drupal/core-recommended', $list['drupal/core-recommended']->name);
-    $this->assertSame('project', $list['drupal/core-recommended']->type);
-    $this->assertSame('9.8.0', $list['drupal/core']->version);
-    $this->assertSame("$project_root/vendor/drupal/core-recommended", $list['drupal/core-recommended']->path);
-
-    $this->assertInstanceOf(InstalledPackage::class, $list['drupal/core-dev']);
-    $this->assertSame('drupal/core-dev', $list['drupal/core-dev']->name);
-    $this->assertSame('package', $list['drupal/core-dev']->type);
-    $this->assertSame('9.8.0', $list['drupal/core']->version);
-    $this->assertSame("$project_root/vendor/drupal/core-dev", $list['drupal/core-dev']->path);
+    $expected_list = new InstalledPackagesList([
+      'drupal/core' => InstalledPackage::createFromArray([
+        'name' => 'drupal/core',
+        'type' => 'drupal-core',
+        'version' => '9.8.0',
+        'path' => "$project_root/vendor/drupal/core",
+      ]),
+      'drupal/core-recommended' => InstalledPackage::createFromArray([
+        'name' => 'drupal/core-recommended',
+        'type' => 'project',
+        'version' => '9.8.0',
+        'path' => "$project_root/vendor/drupal/core-recommended",
+      ]),
+      'drupal/core-dev' => InstalledPackage::createFromArray([
+        'name' => 'drupal/core-dev',
+        'type' => 'package',
+        'version' => '9.8.0',
+        'path' => "$project_root/vendor/drupal/core-dev",
+      ]),
+    ]);
+
+    $this->assertPackageListsEqual($expected_list, $list);
 
     // Since the lock file hasn't changed, we should get the same package list
     // back if we call getInstalledPackageList() again.
diff --git a/package_manager/tests/src/Kernel/FixtureManipulatorTest.php b/package_manager/tests/src/Kernel/FixtureManipulatorTest.php
index 3cf7c208fe..c142190092 100644
--- a/package_manager/tests/src/Kernel/FixtureManipulatorTest.php
+++ b/package_manager/tests/src/Kernel/FixtureManipulatorTest.php
@@ -7,6 +7,8 @@ namespace Drupal\Tests\package_manager\Kernel;
 use Drupal\fixture_manipulator\ActiveFixtureManipulator;
 use Drupal\fixture_manipulator\FixtureManipulator;
 use Drupal\package_manager\ComposerInspector;
+use Drupal\package_manager\InstalledPackagesList;
+use Drupal\Tests\package_manager\Traits\InstalledPackagesListTrait;
 use Drupal\package_manager\PathLocator;
 
 /**
@@ -16,6 +18,8 @@ use Drupal\package_manager\PathLocator;
  */
 class FixtureManipulatorTest extends PackageManagerKernelTestBase {
 
+  use InstalledPackagesListTrait;
+
   /**
    * The root directory of the test project.
    *
@@ -31,32 +35,18 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
   private \Exception $expectedTearDownException;
 
   /**
-   * The original 'installed.php' data before any manipulation.
+   * The Composer inspector service.
    *
-   * @var array
+   * @var \Drupal\package_manager\ComposerInspector
    */
-  private array $originalInstalledPhp;
+  private ComposerInspector $inspector;
 
   /**
-   * Ensures the original fixture packages in 'installed.php' are unchanged.
+   * The original fixture package list at the start of the test.
    *
-   * @param array $installed_php
-   *   The current 'installed.php' data.
+   * @var \Drupal\package_manager\InstalledPackagesList
    */
-  private function assertOriginalFixturePackagesUnchanged(array $installed_php): void {
-    $original_package_names = array_keys($this->originalInstalledPhp);
-    $installed_php_core_packages = array_intersect_key($installed_php, array_flip($original_package_names));
-    // `reference` is never the same as the original because the relative path
-    // repos from the `fake_site` fixture are converted to absolute ones, which
-    // causes a new reference to be computed.
-    $without_reference_key = function (array $a): array {
-      return array_diff_key($a, array_flip(['reference']));
-    };
-    $this->assertSame(
-      array_map($without_reference_key, $this->originalInstalledPhp),
-      array_map($without_reference_key, $installed_php_core_packages)
-    );
-  }
+  private InstalledPackagesList $originalFixturePackages;
 
   /**
    * {@inheritdoc}
@@ -66,7 +56,7 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
 
     $this->dir = $this->container->get(PathLocator::class)->getProjectRoot();
 
-    [, $this->originalInstalledPhp] = $this->getData();
+    $this->inspector = $this->container->get(ComposerInspector::class);
 
     $manipulator = new ActiveFixtureManipulator();
     $manipulator
@@ -84,6 +74,7 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
         TRUE
       )
       ->commitChanges();
+    $this->originalFixturePackages = $this->inspector->getInstalledPackagesList($this->dir);
   }
 
   /**
@@ -138,50 +129,14 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
     catch (\LogicException $e) {
       $this->assertStringContainsString("Expected package 'my/package' to not be installed, but it was.", $e->getMessage());
     }
-
-    $installed_json_expected_packages = [
-      'my/dev-package' => [
-        'name' => 'my/dev-package',
-        'version' => '2.1.0',
-        'version_normalized' => '2.1.0.0',
-        'type' => 'library',
-      ],
-      'my/package' => [
-        'name' => 'my/package',
-        // If no version is specified in a new package it will be added.
-        'version' => '1.2.3',
-        'version_normalized' => '1.2.3.0',
-        'type' => 'library',
-      ],
-    ];
-    $installed_php_expected_packages = $installed_json_expected_packages;
-    foreach ($installed_php_expected_packages as $package_name => &$expectation) {
-      // Composer stores `version_normalized`in 'installed.json' but in
-      // 'installed.php' that is just 'version', and 'version' is
-      // 'pretty_version'.
-      $expectation['pretty_version'] = $expectation['version'];
-      $expectation['version'] = $expectation['version_normalized'];
-      unset($expectation['version_normalized']);
-      // `name` is omitted in installed.php.
-      unset($expectation['name']);
-      // Compute the expected `install_path`.
-      $expectation['install_path'] = $expectation['type'] === 'metapackage' ? NULL : "$this->dir/vendor/composer/../$package_name";
-    }
-    [$installed_json, $installed_php] = $this->getData();
-    $installed_json['packages'] = array_intersect_key($installed_json['packages'], $installed_json_expected_packages);
-    $this->assertSame($installed_json_expected_packages, array_map(fn (array $package) => array_intersect_key($package, array_flip(['name', 'type', 'version', 'version_normalized'])), $installed_json['packages']));
-    $this->assertContains('my/dev-package', $installed_json['dev-package-names']);
-    $this->assertNotContains('my/package', $installed_json['dev-package-names']);
-
-    // None of the operations should have changed the original packages.
-    $this->assertOriginalFixturePackagesUnchanged($installed_php);
-
-    // Remove the original packages since we have confirmed that they have not
-    // changed.
-    $installed_php = array_diff_key($installed_php, $this->originalInstalledPhp);
-    foreach ($installed_php_expected_packages as $package_name => $expected_data) {
-      $this->assertEquals($installed_php_expected_packages[$package_name], array_intersect_key($installed_php[$package_name], array_flip(['version', 'type', 'pretty_version', 'install_path'])), $package_name);
-    }
+    // Ensure that none of the failed calls to ::addPackage() changed the installed
+    // packages.
+    $this->assertPackageListsEqual($this->originalFixturePackages, $this->inspector->getInstalledPackagesList($this->dir));
+    $root_info = $this->inspector->getRootPackageInfo($this->dir);
+    $this->assertSame(
+      ['drupal/core-dev', 'my/dev-package'],
+      array_keys($root_info['devRequires'])
+    );
   }
 
   /**
@@ -189,87 +144,33 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
    */
   public function testModifyPackageConfig(): void {
     $inspector = $this->container->get(ComposerInspector::class);
-
     // Assert ::modifyPackage() works with a package in an existing fixture not
     // created by ::addPackage().
-    $decode_installed_json = function () {
-      return json_decode(file_get_contents($this->dir . '/vendor/composer/installed.json'), TRUE, 512, JSON_THROW_ON_ERROR);
+    $decode_composer_json = function ($package_name): array {
+      return json_decode(file_get_contents($this->dir . "/vendor/$package_name/composer.json"), TRUE, 512, JSON_THROW_ON_ERROR);
     };
-    $original_installed_json = $decode_installed_json();
-    $this->assertIsArray($original_installed_json);
+    $original_composer_json = $decode_composer_json('my/dev-package');
     (new ActiveFixtureManipulator())
       // @see ::setUp()
       ->modifyPackageConfig('my/dev-package', '2.1.0', ['description' => 'something else'], TRUE)
       ->commitChanges();
     // Verify that the package is indeed properly installed.
-    $this->assertSame('2.1.0', $inspector->getInstalledPackagesList($this->dir)['my/dev-package']->version);
+    $this->assertSame('2.1.0', $this->inspector->getInstalledPackagesList($this->dir)['my/dev-package']?->version);
     // Verify that the original exists, but has no description.
-    $this->assertSame('my/dev-package', $original_installed_json['packages'][3]['name']);
-    $this->assertArrayNotHasKey('description', $original_installed_json['packages']);
+    $this->assertSame('my/dev-package', $original_composer_json['name']);
+    $this->assertArrayNotHasKey('description', $original_composer_json);
     // Verify that the description was updated.
-    $this->assertSame('something else', $decode_installed_json()['packages'][3]['description']);
+    $this->assertSame('something else', $decode_composer_json('my/dev-package')['description']);
 
     (new ActiveFixtureManipulator())
       // Add a key to an existing package.
       ->modifyPackageConfig('my/package', '1.2.3', ['extra' => ['foo' => 'bar']])
       // Change a key in an existing package.
       ->setVersion('my/dev-package', '3.2.1', TRUE)
-      // Move an existing package to dev requirements.
-      ->addPackage([
-        'name' => 'my/other-package',
-        'type' => 'library',
-      ])
       ->commitChanges();
+    $this->assertSame(['foo' => 'bar'], $decode_composer_json('my/package')['extra']);
+    $this->assertSame('3.2.1', $this->inspector->getInstalledPackagesList($this->dir)['my/dev-package']?->version);
 
-    $install_json_expected_packages = [
-      'my/dev-package' => [
-        'name' => 'my/dev-package',
-        'version' => '3.2.1',
-        'version_normalized' => '3.2.1.0',
-        'type' => 'library',
-      ],
-      'my/other-package' => [
-        'name' => 'my/other-package',
-        'version' => '1.2.3',
-        'version_normalized' => '1.2.3.0',
-        'type' => 'library',
-      ],
-      'my/package' => [
-        'name' => 'my/package',
-        'version' => '1.2.3',
-        'version_normalized' => '1.2.3.0',
-        'type' => 'library',
-      ],
-    ];
-    $installed_php_expected_packages = $install_json_expected_packages;
-    foreach ($installed_php_expected_packages as $package_name => &$expectation) {
-      // Composer stores `version_normalized`in 'installed.json' but in
-      // 'installed.php' that is just 'version', and 'version' is
-      // 'pretty_version'.
-      $expectation['pretty_version'] = $expectation['version'];
-      $expectation['version'] = $expectation['version_normalized'];
-      unset($expectation['version_normalized']);
-      // `name` is omitted in installed.php.
-      unset($expectation['name']);
-      // Compute the expected `install_path`.
-      $expectation['install_path'] = $expectation['type'] === 'metapackage' ? NULL : "$this->dir/vendor/composer/../$package_name";
-    }
-    [$installed_json, $installed_php] = $this->getData();
-    $installed_json['packages'] = array_intersect_key($installed_json['packages'], $install_json_expected_packages);
-    $this->assertSame($install_json_expected_packages, array_map(fn (array $package) => array_intersect_key($package, array_flip(['name', 'type', 'version', 'version_normalized'])), $installed_json['packages']));
-    $this->assertContains('my/dev-package', $installed_json['dev-package-names']);
-    $this->assertNotContains('my/other-package', $installed_json['dev-package-names']);
-    $this->assertNotContains('my/package', $installed_json['dev-package-names']);
-
-    // None of the operations should have changed the original packages.
-    $this->assertOriginalFixturePackagesUnchanged($installed_php);
-
-    // Remove the original packages since we have confirmed that they have not
-    // changed.
-    $installed_php = array_diff_key($installed_php, $this->originalInstalledPhp);
-    foreach ($installed_php_expected_packages as $package_name => $expected_data) {
-      $this->assertEquals($installed_php_expected_packages[$package_name], array_intersect_key($installed_php[$package_name], array_flip(['version', 'type', 'pretty_version', 'install_path'])), $package_name);
-    }
   }
 
   /**
@@ -287,42 +188,20 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
       $this->assertStringContainsString('junk/drawer is not required in your composer.json and has not been remove', $e->getMessage());
     }
 
+    // Remove the 2 packages that were added in ::setUp().
     (new ActiveFixtureManipulator())
       ->removePackage('my/package')
       ->removePackage('my/dev-package', TRUE)
       ->commitChanges();
-
-    foreach (['json', 'php'] as $extension) {
-      $file = "$this->dir/vendor/composer/installed.$extension";
-      $contents = file_get_contents($file);
-      $this->assertStringNotContainsString('my/package', $contents, "'my/package' not found in $file");
-      $this->assertStringNotContainsString('my/dev-package', $contents, "'my/dev-package' not found in $file");
-    }
-  }
-
-  /**
-   * Returns the data from installed.php and installed.json.
-   *
-   * @return array[]
-   *   An array of two arrays. The first array will be the contents of
-   *   installed.json, with the `packages` array keyed by package name. The
-   *   second array will be the `versions` array from installed.php.
-   */
-  private function getData(): array {
-    $installed_json = file_get_contents("$this->dir/vendor/composer/installed.json");
-    $installed_json = json_decode($installed_json, TRUE, 512, JSON_THROW_ON_ERROR);
-
-    $keyed_packages = [];
-    foreach ($installed_json['packages'] as $package) {
-      $keyed_packages[$package['name']] = $package;
-    }
-    $installed_json['packages'] = $keyed_packages;
-
-    $installed_php = require "$this->dir/vendor/composer/installed.php";
-    return [
-      $installed_json,
-      $installed_php['versions'],
-    ];
+    $expected_packages = $this->originalFixturePackages->getArrayCopy();
+    unset($expected_packages['my/package'], $expected_packages['my/dev-package']);
+    $expected_list = new InstalledPackagesList($expected_packages);
+    $this->assertPackageListsEqual($expected_list, $this->inspector->getInstalledPackagesList($this->dir));
+    $root_info = $this->inspector->getRootPackageInfo($this->dir);
+    $this->assertSame(
+      ['drupal/core-dev'],
+      array_keys($root_info['devRequires'])
+    );
   }
 
   /**
diff --git a/package_manager/tests/src/Traits/InstalledPackagesListTrait.php b/package_manager/tests/src/Traits/InstalledPackagesListTrait.php
new file mode 100644
index 0000000000..e1abf45134
--- /dev/null
+++ b/package_manager/tests/src/Traits/InstalledPackagesListTrait.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Tests\package_manager\Traits;
+
+use Drupal\package_manager\InstalledPackage;
+use Drupal\package_manager\InstalledPackagesList;
+
+/**
+ * A trait for comparing InstalledPackagesList objects.
+ *
+ * @internal
+ *   This is an internal part of Package Manager and may be changed or removed
+ *   at any time without warning. External code should not interact with this
+ *   class.
+ */
+trait InstalledPackagesListTrait {
+
+  /**
+   * Asserts that 2 installed package lists are equal.
+   *
+   * @param \Drupal\package_manager\InstalledPackagesList $expected_list
+   *   The expected list.
+   * @param \Drupal\package_manager\InstalledPackagesList $actual_list
+   *   The actual list.
+   */
+  private function assertPackageListsEqual(InstalledPackagesList $expected_list, InstalledPackagesList $actual_list): void {
+    $expected_array = $expected_list->getArrayCopy();
+    $actual_array = $actual_list->getArrayCopy();
+    ksort($expected_array);
+    ksort($actual_array);
+    $this->assertSame(array_keys($expected_array), array_keys($actual_array));
+    foreach ($expected_list as $package_name => $expected_package) {
+      $this->assertInstanceOf(InstalledPackage::class, $expected_package);
+      $actual_package = $actual_list[$package_name];
+      $this->assertInstanceOf(InstalledPackage::class, $actual_package);
+      $this->assertSame($expected_package->name, $actual_package->name);
+      $this->assertSame($expected_package->version, $actual_package->version);
+      $this->assertSame($expected_package->path, $actual_package->path);
+      $this->assertSame($expected_package->type, $actual_package->type);
+      $this->assertSame($expected_package->getProjectName(), $actual_package->getProjectName());
+    }
+  }
+
+}
-- 
GitLab