- Introduce small interfaces and default adapters (DIP): - Support\Contracts\ConfigInterface + Support\DefaultConfig - Http\Contracts\ErrorFormatNegotiatorInterface + Http\Support\DefaultErrorFormatNegotiator - Http\Contracts\RequestIdProviderInterface + Http\Support\DefaultRequestIdProvider - Http\Contracts\ExceptionToStatusMapperInterface + Http\Support\DefaultExceptionToStatusMapper - Kernel: bind new contracts in the container; keep DelegatingApiResponseFactory wiring - ContentNegotiationMiddleware: depend on ConfigInterface + negotiator; honor Accept for JSON:API - ProblemDetailsMiddleware: inject negotiator + config; split into small helpers; deterministic content negotiation; stable Whoops HTML; include X-Request-Id - DispatchMiddleware: SRP refactor into small methods; remove hidden coupling; normalize non-Response returns - Add/adjust tests: - tests/ErrorHandlingTest.php for problem details, JSON:API errors, and Whoops HTML - tests/ContentNegotiationTest.php for format selection - tests/MakeCommandTest.php aligned with create:command scaffolder - Docs/Meta: update README and MILESTONES; .gitignore to ignore .junie.json No runtime behavior changes intended beyond clearer DI boundaries and content-negotiation determinism. All tests green.
70 lines
2.5 KiB
PHP
70 lines
2.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Phred\Tests;
|
|
|
|
use Nyholm\Psr7Server\ServerRequestCreator;
|
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
|
use Phred\Http\Kernel;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class ContentNegotiationTest extends TestCase
|
|
{
|
|
private function kernel(): Kernel
|
|
{
|
|
return new Kernel();
|
|
}
|
|
|
|
private function request(string $method, string $uri, array $headers = []): \Psr\Http\Message\ServerRequestInterface
|
|
{
|
|
$psr17 = new Psr17Factory();
|
|
$creator = new ServerRequestCreator($psr17, $psr17, $psr17, $psr17);
|
|
$server = [
|
|
'REQUEST_METHOD' => strtoupper($method),
|
|
'REQUEST_URI' => $uri,
|
|
];
|
|
return $creator->fromArrays($server, $headers, [], [], []);
|
|
}
|
|
|
|
public function testDefaultRestWhenNoAccept(): void
|
|
{
|
|
putenv('API_FORMAT'); // unset to use default
|
|
$kernel = $this->kernel();
|
|
$req = $this->request('GET', '/_phred/format');
|
|
$res = $kernel->handle($req);
|
|
$this->assertSame(200, $res->getStatusCode());
|
|
$this->assertStringStartsWith('application/json', $res->getHeaderLine('Content-Type'));
|
|
$data = json_decode((string) $res->getBody(), true);
|
|
$this->assertIsArray($data);
|
|
$this->assertSame('rest', $data['format'] ?? null);
|
|
}
|
|
|
|
public function testJsonApiWhenAcceptHeaderPresent(): void
|
|
{
|
|
putenv('API_FORMAT'); // unset
|
|
$kernel = $this->kernel();
|
|
$req = $this->request('GET', '/_phred/format', ['Accept' => 'application/vnd.api+json']);
|
|
$res = $kernel->handle($req);
|
|
$this->assertSame(200, $res->getStatusCode());
|
|
$this->assertSame('application/vnd.api+json', $res->getHeaderLine('Content-Type'));
|
|
$doc = json_decode((string) $res->getBody(), true);
|
|
$this->assertIsArray($doc);
|
|
$this->assertArrayHasKey('data', $doc);
|
|
$this->assertSame('jsonapi', $doc['data']['format'] ?? null);
|
|
}
|
|
|
|
public function testEnvDefaultJsonApiWithoutAccept(): void
|
|
{
|
|
putenv('API_FORMAT=jsonapi');
|
|
$kernel = $this->kernel();
|
|
$req = $this->request('GET', '/_phred/format');
|
|
$res = $kernel->handle($req);
|
|
$this->assertSame(200, $res->getStatusCode());
|
|
$this->assertSame('application/vnd.api+json', $res->getHeaderLine('Content-Type'));
|
|
$doc = json_decode((string) $res->getBody(), true);
|
|
$this->assertIsArray($doc);
|
|
$this->assertArrayHasKey('data', $doc);
|
|
$this->assertSame('jsonapi', $doc['data']['format'] ?? null);
|
|
}
|
|
}
|