From 12d595757ca903954a2444a7c4236e2cc5facc00 Mon Sep 17 00:00:00 2001 From: Hugo Thunnissen Date: Mon, 20 Jun 2022 14:36:39 +0200 Subject: [PATCH] Add tests for InstallCommand (user story 5) --- .../Test/Command/InstallCommandTest.php | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 tests/Composer/Test/Command/InstallCommandTest.php diff --git a/tests/Composer/Test/Command/InstallCommandTest.php b/tests/Composer/Test/Command/InstallCommandTest.php new file mode 100644 index 000000000..f5c71d4b9 --- /dev/null +++ b/tests/Composer/Test/Command/InstallCommandTest.php @@ -0,0 +1,271 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Command; + +use Composer\Command\InitCommand; +use Composer\Util\Filesystem; +use Composer\Command\InstallCommand; +use Symfony\Component\Console\Tester\ApplicationTester; +use Composer\Command\RequireCommand; +use Symfony\Component\Console\Input\ArrayInput; +use Composer\Console\Application; +use Composer\Test\TestCase; +use Symfony\Component\Process\Process; + +class InstallCommandTest extends TestCase +{ + /** + * @var string + */ + private $start_directory; + private $working_directory = null; + private $local_repositories = []; + + public function setUp(): void + { + $this->working_directory = null; + $this->local_repositories = []; + $this->start_directory = getcwd(); + } + + protected function tearDown(): void + { + parent::tearDown(); + + // Reset the working directory. Some commands leave the working + // directory contaminated when they exit with an error. + chdir($this->start_directory); + + $fs = new Filesystem(); + + if(null !== $this->working_directory) { + $fs->removeDirectory($this->working_directory); + } + + foreach($this->local_repositories as $repository) { + $fs->removeDirectory($repository); + } + } + + /** + * testcase: UC01-007 + */ + public function testInstallVersionLockedPackages(): void + { + // Arrange + $this->makeLocalRepo('test/one', '1.0.0', 'Test\\One\\'); + $this->makeLocalRepo('test/one', '1.0.1', 'Test\\One\\'); + $this->makeLocalRepo('test/one', '1.0.2', 'Test\\One\\'); + $this->makeLocalRepo('test/one', '1.0.3', 'Test\\One\\'); + $this->makeLocalRepo('test/one', '0.0.3', 'Test\\One\\'); + $this->makeLocalRepo('test/one', '2.0.0', 'Test\\One\\'); + + $this->makeLocalRepo('test/two', '1.0.0', 'Test\\Two\\'); + $this->makeLocalRepo('test/two', '1.2.0', 'Test\\Two\\'); + $this->makeLocalRepo('test/two', '1.5.0', 'Test\\Two\\'); + + $this->makeWorkingDirectory(); + + $application = new Application(); + $application->setAutoExit(false); + $application->add(new InstallCommand()); + $tester = new ApplicationTester($application); + + // Confirmation 1: install multiple dependencies + $this->requirePackage('test/one 1.0.2'); + $this->requirePackage('test/two 1.2.0'); + + // Confirmation 3: remove "vendor" folder + $fs = new Filesystem(); + $fs->removeDirectory($this->working_directory . '/vendor'); + + // Act + $tester->run([ + // Confirmation 4: install all dependencies + 'command' => 'install', + '-d' => $this->working_directory, + ]); + + // Assert + $tester->assertCommandIsSuccessful(); + + // Confirmation 2: System created composer.lock with dependencies and + // version numbers. + $this->assertPackageVersionLocked('test/one', '1.0.2'); + $this->assertPackageVersionLocked('test/two', '1.2.0'); + + // Confirmation 5: System has installed exact versions of dependencies + // that have been set in composer.lock + $this->assertPackageVersionInstalled('test/one', '1.0.2'); + $this->assertPackageVersionInstalled('test/two', '1.2.0'); + } + + /** + * testcase: UC01-008 + */ + public function testInstallVersionLockedPackagesRepoUnavailable(): void + { + // Arrange + $this->makeLocalRepo('test/one', '1.0.2', 'Test\\One\\'); + $one_two_repo = $this->makeLocalRepo('test/two', '1.2.0', 'Test\\Two\\'); + + $this->makeWorkingDirectory(); + + $application = new Application(); + $application->setAutoExit(false); + $application->add(new InstallCommand()); + $tester = new ApplicationTester($application); + + // Confirmation 1: install multiple dependencies + $this->requirePackage('test/one 1.0.2'); + $this->requirePackage('test/two 1.2.0'); + + $fs = new Filesystem(); + // Confirmation 3: remove "vendor" folder + $fs->removeDirectory($this->working_directory . '/vendor'); + + // Remove the repository of one of the dependencies to make it + // unavailable. + $fs->removeDirectory($one_two_repo); + + // Act + $tester->run([ + // Confirmation 4: install all dependencies + 'command' => 'install', + '-d' => $this->working_directory, + ]); + + // Assert + + // Confirmation 2: System created composer.lock with dependencies and + // version numbers. + $this->assertPackageVersionLocked('test/one', '1.0.2'); + $this->assertPackageVersionLocked('test/two', '1.2.0'); + + // Confirm that dependency installation failed and system communicated + // this with its exit code and text output. + $this->assertNotEquals(0, $tester->getStatusCode()); + $this->assertStringContainsString( + 'is not found for package test/two', + $tester->getDisplay() + ); + } + + private function assertPackageVersionLocked(string $package, string $version): void + { + $lockfile = json_decode( + file_get_contents($this->working_directory . '/composer.lock'), + true + ); + $packages = $lockfile['packages']; + $installed_package = null; + foreach($packages as $package_lock) { + if ($package_lock['name'] === $package) { + $installed_package = $package_lock; + } + } + + $this->assertNotNull($installed_package); + $this->assertEquals($version, $installed_package['version']); + } + + + private function assertPackageVersionInstalled(string $package, string $version): void + { + $install_directory = $this->working_directory . '/vendor/' . $package; + $this->assertTrue(file_exists($install_directory)); + + $manifest = json_decode( + file_get_contents($install_directory . '/composer.json'), + true + ); + + $this->assertEquals($version, $manifest['version']); + } + + private function requirePackage(string $package): void + { + // Arrange + $application = new Application(); + $application->setAutoExit(false); + $application->add(new RequireCommand()); + + $tester = new ApplicationTester($application); + $tester->run([ + 'command' => 'require', + 'packages' => [ $package ], + '-d' => $this->working_directory, + ]); + + $output = $tester->getDisplay(); + + $tester->assertCommandIsSuccessful(); + } + + + private function makeLocalRepo(string $name, string $version, string $autoload): string + { + $local_repository = $this->getUniqueTmpDirectory(); + + $repo_manifest = [ + 'name' => $name, + 'version' => $version, + 'type' => 'library', + 'autoload' => [ + 'psr-4' => [ + $autoload => 'src/', + ] + ] + ]; + + file_put_contents( + $local_repository.'/composer.json', + json_encode( + $repo_manifest, + JSON_UNESCAPED_SLASHES, + ) + ); + + $this->local_repositories[] = $local_repository; + + return $local_repository; + } + + private function makeWorkingDirectory(): void + { + $this->working_directory = $this->getUniqueTmpDirectory(); + + $manifest = [ + 'name' => 'test/something', + 'require' => (object) null, + 'repositories' => [ + [ + 'packagist.org' => false + ] + ] + ]; + + foreach($this->local_repositories as $repository) { + $manifest['repositories'][] = [ + 'type' => 'path', + 'url' => $repository, + 'canonical' => false, + ]; + } + + file_put_contents( + $this->working_directory.'/composer.json', + json_encode($manifest, JSON_UNESCAPED_SLASHES) + ); + } +}