index/tests/RouterTest.php

188 lines
7.8 KiB
PHP

<?php
// RouterTest.php
// Created: 2022-01-20
// Updated: 2024-12-02
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Http\{HttpHeaders,HttpRequest};
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,HttpPut,HttpRouter,RouteHandler,RouteHandlerTrait};
/**
* This test isn't super representative of the current functionality
* it mostly just does the same tests that were done against the previous implementation
*/
#[CoversClass(HttpGet::class)]
#[CoversClass(HttpMiddleware::class)]
#[CoversClass(HttpPost::class)]
#[CoversClass(HttpPut::class)]
#[CoversClass(HttpRouter::class)]
#[CoversClass(RouteHandler::class)]
#[CoversClass(RouteHandlerTrait::class)]
final class RouterTest extends TestCase {
public function testRouter(): void {
$router1 = new HttpRouter;
$router1->get('/', fn() => 'get');
$router1->post('/', fn() => 'post');
$router1->delete('/', fn() => 'delete');
$router1->patch('/', fn() => 'patch');
$router1->put('/', fn() => 'put');
$router1->add('custom', '/', fn() => 'wacky');
$this->assertEquals('get', $router1->resolve('GET', '/')->dispatch([]));
$this->assertEquals('wacky', $router1->resolve('CUSTOM', '/')->dispatch([]));
$router1->use('/', function() { /* this one intentionally does nothing */ });
// registration order should matter
$router1->use('/deep', fn() => 'deep');
$postRoot = $router1->resolve('POST', '/');
$this->assertNull($postRoot->runMiddleware([]));
$this->assertEquals('post', $postRoot->dispatch([]));
$this->assertEquals('deep', $router1->resolve('GET', '/deep/nothing')->runMiddleware([]));
$router1->use('/user/([A-Za-z0-9]+)/below', fn(string $user) => 'warioware below ' . $user);
$router1->get('/user/static', fn() => 'the static one');
$router1->get('/user/static/below', fn() => 'below the static one');
$router1->get('/user/([A-Za-z0-9]+)', fn(string $user) => $user);
$router1->get('/user/([A-Za-z0-9]+)/below', fn(string $user) => 'below ' . $user);
$this->assertEquals('below the static one', $router1->resolve('GET', '/user/static/below')->dispatch([]));
$getWariowareBelowFlashwave = $router1->resolve('GET', '/user/flashwave/below');
$this->assertEquals('warioware below flashwave', $getWariowareBelowFlashwave->runMiddleware([]));
$this->assertEquals('below flashwave', $getWariowareBelowFlashwave->dispatch([]));
$router2 = new HttpRouter;
$router2->use('/', fn() => 'meow');
$router2->get('/rules', fn() => 'rules page');
$router2->get('/contact', fn() => 'contact page');
$router2->get('/25252', fn() => 'numeric test');
$getRules = $router2->resolve('GET', '/rules');
$this->assertEquals('meow', $getRules->runMiddleware([]));
$this->assertEquals('rules page', $getRules->dispatch([]));
$get25252 = $router2->resolve('GET', '/25252');
$this->assertEquals('meow', $get25252->runMiddleware([]));
$this->assertEquals('numeric test', $get25252->dispatch([]));
$router3 = $router1->scopeTo('/scoped');
$router3->get('/static', fn() => 'wrong');
$router1->get('/scoped/static/0', fn() => 'correct');
$router3->get('/variable', fn() => 'wrong');
$router3->get('/variable/([0-9]+)', fn(string $num) => $num === '0' ? 'correct' : 'VERY wrong');
$router3->get('/variable/([a-z]+)', fn(string $char) => $char === 'a' ? 'correct' : 'VERY wrong');
$this->assertEquals('correct', $router3->resolve('GET', '/static/0')->dispatch([]));
$this->assertEquals('correct', $router1->resolve('GET', '/scoped/variable/0')->dispatch([]));
$this->assertEquals('correct', $router3->resolve('GET', '/variable/a')->dispatch([]));
}
public function testAttribute(): void {
$router = new HttpRouter;
$handler = new class implements RouteHandler {
use RouteHandlerTrait;
#[HttpGet('/')]
public function getIndex(): string {
return 'index';
}
#[HttpPost('/avatar')]
public function postAvatar(): string {
return 'avatar';
}
#[HttpPut('/static')]
public static function putStatic(): string {
return 'static';
}
#[HttpGet('/meow')]
#[HttpPost('/meow')]
public function multiple(): string {
return 'meow';
}
#[HttpMiddleware('/mw')]
public function useMw(): string {
return 'this intercepts';
}
#[HttpGet('/mw')]
public function getMw(): string {
return 'this is intercepted';
}
public function hasNoAttr(): string {
return 'not a route';
}
};
$router->register($handler);
$this->assertFalse($router->resolve('GET', '/soap')->hasHandler());
$patchAvatar = $router->resolve('PATCH', '/avatar');
$this->assertFalse($patchAvatar->hasHandler());
$this->assertTrue($patchAvatar->hasOtherMethods());
$this->assertEquals(['POST'], $patchAvatar->getSupportedMethods());
$this->assertEquals('index', $router->resolve('GET', '/')->dispatch([]));
$this->assertEquals('avatar', $router->resolve('POST', '/avatar')->dispatch([]));
$this->assertEquals('static', $router->resolve('PUT', '/static')->dispatch([]));
$this->assertEquals('meow', $router->resolve('GET', '/meow')->dispatch([]));
$this->assertEquals('meow', $router->resolve('POST', '/meow')->dispatch([]));
// stopping on middleware is the dispatcher's job
$getMw = $router->resolve('GET', '/mw');
$this->assertEquals('this intercepts', $getMw->runMiddleware([]));
$this->assertEquals('this is intercepted', $getMw->dispatch([]));
$scoped = $router->scopeTo('/scoped');
$scoped->register($handler);
$this->assertEquals('index', $scoped->resolve('GET', '/')->dispatch([]));
$this->assertEquals('avatar', $router->resolve('POST', '/scoped/avatar')->dispatch([]));
$this->assertEquals('static', $scoped->resolve('PUT', '/static')->dispatch([]));
$this->assertEquals('meow', $router->resolve('GET', '/scoped/meow')->dispatch([]));
$this->assertEquals('meow', $scoped->resolve('POST', '/meow')->dispatch([]));
}
public function testEEPROMSituation(): void {
$router = new HttpRouter;
$router->options('/uploads/([A-Za-z0-9\-_]+)(?:\.(t|json))?', function() {});
$router->get('/uploads/([A-Za-z0-9\-_]+)(?:\.(t|json))?', function() {});
$router->delete('/uploads/([A-Za-z0-9\-_]+)', function() {});
$resolved = $router->resolve('DELETE', '/uploads/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$this->assertEquals(['OPTIONS', 'GET', 'DELETE'], $resolved->getSupportedMethods());
}
public function testMiddlewareInterceptionOnRoot(): void {
$router = new HttpRouter;
$router->use('/', fn() => 'expected');
$router->get('/', fn() => 'unexpected');
$router->get('/test', fn() => 'also unexpected');
ob_start();
$router->dispatch(new HttpRequest('::1', true, 'NL', '1.1', 'GET', '/', [], [], new HttpHeaders([]), null));
$this->assertEquals('expected', ob_get_clean());
ob_start();
$router->dispatch(new HttpRequest('::1', true, 'US', '1.1', 'GET', '/test', [], [], new HttpHeaders([]), null));
$this->assertEquals('expected', ob_get_clean());
ob_start();
$router->dispatch(new HttpRequest('::1', true, 'GB', '1.1', 'GET', '/error', [], [], new HttpHeaders([]), null));
$this->assertEquals('expected', ob_get_clean());
}
}