Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes header construction. #36

Merged
merged 1 commit into from
Dec 19, 2024
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
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@
"ext-mbstring": "*"
},
"require-dev": {
"slim/psr7": "^1.7",
"slim/slim": "^4.14",
"slim/psr7": "^1",
"slim/slim": "^4",
"phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^1",
"phpunit/phpunit": "^11",
"infection/infection": "^0",
"squizlabs/php_codesniffer": "^3.11"
"squizlabs/php_codesniffer": "^3.11",
"laminas/laminas-httphandlerrunner": "^2"
},
"suggest": {
"ext-mbstring": "Provides multibyte-specific string functions that help us deal with multibyte encodings in PHP."
Expand Down
2 changes: 1 addition & 1 deletion src/CacheControl.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ public static function fromResponseDirectives(ResponseCacheDirectives ...$direct

public function toArray(): array
{
return ['Cache-Control' => implode(', ', $this->directives)];
return ['Cache-Control' => [implode(', ', $this->directives)]];
}
}
10 changes: 5 additions & 5 deletions src/ContentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ public static function applicationFormUrlencoded(?Charset $charset = null): Cont

public function toArray(): array
{
return [
'Content-Type' => $this->charset
? sprintf('%s; %s', $this->mimeType->value, $this->charset->toString())
: $this->mimeType->value
];
$value = $this->charset
? sprintf('%s; %s', $this->mimeType->value, $this->charset->toString())
: $this->mimeType->value;

return ['Content-Type' => [$value]];
}
}
6 changes: 2 additions & 4 deletions src/Internal/Response/ResponseHeaders.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ public static function fromOrDefault(Headers ...$headers): ResponseHeaders

public static function fromNameAndValue(string $name, mixed $value): ResponseHeaders
{
return new ResponseHeaders(headers: [$name => $value]);
return new ResponseHeaders(headers: [$name => [$value]]);
}

public function getByName(string $name): array
{
$headers = array_change_key_case($this->headers);
$value = $headers[strtolower($name)] ?? [];

return is_array($value) ? $value : [$value];
return $headers[strtolower($name)] ?? [];
}

public function hasHeader(string $name): bool
Expand All @@ -53,7 +52,6 @@ public function removeByName(string $name): ResponseHeaders
return new ResponseHeaders(headers: $headers);
}


