From 012ac0202887afd3d84ea47b9c9864d19bf93182 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Wed, 8 May 2024 11:23:33 -0700 Subject: [PATCH] CLI-1330: TypeError for SSH commands on Node environments (#1734) * CLI-1330: TypeError for SSH commands on Node environments * trigger codecov * Don't allow node environments * kill mutant * kill mutant * kill mutants * kill mutant * kill mutant * kill mutant * determineEnvironment flip parameter * Kill mutants * kill mutant --- src/Command/CommandBase.php | 35 ++++-- src/Command/Env/EnvCertCreateCommand.php | 2 +- src/Command/Pull/PullCommandBase.php | 12 +- src/Command/Push/PushDatabaseCommand.php | 2 +- src/Command/Push/PushFilesCommand.php | 7 +- src/Command/Remote/DrushCommand.php | 2 +- src/Command/Remote/SshCommand.php | 2 +- src/Command/Ssh/SshKeyCommandBase.php | 4 +- src/Helpers/SshHelper.php | 13 +-- tests/phpunit/src/CommandTestBase.php | 35 +++--- .../src/Commands/App/LogTailCommandTest.php | 34 +++++- .../Commands/Env/EnvCertCreateCommandTest.php | 68 ++++++++++-- .../IdeWizardCreateSshKeyCommandTest.php | 4 +- .../src/Commands/Pull/PullCommandTestBase.php | 2 +- .../Commands/Pull/PullDatabaseCommandTest.php | 28 ++++- .../Commands/Push/PushArtifactCommandTest.php | 1 + .../Commands/Push/PushDatabaseCommandTest.php | 103 ++++++++++++------ .../Commands/Ssh/SshKeyUploadCommandTest.php | 56 +++++++++- tests/phpunit/src/Commands/WizardTestBase.php | 4 +- 19 files changed, 307 insertions(+), 107 deletions(-) diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 42d5ab9aa..f6a4f991e 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -574,7 +574,7 @@ private function promptChooseDatabases( return [$environmentDatabases[$chosenDatabaseIndex]]; } - protected function determineEnvironment(InputInterface $input, OutputInterface $output, bool $allowProduction = FALSE): array|string|EnvironmentResponse { + protected function determineEnvironment(InputInterface $input, OutputInterface $output, bool $allowProduction = FALSE, bool $allowNode = FALSE): array|string|EnvironmentResponse { if ($input->getArgument('environmentId')) { $environmentId = $input->getArgument('environmentId'); $chosenEnvironment = $this->getCloudEnvironment($environmentId); @@ -584,7 +584,7 @@ protected function determineEnvironment(InputInterface $input, OutputInterface $ $cloudApplication = $this->getCloudApplication($cloudApplicationUuid); $output->writeln('Using Cloud Application ' . $cloudApplication->name . ''); $acquiaCloudClient = $this->cloudApiClientService->getClient(); - $chosenEnvironment = $this->promptChooseEnvironmentConsiderProd($acquiaCloudClient, $cloudApplicationUuid, $allowProduction); + $chosenEnvironment = $this->promptChooseEnvironmentConsiderProd($acquiaCloudClient, $cloudApplicationUuid, $allowProduction, $allowNode); } $this->logger->debug("Using environment $chosenEnvironment->label $chosenEnvironment->uuid"); @@ -592,12 +592,14 @@ protected function determineEnvironment(InputInterface $input, OutputInterface $ } // Todo: obviously combine this with promptChooseEnvironment. - private function promptChooseEnvironmentConsiderProd(Client $acquiaCloudClient, string $applicationUuid, bool $allowProduction = FALSE): EnvironmentResponse { + private function promptChooseEnvironmentConsiderProd(Client $acquiaCloudClient, string $applicationUuid, bool $allowProduction, bool $allowNode): EnvironmentResponse { $environmentResource = new Environments($acquiaCloudClient); $applicationEnvironments = iterator_to_array($environmentResource->getAll($applicationUuid)); $choices = []; foreach ($applicationEnvironments as $key => $environment) { - if (!$allowProduction && $environment->flags->production) { + $productionNotAllowed = !$allowProduction && $environment->flags->production; + $nodeNotAllowed = !$allowNode && $environment->type === 'node'; + if ($productionNotAllowed || $nodeNotAllowed) { unset($applicationEnvironments[$key]); // Re-index array so keys match those in $choices. $applicationEnvironments = array_values($applicationEnvironments); @@ -605,6 +607,9 @@ private function promptChooseEnvironmentConsiderProd(Client $acquiaCloudClient, } $choices[] = "$environment->label, $environment->name (vcs: {$environment->vcs->path})"; } + if (count($choices) === 0) { + throw new AcquiaCliException('No compatible environments found'); + } $chosenEnvironmentLabel = $this->io->choice('Choose a Cloud Platform environment', $choices, $choices[0]); $chosenEnvironmentIndex = array_search($chosenEnvironmentLabel, $choices, TRUE); @@ -1330,10 +1335,10 @@ protected function isAcsfEnv(mixed $cloudEnvironment): bool { /** * @return array */ - protected function getAcsfSites(EnvironmentResponse $cloudEnvironment): array { + private function getAcsfSites(EnvironmentResponse $cloudEnvironment): array { $envAlias = self::getEnvironmentAlias($cloudEnvironment); $command = ['cat', "/var/www/site-php/$envAlias/multisite-config.json"]; - $process = $this->sshHelper->executeCommand($cloudEnvironment, $command, FALSE); + $process = $this->sshHelper->executeCommand($cloudEnvironment->sshUrl, $command, FALSE); if ($process->isSuccessful()) { return json_decode($process->getOutput(), TRUE, 512, JSON_THROW_ON_ERROR); } @@ -1346,7 +1351,7 @@ protected function getAcsfSites(EnvironmentResponse $cloudEnvironment): array { private function getCloudSites(EnvironmentResponse $cloudEnvironment): array { $sitegroup = self::getSitegroup($cloudEnvironment); $command = ['ls', $this->getCloudSitesPath($cloudEnvironment, $sitegroup)]; - $process = $this->sshHelper->executeCommand($cloudEnvironment, $command, FALSE); + $process = $this->sshHelper->executeCommand($cloudEnvironment->sshUrl, $command, FALSE); $sites = array_filter(explode("\n", trim($process->getOutput()))); if ($process->isSuccessful() && $sites) { return $sites; @@ -1682,8 +1687,8 @@ private function getAnyAhEnvironment(string $cloudAppUuid, callable $filter): En * Get the first non-prod environment for a given Cloud application. */ protected function getAnyNonProdAhEnvironment(string $cloudAppUuid): EnvironmentResponse|false { - return $this->getAnyAhEnvironment($cloudAppUuid, function (mixed $environment) { - return !$environment->flags->production; + return $this->getAnyAhEnvironment($cloudAppUuid, function (EnvironmentResponse $environment) { + return !$environment->flags->production && $environment->type === 'drupal'; }); } @@ -1691,8 +1696,8 @@ protected function getAnyNonProdAhEnvironment(string $cloudAppUuid): Environment * Get the first prod environment for a given Cloud application. */ protected function getAnyProdAhEnvironment(string $cloudAppUuid): EnvironmentResponse|false { - return $this->getAnyAhEnvironment($cloudAppUuid, function (mixed $environment) { - return $environment->flags->production; + return $this->getAnyAhEnvironment($cloudAppUuid, function (EnvironmentResponse $environment) { + return $environment->flags->production && $environment->type === 'drupal'; }); } @@ -1843,4 +1848,12 @@ protected function validatePhpVersion(string $version): string { return $version; } + protected function promptChooseDrupalSite(EnvironmentResponse $environment): string { + if ($this->isAcsfEnv($environment)) { + return $this->promptChooseAcsfSite($environment); + } + + return $this->promptChooseCloudSite($environment); + } + } diff --git a/src/Command/Env/EnvCertCreateCommand.php b/src/Command/Env/EnvCertCreateCommand.php index c1f32f717..a68051f82 100644 --- a/src/Command/Env/EnvCertCreateCommand.php +++ b/src/Command/Env/EnvCertCreateCommand.php @@ -31,7 +31,7 @@ protected function configure(): void { protected function execute(InputInterface $input, OutputInterface $output): int { $acquiaCloudClient = $this->cloudApiClientService->getClient(); - $environment = $this->determineEnvironment($input, $output); + $environment = $this->determineEnvironment($input, $output, TRUE, TRUE); $certificate = $input->getArgument('certificate'); $privateKey = $input->getArgument('private-key'); $label = $this->determineOption('label'); diff --git a/src/Command/Pull/PullCommandBase.php b/src/Command/Pull/PullCommandBase.php index 745a34fa8..ce18ba740 100644 --- a/src/Command/Pull/PullCommandBase.php +++ b/src/Command/Pull/PullCommandBase.php @@ -414,7 +414,7 @@ private function importDatabaseDump(string $localDumpFilepath, string $dbHost, s } } - private function determineSite(string|EnvironmentResponse|array $environment, InputInterface $input): mixed { + private function determineSite(string|EnvironmentResponse|array $environment, InputInterface $input): string { if (isset($this->site)) { return $this->site; } @@ -423,15 +423,9 @@ private function determineSite(string|EnvironmentResponse|array $environment, In return $input->getArgument('site'); } - if ($this->isAcsfEnv($environment)) { - $site = $this->promptChooseAcsfSite($environment); - } - else { - $site = $this->promptChooseCloudSite($environment); - } - $this->site = $site; + $this->site = $this->promptChooseDrupalSite($environment); - return $site; + return $this->site; } private function rsyncFilesFromCloud(EnvironmentResponse $chosenEnvironment, Closure $outputCallback, string $site): void { diff --git a/src/Command/Push/PushDatabaseCommand.php b/src/Command/Push/PushDatabaseCommand.php index d3b2363a1..a244c7018 100644 --- a/src/Command/Push/PushDatabaseCommand.php +++ b/src/Command/Push/PushDatabaseCommand.php @@ -83,7 +83,7 @@ private function uploadDatabaseDump( private function importDatabaseDumpOnRemote(EnvironmentResponse $environment, string $remoteDumpFilepath, DatabaseResponse $database): void { $this->logger->debug("Importing $remoteDumpFilepath to MySQL on remote machine"); $command = "pv $remoteDumpFilepath --bytes --rate | gunzip | MYSQL_PWD={$database->password} mysql --host={$this->getHostFromDatabaseResponse($environment, $database)} --user={$database->user_name} {$this->getNameFromDatabaseResponse($database)}"; - $process = $this->sshHelper->executeCommand($environment, [$command], ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL)); + $process = $this->sshHelper->executeCommand($environment->sshUrl, [$command], ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL)); if (!$process->isSuccessful()) { throw new AcquiaCliException('Unable to import database on remote machine. {message}', ['message' => $process->getErrorOutput()]); } diff --git a/src/Command/Push/PushFilesCommand.php b/src/Command/Push/PushFilesCommand.php index f4e7c89cb..08fe07661 100644 --- a/src/Command/Push/PushFilesCommand.php +++ b/src/Command/Push/PushFilesCommand.php @@ -27,12 +27,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $destinationEnvironment = $this->determineEnvironment($input, $output); $chosenSite = $input->getArgument('site'); if (!$chosenSite) { - if ($this->isAcsfEnv($destinationEnvironment)) { - $chosenSite = $this->promptChooseAcsfSite($destinationEnvironment); - } - else { - $chosenSite = $this->promptChooseCloudSite($destinationEnvironment); - } + $chosenSite = $this->promptChooseDrupalSite($destinationEnvironment); } $answer = $this->io->confirm("Overwrite the public files directory on $destinationEnvironment->name with a copy of the files from the current machine?"); if (!$answer) { diff --git a/src/Command/Remote/DrushCommand.php b/src/Command/Remote/DrushCommand.php index c81efed7e..df1372fa1 100644 --- a/src/Command/Remote/DrushCommand.php +++ b/src/Command/Remote/DrushCommand.php @@ -43,7 +43,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int implode(' ', $drushArguments), ]; - return $this->sshHelper->executeCommand($environment, $drushCommandArguments)->getExitCode(); + return $this->sshHelper->executeCommand($environment->sshUrl, $drushCommandArguments)->getExitCode(); } } diff --git a/src/Command/Remote/SshCommand.php b/src/Command/Remote/SshCommand.php index f924406bc..dfc59e219 100644 --- a/src/Command/Remote/SshCommand.php +++ b/src/Command/Remote/SshCommand.php @@ -45,7 +45,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int $sshCommand[] = implode(' ', $arguments['ssh_command']); } $sshCommand = (array) implode('; ', $sshCommand); - return $this->sshHelper->executeCommand($environment, $sshCommand)->getExitCode(); + return $this->sshHelper->executeCommand($environment->sshUrl, $sshCommand)->getExitCode(); } } diff --git a/src/Command/Ssh/SshKeyCommandBase.php b/src/Command/Ssh/SshKeyCommandBase.php index 83fdce38d..8620f56fd 100644 --- a/src/Command/Ssh/SshKeyCommandBase.php +++ b/src/Command/Ssh/SshKeyCommandBase.php @@ -186,12 +186,12 @@ private function checkPermissions(array $userPerms, string $cloudAppUuid, Output break; case 'add ssh key to non-prod': if ($nonProdEnv = $this->getAnyNonProdAhEnvironment($cloudAppUuid)) { - $mappings['nonprod']['ssh_target'] = $nonProdEnv; + $mappings['nonprod']['ssh_target'] = $nonProdEnv->sshUrl; } break; case 'add ssh key to prod': if ($prodEnv = $this->getAnyProdAhEnvironment($cloudAppUuid)) { - $mappings['prod']['ssh_target'] = $prodEnv; + $mappings['prod']['ssh_target'] = $prodEnv->sshUrl; } break; } diff --git a/src/Helpers/SshHelper.php b/src/Helpers/SshHelper.php index 6a32b9066..9d2b61934 100644 --- a/src/Helpers/SshHelper.php +++ b/src/Helpers/SshHelper.php @@ -5,7 +5,6 @@ namespace Acquia\Cli\Helpers; use Acquia\Cli\Exception\AcquiaCliException; -use AcquiaCloudApi\Response\EnvironmentResponse; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; @@ -32,20 +31,16 @@ public function __construct( * * @param int|null $timeout */ - public function executeCommand(EnvironmentResponse|string $target, array $commandArgs, bool $printOutput = TRUE, int $timeout = NULL): Process { + public function executeCommand(string $sshUrl, array $commandArgs, bool $printOutput = TRUE, int $timeout = NULL): Process { $commandSummary = $this->getCommandSummary($commandArgs); - if (is_a($target, EnvironmentResponse::class)) { - $target = $target->sshUrl; - } - // Remove site_env arg. unset($commandArgs['alias']); - $process = $this->sendCommand($target, $commandArgs, $printOutput, $timeout); + $process = $this->sendCommand($sshUrl, $commandArgs, $printOutput, $timeout); $this->logger->debug('Command: {command} [Exit: {exit}]', [ 'command' => $commandSummary, - 'env' => $target, + 'env' => $sshUrl, 'exit' => $process->getExitCode(), ]); @@ -56,7 +51,7 @@ public function executeCommand(EnvironmentResponse|string $target, array $comman return $process; } - private function sendCommand(?string $url, array $command, bool $printOutput, ?int $timeout = NULL): Process { + private function sendCommand(string $url, array $command, bool $printOutput, ?int $timeout = NULL): Process { $command = array_values($this->getSshCommand($url, $command)); $this->localMachineHelper->checkRequiredBinariesExist(['ssh']); diff --git a/tests/phpunit/src/CommandTestBase.php b/tests/phpunit/src/CommandTestBase.php index 6a9c09b8c..0374d1b81 100644 --- a/tests/phpunit/src/CommandTestBase.php +++ b/tests/phpunit/src/CommandTestBase.php @@ -12,7 +12,6 @@ use Acquia\Cli\Helpers\LocalMachineHelper; use Acquia\Cli\Helpers\SshHelper; use AcquiaCloudApi\Response\DatabaseResponse; -use AcquiaCloudApi\Response\EnvironmentResponse; use Exception; use Gitlab\Api\Projects; use Gitlab\Api\Users; @@ -82,7 +81,7 @@ protected function setCommand(CommandBase $command): void { * An array of strings representing each input passed to the command input * stream. */ - protected function executeCommand(array $args = [], array $inputs = []): void { + protected function executeCommand(array $args = [], array $inputs = [], int $verbosity = Output::VERBOSITY_VERY_VERBOSE): void { $cwd = $this->projectDir; $tester = $this->getCommandTester(); $tester->setInputs($inputs); @@ -96,7 +95,7 @@ protected function executeCommand(array $args = [], array $inputs = []): void { } try { - $tester->execute($args, ['verbosity' => Output::VERBOSITY_VERY_VERBOSE]); + $tester->execute($args, ['verbosity' => $verbosity]); } catch (Exception $e) { if (getenv('ACLI_PRINT_COMMAND_OUTPUT')) { @@ -264,7 +263,7 @@ protected function mockGetAcsfSites(mixed $sshHelper): array { $multisiteConfig = file_get_contents(Path::join($this->realFixtureDir, '/multisite-config.json')); $acsfMultisiteFetchProcess->getOutput()->willReturn($multisiteConfig)->shouldBeCalled(); $sshHelper->executeCommand( - Argument::type('object'), + Argument::type('string'), ['cat', '/var/www/site-php/profserv2.01dev/multisite-config.json'], FALSE )->willReturn($acsfMultisiteFetchProcess->reveal())->shouldBeCalled(); @@ -277,7 +276,7 @@ protected function mockGetCloudSites(mixed $sshHelper, mixed $environment): void $parts = explode('.', $environment->ssh_url); $sitegroup = reset($parts); $sshHelper->executeCommand( - Argument::type('object'), + Argument::type('string'), ['ls', "/mnt/files/$sitegroup.{$environment->name}/sites"], FALSE )->willReturn($cloudMultisiteFetchProcess->reveal())->shouldBeCalled(); @@ -375,12 +374,12 @@ protected function mockNotificationResponseFromObject(object $responseWithNotifi return $this->mockRequest('getNotificationByUuid', $uuid); } - protected function mockCreateMySqlDumpOnLocal(ObjectProphecy $localMachineHelper): void { + protected function mockCreateMySqlDumpOnLocal(ObjectProphecy $localMachineHelper, bool $printOutput = TRUE): void { $localMachineHelper->checkRequiredBinariesExist(["mysqldump", "gzip"])->shouldBeCalled(); $process = $this->mockProcess(); $process->getOutput()->willReturn(''); $command = 'MYSQL_PWD=drupal mysqldump --host=localhost --user=drupal drupal | pv --rate --bytes | gzip -9 > ' . sys_get_temp_dir() . '/acli-mysql-dump-drupal.sql.gz'; - $localMachineHelper->executeFromCmd($command, Argument::type('callable'), NULL, TRUE)->willReturn($process->reveal()) + $localMachineHelper->executeFromCmd($command, Argument::type('callable'), NULL, $printOutput)->willReturn($process->reveal()) ->shouldBeCalled(); } @@ -420,7 +419,7 @@ protected function setUpdateClient(int $statusCode = 200): void { $this->command->setUpdateClient($guzzleClient->reveal()); } - protected function mockPollCloudViaSsh(object $environmentsResponse): ObjectProphecy { + protected function mockPollCloudViaSsh(array $environmentsResponse, bool $ssh = TRUE): ObjectProphecy { $process = $this->prophet->prophesize(Process::class); $process->isSuccessful()->willReturn(TRUE); $process->getExitCode()->willReturn(0); @@ -429,18 +428,20 @@ protected function mockPollCloudViaSsh(object $environmentsResponse): ObjectProp $gitProcess->getExitCode()->willReturn(128); $sshHelper = $this->mockSshHelper(); // Mock Git. - $urlParts = explode(':', $environmentsResponse->_embedded->items[0]->vcs->url); + $urlParts = explode(':', $environmentsResponse[0]->vcs->url); $sshHelper->executeCommand($urlParts[0], ['ls'], FALSE) ->willReturn($gitProcess->reveal()) ->shouldBeCalled(); - // Mock non-prod. - $sshHelper->executeCommand(new EnvironmentResponse($environmentsResponse->_embedded->items[0]), ['ls'], FALSE) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - // Mock prod. - $sshHelper->executeCommand(new EnvironmentResponse($environmentsResponse->_embedded->items[1]), ['ls'], FALSE) - ->willReturn($process->reveal()) - ->shouldBeCalled(); + if ($ssh) { + // Mock non-prod. + $sshHelper->executeCommand($environmentsResponse[0]->ssh_url, ['ls'], FALSE) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + // Mock prod. + $sshHelper->executeCommand($environmentsResponse[1]->ssh_url, ['ls'], FALSE) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + } return $sshHelper; } diff --git a/tests/phpunit/src/Commands/App/LogTailCommandTest.php b/tests/phpunit/src/Commands/App/LogTailCommandTest.php index 8e3dabf14..3190abfec 100644 --- a/tests/phpunit/src/Commands/App/LogTailCommandTest.php +++ b/tests/phpunit/src/Commands/App/LogTailCommandTest.php @@ -6,6 +6,7 @@ use Acquia\Cli\Command\App\LogTailCommand; use Acquia\Cli\Command\CommandBase; +use Acquia\Cli\Exception\AcquiaCliException; use Acquia\Cli\Tests\CommandTestBase; use AcquiaLogstream\LogstreamManager; use Prophecy\Argument; @@ -32,10 +33,6 @@ protected function createCommand(): CommandBase { // Must initialize this here instead of in setUp() because we need the // prophet to be initialized first. $this->logStreamManagerProphecy = $this->prophet->prophesize(LogstreamManager::class); - $this->logStreamManagerProphecy->setColourise(TRUE)->shouldBeCalled(); - $this->logStreamManagerProphecy->setParams(Argument::type('object'))->shouldBeCalled(); - $this->logStreamManagerProphecy->setLogTypeFilter(["bal-request"])->shouldBeCalled(); - $this->logStreamManagerProphecy->stream()->shouldBeCalled(); return new LogTailCommand( $this->localMachineHelper, @@ -56,6 +53,10 @@ protected function createCommand(): CommandBase { * @dataProvider providerLogTailCommand */ public function testLogTailCommand(?int $stream): void { + $this->logStreamManagerProphecy->setColourise(TRUE)->shouldBeCalled(); + $this->logStreamManagerProphecy->setParams(Argument::type('object'))->shouldBeCalled(); + $this->logStreamManagerProphecy->setLogTypeFilter(["bal-request"])->shouldBeCalled(); + $this->logStreamManagerProphecy->stream()->shouldBeCalled(); $this->mockGetEnvironment(); $this->mockLogStreamRequest(); $this->executeCommand([], [ @@ -95,6 +96,31 @@ public function testLogTailCommandWithEnvArg(): void { $this->assertStringContainsString('Drupal request', $output); } + public function testLogTailNode(): void { + $applications = $this->mockRequest('getApplications'); + $application = $this->mockRequest('getApplicationByUuid', $applications[self::$INPUT_DEFAULT_CHOICE]->uuid); + $tamper = function ($responses): void { + foreach ($responses as $response) { + $response->type = 'node'; + } + }; + $this->mockRequest('getApplicationEnvironments', $application->uuid, NULL, NULL, $tamper); + $this->expectException(AcquiaCliException::class); + $this->expectExceptionMessage('No compatible environments found'); + $this->executeCommand([], [ + // Would you like Acquia CLI to search for a Cloud application that matches your local git config? + 'n', + // Select the application. + 0, + // Would you like to link the project at ... ? + 'y', + // Select environment. + 0, + // Select log. + 0, + ]); + } + private function mockLogStreamRequest(): void { $response = $this->getMockResponseFromSpec('/environments/{environmentId}/logstream', 'get', '200'); diff --git a/tests/phpunit/src/Commands/Env/EnvCertCreateCommandTest.php b/tests/phpunit/src/Commands/Env/EnvCertCreateCommandTest.php index 723219cf3..73d5cea5c 100644 --- a/tests/phpunit/src/Commands/Env/EnvCertCreateCommandTest.php +++ b/tests/phpunit/src/Commands/Env/EnvCertCreateCommandTest.php @@ -14,13 +14,10 @@ protected function createCommand(): CommandBase { return $this->injectCommand(EnvCertCreateCommand::class); } - /** - * @group brokenProphecy - */ public function testCreateCert(): void { - $applicationsResponse = $this->mockApplicationsRequest(); - $this->mockApplicationRequest(); - $environmentsResponse = $this->mockEnvironmentsRequest($applicationsResponse); + $applications = $this->mockRequest('getApplications'); + $application = $this->mockRequest('getApplicationByUuid', $applications[self::$INPUT_DEFAULT_CHOICE]->uuid); + $environments = $this->mockRequest('getApplicationEnvironments', $application->uuid); $localMachineHelper = $this->mockLocalMachineHelper(); $certContents = 'cert-contents'; $keyContents = 'key-contents'; @@ -43,7 +40,64 @@ public function testCreateCert(): void { 'private_key' => $keyContents, ], ]; - $this->clientProphecy->request('post', "/environments/{$environmentsResponse->{'_embedded'}->items[0]->id}/ssl/certificates", $options) + $this->clientProphecy->request('post', "/environments/{$environments[1]->id}/ssl/certificates", $options) + ->willReturn($sslResponse->{'Site is being imported'}->value) + ->shouldBeCalled(); + $this->mockNotificationResponseFromObject($sslResponse->{'Site is being imported'}->value); + + $this->executeCommand( + [ + '--csr-id' => $csrId, + '--label' => $label, + '--legacy' => FALSE, + 'certificate' => $certName, + 'private-key' => $keyName, + ], + [ + // Would you like Acquia CLI to search for a Cloud application that matches your local git config?'. + 'n', + // Select a Cloud Platform application: [Sample application 1]: + 0, + 'n', + 1, + '', + ] + ); + + } + + public function testCreateCertNode(): void { + $applications = $this->mockRequest('getApplications'); + $application = $this->mockRequest('getApplicationByUuid', $applications[self::$INPUT_DEFAULT_CHOICE]->uuid); + $tamper = function ($responses): void { + foreach ($responses as $response) { + $response->type = 'node'; + } + }; + $environments = $this->mockRequest('getApplicationEnvironments', $application->uuid, NULL, NULL, $tamper); + $localMachineHelper = $this->mockLocalMachineHelper(); + $certContents = 'cert-contents'; + $keyContents = 'key-contents'; + $certName = 'cert.pem'; + $keyName = 'key.pem'; + $label = 'My certificate'; + $csrId = 123; + $localMachineHelper->readFile($certName)->willReturn($certContents)->shouldBeCalled(); + $localMachineHelper->readFile($keyName)->willReturn($keyContents)->shouldBeCalled(); + + $sslResponse = $this->getMockResponseFromSpec('/environments/{environmentId}/ssl/certificates', + 'post', '202'); + $options = [ + 'json' => [ + 'ca_certificates' => NULL, + 'certificate' => $certContents, + 'csr_id' => $csrId, + 'label' => $label, + 'legacy' => FALSE, + 'private_key' => $keyContents, + ], + ]; + $this->clientProphecy->request('post', "/environments/{$environments[0]->id}/ssl/certificates", $options) ->willReturn($sslResponse->{'Site is being imported'}->value) ->shouldBeCalled(); $this->mockNotificationResponseFromObject($sslResponse->{'Site is being imported'}->value); diff --git a/tests/phpunit/src/Commands/Ide/Wizard/IdeWizardCreateSshKeyCommandTest.php b/tests/phpunit/src/Commands/Ide/Wizard/IdeWizardCreateSshKeyCommandTest.php index 45d747f47..334e65b56 100644 --- a/tests/phpunit/src/Commands/Ide/Wizard/IdeWizardCreateSshKeyCommandTest.php +++ b/tests/phpunit/src/Commands/Ide/Wizard/IdeWizardCreateSshKeyCommandTest.php @@ -31,14 +31,14 @@ protected function createCommand(): CommandBase { } public function testCreate(): void { - parent::runTestCreate(); + $this->runTestCreate(); } /** * @group brokenProphecy */ public function testSshKeyAlreadyUploaded(): void { - parent::runTestSshKeyAlreadyUploaded(); + $this->runTestSshKeyAlreadyUploaded(); } } diff --git a/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php b/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php index 03fdc7f9e..114239615 100644 --- a/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php +++ b/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php @@ -316,7 +316,7 @@ protected function mockSettingsFiles(ObjectProphecy $fs): void { protected function mockListSites(SshHelper|ObjectProphecy $sshHelper): void { $process = $this->mockProcess(); $process->getOutput()->willReturn('default')->shouldBeCalled(); - $sshHelper->executeCommand(Argument::type('object'), ['ls', '/mnt/files/site.dev/sites'], FALSE) + $sshHelper->executeCommand(Argument::type('string'), ['ls', '/mnt/files/site.dev/sites'], FALSE) ->willReturn($process->reveal())->shouldBeCalled(); } diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 72338d8fe..865a3b1a2 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -88,7 +88,7 @@ public function testPullProdDatabase(): void { $sshHelper = $this->mockSshHelper(); $process = $this->mockProcess(); $process->getOutput()->willReturn('default')->shouldBeCalled(); - $sshHelper->executeCommand(Argument::type('object'), ['ls', '/mnt/files/site.prod/sites'], FALSE) + $sshHelper->executeCommand(Argument::type('string'), ['ls', '/mnt/files/site.prod/sites'], FALSE) ->willReturn($process->reveal())->shouldBeCalled(); $this->mockGetBackup($environment); $this->mockExecuteMySqlListTables($localMachineHelper, 'drupal'); @@ -359,4 +359,30 @@ public function testDownloadProgressDisplay(): void { $this->assertStringContainsString('100/100 [============================] 100%', $output->fetch()); } + public function testPullNode(): void { + $applications = $this->mockRequest('getApplications'); + $application = $this->mockRequest('getApplicationByUuid', $applications[self::$INPUT_DEFAULT_CHOICE]->uuid); + $tamper = function ($responses): void { + foreach ($responses as $response) { + $response->type = 'node'; + } + }; + $this->mockRequest('getApplicationEnvironments', $application->uuid, NULL, NULL, $tamper); + + $this->expectException(AcquiaCliException::class); + $this->expectExceptionMessage('No compatible environments found'); + $this->executeCommand([ + '--no-scripts' => TRUE, + ], [ + // Would you like Acquia CLI to search for a Cloud application that matches your local git config? + 'n', + // Select a Cloud Platform application: + self::$INPUT_DEFAULT_CHOICE, + // Would you like to link the project at ... ? + 'n', + // Choose an Acquia environment: + 1, + ]); + } + } diff --git a/tests/phpunit/src/Commands/Push/PushArtifactCommandTest.php b/tests/phpunit/src/Commands/Push/PushArtifactCommandTest.php index 220063c7e..fbef27e9a 100644 --- a/tests/phpunit/src/Commands/Push/PushArtifactCommandTest.php +++ b/tests/phpunit/src/Commands/Push/PushArtifactCommandTest.php @@ -51,6 +51,7 @@ public function testPushArtifact(): void { $this->assertStringContainsString('[0] Sample application 1', $output); $this->assertStringContainsString('Choose a Cloud Platform environment', $output); $this->assertStringContainsString('[0] Dev, dev (vcs: master)', $output); + $this->assertStringNotContainsString('Production, prod', $output); $this->assertStringContainsString('Acquia CLI will:', $output); $this->assertStringContainsString('- git clone master from site@svn-3.hosted.acquia-sites.com:site.git', $output); $this->assertStringContainsString('- Compile the contents of vfs://root/project into an artifact', $output); diff --git a/tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php b/tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php index a8a27e467..03568a58c 100644 --- a/tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php @@ -6,17 +6,32 @@ use Acquia\Cli\Command\CommandBase; use Acquia\Cli\Command\Push\PushDatabaseCommand; +use Acquia\Cli\Helpers\LocalMachineHelper; +use Acquia\Cli\Helpers\SshHelper; use Acquia\Cli\Tests\CommandTestBase; -use AcquiaCloudApi\Response\EnvironmentResponse; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Path; +use Symfony\Component\Process\Process; /** * @property \Acquia\Cli\Command\Push\PushDatabaseCommand $command */ class PushDatabaseCommandTest extends CommandTestBase { + /** + * @return mixed[] + */ + public function providerTestPushDatabase(): array { + return [ + [OutputInterface::VERBOSITY_NORMAL, FALSE], + [OutputInterface::VERBOSITY_VERY_VERBOSE, TRUE], + ]; + } + protected function createCommand(): CommandBase { + return $this->injectCommand(PushDatabaseCommand::class); } @@ -25,26 +40,34 @@ public function setUp(): void { parent::setUp(); } - public function testPushDatabase(): void { - $applicationsResponse = $this->mockApplicationsRequest(); - $this->mockApplicationRequest(); - $environmentsResponse = $this->mockAcsfEnvironmentsRequest($applicationsResponse); - $selectedEnvironment = $environmentsResponse->_embedded->items[0]; + /** + * @dataProvider providerTestPushDatabase + */ + public function testPushDatabase(int $verbosity, bool $printOutput): void { + $applications = $this->mockRequest('getApplications'); + $application = $this->mockRequest('getApplicationByUuid', $applications[self::$INPUT_DEFAULT_CHOICE]->uuid); + $tamper = function ($responses): void { + foreach ($responses as $response) { + $response->ssh_url = 'profserv2.01dev@profserv201dev.ssh.enterprise-g1.acquia-sites.com'; + $response->domains = ["profserv201dev.enterprise-g1.acquia-sites.com"]; + } + }; + $environments = $this->mockRequest('getApplicationEnvironments', $application->uuid, NULL, NULL, $tamper); $this->createMockGitConfigFile(); - $this->mockAcsfDatabasesResponse($selectedEnvironment); - $sshHelper = $this->mockSshHelper(); - $this->mockGetAcsfSites($sshHelper); + $this->mockAcsfDatabasesResponse($environments[self::$INPUT_DEFAULT_CHOICE]); $process = $this->mockProcess(); $localMachineHelper = $this->mockLocalMachineHelper(); + $localMachineHelper->checkRequiredBinariesExist(['ssh'])->shouldBeCalled(); + $this->mockGetAcsfSitesLMH($localMachineHelper); // Database. $this->mockExecutePvExists($localMachineHelper); - $this->mockCreateMySqlDumpOnLocal($localMachineHelper); - $this->mockUploadDatabaseDump($localMachineHelper, $process); - $this->mockImportDatabaseDumpOnRemote($sshHelper, $selectedEnvironment, $process); + $this->mockCreateMySqlDumpOnLocal($localMachineHelper, $printOutput); + $this->mockUploadDatabaseDump($localMachineHelper, $process, $printOutput); + $this->mockImportDatabaseDumpOnRemote($localMachineHelper, $process, $printOutput); - $this->command->sshHelper = $sshHelper->reveal(); + $this->command->sshHelper = new SshHelper($this->output, $localMachineHelper->reveal(), $this->logger); $inputs = [ // Would you like Acquia CLI to search for a Cloud application that matches your local git config? @@ -61,7 +84,7 @@ public function testPushDatabase(): void { 'y', ]; - $this->executeCommand([], $inputs); + $this->executeCommand([], $inputs, $verbosity); $output = $this->getDisplay(); @@ -77,7 +100,8 @@ public function testPushDatabase(): void { protected function mockUploadDatabaseDump( ObjectProphecy $localMachineHelper, - ObjectProphecy $process + ObjectProphecy $process, + bool $printOutput = TRUE, ): void { $localMachineHelper->checkRequiredBinariesExist(['rsync'])->shouldBeCalled(); $command = [ @@ -87,22 +111,7 @@ protected function mockUploadDatabaseDump( sys_get_temp_dir() . '/acli-mysql-dump-drupal.sql.gz', 'profserv2.01dev@profserv201dev.ssh.enterprise-g1.acquia-sites.com:/mnt/tmp/profserv2.01dev/acli-mysql-dump-drupal.sql.gz', ]; - $localMachineHelper->execute($command, Argument::type('callable'), NULL, TRUE, NULL) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - } - - protected function mockImportDatabaseDumpOnRemote( - ObjectProphecy $sshHelper, - object $environmentsResponse, - mixed $process - ): void { - $sshHelper->executeCommand( - new EnvironmentResponse($environmentsResponse), - ['pv /mnt/tmp/profserv2.01dev/acli-mysql-dump-drupal.sql.gz --bytes --rate | gunzip | MYSQL_PWD=password mysql --host=fsdb-74.enterprise-g1.hosting.acquia.com.enterprise-g1.hosting.acquia.com --user=s164 profserv2db14390'], - TRUE, - NULL - ) + $localMachineHelper->execute($command, Argument::type('callable'), NULL, $printOutput, NULL) ->willReturn($process->reveal()) ->shouldBeCalled(); } @@ -119,4 +128,36 @@ protected function mockExecuteMySqlImport( ->shouldBeCalled(); } + protected function mockGetAcsfSitesLMH(ObjectProphecy $localMachineHelper): void { + $acsfMultisiteFetchProcess = $this->mockProcess(); + $multisiteConfig = file_get_contents(Path::join($this->realFixtureDir, '/multisite-config.json')); + $acsfMultisiteFetchProcess->getOutput()->willReturn($multisiteConfig)->shouldBeCalled(); + $cmd = [ + 0 => 'ssh', + 1 => 'profserv2.01dev@profserv201dev.ssh.enterprise-g1.acquia-sites.com', + 2 => '-t', + 3 => '-o StrictHostKeyChecking=no', + 4 => '-o AddressFamily inet', + 5 => '-o LogLevel=ERROR', + 6 => 'cat', + 7 => '/var/www/site-php/profserv2.01dev/multisite-config.json', + ]; + $localMachineHelper->execute($cmd, Argument::type('callable'), NULL, FALSE, NULL)->willReturn($acsfMultisiteFetchProcess->reveal())->shouldBeCalled(); + } + + private function mockImportDatabaseDumpOnRemote(ObjectProphecy|LocalMachineHelper $localMachineHelper, Process|ObjectProphecy $process, bool $printOutput = TRUE): void { + $cmd = [ + 0 => 'ssh', + 1 => 'profserv2.01dev@profserv201dev.ssh.enterprise-g1.acquia-sites.com', + 2 => '-t', + 3 => '-o StrictHostKeyChecking=no', + 4 => '-o AddressFamily inet', + 5 => '-o LogLevel=ERROR', + 6 => 'pv /mnt/tmp/profserv2.01dev/acli-mysql-dump-drupal.sql.gz --bytes --rate | gunzip | MYSQL_PWD=password mysql --host=fsdb-74.enterprise-g1.hosting.acquia.com.enterprise-g1.hosting.acquia.com --user=s164 profserv2db14390', + ]; + $localMachineHelper->execute($cmd, Argument::type('callable'), NULL, $printOutput, NULL) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + } + } diff --git a/tests/phpunit/src/Commands/Ssh/SshKeyUploadCommandTest.php b/tests/phpunit/src/Commands/Ssh/SshKeyUploadCommandTest.php index cef62606c..edeb76a61 100644 --- a/tests/phpunit/src/Commands/Ssh/SshKeyUploadCommandTest.php +++ b/tests/phpunit/src/Commands/Ssh/SshKeyUploadCommandTest.php @@ -89,7 +89,7 @@ public function testUpload(array $args, array $inputs, bool $perms): void { if ($perms) { $environmentsResponse = $this->mockEnvironmentsRequest($applicationsResponse); - $sshHelper = $this->mockPollCloudViaSsh($environmentsResponse); + $sshHelper = $this->mockPollCloudViaSsh($environmentsResponse->_embedded->items); $this->command->sshHelper = $sshHelper->reveal(); } @@ -103,6 +103,60 @@ public function testUpload(array $args, array $inputs, bool $perms): void { $this->assertStringContainsString('Your SSH key is ready for use!', $output); } + // Ensure permission checks aren't against a Node environment. + public function testUploadNode(): void { + $sshKeysRequestBody = $this->getMockRequestBodyFromSpec('/account/ssh-keys'); + $body = [ + 'json' => [ + 'label' => $sshKeysRequestBody['label'], + 'public_key' => $sshKeysRequestBody['public_key'], + ], + ]; + $this->mockRequest('postAccountSshKeys', NULL, $body); + $this->mockListSshKeyRequestWithUploadedKey($sshKeysRequestBody); + $applicationsResponse = $this->mockApplicationsRequest(); + $applicationResponse = $this->mockApplicationRequest(); + $this->mockPermissionsRequest($applicationResponse, TRUE); + + $localMachineHelper = $this->mockLocalMachineHelper(); + /** @var Filesystem|\Prophecy\Prophecy\ObjectProphecy $fileSystem */ + $fileSystem = $this->prophet->prophesize(Filesystem::class); + $fileName = $this->mockGetLocalSshKey($localMachineHelper, $fileSystem, $sshKeysRequestBody['public_key']); + + $localMachineHelper->getFilesystem()->willReturn($fileSystem); + $fileSystem->exists(Argument::type('string'))->willReturn(TRUE); + $localMachineHelper->getLocalFilepath(Argument::containingString('id_rsa'))->willReturn('id_rsa.pub'); + $localMachineHelper->readFile(Argument::type('string'))->willReturn($sshKeysRequestBody['public_key']); + + $tamper = function ($responses): void { + foreach ($responses as $response) { + $response->type = 'node'; + } + }; + $environmentsResponse = $this->mockRequest('getApplicationEnvironments', $applicationsResponse->_embedded->items[0]->uuid, NULL, NULL, $tamper); + $sshHelper = $this->mockPollCloudViaSsh($environmentsResponse, FALSE); + $this->command->sshHelper = $sshHelper->reveal(); + + // Choose a local SSH key to upload to the Cloud Platform. + $inputs = [ + // Choose key. + '0', + // Enter a Cloud Platform label for this SSH key: + $sshKeysRequestBody['label'], + // Would you like to wait until Cloud Platform is ready? (yes/no) + 'y', + // Would you like Acquia CLI to search for a Cloud application that matches your local git config? (yes/no) + 'n', + ]; + $this->executeCommand([], $inputs); + + // Assert. + $output = $this->getDisplay(); + $this->assertStringContainsString("Uploaded $fileName to the Cloud Platform with label " . $sshKeysRequestBody['label'], $output); + $this->assertStringContainsString('Would you like to wait until your key is installed on all of your application\'s servers?', $output); + $this->assertStringContainsString('Your SSH key is ready for use!', $output); + } + public function testInvalidFilepath(): void { $inputs = [ // Choose key. diff --git a/tests/phpunit/src/Commands/WizardTestBase.php b/tests/phpunit/src/Commands/WizardTestBase.php index b22a98d80..5b2142571 100644 --- a/tests/phpunit/src/Commands/WizardTestBase.php +++ b/tests/phpunit/src/Commands/WizardTestBase.php @@ -64,7 +64,7 @@ protected function runTestCreate(): void { $localMachineHelper = $this->mockLocalMachineHelper(); // Poll Cloud. - $sshHelper = $this->mockPollCloudViaSsh($environmentsResponse); + $sshHelper = $this->mockPollCloudViaSsh($environmentsResponse->_embedded->items); $this->command->sshHelper = $sshHelper->reveal(); $fileSystem = $this->prophet->prophesize(Filesystem::class); @@ -132,7 +132,7 @@ protected function runTestSshKeyAlreadyUploaded(): void { $this->mockRequest('postAccountSshKeys', NULL, $body); // Poll Cloud. - $sshHelper = $this->mockPollCloudViaSsh($environmentsResponse); + $sshHelper = $this->mockPollCloudViaSsh($environmentsResponse->_embedded->items); $this->command->sshHelper = $sshHelper->reveal(); $fileSystem = $this->prophet->prophesize(Filesystem::class);