- 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.
80 lines
3 KiB
PHP
80 lines
3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Phred\Tests;
|
|
|
|
use Nyholm\Psr7\ServerRequest;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class ErrorHandlingTest extends TestCase
|
|
{
|
|
public function testRestProblemDetailsOnException(): void
|
|
{
|
|
$root = dirname(__DIR__);
|
|
/** @var object $app */
|
|
$app = require $root . '/bootstrap/app.php';
|
|
$this->assertInstanceOf(\Phred\Http\Kernel::class, $app);
|
|
|
|
// Default format is REST (unless ACCEPT requests JSON:API)
|
|
$request = new ServerRequest('GET', '/_phred/error');
|
|
$response = $app->handle($request);
|
|
|
|
$this->assertSame(500, $response->getStatusCode());
|
|
$this->assertSame('application/problem+json', $response->getHeaderLine('Content-Type'));
|
|
$data = json_decode((string) $response->getBody(), true);
|
|
$this->assertIsArray($data);
|
|
$this->assertArrayHasKey('type', $data);
|
|
$this->assertArrayHasKey('title', $data);
|
|
$this->assertArrayHasKey('status', $data);
|
|
$this->assertArrayHasKey('detail', $data);
|
|
$this->assertSame(500, $data['status']);
|
|
$this->assertSame('RuntimeException', $data['title']);
|
|
$this->assertStringContainsString('Boom', (string) $data['detail']);
|
|
}
|
|
|
|
public function testJsonApiErrorDocumentOnException(): void
|
|
{
|
|
$root = dirname(__DIR__);
|
|
/** @var object $app */
|
|
$app = require $root . '/bootstrap/app.php';
|
|
$this->assertInstanceOf(\Phred\Http\Kernel::class, $app);
|
|
|
|
$request = (new ServerRequest('GET', '/_phred/error'))
|
|
->withHeader('Accept', 'application/vnd.api+json');
|
|
$response = $app->handle($request);
|
|
|
|
$this->assertSame(500, $response->getStatusCode());
|
|
$this->assertSame('application/vnd.api+json', $response->getHeaderLine('Content-Type'));
|
|
$data = json_decode((string) $response->getBody(), true);
|
|
$this->assertIsArray($data);
|
|
$this->assertArrayHasKey('errors', $data);
|
|
$this->assertIsArray($data['errors']);
|
|
$this->assertNotEmpty($data['errors']);
|
|
$err = $data['errors'][0];
|
|
$this->assertSame('500', $err['status']);
|
|
$this->assertSame('RuntimeException', $err['title']);
|
|
$this->assertStringContainsString('Boom', (string) $err['detail']);
|
|
}
|
|
|
|
public function testWhoopsHtmlInDebugMode(): void
|
|
{
|
|
// Enable debug for this test
|
|
putenv('APP_DEBUG=true');
|
|
|
|
$root = dirname(__DIR__);
|
|
/** @var object $app */
|
|
$app = require $root . '/bootstrap/app.php';
|
|
$this->assertInstanceOf(\Phred\Http\Kernel::class, $app);
|
|
|
|
$request = (new ServerRequest('GET', '/_phred/error'))
|
|
->withHeader('Accept', 'text/html');
|
|
$response = $app->handle($request);
|
|
|
|
$this->assertSame(500, $response->getStatusCode());
|
|
$this->assertStringContainsString('text/html', $response->getHeaderLine('Content-Type'));
|
|
$html = (string) $response->getBody();
|
|
$this->assertNotSame('', $html);
|
|
$this->assertStringContainsString('Whoops', $html);
|
|
}
|
|
}
|