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
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.PHP_VERSION }}
extensions: bcmath
tools: composer:2

- name: Validate composer.json
Expand Down Expand Up @@ -52,7 +51,6 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.PHP_VERSION }}
extensions: bcmath
tools: composer:2

- name: Download vendor artifact from build
Expand All @@ -77,7 +75,6 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.PHP_VERSION }}
extensions: bcmath
tools: composer:2

- name: Download vendor artifact from build
Expand Down
3 changes: 1 addition & 2 deletions infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
"customPath": "./vendor/bin/phpunit"
},
"mutators": {
"@default": true,
"LogicalOr": false
"@default": true
},
"minCoveredMsi": 100,
"testFramework": "phpunit"
Expand Down
8 changes: 1 addition & 7 deletions src/Internal/Response/Stream/Stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,7 @@ public function isWritable(): bool
return false;
}

$mode = $this->metaData->getMode();

return str_contains($mode, 'x')
|| str_contains($mode, 'w')
|| str_contains($mode, 'c')
|| str_contains($mode, 'a')
|| str_contains($mode, '+');
return strpbrk($this->metaData->getMode(), 'xwca+') !== false;
}

public function isSeekable(): bool
Expand Down
10 changes: 10 additions & 0 deletions src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ public static function badRequest(mixed $body, Headers ...$headers): ResponseInt
return InternalResponse::createWithBody($body, Code::BAD_REQUEST, ...$headers);
}

public static function unauthorized(mixed $body, Headers ...$headers): ResponseInterface
{
return InternalResponse::createWithBody($body, Code::UNAUTHORIZED, ...$headers);
}

public static function forbidden(mixed $body, Headers ...$headers): ResponseInterface
{
return InternalResponse::createWithBody($body, Code::FORBIDDEN, ...$headers);
}

public static function notFound(mixed $body, Headers ...$headers): ResponseInterface
{
return InternalResponse::createWithBody($body, Code::NOT_FOUND, ...$headers);
Expand Down
18 changes: 18 additions & 0 deletions src/Responses.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ public static function noContent(Headers ...$headers): ResponseInterface;
*/
public static function badRequest(mixed $body, Headers ...$headers): ResponseInterface;

/**
* Creates a response with a 401 Unauthorized status.
*
* @param mixed $body The body of the response.
* @param Headers ...$headers Optional additional headers for the response.
* @return ResponseInterface The generated 401 Unauthorized response.
*/
public static function unauthorized(mixed $body, Headers ...$headers): ResponseInterface;

/**
* Creates a response with a 403 Forbidden status.
*
* @param mixed $body The body of the response.
* @param Headers ...$headers Optional additional headers for the response.
* @return ResponseInterface The generated 403 Forbidden response.
*/
public static function forbidden(mixed $body, Headers ...$headers): ResponseInterface;

/**
* Creates a response with a 404 Not Found status.
*
Expand Down
60 changes: 60 additions & 0 deletions tests/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,66 @@ public function testResponseBadRequest(): void
self::assertSame(['Content-Type' => ['application/json; charset=utf-8']], $actual->getHeaders());
}

public function testResponseUnauthorized(): void
{
/** @Given a body with error details */
$body = [
'error' => 'Unauthorized',
'message' => 'Authentication is required to access this resource.'
];

/** @When we create the HTTP response with this body */
$actual = Response::unauthorized(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 JSON-encoded body */
self::assertSame(json_encode($body, JSON_PRESERVE_ZERO_FRACTION), $actual->getBody()->__toString());
self::assertSame(json_encode($body, JSON_PRESERVE_ZERO_FRACTION), $actual->getBody()->getContents());

/** @And the status code should be 401 */
self::assertSame(Code::UNAUTHORIZED->value, $actual->getStatusCode());
self::assertTrue(Code::isValidCode(code: $actual->getStatusCode()));
self::assertTrue(Code::isErrorCode(code: $actual->getStatusCode()));

/** @And the reason phrase should be "Unauthorized" */
self::assertSame(Code::UNAUTHORIZED->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 testResponseForbidden(): void
{
/** @Given a body with error details */
$body = [
'error' => 'Forbidden',
'message' => 'You do not have permission to access this resource.'
];

/** @When we create the HTTP response with this body */
$actual = Response::forbidden(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 JSON-encoded body */
self::assertSame(json_encode($body, JSON_PRESERVE_ZERO_FRACTION), $actual->getBody()->__toString());
self::assertSame(json_encode($body, JSON_PRESERVE_ZERO_FRACTION), $actual->getBody()->getContents());

/** @And the status code should be 403 */
self::assertSame(Code::FORBIDDEN->value, $actual->getStatusCode());
self::assertTrue(Code::isValidCode(code: $actual->getStatusCode()));
self::assertTrue(Code::isErrorCode(code: $actual->getStatusCode()));

/** @And the reason phrase should be "Forbidden" */
self::assertSame(Code::FORBIDDEN->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 testResponseNotFound(): void
{
/** @Given a body with error details */
Expand Down