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
5 changes: 5 additions & 0 deletions src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions src/Responses.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
56 changes: 56 additions & 0 deletions tests/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
{
Expand Down