From d1ff05d8a1e11bef8b2dfd8742cee73e580ca677 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Mon, 24 Aug 2026 11:01:59 -0700 Subject: [PATCH 1/6] Link environments to credential pairs --- src/CloudApi/CloudCredentials.php | 20 ++++++- src/Command/Auth/AuthLoginCommand.php | 50 +++++++++++++---- src/Config/CloudDataConfig.php | 2 + .../src/CloudApi/CloudCredentialsTest.php | 55 +++++++++++++++++++ .../Commands/Auth/AuthLoginCommandTest.php | 50 +++++++++++++++++ 5 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 tests/phpunit/src/CloudApi/CloudCredentialsTest.php diff --git a/src/CloudApi/CloudCredentials.php b/src/CloudApi/CloudCredentials.php index 793b05d43..424304de6 100644 --- a/src/CloudApi/CloudCredentials.php +++ b/src/CloudApi/CloudCredentials.php @@ -84,7 +84,7 @@ public function getBaseUri(): ?string if ($uri = getenv('ACLI_CLOUD_API_BASE_URI')) { return $uri; } - return null; + return $this->getActiveKeyData()['cloud_api_base_uri'] ?? null; } /** @@ -104,6 +104,22 @@ public function getAccountsUri(): ?string if ($uri = getenv('ACLI_CLOUD_API_ACCOUNTS_URI')) { return $uri; } - return null; + return $this->getActiveKeyData()['accounts_uri'] ?? null; + } + + /** + * @return array|null + */ + private function getActiveKeyData(): ?array + { + $activeKey = $this->datastoreCloud->get('acli_key'); + if (!$activeKey) { + return null; + } + $keys = $this->datastoreCloud->get('keys'); + if (!is_array($keys) || !array_key_exists($activeKey, $keys)) { + return null; + } + return $keys[$activeKey]; } } diff --git a/src/Command/Auth/AuthLoginCommand.php b/src/Command/Auth/AuthLoginCommand.php index 098598fa1..e6b033803 100644 --- a/src/Command/Auth/AuthLoginCommand.php +++ b/src/Command/Auth/AuthLoginCommand.php @@ -20,11 +20,15 @@ protected function configure(): void $this ->addOption('key', 'k', InputOption::VALUE_REQUIRED, 'Your Cloud Platform API key') ->addOption('secret', 's', InputOption::VALUE_REQUIRED, 'Your Cloud Platform API secret') + ->addOption('environment', null, InputOption::VALUE_REQUIRED, '', 'prod') ->setHelp('Acquia CLI can store multiple sets of credentials in case you have multiple Cloud Platform accounts. However, only a single account can be active at a time. This command allows you to activate a new or existing set of credentials.'); } protected function execute(InputInterface $input, OutputInterface $output): int { + $env = $input->getOption('environment'); + [$baseUri, $accountsUri] = $this->getUrisForEnvironment($env); + $keys = $this->datastoreCloud->get('keys'); $activeKey = $this->datastoreCloud->get('acli_key'); if ($activeKey) { @@ -34,16 +38,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('No Cloud Platform API key is active'); } - // If keys already are saved locally, prompt to select. - if ($keys && $input->isInteractive()) { - foreach ($keys as $uuid => $key) { - $keys[$uuid]['uuid'] = $uuid; + $matchingKeys = array_filter( + $keys ?? [], + static fn(array $keyData) => ($keyData['cloud_api_base_uri'] ?? null) === $baseUri, + ); + + // If keys already are saved locally for this environment, prompt to select. + if ($matchingKeys && $input->isInteractive()) { + foreach ($matchingKeys as $uuid => $key) { + $matchingKeys[$uuid]['uuid'] = $uuid; } - $keys['create_new'] = [ + $matchingKeys['create_new'] = [ 'label' => 'Enter a new API key', 'uuid' => 'create_new', ]; - $selectedKey = $this->promptChooseFromObjectsOrArrays($keys, 'uuid', 'label', 'Activate a Cloud Platform API key'); + $selectedKey = $this->promptChooseFromObjectsOrArrays($matchingKeys, 'uuid', 'label', 'Activate a Cloud Platform API key'); if ($selectedKey['uuid'] !== 'create_new') { $this->datastoreCloud->set('acli_key', $selectedKey['uuid']); $output->writeln("Acquia CLI will use the API key {$selectedKey['label']}"); @@ -55,23 +64,44 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->promptOpenBrowserToCreateToken($input); $apiKey = $this->determineApiKey(); $apiSecret = $this->determineApiSecret(); - $this->reAuthenticate($apiKey, $apiSecret, $this->cloudCredentials->getBaseUri(), $this->cloudCredentials->getAccountsUri()); - $this->writeApiCredentialsToDisk($apiKey, $apiSecret); + $this->reAuthenticate($apiKey, $apiSecret, $baseUri, $accountsUri); + $this->writeApiCredentialsToDisk($apiKey, $apiSecret, $baseUri, $accountsUri); $output->writeln("Saved credentials"); return Command::SUCCESS; } - private function writeApiCredentialsToDisk(string $apiKey, string $apiSecret): void + /** + * @return array{?string, ?string} + */ + private function getUrisForEnvironment(string $env): array + { + if ($env === 'prod') { + return [null, null]; + } + return [ + "https://{$env}.cloud.acquia.com/api", + "https://{$env}.accounts.acquia.com/api/auth/oauth/token", + ]; + } + + private function writeApiCredentialsToDisk(string $apiKey, string $apiSecret, ?string $baseUri = null, ?string $accountsUri = null): void { $account = new Account($this->cloudApiClientService->getClient()); $accountInfo = $account->get(); $keys = $this->datastoreCloud->get('keys'); - $keys[$apiKey] = [ + $keyData = [ 'label' => $accountInfo->mail, 'secret' => $apiSecret, 'uuid' => $apiKey, ]; + if ($baseUri !== null) { + $keyData['cloud_api_base_uri'] = $baseUri; + } + if ($accountsUri !== null) { + $keyData['accounts_uri'] = $accountsUri; + } + $keys[$apiKey] = $keyData; $this->datastoreCloud->set('keys', $keys); $this->datastoreCloud->set('acli_key', $apiKey); } diff --git a/src/Config/CloudDataConfig.php b/src/Config/CloudDataConfig.php index a5ae83707..c4eef2c40 100644 --- a/src/Config/CloudDataConfig.php +++ b/src/Config/CloudDataConfig.php @@ -34,6 +34,8 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('label')->end() ->scalarNode('uuid')->end() ->scalarNode('secret')->isRequired()->end() + ->scalarNode('cloud_api_base_uri')->defaultNull()->end() + ->scalarNode('accounts_uri')->defaultNull()->end() ->end() ->end() ->end() diff --git a/tests/phpunit/src/CloudApi/CloudCredentialsTest.php b/tests/phpunit/src/CloudApi/CloudCredentialsTest.php new file mode 100644 index 000000000..b6f2944d7 --- /dev/null +++ b/tests/phpunit/src/CloudApi/CloudCredentialsTest.php @@ -0,0 +1,55 @@ +assertNull($this->cloudCredentials->getBaseUri()); + $this->assertNull($this->cloudCredentials->getAccountsUri()); + } + + public function testGetBaseUriReturnsStoredUriWhenNoEnvVar(): void + { + $this->datastoreCloud->set('keys', [ + self::$key => [ + 'accounts_uri' => 'https://staging.accounts.acquia.com/api/auth/oauth/token', + 'cloud_api_base_uri' => 'https://staging.cloud.acquia.com/api', + 'label' => 'Test Key', + 'secret' => self::$secret, + 'uuid' => self::$key, + ], + ]); + + $this->assertSame('https://staging.cloud.acquia.com/api', $this->cloudCredentials->getBaseUri()); + $this->assertSame('https://staging.accounts.acquia.com/api/auth/oauth/token', $this->cloudCredentials->getAccountsUri()); + } + + public function testGetBaseUriEnvVarTakesPriorityOverStoredUri(): void + { + $this->datastoreCloud->set('keys', [ + self::$key => [ + 'accounts_uri' => 'https://staging.accounts.acquia.com/api/auth/oauth/token', + 'cloud_api_base_uri' => 'https://staging.cloud.acquia.com/api', + 'label' => 'Test Key', + 'secret' => self::$secret, + 'uuid' => self::$key, + ], + ]); + putenv('ACLI_CLOUD_API_BASE_URI=https://qa.cloud.acquia.com/api'); + putenv('ACLI_CLOUD_API_ACCOUNTS_URI=https://qa.accounts.acquia.com/api/auth/oauth/token'); + + try { + $this->assertSame('https://qa.cloud.acquia.com/api', $this->cloudCredentials->getBaseUri()); + $this->assertSame('https://qa.accounts.acquia.com/api/auth/oauth/token', $this->cloudCredentials->getAccountsUri()); + } finally { + putenv('ACLI_CLOUD_API_BASE_URI'); + putenv('ACLI_CLOUD_API_ACCOUNTS_URI'); + } + } +} diff --git a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php index 7a7900567..176774393 100644 --- a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php +++ b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php @@ -47,6 +47,56 @@ public function testAuthLoginCommand(): void $this->assertKeySavedCorrectly(); } + public function testAuthLoginCommandWithStagingEnvironment(): void + { + $this->mockRequest('getAccount'); + $this->clientServiceProphecy->setConnector(Argument::type(Connector::class)) + ->shouldBeCalled(); + $this->clientServiceProphecy->isMachineAuthenticated() + ->willReturn(false); + $this->removeMockCloudConfigFile(); + $this->createDataStores(); + $this->command = $this->createCommand(); + + $this->executeCommand([ + '--environment' => 'staging', + '--key' => self::$key, + '--secret' => self::$secret, + ]); + $output = $this->getDisplay(); + + $this->assertStringContainsString('Saved credentials', $output); + $this->assertKeySavedCorrectly(); + $config = new CloudDataStore($this->localMachineHelper, new CloudDataConfig(), $this->cloudConfigFilepath); + $keys = $config->get('keys'); + $this->assertSame('https://staging.cloud.acquia.com/api', $keys[self::$key]['cloud_api_base_uri']); + $this->assertSame('https://staging.accounts.acquia.com/api/auth/oauth/token', $keys[self::$key]['accounts_uri']); + } + + public function testAuthLoginCommandProdEnvironmentDoesNotStoreUris(): void + { + $this->mockRequest('getAccount'); + $this->clientServiceProphecy->setConnector(Argument::type(Connector::class)) + ->shouldBeCalled(); + $this->clientServiceProphecy->isMachineAuthenticated() + ->willReturn(false); + $this->removeMockCloudConfigFile(); + $this->createDataStores(); + $this->command = $this->createCommand(); + + $this->executeCommand([ + '--key' => self::$key, + '--secret' => self::$secret, + ]); + $output = $this->getDisplay(); + + $this->assertStringContainsString('Saved credentials', $output); + $config = new CloudDataStore($this->localMachineHelper, new CloudDataConfig(), $this->cloudConfigFilepath); + $keys = $config->get('keys'); + $this->assertNull($keys[self::$key]['cloud_api_base_uri'] ?? null); + $this->assertNull($keys[self::$key]['accounts_uri'] ?? null); + } + public function testAuthLoginNoKeysCommand(): void { $this->mockRequest('getAccount'); From e07415e5191f49180ad65a7b7adbf4f26adba098 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Mon, 24 Aug 2026 11:29:34 -0700 Subject: [PATCH 2/6] add description --- src/Command/Auth/AuthLoginCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/Auth/AuthLoginCommand.php b/src/Command/Auth/AuthLoginCommand.php index e6b033803..a4d2d5fd6 100644 --- a/src/Command/Auth/AuthLoginCommand.php +++ b/src/Command/Auth/AuthLoginCommand.php @@ -20,7 +20,7 @@ protected function configure(): void $this ->addOption('key', 'k', InputOption::VALUE_REQUIRED, 'Your Cloud Platform API key') ->addOption('secret', 's', InputOption::VALUE_REQUIRED, 'Your Cloud Platform API secret') - ->addOption('environment', null, InputOption::VALUE_REQUIRED, '', 'prod') + ->addOption('environment', null, InputOption::VALUE_REQUIRED, 'Cloud Platform API environment', 'prod') ->setHelp('Acquia CLI can store multiple sets of credentials in case you have multiple Cloud Platform accounts. However, only a single account can be active at a time. This command allows you to activate a new or existing set of credentials.'); } From b643847b79016c0758d011a9dae547601965182a Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Mon, 24 Aug 2026 11:41:54 -0700 Subject: [PATCH 3/6] add test, fix warning, add validation --- src/CloudApi/CloudCredentials.php | 4 ++-- src/Command/Auth/AuthLoginCommand.php | 11 +++++++--- .../Commands/Auth/AuthLoginCommandTest.php | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/CloudApi/CloudCredentials.php b/src/CloudApi/CloudCredentials.php index 424304de6..79c906354 100644 --- a/src/CloudApi/CloudCredentials.php +++ b/src/CloudApi/CloudCredentials.php @@ -84,7 +84,7 @@ public function getBaseUri(): ?string if ($uri = getenv('ACLI_CLOUD_API_BASE_URI')) { return $uri; } - return $this->getActiveKeyData()['cloud_api_base_uri'] ?? null; + return ($this->getActiveKeyData() ?? [])['cloud_api_base_uri'] ?? null; } /** @@ -104,7 +104,7 @@ public function getAccountsUri(): ?string if ($uri = getenv('ACLI_CLOUD_API_ACCOUNTS_URI')) { return $uri; } - return $this->getActiveKeyData()['accounts_uri'] ?? null; + return ($this->getActiveKeyData() ?? [])['accounts_uri'] ?? null; } /** diff --git a/src/Command/Auth/AuthLoginCommand.php b/src/Command/Auth/AuthLoginCommand.php index a4d2d5fd6..28118ff62 100644 --- a/src/Command/Auth/AuthLoginCommand.php +++ b/src/Command/Auth/AuthLoginCommand.php @@ -5,6 +5,7 @@ namespace Acquia\Cli\Command\Auth; use Acquia\Cli\Command\CommandBase; +use Acquia\Cli\Exception\AcquiaCliException; use AcquiaCloudApi\Endpoints\Account; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -76,12 +77,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ private function getUrisForEnvironment(string $env): array { - if ($env === 'prod') { + $env = strtolower(trim($env)); + if ($env === '' || $env === 'prod') { return [null, null]; } + if (!preg_match('/^[a-z0-9-]+$/', $env)) { + throw new AcquiaCliException('Invalid environment value: {env}', ['env' => $env]); + } return [ - "https://{$env}.cloud.acquia.com/api", - "https://{$env}.accounts.acquia.com/api/auth/oauth/token", + "https://$env.cloud.acquia.com/api", + "https://$env.accounts.acquia.com/api/auth/oauth/token", ]; } diff --git a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php index 176774393..72ba968af 100644 --- a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php +++ b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php @@ -150,6 +150,26 @@ public function testAuthLoginInvalidInputCommand(array $inputs, array $args): vo $this->executeCommand($args, $inputs); } + public static function providerTestAuthLoginInvalidEnvironmentCommand(): Generator + { + yield [['--key' => self::$key, '--secret' => self::$secret, '--environment' => 'my env']]; + yield [['--key' => self::$key, '--secret' => self::$secret, '--environment' => 'env!']]; + yield [['--key' => self::$key, '--secret' => self::$secret, '--environment' => 'env_name']]; + } + + #[DataProvider('providerTestAuthLoginInvalidEnvironmentCommand')] + public function testAuthLoginInvalidEnvironmentCommand(array $args): void + { + $this->clientServiceProphecy->isMachineAuthenticated() + ->willReturn(false); + $this->removeMockCloudConfigFile(); + $this->createDataStores(); + $this->command = $this->createCommand(); + $this->expectException(AcquiaCliException::class); + $this->expectExceptionMessage('Invalid environment value'); + $this->executeCommand($args); + } + public function testAuthLoginInvalidDatastore(): void { $this->clientServiceProphecy->isMachineAuthenticated() From 99bd93d42c730c51ca574221e7267d8698298571 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Mon, 31 Aug 2026 11:11:58 -0700 Subject: [PATCH 4/6] add coverage --- .../src/CloudApi/CloudCredentialsTest.php | 17 +++ .../Commands/Auth/AuthLoginCommandTest.php | 139 ++++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/tests/phpunit/src/CloudApi/CloudCredentialsTest.php b/tests/phpunit/src/CloudApi/CloudCredentialsTest.php index b6f2944d7..cd6fa4066 100644 --- a/tests/phpunit/src/CloudApi/CloudCredentialsTest.php +++ b/tests/phpunit/src/CloudApi/CloudCredentialsTest.php @@ -30,6 +30,23 @@ public function testGetBaseUriReturnsStoredUriWhenNoEnvVar(): void $this->assertSame('https://staging.accounts.acquia.com/api/auth/oauth/token', $this->cloudCredentials->getAccountsUri()); } + public function testGetBaseUriReturnsNullWhenActiveKeyNotInKeysArray(): void + { + $this->datastoreCloud->set('keys', [ + self::$key => [ + 'accounts_uri' => 'https://staging.accounts.acquia.com/api/auth/oauth/token', + 'cloud_api_base_uri' => 'https://staging.cloud.acquia.com/api', + 'label' => 'Test Key', + 'secret' => self::$secret, + 'uuid' => self::$key, + ], + ]); + $this->datastoreCloud->set('acli_key', 'nonexistent-key-uuid'); + + $this->assertNull($this->cloudCredentials->getBaseUri()); + $this->assertNull($this->cloudCredentials->getAccountsUri()); + } + public function testGetBaseUriEnvVarTakesPriorityOverStoredUri(): void { $this->datastoreCloud->set('keys', [ diff --git a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php index 72ba968af..9cc11cf9d 100644 --- a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php +++ b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php @@ -4,6 +4,7 @@ namespace Acquia\Cli\Tests\Commands\Auth; +use Acquia\Cli\CloudApi\CloudCredentials; use Acquia\Cli\Command\Auth\AuthLoginCommand; use Acquia\Cli\Command\CommandBase; use Acquia\Cli\Config\CloudDataConfig; @@ -97,6 +98,144 @@ public function testAuthLoginCommandProdEnvironmentDoesNotStoreUris(): void $this->assertNull($keys[self::$key]['accounts_uri'] ?? null); } + public function testAuthLoginExplicitProdEnvironmentDoesNotStoreUris(): void + { + $this->mockRequest('getAccount'); + $this->clientServiceProphecy->setConnector(Argument::type(Connector::class)) + ->shouldBeCalled(); + $this->clientServiceProphecy->isMachineAuthenticated() + ->willReturn(false); + $this->removeMockCloudConfigFile(); + $this->createDataStores(); + $this->command = $this->createCommand(); + + $this->executeCommand([ + '--environment' => 'prod', + '--key' => self::$key, + '--secret' => self::$secret, + ]); + $output = $this->getDisplay(); + + $this->assertStringContainsString('Saved credentials', $output); + $config = new CloudDataStore($this->localMachineHelper, new CloudDataConfig(), $this->cloudConfigFilepath); + $keys = $config->get('keys'); + $this->assertNull($keys[self::$key]['cloud_api_base_uri'] ?? null); + $this->assertNull($keys[self::$key]['accounts_uri'] ?? null); + } + + public function testAuthLoginInteractiveSelectsExistingEnvironmentKey(): void + { + $stagingKeyUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'; + $this->clientServiceProphecy->setConnector(Argument::type(Connector::class)) + ->shouldBeCalled(); + $this->clientServiceProphecy->isMachineAuthenticated() + ->willReturn(false); + $this->fs->dumpFile($this->cloudConfigFilepath, json_encode([ + 'acli_key' => $stagingKeyUuid, + 'keys' => [ + $stagingKeyUuid => [ + 'accounts_uri' => 'https://staging.accounts.acquia.com/api/auth/oauth/token', + 'cloud_api_base_uri' => 'https://staging.cloud.acquia.com/api', + 'label' => 'Staging Key', + 'secret' => self::$secret, + 'uuid' => $stagingKeyUuid, + ], + ], + 'send_telemetry' => false, + ])); + $this->createDataStores(); + $this->cloudCredentials = new CloudCredentials($this->datastoreCloud); + $this->command = $this->createCommand(); + + $this->executeCommand( + ['--environment' => 'staging'], + ['Staging Key'], + ); + $output = $this->getDisplay(); + + $this->assertStringContainsString('Acquia CLI will use the API key', $output); + $this->assertStringContainsString('Staging Key', $output); + $config = new CloudDataStore($this->localMachineHelper, new CloudDataConfig(), $this->cloudConfigFilepath); + $this->assertSame($stagingKeyUuid, $config->get('acli_key')); + } + + public function testAuthLoginInteractiveCreatesNewKeyForExistingEnvironment(): void + { + $stagingKeyUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'; + $this->mockRequest('getAccount'); + $this->clientServiceProphecy->setConnector(Argument::type(Connector::class)) + ->shouldBeCalled(); + $this->clientServiceProphecy->isMachineAuthenticated() + ->willReturn(false); + $this->fs->dumpFile($this->cloudConfigFilepath, json_encode([ + 'acli_key' => $stagingKeyUuid, + 'keys' => [ + $stagingKeyUuid => [ + 'accounts_uri' => 'https://staging.accounts.acquia.com/api/auth/oauth/token', + 'cloud_api_base_uri' => 'https://staging.cloud.acquia.com/api', + 'label' => 'Staging Key', + 'secret' => self::$secret, + 'uuid' => $stagingKeyUuid, + ], + ], + 'send_telemetry' => false, + ])); + $this->createDataStores(); + $this->command = $this->createCommand(); + + $this->executeCommand( + ['--environment' => 'staging', '--key' => self::$key, '--secret' => self::$secret], + ['Enter a new API key'], + ); + $output = $this->getDisplay(); + + $this->assertStringContainsString('Saved credentials', $output); + $config = new CloudDataStore($this->localMachineHelper, new CloudDataConfig(), $this->cloudConfigFilepath); + $this->assertSame(self::$key, $config->get('acli_key')); + $keys = $config->get('keys'); + $this->assertSame('https://staging.cloud.acquia.com/api', $keys[self::$key]['cloud_api_base_uri']); + $this->assertSame('https://staging.accounts.acquia.com/api/auth/oauth/token', $keys[self::$key]['accounts_uri']); + } + + public function testAuthLoginProdLoginSkipsNonProdKeys(): void + { + $stagingKeyUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'; + $this->mockRequest('getAccount'); + $this->clientServiceProphecy->setConnector(Argument::type(Connector::class)) + ->shouldBeCalled(); + $this->clientServiceProphecy->isMachineAuthenticated() + ->willReturn(false); + // Only a staging key exists; logging in to prod should not prompt for it. + $this->fs->dumpFile($this->cloudConfigFilepath, json_encode([ + 'acli_key' => $stagingKeyUuid, + 'keys' => [ + $stagingKeyUuid => [ + 'accounts_uri' => 'https://staging.accounts.acquia.com/api/auth/oauth/token', + 'cloud_api_base_uri' => 'https://staging.cloud.acquia.com/api', + 'label' => 'Staging Key', + 'secret' => self::$secret, + 'uuid' => $stagingKeyUuid, + ], + ], + 'send_telemetry' => false, + ])); + $this->createDataStores(); + $this->command = $this->createCommand(); + + // No interactive input needed: no prod keys exist so no selection prompt is shown. + $this->executeCommand([ + '--key' => self::$key, + '--secret' => self::$secret, + ]); + $output = $this->getDisplay(); + + $this->assertStringContainsString('Saved credentials', $output); + $config = new CloudDataStore($this->localMachineHelper, new CloudDataConfig(), $this->cloudConfigFilepath); + $this->assertSame(self::$key, $config->get('acli_key')); + $keys = $config->get('keys'); + $this->assertNull($keys[self::$key]['cloud_api_base_uri'] ?? null); + } + public function testAuthLoginNoKeysCommand(): void { $this->mockRequest('getAccount'); From a7f7ac4a896612f763c8b1d9c0942f8c24070e06 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Mon, 31 Aug 2026 11:52:27 -0700 Subject: [PATCH 5/6] kill mutant --- .../Commands/Auth/AuthLoginCommandTest.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php index 9cc11cf9d..8b110801a 100644 --- a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php +++ b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php @@ -236,6 +236,41 @@ public function testAuthLoginProdLoginSkipsNonProdKeys(): void $this->assertNull($keys[self::$key]['cloud_api_base_uri'] ?? null); } + public function testAuthLoginNonInteractiveWithExistingProdKey(): void + { + $existingKeyUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'; + $this->mockRequest('getAccount'); + $this->clientServiceProphecy->setConnector(Argument::type(Connector::class)) + ->shouldBeCalled(); + $this->clientServiceProphecy->isMachineAuthenticated() + ->willReturn(false); + $this->fs->dumpFile($this->cloudConfigFilepath, json_encode([ + 'acli_key' => $existingKeyUuid, + 'keys' => [ + $existingKeyUuid => [ + 'label' => 'Existing Key', + 'secret' => 'existing-secret', + 'uuid' => $existingKeyUuid, + // No cloud_api_base_uri = prod key. + ], + ], + 'send_telemetry' => false, + ])); + $this->createDataStores(); + $this->command = $this->createCommand(); + + $this->executeCommand( + ['--key' => self::$key, '--secret' => self::$secret], + inputs: [], + interactive: false, + ); + $output = $this->getDisplay(); + + $this->assertStringContainsString('Saved credentials', $output); + $config = new CloudDataStore($this->localMachineHelper, new CloudDataConfig(), $this->cloudConfigFilepath); + $this->assertSame(self::$key, $config->get('acli_key')); + } + public function testAuthLoginNoKeysCommand(): void { $this->mockRequest('getAccount'); From 1ec92b847fabc093594c3cd6ac0d872b9db14e8e Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Mon, 31 Aug 2026 12:21:17 -0700 Subject: [PATCH 6/6] mutants --- .../Commands/Auth/AuthLoginCommandTest.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php index 8b110801a..13ae91c0e 100644 --- a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php +++ b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php @@ -48,7 +48,20 @@ public function testAuthLoginCommand(): void $this->assertKeySavedCorrectly(); } - public function testAuthLoginCommandWithStagingEnvironment(): void + /** + * @return string[] + */ + public static function providerTestAuthLoginCommandWithStagingEnvironment(): array + { + return [ + ['staging'], + ['Staging'], + [' staging'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerTestAuthLoginCommandWithStagingEnvironment')] + public function testAuthLoginCommandWithStagingEnvironment(string $environment): void { $this->mockRequest('getAccount'); $this->clientServiceProphecy->setConnector(Argument::type(Connector::class)) @@ -60,7 +73,7 @@ public function testAuthLoginCommandWithStagingEnvironment(): void $this->command = $this->createCommand(); $this->executeCommand([ - '--environment' => 'staging', + '--environment' => $environment, '--key' => self::$key, '--secret' => self::$secret, ]); @@ -340,7 +353,7 @@ public function testAuthLoginInvalidEnvironmentCommand(array $args): void $this->createDataStores(); $this->command = $this->createCommand(); $this->expectException(AcquiaCliException::class); - $this->expectExceptionMessage('Invalid environment value'); + $this->expectExceptionMessage('Invalid environment value: ' . $args['--environment']); $this->executeCommand($args); }