2022-09-13 13:13:11 +00:00
|
|
|
<?php
|
|
|
|
// RouterTest.php
|
|
|
|
// Created: 2022-01-20
|
2024-12-02 01:30:36 +00:00
|
|
|
// Updated: 2024-12-02
|
2022-09-13 13:13:11 +00:00
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
use PHPUnit\Framework\TestCase;
|
2024-07-31 18:12:46 +00:00
|
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
2024-08-18 16:41:02 +00:00
|
|
|
use Index\Http\{HttpHeaders,HttpRequest};
|
2024-10-02 02:09:21 +00:00
|
|
|
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,HttpPut,HttpRouter,RouteHandler,RouteHandlerTrait};
|
2022-09-13 13:13:11 +00:00
|
|
|
|
|
|
|
/**
|
2024-03-30 16:24:34 +00:00
|
|
|
* This test isn't super representative of the current functionality
|
|
|
|
* it mostly just does the same tests that were done against the previous implementation
|
2022-09-13 13:13:11 +00:00
|
|
|
*/
|
2024-07-31 18:12:46 +00:00
|
|
|
#[CoversClass(HttpGet::class)]
|
|
|
|
#[CoversClass(HttpMiddleware::class)]
|
|
|
|
#[CoversClass(HttpPost::class)]
|
|
|
|
#[CoversClass(HttpPut::class)]
|
|
|
|
#[CoversClass(HttpRouter::class)]
|
|
|
|
#[CoversClass(RouteHandler::class)]
|
2024-10-02 02:09:21 +00:00
|
|
|
#[CoversClass(RouteHandlerTrait::class)]
|
2022-09-13 13:13:11 +00:00
|
|
|
final class RouterTest extends TestCase {
|
|
|
|
public function testRouter(): void {
|
2024-03-30 16:24:34 +00:00
|
|
|
$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([]));
|
2022-09-13 13:13:11 +00:00
|
|
|
}
|
2023-09-07 22:08:31 +00:00
|
|
|
|
|
|
|
public function testAttribute(): void {
|
2024-03-30 16:24:34 +00:00
|
|
|
$router = new HttpRouter;
|
2024-10-02 02:09:21 +00:00
|
|
|
$handler = new class implements RouteHandler {
|
|
|
|
use RouteHandlerTrait;
|
|
|
|
|
2024-03-30 16:24:34 +00:00
|
|
|
#[HttpGet('/')]
|
2024-12-02 01:30:36 +00:00
|
|
|
public function getIndex(): string {
|
2023-09-07 22:08:31 +00:00
|
|
|
return 'index';
|
|
|
|
}
|
|
|
|
|
2024-03-30 16:24:34 +00:00
|
|
|
#[HttpPost('/avatar')]
|
2024-12-02 01:30:36 +00:00
|
|
|
public function postAvatar(): string {
|
2023-09-07 22:08:31 +00:00
|
|
|
return 'avatar';
|
|
|
|
}
|
|
|
|
|
2024-03-30 16:24:34 +00:00
|
|
|
#[HttpPut('/static')]
|
2024-12-02 01:30:36 +00:00
|
|
|
public static function putStatic(): string {
|
2023-09-07 22:08:31 +00:00
|
|
|
return 'static';
|
|
|
|
}
|
|
|
|
|
2024-03-30 16:24:34 +00:00
|
|
|
#[HttpGet('/meow')]
|
|
|
|
#[HttpPost('/meow')]
|
2024-12-02 01:30:36 +00:00
|
|
|
public function multiple(): string {
|
2023-09-07 22:08:31 +00:00
|
|
|
return 'meow';
|
|
|
|
}
|
2023-09-07 22:37:04 +00:00
|
|
|
|
2024-03-30 16:24:34 +00:00
|
|
|
#[HttpMiddleware('/mw')]
|
2024-12-02 01:30:36 +00:00
|
|
|
public function useMw(): string {
|
2023-09-08 00:09:23 +00:00
|
|
|
return 'this intercepts';
|
|
|
|
}
|
|
|
|
|
2024-03-30 16:24:34 +00:00
|
|
|
#[HttpGet('/mw')]
|
2024-12-02 01:30:36 +00:00
|
|
|
public function getMw(): string {
|
2023-09-08 00:09:23 +00:00
|
|
|
return 'this is intercepted';
|
|
|
|
}
|
|
|
|
|
2024-12-02 01:30:36 +00:00
|
|
|
public function hasNoAttr(): string {
|
2023-09-07 22:37:04 +00:00
|
|
|
return 'not a route';
|
|
|
|
}
|
2023-09-07 22:08:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
$router->register($handler);
|
2024-03-30 16:24:34 +00:00
|
|
|
$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([]));
|
2023-09-07 22:08:31 +00:00
|
|
|
}
|
2024-04-02 17:27:06 +00:00
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
2024-08-18 16:41:02 +00:00
|
|
|
|
|
|
|
public function testMiddlewareInterceptionOnRoot(): void {
|
|
|
|
$router = new HttpRouter;
|
|
|
|
$router->use('/', fn() => 'expected');
|
|
|
|
$router->get('/', fn() => 'unexpected');
|
|
|
|
$router->get('/test', fn() => 'also unexpected');
|
|
|
|
|
|
|
|
ob_start();
|
2024-10-05 14:10:08 +00:00
|
|
|
$router->dispatch(new HttpRequest('::1', true, 'NL', '1.1', 'GET', '/', [], [], new HttpHeaders([]), null));
|
2024-08-18 16:41:02 +00:00
|
|
|
$this->assertEquals('expected', ob_get_clean());
|
|
|
|
|
|
|
|
ob_start();
|
2024-10-05 14:10:08 +00:00
|
|
|
$router->dispatch(new HttpRequest('::1', true, 'US', '1.1', 'GET', '/test', [], [], new HttpHeaders([]), null));
|
2024-08-18 16:41:02 +00:00
|
|
|
$this->assertEquals('expected', ob_get_clean());
|
|
|
|
|
|
|
|
ob_start();
|
2024-10-05 14:10:08 +00:00
|
|
|
$router->dispatch(new HttpRequest('::1', true, 'GB', '1.1', 'GET', '/error', [], [], new HttpHeaders([]), null));
|
2024-08-18 16:41:02 +00:00
|
|
|
$this->assertEquals('expected', ob_get_clean());
|
|
|
|
}
|
2022-09-13 13:13:11 +00:00
|
|
|
}
|