From 25849c18d75e929cc3ea619257c9bb0b7376ebd0 Mon Sep 17 00:00:00 2001 From: Kalindi Adhiya Date: Mon, 20 Apr 2026 16:48:28 +0530 Subject: [PATCH 1/8] acli pull db db connector changes --- composer.json | 2 +- composer.lock | 14 +++++------ src/Command/CommandBase.php | 29 +++++++++++++++++----- src/Transformer/EnvironmentTransformer.php | 15 ++++++----- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index 8d760a063..1c8415dfc 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "symfony/yaml": "^6.3", "thecodingmachine/safe": "3.0.2", "typhonius/acquia-logstream": "^0.0.15", - "typhonius/acquia-php-sdk-v2": "^3.7.3", + "typhonius/acquia-php-sdk-v2": "^3.8.0", "vlucas/phpdotenv": "^5.5", "zumba/amplitude-php": "^1.0.9" }, diff --git a/composer.lock b/composer.lock index fd388c7a6..d5bb3cf09 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "afc2b88a6b412cba3a90af463de32792", + "content-hash": "1c75ef068253fdd43416f94dd2f5cbe9", "packages": [ { "name": "acquia/drupal-environment-detector", @@ -6409,16 +6409,16 @@ }, { "name": "typhonius/acquia-php-sdk-v2", - "version": "3.7.3", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/typhonius/acquia-php-sdk-v2.git", - "reference": "eca4b1d1b250ff7a29f9a2fab9a6fc3d558c22b8" + "reference": "ad549fa3b1277c4c86148593124769ba363d5d4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/typhonius/acquia-php-sdk-v2/zipball/eca4b1d1b250ff7a29f9a2fab9a6fc3d558c22b8", - "reference": "eca4b1d1b250ff7a29f9a2fab9a6fc3d558c22b8", + "url": "https://api.github.com/repos/typhonius/acquia-php-sdk-v2/zipball/ad549fa3b1277c4c86148593124769ba363d5d4f", + "reference": "ad549fa3b1277c4c86148593124769ba363d5d4f", "shasum": "" }, "require": { @@ -6460,7 +6460,7 @@ "description": "A PHP SDK for Acquia CloudAPI v2", "support": { "issues": "https://github.com/typhonius/acquia-php-sdk-v2/issues", - "source": "https://github.com/typhonius/acquia-php-sdk-v2/tree/3.7.3" + "source": "https://github.com/typhonius/acquia-php-sdk-v2/tree/3.8.0" }, "funding": [ { @@ -6468,7 +6468,7 @@ "type": "github" } ], - "time": "2025-09-08T15:28:31+00:00" + "time": "2026-04-13T19:20:36+00:00" }, { "name": "vlucas/phpdotenv", diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 5f4ed423c..9a44928e6 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -46,6 +46,7 @@ use AcquiaCloudApi\Response\EnvironmentResponse; use AcquiaCloudApi\Response\EnvironmentsResponse; use AcquiaCloudApi\Response\NotificationResponse; +use AcquiaCloudApi\Response\SiteInstanceDatabaseConnectionResponse; use AcquiaCloudApi\Response\SiteInstanceDatabaseResponse; use AcquiaCloudApi\Response\SiteInstanceResponse; use AcquiaCloudApi\Response\SiteResponse; @@ -589,9 +590,13 @@ protected function determineCloudDatabases(Client $acquiaCloudClient, Environmen { $codebaseUuid = self::getCodebaseUuid(); if ($codebaseUuid && $this->siteId) { - $database = EnvironmentTransformer::transformSiteInstanceDatabase($this->getSiteInstanceDatabase($this->siteId, $chosenEnvironment->uuid)); - if ($database) { - return [$database]; + $siteInstanceDb = $this->getSiteInstanceDatabase($this->siteId, $chosenEnvironment->uuid); + $siteInstanceDbConnection = $this->getSiteInstanceDatabaseConnection($this->siteId, $chosenEnvironment->uuid); + if ($siteInstanceDb && $siteInstanceDbConnection) { + $siteInstanceDatabaseResponse = EnvironmentTransformer::transformSiteInstanceDatabase($siteInstanceDb, $siteInstanceDbConnection); + if ($siteInstanceDatabaseResponse) { + return [$siteInstanceDatabaseResponse]; + } } } $databasesRequest = new Databases($acquiaCloudClient); @@ -1480,9 +1485,6 @@ protected function getSiteInstance(string $siteId, string $environmentId): ?Site } /** * Get the database for a site instance in a given environment. - * - * @param object|null $site (site object from getSitesByCodebase) - * @return DatabaseResponse|null */ private function getSiteInstanceDatabase(string $siteUuid, string $environmentUuid): ?SiteInstanceDatabaseResponse { @@ -1497,6 +1499,21 @@ private function getSiteInstanceDatabase(string $siteUuid, string $environmentUu return null; } + /** + * Get the database connection details for a site instance in a given environment. + */ + private function getSiteInstanceDatabaseConnection(string $siteUuid, string $environmentUuid): ?SiteInstanceDatabaseConnectionResponse + { + try { + $acquiaCloudClient = $this->cloudApiClientService->getClient(); + $siteInstancesResource = new SiteInstances($acquiaCloudClient); + return $siteInstancesResource->getDatabaseConnection($siteUuid, $environmentUuid); + } catch (\Exception $e) { + $this->logger->debug('Could not get site instance database connection: ' . $e->getMessage()); + } + return null; + } + public static function validateEnvironmentAlias(string $alias): string { $violations = Validation::createValidator()->validate($alias, [ diff --git a/src/Transformer/EnvironmentTransformer.php b/src/Transformer/EnvironmentTransformer.php index ef0f2777a..bd6292255 100644 --- a/src/Transformer/EnvironmentTransformer.php +++ b/src/Transformer/EnvironmentTransformer.php @@ -7,6 +7,8 @@ use AcquiaCloudApi\Response\BackupResponse; use AcquiaCloudApi\Response\DatabaseResponse; use AcquiaCloudApi\Response\EnvironmentResponse; +use AcquiaCloudApi\Response\SiteInstanceDatabaseConnectionResponse; +use AcquiaCloudApi\Response\SiteInstanceDatabaseResponse; use stdClass; class EnvironmentTransformer @@ -72,18 +74,19 @@ public static function transform(mixed $codebaseEnv): EnvironmentResponse } /** - * Transform a SiteInstanceDatabaseResponse object to a DatabaseResponse object. + * Transform a SiteInstanceDatabaseResponse and SiteInstanceDatabaseConnectionResponse + * object to a DatabaseResponse object. */ - public static function transformSiteInstanceDatabase(mixed $siteInstanceDb): DatabaseResponse + public static function transformSiteInstanceDatabase(SiteInstanceDatabaseResponse $siteInstanceDb, SiteInstanceDatabaseConnectionResponse $siteInstanceDbConnection): DatabaseResponse { $db = new \stdClass(); $db->id = $siteInstanceDb->databaseName; $db->name = $siteInstanceDb->databaseName; - $db->user_name = $siteInstanceDb->databaseUserName; - $db->password = $siteInstanceDb->databasePassword; + $db->user_name = $siteInstanceDbConnection->databaseUserName; + $db->password = $siteInstanceDbConnection->databasePassword; $db->url = null; - $db->db_host = $siteInstanceDb->databaseHost; - $db->ssh_host = null; + $db->db_host = $siteInstanceDbConnection->databaseHost; + $db->ssh_host = $siteInstanceDbConnection->sshHost; $db->flags = (object) ['role' => $siteInstanceDb->databaseRole, 'default' => false]; $db->environment = new stdClass(); return new DatabaseResponse($db); From fa59b95071f8e48ce0f6e8094509ae8abd76f78a Mon Sep 17 00:00:00 2001 From: Kalindi Adhiya Date: Mon, 20 Apr 2026 17:17:44 +0530 Subject: [PATCH 2/8] php unit fix --- .../Commands/Pull/PullDatabaseCommandTest.php | 13 +++++++-- tests/phpunit/src/TestBase.php | 23 +++++++++++++--- .../EnvironmentTransformerTest.php | 27 ++++++++++++------- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 9d79eebec..704dc85aa 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -11,6 +11,7 @@ use Acquia\Cli\Helpers\SshHelper; use Acquia\Cli\Transformer\EnvironmentTransformer; use AcquiaCloudApi\Response\SiteInstanceDatabaseBackupResponse; +use AcquiaCloudApi\Response\SiteInstanceDatabaseConnectionResponse; use AcquiaCloudApi\Response\SiteInstanceDatabaseResponse; use GuzzleHttp\Client; use Prophecy\Argument; @@ -463,6 +464,10 @@ public function testPullDatabasesWithCodebaseUuid(): void $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database') ->willReturn($siteInstanceDatabase) ->shouldBeCalled(); + $siteInstanceDatabaseConnection = $this->getMockSiteInstanceDatabaseConnectionResponse(); + $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/connection') + ->willReturn($siteInstanceDatabaseConnection) + ->shouldBeCalled(); $createSiteInstanceDatabaseBackup = $this->getMockSiteInstanceDatabaseBackupsResponse('post', '201'); $this->clientProphecy->request('post', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/backups') ->willReturn($createSiteInstanceDatabaseBackup); @@ -472,7 +477,7 @@ public function testPullDatabasesWithCodebaseUuid(): void ->shouldBeCalled(); $url = "https://environment-service-php.acquia.com/api/environments/d3f7270e-c45f-4801-9308-5e8afe84a323/"; - $this->mockDownloadCodebaseBackup(EnvironmentTransformer::transformSiteInstanceDatabase(new SiteInstanceDatabaseResponse($siteInstanceDatabase)), $url, EnvironmentTransformer::transformSiteInstanceDatabaseBackup(new SiteInstanceDatabaseBackupResponse($siteInstanceDatabaseBackups->_embedded->items[0]))); + $this->mockDownloadCodebaseBackup(EnvironmentTransformer::transformSiteInstanceDatabase(new SiteInstanceDatabaseResponse($siteInstanceDatabase), new SiteInstanceDatabaseConnectionResponse($siteInstanceDatabaseConnection)), $url, EnvironmentTransformer::transformSiteInstanceDatabaseBackup(new SiteInstanceDatabaseBackupResponse($siteInstanceDatabaseBackups->_embedded->items[0]))); $localMachineHelper = $this->mockLocalMachineHelper(); $this->mockExecuteMySqlConnect($localMachineHelper, true); @@ -587,6 +592,10 @@ public function testPullDatabasesWithCodebaseUuidOnDemand(): void $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database') ->willReturn($siteInstanceDatabase) ->shouldBeCalled(); + $siteInstanceDatabaseConnection = $this->getMockSiteInstanceDatabaseConnectionResponse(); + $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/connection') + ->willReturn($siteInstanceDatabaseConnection) + ->shouldBeCalled(); $createSiteInstanceDatabaseBackup = $this->getMockSiteInstanceDatabaseBackupsResponse('post', '201'); $this->clientProphecy->request('post', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/backups') ->willReturn($createSiteInstanceDatabaseBackup) @@ -598,7 +607,7 @@ public function testPullDatabasesWithCodebaseUuidOnDemand(): void ->shouldBeCalled(); $url = "https://environment-service-php.acquia.com/api/environments/d3f7270e-c45f-4801-9308-5e8afe84a323/"; - $this->mockDownloadCodebaseBackup(EnvironmentTransformer::transformSiteInstanceDatabase(new SiteInstanceDatabaseResponse($siteInstanceDatabase)), $url, EnvironmentTransformer::transformSiteInstanceDatabaseBackup(new SiteInstanceDatabaseBackupResponse($siteInstanceDatabaseBackups->_embedded->items[0]))); + $this->mockDownloadCodebaseBackup(EnvironmentTransformer::transformSiteInstanceDatabase(new SiteInstanceDatabaseResponse($siteInstanceDatabase), new SiteInstanceDatabaseConnectionResponse($siteInstanceDatabaseConnection)), $url, EnvironmentTransformer::transformSiteInstanceDatabaseBackup(new SiteInstanceDatabaseBackupResponse($siteInstanceDatabaseBackups->_embedded->items[0]))); $localMachineHelper = $this->mockLocalMachineHelper(); $this->mockExecuteMySqlConnect($localMachineHelper, true); diff --git a/tests/phpunit/src/TestBase.php b/tests/phpunit/src/TestBase.php index 171f003b9..626510530 100644 --- a/tests/phpunit/src/TestBase.php +++ b/tests/phpunit/src/TestBase.php @@ -661,12 +661,8 @@ protected function getMockSiteInstanceResponse(string $method = 'get', string $h protected function getMockSiteInstanceDatabaseResponse(string $method = 'get', string $httpCode = '200'): object { return (object) array( - 'database_host' => 'localhost', 'database_name' => 'example', - 'database_password' => 'example@123', - 'database_port' => 3306, 'database_role' => 'example', - 'database_user_name' => 'example', '_links' => (object) array( 'self' => @@ -676,6 +672,25 @@ protected function getMockSiteInstanceDatabaseResponse(string $method = 'get', s ), ); } + + protected function getMockSiteInstanceDatabaseConnectionResponse(): object + { + return (object) array( + 'db_host' => 'localhost', + 'name' => 'example', + 'password' => 'example@123', + 'ssh_host' => '', + 'user_name' => 'example', + '_links' => + (object) array( + 'self' => + (object) array( + 'href' => 'https://environment-service-php.acquia.com/api/site-instances/3e8ecbec-ea7c-4260-8414-ef2938c859bc.d3f7270e-c45f-4801-9308-5e8afe84a323/database/connection', + ), + ), + ); + } + protected function getMockSiteInstanceDatabaseBackupsResponse(string $method = 'get', string $httpCode = '200'): object { if ($method === 'post') { diff --git a/tests/phpunit/src/Transformer/EnvironmentTransformerTest.php b/tests/phpunit/src/Transformer/EnvironmentTransformerTest.php index dc7219e89..d9b5c15c5 100644 --- a/tests/phpunit/src/Transformer/EnvironmentTransformerTest.php +++ b/tests/phpunit/src/Transformer/EnvironmentTransformerTest.php @@ -8,6 +8,8 @@ use AcquiaCloudApi\Response\BackupResponse; use AcquiaCloudApi\Response\DatabaseResponse; use AcquiaCloudApi\Response\EnvironmentResponse; +use AcquiaCloudApi\Response\SiteInstanceDatabaseConnectionResponse; +use AcquiaCloudApi\Response\SiteInstanceDatabaseResponse; use PHPUnit\Framework\TestCase; class EnvironmentTransformerTest extends TestCase @@ -184,15 +186,22 @@ public function testSshUrlPrefersPropertiesOverVcsUrl(): void public function testTransformSiteInstanceDatabase(): void { - $siteInstanceDb = (object)[ - 'databaseHost' => 'test.example.com', - 'databaseName' => 'test_db', - 'databasePassword' => 'test_password', - 'databaseRole' => 'primary', - 'databaseUserName' => 'test_user', - ]; + $siteInstanceDb = new SiteInstanceDatabaseResponse((object)[ + 'database_name' => 'test_db', + 'database_role' => 'primary', + '_links' => (object)[], + ]); + + $siteInstanceDbConnection = new SiteInstanceDatabaseConnectionResponse((object)[ + 'db_host' => 'test.example.com', + 'name' => 'test_db', + 'password' => 'test_password', + 'ssh_host' => '', + 'user_name' => 'test_user', + '_links' => (object)[], + ]); - $databaseResponse = EnvironmentTransformer::transformSiteInstanceDatabase($siteInstanceDb); + $databaseResponse = EnvironmentTransformer::transformSiteInstanceDatabase($siteInstanceDb, $siteInstanceDbConnection); $this->assertInstanceOf(DatabaseResponse::class, $databaseResponse); $this->assertEquals('test_db', $databaseResponse->id); @@ -201,7 +210,7 @@ public function testTransformSiteInstanceDatabase(): void $this->assertEquals('test_password', $databaseResponse->password); $this->assertNull($databaseResponse->url); $this->assertEquals('test.example.com', $databaseResponse->db_host); - $this->assertNull($databaseResponse->ssh_host); + $this->assertEquals('', $databaseResponse->ssh_host); $this->assertIsObject($databaseResponse->flags); $this->assertEquals('primary', $databaseResponse->flags->role); $this->assertFalse($databaseResponse->flags->default); From b7be45c089172b9d2c853a5c2d8082e50a780c54 Mon Sep 17 00:00:00 2001 From: Kalindi Adhiya Date: Mon, 20 Apr 2026 21:45:37 +0530 Subject: [PATCH 3/8] code cov fix --- .../Commands/Pull/PullDatabaseCommandTest.php | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 704dc85aa..f3f36779d 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -646,4 +646,98 @@ public function testPullDatabasesWithCodebaseUuidOnDemand(): void self::unsetEnvVars(['AH_CODEBASE_UUID']); } + + /** + * Test that when getSiteInstanceDatabaseConnection throws an exception, + * the exception is caught and null is returned, allowing fallback logic. + * + * This ensures code coverage for the catch block in getSiteInstanceDatabaseConnection: + * } catch (\Exception $e) { + * $this->logger->debug('Could not get site instance database connection: ' . $e->getMessage()); + * } + * return null; + */ + public function testGetSiteInstanceDatabaseConnectionExceptionHandling(): void + { + $codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8'; + self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]); + + // Mock codebase. + $codebase = $this->getMockCodeBaseResponse(); + $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid) + ->willReturn($codebase); + + // Mock codebase environment. + $codebaseEnv = $this->getMockCodeBaseEnvironment(); + $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/environments') + ->willReturn([$codebaseEnv]) + ->shouldBeCalled(); + + // Mock codebase sites. + $codeabaseSites = $this->getMockCodeBaseSites(); + $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/sites') + ->willReturn($codeabaseSites); + + // Mock site instance. + $siteInstance = $this->getMockSiteInstanceResponse(); + $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc') + ->willReturn($siteInstance); + // Note: Not using shouldBeCalled() - this will NOT be called due to environment flow. + // Mock site. + $siteId = '8979a8ac-80dc-4df8-b2f0-6be36554a370'; + $site = $this->getMockSite(); + $this->clientProphecy->request('get', '/sites/' . $siteId) + ->willReturn($site); + // Note: Not using shouldBeCalled() - this will NOT be called. + // Mock site instance database. + $siteInstanceDatabase = $this->getMockSiteInstanceDatabaseResponse(); + $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database') + ->willReturn($siteInstanceDatabase); + // Note: Not using shouldBeCalled() - this will NOT be called. + // CRITICAL: Make database/connection throw an exception to test catch block. + $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/connection') + ->willThrow(new \Exception('API connection error')); + // Note: Not using shouldBeCalled() - this will NOT be called. + // The exception in getSiteInstanceDatabaseConnection will be caught, + // and it will return null. This causes the if condition to fail, + // and the code falls back to the regular ACSF database endpoint. + // Mock the fallback call to /environments/{uuid}/databases with a properly structured database. + $mockDatabase = (object)[ + 'db_host' => 'fsdb-test.acquia.com', + 'environment' => (object)['id' => '3e8ecbec-ea7c-4260-8414-ef2938c859bc', 'name' => 'dev'], + 'flags' => (object)['default' => true], + 'id' => 'testdb123', + 'name' => 'testdb', + 'password' => 'password', + 'ssh_host' => 'web-test.acquia.com', + 'url' => 'mysqli://testuser:password@127.0.0.1:3306/testdb', + 'user_name' => 'testuser', + '_links' => (object)['self' => (object)['href' => 'https://cloud.acquia.com/api/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases/testdb']], + ]; + $databaseResponse = new \AcquiaCloudApi\Response\DatabaseResponse($mockDatabase); + + $this->clientProphecy->request('get', '/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases') + ->willReturn([$databaseResponse]); + + // Mock backups to create an on-demand backup scenario (no existing backups) + $this->clientProphecy->request('get', '/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases/testdb/backups') + ->willReturn([]); + + // Mock local machine for MySQL operations to stop execution before on-demand backup. + $localMachineHelper = $this->mockLocalMachineHelper(); + $this->mockExecuteMySqlConnect($localMachineHelper, false); + + $inputs = self::inputChooseEnvironment(); + + // Expect exception when MySQL connection fails. + $this->expectException(AcquiaCliException::class); + $this->expectExceptionMessage('Unable to connect'); + + $this->executeCommand([ + '--no-scripts' => true, + 'site' => 'jxr5000596dev', + ], $inputs); + + self::unsetEnvVars(['AH_CODEBASE_UUID']); + } } From bc9076033ba1d58c0f1ac841f54cd479a8506a28 Mon Sep 17 00:00:00 2001 From: Kalindi Adhiya Date: Mon, 20 Apr 2026 22:12:16 +0530 Subject: [PATCH 4/8] code cov , code review accomodated --- src/Command/CommandBase.php | 12 +++++++----- .../src/Commands/Pull/PullCommandTestBase.php | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 9a44928e6..760be055c 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -591,11 +591,13 @@ protected function determineCloudDatabases(Client $acquiaCloudClient, Environmen $codebaseUuid = self::getCodebaseUuid(); if ($codebaseUuid && $this->siteId) { $siteInstanceDb = $this->getSiteInstanceDatabase($this->siteId, $chosenEnvironment->uuid); - $siteInstanceDbConnection = $this->getSiteInstanceDatabaseConnection($this->siteId, $chosenEnvironment->uuid); - if ($siteInstanceDb && $siteInstanceDbConnection) { - $siteInstanceDatabaseResponse = EnvironmentTransformer::transformSiteInstanceDatabase($siteInstanceDb, $siteInstanceDbConnection); - if ($siteInstanceDatabaseResponse) { - return [$siteInstanceDatabaseResponse]; + if ($siteInstanceDb) { + $siteInstanceDbConnection = $this->getSiteInstanceDatabaseConnection($this->siteId, $chosenEnvironment->uuid); + if ($siteInstanceDbConnection) { + $siteInstanceDatabaseResponse = EnvironmentTransformer::transformSiteInstanceDatabase($siteInstanceDb, $siteInstanceDbConnection); + if ($siteInstanceDatabaseResponse) { + return [$siteInstanceDatabaseResponse]; + } } } } diff --git a/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php b/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php index 89739d341..8ece1a6a7 100644 --- a/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php +++ b/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php @@ -41,6 +41,7 @@ public function setUp(): void 'ACLI_DB_USER', 'ACLI_DB_PASSWORD', 'ACLI_DB_NAME', + 'AH_CODEBASE_UUID', ]); parent::setUp(); } From 403f30657e2458163e51e80227c20b167afbccd8 Mon Sep 17 00:00:00 2001 From: Kalindi Adhiya Date: Wed, 22 Apr 2026 10:22:22 +0530 Subject: [PATCH 5/8] code cov fix --- .../Commands/Pull/PullDatabaseCommandTest.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index f3f36779d..5ce7077fa 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -740,4 +740,80 @@ public function testGetSiteInstanceDatabaseConnectionExceptionHandling(): void self::unsetEnvVars(['AH_CODEBASE_UUID']); } + + /** + * Test that when getSiteInstanceDatabase throws an exception and returns null, + * the code falls back to the regular ACSF database endpoint. + * + * This ensures code coverage for the first nested if check: + * if ($siteInstanceDb) { + * // This block should NOT execute when $siteInstanceDb is null + * } + * // Falls back to regular database endpoint + */ + public function testGetSiteInstanceDatabaseExceptionHandling(): void + { + $codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8'; + self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]); + + // Mock codebase. + $codebase = $this->getMockCodeBaseResponse(); + $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid) + ->willReturn($codebase); + + // Mock codebase environment. + $codebaseEnv = $this->getMockCodeBaseEnvironment(); + $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/environments') + ->willReturn([$codebaseEnv]) + ->shouldBeCalled(); + + // Mock codebase sites. + $codeabaseSites = $this->getMockCodeBaseSites(); + $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/sites') + ->willReturn($codeabaseSites); + + // CRITICAL: Make getSiteInstanceDatabase throw an exception + // This tests the first nested if ($siteInstanceDb) when it's null. + $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database') + ->willThrow(new \Exception('Site instance database not found')); + + // Mock the fallback call to /environments/{uuid}/databases. + $mockDatabase = (object)[ + 'db_host' => 'fsdb-test.acquia.com', + 'environment' => (object)['id' => '3e8ecbec-ea7c-4260-8414-ef2938c859bc', 'name' => 'dev'], + 'flags' => (object)['default' => true], + 'id' => 'testdb123', + 'name' => 'testdb', + 'password' => 'password', + 'ssh_host' => 'web-test.acquia.com', + 'url' => 'mysqli://testuser:password@127.0.0.1:3306/testdb', + 'user_name' => 'testuser', + '_links' => (object)['self' => (object)['href' => 'https://cloud.acquia.com/api/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases/testdb']], + ]; + $databaseResponse = new \AcquiaCloudApi\Response\DatabaseResponse($mockDatabase); + + $this->clientProphecy->request('get', '/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases') + ->willReturn([$databaseResponse]); + + // Mock backups to create an on-demand backup scenario. + $this->clientProphecy->request('get', '/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases/testdb/backups') + ->willReturn([]); + + // Mock local machine for MySQL operations. + $localMachineHelper = $this->mockLocalMachineHelper(); + $this->mockExecuteMySqlConnect($localMachineHelper, false); + + $inputs = self::inputChooseEnvironment(); + + // Expect exception when MySQL connection fails. + $this->expectException(AcquiaCliException::class); + $this->expectExceptionMessage('Unable to connect'); + + $this->executeCommand([ + '--no-scripts' => true, + 'site' => 'jxr5000596dev', + ], $inputs); + + self::unsetEnvVars(['AH_CODEBASE_UUID']); + } } From 6ea42eb24bdbd65043e2c4290b8f3f9d634a3207 Mon Sep 17 00:00:00 2001 From: Kalindi Adhiya Date: Wed, 22 Apr 2026 10:46:16 +0530 Subject: [PATCH 6/8] code cov fix --- .../Commands/Pull/PullDatabaseCommandTest.php | 176 ++---------------- 1 file changed, 14 insertions(+), 162 deletions(-) diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 5ce7077fa..4751baab6 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -648,172 +648,24 @@ public function testPullDatabasesWithCodebaseUuidOnDemand(): void } /** - * Test that when getSiteInstanceDatabaseConnection throws an exception, - * the exception is caught and null is returned, allowing fallback logic. - * - * This ensures code coverage for the catch block in getSiteInstanceDatabaseConnection: - * } catch (\Exception $e) { - * $this->logger->debug('Could not get site instance database connection: ' . $e->getMessage()); - * } - * return null; - */ - public function testGetSiteInstanceDatabaseConnectionExceptionHandling(): void - { - $codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8'; - self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]); - - // Mock codebase. - $codebase = $this->getMockCodeBaseResponse(); - $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid) - ->willReturn($codebase); - - // Mock codebase environment. - $codebaseEnv = $this->getMockCodeBaseEnvironment(); - $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/environments') - ->willReturn([$codebaseEnv]) - ->shouldBeCalled(); - - // Mock codebase sites. - $codeabaseSites = $this->getMockCodeBaseSites(); - $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/sites') - ->willReturn($codeabaseSites); - - // Mock site instance. - $siteInstance = $this->getMockSiteInstanceResponse(); - $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc') - ->willReturn($siteInstance); - // Note: Not using shouldBeCalled() - this will NOT be called due to environment flow. - // Mock site. - $siteId = '8979a8ac-80dc-4df8-b2f0-6be36554a370'; - $site = $this->getMockSite(); - $this->clientProphecy->request('get', '/sites/' . $siteId) - ->willReturn($site); - // Note: Not using shouldBeCalled() - this will NOT be called. - // Mock site instance database. - $siteInstanceDatabase = $this->getMockSiteInstanceDatabaseResponse(); - $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database') - ->willReturn($siteInstanceDatabase); - // Note: Not using shouldBeCalled() - this will NOT be called. - // CRITICAL: Make database/connection throw an exception to test catch block. - $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/connection') - ->willThrow(new \Exception('API connection error')); - // Note: Not using shouldBeCalled() - this will NOT be called. - // The exception in getSiteInstanceDatabaseConnection will be caught, - // and it will return null. This causes the if condition to fail, - // and the code falls back to the regular ACSF database endpoint. - // Mock the fallback call to /environments/{uuid}/databases with a properly structured database. - $mockDatabase = (object)[ - 'db_host' => 'fsdb-test.acquia.com', - 'environment' => (object)['id' => '3e8ecbec-ea7c-4260-8414-ef2938c859bc', 'name' => 'dev'], - 'flags' => (object)['default' => true], - 'id' => 'testdb123', - 'name' => 'testdb', - 'password' => 'password', - 'ssh_host' => 'web-test.acquia.com', - 'url' => 'mysqli://testuser:password@127.0.0.1:3306/testdb', - 'user_name' => 'testuser', - '_links' => (object)['self' => (object)['href' => 'https://cloud.acquia.com/api/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases/testdb']], - ]; - $databaseResponse = new \AcquiaCloudApi\Response\DatabaseResponse($mockDatabase); - - $this->clientProphecy->request('get', '/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases') - ->willReturn([$databaseResponse]); - - // Mock backups to create an on-demand backup scenario (no existing backups) - $this->clientProphecy->request('get', '/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases/testdb/backups') - ->willReturn([]); - - // Mock local machine for MySQL operations to stop execution before on-demand backup. - $localMachineHelper = $this->mockLocalMachineHelper(); - $this->mockExecuteMySqlConnect($localMachineHelper, false); - - $inputs = self::inputChooseEnvironment(); - - // Expect exception when MySQL connection fails. - $this->expectException(AcquiaCliException::class); - $this->expectExceptionMessage('Unable to connect'); - - $this->executeCommand([ - '--no-scripts' => true, - 'site' => 'jxr5000596dev', - ], $inputs); - - self::unsetEnvVars(['AH_CODEBASE_UUID']); - } - - /** - * Test that when getSiteInstanceDatabase throws an exception and returns null, - * the code falls back to the regular ACSF database endpoint. - * - * This ensures code coverage for the first nested if check: - * if ($siteInstanceDb) { - * // This block should NOT execute when $siteInstanceDb is null - * } - * // Falls back to regular database endpoint + * Test catch block in getSiteInstanceDatabaseConnection method. + * Covers the logger->debug() line when an exception is caught. */ - public function testGetSiteInstanceDatabaseExceptionHandling(): void + public function testGetSiteInstanceDatabaseConnectionCatchBlock(): void { - $codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8'; - self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]); + // Mock the client to throw an exception. + $this->clientProphecy->request('get', Argument::containingString('/site-instances/')) + ->willThrow(new \Exception('API Connection Error')); - // Mock codebase. - $codebase = $this->getMockCodeBaseResponse(); - $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid) - ->willReturn($codebase); - - // Mock codebase environment. - $codebaseEnv = $this->getMockCodeBaseEnvironment(); - $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/environments') - ->willReturn([$codebaseEnv]) - ->shouldBeCalled(); - - // Mock codebase sites. - $codeabaseSites = $this->getMockCodeBaseSites(); - $this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/sites') - ->willReturn($codeabaseSites); - - // CRITICAL: Make getSiteInstanceDatabase throw an exception - // This tests the first nested if ($siteInstanceDb) when it's null. - $this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database') - ->willThrow(new \Exception('Site instance database not found')); - - // Mock the fallback call to /environments/{uuid}/databases. - $mockDatabase = (object)[ - 'db_host' => 'fsdb-test.acquia.com', - 'environment' => (object)['id' => '3e8ecbec-ea7c-4260-8414-ef2938c859bc', 'name' => 'dev'], - 'flags' => (object)['default' => true], - 'id' => 'testdb123', - 'name' => 'testdb', - 'password' => 'password', - 'ssh_host' => 'web-test.acquia.com', - 'url' => 'mysqli://testuser:password@127.0.0.1:3306/testdb', - 'user_name' => 'testuser', - '_links' => (object)['self' => (object)['href' => 'https://cloud.acquia.com/api/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases/testdb']], - ]; - $databaseResponse = new \AcquiaCloudApi\Response\DatabaseResponse($mockDatabase); - - $this->clientProphecy->request('get', '/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases') - ->willReturn([$databaseResponse]); - - // Mock backups to create an on-demand backup scenario. - $this->clientProphecy->request('get', '/environments/3e8ecbec-ea7c-4260-8414-ef2938c859bc/databases/testdb/backups') - ->willReturn([]); - - // Mock local machine for MySQL operations. - $localMachineHelper = $this->mockLocalMachineHelper(); - $this->mockExecuteMySqlConnect($localMachineHelper, false); - - $inputs = self::inputChooseEnvironment(); - - // Expect exception when MySQL connection fails. - $this->expectException(AcquiaCliException::class); - $this->expectExceptionMessage('Unable to connect'); + // Use reflection to call the private method. + $reflection = new \ReflectionClass($this->command); + $method = $reflection->getMethod('getSiteInstanceDatabaseConnection'); + $method->setAccessible(true); - $this->executeCommand([ - '--no-scripts' => true, - 'site' => 'jxr5000596dev', - ], $inputs); + // Call the method - it should catch the exception and return null. + $result = $method->invoke($this->command, 'test-site-uuid', 'test-env-uuid'); - self::unsetEnvVars(['AH_CODEBASE_UUID']); + // Assert null is returned when exception is caught. + $this->assertNull($result); } } From c18b68e4c41fbc12c8cefd502615d6cbd7cc7a90 Mon Sep 17 00:00:00 2001 From: Kalindi Adhiya Date: Wed, 22 Apr 2026 10:50:27 +0530 Subject: [PATCH 7/8] test depreciation fix --- .../Commands/Pull/PullDatabaseCommandTest.php | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 4751baab6..53a62dd9a 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -657,10 +657,30 @@ public function testGetSiteInstanceDatabaseConnectionCatchBlock(): void $this->clientProphecy->request('get', Argument::containingString('/site-instances/')) ->willThrow(new \Exception('API Connection Error')); - // Use reflection to call the private method. + // Use reflection to call the private method (PHP 8.1+ doesn't need setAccessible). $reflection = new \ReflectionClass($this->command); $method = $reflection->getMethod('getSiteInstanceDatabaseConnection'); - $method->setAccessible(true); + + // Call the method - it should catch the exception and return null. + $result = $method->invoke($this->command, 'test-site-uuid', 'test-env-uuid'); + + // Assert null is returned when exception is caught. + $this->assertNull($result); + } + + /** + * Test catch block in getSiteInstanceDatabase method. + * Covers the logger->debug() line when an exception is caught. + */ + public function testGetSiteInstanceDatabaseCatchBlock(): void + { + // Mock the client to throw an exception. + $this->clientProphecy->request('get', Argument::containingString('/site-instances/')) + ->willThrow(new \Exception('API Error')); + + // Use reflection to call the private method (PHP 8.1+ doesn't need setAccessible). + $reflection = new \ReflectionClass($this->command); + $method = $reflection->getMethod('getSiteInstanceDatabase'); // Call the method - it should catch the exception and return null. $result = $method->invoke($this->command, 'test-site-uuid', 'test-env-uuid'); From 9d168cb8e9f9efd14543cddaf2ff871967d31958 Mon Sep 17 00:00:00 2001 From: Kalindi Adhiya Date: Wed, 22 Apr 2026 11:05:35 +0530 Subject: [PATCH 8/8] co pilot reviews addressed --- src/Command/CommandBase.php | 4 +--- src/Transformer/EnvironmentTransformer.php | 4 ++-- tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php | 6 ++++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 760be055c..8c217f6d3 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -595,9 +595,7 @@ protected function determineCloudDatabases(Client $acquiaCloudClient, Environmen $siteInstanceDbConnection = $this->getSiteInstanceDatabaseConnection($this->siteId, $chosenEnvironment->uuid); if ($siteInstanceDbConnection) { $siteInstanceDatabaseResponse = EnvironmentTransformer::transformSiteInstanceDatabase($siteInstanceDb, $siteInstanceDbConnection); - if ($siteInstanceDatabaseResponse) { - return [$siteInstanceDatabaseResponse]; - } + return [$siteInstanceDatabaseResponse]; } } } diff --git a/src/Transformer/EnvironmentTransformer.php b/src/Transformer/EnvironmentTransformer.php index bd6292255..acb9bbd62 100644 --- a/src/Transformer/EnvironmentTransformer.php +++ b/src/Transformer/EnvironmentTransformer.php @@ -74,8 +74,8 @@ public static function transform(mixed $codebaseEnv): EnvironmentResponse } /** - * Transform a SiteInstanceDatabaseResponse and SiteInstanceDatabaseConnectionResponse - * object to a DatabaseResponse object. + * Transform SiteInstanceDatabaseResponse and SiteInstanceDatabaseConnectionResponse + * objects to a DatabaseResponse object. */ public static function transformSiteInstanceDatabase(SiteInstanceDatabaseResponse $siteInstanceDb, SiteInstanceDatabaseConnectionResponse $siteInstanceDbConnection): DatabaseResponse { diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 53a62dd9a..8a7ed1a09 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -655,7 +655,8 @@ public function testGetSiteInstanceDatabaseConnectionCatchBlock(): void { // Mock the client to throw an exception. $this->clientProphecy->request('get', Argument::containingString('/site-instances/')) - ->willThrow(new \Exception('API Connection Error')); + ->willThrow(new \Exception('API Connection Error')) + ->shouldBeCalled(); // Use reflection to call the private method (PHP 8.1+ doesn't need setAccessible). $reflection = new \ReflectionClass($this->command); @@ -676,7 +677,8 @@ public function testGetSiteInstanceDatabaseCatchBlock(): void { // Mock the client to throw an exception. $this->clientProphecy->request('get', Argument::containingString('/site-instances/')) - ->willThrow(new \Exception('API Error')); + ->willThrow(new \Exception('API Error')) + ->shouldBeCalled(); // Use reflection to call the private method (PHP 8.1+ doesn't need setAccessible). $reflection = new \ReflectionClass($this->command);