createMock(ContainerInterface::class); $container->method('get')->willReturnCallback( /** @param class-string $service */ function (string $service): mixed { switch ($service) { case QuieterConsoleIO::class: return new QuieterConsoleIO( new ArrayInput([]), new BufferedOutput(), new MinimalHelperSet(['question' => new QuestionHelper()]), ); default: return $this->createMock($service); } }, ); $this->composerFactoryForProject = $this->createMock(ComposerFactoryForProject::class); $this->findMatchingPackages = $this->createMock(FindMatchingPackages::class); $this->installedPiePackages = $this->createMock(InstalledPiePackages::class); $this->installSelectedPackage = $this->createMock(InstallSelectedPackage::class); $this->installPiePackage = $this->createMock(InstallPiePackageFromPath::class); $this->questionHelper = $this->createMock(QuestionHelper::class); $cmd = new InstallExtensionsForProjectCommand( $this->composerFactoryForProject, new DetermineExtensionsRequired(), $this->installedPiePackages, $this->findMatchingPackages, $this->installSelectedPackage, $this->installPiePackage, $container, ); $cmd->setHelperSet(new HelperSet([ 'question' => $this->questionHelper, ])); $this->commandTester = new CommandTester($cmd); } public function testInstallingExtensionsForPhpProject(): 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'], ]); $this->questionHelper->method('ask')->willReturn('vendor1/foobar: The official foobar implementation'); $this->installSelectedPackage->expects(self::once()) ->method('withPieCli') ->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($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'); $rootPackage->setType(ExtensionType::PhpModule->value); $this->composerFactoryForProject->method('rootPackage')->willReturn($rootPackage); $this->installPiePackage ->expects(self::once()) ->method('__invoke') ->with( self::isInstanceOf(InstallExtensionsForProjectCommand::class), getcwd(), $rootPackage, self::isInstanceOf(PieJsonEditor::class), self::isInstanceOf(InputInterface::class), self::isInstanceOf(OutputInterface::class), ) ->willReturn(Command::SUCCESS); $this->commandTester->execute( [], ['verbosity' => BufferedOutput::VERBOSITY_VERY_VERBOSE], ); $this->commandTester->assertCommandIsSuccessful(); } }