public function toArray(): array
{
return $this->headers;
Expand Down
86 changes: 86 additions & 0 deletions tests/Drivers/Laminas/LaminasTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\Http\Drivers\Laminas;

use DateTimeInterface;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use TinyBlocks\Http\CacheControl;
use TinyBlocks\Http\Charset;
use TinyBlocks\Http\Code;
use TinyBlocks\Http\ContentType;
use TinyBlocks\Http\Drivers\Endpoint;
use TinyBlocks\Http\Drivers\Middleware;
use TinyBlocks\Http\Response;
use TinyBlocks\Http\ResponseCacheDirectives;

final class LaminasTest extends TestCase
{
private SapiEmitter $emitter;

private Middleware $middleware;

protected function setUp(): void
{
$this->emitter = new SapiEmitter();
$this->middleware = new Middleware();
}

/**
* @throws Exception
*/
public function testSuccessfulRequestProcessingWithLaminas(): void
{
/** @Given a valid request */
$request = $this->createMock(ServerRequestInterface::class);

/** @And the Content-Type for the response is set to application/json with UTF-8 charset */
$contentType = ContentType::applicationJson(charset: Charset::UTF_8);

/** @And a Cache-Control header is set with no-cache directive */
$cacheControl = CacheControl::fromResponseDirectives(noCache: ResponseCacheDirectives::noCache());

/** @And an HTTP response is created with a 200 OK status and a body containing the creation timestamp */
$response = Response::ok(['createdAt' => date(DateTimeInterface::ATOM)], $contentType, $cacheControl);

/** @When the request is processed by the handler */
$actual = $this->middleware->process(request: $request, handler: new Endpoint(response: $response));

/** @Then the response status should indicate success */
self::assertSame(Code::OK->value, $actual->getStatusCode());

/** @And the response body should match the expected body */
self::assertSame($response->getBody()->getContents(), $actual->getBody()->getContents());

/** @And the response headers should match the expected headers */
self::assertSame($response->getHeaders(), $actual->getHeaders());
}

public function testResponseEmissionWithLaminas(): void
{
/** @Given the Content-Type for the response is set to application/json with UTF-8 charset */
$contentType = ContentType::applicationJson(charset: Charset::UTF_8);

/** @And a Cache-Control header is set with no-cache directive */
$cacheControl = CacheControl::fromResponseDirectives(noCache: ResponseCacheDirectives::noCache());

/** @And an HTTP response is created with a 200 OK status and a body containing the creation timestamp */
$response = Response::ok(
['createdAt' => date(DateTimeInterface::ATOM)],
$contentType,
$cacheControl
)->withHeader(name: 'X-Request-ID', value: '123456');

/** @When the response is emitted */
ob_start();
$this->emitter->emit($response);
$actual = ob_get_clean();

/** @Then the emitted response content should match the response body */
self::assertSame($response->getBody()->__toString(), $actual);
}
}
41 changes: 0 additions & 41 deletions tests/Drivers/Slim/RequestFactory.php

This file was deleted.

42 changes: 36 additions & 6 deletions tests/Drivers/Slim/SlimTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
namespace TinyBlocks\Http\Drivers\Slim;

use DateTimeInterface;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Slim\ResponseEmitter;
use TinyBlocks\Http\CacheControl;
use TinyBlocks\Http\Charset;
use TinyBlocks\Http\Code;
Expand All @@ -17,20 +20,23 @@

final class SlimTest extends TestCase
{
private ResponseEmitter $emitter;

private Middleware $middleware;

protected function setUp(): void
{
$this->emitter = new ResponseEmitter();
$this->middleware = new Middleware();
}

public function testSuccessfulResponse(): void
/**
* @throws Exception
*/
public function testSuccessfulRequestProcessingWithSlim(): void
{
/** @Given valid data */
$payload = ['id' => PHP_INT_MAX, 'name' => 'Drakkor Emberclaw'];

/** @And this data is used to create a request */
$request = RequestFactory::postFrom(payload: $payload);
/** @Given a valid request */
$request = $this->createMock(ServerRequestInterface::class);

/** @And the Content-Type for the response is set to application/json with UTF-8 charset */
$contentType = ContentType::applicationJson(charset: Charset::UTF_8);
Expand All @@ -53,4 +59,28 @@ public function testSuccessfulResponse(): void
/** @And the response headers should match the expected headers */
self::assertSame($response->getHeaders(), $actual->getHeaders());
}

public function testResponseEmissionWithSlim(): void
{
/** @Given the Content-Type for the response is set to application/json with UTF-8 charset */
$contentType = ContentType::applicationJson(charset: Charset::UTF_8);

/** @And a Cache-Control header is set with no-cache directive */
$cacheControl = CacheControl::fromResponseDirectives(noCache: ResponseCacheDirectives::noCache());

/** @And an HTTP response is created with a 200 OK status and a body containing the creation timestamp */
$response = Response::ok(
['createdAt' => date(DateTimeInterface::ATOM)],
$contentType,
$cacheControl
)->withHeader(name: 'X-Request-ID', value: '123456');

/** @When the response is emitted */
ob_start();
$this->emitter->emit($response);
$actual = ob_get_clean();

/** @Then the emitted response content should match the response body */
self::assertSame($response->getBody()->__toString(), $actual);
}
}
37 changes: 26 additions & 11 deletions tests/Response/HeadersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function testResponseWithCustomHeaders(): void
$response = Response::noContent();

/** @And by default, the response contains the 'Content-Type' header set to 'application/json; charset=utf-8' */
self::assertSame(['Content-Type' => 'application/json; charset=utf-8'], $response->getHeaders());
self::assertSame(['Content-Type' => ['application/json; charset=utf-8']], $response->getHeaders());

/** @When we add custom headers to the response */
$actual = $response
Expand All @@ -27,7 +27,7 @@ public function testResponseWithCustomHeaders(): void

/** @Then the response should contain the correct headers */
self::assertSame(
['Content-Type' => 'application/json; charset=utf-8', 'X-ID' => 100, 'X-NAME' => 'Xpto'],
['Content-Type' => ['application/json; charset=utf-8'], 'X-ID' => [100], 'X-NAME' => ['Xpto']],
$actual->getHeaders()
);

Expand All @@ -37,15 +37,18 @@ public function testResponseWithCustomHeaders(): void
/** @Then the response should contain the updated 'X-ID' header value */
self::assertSame('200', $actual->withAddedHeader(name: 'X-ID', value: 200)->getHeaderLine(name: 'X-ID'));
self::assertSame(
['Content-Type' => 'application/json; charset=utf-8', 'X-ID' => 200, 'X-NAME' => 'Xpto'],
['Content-Type' => ['application/json; charset=utf-8'], 'X-ID' => [200], 'X-NAME' => ['Xpto']],
$actual->getHeaders()
);

/** @And when we remove the 'X-NAME' header */
$actual = $actual->withoutHeader(name: 'X-NAME');

/** @Then the response should contain only the 'X-ID' header and the default 'Content-Type' header */
self::assertSame(['Content-Type' => 'application/json; charset=utf-8', 'X-ID' => 200], $actual->getHeaders());
self::assertSame(
['Content-Type' => ['application/json; charset=utf-8'], 'X-ID' => [200]],
$actual->getHeaders()
);
}

