Skip to content

Commit 0ac894f

Browse files
test cases added
1 parent faaad8a commit 0ac894f

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed

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

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use GuzzleHttp\Client;
1616
use Prophecy\Argument;
1717
use Prophecy\Prophecy\ObjectProphecy;
18+
use Psr\Http\Message\ResponseInterface;
1819
use Symfony\Component\Console\Output\BufferedOutput;
1920
use Symfony\Component\Filesystem\Filesystem;
2021

@@ -637,4 +638,342 @@ public function testPullDatabasesWithCodebaseUuidOnDemand(): void
637638

638639
self::unsetEnvVars(['AH_CODEBASE_UUID']);
639640
}
641+
642+
/**
643+
* Test that valid downloads with HTTP 200 and valid gzip continue to work.
644+
*/
645+
public function testPullDatabaseWithValidDownload(): void
646+
{
647+
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
648+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
649+
650+
$this->mockCodebaseEnvironmentSetup($codebaseUuid);
651+
652+
// Mock successful HTTP response with valid gzip file.
653+
$response = $this->prophet->prophesize(ResponseInterface::class);
654+
$response->getStatusCode()->willReturn(200)->shouldBeCalled();
655+
656+
$siteInstanceDatabaseBackups = $this->getMockSiteInstanceDatabaseBackupsResponse();
657+
$downloadUrl = $siteInstanceDatabaseBackups->_embedded->items[0]->links->download->href ?? 'https://example.com/download-backup';
658+
659+
$this->httpClientProphecy
660+
->request(
661+
'GET',
662+
$downloadUrl,
663+
Argument::that(function (array $opts): bool {
664+
// Create a valid gzip file.
665+
if (isset($opts['sink'])) {
666+
$filename = $opts['sink'];
667+
// Create valid gzip header (0x1f 0x8b) + some data.
668+
file_put_contents($filename, hex2bin('1f8b') . 'compressed data');
669+
}
670+
return isset($opts['sink']) && isset($opts['progress']);
671+
})
672+
)
673+
->will(function ($args) use ($response) {
674+
return $response->reveal();
675+
})
676+
->shouldBeCalled();
677+
678+
$localMachineHelper = $this->mockLocalMachineHelper();
679+
$this->mockExecuteMySqlConnect($localMachineHelper, true);
680+
$this->mockExecuteMySqlListTables($localMachineHelper, 'example');
681+
$fs = $this->prophet->prophesize(Filesystem::class);
682+
$this->mockExecuteMySqlDropDb($localMachineHelper, true, $fs);
683+
$this->mockExecuteMySqlImport($localMachineHelper, true, true, 'example', 'dbexample', 'example', 'environment_3e8ecbec-ea7c-4260-8414-ef2938c859bc', '2025-04-01T13:01:06.603Z');
684+
$fs->remove(Argument::type('string'))->shouldBeCalled();
685+
$localMachineHelper->getFilesystem()->willReturn($fs->reveal())->shouldBeCalled();
686+
687+
$this->executeCommand([
688+
'--no-scripts' => true,
689+
], self::inputChooseEnvironment());
690+
691+
$output = $this->getDisplay();
692+
$this->assertStringContainsString('Connecting to database', $output);
693+
694+
self::unsetEnvVars(['AH_CODEBASE_UUID']);
695+
}
696+
697+
/**
698+
* Test that invalid HTTP responses (404) are caught and reported.
699+
*/
700+
public function testPullDatabaseWithInvalidHttpResponse404(): void
701+
{
702+
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
703+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
704+
705+
$this->mockCodebaseEnvironmentSetup($codebaseUuid);
706+
707+
// Mock 404 HTTP response.
708+
$response = $this->prophet->prophesize(ResponseInterface::class);
709+
$response->getStatusCode()->willReturn(404)->shouldBeCalled();
710+
711+
$siteInstanceDatabaseBackups = $this->getMockSiteInstanceDatabaseBackupsResponse();
712+
$downloadUrl = $siteInstanceDatabaseBackups->_embedded->items[0]->links->download->href ?? 'https://example.com/download-backup';
713+
714+
$this->httpClientProphecy
715+
->request(
716+
'GET',
717+
$downloadUrl,
718+
Argument::that(function (array $opts): bool {
719+
// Create a file (will be cleaned up by validation)
720+
if (isset($opts['sink'])) {
721+
file_put_contents($opts['sink'], 'error response');
722+
}
723+
return isset($opts['sink']);
724+
})
725+
)
726+
->will(function () use ($response) {
727+
return $response->reveal();
728+
})
729+
->shouldBeCalled();
730+
731+
$localMachineHelper = $this->mockLocalMachineHelper();
732+
$this->mockExecuteMySqlConnect($localMachineHelper, true);
733+
$fs = $this->prophet->prophesize(Filesystem::class);
734+
$fs->remove(Argument::type('string'))->shouldBeCalled();
735+
$localMachineHelper->getFilesystem()->willReturn($fs->reveal())->shouldBeCalled();
736+
737+
$this->expectException(AcquiaCliException::class);
738+
$this->expectExceptionMessage('Database backup download failed with HTTP status 404');
739+
$this->executeCommand([
740+
'--no-scripts' => true,
741+
], self::inputChooseEnvironment());
742+
743+
self::unsetEnvVars(['AH_CODEBASE_UUID']);
744+
}
745+
746+
/**
747+
* Test that invalid HTTP responses (500) are caught and reported.
748+
*/
749+
public function testPullDatabaseWithInvalidHttpResponse500(): void
750+
{
751+
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
752+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
753+
754+
$this->mockCodebaseEnvironmentSetup($codebaseUuid);
755+
756+
// Mock 500 HTTP response.
757+
$response = $this->prophet->prophesize(ResponseInterface::class);
758+
$response->getStatusCode()->willReturn(500)->shouldBeCalled();
759+
760+
$siteInstanceDatabaseBackups = $this->getMockSiteInstanceDatabaseBackupsResponse();
761+
$downloadUrl = $siteInstanceDatabaseBackups->_embedded->items[0]->links->download->href ?? 'https://example.com/download-backup';
762+
763+
$this->httpClientProphecy
764+
->request(
765+
'GET',
766+
$downloadUrl,
767+
Argument::that(function (array $opts): bool {
768+
if (isset($opts['sink'])) {
769+
file_put_contents($opts['sink'], 'server error');
770+
}
771+
return isset($opts['sink']);
772+
})
773+
)
774+
->will(function () use ($response) {
775+
return $response->reveal();
776+
})
777+
->shouldBeCalled();
778+
779+
$localMachineHelper = $this->mockLocalMachineHelper();
780+
$this->mockExecuteMySqlConnect($localMachineHelper, true);
781+
$fs = $this->prophet->prophesize(Filesystem::class);
782+
$fs->remove(Argument::type('string'))->shouldBeCalled();
783+
$localMachineHelper->getFilesystem()->willReturn($fs->reveal())->shouldBeCalled();
784+
785+
$this->expectException(AcquiaCliException::class);
786+
$this->expectExceptionMessage('Database backup download failed with HTTP status 500');
787+
$this->executeCommand([
788+
'--no-scripts' => true,
789+
], self::inputChooseEnvironment());
790+
791+
self::unsetEnvVars(['AH_CODEBASE_UUID']);
792+
}
793+
794+
/**
795+
* Test that empty files are detected and rejected.
796+
*/
797+
public function testPullDatabaseWithEmptyFile(): void
798+
{
799+
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
800+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
801+
802+
$this->mockCodebaseEnvironmentSetup($codebaseUuid);
803+
804+
// Mock successful HTTP response but create empty file.
805+
$response = $this->prophet->prophesize(ResponseInterface::class);
806+
$response->getStatusCode()->willReturn(200)->shouldBeCalled();
807+
808+
$siteInstanceDatabaseBackups = $this->getMockSiteInstanceDatabaseBackupsResponse();
809+
$downloadUrl = $siteInstanceDatabaseBackups->_embedded->items[0]->links->download->href ?? 'https://example.com/download-backup';
810+
811+
$this->httpClientProphecy
812+
->request(
813+
'GET',
814+
$downloadUrl,
815+
Argument::that(function (array $opts): bool {
816+
// Create an empty file.
817+
if (isset($opts['sink'])) {
818+
file_put_contents($opts['sink'], '');
819+
}
820+
return isset($opts['sink']);
821+
})
822+
)
823+
->will(function () use ($response) {
824+
return $response->reveal();
825+
})
826+
->shouldBeCalled();
827+
828+
$localMachineHelper = $this->mockLocalMachineHelper();
829+
$this->mockExecuteMySqlConnect($localMachineHelper, true);
830+
$fs = $this->prophet->prophesize(Filesystem::class);
831+
$fs->remove(Argument::type('string'))->shouldBeCalled();
832+
$localMachineHelper->getFilesystem()->willReturn($fs->reveal())->shouldBeCalled();
833+
834+
$this->expectException(AcquiaCliException::class);
835+
$this->expectExceptionMessage('Database backup download failed or returned an invalid response');
836+
$this->executeCommand([
837+
'--no-scripts' => true,
838+
], self::inputChooseEnvironment());
839+
840+
self::unsetEnvVars(['AH_CODEBASE_UUID']);
841+
}
842+
843+
/**
844+
* Test that invalid gzip files are detected and rejected.
845+
*/
846+
public function testPullDatabaseWithInvalidGzipFile(): void
847+
{
848+
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
849+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
850+
851+
$this->mockCodebaseEnvironmentSetup($codebaseUuid);
852+
853+
// Mock successful HTTP response but create invalid gzip file.
854+
$response = $this->prophet->prophesize(ResponseInterface::class);
855+
$response->getStatusCode()->willReturn(200)->shouldBeCalled();
856+
857+
$siteInstanceDatabaseBackups = $this->getMockSiteInstanceDatabaseBackupsResponse();
858+
$downloadUrl = $siteInstanceDatabaseBackups->_embedded->items[0]->links->download->href ?? 'https://example.com/download-backup';
859+
860+
$this->httpClientProphecy
861+
->request(
862+
'GET',
863+
$downloadUrl,
864+
Argument::that(function (array $opts): bool {
865+
// Create a file with invalid gzip header.
866+
if (isset($opts['sink'])) {
867+
file_put_contents($opts['sink'], 'This is not a valid gzip file');
868+
}
869+
return isset($opts['sink']);
870+
})
871+
)
872+
->will(function () use ($response) {
873+
return $response->reveal();
874+
})
875+
->shouldBeCalled();
876+
877+
$localMachineHelper = $this->mockLocalMachineHelper();
878+
$this->mockExecuteMySqlConnect($localMachineHelper, true);
879+
$fs = $this->prophet->prophesize(Filesystem::class);
880+
$fs->remove(Argument::type('string'))->shouldBeCalled();
881+
$localMachineHelper->getFilesystem()->willReturn($fs->reveal())->shouldBeCalled();
882+
883+
$this->expectException(AcquiaCliException::class);
884+
$this->expectExceptionMessage('The downloaded file is not a valid gzip archive');
885+
$this->executeCommand([
886+
'--no-scripts' => true,
887+
], self::inputChooseEnvironment());
888+
889+
self::unsetEnvVars(['AH_CODEBASE_UUID']);
890+
}
891+
892+
/**
893+
* Test that files with too-small gzip headers are detected and rejected.
894+
*/
895+
public function testPullDatabaseWithTooSmallGzipFile(): void
896+
{
897+
$codebaseUuid = '11111111-041c-44c7-a486-7972ed2cafc8';
898+
self::SetEnvVars(['AH_CODEBASE_UUID' => $codebaseUuid]);
899+
900+
$this->mockCodebaseEnvironmentSetup($codebaseUuid);
901+
902+
// Mock successful HTTP response but create file with only 1 byte.
903+
$response = $this->prophet->prophesize(ResponseInterface::class);
904+
$response->getStatusCode()->willReturn(200)->shouldBeCalled();
905+
906+
$siteInstanceDatabaseBackups = $this->getMockSiteInstanceDatabaseBackupsResponse();
907+
$downloadUrl = $siteInstanceDatabaseBackups->_embedded->items[0]->links->download->href ?? 'https://example.com/download-backup';
908+
909+
$this->httpClientProphecy
910+
->request(
911+
'GET',
912+
$downloadUrl,
913+
Argument::that(function (array $opts): bool {
914+
// Create a file that's too small to have a valid header.
915+
if (isset($opts['sink'])) {
916+
file_put_contents($opts['sink'], 'X');
917+
}
918+
return isset($opts['sink']);
919+
})
920+
)
921+
->will(function () use ($response) {
922+
return $response->reveal();
923+
})
924+
->shouldBeCalled();
925+
926+
$localMachineHelper = $this->mockLocalMachineHelper();
927+
$this->mockExecuteMySqlConnect($localMachineHelper, true);
928+
$fs = $this->prophet->prophesize(Filesystem::class);
929+
$fs->remove(Argument::type('string'))->shouldBeCalled();
930+
$localMachineHelper->getFilesystem()->willReturn($fs->reveal())->shouldBeCalled();
931+
932+
$this->expectException(AcquiaCliException::class);
933+
$this->expectExceptionMessage('Database backup download failed: file is too small to be valid');
934+
$this->executeCommand([
935+
'--no-scripts' => true,
936+
], self::inputChooseEnvironment());
937+
938+
self::unsetEnvVars(['AH_CODEBASE_UUID']);
939+
}
940+
941+
/**
942+
* Helper method to mock codebase environment setup.
943+
*/
944+
private function mockCodebaseEnvironmentSetup(string $codebaseUuid): void
945+
{
946+
$codebase = $this->getMockCodeBaseResponse();
947+
$this->clientProphecy->request('get', '/codebases/' . $codebaseUuid)
948+
->willReturn($codebase);
949+
950+
$codebaseEnv = $this->getMockCodeBaseEnvironment();
951+
$this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/environments')
952+
->willReturn([$codebaseEnv])
953+
->shouldBeCalled();
954+
955+
$codeabaseSites = $this->getMockCodeBaseSites();
956+
$this->clientProphecy->request('get', '/codebases/' . $codebaseUuid . '/sites')
957+
->willReturn($codeabaseSites);
958+
959+
$siteInstance = $this->getMockSiteInstanceResponse();
960+
$this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc')
961+
->willReturn($siteInstance)
962+
->shouldBeCalled();
963+
964+
$siteId = '8979a8ac-80dc-4df8-b2f0-6be36554a370';
965+
$site = $this->getMockSite();
966+
$this->clientProphecy->request('get', '/sites/' . $siteId)
967+
->willReturn($site)
968+
->shouldBeCalled();
969+
970+
$siteInstanceDatabase = $this->getMockSiteInstanceDatabaseResponse();
971+
$this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database')
972+
->willReturn($siteInstanceDatabase)
973+
->shouldBeCalled();
974+
975+
$siteInstanceDatabaseBackups = $this->getMockSiteInstanceDatabaseBackupsResponse();
976+
$this->clientProphecy->request('get', '/site-instances/8979a8ac-80dc-4df8-b2f0-6be36554a370.3e8ecbec-ea7c-4260-8414-ef2938c859bc/database/backups')
977+
->willReturn($siteInstanceDatabaseBackups);
978+
}
640979
}

0 commit comments

Comments
 (0)