Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Command/Api/ApiBaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$acquiaCloudClient->addOption('headers', [
'Accept' => 'application/hal+json, version=2',
]);
// Some POST endpoints have no request body (e.g. schedule-delete). The
// Cloud Platform API requires both a Content-Type header and a body for
// all POST requests. Guzzle's `json` option satisfies both; passing an
// empty object sends `{}` with Content-Type: application/json.
// PUT/PATCH endpoints with no body (e.g. site-instances:domain:add) must
// not send `{}`: Guzzle creates a non-seekable stream which triggers
// `curl_setopt_array(): Stream does not support seeking`.
if (strtoupper($this->method) === 'POST' && empty($this->postParams)) {
$acquiaCloudClient->addOption('json', new \stdClass());
}

try {
if ($this->output->isVeryVerbose()) {
Expand Down
42 changes: 42 additions & 0 deletions tests/phpunit/src/Commands/Api/ApiCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,48 @@ public function testTrustedProxiesCommandExecutesHttpGet(): void
$this->assertEquals(0, $this->getStatusCode());
}

/**
* A body-less POST (no requestBody in the spec) must send an empty JSON
* object so the Cloud Platform API receives both a Content-Type header and
* a body, preventing HTTP 415 / HTTP 400 errors.
*/
public function testBodylessPostSendsEmptyJsonBody(): void
{
$agreementUuid = 'da1c0a8e-ff69-45db-88fc-acd6d2affbb7';
$this->mockRequest('postAcceptAgreement', $agreementUuid);
$this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])
->shouldBeCalled();
$this->clientProphecy->addOption('json', new \stdClass())
->shouldBeCalled();
$this->command = $this->getApiCommandByName('api:agreements:accept');
$this->executeCommand(['agreementUuid' => $agreementUuid]);
$this->assertEquals(0, $this->getStatusCode());
}

/**
* A body-less PUT (requestBody with no properties, required: false) must
* NOT send `{}`. Guzzle creates a non-seekable stream for the empty object
* which triggers `curl_setopt_array(): Stream does not support seeking`.
*/
public function testBodylessPutDoesNotSendEmptyJsonBody(): void
{
$siteId = '3e8ecbec-ea7c-4260-8414-ef2938c859bc';
$environmentId = 'd3f7270e-c45f-4801-9308-5e8afe84a323';
$domainName = 'example.com';
$this->mockRequest('add_domain_to_site', [$siteId, $environmentId, $domainName]);
$this->clientProphecy->addOption('headers', ['Accept' => 'application/hal+json, version=2'])
->shouldBeCalled();
$this->clientProphecy->addOption('json', new \stdClass())
->shouldNotBeCalled();
$this->command = $this->getApiCommandByName('api:site-instances:domain:add');
$this->executeCommand([
'domainName' => $domainName,
'environmentId' => $environmentId,
'siteId' => $siteId,
]);
$this->assertEquals(0, $this->getStatusCode());
}

/**
* oneOf entries that lack a 'type' key (e.g. unresolved $ref) must be
* skipped silently rather than throwing an "Undefined array key" warning.
Expand Down
Loading