diff --git a/.github/ISSUE_TEMPLATE/support-request.md b/.github/ISSUE_TEMPLATE/support-request.md deleted file mode 100644 index 80df318d2..000000000 --- a/.github/ISSUE_TEMPLATE/support-request.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Support request -about: Ask questions and get help -title: '' -labels: support -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/support_request.md b/.github/ISSUE_TEMPLATE/support_request.md new file mode 100644 index 000000000..acf13ef0e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support_request.md @@ -0,0 +1,20 @@ +--- +name: Support request +about: Ask questions and get help +title: '' +labels: support +assignees: '' + +--- + +**Describe the issue** +A clear and concise description of the issue. + +**Impact** +What is the impact of this issue on your business or application? e.g., is it blocking deployments, destroying data, or just a nuisance? + +**Time sensitivity** +Does this issue have a fixed deadline to address? + +**Where does the issue occur?** +Please provide your Acquia CLI version, any log output after running the command with the verbose flag (-vvv), and information on the environment where you are running the application (e.g., locally or in Acquia Cloud) diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index e7e2cb1b8..06ea1696c 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -32,11 +32,10 @@ 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 -j8 --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 - name: Run Infection for all files if: github.event_name == 'push' env: INFECTION_DASHBOARD_API_KEY: ${{ secrets.INFECTION_DASHBOARD_API_KEY }} - run: | - php vendor/bin/infection -j8 --only-covered + run: composer mutation diff --git a/composer.json b/composer.json index 8d9dd1009..ef0e58670 100644 --- a/composer.json +++ b/composer.json @@ -129,8 +129,8 @@ "box-compile": [ "php var/box.phar compile" ], - "infection": [ - "php -d pcov.enabled=1 vendor/bin/infection --threads=8" + "mutation": [ + "infection --threads=max --only-covered" ], "cs": "phpcs", "cbf": "phpcbf", diff --git a/composer.lock b/composer.lock index 5fa262471..9b9fc0971 100644 --- a/composer.lock +++ b/composer.lock @@ -1310,16 +1310,16 @@ }, { "name": "laminas/laminas-validator", - "version": "2.47.0", + "version": "2.49.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-validator.git", - "reference": "5c3fc8c4f1263cda5c5f14aed874fdadd5b90bbd" + "reference": "d58c2e7d3cd420554400dd8cca694fafa3b8e45f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/5c3fc8c4f1263cda5c5f14aed874fdadd5b90bbd", - "reference": "5c3fc8c4f1263cda5c5f14aed874fdadd5b90bbd", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/d58c2e7d3cd420554400dd8cca694fafa3b8e45f", + "reference": "d58c2e7d3cd420554400dd8cca694fafa3b8e45f", "shasum": "" }, "require": { @@ -1333,16 +1333,16 @@ }, "require-dev": { "laminas/laminas-coding-standard": "^2.5", - "laminas/laminas-db": "^2.18", - "laminas/laminas-filter": "^2.33", - "laminas/laminas-i18n": "^2.24.1", - "laminas/laminas-session": "^2.17", + "laminas/laminas-db": "^2.19", + "laminas/laminas-filter": "^2.34", + "laminas/laminas-i18n": "^2.26.0", + "laminas/laminas-session": "^2.18", "laminas/laminas-uri": "^2.11.0", - "phpunit/phpunit": "^10.5.2", + "phpunit/phpunit": "^10.5.10", "psalm/plugin-phpunit": "^0.18.4", "psr/http-client": "^1.0.3", "psr/http-factory": "^1.0.2", - "vimeo/psalm": "^5.17" + "vimeo/psalm": "^5.22.1" }, "suggest": { "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", @@ -1390,20 +1390,20 @@ "type": "community_bridge" } ], - "time": "2024-01-17T11:31:50+00:00" + "time": "2024-02-22T16:46:06+00:00" }, { "name": "league/csv", - "version": "9.14.0", + "version": "9.15.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "34bf0df7340b60824b9449b5c526fcc3325070d5" + "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/34bf0df7340b60824b9449b5c526fcc3325070d5", - "reference": "34bf0df7340b60824b9449b5c526fcc3325070d5", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", + "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", "shasum": "" }, "require": { @@ -1418,12 +1418,12 @@ "ext-xdebug": "*", "friendsofphp/php-cs-fixer": "^v3.22.0", "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^1.10.50", + "phpstan/phpstan": "^1.10.57", "phpstan/phpstan-deprecation-rules": "^1.1.4", "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.2", - "phpunit/phpunit": "^10.5.3", - "symfony/var-dumper": "^6.4.0" + "phpunit/phpunit": "^10.5.9", + "symfony/var-dumper": "^6.4.2" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", @@ -1479,7 +1479,7 @@ "type": "github" } ], - "time": "2023-12-29T07:34:53+00:00" + "time": "2024-02-20T20:00:00+00:00" }, { "name": "league/oauth2-client", @@ -8814,16 +8814,16 @@ }, { "name": "infection/infection", - "version": "0.27.9", + "version": "0.27.10", "source": { "type": "git", "url": "https://github.com/infection/infection.git", - "reference": "61e6d0645b89104fbd660218d3408219ad7176b5" + "reference": "873cd3335774a114bef9ca93388e623bf362d820" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/infection/infection/zipball/61e6d0645b89104fbd660218d3408219ad7176b5", - "reference": "61e6d0645b89104fbd660218d3408219ad7176b5", + "url": "https://api.github.com/repos/infection/infection/zipball/873cd3335774a114bef9ca93388e623bf362d820", + "reference": "873cd3335774a114bef9ca93388e623bf362d820", "shasum": "" }, "require": { @@ -8844,7 +8844,7 @@ "php": "^8.1", "sanmai/later": "^0.1.1", "sanmai/pipeline": "^5.1 || ^6", - "sebastian/diff": "^3.0.2 || ^4.0 || ^5.0", + "sebastian/diff": "^3.0.2 || ^4.0 || ^5.0 || ^6.0", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", "symfony/finder": "^5.4 || ^6.0 || ^7.0", @@ -8930,7 +8930,7 @@ ], "support": { "issues": "https://github.com/infection/infection/issues", - "source": "https://github.com/infection/infection/tree/0.27.9" + "source": "https://github.com/infection/infection/tree/0.27.10" }, "funding": [ { @@ -8942,7 +8942,7 @@ "type": "open_collective" } ], - "time": "2023-12-07T17:42:43+00:00" + "time": "2024-02-20T00:08:52+00:00" }, { "name": "jangregor/phpstan-prophecy", @@ -10597,16 +10597,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.58", + "version": "1.10.59", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "a23518379ec4defd9e47cbf81019526861623ec2" + "reference": "e607609388d3a6d418a50a49f7940e8086798281" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a23518379ec4defd9e47cbf81019526861623ec2", - "reference": "a23518379ec4defd9e47cbf81019526861623ec2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e607609388d3a6d418a50a49f7940e8086798281", + "reference": "e607609388d3a6d418a50a49f7940e8086798281", "shasum": "" }, "require": { @@ -10655,7 +10655,7 @@ "type": "tidelift" } ], - "time": "2024-02-12T20:02:57+00:00" + "time": "2024-02-20T13:59:13+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -11026,16 +11026,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.16", + "version": "9.6.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f" + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f", - "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", "shasum": "" }, "require": { @@ -11109,7 +11109,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" }, "funding": [ { @@ -11125,7 +11125,7 @@ "type": "tidelift" } ], - "time": "2024-01-19T07:03:14+00:00" + "time": "2024-02-23T13:14:51+00:00" }, { "name": "psr/simple-cache", diff --git a/infection.json b/infection.json deleted file mode 100644 index 5e0a8b0fa..000000000 --- a/infection.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/infection/infection/0.26.13/resources/schema.json", - "source": { - "directories": [ - "src" - ] - }, - "logs": { - "stryker": { - "report": "main" - } - }, - "mutators": { - "@default": true - } -} diff --git a/infection.json5 b/infection.json5 new file mode 100644 index 000000000..278489939 --- /dev/null +++ b/infection.json5 @@ -0,0 +1,19 @@ +{ + "$schema": "vendor/infection/infection/resources/schema.json", + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "stryker": { + "report": "main" + }, + "html": "var/infection.html" + }, + "mutators": { + "@default": true + }, + "timeout": 300, + "testFrameworkOptions": "--exclude-group=serial" +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9428e59f4..f88e78182 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,6 +10,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" convertDeprecationsToExceptions="true" + executionOrder="random" > diff --git a/src/Command/Api/ApiCommandHelper.php b/src/Command/Api/ApiCommandHelper.php index 2695d2b2c..dc55dffdc 100644 --- a/src/Command/Api/ApiCommandHelper.php +++ b/src/Command/Api/ApiCommandHelper.php @@ -277,16 +277,16 @@ private function getParameterSchemaFromSpec(string $paramKey, array $acquiaCloud return $acquiaCloudSpec['components']['schemas'][$paramKey]; } + /** @infection-ignore-all */ private function isApiSpecChecksumCacheValid(\Symfony\Component\Cache\CacheItem $cacheItem, string $acquiaCloudSpecFileChecksum): bool { // If the spec file doesn't exist, assume cache is valid. - if ($cacheItem->isHit() && !$acquiaCloudSpecFileChecksum) { + if (!$acquiaCloudSpecFileChecksum && $cacheItem->isHit()) { return TRUE; } // If there's an invalid entry OR there's no entry, return false. - if (!$cacheItem->isHit() || ($cacheItem->isHit() && $cacheItem->get() !== $acquiaCloudSpecFileChecksum)) { + if (!$cacheItem->isHit() || $cacheItem->get() !== $acquiaCloudSpecFileChecksum) { return FALSE; } - return TRUE; } @@ -296,6 +296,7 @@ private function isApiSpecChecksumCacheValid(\Symfony\Component\Cache\CacheItem private function getCloudApiSpec(string $specFilePath): array { $cacheKey = basename($specFilePath); $cache = new PhpArrayAdapter(__DIR__ . '/../../../var/cache/' . $cacheKey . '.cache', new NullAdapter()); + /** @infection-ignore-all */ $cacheItemChecksum = $cache->getItem($cacheKey . '.checksum'); $cacheItemSpec = $cache->getItem($cacheKey); @@ -306,6 +307,7 @@ private function getCloudApiSpec(string $specFilePath): array { // Otherwise, only use cache when it is valid. $checksum = md5_file($specFilePath); + /** @infection-ignore-all */ if ($this->useCloudApiSpecCache() && $this->isApiSpecChecksumCacheValid($cacheItemChecksum, $checksum) && $cacheItemSpec->isHit() ) { @@ -325,13 +327,13 @@ private function getCloudApiSpec(string $specFilePath): array { } /** - * @param array $acquiaCloudSpec * @return ApiBaseCommand[] */ private function generateApiCommandsFromSpec(array $acquiaCloudSpec, string $commandPrefix, CommandFactoryInterface $commandFactory): array { $apiCommands = []; foreach ($acquiaCloudSpec['paths'] as $path => $endpoint) { // Skip internal endpoints. These shouldn't actually be in the spec. + /** @infection-ignore-all */ if (array_key_exists('x-internal', $endpoint) && $endpoint['x-internal']) { continue; } @@ -346,6 +348,7 @@ private function generateApiCommandsFromSpec(array $acquiaCloudSpec, string $com } // Skip deprecated endpoints. + /** @infection-ignore-all */ if (array_key_exists('deprecated', $schema) && $schema['deprecated']) { continue; } diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 0bcb5bb0a..510cd1831 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -965,6 +965,10 @@ protected static function getThisCloudIdeLabel(): false|string { return getenv('REMOTEIDE_LABEL'); } + protected static function getThisCloudIdeWebUrl(): false|string { + return getenv('REMOTEIDE_WEB_HOST'); + } + protected function getCloudApplication(string $applicationUuid): ApplicationResponse { $applicationsResource = new Applications($this->cloudApiClientService->getClient()); return $applicationsResource->get($applicationUuid); @@ -1745,6 +1749,7 @@ protected function waitForNotificationToComplete(Client $acquiaCloudClient, stri $notification = NULL; $checkNotificationStatus = static function () use ($notificationsResource, &$notification, $uuid): bool { $notification = $notificationsResource->get($uuid); + /** @infection-ignore-all */ return $notification->status !== 'in-progress'; }; if ($success === NULL) { diff --git a/src/Command/Email/ConfigurePlatformEmailCommand.php b/src/Command/Email/ConfigurePlatformEmailCommand.php index 4bb3f4742..40bc465dd 100644 --- a/src/Command/Email/ConfigurePlatformEmailCommand.php +++ b/src/Command/Email/ConfigurePlatformEmailCommand.php @@ -305,6 +305,7 @@ private function checkIfDomainVerified( return TRUE; } + /** @infection-ignore-all */ if (isset($response->health) && str_starts_with($response->health->code, "4")) { $this->io->error($response->health->details); if ($this->io->confirm('Would you like to refresh?')) { diff --git a/src/Command/Ide/IdeCreateCommand.php b/src/Command/Ide/IdeCreateCommand.php index cc700248d..22c53a133 100644 --- a/src/Command/Ide/IdeCreateCommand.php +++ b/src/Command/Ide/IdeCreateCommand.php @@ -4,14 +4,22 @@ namespace Acquia\Cli\Command\Ide; +use Acquia\Cli\ApiCredentialsInterface; use Acquia\Cli\Attribute\RequireAuth; +use Acquia\Cli\CloudApi\ClientService; +use Acquia\Cli\DataStore\AcquiaCliDatastore; +use Acquia\Cli\DataStore\CloudDataStore; +use Acquia\Cli\Helpers\LocalMachineHelper; use Acquia\Cli\Helpers\LoopHelper; +use Acquia\Cli\Helpers\SshHelper; +use Acquia\Cli\Helpers\TelemetryHelper; use Acquia\Cli\Output\Checklist; use AcquiaCloudApi\Endpoints\Account; use AcquiaCloudApi\Endpoints\Ides; use AcquiaCloudApi\Response\IdeResponse; use AcquiaCloudApi\Response\OperationResponse; use GuzzleHttp\Client; +use Psr\Log\LoggerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -27,7 +35,21 @@ final class IdeCreateCommand extends IdeCommandBase { private IdeResponse $ide; - private Client $client; + public function __construct( + public LocalMachineHelper $localMachineHelper, + protected CloudDataStore $datastoreCloud, + protected AcquiaCliDatastore $datastoreAcli, + protected ApiCredentialsInterface $cloudCredentials, + protected TelemetryHelper $telemetryHelper, + protected string $projectDir, + protected ClientService $cloudApiClientService, + public SshHelper $sshHelper, + protected string $sshDir, + LoggerInterface $logger, + protected Client $httpClient + ) { + parent::__construct($this->localMachineHelper, $this->datastoreCloud, $this->datastoreAcli, $this->cloudCredentials, $this->telemetryHelper, $this->projectDir, $this->cloudApiClientService, $this->sshHelper, $this->sshDir, $logger); + } protected function configure(): void { $this->acceptApplicationUuid(); @@ -76,13 +98,14 @@ public function validateIdeLabel(string $label): string { return $label; } - private function waitForDnsPropagation(mixed $ideUrl): int { + private function waitForDnsPropagation(string $ideUrl): int { $ideCreated = FALSE; - if (!$this->getClient()) { - $this->setClient(new Client(['base_uri' => $ideUrl])); - } - $checkIdeStatus = function () use (&$ideCreated) { - $response = $this->client->request('GET', '/health'); + $checkIdeStatus = function () use (&$ideCreated, $ideUrl) { + // Ideally we'd set $ideUrl as the Guzzle base_url, but that requires creating a client factory. + // @see https://stackoverflow.com/questions/28277889/guzzlehttp-client-change-base-url-dynamically + $response = $this->httpClient->request('GET', "$ideUrl/health"); + // Mutating this will result in an infinite loop and timeout. + /** @infection-ignore-all */ if ($response->getStatusCode() === 200) { $ideCreated = TRUE; } @@ -111,14 +134,6 @@ private function writeIdeLinksToScreen(): void { // @todo Prompt to open browser. } - private function getClient(): ?Client { - return $this->client ?? NULL; - } - - public function setClient(Client $client): void { - $this->client = $client; - } - private function getIdeFromResponse( OperationResponse $response, \AcquiaCloudApi\Connector\Client $acquiaCloudClient diff --git a/src/Command/Ide/IdeShareCommand.php b/src/Command/Ide/IdeShareCommand.php index 5d671b601..46fdf0d5a 100644 --- a/src/Command/Ide/IdeShareCommand.php +++ b/src/Command/Ide/IdeShareCommand.php @@ -6,7 +6,6 @@ use Acquia\Cli\Command\CommandBase; use Acquia\DrupalEnvironmentDetector\AcquiaDrupalEnvironmentDetector; -use AcquiaCloudApi\Endpoints\Ides; use Ramsey\Uuid\Uuid; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -36,12 +35,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $shareUuid = $this->localMachineHelper->readFile($this->getShareCodeFilepaths()[0]); - $acquiaCloudClient = $this->cloudApiClientService->getClient(); - $idesResource = new Ides($acquiaCloudClient); - $ide = $idesResource->get($this::getThisCloudIdeUuid()); + $webUrl = self::getThisCloudIdeWebUrl(); $this->output->writeln(''); - $this->output->writeln("Your IDE Share URL: links->web->href}>{$ide->links->web->href}?share=$shareUuid"); + $this->output->writeln("Your IDE Share URL: https://$webUrl?share=$shareUuid"); return Command::SUCCESS; } diff --git a/src/EventListener/ComposerScriptsListener.php b/src/EventListener/ComposerScriptsListener.php index 1b484edac..de2474acb 100644 --- a/src/EventListener/ComposerScriptsListener.php +++ b/src/EventListener/ComposerScriptsListener.php @@ -37,8 +37,7 @@ public function onConsoleTerminate(ConsoleTerminateEvent $event): void { private function executeComposerScripts(ConsoleCommandEvent|ConsoleTerminateEvent $event, string $prefix): void { /** @var CommandBase $command */ $command = $event->getCommand(); - // If a command has the --no-script option and it's passed, do not execute post scripts. - if ($event->getInput()->hasOption('no-script') && $event->getInput()->getOption('no-scripts')) { + if ($event->getInput()->hasOption('no-scripts') && $event->getInput()->getOption('no-scripts')) { return; } // Only successful commands should be executed. @@ -46,19 +45,16 @@ private function executeComposerScripts(ConsoleCommandEvent|ConsoleTerminateEven $composerJsonFilepath = Path::join($command->getProjectDir(), 'composer.json'); if (file_exists($composerJsonFilepath)) { $composerJson = json_decode($command->localMachineHelper->readFile($composerJsonFilepath), TRUE, 512, JSON_THROW_ON_ERROR); - // Protect against invalid JSON. - if ($composerJson) { - $commandName = $command->getName(); - // Replace colons with hyphens. E.g., pull:db becomes pull-db. - $scriptName = $prefix . '-acli-' . str_replace(':', '-', $commandName); - if (array_key_exists('scripts', $composerJson) && array_key_exists($scriptName, $composerJson['scripts'])) { - $event->getOutput()->writeln("Executing composer script `$scriptName` defined in `$composerJsonFilepath`", OutputInterface::VERBOSITY_VERBOSE); - $event->getOutput()->writeln($scriptName); - $command->localMachineHelper->execute(['composer', 'run-script', $scriptName]); - } - else { - $event->getOutput()->writeln("Notice: Composer script `$scriptName` does not exist in `$composerJsonFilepath`, skipping. This is not an error.", OutputInterface::VERBOSITY_VERBOSE); - } + $commandName = $command->getName(); + // Replace colons with hyphens. E.g., pull:db becomes pull-db. + $scriptName = $prefix . '-acli-' . str_replace(':', '-', $commandName); + if (array_key_exists('scripts', $composerJson) && array_key_exists($scriptName, $composerJson['scripts'])) { + $event->getOutput()->writeln("Executing composer script `$scriptName` defined in `$composerJsonFilepath`", OutputInterface::VERBOSITY_VERBOSE); + $event->getOutput()->writeln($scriptName); + $command->localMachineHelper->execute(['composer', 'run-script', $scriptName]); + } + else { + $event->getOutput()->writeln("Notice: Composer script `$scriptName` does not exist in `$composerJsonFilepath`, skipping. This is not an error.", OutputInterface::VERBOSITY_VERBOSE); } } } diff --git a/src/Helpers/LoopHelper.php b/src/Helpers/LoopHelper.php index 9a27d51cd..8d5ec5a7f 100644 --- a/src/Helpers/LoopHelper.php +++ b/src/Helpers/LoopHelper.php @@ -23,12 +23,14 @@ public static function getLoopy(OutputInterface $output, SymfonyStyle $io, Logge $spinner->start(); $cancelTimers = static function () use (&$timers, $spinner): void { + /** @infection-ignore-all */ array_map('\React\EventLoop\Loop::cancelTimer', $timers); $timers = []; $spinner->finish(); }; $periodicCallback = static function () use ($logger, $statusCallback, $doneCallback, $cancelTimers): void { try { + /** @infection-ignore-all */ if ($statusCallback()) { $cancelTimers(); $doneCallback(); diff --git a/tests/phpunit/src/Application/ComposerScriptsListenerTest.php b/tests/phpunit/src/Application/ComposerScriptsListenerTest.php new file mode 100644 index 000000000..f330bc415 --- /dev/null +++ b/tests/phpunit/src/Application/ComposerScriptsListenerTest.php @@ -0,0 +1,97 @@ + [ + 'pre-acli-hello-world' => [ + 'echo "good morning world"', + ], + ], + ]; + file_put_contents( + Path::join($this->projectDir, 'composer.json'), + json_encode($json, JSON_THROW_ON_ERROR) + ); + $this->mockRequest('getAccount'); + $this->setInput([ + 'command' => 'hello-world', + ]); + $buffer = $this->runApp(); + self::assertStringContainsString('pre-acli-hello-world', $buffer); + } + + /** + * @group serial + */ + public function testPostScripts(): void { + $json = [ + 'scripts' => [ + 'post-acli-hello-world' => [ + 'echo "goodbye world"', + ], + ], + ]; + file_put_contents( + Path::join($this->projectDir, 'composer.json'), + json_encode($json, JSON_THROW_ON_ERROR) + ); + $this->mockRequest('getAccount'); + $this->setInput([ + 'command' => 'hello-world', + ]); + $buffer = $this->runApp(); + self::assertStringContainsString('post-acli-hello-world', $buffer); + } + + public function testNoScripts(): void { + $json = [ + 'scripts' => [ + 'pre-acli-pull-code' => [ + 'echo "goodbye world"', + ], + ], + ]; + file_put_contents( + Path::join($this->projectDir, 'composer.json'), + json_encode($json, JSON_THROW_ON_ERROR) + ); + $this->setInput([ + '--no-scripts' => TRUE, + 'command' => 'pull:code', + ]); + $buffer = $this->runApp(); + self::assertStringNotContainsString('pre-acli-pull-code', $buffer); + } + + // Hack to ensure listener methods are recognized as used. + // If we were unit testing properly, we'd make meaningful assertions here instead of the integration tests above. + public function testApi(): void { + $listener = new ComposerScriptsListener(); + $listener->onConsoleCommand(new ConsoleCommandEvent($this->injectCommand(HelloWorldCommand::class), $this->input, $this->output)); + $listener->onConsoleTerminate(new ConsoleTerminateEvent($this->injectCommand(HelloWorldCommand::class), $this->input, $this->output, 0)); + self::assertTrue(TRUE); + } + +} diff --git a/tests/phpunit/src/Application/ExceptionApplicationTest.php b/tests/phpunit/src/Application/ExceptionApplicationTest.php index 22268c31c..c79061ef1 100644 --- a/tests/phpunit/src/Application/ExceptionApplicationTest.php +++ b/tests/phpunit/src/Application/ExceptionApplicationTest.php @@ -5,7 +5,6 @@ namespace Acquia\Cli\Tests\Application; use Acquia\Cli\Tests\ApplicationTestBase; -use Symfony\Component\Filesystem\Path; /** * Tests exceptions rewritten by the Symfony Event Dispatcher. @@ -18,53 +17,7 @@ class ExceptionApplicationTest extends ApplicationTestBase { /** * @group serial */ - public function testPreScripts(): void { - $json = [ - 'scripts' => [ - 'pre-acli-hello-world' => [ - 'echo "good morning world"', - ], - ], - ]; - file_put_contents( - Path::join($this->projectDir, 'composer.json'), - json_encode($json, JSON_THROW_ON_ERROR) - ); - $this->mockRequest('getAccount'); - $this->setInput([ - 'command' => 'hello-world', - ]); - $buffer = $this->runApp(); - self::assertStringContainsString('pre-acli-hello-world', $buffer); - } - - /** - * @group serial - */ - public function testPostScripts(): void { - $json = [ - 'scripts' => [ - 'post-acli-hello-world' => [ - 'echo "goodbye world"', - ], - ], - ]; - file_put_contents( - Path::join($this->projectDir, 'composer.json'), - json_encode($json, JSON_THROW_ON_ERROR) - ); - $this->mockRequest('getAccount'); - $this->setInput([ - 'command' => 'hello-world', - ]); - $buffer = $this->runApp(); - self::assertStringContainsString('post-acli-hello-world', $buffer); - } - - /** - * @group serial - */ - public function testInvalidApiCreds(): void { + public function testInvalidApiCredentials(): void { $this->setInput([ 'applicationUuid' => '2ed281d4-9dec-4cc3-ac63-691c3ba002c2', 'command' => 'aliases', diff --git a/tests/phpunit/src/Commands/Ide/IdeCreateCommandTest.php b/tests/phpunit/src/Commands/Ide/IdeCreateCommandTest.php index 85739ca52..086b56e0a 100644 --- a/tests/phpunit/src/Commands/Ide/IdeCreateCommandTest.php +++ b/tests/phpunit/src/Commands/Ide/IdeCreateCommandTest.php @@ -9,12 +9,33 @@ use Acquia\Cli\Tests\CommandTestBase; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Response; +use Prophecy\Prophecy\ObjectProphecy; /** * @property IdeCreateCommand $command */ class IdeCreateCommandTest extends CommandTestBase { + protected Client|ObjectProphecy $httpClientProphecy; + + protected function createCommand(): CommandBase { + $this->httpClientProphecy = $this->prophet->prophesize(Client::class); + + return new IdeCreateCommand( + $this->localMachineHelper, + $this->datastoreCloud, + $this->datastoreAcli, + $this->cloudCredentials, + $this->telemetryHelper, + $this->acliRepoRoot, + $this->clientServiceProphecy->reveal(), + $this->sshHelper, + $this->sshDir, + $this->logger, + $this->httpClientProphecy->reveal() + ); + } + /** * @group brokenProphecy */ @@ -34,9 +55,7 @@ public function testCreate(): void { /** @var \Prophecy\Prophecy\ObjectProphecy|\GuzzleHttp\Psr7\Response $guzzleResponse */ $guzzleResponse = $this->prophet->prophesize(Response::class); $guzzleResponse->getStatusCode()->willReturn(200); - $guzzleClient = $this->prophet->prophesize(Client::class); - $guzzleClient->request('GET', '/health')->willReturn($guzzleResponse->reveal())->shouldBeCalled(); - $this->command->setClient($guzzleClient->reveal()); + $this->httpClientProphecy->request('GET', 'https://215824ff-272a-4a8c-9027-df32ed1d68a9.ides.acquia.com/health')->willReturn($guzzleResponse->reveal())->shouldBeCalled(); $inputs = [ // Would you like Acquia CLI to search for a Cloud application that matches your local git config? @@ -61,11 +80,4 @@ public function testCreate(): void { $this->assertStringContainsString('Your Drupal Site URL: https://ide-215824ff-272a-4a8c-9027-df32ed1d68a9.prod.acquia-sites.com', $output); } - /** - * @return \Acquia\Cli\Command\Ide\IdeCreateCommand - */ - protected function createCommand(): CommandBase { - return $this->injectCommand(IdeCreateCommand::class); - } - } diff --git a/tests/phpunit/src/Commands/Ide/IdeShareCommandTest.php b/tests/phpunit/src/Commands/Ide/IdeShareCommandTest.php index aa9cfe3ee..f0bbf6e0a 100644 --- a/tests/phpunit/src/Commands/Ide/IdeShareCommandTest.php +++ b/tests/phpunit/src/Commands/Ide/IdeShareCommandTest.php @@ -7,7 +7,6 @@ use Acquia\Cli\Command\CommandBase; use Acquia\Cli\Command\Ide\IdeShareCommand; use Acquia\Cli\Tests\CommandTestBase; -use AcquiaCloudApi\Response\IdeResponse; use Symfony\Component\Console\Output\OutputInterface; /** @@ -41,24 +40,14 @@ protected function createCommand(): CommandBase { } public function testIdeShareCommand(): void { - $ideGetResponse = $this->mockRequest('getIde', IdeHelper::$remoteIdeUuid); - $ide = new IdeResponse((object) $ideGetResponse); $this->executeCommand(); - - // Assert. - $output = $this->getDisplay(); $this->assertStringContainsString('Your IDE Share URL: ', $output); $this->assertStringContainsString($this->shareCode, $output); } public function testIdeShareRegenerateCommand(): void { - $ideGetResponse = $this->mockRequest('getIde', IdeHelper::$remoteIdeUuid); - $ide = new IdeResponse((object) $ideGetResponse); - $this->executeCommand(['--regenerate' => TRUE], []); - - // Assert. - + $this->executeCommand(['--regenerate' => TRUE]); $output = $this->getDisplay(); $this->assertStringContainsString('Your IDE Share URL: ', $output); $this->assertStringNotContainsString($this->shareCode, $output);