Files
duckbrain-toolbox/packages/test/tests/EndpointTestCase.php
T
2026-10-05 15:08:39 -03:00

114 lines
3.4 KiB
PHP

<?php
namespace Tests;
use Libs\Neuron;
use Libs\Router;
use Tests\Support\HttpStubs;
use Tests\Support\Response;
/**
* Base para tests de endpoint: despacha peticiones al router in-process
* (sin servidor HTTP) y expone helpers de aserción.
*
* No incluye helpers de autenticación: Duckbrain no define login por defecto,
* así que simular una sesión es responsabilidad de cada proyecto. Extiende esta
* clase (o crea tu propia subclase) y añade tu helper, p. ej. `loggedInAs()`.
*
* Nota: `#[RunTestsInSeparateProcesses]` NO se hereda en PHPUnit 11, así que
* cada clase concreta de endpoint debe declarar el atributo para aislar el
* estado estático (Synapsis, Router::$params, superglobales, http_response_code).
*/
abstract class EndpointTestCase extends TestCase
{
protected function dispatch(
string $method,
string $path,
array $data = [],
array $headers = []
): Response {
$method = strtoupper($method);
$_SERVER['REQUEST_METHOD'] = $method;
$_SERVER['REQUEST_URI'] = $path;
$_SERVER['HTTP_ACCEPT'] = $headers['Accept'] ?? 'text/html';
$_GET = [];
$_POST = [];
$query = parse_url($path, PHP_URL_QUERY);
if (is_string($query)) {
parse_str($query, $_GET);
}
if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
$_POST = $data;
}
foreach ($headers as $name => $value) {
$key = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
$_SERVER[$key] = $value;
}
Router::$params = new Neuron();
HttpStubs::reset();
http_response_code(200);
ob_start();
try {
Router::apply(parse_url($path, PHP_URL_PATH) ?: $path);
} finally {
$body = (string) ob_get_clean();
}
return new Response(
http_response_code(),
$body,
HttpStubs::$headers,
HttpStubs::$cookies
);
}
protected function get(string $path, array $headers = []): Response
{
return $this->dispatch('GET', $path, [], $headers);
}
protected function post(string $path, array $data = [], array $headers = []): Response
{
return $this->dispatch('POST', $path, $data, $headers);
}
protected function patch(string $path, array $data = [], array $headers = []): Response
{
return $this->dispatch('PATCH', $path, $data, $headers);
}
protected function delete(string $path, array $data = [], array $headers = []): Response
{
return $this->dispatch('DELETE', $path, $data, $headers);
}
protected function assertStatus(int $expected, Response $response): void
{
$this->assertSame(
$expected,
$response->status,
"Expected status {$expected}, got {$response->status}. Body: {$response->body}"
);
}
protected function assertRedirect(string $expectedPath, Response $response): void
{
$target = $response->redirectTarget();
$this->assertNotNull($target, 'Expected a redirect header (HX-Redirect or Location).');
$this->assertStringEndsWith($expectedPath, $target);
}
protected function assertHtmxTrigger(string $event, Response $response): void
{
$this->assertSame($event, $response->trigger());
}
}