public function testResponseWithDuplicatedHeader(): void
Expand All @@ -62,7 +65,19 @@ public function testResponseWithDuplicatedHeader(): void
self::assertSame('application/json; charset=ISO-8859-1', $actual->getHeaderLine(name: 'Content-Type'));

/** @And the headers should only contain the last 'Content-Type' value */
self::assertSame(['Content-Type' => 'application/json; charset=ISO-8859-1'], $actual->getHeaders());
self::assertSame(['Content-Type' => ['application/json; charset=ISO-8859-1']], $actual->getHeaders());
}

public function testResponseHeadersWithNoCustomHeader(): void
{
/** @Given an HTTP response with no custom headers */
$response = Response::noContent();

/** @When we retrieve the header that doesn't exist */
$actual = $response->getHeader(name: 'Non-Existent-Header');

/** @Then the header should return an empty array */
self::assertSame([], $actual);
}

public function testResponseWithCacheControl(): void
Expand Down Expand Up @@ -108,7 +123,7 @@ public function testResponseWithContentTypePDF(): void

self::assertSame($expected, $actual->getHeaderLine(name: 'Content-Type'));
self::assertSame([$expected], $actual->getHeader(name: 'Content-Type'));
self::assertSame(['Content-Type' => $expected], $actual->getHeaders());
self::assertSame(['Content-Type' => [$expected]], $actual->getHeaders());
}

public function testResponseWithContentTypeHTML(): void
Expand All @@ -127,7 +142,7 @@ public function testResponseWithContentTypeHTML(): void

self::assertSame($expected, $actual->getHeaderLine(name: 'Content-Type'));
self::assertSame([$expected], $actual->getHeader(name: 'Content-Type'));
self::assertSame(['Content-Type' => $expected], $actual->getHeaders());
self::assertSame(['Content-Type' => [$expected]], $actual->getHeaders());
}

public function testResponseWithContentTypeJSON(): void
Expand All @@ -146,7 +161,7 @@ public function testResponseWithContentTypeJSON(): void

self::assertSame($expected, $actual->getHeaderLine(name: 'Content-Type'));
self::assertSame([$expected], $actual->getHeader(name: 'Content-Type'));
self::assertSame(['Content-Type' => $expected], $actual->getHeaders());
self::assertSame(['Content-Type' => [$expected]], $actual->getHeaders());
}

public function testResponseWithContentTypePlainText(): void
Expand All @@ -165,7 +180,7 @@ public function testResponseWithContentTypePlainText(): void

self::assertSame($expected, $actual->getHeaderLine(name: 'Content-Type'));
self::assertSame([$expected], $actual->getHeader(name: 'Content-Type'));
self::assertSame(['Content-Type' => $expected], $actual->getHeaders());
self::assertSame(['Content-Type' => [$expected]], $actual->getHeaders());
}

public function testResponseWithContentTypeOctetStream(): void
Expand All @@ -184,7 +199,7 @@ public function testResponseWithContentTypeOctetStream(): void

self::assertSame($expected, $actual->getHeaderLine(name: 'Content-Type'));
self::assertSame([$expected], $actual->getHeader(name: 'Content-Type'));
self::assertSame(['Content-Type' => $expected], $actual->getHeaders());
self::assertSame(['Content-Type' => [$expected]], $actual->getHeaders());
}

public function testResponseWithContentTypeFormUrlencoded(): void
Expand All @@ -203,6 +218,6 @@ public function testResponseWithContentTypeFormUrlencoded(): void

self::assertSame($expected, $actual->getHeaderLine(name: 'Content-Type'));
self::assertSame([$expected], $actual->getHeader(name: 'Content-Type'));
self::assertSame(['Content-Type' => $expected], $actual->getHeaders());
self::assertSame(['Content-Type' => [$expected]], $actual->getHeaders());
}
}
Loading
Loading