Skip to content

Commit 14d2fd5

Browse files
committed
[HttpClient] Fix handling thrown \Exception in \Generator in MockResponse
1 parent f13c1e2 commit 14d2fd5

File tree

2 files changed

+61
-11
lines changed

2 files changed

+61
-11
lines changed

Response/MockResponse.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class MockResponse implements ResponseInterface
3838
/**
3939
* @param string|string[]|iterable $body The response body as a string or an iterable of strings,
4040
* yielding an empty string simulates an idle timeout,
41-
* exceptions are turned to TransportException
41+
* throwing an exception yields an ErrorChunk
4242
*
4343
* @see ResponseInterface::getInfo() for possible info, e.g. "response_headers"
4444
*/
@@ -183,6 +183,9 @@ protected static function perform(ClientState $multi, array &$responses): void
183183
$multi->handlesActivity[$id][] = null;
184184
$multi->handlesActivity[$id][] = $e;
185185
}
186+
} elseif ($chunk instanceof \Throwable) {
187+
$multi->handlesActivity[$id][] = null;
188+
$multi->handlesActivity[$id][] = $chunk;
186189
} else {
187190
// Data or timeout chunk
188191
$multi->handlesActivity[$id][] = $chunk;
@@ -275,16 +278,20 @@ private static function readResponse(self $response, array $options, ResponseInt
275278
$body = $mock instanceof self ? $mock->body : $mock->getContent(false);
276279

277280
if (!\is_string($body)) {
278-
foreach ($body as $chunk) {
279-
if ('' === $chunk = (string) $chunk) {
280-
// simulate an idle timeout
281-
$response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url']));
282-
} else {
283-
$response->body[] = $chunk;
284-
$offset += \strlen($chunk);
285-
// "notify" download progress
286-
$onProgress($offset, $dlSize, $response->info);
281+
try {
282+
foreach ($body as $chunk) {
283+
if ('' === $chunk = (string) $chunk) {
284+
// simulate an idle timeout
285+
$response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url']));
286+
} else {
287+
$response->body[] = $chunk;
288+
$offset += \strlen($chunk);
289+
// "notify" download progress
290+
$onProgress($offset, $dlSize, $response->info);
291+
}
287292
}
293+
} catch (\Throwable $e) {
294+
$response->body[] = $e;
288295
}
289296
} elseif ('' !== $body) {
290297
$response->body[] = $body;

Tests/MockHttpClientTest.php

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace Symfony\Component\HttpClient\Tests;
1313

14+
use Symfony\Component\HttpClient\Chunk\DataChunk;
15+
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
16+
use Symfony\Component\HttpClient\Chunk\FirstChunk;
1417
use Symfony\Component\HttpClient\Exception\TransportException;
1518
use Symfony\Component\HttpClient\MockHttpClient;
1619
use Symfony\Component\HttpClient\NativeHttpClient;
@@ -63,6 +66,46 @@ public function invalidResponseFactoryProvider()
6366
];
6467
}
6568

69+
public function testThrowExceptionInBodyGenerator()
70+
{
71+
$mockHttpClient = new MockHttpClient([
72+
new MockResponse((static function (): \Generator {
73+
yield 'foo';
74+
throw new TransportException('foo ccc');
75+
})()),
76+
new MockResponse((static function (): \Generator {
77+
yield 'bar';
78+
throw new \RuntimeException('bar ccc');
79+
})()),
80+
]);
81+
82+
try {
83+
$mockHttpClient->request('GET', 'https://symfony.com', [])->getContent();
84+
$this->fail();
85+
} catch (TransportException $e) {
86+
$this->assertEquals(new TransportException('foo ccc'), $e->getPrevious());
87+
$this->assertSame('foo ccc', $e->getMessage());
88+
}
89+
90+
$chunks = [];
91+
try {
92+
foreach ($mockHttpClient->stream($mockHttpClient->request('GET', 'https://symfony.com', [])) as $chunk) {
93+
$chunks[] = $chunk;
94+
}
95+
$this->fail();
96+
} catch (TransportException $e) {
97+
$this->assertEquals(new \RuntimeException('bar ccc'), $e->getPrevious());
98+
$this->assertSame('bar ccc', $e->getMessage());
99+
}
100+
101+
$this->assertCount(3, $chunks);
102+
$this->assertEquals(new FirstChunk(0, ''), $chunks[0]);
103+
$this->assertEquals(new DataChunk(0, 'bar'), $chunks[1]);
104+
$this->assertInstanceOf(ErrorChunk::class, $chunks[2]);
105+
$this->assertSame(3, $chunks[2]->getOffset());
106+
$this->assertSame('bar ccc', $chunks[2]->getError());
107+
}
108+
66109
protected function getHttpClient(string $testCase): HttpClientInterface
67110
{
68111
$responses = [];
@@ -167,7 +210,7 @@ protected function getHttpClient(string $testCase): HttpClientInterface
167210
case 'testResolve':
168211
$responses[] = new MockResponse($body, ['response_headers' => $headers]);
169212
$responses[] = new MockResponse($body, ['response_headers' => $headers]);
170-
$responses[] = new MockResponse((function () { throw new \Exception('Fake connection timeout'); yield ''; })(), ['response_headers' => $headers]);
213+
$responses[] = new MockResponse((function () { yield ''; })(), ['response_headers' => $headers]);
171214
break;
172215

173216
case 'testTimeoutOnStream':

0 commit comments

Comments
 (0)