Skip to content

Commit

Permalink
Fixes header construction.
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavofreze authored Dec 19, 2024
1 parent eb7ecf3 commit 86dd6d7
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 80 deletions.
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

0 comments on commit 86dd6d7

Please sign in to comment.