diff --git a/src/Core/BaseClient.php b/src/Core/BaseClient.php index 4992675c1..c3f44f8eb 100644 --- a/src/Core/BaseClient.php +++ b/src/Core/BaseClient.php @@ -146,6 +146,13 @@ protected function buildRequest( ...$idempotencyHeaders, ]; + // Generated methods always pass `body` for operations that define a request + // body, so only elide the content-type header when the operation has no body + // at all, not when an optional body param was omitted. + if (null === $body && !array_key_exists('Content-Type', $headers)) { + unset($mergedHeaders['Content-Type']); + } + $req = ['method' => strtoupper($method), 'path' => $uri, 'query' => $mergedQuery, 'headers' => $mergedHeaders, 'body' => $body]; return [$req, $options]; diff --git a/src/Services/WebhooksRawService.php b/src/Services/WebhooksRawService.php index 9912850a9..7a407bbdc 100644 --- a/src/Services/WebhooksRawService.php +++ b/src/Services/WebhooksRawService.php @@ -263,7 +263,6 @@ public function delete( $params, $requestOptions, ); - // @phpstan-ignore-next-line return.type return $this->client->request( method: 'delete', diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 907a6782f..12ad023f9 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -41,4 +41,93 @@ public function testDefaultHeaders(): void $this->assertNotEmpty($sent); } } + + public function testBodylessDeleteOmitsContentType(): void + { + $transporter = new Client; + $transporter->setDefaultResponse( + Psr17FactoryDiscovery::findResponseFactory()->createResponse()->withStatus(204) + ); + + $client = new \SentDm\Client( + baseUrl: 'http://localhost', + apiKey: 'My API Key', + requestOptions: ['transporter' => $transporter], + ); + + $client->webhooks->delete('d4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8'); + + $this->assertNotFalse($requested = $transporter->getRequests()[0] ?? false); + $this->assertSame('DELETE', $requested->getMethod()); + $this->assertSame('', $requested->getHeaderLine('content-type')); + $this->assertSame('', (string) $requested->getBody()); + } + + public function testBodylessGetOmitsContentType(): void + { + $transporter = new Client; + $mockRsp = Psr17FactoryDiscovery::findResponseFactory() + ->createResponse() + ->withStatus(200) + ->withHeader('Content-Type', 'application/json') + ->withBody(Psr17FactoryDiscovery::findStreamFactory()->createStream(json_encode([], flags: Util::JSON_ENCODE_FLAGS) ?: '')) + ; + $transporter->setDefaultResponse($mockRsp); + + $client = new \SentDm\Client( + baseUrl: 'http://localhost', + apiKey: 'My API Key', + requestOptions: ['transporter' => $transporter], + ); + + $client->messages->retrieveStatus('msg_123'); + + $this->assertNotFalse($requested = $transporter->getRequests()[0] ?? false); + $this->assertSame('GET', $requested->getMethod()); + $this->assertSame('', $requested->getHeaderLine('content-type')); + } + + public function testDeleteWithExplicitBodyKeepsContentType(): void + { + $transporter = new Client; + $transporter->setDefaultResponse( + Psr17FactoryDiscovery::findResponseFactory()->createResponse()->withStatus(204) + ); + + $client = new \SentDm\Client( + baseUrl: 'http://localhost', + apiKey: 'My API Key', + requestOptions: ['transporter' => $transporter], + ); + + $client->profiles->delete('770e8400-e29b-41d4-a716-446655440002'); + + $this->assertNotFalse($requested = $transporter->getRequests()[0] ?? false); + $this->assertSame('DELETE', $requested->getMethod()); + $this->assertSame('application/json', $requested->getHeaderLine('content-type')); + $this->assertSame('{}', (string) $requested->getBody()); + } + + public function testBodylessRequestKeepsAnExplicitContentTypeOverride(): void + { + $transporter = new Client; + $transporter->setDefaultResponse( + Psr17FactoryDiscovery::findResponseFactory()->createResponse()->withStatus(204) + ); + + $client = new \SentDm\Client( + baseUrl: 'http://localhost', + apiKey: 'My API Key', + requestOptions: ['transporter' => $transporter], + ); + + $client->request( + method: 'delete', + path: 'v3/webhooks/d4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8', + headers: ['Content-Type' => 'application/json'], + ); + + $this->assertNotFalse($requested = $transporter->getRequests()[0] ?? false); + $this->assertSame('application/json', $requested->getHeaderLine('content-type')); + } }