* 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\Semver\Constraint\Constraint; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Semver\Constraint\MultiConstraint; use Symfony\Component\Console\Command\Command; use UnexpectedValueException; use InvalidArgumentException; use Composer\Test\TestCase; use Composer\Package\Link; use RuntimeException; use Generator; class BaseDependencyCommandTest extends TestCase { /** * Test that an exception is throw when there weren't provided some parameters * * @covers \Composer\Command\BaseDependencyCommand * @covers \Composer\Command\DependsCommand * @covers \Composer\Command\ProhibitsCommand * * @dataProvider noParametersCaseProvider * * @param array $parameters */ public function testExceptionWhenNoRequiredParameters( string $command, array $parameters, string $expectedExceptionMessage ): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessage($expectedExceptionMessage); $appTester = $this->getApplicationTester(); self::assertEquals(Command::FAILURE, $appTester->run(['command' => $command] + $parameters)); } /** * @return Generator, string}> */ public static function noParametersCaseProvider(): Generator { yield '`why` command without package parameter' => [ 'why', [], 'Not enough arguments (missing: "package").' ]; yield '`why-not` command without package and version parameters' => [ 'why-not', [], 'Not enough arguments (missing: "package, version").' ]; yield '`why-not` command without package parameter' => [ 'why-not', ['version' => '*'], 'Not enough arguments (missing: "package").' ]; yield '`why-not` command without version parameter' => [ 'why-not', ['package' => 'vendor1/package1'], 'Not enough arguments (missing: "version").' ]; } /** * Test that an exception is throw when there wasn't provided the locked file alongside `--locked` parameter * * @covers \Composer\Command\BaseDependencyCommand * @covers \Composer\Command\DependsCommand * @covers \Composer\Command\ProhibitsCommand * * @dataProvider caseProvider * * @param array $parameters */ public function testExceptionWhenRunningLockedWithoutLockFile(string $command, array $parameters): void { $this->initTempComposer(); $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('A valid composer.lock file is required to run this command with --locked'); $appTester = $this->getApplicationTester(); self::assertEquals( Command::FAILURE, $appTester->run(['command' => $command] + $parameters + ['--locked' => true] ) ); } /** * Test that an exception is throw when the provided package to be inspected isn't required by the project * * @covers \Composer\Command\BaseDependencyCommand * @covers \Composer\Command\DependsCommand * @covers \Composer\Command\ProhibitsCommand * * @dataProvider caseProvider * * @param array $parameters */ public function testExceptionWhenItCouldNotFoundThePackage(string $command, array $parameters): void { $packageToBeInspected = $parameters['package']; $this->initTempComposer(); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage(sprintf('Could not find package "%s" in your project', $packageToBeInspected)); $appTester = $this->getApplicationTester(); self::assertEquals( Command::FAILURE, $appTester->run(['command' => $command] + $parameters) ); } /** * Test that it shows a warning message when the package to be inspected wasn't found in the project * * @covers \Composer\Command\BaseDependencyCommand * @covers \Composer\Command\DependsCommand * @covers \Composer\Command\ProhibitsCommand * * @dataProvider caseProvider * * @param array $parameters */ public function testExceptionWhenPackageWasNotFoundInProject(string $command, array $parameters): void { $packageToBeInspected = $parameters['package']; $this->initTempComposer([ 'require' => [ 'vendor1/package2' => '1.*', 'vendor2/package1' => '2.*' ] ]); $firstRequiredPackage = self::getPackage('vendor1/package2'); $secondRequiredPackage = self::getPackage('vendor2/package1'); $this->createInstalledJson([$firstRequiredPackage, $secondRequiredPackage]); $this->createComposerLock([$firstRequiredPackage, $secondRequiredPackage]); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage(sprintf('Could not find package "%s" in your project', $packageToBeInspected)); $appTester = $this->getApplicationTester(); self::assertEquals(Command::FAILURE, $appTester->run(['command' => $command] + $parameters)); } /** * Test that it shows a warning message when the dependencies haven't been installed yet * * @covers \Composer\Command\BaseDependencyCommand * @covers \Composer\Command\DependsCommand * @covers \Composer\Command\ProhibitsCommand * * @dataProvider caseProvider * * @param array $parameters */ public function testWarningWhenDependenciesAreNotInstalled(string $command, array $parameters): void { $expectedWarningMessage = 'No dependencies installed. Try running composer install or update, or use --locked.'; $this->initTempComposer([ 'require' => [ 'vendor1/package1' => '1.*' ], 'require-dev' => [ 'vendor2/package1' => '2.*' ] ]); $someRequiredPackage = self::getPackage('vendor1/package1'); $someDevRequiredPackage = self::getPackage('vendor2/package1'); $this->createComposerLock([$someRequiredPackage], [$someDevRequiredPackage]); $appTester = $this->getApplicationTester(); $appTester->run(['command' => $command] + $parameters); self::assertSame($expectedWarningMessage, trim($appTester->getDisplay(true))); } /** * @return Generator}> */ public static function caseProvider(): Generator { yield '`why` command' => [ 'why', ['package' => 'vendor1/package1'] ]; yield '`why-not` command' => [ 'why-not', ['package' => 'vendor1/package1', 'version' => '1.*'] ]; } /** * Test that it finishes successfully and show some expected outputs depending on different command parameters * * @covers \Composer\Command\BaseDependencyCommand * @covers \Composer\Command\DependsCommand * * @dataProvider caseWhyProvider * * @param array $parameters */ public function testWhyCommandOutputs(array $parameters, string $expectedOutput, int $expectedStatusCode): void { $packageToBeInspected = $parameters['package']; $renderAsTree = $parameters['--tree'] ?? false; $renderRecursively = $parameters['--recursive'] ?? false; $this->initTempComposer([ 'repositories' => [ 'packages' => [ 'type' => 'package', 'package' => [ ['name' => 'vendor1/package1', 'version' => '1.3.0', 'require' => ['vendor1/package2' => '^2']], ['name' => 'vendor1/package2', 'version' => '2.3.0', 'require' => ['vendor1/package3' => '^1']], ['name' => 'vendor1/package3', 'version' => '2.1.0'] ], ], ], 'require' => [ 'vendor1/package2' => '1.3.0', 'vendor1/package3' => '2.3.0', ], 'require-dev' => [ 'vendor2/package1' => '2.*' ] ]); $firstRequiredPackage = self::getPackage('vendor1/package1', '1.3.0'); $firstRequiredPackage->setRequires([ 'vendor1/package2' => new Link( 'vendor1/package1', 'vendor1/package2', new MatchAllConstraint(), Link::TYPE_REQUIRE, '^2' ) ]); $secondRequiredPackage = self::getPackage('vendor1/package2', '2.3.0'); $secondRequiredPackage->setRequires([ 'vendor1/package3' => new Link( 'vendor1/package2', 'vendor1/package3', new MatchAllConstraint(), Link::TYPE_REQUIRE, '^1' ) ]); $thirdRequiredPackage = self::getPackage('vendor1/package3', '2.1.0'); $someDevRequiredPackage = self::getPackage('vendor2/package1'); $this->createComposerLock( [$firstRequiredPackage, $secondRequiredPackage, $thirdRequiredPackage], [$someDevRequiredPackage] ); $this->createInstalledJson( [$firstRequiredPackage, $secondRequiredPackage, $thirdRequiredPackage], [$someDevRequiredPackage] ); $appTester = $this->getApplicationTester(); $appTester->run([ 'command' => 'why', 'package' => $packageToBeInspected, '--tree' => $renderAsTree, '--recursive' => $renderRecursively, '--locked' => true ]); self::assertSame($expectedStatusCode, $appTester->getStatusCode()); self::assertEquals(trim($expectedOutput), $this->trimLines($appTester->getDisplay(true))); } /** * @return Generator, string}> */ public static function caseWhyProvider(): Generator { yield 'there is no installed package depending on the package' => [ ['package' => 'vendor1/package1'], 'There is no installed package depending on "vendor1/package1"', 1 ]; yield 'a nested package dependency' => [ ['package' => 'vendor1/package3'], << [ ['package' => 'vendor1/package3', '--tree' => true], << [ ['package' => 'vendor1/package3', '--recursive' => true], << [ ['package' => 'vendor2/package1'], '__root__ - requires (for development) vendor2/package1 (2.*)', 0 ]; } /** * Test that it finishes successfully and show some expected outputs depending on different command parameters * * @covers \Composer\Command\BaseDependencyCommand * @covers \Composer\Command\ProhibitsCommand * * @dataProvider caseWhyNotProvider * * @param array $parameters */ public function testWhyNotCommandOutputs(array $parameters, string $expectedOutput, int $expectedStatusCode): void { $packageToBeInspected = $parameters['package']; $packageVersionToBeInspected = $parameters['version']; $this->initTempComposer([ 'repositories' => [ 'packages' => [ 'type' => 'package', 'package' => [ ['name' => 'vendor1/package1', 'version' => '1.3.0'], ['name' => 'vendor2/package1', 'version' => '2.0.0'], ['name' => 'vendor2/package2', 'version' => '1.0.0', 'require' => ['vendor2/package3' => '1.4.*', 'php' => '^8.2']], ['name' => 'vendor2/package3', 'version' => '1.4.0'], ['name' => 'vendor2/package3', 'version' => '1.5.0'] ], ], ], 'require' => [ 'vendor1/package1' => '1.*', 'php' => '^8', ], 'require-dev' => [ 'vendor2/package1' => '2.*', 'vendor2/package2' => '^1' ], 'config' => [ 'platform' => [ 'php' => '8.3.2', ], ], ]); $someRequiredPackage = self::getPackage('vendor1/package1', '1.3.0'); $firstDevRequiredPackage = self::getPackage('vendor2/package1', '2.0.0'); $secondDevRequiredPackage = self::getPackage('vendor2/package2', '1.0.0'); $secondDevRequiredPackage->setRequires([ 'vendor2/package3' => new Link( 'vendor2/package2', 'vendor2/package3', new MatchAllConstraint(), Link::TYPE_REQUIRE, '1.4.*' ), 'php' => new Link( 'vendor2/package2', 'php', new MultiConstraint([self::getVersionConstraint('>=', '8.2.0.0'), self::getVersionConstraint('<', '9.0.0.0-dev')]), Link::TYPE_REQUIRE, '^8.2' ), ]); $secondDevNestedRequiredPackage = self::getPackage('vendor2/package3', '1.4.0'); $this->createComposerLock( [$someRequiredPackage], [$firstDevRequiredPackage, $secondDevRequiredPackage] ); $this->createInstalledJson( [$someRequiredPackage], [$firstDevRequiredPackage, $secondDevRequiredPackage, $secondDevNestedRequiredPackage] ); $appTester = $this->getApplicationTester(); $appTester->run([ 'command' => 'why-not', 'package' => $packageToBeInspected, 'version' => $packageVersionToBeInspected ]); self::assertSame($expectedStatusCode, $appTester->getStatusCode()); self::assertSame(trim($expectedOutput), $this->trimLines($appTester->getDisplay(true))); } /** * @return Generator, string}> */ public function caseWhyNotProvider(): Generator { yield 'it could not found the package with a specific version' => [ ['package' => 'vendor1/package1', 'version' => '3.*'], << [ ['package' => 'vendor1/package1', 'version' => '^1.4'], << [ ['package' => 'vendor1/package1', 'version' => '^1.3'], << [ ['package' => 'vendor2/package3', 'version' => '1.5.0'], << [ ['package' => 'php', 'version' => '^8'], << [ ['package' => 'php', 'version' => '9.1.0'], <<