diff --git a/src/Command/Pull/PullCommand.php b/src/Command/Pull/PullCommand.php index e223e90a1..7c632dac8 100644 --- a/src/Command/Pull/PullCommand.php +++ b/src/Command/Pull/PullCommand.php @@ -35,8 +35,6 @@ protected function configure(): void { } protected function execute(InputInterface $input, OutputInterface $output): int { - parent::execute($input, $output); - $this->setDirAndRequireProjectCwd($input); $clone = $this->determineCloneProject($output); $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); @@ -50,7 +48,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if (!$input->getOption('no-databases')) { - $this->pullDatabase($input, $output); + $this->pullDatabase($input, $output, $sourceEnvironment); } if (!$input->getOption('no-scripts')) { diff --git a/src/Command/Pull/PullCommandBase.php b/src/Command/Pull/PullCommandBase.php index 40750cc5b..745a34fa8 100644 --- a/src/Command/Pull/PullCommandBase.php +++ b/src/Command/Pull/PullCommandBase.php @@ -29,7 +29,6 @@ use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; use React\EventLoop\Loop; -use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; @@ -112,10 +111,6 @@ protected function initialize(InputInterface $input, OutputInterface $output): v $this->checklist = new Checklist($output); } - protected function execute(InputInterface $input, OutputInterface $output): int { - return Command::SUCCESS; - } - protected function pullCode(InputInterface $input, OutputInterface $output, bool $clone, EnvironmentResponse $sourceEnvironment): void { if ($clone) { $this->checklist->addItem('Cloning git repository from the Cloud Platform'); @@ -132,14 +127,12 @@ protected function pullCode(InputInterface $input, OutputInterface $output, bool * @param bool $onDemand Force on-demand backup. * @param bool $noImport Skip import. */ - protected function pullDatabase(InputInterface $input, OutputInterface $output, bool $onDemand = FALSE, bool $noImport = FALSE, bool $multipleDbs = FALSE): void { - $this->setDirAndRequireProjectCwd($input); + protected function pullDatabase(InputInterface $input, OutputInterface $output, EnvironmentResponse $sourceEnvironment, bool $onDemand = FALSE, bool $noImport = FALSE, bool $multipleDbs = FALSE): void { if (!$noImport) { // Verify database connection. $this->connectToLocalDatabase($this->getLocalDbHost(), $this->getLocalDbUser(), $this->getLocalDbName(), $this->getLocalDbPassword(), $this->getOutputCallback($output, $this->checklist)); } $acquiaCloudClient = $this->cloudApiClientService->getClient(); - $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); $site = $this->determineSite($sourceEnvironment, $input); $databases = $this->determineCloudDatabases($acquiaCloudClient, $sourceEnvironment, $site, $multipleDbs); diff --git a/src/Command/Pull/PullDatabaseCommand.php b/src/Command/Pull/PullDatabaseCommand.php index 6979a0a03..15fe38a55 100644 --- a/src/Command/Pull/PullDatabaseCommand.php +++ b/src/Command/Pull/PullDatabaseCommand.php @@ -39,7 +39,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $multipleDbs = $input->hasOption('multiple-dbs') && $input->getOption('multiple-dbs'); // $noImport implies $noScripts. $noScripts = $noImport || $noScripts; - $this->pullDatabase($input, $output, $onDemand, $noImport, $multipleDbs); + $this->setDirAndRequireProjectCwd($input); + $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); + $this->pullDatabase($input, $output, $sourceEnvironment, $onDemand, $noImport, $multipleDbs); if (!$noScripts) { $this->runDrushCacheClear($this->getOutputCallback($output, $this->checklist), $this->checklist); $this->runDrushSqlSanitize($this->getOutputCallback($output, $this->checklist), $this->checklist); diff --git a/src/Command/Pull/PullFilesCommand.php b/src/Command/Pull/PullFilesCommand.php index 032dc0b15..abf72f9d3 100644 --- a/src/Command/Pull/PullFilesCommand.php +++ b/src/Command/Pull/PullFilesCommand.php @@ -21,7 +21,6 @@ protected function configure(): void { } protected function execute(InputInterface $input, OutputInterface $output): int { - parent::execute($input, $output); $this->setDirAndRequireProjectCwd($input); $sourceEnvironment = $this->determineEnvironment($input, $output, TRUE); $this->pullFiles($input, $output, $sourceEnvironment); diff --git a/tests/phpunit/src/Commands/Archive/ArchiveExporterCommandTest.php b/tests/phpunit/src/Commands/Archive/ArchiveExporterCommandTest.php index 28d048240..e88c7b7c3 100644 --- a/tests/phpunit/src/Commands/Archive/ArchiveExporterCommandTest.php +++ b/tests/phpunit/src/Commands/Archive/ArchiveExporterCommandTest.php @@ -35,7 +35,7 @@ public function testArchiveExport(): void { $localMachineHelper->getFilesystem()->willReturn($fileSystem->reveal())->shouldBeCalled(); $this->mockExecutePvExists($localMachineHelper); $this->mockExecuteDrushExists($localMachineHelper); - $this->mockExecuteDrushStatus($localMachineHelper, TRUE, $this->projectDir); + $this->mockExecuteDrushStatus($localMachineHelper, $this->projectDir); $this->mockCreateMySqlDumpOnLocal($localMachineHelper); $localMachineHelper->checkRequiredBinariesExist(["tar"])->shouldBeCalled(); $localMachineHelper->execute(Argument::type('array'), Argument::type('callable'), NULL, TRUE)->willReturn($this->mockProcess())->shouldBeCalled(); diff --git a/tests/phpunit/src/Commands/Pull/PullCodeCommandTest.php b/tests/phpunit/src/Commands/Pull/PullCodeCommandTest.php index 33558e141..9aba199a0 100644 --- a/tests/phpunit/src/Commands/Pull/PullCodeCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullCodeCommandTest.php @@ -112,7 +112,7 @@ public function testWithScripts(): void { $this->mockExecuteComposerExists($localMachineHelper); $this->mockExecuteComposerInstall($localMachineHelper, $process); $this->mockExecuteDrushExists($localMachineHelper); - $this->mockExecuteDrushStatus($localMachineHelper, TRUE, $this->projectDir); + $this->mockExecuteDrushStatus($localMachineHelper, $this->projectDir); $this->mockExecuteDrushCacheRebuild($localMachineHelper, $process); $this->executeCommand([], self::inputChooseEnvironment()); @@ -139,7 +139,7 @@ public function testNoComposerJson(): void { $this->mockExecuteGitStatus(FALSE, $localMachineHelper, $this->projectDir); $process = $this->mockProcess(); $this->mockExecuteDrushExists($localMachineHelper); - $this->mockExecuteDrushStatus($localMachineHelper, TRUE, $this->projectDir); + $this->mockExecuteDrushStatus($localMachineHelper, $this->projectDir); $this->mockExecuteDrushCacheRebuild($localMachineHelper, $process); $this->executeCommand([], self::inputChooseEnvironment()); @@ -169,7 +169,7 @@ public function testNoComposer(): void { ->willReturn(FALSE) ->shouldBeCalled(); $this->mockExecuteDrushExists($localMachineHelper); - $this->mockExecuteDrushStatus($localMachineHelper, TRUE, $this->projectDir); + $this->mockExecuteDrushStatus($localMachineHelper, $this->projectDir); $this->mockExecuteDrushCacheRebuild($localMachineHelper, $process); $this->executeCommand([], self::inputChooseEnvironment()); @@ -198,7 +198,7 @@ public function testWithVendorDir(): void { $process = $this->mockProcess(); $this->mockExecuteComposerExists($localMachineHelper); $this->mockExecuteDrushExists($localMachineHelper); - $this->mockExecuteDrushStatus($localMachineHelper, TRUE, $this->projectDir); + $this->mockExecuteDrushStatus($localMachineHelper, $this->projectDir); $this->mockExecuteDrushCacheRebuild($localMachineHelper, $process); $this->executeCommand([], self::inputChooseEnvironment()); @@ -289,30 +289,4 @@ protected function mockExecuteGitClone( ->shouldBeCalled(); } - protected function mockExecuteGitFetchAndCheckout( - ObjectProphecy $localMachineHelper, - ObjectProphecy $process, - mixed $cwd, - mixed $vcsPath - ): void { - $localMachineHelper->execute([ - 'git', - 'fetch', - '--all', - ], Argument::type('callable'), $cwd, FALSE) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - $this->mockExecuteGitCheckout($localMachineHelper, $vcsPath, $cwd, $process); - } - - protected function mockExecuteGitCheckout(ObjectProphecy $localMachineHelper, mixed $vcsPath, mixed $cwd, ObjectProphecy $process): void { - $localMachineHelper->execute([ - 'git', - 'checkout', - $vcsPath, - ], Argument::type('callable'), $cwd, FALSE) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - } - } diff --git a/tests/phpunit/src/Commands/Pull/PullCommandTest.php b/tests/phpunit/src/Commands/Pull/PullCommandTest.php index b33433952..1118245cf 100644 --- a/tests/phpunit/src/Commands/Pull/PullCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullCommandTest.php @@ -8,17 +8,13 @@ use Acquia\Cli\Command\Pull\PullCommand; use Acquia\Cli\Exception\AcquiaCliException; use GuzzleHttp\Client; +use Prophecy\Argument; /** * @property \Acquia\Cli\Command\Pull\PullCommand $command */ class PullCommandTest extends PullCommandTestBase { - public function setUp(): void { - parent::setUp(); - $this->setupFsFixture(); - } - protected function createCommand(): CommandBase { $this->httpClientProphecy = $this->prophet->prophesize(Client::class); @@ -37,7 +33,62 @@ protected function createCommand(): CommandBase { ); } + public function testPull(): void { + // Pull code. + $environment = $this->mockGetEnvironment(); + $this->createMockGitConfigFile(); + $localMachineHelper = $this->mockLocalMachineHelper(); + $localMachineHelper->checkRequiredBinariesExist(["git"])->shouldBeCalled(); + $finder = $this->mockFinder(); + $localMachineHelper->getFinder()->willReturn($finder->reveal()); + $process = $this->mockProcess(); + $this->mockExecuteGitFetchAndCheckout($localMachineHelper, $process, $this->projectDir, $environment->vcs->path); + $this->mockExecuteGitStatus(FALSE, $localMachineHelper, $this->projectDir); + + // Pull files. + $sshHelper = $this->mockSshHelper(); + $this->mockGetCloudSites($sshHelper, $environment); + $this->mockGetFilesystem($localMachineHelper); + $parts = explode('.', $environment->ssh_url); + $sitegroup = reset($parts); + $this->mockExecuteRsync($localMachineHelper, $environment, '/mnt/files/' . $sitegroup . '.' . $environment->name . '/sites/bar/files/', $this->projectDir . '/docroot/sites/bar/files'); + $this->command->sshHelper = $sshHelper->reveal(); + + // Pull database. + $this->mockExecuteMySqlConnect($localMachineHelper, TRUE); + $this->mockGetBackup($environment); + $this->mockExecuteMySqlListTables($localMachineHelper, 'drupal'); + $process = $this->mockProcess(); + $localMachineHelper + ->execute(Argument::type('array'), Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword]) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + $this->mockExecuteMySqlImport($localMachineHelper, TRUE, TRUE, 'my_db', 'my_dbdev', 'drupal'); + $this->executeCommand([ + '--no-scripts' => TRUE, + ], [ + // Would you like Acquia CLI to search for a Cloud application that matches your local git config? + 'n', + // Select a Cloud Platform application: + self::$INPUT_DEFAULT_CHOICE, + // Would you like to link the project at ... ? + 'n', + // Choose an Acquia environment: + self::$INPUT_DEFAULT_CHOICE, + self::$INPUT_DEFAULT_CHOICE, + ]); + + $output = $this->getDisplay(); + + $this->assertStringContainsString('Select a Cloud Platform application:', $output); + $this->assertStringContainsString('[0] Sample application 1', $output); + $this->assertStringContainsString('Choose a Cloud Platform environment', $output); + $this->assertStringContainsString('[0] Dev, dev (vcs: master)', $output); + $this->assertStringContainsString('Choose a database [my_db (default)]:', $output); + } + public function testMissingLocalRepo(): void { + $this->setupFsFixture(); // Unset repo root. Mimics failing to find local git repo. Command must be re-created // to re-inject the parameter into the command. $this->acliRepoRoot = ''; diff --git a/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php b/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php index f4ac37d48..03fdc7f9e 100644 --- a/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php +++ b/tests/phpunit/src/Commands/Pull/PullCommandTestBase.php @@ -4,11 +4,19 @@ namespace Acquia\Cli\Tests\Commands\Pull; +use Acquia\Cli\Helpers\LocalMachineHelper; +use Acquia\Cli\Helpers\SshHelper; use Acquia\Cli\Tests\Commands\Ide\IdeRequiredTestTrait; use Acquia\Cli\Tests\CommandTestBase; +use AcquiaCloudApi\Response\BackupsResponse; use GuzzleHttp\Client; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Psr7\Uri; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; +use Symfony\Component\Filesystem\Path; use Symfony\Component\Finder\Finder; use Symfony\Component\Process\Process; @@ -18,7 +26,13 @@ abstract class PullCommandTestBase extends CommandTestBase { protected Client|ObjectProphecy $httpClientProphecy; + protected string $dbUser = 'drupal'; + protected string $dbPassword = 'drupal'; + 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(); } @@ -33,12 +47,11 @@ protected function mockExecuteDrushExists( protected function mockExecuteDrushStatus( ObjectProphecy $localMachineHelper, - mixed $hasConnection, string $dir = NULL ): void { $drushStatusProcess = $this->prophet->prophesize(Process::class); - $drushStatusProcess->isSuccessful()->willReturn($hasConnection); - $drushStatusProcess->getExitCode()->willReturn($hasConnection ? 0 : 1); + $drushStatusProcess->isSuccessful()->willReturn(TRUE); + $drushStatusProcess->getExitCode()->willReturn(0); $drushStatusProcess->getOutput() ->willReturn(json_encode(['db-status' => 'Connected'])); $localMachineHelper @@ -159,4 +172,209 @@ protected function mockFinder(): ObjectProphecy { return $finder; } + protected function mockExecuteGitFetchAndCheckout( + ObjectProphecy $localMachineHelper, + ObjectProphecy $process, + string $cwd, + string $vcsPath + ): void { + $localMachineHelper->execute([ + 'git', + 'fetch', + '--all', + ], Argument::type('callable'), $cwd, FALSE) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + $this->mockExecuteGitCheckout($localMachineHelper, $vcsPath, $cwd, $process); + } + + protected function mockExecuteGitCheckout(ObjectProphecy $localMachineHelper, string $vcsPath, string $cwd, ObjectProphecy $process): void { + $localMachineHelper->execute([ + 'git', + 'checkout', + $vcsPath, + ], Argument::type('callable'), $cwd, FALSE) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + } + + protected function mockExecuteRsync( + LocalMachineHelper|ObjectProphecy $localMachineHelper, + mixed $environment, + string $sourceDir, + string $destinationDir + ): void { + $process = $this->mockProcess(); + $localMachineHelper->checkRequiredBinariesExist(['rsync'])->shouldBeCalled(); + $command = [ + 'rsync', + '-avPhze', + 'ssh -o StrictHostKeyChecking=no', + $environment->ssh_url . ':' . $sourceDir, + $destinationDir, + ]; + $localMachineHelper->execute($command, Argument::type('callable'), NULL, TRUE) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + } + + protected function mockExecuteMySqlConnect( + ObjectProphecy $localMachineHelper, + bool $success + ): void { + $localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled(); + $process = $this->mockProcess($success); + $localMachineHelper + ->execute([ + 'mysql', + '--host', + $this->dbHost, + '--user', + 'drupal', + 'drupal', + ], Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => 'drupal']) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + } + + protected function mockExecuteMySqlListTables( + LocalMachineHelper|ObjectProphecy $localMachineHelper, + string $dbName = 'jxr5000596dev', + ): void { + $localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled(); + $process = $this->mockProcess(); + $process->getOutput()->willReturn('table1'); + $command = [ + 'mysql', + '--host', + 'localhost', + '--user', + 'drupal', + $dbName, + '--silent', + '-e', + 'SHOW TABLES;', + ]; + $localMachineHelper + ->execute($command, Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword]) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + } + + protected function mockExecuteMySqlDropDb( + LocalMachineHelper|ObjectProphecy $localMachineHelper, + bool $success, + ObjectProphecy $fs + ): void { + $localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled(); + $process = $this->mockProcess($success); + $fs->tempnam(Argument::type('string'), 'acli_drop_table_', '.sql')->willReturn('something')->shouldBeCalled(); + $fs->dumpfile('something', Argument::type('string'))->shouldBeCalled(); + $localMachineHelper + ->execute(Argument::type('array'), Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword]) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + } + + protected function mockExecuteMySqlImport( + ObjectProphecy $localMachineHelper, + bool $success, + bool $pvExists, + string $dbName = 'jxr5000596dev', + string $dbMachineName = 'db554675', + string $localDbName = 'jxr5000596dev', + string $env = 'dev' + ): void { + $localMachineHelper->checkRequiredBinariesExist(['gunzip', 'mysql'])->shouldBeCalled(); + $this->mockExecutePvExists($localMachineHelper, $pvExists); + $process = $this->mockProcess($success); + $filePath = Path::join(sys_get_temp_dir(), "$env-$dbName-$dbMachineName-2012-05-15T12:00:00Z.sql.gz"); + $command = $pvExists ? "pv $filePath --bytes --rate | gunzip | MYSQL_PWD=drupal mysql --host=localhost --user=drupal $localDbName" : "gunzip -c $filePath | MYSQL_PWD=drupal mysql --host=localhost --user=drupal $localDbName"; + // MySQL import command. + $localMachineHelper + ->executeFromCmd($command, Argument::type('callable'), + NULL, TRUE, NULL) + ->willReturn($process->reveal()) + ->shouldBeCalled(); + } + + protected function mockDownloadMySqlDump(ObjectProphecy $localMachineHelper, mixed $success): void { + $this->mockProcess($success); + $localMachineHelper->writeFile( + Argument::containingString("dev-profserv2-profserv201dev-something.sql.gz"), + 'backupfilecontents' + ) + ->shouldBeCalled(); + } + + protected function mockSettingsFiles(ObjectProphecy $fs): void { + $fs->remove(Argument::type('string')) + ->willReturn() + ->shouldBeCalled(); + } + + protected function mockListSites(SshHelper|ObjectProphecy $sshHelper): void { + $process = $this->mockProcess(); + $process->getOutput()->willReturn('default')->shouldBeCalled(); + $sshHelper->executeCommand(Argument::type('object'), ['ls', '/mnt/files/site.dev/sites'], FALSE) + ->willReturn($process->reveal())->shouldBeCalled(); + } + + public function mockGetBackup(mixed $environment): void { + $databases = $this->mockRequest('getEnvironmentsDatabases', $environment->id); + $tamper = function ($backups): void { + $backups[0]->completedAt = $backups[0]->completed_at; + }; + $backups = new BackupsResponse( + $this->mockRequest('getEnvironmentsDatabaseBackups', [ + $environment->id, + 'my_db', + ], NULL, NULL, $tamper) + ); + $this->mockDownloadBackup($databases[0], $environment, $backups[0]); + } + + protected function mockDownloadBackup(object $database, object $environment, object $backup, int $curlCode = 0): object { + if ($curlCode) { + $this->prophet->prophesize(StreamInterface::class); + /** @var RequestException|ObjectProphecy $requestException */ + $requestException = $this->prophet->prophesize(RequestException::class); + $requestException->getHandlerContext()->willReturn(['errno' => $curlCode]); + $this->clientProphecy->stream('get', "/environments/{$environment->id}/databases/{$database->name}/backups/1/actions/download", []) + ->willThrow($requestException->reveal()) + ->shouldBeCalled(); + $response = $this->prophet->prophesize(ResponseInterface::class); + $this->httpClientProphecy->request('GET', 'https://other.example.com/download-backup', Argument::type('array'))->willReturn($response->reveal())->shouldBeCalled(); + $domainsResponse = $this->getMockResponseFromSpec('/environments/{environmentId}/domains', 'get', 200); + $this->clientProphecy->request('get', "/environments/{$environment->id}/domains")->willReturn($domainsResponse->_embedded->items); + $this->command->setBackupDownloadUrl(new Uri( 'https://www.example.com/download-backup')); + } + else { + $this->mockDownloadBackupResponse($environment, $database->name, 1); + } + if ($database->flags->default) { + $dbMachineName = $database->name . $environment->name; + } + else { + $dbMachineName = 'db' . $database->id; + } + $filename = implode('-', [ + $environment->name, + $database->name, + $dbMachineName, + $backup->completedAt, + ]) . '.sql.gz'; + $localFilepath = Path::join(sys_get_temp_dir(), $filename); + $this->clientProphecy->addOption('sink', $localFilepath)->shouldBeCalled(); + $this->clientProphecy->addOption('curl.options', [ + 'CURLOPT_FILE' => $localFilepath, + 'CURLOPT_RETURNTRANSFER' => FALSE, + ])->shouldBeCalled(); + $this->clientProphecy->addOption('progress', Argument::type('Closure'))->shouldBeCalled(); + $this->clientProphecy->addOption('on_stats', Argument::type('Closure'))->shouldBeCalled(); + $this->clientProphecy->getOptions()->willReturn([]); + + return $database; + } + } diff --git a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php index c6361e97f..72338d8fe 100644 --- a/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullDatabaseCommandTest.php @@ -8,35 +8,18 @@ use Acquia\Cli\Command\Pull\PullCommandBase; use Acquia\Cli\Command\Pull\PullDatabaseCommand; use Acquia\Cli\Exception\AcquiaCliException; -use Acquia\Cli\Helpers\LocalMachineHelper; use Acquia\Cli\Helpers\SshHelper; -use AcquiaCloudApi\Response\BackupsResponse; use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Psr7\Uri; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\Filesystem\Path; /** * @property \Acquia\Cli\Command\Pull\PullDatabaseCommand $command */ class PullDatabaseCommandTest extends PullCommandTestBase { - protected string $dbUser = 'drupal'; - protected string $dbPassword = 'drupal'; - 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[][] */ @@ -44,20 +27,6 @@ public function providerTestPullDatabaseWithInvalidSslCertificate(): array { return [[51], [60]]; } - public function mockGetBackup(mixed $environment): void { - $databases = $this->mockRequest('getEnvironmentsDatabases', $environment->id); - $tamper = function ($backups): void { - $backups[0]->completedAt = $backups[0]->completed_at; - }; - $backups = new BackupsResponse( - $this->mockRequest('getEnvironmentsDatabaseBackups', [ - $environment->id, - 'my_db', - ], NULL, NULL, $tamper) - ); - $this->mockDownloadBackup($databases[0], $environment, $backups[0]); - } - protected function createCommand(): CommandBase { $this->httpClientProphecy = $this->prophet->prophesize(Client::class); @@ -89,9 +58,14 @@ public function testPullDatabases(): void { $this->mockExecuteMySqlImport($localMachineHelper, TRUE, TRUE, 'my_db', 'my_dbdev', 'drupal'); $fs->remove(Argument::type('string'))->shouldBeCalled(); $localMachineHelper->getFilesystem()->willReturn($fs)->shouldBeCalled(); + $this->mockExecuteDrushExists($localMachineHelper); + $this->mockExecuteDrushStatus($localMachineHelper, $this->projectDir); + $process = $this->mockProcess(); + $this->mockExecuteDrushCacheRebuild($localMachineHelper, $process); + $this->mockExecuteDrushSqlSanitize($localMachineHelper, $process); $this->executeCommand([ - '--no-scripts' => TRUE, + '--no-scripts' => FALSE, ], self::inputChooseEnvironment()); $output = $this->getDisplay(); @@ -104,7 +78,51 @@ public function testPullDatabases(): void { $this->assertStringContainsString('Downloading backup 1', $output); } + public function testPullProdDatabase(): void { + $localMachineHelper = $this->mockLocalMachineHelper(); + $this->mockExecuteMySqlConnect($localMachineHelper, TRUE); + $applications = $this->mockRequest('getApplications'); + $application = $this->mockRequest('getApplicationByUuid', $applications[self::$INPUT_DEFAULT_CHOICE]->uuid); + $environments = $this->mockRequest('getApplicationEnvironments', $application->uuid); + $environment = $environments[1]; + $sshHelper = $this->mockSshHelper(); + $process = $this->mockProcess(); + $process->getOutput()->willReturn('default')->shouldBeCalled(); + $sshHelper->executeCommand(Argument::type('object'), ['ls', '/mnt/files/site.prod/sites'], FALSE) + ->willReturn($process->reveal())->shouldBeCalled(); + $this->mockGetBackup($environment); + $this->mockExecuteMySqlListTables($localMachineHelper, 'drupal'); + $fs = $this->prophet->prophesize(Filesystem::class); + $this->mockExecuteMySqlDropDb($localMachineHelper, TRUE, $fs); + $this->mockExecuteMySqlImport($localMachineHelper, TRUE, TRUE, 'my_db', 'my_dbprod', 'drupal', 'prod'); + $fs->remove(Argument::type('string'))->shouldBeCalled(); + $localMachineHelper->getFilesystem()->willReturn($fs)->shouldBeCalled(); + + $this->executeCommand([ + '--no-scripts' => TRUE, + ], [ + // Would you like Acquia CLI to search for a Cloud application that matches your local git config? + 'n', + // Select a Cloud Platform application: + self::$INPUT_DEFAULT_CHOICE, + // Would you like to link the project at ... ? + 'n', + // Choose an Acquia environment: + 1, + ]); + + $output = $this->getDisplay(); + + $this->assertStringContainsString('Select a Cloud Platform application:', $output); + $this->assertStringContainsString('[0] Sample application 1', $output); + $this->assertStringContainsString('Choose a Cloud Platform environment', $output); + $this->assertStringContainsString('[0] Dev, dev (vcs: master)', $output); + $this->assertStringContainsString('Choose a database [my_db (default)]:', $output); + $this->assertStringContainsString('Downloading backup 1', $output); + } + public function testPullDatabasesLocalConnectionFailure(): void { + $this->mockGetEnvironment(); $localMachineHelper = $this->mockLocalMachineHelper(); $this->mockExecuteMySqlConnect($localMachineHelper, FALSE); @@ -326,107 +344,6 @@ protected function mockSshHelper(): ObjectProphecy|SshHelper { return $sshHelper; } - private function mockListSites(SshHelper|ObjectProphecy $sshHelper): void { - $process = $this->mockProcess(); - $process->getOutput()->willReturn('default')->shouldBeCalled(); - $sshHelper->executeCommand(Argument::type('object'), ['ls', '/mnt/files/site.dev/sites'], FALSE) - ->willReturn($process->reveal())->shouldBeCalled(); - } - - protected function mockExecuteMySqlConnect( - ObjectProphecy $localMachineHelper, - bool $success - ): void { - $localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled(); - $process = $this->mockProcess($success); - $localMachineHelper - ->execute([ - 'mysql', - '--host', - $this->dbHost, - '--user', - 'drupal', - 'drupal', - ], Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => 'drupal']) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - } - - protected function mockExecuteMySqlListTables( - LocalMachineHelper|ObjectProphecy $localMachineHelper, - string $dbName = 'jxr5000596dev', - ): void { - $localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled(); - $process = $this->mockProcess(); - $process->getOutput()->willReturn('table1'); - $command = [ - 'mysql', - '--host', - 'localhost', - '--user', - 'drupal', - $dbName, - '--silent', - '-e', - 'SHOW TABLES;', - ]; - $localMachineHelper - ->execute($command, Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword]) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - } - - protected function mockExecuteMySqlDropDb( - LocalMachineHelper|ObjectProphecy $localMachineHelper, - bool $success, - ObjectProphecy $fs - ): void { - $localMachineHelper->checkRequiredBinariesExist(["mysql"])->shouldBeCalled(); - $process = $this->mockProcess($success); - $fs->tempnam(Argument::type('string'), 'acli_drop_table_', '.sql')->willReturn('something')->shouldBeCalled(); - $fs->dumpfile('something', Argument::type('string'))->shouldBeCalled(); - $localMachineHelper - ->execute(Argument::type('array'), Argument::type('callable'), NULL, FALSE, NULL, ['MYSQL_PWD' => $this->dbPassword]) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - } - - protected function mockExecuteMySqlImport( - ObjectProphecy $localMachineHelper, - bool $success, - bool $pvExists, - string $dbName = 'jxr5000596dev', - string $dbMachineName = 'db554675', - string $localDbName = 'jxr5000596dev' - ): void { - $localMachineHelper->checkRequiredBinariesExist(['gunzip', 'mysql'])->shouldBeCalled(); - $this->mockExecutePvExists($localMachineHelper, $pvExists); - $process = $this->mockProcess($success); - $filePath = Path::join(sys_get_temp_dir(), "dev-$dbName-$dbMachineName-2012-05-15T12:00:00Z.sql.gz"); - $command = $pvExists ? "pv $filePath --bytes --rate | gunzip | MYSQL_PWD=drupal mysql --host=localhost --user=drupal $localDbName" : "gunzip -c $filePath | MYSQL_PWD=drupal mysql --host=localhost --user=drupal $localDbName"; - // MySQL import command. - $localMachineHelper - ->executeFromCmd($command, Argument::type('callable'), - NULL, TRUE, NULL) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - } - - protected function mockDownloadMySqlDump(ObjectProphecy $localMachineHelper, mixed $success): void { - $this->mockProcess($success); - $localMachineHelper->writeFile( - Argument::containingString("dev-profserv2-profserv201dev-something.sql.gz"), - 'backupfilecontents' - ) - ->shouldBeCalled(); - } - - protected function mockSettingsFiles(ObjectProphecy $fs): void { - $fs->remove(Argument::type('string')) - ->willReturn() - ->shouldBeCalled(); - } - public function testDownloadProgressDisplay(): void { $output = new BufferedOutput(); $progress = NULL; @@ -442,47 +359,4 @@ public function testDownloadProgressDisplay(): void { $this->assertStringContainsString('100/100 [============================] 100%', $output->fetch()); } - protected function mockDownloadBackup(object $database, object $environment, object $backup, int $curlCode = 0): object { - if ($curlCode) { - $this->prophet->prophesize(StreamInterface::class); - /** @var RequestException|ObjectProphecy $requestException */ - $requestException = $this->prophet->prophesize(RequestException::class); - $requestException->getHandlerContext()->willReturn(['errno' => $curlCode]); - $this->clientProphecy->stream('get', "/environments/{$environment->id}/databases/{$database->name}/backups/1/actions/download", []) - ->willThrow($requestException->reveal()) - ->shouldBeCalled(); - $response = $this->prophet->prophesize(ResponseInterface::class); - $this->httpClientProphecy->request('GET', 'https://other.example.com/download-backup', Argument::type('array'))->willReturn($response->reveal())->shouldBeCalled(); - $domainsResponse = $this->getMockResponseFromSpec('/environments/{environmentId}/domains', 'get', 200); - $this->clientProphecy->request('get', "/environments/{$environment->id}/domains")->willReturn($domainsResponse->_embedded->items); - $this->command->setBackupDownloadUrl(new Uri( 'https://www.example.com/download-backup')); - } - else { - $this->mockDownloadBackupResponse($environment, $database->name, 1); - } - if ($database->flags->default) { - $dbMachineName = $database->name . $environment->name; - } - else { - $dbMachineName = 'db' . $database->id; - } - $filename = implode('-', [ - $environment->name, - $database->name, - $dbMachineName, - $backup->completedAt, - ]) . '.sql.gz'; - $localFilepath = Path::join(sys_get_temp_dir(), $filename); - $this->clientProphecy->addOption('sink', $localFilepath)->shouldBeCalled(); - $this->clientProphecy->addOption('curl.options', [ - 'CURLOPT_FILE' => $localFilepath, - 'CURLOPT_RETURNTRANSFER' => FALSE, -])->shouldBeCalled(); - $this->clientProphecy->addOption('progress', Argument::type('Closure'))->shouldBeCalled(); - $this->clientProphecy->addOption('on_stats', Argument::type('Closure'))->shouldBeCalled(); - $this->clientProphecy->getOptions()->willReturn([]); - - return $database; - } - } diff --git a/tests/phpunit/src/Commands/Pull/PullFilesCommandTest.php b/tests/phpunit/src/Commands/Pull/PullFilesCommandTest.php index 4c8fffdc7..2dbc70905 100644 --- a/tests/phpunit/src/Commands/Pull/PullFilesCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullFilesCommandTest.php @@ -7,11 +7,8 @@ use Acquia\Cli\Command\CommandBase; use Acquia\Cli\Command\Pull\PullFilesCommand; use Acquia\Cli\Exception\AcquiaCliException; -use Acquia\Cli\Helpers\LocalMachineHelper; use Acquia\Cli\Tests\Commands\Ide\IdeHelper; use GuzzleHttp\Client; -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; class PullFilesCommandTest extends PullCommandTestBase { @@ -118,24 +115,4 @@ public function testInvalidCwd(): void { IdeHelper::unsetCloudIdeEnvVars(); } - protected function mockExecuteRsync( - LocalMachineHelper|ObjectProphecy $localMachineHelper, - mixed $environment, - string $sourceDir, - string $destinationDir - ): void { - $process = $this->mockProcess(); - $localMachineHelper->checkRequiredBinariesExist(['rsync'])->shouldBeCalled(); - $command = [ - 'rsync', - '-avPhze', - 'ssh -o StrictHostKeyChecking=no', - $environment->ssh_url . ':' . $sourceDir, - $destinationDir, - ]; - $localMachineHelper->execute($command, Argument::type('callable'), NULL, TRUE) - ->willReturn($process->reveal()) - ->shouldBeCalled(); - } - } diff --git a/tests/phpunit/src/Commands/Pull/PullScriptsCommandTest.php b/tests/phpunit/src/Commands/Pull/PullScriptsCommandTest.php index 25aa4780d..cfd042e5d 100644 --- a/tests/phpunit/src/Commands/Pull/PullScriptsCommandTest.php +++ b/tests/phpunit/src/Commands/Pull/PullScriptsCommandTest.php @@ -27,13 +27,10 @@ public function testRefreshScripts(): void { $this->mockExecuteComposerInstall($localMachineHelper, $process); // Drush. - $drushConnectionExists = TRUE; $this->mockExecuteDrushExists($localMachineHelper); - $this->mockExecuteDrushStatus($localMachineHelper, $drushConnectionExists, $this->projectDir); - if ($drushConnectionExists) { - $this->mockExecuteDrushCacheRebuild($localMachineHelper, $process); - $this->mockExecuteDrushSqlSanitize($localMachineHelper, $process); - } + $this->mockExecuteDrushStatus($localMachineHelper, $this->projectDir); + $this->mockExecuteDrushCacheRebuild($localMachineHelper, $process); + $this->mockExecuteDrushSqlSanitize($localMachineHelper, $process); $inputs = [ // Would you like Acquia CLI to search for a Cloud application that matches your local git config? @@ -50,7 +47,7 @@ public function testRefreshScripts(): void { '--dir' => $this->projectDir, ], $inputs); - $output = $this->getDisplay(); + $this->getDisplay(); } }