diff --git a/src/Composer/Plugin/ExtraDownloadsPlugin.php b/src/Composer/Plugin/ExtraDownloadsPlugin.php new file mode 100644 index 0000000..7971cd0 --- /dev/null +++ b/src/Composer/Plugin/ExtraDownloadsPlugin.php @@ -0,0 +1,92 @@ + ['handlePackageEvent', self::EVENT_PRIORITY], + PackageEvents::POST_PACKAGE_UPDATE => ['handlePackageEvent', self::EVENT_PRIORITY], + ScriptEvents::POST_INSTALL_CMD => ['handleScriptEvent', self::EVENT_PRIORITY], + ScriptEvents::POST_UPDATE_CMD => ['handleScriptEvent', self::EVENT_PRIORITY], + ]; + } + + public function handleScriptEvent(Event $event): void + { + $rootPackage = $event->getComposer()->getPackage(); + $this->getHandler($event->getComposer(), $event->getIO())->handle($rootPackage); + + // Ensure that any other packages are properly reconciled. + $localRepo = $event->getComposer()->getRepositoryManager()->getLocalRepository(); + foreach ($localRepo->getCanonicalPackages() as $package) { + $this->getHandler($event->getComposer(), $event->getIO())->handle($package); + } + } + + public function handlePackageEvent(PackageEvent $event): void + { + $package = match (\get_class($event->getOperation())) { + InstallOperation::class => $event->getOperation()->getPackage(), + UpdateOperation::class => $event->getOperation()->getTargetPackage(), + default => throw new OutOfRangeException(sprintf('Operation %s not supported', $event->getOperation()->getOperationType())) + }; + $this->getHandler($event->getComposer(), $event->getIO())->handle($package); + } + + public function activate(Composer $composer, IOInterface $io): void + { + $this->addInstallers($composer, $io); + } + + public function deactivate(Composer $composer, IOInterface $io): void + { + } + + public function uninstall(Composer $composer, IOInterface $io): void + { + } + + private function addInstallers(Composer $composer, IOInterface $io): void + { + $installers = [ + new ArchiveInstaller($io, $composer), + new FileInstaller($io, $composer), + ]; + $installationManager = $composer->getInstallationManager(); + foreach ($installers as $installer) { + $installationManager->addInstaller($installer); + } + } + + private function getHandler(Composer $composer, IOInterface $io): PackageHandlerInterface + { + $this->handler ??= new PackageHandler($composer, $io); + + return $this->handler; + } +} diff --git a/src/Exception/OutOfRangeException.php b/src/Exception/OutOfRangeException.php index 4410ae8..08ba380 100644 --- a/src/Exception/OutOfRangeException.php +++ b/src/Exception/OutOfRangeException.php @@ -2,6 +2,6 @@ namespace LastCall\DownloadsPlugin\Exception; -class OutOfRangeException extends \OutOfRangeException +class OutOfRangeException extends BaseException { } diff --git a/src/Exception/UnexpectedValueException.php b/src/Exception/UnexpectedValueException.php index ddce5d3..2d54874 100644 --- a/src/Exception/UnexpectedValueException.php +++ b/src/Exception/UnexpectedValueException.php @@ -2,6 +2,6 @@ namespace LastCall\DownloadsPlugin\Exception; -class UnexpectedValueException extends \UnexpectedValueException +class UnexpectedValueException extends BaseException { } diff --git a/tests/Unit/Composer/Plugin/ExtraDownloadsPluginTest.php b/tests/Unit/Composer/Plugin/ExtraDownloadsPluginTest.php new file mode 100644 index 0000000..199c004 --- /dev/null +++ b/tests/Unit/Composer/Plugin/ExtraDownloadsPluginTest.php @@ -0,0 +1,163 @@ +handler = $this->createMock(PackageHandlerInterface::class); + $this->plugin = new ExtraDownloadsPlugin($this->handler); + $this->composer = $this->createMock(Composer::class); + $this->io = $this->createMock(IOInterface::class); + $this->installationManager = $this->createMock(InstallationManager::class); + } + + public function testGetSubscribedEvents(): void + { + $this->assertSame([ + PackageEvents::POST_PACKAGE_INSTALL => ['handlePackageEvent', 10], + PackageEvents::POST_PACKAGE_UPDATE => ['handlePackageEvent', 10], + ScriptEvents::POST_INSTALL_CMD => ['handleScriptEvent', 10], + ScriptEvents::POST_UPDATE_CMD => ['handleScriptEvent', 10], + ], ExtraDownloadsPlugin::getSubscribedEvents()); + } + + public function testActivate(): void + { + $this->composer + ->expects($this->once()) + ->method('getInstallationManager') + ->willReturn($this->installationManager); + $this->installationManager + ->expects($this->exactly(2)) + ->method('addInstaller') + ->withConsecutive( + [$this->isInstanceOf(ArchiveInstaller::class)], + [$this->isInstanceOf(FileInstaller::class)], + ); + $this->plugin->activate($this->composer, $this->io); + } + + public function testDeactivate(): void + { + $this->expectNotToPerformAssertions(); + $this->plugin->deactivate($this->composer, $this->io); + } + + public function testUninstall(): void + { + $this->expectNotToPerformAssertions(); + $this->plugin->uninstall($this->composer, $this->io); + } + + public function testHandleScriptEvent(): void + { + $rootPackage = $this->createMock(RootPackageInterface::class); + $this->composer->expects($this->once())->method('getPackage')->willReturn($rootPackage); + $repositoryManager = $this->createMock(RepositoryManager::class); + $this->composer->expects($this->once())->method('getRepositoryManager')->willReturn($repositoryManager); + $localRepository = $this->createMock(InstalledRepositoryInterface::class); + $repositoryManager->expects($this->once())->method('getLocalRepository')->willReturn($localRepository); + $packages = [ + $this->createMock(PackageInterface::class), + $this->createMock(PackageInterface::class), + $this->createMock(PackageInterface::class), + ]; + $localRepository->expects($this->once())->method('getCanonicalPackages')->willReturn($packages); + $this->handler + ->expects($this->exactly(\count($packages) + 1)) + ->method('handle') + ->withConsecutive( + [$rootPackage], + ...array_map(fn (PackageInterface $package) => [$package], $packages), + ); + $event = new Event('name', $this->composer, $this->io); + $this->plugin->handleScriptEvent($event); + } + + public function testHandleInstallPackageEvent(): void + { + $package = $this->createMock(PackageInterface::class); + $this->handler + ->expects($this->once()) + ->method('handle') + ->with($package); + $event = new PackageEvent( + 'install', + $this->composer, + $this->io, + false, + $this->createMock(RepositoryInterface::class), + [], + new InstallOperation($package) + ); + $this->plugin->handlePackageEvent($event); + } + + public function testHandleUpdatePackageEvent(): void + { + $initial = $this->createMock(PackageInterface::class); + $target = $this->createMock(PackageInterface::class); + $this->handler + ->expects($this->once()) + ->method('handle') + ->with($target); + $event = new PackageEvent( + 'update', + $this->composer, + $this->io, + false, + $this->createMock(RepositoryInterface::class), + [], + new UpdateOperation($initial, $target) + ); + $this->plugin->handlePackageEvent($event); + } + + public function testHandleUninstallPackageEvent(): void + { + $this->expectException(OutOfRangeException::class); + $this->expectExceptionMessage('Operation uninstall not supported'); + $package = $this->createMock(PackageInterface::class); + $event = new PackageEvent( + 'uninstall', + $this->composer, + $this->io, + false, + $this->createMock(RepositoryInterface::class), + [], + new UninstallOperation($package) + ); + $this->plugin->handlePackageEvent($event); + } +}