diff --git a/src/Command/Pull/PullCommandBase.php b/src/Command/Pull/PullCommandBase.php index 95e03f300..f3be7792a 100644 --- a/src/Command/Pull/PullCommandBase.php +++ b/src/Command/Pull/PullCommandBase.php @@ -46,14 +46,36 @@ abstract class PullCommandBase extends CommandBase { private UriInterface $backupDownloadUrl; + /** + * @see https://github.com/drush-ops/drush/blob/c21a5a24a295cc0513bfdecead6f87f1a2cf91a2/src/Sql/SqlMysql.php#L168 + * @return string[] + */ + private function listTables(string $out): array { + $tables = []; + if ($out = trim($out)) { + $tables = explode(PHP_EOL, $out); + } + return $tables; + } + + /** + * @see https://github.com/drush-ops/drush/blob/c21a5a24a295cc0513bfdecead6f87f1a2cf91a2/src/Sql/SqlMysql.php#L178 + * @return string[] + */ + private function listTablesQuoted(string $out): array { + $tables = $this->listTables($out); + foreach ($tables as &$table) { + $table = "`$table`"; + } + return $tables; + } + protected function getCloudFilesDir(EnvironmentResponse $chosenEnvironment, string $site): string { $sitegroup = self::getSiteGroupFromSshUrl($chosenEnvironment->sshUrl); if ($this->isAcsfEnv($chosenEnvironment)) { return '/mnt/files/' . $sitegroup . '.' . $chosenEnvironment->name . '/sites/g/files/' . $site . '/files'; } - else { - return $this->getCloudSitesPath($chosenEnvironment, $sitegroup) . "/$site/files"; - } + return $this->getCloudSitesPath($chosenEnvironment, $sitegroup) . "/$site/files"; } protected function getLocalFilesDir(string $site): string { @@ -190,7 +212,6 @@ private function doImportRemoteDatabase( Closure $outputCallback = NULL ): void { $this->dropDbTables($databaseHost, $databaseUser, $databaseName, $databasePassword, $outputCallback); - $this->createLocalDatabase($databaseHost, $databaseUser, $databaseName, $databasePassword, $outputCallback); $this->importDatabaseDump($localFilepath, $databaseHost, $databaseUser, $databaseName, $databasePassword, $outputCallback); $this->localMachineHelper->getFilesystem()->remove($localFilepath); } @@ -353,49 +374,28 @@ private function dropDbTables(string $dbHost, string $dbUser, string $dbName, st '--user', $dbUser, $dbName, + '--silent', '-e', 'SHOW TABLES;', ]; $process = $this->localMachineHelper->execute($command, $outputCallback, NULL, FALSE, NULL, ['MYSQL_PWD' => $dbPassword]); - $output = trim($process->getOutput()); - $tables = explode(PHP_EOL, $output); - foreach ($tables as &$table) { - $table = "`$table`"; - } - $sql = 'DROP TABLE ' . implode(', ', $tables); - $command = [ - 'mysql', - '--host', - $dbHost, - '--user', - $dbUser, - $dbName, - '-e', - $sql, - ]; - $process = $this->localMachineHelper->execute($command, $outputCallback, NULL, FALSE, NULL, ['MYSQL_PWD' => $dbPassword]); - if (!$process->isSuccessful()) { - throw new AcquiaCliException('Unable to drop tables from database. {message}', ['message' => $process->getErrorOutput()]); - } - } - - private function createLocalDatabase(string $dbHost, string $dbUser, string $dbName, string $dbPassword, ?\Closure $outputCallback = NULL): void { - if ($outputCallback) { - $outputCallback('out', "Creating new empty database $dbName"); - } - $this->localMachineHelper->checkRequiredBinariesExist(['mysql']); - $command = [ - 'mysql', - '--host', - $dbHost, - '--user', - $dbUser, - '-e', - 'create database ' . $dbName, - ]; - $process = $this->localMachineHelper->execute($command, $outputCallback, NULL, FALSE, NULL, ['MYSQL_PWD' => $dbPassword]); - if (!$process->isSuccessful()) { - throw new AcquiaCliException('Unable to create a local database. {message}', ['message' => $process->getErrorOutput()]); + $tables = $this->listTablesQuoted($process->getOutput()); + if ($tables) { + $sql = 'DROP TABLE ' . implode(', ', $tables); + $command = [ + 'mysql', + '--host', + $dbHost, + '--user', + $dbUser, + $dbName, + '-e', + $sql, + ]; + $process = $this->localMachineHelper->execute($command, $outputCallback, NULL, FALSE, NULL, ['MYSQL_PWD' => $dbPassword]); + if (!$process->isSuccessful()) { + throw new AcquiaCliException('Unable to drop tables from database. {message}', ['message' => $process->getErrorOutput()]); + } } } diff --git a/tests/phpunit/src/CommandTestBase.php b/tests/phpunit/src/CommandTestBase.php index 4c20c7a41..9593655ea 100644 --- a/tests/phpunit/src/CommandTestBase.php +++ b/tests/phpunit/src/CommandTestBase.php @@ -350,7 +350,7 @@ protected function mockNotificationResponseFromObject(object $responseWithNotifi protected function mockCreateMySqlDumpOnLocal(ObjectProphecy $localMachineHelper): void { $localMachineHelper->checkRequiredBinariesExist(["mysqldump", "gzip"])->shouldBeCalled(); - $process = $this->mockProcess(TRUE); + $process = $this->mockProcess(); $process->getOutput()->willReturn(''); $command = 'MYSQL_PWD=drupal mysqldump --host=localhost --user=drupal drupal | pv --rate --bytes | gzip -9 > ' . sys_get_temp_dir() . '/acli-mysql-dump-drupal.sql.gz'; $localMachineHelper->executeFromCmd($command, Argument::type('callable'), NULL, TRUE)->willReturn($process->reveal()) diff --git a/tests/phpunit/src/Commands/Archive/ArchiveExporterCommandTest.php b/tests/phpunit/src/Commands/Archive/ArchiveExporterCommandTest.php index 95b8afa95..83fc2a7b1 100644 --- a/tests/phpunit/src/Commands/Archive/ArchiveExporterCommandTest.php +++ b/tests/phpunit/src/Commands/Archive/ArchiveExporterCommandTest.php @@ -22,6 +22,11 @@ protected function createCommand(): Command { return $this->injectCommand(ArchiveExportCommand::class); } + public function setUp(): void { + self::unsetEnvVars(['ACLI_DB_HOST', 'ACLI_DB_USER', 'ACLI_DB_PASSWORD', 'ACLI_DB_NAME']); + parent::setUp(); + } + public function testArchiveExport(): void { touch(Path::join($this->projectDir, '.gitignore')); $destinationDir = 'foo'; diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index 9dffbf465..1f3476ee4 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -7,6 +7,7 @@ use Acquia\Cli\Command\Pull\PullCommandBase; use Acquia\Cli\Command\Pull\PullDatabaseCommand; use Acquia\Cli\Exception\AcquiaCliException; +use Acquia\Cli\Helpers\LocalMachineHelper; use AcquiaCloudApi\Response\DatabaseResponse; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Psr7\Uri; @@ -29,6 +30,11 @@ class PullDatabaseCommandTest extends PullCommandTestBase { protected string $dbHost = 'localhost'; protected string $dbName = 'drupal'; + public function setUp(): void { + self::unsetEnvVars(['ACLI_DB_HOST', 'ACLI_DB_USER', 'ACLI_DB_PASSWORD', 'ACLI_DB_NAME']); + parent::setUp(); + } + /** * @return int[][] */ @@ -41,7 +47,7 @@ protected function createCommand(): Command { } public function testPullDatabases(): void { - $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE); + $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE); $inputs = $this->getInputs(); $this->executeCommand([ @@ -60,7 +66,7 @@ public function testPullDatabases(): void { } public function testPullDatabasesLocalConnectionFailure(): void { - $this->setupPullDatabase(FALSE, TRUE, TRUE, TRUE, TRUE); + $this->setupPullDatabase(FALSE, TRUE, TRUE, TRUE); $inputs = $this->getInputs(); $this->expectException(AcquiaCliException::class); @@ -71,7 +77,7 @@ public function testPullDatabasesLocalConnectionFailure(): void { } public function testPullDatabaseNoPv(): void { - $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, 0, TRUE, FALSE); + $this->setupPullDatabase(TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, 0, TRUE, FALSE); $inputs = $this->getInputs(); $this->executeCommand(['--no-scripts' => TRUE], $inputs); @@ -80,7 +86,7 @@ public function testPullDatabaseNoPv(): void { } public function testPullMultipleDatabases(): void { - $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE); + $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE); $inputs = [ // Would you like Acquia CLI to search for a Cloud application that matches your local git config? 'n', @@ -103,7 +109,7 @@ public function testPullMultipleDatabases(): void { } public function testPullDatabasesOnDemand(): void { - $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE); + $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE); $inputs = $this->getInputs(); $this->executeCommand([ @@ -122,7 +128,7 @@ public function testPullDatabasesOnDemand(): void { } public function testPullDatabasesNoExistingBackup(): void { - $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, 0, FALSE); + $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, 0, FALSE); $inputs = $this->getInputs(); $this->executeCommand([ @@ -141,7 +147,7 @@ public function testPullDatabasesNoExistingBackup(): void { } public function testPullDatabasesSiteArgument(): void { - $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE); + $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, FALSE, FALSE); $inputs = $this->getInputs(); $this->executeCommand([ @@ -159,24 +165,15 @@ public function testPullDatabasesSiteArgument(): void { } public function testPullDatabaseWithMySqlDropError(): void { - $this->setupPullDatabase(TRUE, FALSE, TRUE, TRUE); + $this->setupPullDatabase(TRUE, FALSE, TRUE); $inputs = $this->getInputs(); $this->expectException(AcquiaCliException::class); - $this->expectExceptionMessage('Unable to drop a local database'); - $this->executeCommand(['--no-scripts' => TRUE], $inputs); - } - - public function testPullDatabaseWithMySqlCreateError(): void { - $this->setupPullDatabase(TRUE, TRUE, FALSE, TRUE); - $inputs = $this->getInputs(); - - $this->expectException(AcquiaCliException::class); - $this->expectExceptionMessage('Unable to create a local database'); + $this->expectExceptionMessage('Unable to drop tables from database'); $this->executeCommand(['--no-scripts' => TRUE], $inputs); } public function testPullDatabaseWithMySqlImportError(): void { - $this->setupPullDatabase(TRUE, TRUE, TRUE, FALSE); + $this->setupPullDatabase(TRUE, TRUE, FALSE); $inputs = $this->getInputs(); $this->expectException(AcquiaCliException::class); @@ -188,7 +185,7 @@ public function testPullDatabaseWithMySqlImportError(): void { * @dataProvider providerTestPullDatabaseWithInvalidSslCertificate */ public function testPullDatabaseWithInvalidSslCertificate(int $errorCode): void { - $this->setupPullDatabase(TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, $errorCode); + $this->setupPullDatabase(TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, $errorCode); $inputs = $this->getInputs(); $this->executeCommand(['--no-scripts' => TRUE], $inputs); @@ -197,7 +194,7 @@ public function testPullDatabaseWithInvalidSslCertificate(int $errorCode): void $this->assertStringContainsString('Trying alternative host other.example.com', $output); } - protected function setupPullDatabase(bool $mysqlConnectSuccessful, bool $mysqlDropSuccessful, bool $mysqlCreateSuccessful, bool $mysqlImportSuccessful, bool $mockIdeFs = FALSE, bool $onDemand = FALSE, bool $mockGetAcsfSites = TRUE, bool $multidb = FALSE, int $curlCode = 0, bool $existingBackups = TRUE, bool $pvExists = TRUE): void { + protected function setupPullDatabase(bool $mysqlConnectSuccessful, bool $mysqlDropSuccessful, bool $mysqlImportSuccessful, bool $mockIdeFs = FALSE, bool $onDemand = FALSE, bool $mockGetAcsfSites = TRUE, bool $multiDb = FALSE, int $curlCode = 0, bool $existingBackups = TRUE, bool $pvExists = TRUE): void { $applicationsResponse = $this->mockApplicationsRequest(); $this->mockApplicationRequest(); $environmentsResponse = $this->mockAcsfEnvironmentsRequest($applicationsResponse); @@ -209,7 +206,7 @@ protected function setupPullDatabase(bool $mysqlConnectSuccessful, bool $mysqlDr $databaseBackupsResponse = $this->mockDatabaseBackupsResponse($selectedEnvironment, $databaseResponse->name, 1, $existingBackups); $selectedDatabase = $this->mockDownloadBackup($databaseResponse, $selectedEnvironment, $databaseBackupsResponse->_embedded->items[0], $curlCode); - if ($multidb) { + if ($multiDb) { $databaseResponse2 = $databasesResponse[array_search('profserv2', array_column($databasesResponse, 'name'), TRUE)]; $databaseBackupsResponse2 = $this->mockDatabaseBackupsResponse($selectedEnvironment, $databaseResponse2->name, 1, $existingBackups); $this->mockDownloadBackup($databaseResponse2, $selectedEnvironment, $databaseBackupsResponse2->_embedded->items[0], $curlCode); @@ -239,10 +236,11 @@ protected function setupPullDatabase(bool $mysqlConnectSuccessful, bool $mysqlDr } // Database. + $this->mockExecuteMySqlListTables($localMachineHelper); $this->mockExecuteMySqlDropDb($localMachineHelper, $mysqlDropSuccessful); - $this->mockExecuteMySqlCreateDb($localMachineHelper, $mysqlCreateSuccessful); $this->mockExecuteMySqlImport($localMachineHelper, $mysqlImportSuccessful, $pvExists); - if ($multidb) { + if ($multiDb) { + $this->mockExecuteMySqlListTables($localMachineHelper, 'drupal'); $this->mockExecuteMySqlImport($localMachineHelper, $mysqlImportSuccessful, $pvExists, 'profserv2', 'profserv2dev', 'drupal'); } $this->command->localMachineHelper = $localMachineHelper->reveal(); @@ -268,35 +266,38 @@ protected function mockExecuteMySqlConnect( ->shouldBeCalled(); } - protected function mockExecuteMySqlDropDb( - \Acquia\Cli\Helpers\LocalMachineHelper|ObjectProphecy $localMachineHelper, - bool $success + protected function mockExecuteMySqlListTables( + LocalMachineHelper|ObjectProphecy $localMachineHelper, + string $dbName = 'jxr5000596dev', ): void { $localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled(); - $process = $this->mockProcess($success); + $process = $this->mockProcess(); + $process->getOutput()->willReturn('table1'); + $command = [ + 'mysql', + '--host', + 'localhost', + '--user', + 'drupal', + $dbName, + '--silent', + '-e', + 'SHOW TABLES;', + ]; $localMachineHelper - ->execute(Argument::type('array'), Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword]) + ->execute($command, Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword]) ->willReturn($process->reveal()) ->shouldBeCalled(); } - protected function mockExecuteMySqlCreateDb( - ObjectProphecy $localMachineHelper, + protected function mockExecuteMySqlDropDb( + LocalMachineHelper|ObjectProphecy $localMachineHelper, bool $success ): void { $localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled(); $process = $this->mockProcess($success); $localMachineHelper - ->execute([ - 'mysql', - '--host', - $this->dbHost, - '--user', - $this->dbUser, - '-e', - //'create database drupal', - 'create database jxr5000596dev', - ], Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => 'drupal']) + ->execute(Argument::type('array'), Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword]) ->willReturn($process->reveal()) ->shouldBeCalled(); } diff --git a/tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php b/tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php index f61cad559..e404fd61e 100644 --- a/tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Push/PushDatabaseCommandTest.php @@ -20,6 +20,11 @@ protected function createCommand(): Command { return $this->injectCommand(PushDatabaseCommand::class); } + public function setUp(): void { + self::unsetEnvVars(['ACLI_DB_HOST', 'ACLI_DB_USER', 'ACLI_DB_PASSWORD', 'ACLI_DB_NAME']); + parent::setUp(); + } + public function testPushDatabase(): void { $applicationsResponse = $this->mockApplicationsRequest(); $this->mockApplicationRequest();