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
19 changes: 2 additions & 17 deletions src/Transport/HttpTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,24 +174,9 @@ public function send(JsonRpcMessage|array|null $messageOrPayload): void
// it might result in a parsing error on the client if it's not valid JSON-RPC (e.g. just "null").
// For now, allow encoding null, which results in JSON "null".
$payloadToEncode = null;
} else {
// Should not be reached due to type hints. Internal error if it does.
// Prepare a JSON-RPC error response.
$errorPayload = [
'jsonrpc' => '2.0',
'error' => [
'code' => JsonRpcMessage::INTERNAL_ERROR,
'message' => 'Server error: Invalid message type for sending.'
],
'id' => null
];
$encodedErrorString = json_encode($errorPayload);
$this->response = $this->responseFactory->createResponse(500)
->withHeader('Content-Type', 'application/json')
->withBody($this->streamFactory->createStream($encodedErrorString !== false ? $encodedErrorString : '{"jsonrpc":"2.0","error":{"code":-32000,"message":"Server JSON encoding error"}}'));
$this->responsePrepared = true;
return;
}
// The 'else' block that handled non-JsonRpcMessage/array/null types has been removed
// as it was deemed unreachable due to PHP parameter type hints.

$jsonPayloadString = json_encode($payloadToEncode);

Expand Down
84 changes: 84 additions & 0 deletions tests/Transport/HttpTransportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,88 @@ public function testSendHandlesJsonEncodeFailureSimplified(): void
$this->assertEquals(JsonRpcMessage::INTERNAL_ERROR, $decodedBody['error']['code']);
$this->assertStringContainsString('Failed to encode JSON response', $decodedBody['error']['message']);
}

/**
* @dataProvider invalidBatchItemProvider
* @param list<mixed> $invalidBatchPayload
*/
public function testReceiveBatchWithInvalidItemStructure(array $invalidBatchPayload, string $caseName): void
{
$this->expectException(TransportException::class);
$this->expectExceptionCode(JsonRpcMessage::INVALID_REQUEST);
$this->expectExceptionMessage('Invalid JSON-RPC: Request must be a JSON object or an array of JSON objects.');

$jsonRpcString = json_encode($invalidBatchPayload);

$this->mockRequest->method('getMethod')->willReturn('POST');
$this->mockRequest->method('getHeaderLine')->with('Content-Type')->willReturn('application/json');

// Configure the mockStream instance that was created in setUp()
// getBody() on mockRequest already returns $this->mockStream from setUp.
$this->mockStream->method('__toString')->willReturn($jsonRpcString);

$transport = $this->createTransport();
$transport->receive();
}

/**
* @return array<string, array{list<mixed>, string}>
*/
public static function invalidBatchItemProvider(): array
{
// Using the case name as part of the dataset helps in identifying the failing case from test output.
return [
'string_in_batch' => [[['method' => 'foo'], "not_an_object"], 'string_in_batch'],
'array_in_batch' => [[['method' => 'foo'], [1, 2, 3]], 'array_in_batch'],
];
}

public function testIsStreamOpenReturnsFalse(): void
{
$transport = $this->createTransport();
$this->assertFalse($transport->isStreamOpen());
}

public function testIsClosedInitialState(): void
{
$transport = $this->createTransport();
$this->assertFalse($transport->isClosed(), "isClosed() should be false before send() is called.");
}

public function testIsClosedAfterSend(): void
{
$transport = $this->createTransport();
$transport->send(null); // Call send() to change the state
$this->assertTrue($transport->isClosed(), "isClosed() should be true after send() is called.");
}

public function testGetResponseWhenResponseIsNull(): void
{
$transport = $this->createTransport();

// Use Reflection to set the private $response property to null
$reflection = new \ReflectionClass(HttpTransport::class);
$responseProperty = $reflection->getProperty('response');
$responseProperty->setAccessible(true); // Allow modification of private property
$responseProperty->setValue($transport, null);

$response = $transport->getResponse();

$this->assertInstanceOf(ResponseInterface::class, $response);
$this->assertEquals(500, $response->getStatusCode());
$this->assertStringContainsString('application/json', $response->getHeaderLine('Content-Type'));

$expectedErrorPayload = [
'jsonrpc' => '2.0',
'error' => [
'code' => JsonRpcMessage::INTERNAL_ERROR, // -32603
'message' => 'Response not prepared or available.'
],
'id' => null
];
$this->assertJsonStringEqualsJsonString(
json_encode($expectedErrorPayload, JSON_THROW_ON_ERROR),
(string) $response->getBody()
);
}
}