diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 430ba14..6582e98 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,6 +11,9 @@ displayDetailsOnTestsThatTriggerWarnings="true" failOnRisky="true" failOnWarning="true"> + + + test/unit diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 9af22ad..e1c91d6 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -51,15 +51,15 @@ use const PHP_VERSION; /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ final class CommandHelper { - public const ARG_REQUESTED_PACKAGE_AND_VERSION = 'requested-package-and-version'; - public const OPTION_WITH_PHP_CONFIG = 'with-php-config'; - public const OPTION_WITH_PHP_PATH = 'with-php-path'; - public const OPTION_WITH_PHPIZE_PATH = 'with-phpize-path'; - public const OPTION_WORKING_DIRECTORY = 'working-dir'; + public const ARG_REQUESTED_PACKAGE_AND_VERSION = 'requested-package-and-version'; + public const OPTION_WITH_PHP_CONFIG = 'with-php-config'; + public const OPTION_WITH_PHP_PATH = 'with-php-path'; + public const OPTION_WITH_PHPIZE_PATH = 'with-phpize-path'; + public const OPTION_WORKING_DIRECTORY = 'working-dir'; public const OPTION_ALLOW_NON_INTERACTIVE_PROJECT_INSTALL = 'allow-non-interactive-project-install'; - private const OPTION_MAKE_PARALLEL_JOBS = 'make-parallel-jobs'; - private const OPTION_SKIP_ENABLE_EXTENSION = 'skip-enable-extension'; - private const OPTION_FORCE = 'force'; + private const OPTION_MAKE_PARALLEL_JOBS = 'make-parallel-jobs'; + private const OPTION_SKIP_ENABLE_EXTENSION = 'skip-enable-extension'; + private const OPTION_FORCE = 'force'; /** @psalm-suppress UnusedConstructor */ private function __construct() diff --git a/src/Command/InstallExtensionsForProjectCommand.php b/src/Command/InstallExtensionsForProjectCommand.php index 391063b..c84f258 100644 --- a/src/Command/InstallExtensionsForProjectCommand.php +++ b/src/Command/InstallExtensionsForProjectCommand.php @@ -25,20 +25,22 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ChoiceQuestion; use Throwable; +use function array_column; use function array_keys; use function array_map; use function array_merge; use function array_walk; use function assert; use function chdir; +use function count; use function getcwd; +use function implode; use function in_array; use function is_dir; use function is_string; @@ -131,7 +133,6 @@ final class InstallExtensionsForProjectCommand extends Command 'Aborting! You are not running in interactive mode, and --%s was not specified.', CommandHelper::OPTION_ALLOW_NON_INTERACTIVE_PROJECT_INSTALL, )); - // @todo more details return Command::FAILURE; } @@ -270,6 +271,7 @@ final class InstallExtensionsForProjectCommand extends Command return; } + $selectedPackageName = substr($selectedPackageAnswer, 0, (int) strpos($selectedPackageAnswer, ':')); } else { $selectedPackageName = $matches[0]['name']; diff --git a/test/integration/Command/InstallExtensionsForProjectCommandTest.php b/test/integration/Command/InstallExtensionsForProjectCommandTest.php index f4d6c7b..e05f594 100644 --- a/test/integration/Command/InstallExtensionsForProjectCommandTest.php +++ b/test/integration/Command/InstallExtensionsForProjectCommandTest.php @@ -101,10 +101,6 @@ final class InstallExtensionsForProjectCommandTest extends TestCase new Constraint('>=', '1.2.0.0-dev'), new Constraint('<', '2.0.0.0-dev'), ]), Link::TYPE_REQUIRE, '^1.2'), -// 'ext-mismatching' => new Link('my/project', 'ext-mismatching', new MultiConstraint([ -// new Constraint('>=', '2.0.0.0-dev'), -// new Constraint('<', '3.0.0.0-dev'), -// ]), Link::TYPE_REQUIRE, '^2.0'), ]); $this->composerFactoryForProject->method('rootPackage')->willReturn($rootPackage); @@ -119,20 +115,8 @@ final class InstallExtensionsForProjectCommandTest extends TestCase $this->composerFactoryForProject->method('composer')->willReturn($composer); -// $this->installedPiePackages->method('allPiePackages')->willReturn([ -// 'mismatching' => new Package( -// $this->createMock(CompletePackageInterface::class), -// ExtensionType::PhpModule, -// ExtensionName::normaliseFromString('mismatching'), -// 'vendor/mismatching', -// '1.9.3', -// null, -// ), -// ]); - $this->findMatchingPackages->method('for')->willReturn([ ['name' => 'vendor1/foobar', 'description' => 'The official foobar implementation'], - ['name' => 'vendor2/afoobar', 'description' => 'An improved async foobar extension'], ]); $this->questionHelper->method('ask')->willReturn('vendor1/foobar: The official foobar implementation'); @@ -142,18 +126,65 @@ final class InstallExtensionsForProjectCommandTest extends TestCase ->with('vendor1/foobar:^1.2'); $this->commandTester->execute( - [], + ['--allow-non-interactive-project-install' => true], ['verbosity' => BufferedOutput::VERBOSITY_VERY_VERBOSE], ); $outputString = $this->commandTester->getDisplay(); - $this->commandTester->assertCommandIsSuccessful(); + $this->commandTester->assertCommandIsSuccessful($outputString); self::assertStringContainsString('Checking extensions for your project my/project', $outputString); self::assertStringContainsString('requires: ext-standard:* ✅ Already installed', $outputString); self::assertStringContainsString('requires: ext-foobar:^1.2 🚫 Missing', $outputString); } + public function testInstallingExtensionsForPhpProjectWithMultipleMatches(): void + { + $rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3'); + $rootPackage->setRequires([ + 'ext-standard' => new Link('my/project', 'ext-standard', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*'), + 'ext-foobar' => new Link('my/project', 'ext-foobar', new MultiConstraint([ + new Constraint('>=', '1.2.0.0-dev'), + new Constraint('<', '2.0.0.0-dev'), + ]), Link::TYPE_REQUIRE, '^1.2'), + ]); + $this->composerFactoryForProject->method('rootPackage')->willReturn($rootPackage); + + $installedRepository = new InstalledArrayRepository([$rootPackage]); + + $repositoryManager = $this->createMock(RepositoryManager::class); + $repositoryManager->method('getLocalRepository')->willReturn($installedRepository); + + $composer = $this->createMock(Composer::class); + $composer->method('getPackage')->willReturn($rootPackage); + $composer->method('getRepositoryManager')->willReturn($repositoryManager); + + $this->composerFactoryForProject->method('composer')->willReturn($composer); + + $this->findMatchingPackages->method('for')->willReturn([ + ['name' => 'vendor1/foobar', 'description' => 'The official foobar implementation'], + ['name' => 'vendor2/afoobar', 'description' => 'An improved async foobar extension'], + ]); + + $this->questionHelper->method('ask')->willReturn('vendor1/foobar: The official foobar implementation'); + + $this->installSelectedPackage->expects(self::never()) + ->method('withPieCli'); + + $this->commandTester->execute( + ['--allow-non-interactive-project-install' => true], + ['verbosity' => BufferedOutput::VERBOSITY_VERY_VERBOSE], + ); + + $outputString = $this->commandTester->getDisplay(); + + self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode()); + self::assertStringContainsString('Checking extensions for your project my/project', $outputString); + self::assertStringContainsString('requires: ext-standard:* ✅ Already installed', $outputString); + self::assertStringContainsString('requires: ext-foobar:^1.2 🚫 Missing', $outputString); + self::assertStringContainsString('Multiple packages were found for ext-foobar', $outputString); + } + public function testInstallingExtensionsForPieProject(): void { $rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3');