diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 06ea1696c..1a2862662 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -32,7 +32,7 @@ jobs: run: | git fetch --depth=1 origin $GITHUB_BASE_REF # Explicitly specify GitHub logger since our Stryker reporting will otherwise disable it. - php vendor/bin/infection --threads=max --git-diff-lines --git-diff-base=origin/$GITHUB_BASE_REF --logger-github --only-covered + php vendor/bin/infection --threads=max --git-diff-lines --git-diff-base=origin/$GITHUB_BASE_REF --logger-github --only-covered --min-covered-msi=100 - name: Run Infection for all files if: github.event_name == 'push' diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 3a02d6a2a..5dc96172c 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -9,16 +9,18 @@ use AcquiaCloudApi\Response\DatabasesResponse; use AcquiaCloudApi\Response\EnvironmentResponse; use AcquiaCloudApi\Response\EnvironmentsResponse; + use AcquiaCloudApi\Response\IdeResponse; override(\Acquia\Cli\Tests\TestBase::mockRequest(), map([ 'getAccount' => AccountResponse::class, 'getApplications' => ApplicationsResponse::class, 'getApplicationByUuid' => ApplicationResponse::class, 'getApplicationEnvironments' => EnvironmentsResponse::class, - 'getEnvironmentsDatabases' => DatabasesResponse::class, - 'getEnvironment' => EnvironmentResponse::class, 'getCron' => CronResponse::class, - 'getCronJobsByEnvironmentId' => CronsResponse::class + 'getCronJobsByEnvironmentId' => CronsResponse::class, + 'getEnvironment' => EnvironmentResponse::class, + 'getEnvironmentsDatabases' => DatabasesResponse::class, + 'getIde' => IdeResponse::class ])); } diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 289740424..42d5ab9aa 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -1085,10 +1085,10 @@ protected function requireCloudIdeEnvironment(): void { /** * @return \stdClass|null */ - protected function findIdeSshKeyOnCloud(string $ideUuid): ?stdClass { + protected function findIdeSshKeyOnCloud(string $ideLabel, string $ideUuid): ?stdClass { $acquiaCloudClient = $this->cloudApiClientService->getClient(); $cloudKeys = $acquiaCloudClient->request('get', '/account/ssh-keys'); - $sshKeyLabel = SshKeyCommandBase::getIdeSshKeyLabel(); + $sshKeyLabel = SshKeyCommandBase::getIdeSshKeyLabel($ideLabel, $ideUuid); foreach ($cloudKeys as $cloudKey) { if ($cloudKey->label === $sshKeyLabel) { return $cloudKey; diff --git a/src/Command/Ide/IdeCommandBase.php b/src/Command/Ide/IdeCommandBase.php index a7f09bc71..3c07ab03d 100644 --- a/src/Command/Ide/IdeCommandBase.php +++ b/src/Command/Ide/IdeCommandBase.php @@ -20,7 +20,7 @@ protected function promptIdeChoice( string $questionText, Ides $idesResource, string $cloudApplicationUuid - ): ?IdeResponse { + ): IdeResponse { $ides = iterator_to_array($idesResource->getAll($cloudApplicationUuid)); if (empty($ides)) { throw new AcquiaCliException('No IDEs exist for this application.'); diff --git a/src/Command/Ide/IdeDeleteCommand.php b/src/Command/Ide/IdeDeleteCommand.php index aaae9d213..e90ea2af4 100644 --- a/src/Command/Ide/IdeDeleteCommand.php +++ b/src/Command/Ide/IdeDeleteCommand.php @@ -10,6 +10,7 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; #[RequireAuth] @@ -20,27 +21,32 @@ final class IdeDeleteCommand extends IdeCommandBase { protected function configure(): void { $this->acceptApplicationUuid(); - // @todo Add option to accept an ide UUID. + // @todo make this an argument + $this->addOption('uuid', NULL, InputOption::VALUE_OPTIONAL, 'UUID of the IDE to delete'); } protected function execute(InputInterface $input, OutputInterface $output): int { $acquiaCloudClient = $this->cloudApiClientService->getClient(); $idesResource = new Ides($acquiaCloudClient); - $cloudApplicationUuid = $this->determineCloudApplication(); - $ide = $this->promptIdeChoice("Select the IDE you'd like to delete:", $idesResource, $cloudApplicationUuid); - $answer = $this->io->confirm("Are you sure you want to delete {$ide->label}"); - if (!$answer) { - $this->io->writeln('Ok, nevermind.'); - return 1; + $ideUuid = $input->getOption('uuid'); + if ($ideUuid) { + $ide = $idesResource->get($ideUuid); + } + else { + $cloudApplicationUuid = $this->determineCloudApplication(); + $ide = $this->promptIdeChoice("Select the IDE you'd like to delete:", $idesResource, $cloudApplicationUuid); + $answer = $this->io->confirm("Are you sure you want to delete $ide->label"); + if (!$answer) { + $this->io->writeln('Ok, never mind.'); + return Command::FAILURE; + } } $response = $idesResource->delete($ide->uuid); $this->io->writeln($response->message); - // @todo Remove after CXAPI-8261 is closed. - $this->io->writeln("This process usually takes a few minutes."); // Check to see if an SSH key for this IDE exists on Cloud. - $cloudKey = $this->findIdeSshKeyOnCloud($ide->uuid); + $cloudKey = $this->findIdeSshKeyOnCloud($ide->label, $ide->uuid); if ($cloudKey) { $answer = $this->io->confirm('Would you like to delete the SSH key associated with this IDE from your Cloud Platform account?'); if ($answer) { diff --git a/src/Command/Ide/Wizard/IdeWizardCommandBase.php b/src/Command/Ide/Wizard/IdeWizardCommandBase.php index 2f122ca5c..43cccbb19 100644 --- a/src/Command/Ide/Wizard/IdeWizardCommandBase.php +++ b/src/Command/Ide/Wizard/IdeWizardCommandBase.php @@ -37,11 +37,11 @@ protected function validateEnvironment(): void { } protected function getSshKeyLabel(): string { - return $this::getIdeSshKeyLabel(); + return $this::getIdeSshKeyLabel(self::getThisCloudIdeLabel(), self::getThisCloudIdeUuid()); } protected function deleteThisSshKeyFromCloud(mixed $output): void { - if ($cloudKey = $this->findIdeSshKeyOnCloud($this::getThisCloudIdeUuid())) { + if ($cloudKey = $this->findIdeSshKeyOnCloud($this::getThisCloudIdeLabel(), $this::getThisCloudIdeUuid())) { $this->deleteSshKeyFromCloud($output, $cloudKey); } } diff --git a/src/Command/Ide/Wizard/IdeWizardDeleteSshKeyCommand.php b/src/Command/Ide/Wizard/IdeWizardDeleteSshKeyCommand.php index 97a4c87bf..d1a07faa9 100644 --- a/src/Command/Ide/Wizard/IdeWizardDeleteSshKeyCommand.php +++ b/src/Command/Ide/Wizard/IdeWizardDeleteSshKeyCommand.php @@ -27,7 +27,7 @@ protected function configure(): void { protected function execute(InputInterface $input, OutputInterface $output): int { $this->requireCloudIdeEnvironment(); - $cloudKey = $this->findIdeSshKeyOnCloud($this::getThisCloudIdeUuid()); + $cloudKey = $this->findIdeSshKeyOnCloud($this::getThisCloudIdeLabel(), $this::getThisCloudIdeUuid()); if (!$cloudKey) { throw new AcquiaCliException('Could not find an SSH key on the Cloud Platform matching any local key in this IDE.'); } diff --git a/src/Command/Ssh/SshKeyCommandBase.php b/src/Command/Ssh/SshKeyCommandBase.php index 5db132c7c..83fdce38d 100644 --- a/src/Command/Ssh/SshKeyCommandBase.php +++ b/src/Command/Ssh/SshKeyCommandBase.php @@ -39,16 +39,16 @@ protected function setSshKeyFilepath(string $privateSshKeyFilename): void { $this->publicSshKeyFilepath = $this->privateSshKeyFilepath . '.pub'; } - public static function getIdeSshKeyLabel(): string { - return self::normalizeSshKeyLabel('IDE_' . self::getThisCloudIdeLabel() . '_' . self::getThisCloudIdeUuid()); + protected static function getIdeSshKeyLabel(string $ideLabel, string $ideUuid): string { + return self::normalizeSshKeyLabel('IDE_' . $ideLabel . '_' . $ideUuid); } - public static function normalizeSshKeyLabel(?string $label): string|null { + private static function normalizeSshKeyLabel(?string $label): string|null { if (is_null($label)) { throw new RuntimeException('The label cannot be empty'); } // It may only contain letters, numbers and underscores. - return preg_replace('/[^A-Za-z0-9_]/', '', $label); + return preg_replace('/\W/', '', $label); } /** diff --git a/tests/phpunit/src/Commands/Ide/IdeDeleteCommandTest.php b/tests/phpunit/src/Commands/Ide/IdeDeleteCommandTest.php index c601a8a98..fc446f66f 100644 --- a/tests/phpunit/src/Commands/Ide/IdeDeleteCommandTest.php +++ b/tests/phpunit/src/Commands/Ide/IdeDeleteCommandTest.php @@ -30,15 +30,12 @@ protected function createCommand(): CommandBase { return $this->injectCommand(IdeDeleteCommand::class); } - /** - * @group brokenProphecy - */ public function testIdeDeleteCommand(): void { $applications = $this->mockRequest('getApplications'); $this->mockRequest('getApplicationByUuid', $applications[0]->uuid); $ides = $this->mockRequest('getApplicationIdes', $applications[0]->uuid); $this->mockRequest('deleteIde', $ides[0]->uuid, NULL, 'De-provisioning IDE'); - $sshKeyGetResponse = $this->mockListSshKeysRequestWithIdeKey(); + $sshKeyGetResponse = $this->mockListSshKeysRequestWithIdeKey($ides[0]->label, $ides[0]->uuid); $this->mockDeleteSshKeyRequest($sshKeyGetResponse->{'_embedded'}->items[0]->uuid); @@ -51,7 +48,7 @@ public function testIdeDeleteCommand(): void { 'y', // Select the IDE you'd like to delete: 0, - // Would you like to delete the SSH key associated with this IDE from your Cloud Platform account? + // Are you sure you want to delete ExampleIDE? 'y', ]; @@ -62,4 +59,47 @@ public function testIdeDeleteCommand(): void { $this->assertStringContainsString('The Cloud IDE is being deleted.', $output); } + public function testIdeDeleteByUuid(): void { + $this->mockRequest('getIde', IdeHelper::$remoteIdeUuid); + $this->mockRequest('deleteIde', IdeHelper::$remoteIdeUuid, NULL, 'De-provisioning IDE'); + $sshKeyGetResponse = $this->mockListSshKeysRequestWithIdeKey(IdeHelper::$remoteIdeLabel, IdeHelper::$remoteIdeUuid); + + $this->mockDeleteSshKeyRequest($sshKeyGetResponse->{'_embedded'}->items[0]->uuid); + + $inputs = [ + // Would you like to delete the SSH key associated with this IDE from your Cloud Platform account? + 'y', + ]; + + $this->executeCommand(['--uuid' => IdeHelper::$remoteIdeUuid], $inputs); + + // Assert. + $output = $this->getDisplay(); + $this->assertStringContainsString('The Cloud IDE is being deleted.', $output); + } + + public function testIdeDeleteNeverMind(): void { + $applications = $this->mockRequest('getApplications'); + $this->mockRequest('getApplicationByUuid', $applications[0]->uuid); + $this->mockRequest('getApplicationIdes', $applications[0]->uuid); + $inputs = [ + // Would you like Acquia CLI to search for a Cloud application that matches your local git config? + 'n', + // Select the application for which you'd like to create a new IDE. + 0, + // Would you like to link the project at ... ? + 'y', + // Select the IDE you'd like to delete: + 0, + // Are you sure you want to delete ExampleIDE? + 'n', + ]; + + $this->executeCommand([], $inputs); + + // Assert. + $output = $this->getDisplay(); + $this->assertStringContainsString('Ok, never mind.', $output); + } + } diff --git a/tests/phpunit/src/Commands/Ide/IdeHelper.php b/tests/phpunit/src/Commands/Ide/IdeHelper.php index 15b09fa02..3f261723a 100644 --- a/tests/phpunit/src/Commands/Ide/IdeHelper.php +++ b/tests/phpunit/src/Commands/Ide/IdeHelper.php @@ -9,6 +9,7 @@ class IdeHelper { public static string $remoteIdeUuid = '215824ff-272a-4a8c-9027-df32ed1d68a9'; + public static string $remoteIdeLabel = 'ExampleIDE'; public static function setCloudIdeEnvVars(): void { TestBase::setEnvVars(self::getEnvVars()); @@ -25,7 +26,7 @@ public static function getEnvVars(): array { return [ 'ACQUIA_USER_UUID' => '4acf8956-45df-3cf4-5106-065b62cf1ac8', 'AH_SITE_ENVIRONMENT' => 'IDE', - 'REMOTEIDE_LABEL' => 'ExampleIDE', + 'REMOTEIDE_LABEL' => self::$remoteIdeLabel, 'REMOTEIDE_UUID' => self::$remoteIdeUuid, ]; } diff --git a/tests/phpunit/src/Commands/Ide/Wizard/IdeWizardDeleteSshKeyCommandTest.php b/tests/phpunit/src/Commands/Ide/Wizard/IdeWizardDeleteSshKeyCommandTest.php index 09999118d..046139177 100644 --- a/tests/phpunit/src/Commands/Ide/Wizard/IdeWizardDeleteSshKeyCommandTest.php +++ b/tests/phpunit/src/Commands/Ide/Wizard/IdeWizardDeleteSshKeyCommandTest.php @@ -14,7 +14,7 @@ class IdeWizardDeleteSshKeyCommandTest extends IdeWizardTestBase { public function testDelete(): void { - $mockBody = $this->mockListSshKeysRequestWithIdeKey(); + $mockBody = $this->mockListSshKeysRequestWithIdeKey(IdeHelper::$remoteIdeLabel, IdeHelper::$remoteIdeUuid); $this->mockDeleteSshKeyRequest($mockBody->{'_embedded'}->items[0]->uuid); diff --git a/tests/phpunit/src/TestBase.php b/tests/phpunit/src/TestBase.php index 9d32dd2b4..4dc128af1 100644 --- a/tests/phpunit/src/TestBase.php +++ b/tests/phpunit/src/TestBase.php @@ -8,7 +8,6 @@ use Acquia\Cli\Application; use Acquia\Cli\CloudApi\ClientService; use Acquia\Cli\CloudApi\CloudCredentials; -use Acquia\Cli\Command\Ssh\SshKeyCommandBase; use Acquia\Cli\Config\AcquiaCliConfig; use Acquia\Cli\Config\CloudDataConfig; use Acquia\Cli\DataStore\AcquiaCliDatastore; @@ -575,9 +574,9 @@ protected function mockListSshKeysRequest(): array { return $this->mockRequest('getAccountSshKeys'); } - protected function mockListSshKeysRequestWithIdeKey(): object { + protected function mockListSshKeysRequestWithIdeKey(string $ideLabel, string $ideUuid): object { $mockBody = $this->getMockResponseFromSpec('/account/ssh-keys', 'get', '200'); - $mockBody->{'_embedded'}->items[0]->label = SshKeyCommandBase::getIdeSshKeyLabel(); + $mockBody->{'_embedded'}->items[0]->label = preg_replace('/\W/', '', 'IDE_' . $ideLabel . '_' . $ideUuid); $this->clientProphecy->request('get', '/account/ssh-keys') ->willReturn($mockBody->{'_embedded'}->items) ->shouldBeCalled();