Skip to content

Commit 75a1e70

Browse files
CLI-1778: success message immediately followed by a failure — confusing and misleading. (#1988)
* CLI-1778: success message immediately followed by a failure — confusing and misleading. * add test for coverage. * conflict fixes. * fix test. * refactor test.
1 parent cdcce54 commit 75a1e70

2 files changed

Lines changed: 92 additions & 13 deletions

File tree

src/Command/Pull/PullCommandBase.php

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
use GuzzleHttp\TransferStats;
3232
use Psr\Http\Message\UriInterface;
3333
use Psr\Log\LoggerInterface;
34-
use React\EventLoop\Loop;
3534
use SelfUpdate\SelfUpdateManager;
3635
use Symfony\Component\Console\Helper\ProgressBar;
3736
use Symfony\Component\Console\Input\ArrayInput;
@@ -259,7 +258,9 @@ static function (mixed $totalBytes, mixed $downloadedBytes) use (&$progress, $ou
259258
try {
260259
$codebaseUuid = self::getCodebaseUuid();
261260
if ($codebaseUuid) {
262-
// Download the backup file directly from the provided URL.
261+
if (!isset($backupResponse->links->download->href)) {
262+
throw new AcquiaCliException('Cloud API failed to provide a valid backup download URL. The backup may have failed on the server.');
263+
}
263264
$downloadUrl = $backupResponse->links->download->href;
264265
$this->httpClient->request('GET', $downloadUrl, [
265266
'progress' => static function (mixed $totalBytes, mixed $downloadedBytes) use (&$progress, $output): void {
@@ -395,15 +396,13 @@ private function getCodeabaseDatabaseBackups(string $siteId, string $environment
395396
protected function waitForBackup(string $notificationUuid, Client $acquiaCloudClient): void
396397
{
397398
$spinnerMessage = 'Waiting for database backup to complete...';
398-
$successCallback = function (): void {
399-
$this->output->writeln('');
400-
$this->output->writeln('<info>Database backup is ready!</info>');
401-
};
402-
$success = $this->waitForNotificationToComplete($acquiaCloudClient, $notificationUuid, $spinnerMessage, $successCallback);
403-
Loop::run();
399+
$success = $this->waitForNotificationToComplete($acquiaCloudClient, $notificationUuid, $spinnerMessage, static function (): void {
400+
});
404401
if (!$success) {
405402
throw new AcquiaCliException('Cloud API failed to create a backup');
406403
}
404+
$this->output->writeln('');
405+
$this->output->writeln('<info>Database backup is ready!</info>');
407406
}
408407

409408
private function connectToLocalDatabase(string $dbHost, string $dbUser, string $dbName, string $dbPassword, ?callable $outputCallback = null): void

tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -434,10 +434,10 @@ public function testPullNode(): void
434434
public function testPullDatabasesWithCodebaseUuid(): void
435435
{
436436
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
437-
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
437+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
438438

439439
// Mock the codebase returned from /codebases/{uuid}.
440-
$codebase = $this->getMockCodeBaseResponse();
440+
$codebase = $this->getMockCodeBaseResponse();
441441
$this->clientProphecy->request('get', '/codebases/' . $codebaseUuid)
442442
->willReturn($codebase);
443443

@@ -562,10 +562,10 @@ public function testPullDatabasesKillsLogicalAndMutation(): void
562562
public function testPullDatabasesWithCodebaseUuidOnDemand(): void
563563
{
564564
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
565-
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
565+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
566566

567567
// Mock the codebase returned from /codebases/{uuid}.
568-
$codebase = $this->getMockCodeBaseResponse();
568+
$codebase = $this->getMockCodeBaseResponse();
569569
$this->clientProphecy->request('get', '/codebases/' . $codebaseUuid)
570570
->willReturn($codebase);
571571

@@ -600,7 +600,6 @@ public function testPullDatabasesWithCodebaseUuidOnDemand(): void
600600
$this->clientProphecy->request('post', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/backups')
601601
->willReturn($createSiteInstanceDatabaseBackup)
602602
->shouldBeCalled();
603-
;
604603
$siteInstanceDatabaseBackups = $this->getMockSiteInstanceDatabaseBackupsResponse();
605604
$this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/backups')
606605
->willReturn($siteInstanceDatabaseBackups->_embedded->items)
@@ -647,6 +646,87 @@ public function testPullDatabasesWithCodebaseUuidOnDemand(): void
647646
self::unsetEnvVars(['AH_CODEBASE_UUID']);
648647
}
649648

649+
/**
650+
* Tests that a codebase backup response without a download link
651+
* throws AcquiaCliException before attempting any HTTP download.
652+
*/
653+
public function testPullDatabasesCodebaseBackupMissingDownloadUrl(): void
654+
{
655+
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
656+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
657+
658+
$codebase = $this->getMockCodeBaseResponse();
659+
$this->clientProphecy->request('get', '/codebases/' . $codebaseUuid)
660+
->willReturn($codebase);
661+
662+
$codebaseEnv = $this->getMockCodeBaseEnvironment();
663+
$this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/environments')
664+
->willReturn([$codebaseEnv])
665+
->shouldBeCalled();
666+
667+
$codeabaseSites = $this->getMockCodeBaseSites();
668+
$this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/sites')
669+
->willReturn($codeabaseSites);
670+
$siteInstance = $this->getMockSiteInstanceResponse();
671+
672+
$this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc')
673+
->willReturn($siteInstance)
674+
->shouldBeCalled();
675+
$siteId = '8979a8ac-80dc-4df8-b2f0-6be36554a370';
676+
$site = $this->getMockSite();
677+
$this->clientProphecy->request('get', '/sites/' . $siteId)
678+
->willReturn($site)
679+
->shouldBeCalled();
680+
$siteInstanceDatabase = $this->getMockSiteInstanceDatabaseResponse();
681+
$this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database')
682+
->willReturn($siteInstanceDatabase)
683+
->shouldBeCalled();
684+
$siteInstanceDatabaseConnection = $this->getMockSiteInstanceDatabaseConnectionResponse();
685+
$this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/connection')
686+
->willReturn($siteInstanceDatabaseConnection)
687+
->shouldBeCalled();
688+
$createSiteInstanceDatabaseBackup = $this->getMockSiteInstanceDatabaseBackupsResponse('post', '201');
689+
$this->clientProphecy->request('post', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/backups')
690+
->willReturn($createSiteInstanceDatabaseBackup)
691+
->shouldBeCalled();
692+
693+
// Return a backup response WITHOUT the download link.
694+
$backupWithoutDownload = (object)[
695+
'created_at' => '2025-04-01T13:01:06.603Z',
696+
'database_id' => 'b0c9dff7-56b6-4c0d-bad0-0e6593f66cd3',
697+
'id' => 'e0c9dff7-56b6-4c0d-bad0-0e6593f66cd3',
698+
'_links' => (object)[
699+
'self' => (object)[
700+
'href' => 'https://environment-service-php.acquia.com/api/site-instances/3e8ecbec-ea7c-4260-8414-ef2938c859bc.d3f7270e-c45f-4801-9308-5e8afe84a323/database/backups/a0c9dff7-56b6-4c0d-bad0-0e6593f66cd3',
701+
],
702+
],
703+
];
704+
$this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/backups')
705+
->willReturn([$backupWithoutDownload])
706+
->shouldBeCalled();
707+
708+
// Mock the client options that are set before the download URL check.
709+
$this->clientProphecy->addOption('sink', Argument::type('string'))->shouldBeCalled();
710+
$this->clientProphecy->addOption('curl.options', Argument::type('array'))->shouldBeCalled();
711+
$this->clientProphecy->addOption('progress', Argument::that(static fn($v) => is_callable($v)))->shouldBeCalled();
712+
$this->clientProphecy->addOption('on_stats', Argument::that(static fn($v) => is_callable($v)))->shouldBeCalled();
713+
714+
$localMachineHelper = $this->mockLocalMachineHelper();
715+
$this->mockExecuteMySqlConnect($localMachineHelper, true);
716+
717+
// No HTTP download should be attempted.
718+
$this->httpClientProphecy->request('GET', Argument::any(), Argument::any())->shouldNotBeCalled();
719+
720+
$inputs = self::inputChooseEnvironment();
721+
722+
$this->expectException(AcquiaCliException::class);
723+
$this->expectExceptionMessage('Cloud API failed to provide a valid backup download URL');
724+
$this->executeCommand([
725+
'--no-scripts' => true,
726+
'--on-demand' => true,
727+
], $inputs);
728+
}
729+
650730
/**
651731
* Test catch block in getSiteInstanceDatabaseConnection method.
652732
* Covers the logger->debug() line when an exception is caught.

0 commit comments

Comments
 (0)