114 lines
3.4 KiB
PHP
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());
|
|
}
|
|
}
|