diff --git a/src/Response.php b/src/Response.php index 411c43b..d673034 100644 --- a/src/Response.php +++ b/src/Response.php @@ -9,6 +9,11 @@ final readonly class Response implements Responses { + public static function from(Code $code, mixed $body, Headers ...$headers): ResponseInterface + { + return InternalResponse::createWithBody($body, $code, ...$headers); + } + public static function ok(mixed $body, Headers ...$headers): ResponseInterface { return InternalResponse::createWithBody($body, Code::OK, ...$headers); diff --git a/src/Responses.php b/src/Responses.php index 9ee1392..73b257e 100644 --- a/src/Responses.php +++ b/src/Responses.php @@ -13,6 +13,16 @@ */ interface Responses { + /** + * Creates a response with the specified status code, body, and headers. + * + * @param Code $code The HTTP status code for the response. + * @param mixed $body The body of the response. + * @param Headers ...$headers Optional additional headers for the response. + * @return ResponseInterface The generated response with the specified status code, body, and headers. + */ + public static function from(Code $code, mixed $body, Headers ...$headers): ResponseInterface; + /** * Creates a response with a 200 OK status. * diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 04b24f6..db68e94 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -22,6 +22,31 @@ final class ResponseTest extends TestCase { + #[DataProvider('responseFromProvider')] + public function testResponseFrom(Code $code, mixed $body, string $expectedBody): void + { + /** @Given a specific status code and body */ + /** @When we create the HTTP response using the generic from method */ + $actual = Response::from(code: $code, body: $body); + + /** @Then the protocol version should be "1.1" */ + self::assertSame('1.1', $actual->getProtocolVersion()); + + /** @And the body of the response should match the expected output */ + self::assertSame($expectedBody, $actual->getBody()->__toString()); + self::assertSame($expectedBody, $actual->getBody()->getContents()); + + /** @And the status code should match the provided code */ + self::assertSame($code->value, $actual->getStatusCode()); + self::assertTrue(Code::isValidCode(code: $actual->getStatusCode())); + + /** @And the reason phrase should match the provided code message */ + self::assertSame($code->message(), $actual->getReasonPhrase()); + + /** @And the headers should contain Content-Type as application/json with charset=utf-8 */ + self::assertSame(['Content-Type' => ['application/json; charset=utf-8']], $actual->getHeaders()); + } + public function testResponseOk(): void { /** @Given a body with data */ @@ -351,6 +376,37 @@ public function testResponseInternalServerError(): void self::assertSame(['Content-Type' => ['application/json; charset=utf-8']], $actual->getHeaders()); } + public static function responseFromProvider(): array + { + return [ + 'I am a teapot' => [ + 'code' => Code::IM_A_TEAPOT, + 'body' => 'Short and stout', + 'expectedBody' => 'Short and stout' + ], + 'OK with array body' => [ + 'code' => Code::OK, + 'body' => ['status' => 'success'], + 'expectedBody' => '{"status":"success"}' + ], + 'Accepted with null body' => [ + 'code' => Code::ACCEPTED, + 'body' => null, + 'expectedBody' => '' + ], + 'Not Found with string body' => [ + 'code' => Code::NOT_FOUND, + 'body' => 'Resource not found', + 'expectedBody' => 'Resource not found' + ], + 'Internal Server Error with complex body' => [ + 'code' => Code::INTERNAL_SERVER_ERROR, + 'body' => ['error' => ['code' => 500, 'message' => 'Crash']], + 'expectedBody' => '{"error":{"code":500,"message":"Crash"}}' + ] + ]; + } + #[DataProvider('bodyProviderData')] public function testResponseBodySerialization(mixed $body, string $expected): void {