diff --git a/src/Extracting/Strategies/Responses/ResponseCalls.php b/src/Extracting/Strategies/Responses/ResponseCalls.php index 9d1929b5..29ef4364 100644 --- a/src/Extracting/Strategies/Responses/ResponseCalls.php +++ b/src/Extracting/Strategies/Responses/ResponseCalls.php @@ -5,7 +5,6 @@ use Exception; use Illuminate\Contracts\Http\Kernel; use Illuminate\Http\Request; -use Illuminate\Http\Response; use Illuminate\Http\UploadedFile; use Illuminate\Routing\Route; use Illuminate\Support\Facades\Config; @@ -18,6 +17,8 @@ use Knuckles\Scribe\Tools\ErrorHandlingUtils as e; use Knuckles\Scribe\Tools\Globals; use Knuckles\Scribe\Tools\Utils; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; /** * Make a call to the route and retrieve its response. @@ -89,7 +90,7 @@ public function makeResponseCall(ExtractedEndpointData $endpointData, array $set $response = [ [ 'status' => $response->getStatusCode(), - 'content' => $response->getContent(), + 'content' => $this->getContentFromResponse($response), 'headers' => $this->getResponseHeaders($response), ], ]; @@ -240,7 +241,7 @@ private function addBodyParameters(Request $request, array $body): void * * @param Route $route * - * @return \Symfony\Component\HttpFoundation\Response + * @return Response * @throws Exception */ protected function makeApiCall(Request $request, Route $route) @@ -248,7 +249,7 @@ protected function makeApiCall(Request $request, Route $route) return $this->callLaravelRoute($request); } - protected function callLaravelRoute(Request $request): \Symfony\Component\HttpFoundation\Response + protected function callLaravelRoute(Request $request): Response { /** @var \Illuminate\Foundation\Http\Kernel $kernel */ $kernel = app(Kernel::class); @@ -323,4 +324,26 @@ public static function withSettings( 'cookies', )); } + + protected function getContentFromResponse(Response $response): string|false + { + if (!$response instanceof StreamedResponse) { + return $response->getContent(); + } + + // For streamed responses, the content is null, and only output directly via "echo" when we call "sendContent". + // We use output buffering to capture the output into a new fake response. + $renderedResponse = new Response('', $response->getStatusCode()); + $originalCallback = $response->getCallback(); + $response->setCallback(function () use ($originalCallback, $renderedResponse) { + ob_start(function ($output) use ($renderedResponse) { + $renderedResponse->setContent($output); + }); + $originalCallback(); + ob_end_flush(); + }); + $response->sendContent(); + $renderedResponse->headers = $response->headers; + return $renderedResponse->getContent(); + } } diff --git a/tests/Fixtures/TestController.php b/tests/Fixtures/TestController.php index 68b95089..a901f193 100644 --- a/tests/Fixtures/TestController.php +++ b/tests/Fixtures/TestController.php @@ -254,6 +254,17 @@ public function shouldFetchRouteResponse() ]; } + public function withStreamedResponse() + { + function yieldItems() { + yield 'one'; + yield 'two'; + } + return response()->streamJson([ + 'items' => yieldItems(), + ]); + } + public function echoesConfig() { return [ diff --git a/tests/Strategies/Responses/ResponseCallsTest.php b/tests/Strategies/Responses/ResponseCallsTest.php index 2ee55cd1..70d68075 100644 --- a/tests/Strategies/Responses/ResponseCallsTest.php +++ b/tests/Strategies/Responses/ResponseCallsTest.php @@ -172,6 +172,22 @@ public function does_not_make_response_call_if_success_response_already_gotten() $this->assertNull($responses); } + /** @test */ + public function can_get_content_from_streamed_response() + { + $route = LaravelRouteFacade::post('/withStreamedResponse', [TestController::class, 'withStreamedResponse']); + + $responses = $this->invokeStrategy($route); + + $this->assertEquals(200, $responses[0]['status']); + $this->assertArraySubset([ + 'items' => [ + 'one', + 'two', + ] + ], json_decode($responses[0]['content'], true)); + } + protected function convertRules(array $rules): mixed { return Extractor::transformOldRouteRulesIntoNewSettings('responses', $rules, ResponseCalls::class);