From 9f81e219b0caa2aa181679d99a9b77357d97c0be Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Mon, 11 Nov 2024 17:19:53 +0100 Subject: [PATCH 1/4] feat: add extension overrides, fixes #28 --- src/Config/ConfigFactory.php | 14 +- src/Config/ProjectConfiguration.php | 13 -- src/Config/ProjectExtensionManagement.php | 69 ++++++++- src/Services/AppHelper.php | 32 +++- src/Services/InstallationManager.php | 4 + src/Services/PluginHelper.php | 58 ++++++- src/Services/UpgradeManager.php | 4 + tests/Config/ConfigFactoryTest.php | 24 ++- tests/Config/ProjectConfigurationTest.php | 24 +-- .../Config/ProjectExtensionManagementTest.php | 146 ++++++++++++++++++ .../extension-override/.shopware-project.yml | 25 +++ tests/Services/AppHelperTest.php | 136 ++++++++++++++++ tests/Services/PluginHelperTest.php | 118 ++++++++++++++ 13 files changed, 631 insertions(+), 36 deletions(-) create mode 100644 tests/Config/ProjectExtensionManagementTest.php create mode 100644 tests/Config/_fixtures/extension-override/.shopware-project.yml diff --git a/src/Config/ConfigFactory.php b/src/Config/ConfigFactory.php index 9238f9d..d1b3dd3 100644 --- a/src/Config/ConfigFactory.php +++ b/src/Config/ConfigFactory.php @@ -111,7 +111,19 @@ public static function fillExtensionManagement(ProjectExtensionManagement $exten } if (isset($config['exclude']) && \is_array($config['exclude'])) { - $extensionManagement->excluded = $config['exclude']; + foreach ($config['exclude'] as $excludeExtension) { + $extensionManagement->overrides[(string) $excludeExtension] = ['state' => 'ignore']; + } + } + + if (isset($config['overrides']) && \is_array($config['overrides'])) { + foreach ($config['overrides'] as $extension => $override) { + if (isset($override['state']) && \is_string($override['state']) && \in_array($override['state'], ProjectExtensionManagement::ALLOWED_STATES, true)) { + $keepUserData = \array_key_exists('keepUserData', $override) && \is_bool($override['keepUserData']) && $override['keepUserData']; + + $extensionManagement->overrides[(string) $extension] = ['state' => $override['state'], 'keepUserData' => $keepUserData]; + } + } } } diff --git a/src/Config/ProjectConfiguration.php b/src/Config/ProjectConfiguration.php index 87ca7ef..5579067 100644 --- a/src/Config/ProjectConfiguration.php +++ b/src/Config/ProjectConfiguration.php @@ -28,17 +28,4 @@ public function __construct() $this->maintenance = new ProjectMaintenance(); $this->store = new ProjectStore(); } - - public function isExtensionManaged(string $name): bool - { - if (!$this->extensionManagement->enabled) { - return false; - } - - if (\in_array($name, $this->extensionManagement->excluded, true)) { - return false; - } - - return true; - } } diff --git a/src/Config/ProjectExtensionManagement.php b/src/Config/ProjectExtensionManagement.php index ddf5d0f..6859311 100644 --- a/src/Config/ProjectExtensionManagement.php +++ b/src/Config/ProjectExtensionManagement.php @@ -4,12 +4,77 @@ namespace Shopware\Deployment\Config; +/** + * @phpstan-type ExtensionOverride array{state: 'inactive'|'remove'|'ignore', keepUserData?: bool} + */ class ProjectExtensionManagement { + public const LIFECYCLE_STATE_INACTIVE = 'inactive'; + public const LIFECYCLE_STATE_REMOVE = 'remove'; + public const LIFECYCLE_STATE_IGNORE = 'ignore'; + + public const ALLOWED_STATES = [self::LIFECYCLE_STATE_IGNORE, self::LIFECYCLE_STATE_INACTIVE, self::LIFECYCLE_STATE_REMOVE]; + public bool $enabled = true; /** - * @var array + * The following extensions should be inactive. + * + * @var array */ - public array $excluded = []; + public array $overrides = []; + + public function canExtensionBeInstalled(string $name): bool + { + if (!$this->enabled) { + return false; + } + + $state = $this->getExtensionState($name); + + return !\in_array($state, [self::LIFECYCLE_STATE_REMOVE, self::LIFECYCLE_STATE_IGNORE], true); + } + + public function canExtensionBeActivated(string $name): bool + { + if (!$this->enabled) { + return false; + } + + $state = $this->getExtensionState($name); + + return !\in_array($state, [self::LIFECYCLE_STATE_INACTIVE, self::LIFECYCLE_STATE_REMOVE, self::LIFECYCLE_STATE_IGNORE], true); + } + + public function canExtensionBeRemoved(string $name): bool + { + if (!$this->enabled) { + return false; + } + + $state = $this->getExtensionState($name); + + return $state === self::LIFECYCLE_STATE_REMOVE; + } + + public function canExtensionBeDeactivated(string $name): bool + { + if (!$this->enabled) { + return false; + } + + return $this->getExtensionState($name) === self::LIFECYCLE_STATE_INACTIVE; + } + + /** + * @return ProjectExtensionManagement::LIFECYCLE_STATE_*|null + */ + private function getExtensionState(string $name): ?string + { + if (\array_key_exists($name, $this->overrides)) { + return $this->overrides[$name]['state']; + } + + return null; + } } diff --git a/src/Services/AppHelper.php b/src/Services/AppHelper.php index 55f1170..51140eb 100644 --- a/src/Services/AppHelper.php +++ b/src/Services/AppHelper.php @@ -26,12 +26,12 @@ public function installApps(): void $installed = $this->connection->fetchAllAssociativeIndexed('SELECT name, version, active FROM app'); foreach ($this->appLoader->all() as $app) { - if (!$this->configuration->isExtensionManaged($app['name'])) { + if (!$this->configuration->extensionManagement->canExtensionBeInstalled($app['name'])) { continue; } if (isset($installed[$app['name']])) { - if (!(bool) $installed[$app['name']]['active']) { + if (!(bool) $installed[$app['name']]['active'] && $this->configuration->extensionManagement->canExtensionBeActivated($app['name'])) { $this->processHelper->console(['app:activate', $app['name']]); } @@ -52,7 +52,7 @@ public function updateApps(): void $appNeedsToBeUpdated = false; foreach ($this->appLoader->all() as $app) { - if (!$this->configuration->isExtensionManaged($app['name'])) { + if (!$this->configuration->extensionManagement->canExtensionBeInstalled($app['name'])) { continue; } @@ -72,4 +72,30 @@ public function updateApps(): void $this->processHelper->console(['app:refresh', '--force']); } } + + public function deactivateApps(): void + { + $installed = $this->connection->fetchAllAssociative('SELECT name, version, active FROM app'); + + foreach ($installed as $app) { + if (!(bool) $app['active'] || !$this->configuration->extensionManagement->canExtensionBeDeactivated($app['name'])) { + continue; + } + + $this->processHelper->console(['app:deactivate', $app['name']]); + } + } + + public function removeApps(): void + { + $installed = $this->connection->fetchAllAssociative('SELECT name, version, active FROM app'); + + foreach ($installed as $app) { + if (!$this->configuration->extensionManagement->canExtensionBeRemoved($app['name'])) { + continue; + } + + $this->processHelper->console(['app:uninstall', $app['name']]); + } + } } diff --git a/src/Services/InstallationManager.php b/src/Services/InstallationManager.php index 9529c5e..4b16057 100644 --- a/src/Services/InstallationManager.php +++ b/src/Services/InstallationManager.php @@ -79,6 +79,8 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->processHelper->console(['plugin:refresh']); $this->pluginHelper->installPlugins($configuration->skipAssetsInstall); $this->pluginHelper->updatePlugins($configuration->skipAssetsInstall); + $this->pluginHelper->deactivatePlugins($configuration->skipAssetsInstall); + $this->pluginHelper->removePlugins($configuration->skipAssetsInstall); if ($this->configuration->store->licenseDomain !== '') { $this->accountService->refresh(new SymfonyStyle(new ArgvInput([]), $output), $this->state->getCurrentVersion(), $this->configuration->store->licenseDomain); @@ -86,6 +88,8 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->appHelper->installApps(); $this->appHelper->updateApps(); + $this->appHelper->deactivateApps(); + $this->appHelper->removeApps(); $this->hookExecutor->execute(HookExecutor::HOOK_POST_INSTALL); } diff --git a/src/Services/PluginHelper.php b/src/Services/PluginHelper.php index 78d0cbc..0476efd 100644 --- a/src/Services/PluginHelper.php +++ b/src/Services/PluginHelper.php @@ -25,7 +25,7 @@ public function installPlugins(bool $skipAssetsInstall = false): void } foreach ($this->pluginLoader->all() as $plugin) { - if (!$this->configuration->isExtensionManaged($plugin['name'])) { + if (!$this->configuration->extensionManagement->canExtensionBeInstalled($plugin['name'])) { continue; } @@ -35,12 +35,20 @@ public function installPlugins(bool $skipAssetsInstall = false): void // plugin is installed, but not active if ($plugin['installedAt'] !== null) { - $this->processHelper->console(['plugin:activate', $plugin['name'], ...$additionalParameters]); + if ($this->configuration->extensionManagement->canExtensionBeActivated($plugin['name'])) { + $this->processHelper->console(['plugin:activate', $plugin['name'], ...$additionalParameters]); + } continue; } - $this->processHelper->console(['plugin:install', $plugin['name'], '--activate', ...$additionalParameters]); + $activate = []; + + if ($this->configuration->extensionManagement->canExtensionBeActivated($plugin['name'])) { + $activate[] = '--activate'; + } + + $this->processHelper->console(['plugin:install', $plugin['name'], ...$activate, ...$additionalParameters]); } } @@ -53,7 +61,7 @@ public function updatePlugins(bool $skipAssetsInstall = false): void } foreach ($this->pluginLoader->all() as $plugin) { - if (!$this->configuration->isExtensionManaged($plugin['name'])) { + if (!$this->configuration->extensionManagement->canExtensionBeInstalled($plugin['name'])) { continue; } @@ -64,4 +72,46 @@ public function updatePlugins(bool $skipAssetsInstall = false): void $this->processHelper->console(['plugin:update', $plugin['name'], ...$additionalParameters]); } } + + public function deactivatePlugins(bool $skipAssetsInstall = false): void + { + $additionalParameters = []; + + if ($skipAssetsInstall) { + $additionalParameters[] = '--skip-asset-build'; + } + + foreach ($this->pluginLoader->all() as $plugin) { + if ($plugin['installedAt'] === null || !$plugin['active'] || !$this->configuration->extensionManagement->canExtensionBeDeactivated($plugin['name'])) { + continue; + } + + $this->processHelper->console(['plugin:deactivate', $plugin['name'], ...$additionalParameters]); + } + } + + public function removePlugins(bool $skipAssetsInstall = false): void + { + $additionalParameters = []; + + if ($skipAssetsInstall) { + $additionalParameters[] = '--skip-asset-build'; + } + + foreach ($this->pluginLoader->all() as $plugin) { + if ($plugin['installedAt'] === null || !$this->configuration->extensionManagement->canExtensionBeRemoved($plugin['name'])) { + continue; + } + + $keepUserData = $this->configuration->extensionManagement->overrides[$plugin['name']]['keepUserData'] ?? false; + + $deleteParameters = []; + + if ($keepUserData) { + $deleteParameters[] = '--keep-user-data'; + } + + $this->processHelper->console(['plugin:uninstall', $plugin['name'], ...$deleteParameters, ...$additionalParameters]); + } + } } diff --git a/src/Services/UpgradeManager.php b/src/Services/UpgradeManager.php index e8098ad..78a2fa6 100644 --- a/src/Services/UpgradeManager.php +++ b/src/Services/UpgradeManager.php @@ -66,6 +66,8 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->pluginHelper->installPlugins($configuration->skipAssetsInstall); $this->pluginHelper->updatePlugins($configuration->skipAssetsInstall); + $this->pluginHelper->deactivatePlugins($configuration->skipAssetsInstall); + $this->pluginHelper->removePlugins($configuration->skipAssetsInstall); if ($this->configuration->store->licenseDomain !== '') { $this->accountService->refresh(new SymfonyStyle(new ArgvInput([]), $output), $this->state->getCurrentVersion(), $this->configuration->store->licenseDomain); @@ -73,6 +75,8 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->appHelper->installApps(); $this->appHelper->updateApps(); + $this->appHelper->deactivateApps(); + $this->appHelper->removeApps(); if (!$configuration->skipThemeCompile) { $this->processHelper->console(['theme:compile', '--active-only']); diff --git a/tests/Config/ConfigFactoryTest.php b/tests/Config/ConfigFactoryTest.php index ca7e681..0a80f8d 100644 --- a/tests/Config/ConfigFactoryTest.php +++ b/tests/Config/ConfigFactoryTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Shopware\Deployment\Config\ConfigFactory; +use Shopware\Deployment\Config\ProjectExtensionManagement; use Zalas\PHPUnit\Globals\Attribute\Env; #[CoversClass(ConfigFactory::class)] @@ -18,7 +19,7 @@ public function testCreateConfigWithoutFile(): void $config = ConfigFactory::create(__DIR__); static::assertFalse($config->maintenance->enabled); static::assertTrue($config->extensionManagement->enabled); - static::assertSame([], $config->extensionManagement->excluded); + static::assertSame([], $config->extensionManagement->overrides); static::assertSame([], $config->oneTimeTasks); static::assertSame('', $config->hooks->pre); } @@ -34,7 +35,7 @@ public function testExistingConfigTest(string $configDir): void { $config = ConfigFactory::create($configDir); static::assertTrue($config->extensionManagement->enabled); - static::assertSame(['Name'], $config->extensionManagement->excluded); + static::assertSame('ignore', $config->extensionManagement->overrides['Name']['state']); static::assertSame(['foo' => 'test'], $config->oneTimeTasks); static::assertNotSame('', $config->hooks->pre); static::assertNotSame('', $config->hooks->post); @@ -57,7 +58,7 @@ public function testLicenseDomainPopulatedByEnv(): void static::assertSame('test', $config->store->licenseDomain); } - public function testExistingConfigWithStoreCOnfig(): void + public function testExistingConfigWithStoreConfig(): void { $config = ConfigFactory::create(__DIR__ . '/_fixtures/license-domain'); static::assertSame('example.com', $config->store->licenseDomain); @@ -68,4 +69,21 @@ public function testExistingConfigWithAlwaysClearCache(): void $config = ConfigFactory::create(__DIR__ . '/_fixtures/always-clear-cache'); static::assertTrue($config->alwaysClearCache); } + + public function testExistingConfigWithExtensionOverride(): void + { + $config = ConfigFactory::create(__DIR__ . '/_fixtures/extension-override'); + static::assertNotEmpty($config->extensionManagement->overrides); + + // Test FroshTest (without keepUserData) + static::assertArrayHasKey('FroshTest', $config->extensionManagement->overrides); + static::assertSame(ProjectExtensionManagement::LIFECYCLE_STATE_REMOVE, $config->extensionManagement->overrides['FroshTest']['state']); + static::assertArrayHasKey('keepUserData', $config->extensionManagement->overrides['FroshTest']); + + // Test FroshTest2 (with keepUserData) + static::assertArrayHasKey('FroshTest2', $config->extensionManagement->overrides); + static::assertSame(ProjectExtensionManagement::LIFECYCLE_STATE_REMOVE, $config->extensionManagement->overrides['FroshTest2']['state']); + static::assertArrayHasKey('keepUserData', $config->extensionManagement->overrides['FroshTest2']); + static::assertTrue($config->extensionManagement->overrides['FroshTest2']['keepUserData']); + } } diff --git a/tests/Config/ProjectConfigurationTest.php b/tests/Config/ProjectConfigurationTest.php index ca10faf..2304cfc 100644 --- a/tests/Config/ProjectConfigurationTest.php +++ b/tests/Config/ProjectConfigurationTest.php @@ -7,26 +7,30 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Shopware\Deployment\Config\ProjectConfiguration; -use Shopware\Deployment\Config\ProjectExtensionManagement; use Shopware\Deployment\Config\ProjectHooks; +use Shopware\Deployment\Config\ProjectMaintenance; +use Shopware\Deployment\Config\ProjectStore; #[CoversClass(ProjectConfiguration::class)] -#[CoversClass(ProjectExtensionManagement::class)] #[CoversClass(ProjectHooks::class)] +#[CoversClass(ProjectMaintenance::class)] +#[CoversClass(ProjectStore::class)] class ProjectConfigurationTest extends TestCase { - public function testExtensionManaged(): void + public function testConstructor(): void { $config = new ProjectConfiguration(); - static::assertTrue($config->isExtensionManaged('some-extension')); + static::assertEmpty($config->hooks->pre); + static::assertEmpty($config->hooks->post); + static::assertEmpty($config->hooks->preInstall); + static::assertEmpty($config->hooks->postInstall); + static::assertEmpty($config->hooks->preUpdate); + static::assertEmpty($config->hooks->postUpdate); - $config->extensionManagement->excluded = ['some-extension']; - static::assertFalse($config->isExtensionManaged('some-extension')); - static::assertTrue($config->isExtensionManaged('some-extension2')); + static::assertTrue($config->extensionManagement->enabled); + static::assertEmpty($config->extensionManagement->overrides); - $config->extensionManagement->enabled = false; - - static::assertFalse($config->isExtensionManaged('other-extension')); + static::assertEmpty($config->store->licenseDomain); } } diff --git a/tests/Config/ProjectExtensionManagementTest.php b/tests/Config/ProjectExtensionManagementTest.php new file mode 100644 index 0000000..e35dcf8 --- /dev/null +++ b/tests/Config/ProjectExtensionManagementTest.php @@ -0,0 +1,146 @@ +management = new ProjectExtensionManagement(); + } + + public function testDefaultEnabled(): void + { + self::assertTrue($this->management->enabled); + } + + public function testDefaultOverridesEmpty(): void + { + self::assertEmpty($this->management->overrides); + } + + /** + * @param array $overrides + */ + #[DataProvider('provideCanExtensionBeInstalledCases')] + public function testCanExtensionBeInstalled(bool $enabled, array $overrides, string $extensionName, bool $expected): void + { + $this->management->enabled = $enabled; + $this->management->overrides = $overrides; + + self::assertSame($expected, $this->management->canExtensionBeInstalled($extensionName)); + } + + /** + * @return array, string, bool}> + */ + public static function provideCanExtensionBeInstalledCases(): array + { + return [ + 'disabled_management' => [false, [], 'test', false], + 'no_override' => [true, [], 'test', true], + 'ignore_state' => [true, ['test' => ['state' => 'ignore']], 'test', false], + 'remove_state' => [true, ['test' => ['state' => 'remove']], 'test', false], + 'inactive_state' => [true, ['test' => ['state' => 'inactive']], 'test', true], + 'unknown_extension' => [true, ['other' => ['state' => 'ignore']], 'test', true], + ]; + } + + /** + * @param array $overrides + */ + #[DataProvider('provideCanExtensionBeActivatedCases')] + public function testCanExtensionBeActivated(bool $enabled, array $overrides, string $extensionName, bool $expected): void + { + $this->management->enabled = $enabled; + $this->management->overrides = $overrides; + + self::assertSame($expected, $this->management->canExtensionBeActivated($extensionName)); + } + + /** + * @return array, string, bool}> + */ + public static function provideCanExtensionBeActivatedCases(): array + { + return [ + 'disabled_management' => [false, [], 'test', false], + 'no_override' => [true, [], 'test', true], + 'ignore_state' => [true, ['test' => ['state' => 'ignore']], 'test', false], + 'remove_state' => [true, ['test' => ['state' => 'remove']], 'test', false], + 'inactive_state' => [true, ['test' => ['state' => 'inactive']], 'test', false], + 'unknown_extension' => [true, ['other' => ['state' => 'ignore']], 'test', true], + ]; + } + + /** + * @param array $overrides + */ + #[DataProvider('provideCanExtensionBeRemovedCases')] + public function testCanExtensionBeRemoved(bool $enabled, array $overrides, string $extensionName, bool $expected): void + { + $this->management->enabled = $enabled; + $this->management->overrides = $overrides; + + self::assertSame($expected, $this->management->canExtensionBeRemoved($extensionName)); + } + + /** + * @return array, string, bool}> + */ + public static function provideCanExtensionBeRemovedCases(): array + { + return [ + 'disabled_management' => [false, [], 'test', false], + 'no_override' => [true, [], 'test', false], + 'ignore_state' => [true, ['test' => ['state' => 'ignore']], 'test', false], + 'remove_state' => [true, ['test' => ['state' => 'remove']], 'test', true], + 'inactive_state' => [true, ['test' => ['state' => 'inactive']], 'test', false], + 'unknown_extension' => [true, ['other' => ['state' => 'remove']], 'test', false], + ]; + } + + /** + * @param array $overrides + */ + #[DataProvider('provideCanExtensionBeDeactivatedCases')] + public function testCanExtensionBeDeactivated(bool $enabled, array $overrides, string $extensionName, bool $expected): void + { + $this->management->enabled = $enabled; + $this->management->overrides = $overrides; + + self::assertSame($expected, $this->management->canExtensionBeDeactivated($extensionName)); + } + + /** + * @return array, string, bool}> + */ + public static function provideCanExtensionBeDeactivatedCases(): array + { + return [ + 'disabled_management' => [false, [], 'test', false], + 'no_override' => [true, [], 'test', false], + 'ignore_state' => [true, ['test' => ['state' => 'ignore']], 'test', false], + 'remove_state' => [true, ['test' => ['state' => 'remove']], 'test', false], + 'inactive_state' => [true, ['test' => ['state' => 'inactive']], 'test', true], + 'unknown_extension' => [true, ['other' => ['state' => 'inactive']], 'test', false], + ]; + } + + public function testAllowedStatesConstant(): void + { + /** @var array $expectedStates */ + $expectedStates = ['ignore', 'inactive', 'remove']; + self::assertSame($expectedStates, ProjectExtensionManagement::ALLOWED_STATES); + } +} diff --git a/tests/Config/_fixtures/extension-override/.shopware-project.yml b/tests/Config/_fixtures/extension-override/.shopware-project.yml new file mode 100644 index 0000000..eedf0e0 --- /dev/null +++ b/tests/Config/_fixtures/extension-override/.shopware-project.yml @@ -0,0 +1,25 @@ +deployment: + hooks: + pre: | + echo "Before deployment general" + post: | + echo "After deployment general" + pre-install: | + echo "Before running system:install" + post-install: | + echo "After running system:install" + pre-update: | + echo "Before running system:update" + post-update: | + echo "After running system:update" + + # Automatically installs and updates all extensions + extension-management: + enabled: true + + overrides: + FroshTest: + state: remove + FroshTest2: + state: remove + keepUserData: true \ No newline at end of file diff --git a/tests/Services/AppHelperTest.php b/tests/Services/AppHelperTest.php index c5b48c2..3b9206b 100644 --- a/tests/Services/AppHelperTest.php +++ b/tests/Services/AppHelperTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Shopware\Deployment\Config\ProjectConfiguration; +use Shopware\Deployment\Config\ProjectExtensionManagement; use Shopware\Deployment\Helper\ProcessHelper; use Shopware\Deployment\Services\AppHelper; use Shopware\Deployment\Services\AppLoader; @@ -175,6 +176,141 @@ public function testUpdate(): void $appHelper->updateApps(); } + public function testDeactivate(): void + { + $config = new ProjectConfiguration(); + $config->extensionManagement->overrides['TestApp'] = ['state' => ProjectExtensionManagement::LIFECYCLE_STATE_INACTIVE]; + + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper + ->expects($this->once()) + ->method('console') + ->with(['app:deactivate', 'TestApp']); + + $connection = $this->createMock(Connection::class); + $connection + ->method('fetchAllAssociative') + ->willReturn([ + ['name' => 'TestApp', 'version' => '0.0.1', 'active' => true], + ]); + + $appHelper = new AppHelper( + $this->createAppLoader(), + $processHelper, + $connection, + $config, + ); + + $appHelper->deactivateApps(); + } + + public function testDeactivateDoesNothingWhenDeactivated(): void + { + $config = new ProjectConfiguration(); + $config->extensionManagement->overrides['TestApp'] = ['state' => ProjectExtensionManagement::LIFECYCLE_STATE_INACTIVE]; + + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper + ->expects($this->never()) + ->method('console'); + + $connection = $this->createMock(Connection::class); + $connection + ->method('fetchAllAssociative') + ->willReturn([ + ['name' => 'TestApp', 'version' => '0.0.1', 'active' => false], + ]); + + $appHelper = new AppHelper( + $this->createAppLoader(), + $processHelper, + $connection, + $config, + ); + + $appHelper->deactivateApps(); + } + + public function testDeactivateDoesNothingWhenAppIsFine(): void + { + $config = new ProjectConfiguration(); + + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper + ->expects($this->never()) + ->method('console'); + + $connection = $this->createMock(Connection::class); + $connection + ->method('fetchAllAssociative') + ->willReturn([ + ['name' => 'TestApp', 'version' => '0.0.1', 'active' => true], + ]); + + $appHelper = new AppHelper( + $this->createAppLoader(), + $processHelper, + $connection, + $config, + ); + + $appHelper->deactivateApps(); + } + + public function testUninstall(): void + { + $config = new ProjectConfiguration(); + $config->extensionManagement->overrides['TestApp'] = ['state' => 'remove']; + + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper + ->expects($this->once()) + ->method('console') + ->with(['app:uninstall', 'TestApp']); + + $connection = $this->createMock(Connection::class); + $connection + ->method('fetchAllAssociative') + ->willReturn([ + ['name' => 'TestApp', 'version' => '0.0.1', 'active' => true], + ]); + + $appHelper = new AppHelper( + $this->createAppLoader(), + $processHelper, + $connection, + $config, + ); + + $appHelper->removeApps(); + } + + public function testUninstallWhenNotMatching(): void + { + $config = new ProjectConfiguration(); + + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper + ->expects($this->never()) + ->method('console'); + + $connection = $this->createMock(Connection::class); + $connection + ->method('fetchAllAssociative') + ->willReturn([ + ['name' => 'TestApp', 'version' => '0.0.1', 'active' => true], + ]); + + $appHelper = new AppHelper( + $this->createAppLoader(), + $processHelper, + $connection, + $config, + ); + + $appHelper->removeApps(); + } + public function createAppLoader(): AppLoader&MockObject { $appLoader = $this->createMock(AppLoader::class); diff --git a/tests/Services/PluginHelperTest.php b/tests/Services/PluginHelperTest.php index 1bbd3fb..8cb7568 100644 --- a/tests/Services/PluginHelperTest.php +++ b/tests/Services/PluginHelperTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Shopware\Deployment\Config\ProjectConfiguration; +use Shopware\Deployment\Config\ProjectExtensionManagement; use Shopware\Deployment\Helper\ProcessHelper; use Shopware\Deployment\Services\PluginHelper; use Shopware\Deployment\Services\PluginLoader; @@ -147,6 +148,123 @@ public function testUpdateDisableAssetBuild(): void $helper->updatePlugins(true); } + public function testInactive(): void + { + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->once())->method('console')->with(['plugin:deactivate', 'TestPlugin']); + + $configuration = new ProjectConfiguration(); + $configuration->extensionManagement->overrides['TestPlugin'] = ['state' => ProjectExtensionManagement::LIFECYCLE_STATE_INACTIVE]; + + $helper = new PluginHelper( + $this->getPluginLoader(), + $processHelper, + $configuration, + ); + + $helper->deactivatePlugins(); + } + + public function testInactiveWithDisabledAssets(): void + { + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->once())->method('console')->with(['plugin:deactivate', 'TestPlugin', '--skip-asset-build']); + + $configuration = new ProjectConfiguration(); + $configuration->extensionManagement->overrides['TestPlugin'] = ['state' => ProjectExtensionManagement::LIFECYCLE_STATE_INACTIVE]; + + $helper = new PluginHelper( + $this->getPluginLoader(), + $processHelper, + $configuration, + ); + + $helper->deactivatePlugins(true); + } + + public function testInactiveNotMatching(): void + { + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->never())->method('console'); + + $configuration = new ProjectConfiguration(); + + $helper = new PluginHelper( + $this->getPluginLoader(), + $processHelper, + $configuration, + ); + + $helper->deactivatePlugins(); + } + + public function testUninstall(): void + { + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->once())->method('console')->with(['plugin:uninstall', 'TestPlugin']); + + $configuration = new ProjectConfiguration(); + $configuration->extensionManagement->overrides['TestPlugin'] = ['state' => ProjectExtensionManagement::LIFECYCLE_STATE_REMOVE]; + + $helper = new PluginHelper( + $this->getPluginLoader(), + $processHelper, + $configuration, + ); + + $helper->removePlugins(); + } + + public function testUninstallWithDisabledAssets(): void + { + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->once())->method('console')->with(['plugin:uninstall', 'TestPlugin', '--skip-asset-build']); + + $configuration = new ProjectConfiguration(); + $configuration->extensionManagement->overrides['TestPlugin'] = ['state' => ProjectExtensionManagement::LIFECYCLE_STATE_REMOVE]; + + $helper = new PluginHelper( + $this->getPluginLoader(), + $processHelper, + $configuration, + ); + + $helper->removePlugins(true); + } + + public function testUninstallWithDisabledAssetsKeepData(): void + { + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->once())->method('console')->with(['plugin:uninstall', 'TestPlugin', '--keep-user-data', '--skip-asset-build']); + + $configuration = new ProjectConfiguration(); + $configuration->extensionManagement->overrides['TestPlugin'] = ['state' => ProjectExtensionManagement::LIFECYCLE_STATE_REMOVE, 'keepUserData' => true]; + + $helper = new PluginHelper( + $this->getPluginLoader(), + $processHelper, + $configuration, + ); + + $helper->removePlugins(true); + } + + public function testUninstallNotMatching(): void + { + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->never())->method('console'); + + $configuration = new ProjectConfiguration(); + + $helper = new PluginHelper( + $this->getPluginLoader(), + $processHelper, + $configuration, + ); + + $helper->removePlugins(true); + } + public function getPluginLoader(bool $active = true, ?string $installedAt = 'test', ?string $upgradeVersion = null): PluginLoader&MockObject { $loader = $this->createMock(PluginLoader::class); From 701f82ed63edcd487b306c091cad9d51227f383f Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Tue, 12 Nov 2024 09:35:04 +0100 Subject: [PATCH 2/4] feat: update to phpstan 2 --- composer.json | 10 +++++----- phpstan.neon.dist | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 8424798..6a7120c 100644 --- a/composer.json +++ b/composer.json @@ -27,11 +27,11 @@ "require-dev": { "friendsofphp/php-cs-fixer": "v3.64.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.4", - "phpstan/phpstan-deprecation-rules": "1.2.1", - "phpstan/phpstan-phpunit": "1.4.0", - "phpstan/phpstan-strict-rules": "1.6.1", - "phpstan/phpstan-symfony": "1.4.9", + "phpstan/phpstan": "2.0.1", + "phpstan/phpstan-deprecation-rules": "2.0.0", + "phpstan/phpstan-phpunit": "2.0.0", + "phpstan/phpstan-strict-rules": "2.0.0", + "phpstan/phpstan-symfony": "2.0.0", "phpunit/phpunit": "~11.3", "symfony/var-dumper": "^7.0 || ^6.0", "zalas/phpunit-globals": "^3.3" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 6d888a6..9250b2f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -5,7 +5,6 @@ parameters: phpVersion: 80200 level: 8 treatPhpDocTypesAsCertain: false - checkMissingIterableValueType: true inferPrivatePropertyTypeFromConstructor: true tmpDir: var/cache/phpstan paths: From feff071364949dd17f9584baf572120abdcdbe59 Mon Sep 17 00:00:00 2001 From: Shyim Date: Tue, 12 Nov 2024 10:54:36 +0100 Subject: [PATCH 3/4] Update src/Config/ProjectExtensionManagement.php Co-authored-by: Michael Telgmann --- src/Config/ProjectExtensionManagement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config/ProjectExtensionManagement.php b/src/Config/ProjectExtensionManagement.php index 6859311..70d4a85 100644 --- a/src/Config/ProjectExtensionManagement.php +++ b/src/Config/ProjectExtensionManagement.php @@ -5,7 +5,7 @@ namespace Shopware\Deployment\Config; /** - * @phpstan-type ExtensionOverride array{state: 'inactive'|'remove'|'ignore', keepUserData?: bool} + * @phpstan-type ExtensionOverride array{state: self::LIFECYCLE_STATE_*, keepUserData?: bool} */ class ProjectExtensionManagement { From 5753c9cfc7a8124a2f951fff20df1c23b75972a1 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Tue, 12 Nov 2024 10:55:28 +0100 Subject: [PATCH 4/4] fix: rename parameter --- src/Services/PluginHelper.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Services/PluginHelper.php b/src/Services/PluginHelper.php index 0476efd..c084a43 100644 --- a/src/Services/PluginHelper.php +++ b/src/Services/PluginHelper.php @@ -105,13 +105,13 @@ public function removePlugins(bool $skipAssetsInstall = false): void $keepUserData = $this->configuration->extensionManagement->overrides[$plugin['name']]['keepUserData'] ?? false; - $deleteParameters = []; + $uninstallParameters = []; if ($keepUserData) { - $deleteParameters[] = '--keep-user-data'; + $uninstallParameters[] = '--keep-user-data'; } - $this->processHelper->console(['plugin:uninstall', $plugin['name'], ...$deleteParameters, ...$additionalParameters]); + $this->processHelper->console(['plugin:uninstall', $plugin['name'], ...$uninstallParameters, ...$additionalParameters]); } } }