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..8c217f6d3 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); + if ($siteInstanceDb) { + $siteInstanceDbConnection = $this->getSiteInstanceDatabaseConnection($this->siteId, $chosenEnvironment->uuid); + if ($siteInstanceDbConnection) { + $siteInstanceDatabaseResponse = EnvironmentTransformer::transformSiteInstanceDatabase($siteInstanceDb, $siteInstanceDbConnection); + 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..acb9bbd62 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 SiteInstanceDatabaseResponse and SiteInstanceDatabaseConnectionResponse + * objects 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); 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(); } diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 9d79eebec..8a7ed1a09 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); @@ -637,4 +646,48 @@ public function testPullDatabasesWithCodebaseUuidOnDemand(): void self::unsetEnvVars(['AH_CODEBASE_UUID']); } + + /** + * Test catch block in getSiteInstanceDatabaseConnection method. + * Covers the logger->debug() line when an exception is caught. + */ + 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')) + ->shouldBeCalled(); + + // Use reflection to call the private method (PHP 8.1+ doesn't need setAccessible). + $reflection = new \ReflectionClass($this->command); + $method = $reflection->getMethod('getSiteInstanceDatabaseConnection'); + + // 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')) + ->shouldBeCalled(); + + // 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'); + + // Assert null is returned when exception is caught. + $this->assertNull($result); + } } 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);