Skip to content

Commit

Permalink
CLI-1297: Fix error messages for ACSF authentication (#1712)
Browse files Browse the repository at this point in the history
* CLI-1297: Fix error messages for ACSF authentication

* fix tests

* fix tests on windows

* fix windows again

* more tests
  • Loading branch information
danepowell authored Mar 26, 2024
1 parent 192edb4 commit fcec6eb
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 111 deletions.
21 changes: 0 additions & 21 deletions src/Command/Acsf/AcsfApiBaseCommand.php

This file was deleted.

5 changes: 3 additions & 2 deletions src/Command/Acsf/AcsfCommandFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Acquia\Cli\AcsfApi\AcsfClientService;
use Acquia\Cli\AcsfApi\AcsfCredentials;
use Acquia\Cli\Command\Api\ApiBaseCommand;
use Acquia\Cli\CommandFactoryInterface;
use Acquia\Cli\DataStore\AcquiaCliDatastore;
use Acquia\Cli\DataStore\CloudDataStore;
Expand All @@ -30,8 +31,8 @@ public function __construct(
) {
}

public function createCommand(): AcsfApiBaseCommand {
return new AcsfApiBaseCommand(
public function createCommand(): ApiBaseCommand {
return new ApiBaseCommand(
$this->localMachineHelper,
$this->datastoreCloud,
$this->datastoreAcli,
Expand Down
6 changes: 5 additions & 1 deletion src/Command/CommandBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Acquia\Cli\Command;

use Acquia\Cli\AcsfApi\AcsfClientService;
use Acquia\Cli\ApiCredentialsInterface;
use Acquia\Cli\Attribute\RequireAuth;
use Acquia\Cli\Attribute\RequireDb;
Expand Down Expand Up @@ -1745,7 +1746,10 @@ protected function validateEnvironmentUuid(mixed $envUuidArgument, mixed $argume

protected function checkAuthentication(): void {
if ((new \ReflectionClass(static::class))->getAttributes(RequireAuth::class) && !$this->cloudApiClientService->isMachineAuthenticated()) {
throw new AcquiaCliException('This machine is not yet authenticated with the Cloud Platform. Run `acli auth:login`');
if ($this->cloudApiClientService instanceof AcsfClientService) {
throw new AcquiaCliException('This machine is not yet authenticated with Site Factory.');
}
throw new AcquiaCliException('This machine is not yet authenticated with the Cloud Platform.');
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/CommandFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@

namespace Acquia\Cli;

use Acquia\Cli\Command\Acsf\AcsfApiBaseCommand;
use Acquia\Cli\Command\Acsf\AcsfListCommand;
use Acquia\Cli\Command\Api\ApiBaseCommand;
use Acquia\Cli\Command\Api\ApiListCommand;

interface CommandFactoryInterface {

// @todo return type should really be an interface
public function createCommand(): ApiBaseCommand|AcsfApiBaseCommand;
public function createCommand(): ApiBaseCommand;

public function createListCommand(): ApiListCommand|AcsfListCommand;

Expand Down
6 changes: 6 additions & 0 deletions src/EventListener/ExceptionListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ public function onConsoleError(ConsoleErrorEvent $event): void {
case 'Access token expiry file not found at {file}':
$this->helpMessages[] = 'Get help for this error at https://docs.acquia.com/ide/known-issues/#the-automated-cloud-platform-api-authentication-might-fail';
break;
case 'This machine is not yet authenticated with the Cloud Platform.':
$this->helpMessages[] = 'Run `acli auth:login` to re-authenticated with the Cloud Platform.';
break;
case 'This machine is not yet authenticated with Site Factory.':
$this->helpMessages[] = 'Run `acli auth:acsf-login` to re-authenticate with Site Factory.';
break;
}
}

Expand Down
85 changes: 3 additions & 82 deletions tests/phpunit/src/Application/ExceptionApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
*
* These must be tested using the ApplicationTestBase, since the Symfony
* CommandTester does not fire Event Dispatchers.
*
* This test suite only needs to verify that the listener catches at least one
* exception. Specific exceptions are tested in ExceptionListenerTest.
*/
class ExceptionApplicationTest extends ApplicationTestBase {

Expand All @@ -28,86 +31,4 @@ public function testInvalidApiCredentials(): void {
self::assertStringContainsString('Your Cloud Platform API credentials are invalid.', $buffer);
}

/**
* @group serial
*/
public function testApiError(): void {
$this->setInput([
'applicationUuid' => '2ed281d4-9dec-4cc3-ac63-691c3ba002c2',
'command' => 'aliases',
]);
$this->mockApiError();
$buffer = $this->runApp();
self::assertStringContainsString('Cloud Platform API returned an error:', $buffer);
}

/**
* @group serial
*/
public function testNoAvailableIdes(): void {
$this->setInput([
'applicationUuid' => '2ed281d4-9dec-4cc3-ac63-691c3ba002c2',
'command' => 'aliases',
]);
$this->mockNoAvailableIdes();
$buffer = $this->runApp();
self::assertStringContainsString('Delete an existing IDE', $buffer);
}

/**
* @group serial
*/
public function testInvalidEnvironmentUuid(): void {
$this->mockRequest('getAccount');
$this->mockRequest('getApplications');
$this->setInput([
'command' => 'log:tail',
'environmentId' => 'aoeuth.aoeu',
]);
$buffer = $this->runApp();
self::assertStringContainsString('can also be a site alias.', $buffer);
}

/**
* @group serial
*/
public function testMissingApplicationUuid(): void {
$this->setInput([
'command' => 'ide:open',
]);
$buffer = $this->runApp();
self::assertStringContainsString('Could not determine Cloud Application.', $buffer);
}

/**
* @group serial
*/
public function testInvalidApplicationUuid(): void {
$this->mockRequest('getAccount');
$this->mockRequest('getApplications');
$this->setInput([
'applicationUuid' => 'aoeuthao',
'command' => 'ide:open',
]);
$buffer = $this->runApp();
self::assertStringContainsString('An alias consists of an application name', $buffer);
}

/**
* @group serial
*/
public function testApiTypeError(): void {
$tamper = function ($response): void {
$response[0]->server = [];
};
$this->mockRequest('getCronJobsByEnvironmentId', '24-a47ac10b-58cc-4372-a567-0e02b2c3d470', NULL, NULL, $tamper);
$this->setInput([
'command' => 'env:cron-copy',
'dest_env' => '24-a47ac10b-58cc-4372-a567-0e02b2c3d471',
'source_env' => '24-a47ac10b-58cc-4372-a567-0e02b2c3d470',
]);
$buffer = $this->runApp();
self::assertStringContainsString('Cloud Platform API returned an unexpected data type.', $buffer);
}

}
18 changes: 16 additions & 2 deletions tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
use Acquia\Cli\AcsfApi\AcsfClient;
use Acquia\Cli\AcsfApi\AcsfClientService;
use Acquia\Cli\AcsfApi\AcsfCredentials;
use Acquia\Cli\Command\Acsf\AcsfApiBaseCommand;
use Acquia\Cli\Command\Acsf\AcsfCommandFactory;
use Acquia\Cli\Command\Api\ApiBaseCommand;
use Acquia\Cli\Command\CommandBase;
use Acquia\Cli\CommandFactoryInterface;
use Acquia\Cli\Exception\AcquiaCliException;
use Prophecy\Argument;
use Symfony\Component\Console\Output\OutputInterface;

Expand All @@ -32,7 +33,7 @@ protected function createCommand(): CommandBase {
$this->createMockCloudConfigFile($this->getAcsfCredentialsFileContents());
$this->cloudCredentials = new AcsfCredentials($this->datastoreCloud);
$this->setClientProphecies();
return $this->injectCommand(AcsfApiBaseCommand::class);
return $this->injectCommand(ApiBaseCommand::class);
}

public function testAcsfCommandExecutionForHttpPostWithMultipleDataTypes(): void {
Expand Down Expand Up @@ -114,6 +115,19 @@ public function testAcsfCommandExecutionForHttpGetMultiple(string $method, strin
json_decode($output, TRUE);
}

public function testAcsfUnauthenticatedFailure(): void {
$this->clientServiceProphecy->isMachineAuthenticated()->willReturn(FALSE);
$this->removeMockConfigFiles();

$inputs = [
// Would you like to share anonymous performance usage and data?
'n',
];
$this->expectException(AcquiaCliException::class);
$this->expectExceptionMessage('This machine is not yet authenticated with Site Factory.');
$this->executeCommand([], $inputs);
}

protected function setClientProphecies(): void {
$this->clientProphecy = $this->prophet->prophesize(AcsfClient::class);
$this->clientProphecy->addOption('headers', ['User-Agent' => 'acli/UNKNOWN']);
Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/src/Commands/CommandBaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function testUnauthenticatedFailure(): void {
'n',
];
$this->expectException(AcquiaCliException::class);
$this->expectExceptionMessage('This machine is not yet authenticated with the Cloud Platform. Run `acli auth:login`');
$this->expectExceptionMessage('This machine is not yet authenticated with the Cloud Platform.');
$this->executeCommand([], $inputs);
}

Expand Down
108 changes: 108 additions & 0 deletions tests/phpunit/src/Misc/ExceptionListenerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types = 1);

namespace Acquia\Cli\Tests\Misc;

use Acquia\Cli\Application;
use Acquia\Cli\EventListener\ExceptionListener;
use Acquia\Cli\Exception\AcquiaCliException;
use Acquia\Cli\Tests\TestBase;
use AcquiaCloudApi\Exception\ApiErrorException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Exception\RuntimeException;
use Throwable;

class ExceptionListenerTest extends TestBase {
private static string $siteAliasHelp = '<bg=blue;options=bold>environmentId</> can also be a site alias. E.g. <bg=blue;fg=white;options=bold>myapp.dev</>.' . PHP_EOL . 'Run <bg=blue;options=bold>acli remote:aliases:list</> to see a list of all available aliases.';
private static string $appAliasHelp = 'The <bg=blue;options=bold>applicationUuid</> argument must be a valid UUID or unique application alias accessible to your Cloud Platform user.' . PHP_EOL . PHP_EOL . 'An alias consists of an application name optionally prefixed with a hosting realm, e.g. <bg=blue;fg=white;options=bold>myapp</> or <bg=blue;fg=white;options=bold>prod.myapp</>.' . PHP_EOL . PHP_EOL . 'Run <bg=blue;options=bold>acli remote:aliases:list</> to see a list of all available aliases.';

/**
* @dataProvider providerTestHelp
*/
public function testHelp(Throwable $error, string|array $helpText): void {
$exceptionListener = new ExceptionListener();
$commandProphecy = $this->prophet->prophesize(Command::class);
$applicationProphecy = $this->prophet->prophesize(Application::class);
$messages1 = ['You can find Acquia CLI documentation at https://docs.acquia.com/acquia-cli/', 'You can submit a support ticket at https://support-acquia.force.com/s/contactsupport' . PHP_EOL . 'Re-run the command with the <bg=blue;fg=white;options=bold>-vvv</> flag and include the full command output in your support ticket.'];
if (is_array($helpText)) {
$messages = array_merge($helpText, $messages1);
}
else {
$messages = array_merge([$helpText], $messages1);
}
$applicationProphecy->setHelpMessages($messages)->shouldBeCalled();
$commandProphecy->getApplication()->willReturn($applicationProphecy->reveal());
$consoleErrorEvent = new ConsoleErrorEvent($this->input, $this->output, $error, $commandProphecy->reveal());
$exceptionListener->onConsoleError($consoleErrorEvent);
$this->prophet->checkPredictions();
self::assertTrue(TRUE);
}

/**
* @return string[][]
*/
public function providerTestHelp(): array {
return [
[
new IdentityProviderException('invalid_client', 0, ''),
'Run <bg=blue;fg=white;options=bold>acli auth:login</> to reset your API credentials.',
],
[
new RuntimeException('Not enough arguments (missing: "environmentId").'),
self::$siteAliasHelp,
],
[
new RuntimeException('Not enough arguments (missing: "environmentUuid").'),
self::$siteAliasHelp,
],
[
new AcquiaCliException('No applications match the alias {applicationAlias}'),
self::$appAliasHelp,
],
[
new AcquiaCliException('Multiple applications match the alias {applicationAlias}'),
self::$appAliasHelp,
],
[
new AcquiaCliException('{environmentId} must be a valid UUID or site alias.'),
self::$siteAliasHelp,
],
[
new AcquiaCliException('{environmentUuid} must be a valid UUID or site alias.'),
self::$siteAliasHelp,
],
[
new AcquiaCliException('Access token file not found at {file}'),
'Get help for this error at https://docs.acquia.com/ide/known-issues/#the-automated-cloud-platform-api-authentication-might-fail',
],
[
new AcquiaCliException('Access token expiry file not found at {file}'),
'Get help for this error at https://docs.acquia.com/ide/known-issues/#the-automated-cloud-platform-api-authentication-might-fail',
],
[
new AcquiaCliException('This machine is not yet authenticated with the Cloud Platform.'),
'Run `acli auth:login` to re-authenticated with the Cloud Platform.',
],
[
new AcquiaCliException('This machine is not yet authenticated with Site Factory.'),
'Run `acli auth:acsf-login` to re-authenticate with Site Factory.',
],
[
new ApiErrorException((object) ['error' => '', 'message' => "There are no available Cloud IDEs for this application.\n"]),
'Delete an existing IDE via <bg=blue;fg=white;options=bold>acli ide:delete</> or contact your Account Manager or Acquia Sales to purchase additional IDEs.',
],
[
new ApiErrorException((object) ['error' => '', 'message' => 'This resource requires additional authentication.']),
['This is likely because you have Federated Authentication required for your organization.', 'Run `acli login` to authenticate via API token and then try again.'],
],
[
new ApiErrorException((object) ['error' => 'asdf', 'message' => 'fdsa']),
'You can learn more about Cloud Platform API at https://docs.acquia.com/cloud-platform/develop/api/',
],
];
}

}

0 comments on commit fcec6eb

Please sign in to comment.