diff --git a/composer.lock b/composer.lock index f9d9dc4f3..1dc99b8da 100644 --- a/composer.lock +++ b/composer.lock @@ -1310,16 +1310,16 @@ }, { "name": "laminas/laminas-validator", - "version": "2.51.0", + "version": "2.53.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-validator.git", - "reference": "25cc42efd096352f4dcfcf55dfb00665a1b29aaf" + "reference": "dbcfc19cb7f2e3eb3a27ba5d059c200e8404d72c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/25cc42efd096352f4dcfcf55dfb00665a1b29aaf", - "reference": "25cc42efd096352f4dcfcf55dfb00665a1b29aaf", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/dbcfc19cb7f2e3eb3a27ba5d059c200e8404d72c", + "reference": "dbcfc19cb7f2e3eb3a27ba5d059c200e8404d72c", "shasum": "" }, "require": { @@ -1336,13 +1336,13 @@ "laminas/laminas-db": "^2.19", "laminas/laminas-filter": "^2.34", "laminas/laminas-i18n": "^2.26.0", - "laminas/laminas-session": "^2.18", + "laminas/laminas-session": "^2.20", "laminas/laminas-uri": "^2.11.0", - "phpunit/phpunit": "^10.5.10", - "psalm/plugin-phpunit": "^0.18.4", + "phpunit/phpunit": "^10.5.15", + "psalm/plugin-phpunit": "^0.19.0", "psr/http-client": "^1.0.3", "psr/http-factory": "^1.0.2", - "vimeo/psalm": "^5.22.1" + "vimeo/psalm": "^5.23.1" }, "suggest": { "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", @@ -1390,7 +1390,7 @@ "type": "community_bridge" } ], - "time": "2024-03-16T03:34:49+00:00" + "time": "2024-04-01T09:26:32+00:00" }, { "name": "league/csv", @@ -10602,16 +10602,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.65", + "version": "1.10.66", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3c657d057a0b7ecae19cb12db446bbc99d8839c6" + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3c657d057a0b7ecae19cb12db446bbc99d8839c6", - "reference": "3c657d057a0b7ecae19cb12db446bbc99d8839c6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd", + "reference": "94779c987e4ebd620025d9e5fdd23323903950bd", "shasum": "" }, "require": { @@ -10660,7 +10660,7 @@ "type": "tidelift" } ], - "time": "2024-03-23T10:30:26+00:00" + "time": "2024-03-28T16:17:31+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -12645,16 +12645,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.0", + "version": "3.9.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + "reference": "267a4405fff1d9c847134db3a3c92f1ab7f77909" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/267a4405fff1d9c847134db3a3c92f1ab7f77909", + "reference": "267a4405fff1d9c847134db3a3c92f1ab7f77909", "shasum": "" }, "require": { @@ -12721,7 +12721,7 @@ "type": "open_collective" } ], - "time": "2024-02-16T15:06:51+00:00" + "time": "2024-03-31T21:03:09+00:00" }, { "name": "symfony/css-selector", diff --git a/src/Command/Api/ApiBaseCommand.php b/src/Command/Api/ApiBaseCommand.php index e75f65ce3..8baebf8fe 100644 --- a/src/Command/Api/ApiBaseCommand.php +++ b/src/Command/Api/ApiBaseCommand.php @@ -91,8 +91,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $acquiaCloudClient = $this->cloudApiClientService->getClient(); $this->addQueryParamsToClient($input, $acquiaCloudClient); $this->addPostParamsToClient($input, $acquiaCloudClient); + // Acquia PHP SDK cannot set the Accept header itself because it would break + // API calls returning octet streams (e.g., db backups). It's safe to use + // here because the API command should always return JSON. $acquiaCloudClient->addOption('headers', [ - 'Accept' => 'application/json', + 'Accept' => 'application/hal+json, version=2', ]); try { diff --git a/src/Command/Api/ApiCommandHelper.php b/src/Command/Api/ApiCommandHelper.php index f9570dd3e..9611a2278 100644 --- a/src/Command/Api/ApiCommandHelper.php +++ b/src/Command/Api/ApiCommandHelper.php @@ -75,10 +75,6 @@ private function addApiCommandParameters(array $schema, array $acquiaCloudSpec, elseif ($parameterSpecification['in'] === 'path') { $command->addPathParameter($parameterDefinition->getName(), $parameterSpecification); } - // @todo Remove this! It is a workaround for CLI-769. - elseif ($parameterSpecification['in'] === 'header') { - $command->addPostParameter($parameterDefinition->getName(), $parameterSpecification); - } } $usage .= $queryParamUsageSuffix; $inputDefinition = array_merge($inputDefinition, $queryInputDefinition); @@ -484,16 +480,16 @@ private function generateApiListCommands(array $apiCommands, string $commandPref } /** - * @param $requestBody + * @param array $requestBody * @return array */ - private function getRequestBodyContent(mixed $requestBody): array { + private function getRequestBodyContent(array $requestBody): array { $content = $requestBody['content']; $knownContentTypes = [ + 'application/hal+json', 'application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', - 'application/hal+json', ]; foreach ($knownContentTypes as $contentType) { if (array_key_exists($contentType, $content)) { diff --git a/src/Command/Auth/AuthAcsfLoginCommand.php b/src/Command/Auth/AuthAcsfLoginCommand.php index 2fc7518ac..25d865812 100644 --- a/src/Command/Auth/AuthAcsfLoginCommand.php +++ b/src/Command/Auth/AuthAcsfLoginCommand.php @@ -18,7 +18,7 @@ protected function configure(): void { $this ->addOption('username', 'u', InputOption::VALUE_REQUIRED, "Your Site Factory username") ->addOption('key', 'k', InputOption::VALUE_REQUIRED, "Your Site Factory key") - ->addOption('factory-url', 'f', InputOption::VALUE_REQUIRED, "Your Site Factory URL"); + ->addOption('factory-url', 'f', InputOption::VALUE_REQUIRED, "Your Site Factory URL (including https://)"); } protected function execute(InputInterface $input, OutputInterface $output): int { diff --git a/tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php b/tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php index e10016684..683ad0425 100644 --- a/tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php +++ b/tests/phpunit/src/Commands/Acsf/AcsfApiCommandTest.php @@ -25,7 +25,7 @@ class AcsfApiCommandTest extends AcsfCommandTestBase { public function setUp(): void { parent::setUp(); - $this->clientProphecy->addOption('headers', ['Accept' => 'application/json']); + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2']); putenv('ACQUIA_CLI_USE_CLOUD_API_SPEC_CACHE=1'); } diff --git a/tests/phpunit/src/Commands/Api/ApiCommandTest.php b/tests/phpunit/src/Commands/Api/ApiCommandTest.php index 9744a31be..37162470d 100644 --- a/tests/phpunit/src/Commands/Api/ApiCommandTest.php +++ b/tests/phpunit/src/Commands/Api/ApiCommandTest.php @@ -21,7 +21,6 @@ class ApiCommandTest extends CommandTestBase { public function setUp(): void { parent::setUp(); - $this->clientProphecy->addOption('headers', ['Accept' => 'application/json']); putenv('ACQUIA_CLI_USE_CLOUD_API_SPEC_CACHE=1'); } @@ -79,6 +78,7 @@ public function testArgumentsInteractionValidationFormat(): void { */ public function testApiCommandErrorResponse(): void { $invalidUuid = '257a5440-22c3-49d1-894d-29497a1cf3b9'; + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $this->command = $this->getApiCommandByName('api:applications:find'); $mockBody = $this->getMockResponseFromSpec($this->command->getPath(), $this->command->getMethod(), '404'); $this->clientProphecy->request('get', '/applications/' . $invalidUuid)->willThrow(new ApiErrorException($mockBody))->shouldBeCalled(); @@ -104,6 +104,7 @@ public function testApiCommandErrorResponse(): void { } public function testApiCommandExecutionForHttpGet(): void { + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $mockBody = $this->getMockResponseFromSpec('/account/ssh-keys', 'get', '200'); $this->clientProphecy->addQuery('limit', '1')->shouldBeCalled(); $this->clientProphecy->request('get', '/account/ssh-keys')->willReturn($mockBody->{'_embedded'}->items)->shouldBeCalled(); @@ -136,6 +137,7 @@ public function testObjectParam(): void { } public function testInferApplicationUuidArgument(): void { + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $applications = $this->mockRequest('getApplications'); $application = $this->mockRequest('getApplicationByUuid', $applications[0]->uuid); $this->command = $this->getApiCommandByName('api:applications:find'); @@ -171,6 +173,7 @@ public function providerTestConvertApplicationAliasToUuidArgument(): array { */ public function testConvertApplicationAliasToUuidArgument(bool $support): void { ClearCacheCommand::clearCaches(); + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $tamper = function (&$response): void { unset($response[1]); }; @@ -232,7 +235,11 @@ public function testConvertNonUniqueApplicationAliasToUuidArgument(): void { } + /** + * @serial + */ public function testConvertApplicationAliasWithRealmToUuidArgument(): void { + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $this->mockApplicationsRequest(1, FALSE); $this->clientProphecy->addQuery('filter', 'hosting=@devcloud:devcloud2')->shouldBeCalled(); $this->mockApplicationRequest(); @@ -248,6 +255,7 @@ public function testConvertApplicationAliasWithRealmToUuidArgument(): void { */ public function testConvertEnvironmentAliasToUuidArgument(): void { ClearCacheCommand::clearCaches(); + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $applicationsResponse = $this->mockApplicationsRequest(1); $this->clientProphecy->addQuery('filter', 'hosting=@*:devcloud2')->shouldBeCalled(); $this->mockEnvironmentsRequest($applicationsResponse); @@ -291,6 +299,7 @@ public function testConvertInvalidEnvironmentAliasToUuidArgument(): void { } public function testApiCommandExecutionForHttpPost(): void { + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $mockRequestArgs = $this->getMockRequestBodyFromSpec('/account/ssh-keys'); $mockResponseBody = $this->getMockResponseFromSpec('/account/ssh-keys', 'post', '202'); foreach ($mockRequestArgs as $name => $value) { @@ -308,6 +317,7 @@ public function testApiCommandExecutionForHttpPost(): void { } public function testApiCommandExecutionForHttpPut(): void { + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $mockRequestOptions = $this->getMockRequestBodyFromSpec('/environments/{environmentId}', 'put'); $mockRequestOptions['max_input_vars'] = 1001; $mockResponseBody = $this->getMockEnvironmentResponse('put', '202'); @@ -416,6 +426,7 @@ public function testApiCommandDefinitionRequestBody(string $commandName, string } public function testGetApplicationUuidFromBltYml(): void { + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $mockBody = $this->getMockResponseFromSpec('/applications/{applicationUuid}', 'get', '200'); $this->clientProphecy->request('get', '/applications/' . $mockBody->uuid)->willReturn($mockBody)->shouldBeCalled(); $this->command = $this->getApiCommandByName('api:applications:find'); @@ -431,6 +442,7 @@ public function testOrganizationMemberDeleteByUserUuid(): void { $orgId = 'bfafd31a-83a6-4257-b0ec-afdeff83117a'; $memberUuid = '26c4af83-545b-45cb-b165-d537adc9e0b4'; + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $this->mockRequest('postOrganizationMemberDelete', [$orgId, $memberUuid], NULL, 'Member removed'); $this->command = $this->getApiCommandByName('api:organizations:member-delete'); @@ -450,6 +462,7 @@ public function testOrganizationMemberDeleteByUserUuid(): void { * Test of deletion of the user from organization by user email. */ public function testOrganizationMemberDeleteByUserEmail(): void { + $this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])->shouldBeCalled(); $membersResponse = $this->getMockResponseFromSpec('/organizations/{organizationUuid}/members', 'get', 200); $orgId = 'bfafd31a-83a6-4257-b0ec-afdeff83117a'; $memberMail = $membersResponse->_embedded->items[0]->mail;