Skip to content

Commit e0b240c

Browse files
committed
fix(api-call): read full response bodies before decoding
- switch api responses to string-cast body reads so drained streams from middleware still decode correctly - update retry tests to mock string-castable response streams
1 parent 0b9ac2f commit e0b240c

2 files changed

Lines changed: 20 additions & 32 deletions

File tree

src/ApiCall.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,7 @@ private function makeRequest(string $method, string $endPoint, bool $asJson, arr
256256
$this->setNodeHealthCheck($node, true);
257257
}
258258

259-
$responseContents = $response->getBody()
260-
->getContents();
259+
$responseContents = (string) $response->getBody();
261260

262261
if (!(200 <= $statusCode && $statusCode < 300)) {
263262
try {

tests/Feature/ApiCallRetryTest.php

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818

1919
class ApiCallRetryTest extends TestCase
2020
{
21+
private function createJsonResponseMock(string $json, int $statusCode = 200): ResponseInterface
22+
{
23+
$stream = $this->createMock(StreamInterface::class);
24+
$stream->method('__toString')->willReturn($json);
25+
$stream->method('getContents')->willReturn($json);
26+
27+
$response = $this->createMock(ResponseInterface::class);
28+
$response->method('getStatusCode')->willReturn($statusCode);
29+
$response->method('getBody')->willReturn($stream);
30+
31+
return $response;
32+
}
33+
2134
public function testRetriesOnHttpExceptionWithNon408Status(): void
2235
{
2336
$callCount = 0;
@@ -33,12 +46,7 @@ public function testRetriesOnHttpExceptionWithNon408Status(): void
3346
$response->method('getStatusCode')->willReturn(500);
3447
throw new HttpException('Server error', $this->createMock(RequestInterface::class), $response);
3548
} else {
36-
$response = $this->createMock(ResponseInterface::class);
37-
$response->method('getStatusCode')->willReturn(200);
38-
$stream = $this->createMock(StreamInterface::class);
39-
$stream->method('getContents')->willReturn('{"success": true}');
40-
$response->method('getBody')->willReturn($stream);
41-
return $response;
49+
return $this->createJsonResponseMock('{"success": true}');
4250
}
4351
});
4452

@@ -109,12 +117,7 @@ public function testRetriesOnTypesenseClientError(): void
109117
if ($callCount < $expectedCalls) {
110118
throw new RequestMalformed('Bad request');
111119
} else {
112-
$response = $this->createMock(ResponseInterface::class);
113-
$response->method('getStatusCode')->willReturn(200);
114-
$stream = $this->createMock(StreamInterface::class);
115-
$stream->method('getContents')->willReturn('{"success": true}');
116-
$response->method('getBody')->willReturn($stream);
117-
return $response;
120+
return $this->createJsonResponseMock('{"success": true}');
118121
}
119122
});
120123

@@ -152,12 +155,7 @@ public function testRetriesOnHttpClientException(): void
152155
if ($callCount < $expectedCalls) {
153156
throw new TransferException('Connection error');
154157
} else {
155-
$response = $this->createMock(ResponseInterface::class);
156-
$response->method('getStatusCode')->willReturn(200);
157-
$stream = $this->createMock(StreamInterface::class);
158-
$stream->method('getContents')->willReturn('{"success": true}');
159-
$response->method('getBody')->willReturn($stream);
160-
return $response;
158+
return $this->createJsonResponseMock('{"success": true}');
161159
}
162160
});
163161

@@ -198,12 +196,7 @@ public function testSkips408TimeoutErrorsAndContinuesRetrying(): void
198196
$response->method('getStatusCode')->willReturn(500);
199197
throw new HttpException('Server error', $this->createMock(RequestInterface::class), $response);
200198
} else {
201-
$response = $this->createMock(ResponseInterface::class);
202-
$response->method('getStatusCode')->willReturn(200);
203-
$stream = $this->createMock(StreamInterface::class);
204-
$stream->method('getContents')->willReturn('{"success": true}');
205-
$response->method('getBody')->willReturn($stream);
206-
return $response;
199+
return $this->createJsonResponseMock('{"success": true}');
207200
}
208201
});
209202

@@ -384,12 +377,7 @@ public function test408ErrorsAreSkippedAndRetryingContinues(): void
384377
$response->method('getStatusCode')->willReturn(408);
385378
throw new HttpException('Request timeout', $this->createMock(RequestInterface::class), $response);
386379
} else {
387-
$response = $this->createMock(ResponseInterface::class);
388-
$response->method('getStatusCode')->willReturn(200);
389-
$stream = $this->createMock(StreamInterface::class);
390-
$stream->method('getContents')->willReturn('{"success": true}');
391-
$response->method('getBody')->willReturn($stream);
392-
return $response;
380+
return $this->createJsonResponseMock('{"success": true}');
393381
}
394382
});
395383

@@ -475,6 +463,7 @@ public function testThrowsTypesenseClientErrorWhenSuccessResponseContainsInvalid
475463
$response = $this->createMock(ResponseInterface::class);
476464
$response->method('getStatusCode')->willReturn(200);
477465
$stream = $this->createMock(StreamInterface::class);
466+
$stream->method('__toString')->willReturn('{invalid json');
478467
$stream->method('getContents')->willReturn('{invalid json');
479468
$response->method('getBody')->willReturn($stream);
480469
return $response;

0 commit comments

Comments
 (0)