Skip to content

Commit

Permalink
Fix: more precise tests
Browse files Browse the repository at this point in the history
- Added more tests;
- Fixed RouteBuilder according to new tests;
  • Loading branch information
OleksiiBulba committed May 7, 2023
1 parent 19ec43f commit 1559307
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 26 deletions.
19 changes: 11 additions & 8 deletions src/Business/Route/RouteBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Micro\Plugin\Http\Business\Route;

use Micro\Plugin\Http\Exception\RouteInvalidConfigurationException;
use Symfony\Component\HttpFoundation\Request;

/**
* @author Stanislau Komar <[email protected]>
Expand All @@ -39,7 +40,11 @@ class RouteBuilder implements RouteBuilderInterface
*/
public function __construct(
private readonly array $methodsByDefault = [
'PUT', 'POST', 'PATCH', 'GET', 'DELETE',
Request::METHOD_GET,
Request::METHOD_POST,
Request::METHOD_PUT,
Request::METHOD_PATCH,
Request::METHOD_DELETE,
],
) {
$this->name = null;
Expand Down Expand Up @@ -114,17 +119,15 @@ public function build(): RouteInterface
$exceptions = [];

if (!$this->uri) {
$this->uri = '';

$exceptions[] = 'Uri can not be empty.';
}

if ($this->name && !preg_match('/^(.[aA-zZ_])/', $this->name)) {
$exceptions[] = 'The route name must match "aA-zZ0-9_".';
if ($this->name && !preg_match('/^([a-zA-Z_][a-zA-Z0-9_]*)$/', $this->name)) {
$exceptions[] = 'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".';
}

if (!$this->action) {
$exceptions[] = 'The route action can not be empty and should be callable.';
$exceptions[] = 'The route action can not be empty.';
}

if (
Expand All @@ -138,7 +141,7 @@ public function build(): RouteInterface
}

if (!\count($this->methods)) {
$exceptions[] = 'The route should be contain one or more methods from %s::class.';
$exceptions[] = 'The route should support at least one HTTP method.';
}

if (\count($exceptions)) {
Expand All @@ -159,7 +162,7 @@ public function build(): RouteInterface
$pattern = '/'.addcslashes($this->uri, '/.').'$/';

foreach ($matches[0] as $replaced) {
$pattern = str_replace($replaced, '(.[aA-zZ0-9-_]+)', $pattern);
$pattern = str_replace($replaced, '([a-zA-Z_][a-zA-Z0-9_]*)', $pattern);
}
}
/**
Expand Down
6 changes: 3 additions & 3 deletions src/Exception/RouteInvalidConfigurationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public function __construct(string $routeName, array $messages, int $code = 0, ?
{
$message = <<<EOF
Invalid route "%s" configuration:
* %s
EOF;
* %s
EOF;

$message = sprintf($message, $routeName, implode("\r\n * ", $messages));
$message = sprintf($message, $routeName, implode("\r\n * ", $messages));

$this->messages = $messages;
parent::__construct(
Expand Down
233 changes: 218 additions & 15 deletions tests/Unit/Business/Route/RouteBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,20 @@
class RouteBuilderTest extends TestCase
{
public const METHOD_DEFAULTS = [
'PUT', 'POST', 'PATCH', 'GET', 'DELETE',
'GET', 'POST', 'PUT', 'PATCH', 'DELETE',
];

/**
* @dataProvider dataProvider
*
* @return void
*/
public function testBuild(
string|null $routeName,
callable|null $action,
callable|string|null $action,
string|null $uri,
string|null $pattern,
array|null $methods,
string|null $allowedException
) {
array|null $allowedException
): void {
$builder = new RouteBuilder();

if ($routeName) {
Expand All @@ -53,33 +51,231 @@ public function testBuild(
}

if ($allowedException) {
$this->expectException($allowedException);
$this->expectException($allowedException['class']);
}

try {
$route = $builder->build();
} catch (RouteInvalidConfigurationException $configurationException) {
$this->assertNotEmpty($configurationException->getMessages());
$this->assertEquals($allowedException['messages'], $configurationException->getMessages());

throw $configurationException;
}

$this->assertIsCallable($route->getController());
$this->assertEquals($action, $route->getController());
$this->assertEquals($uri, $route->getUri());
$this->assertEquals($methods ?: self::METHOD_DEFAULTS, $route->getMethods());
$this->assertEquals($pattern, $route->getPattern());
$this->assertEquals($routeName, $route->getName());
}

public function dataProvider()
public static function dataProvider(): array
{
$emptyRouteAction = static function () {};

return [
['test', function () {}, '/{test}.{_format}', '/\/(.[aA-zZ0-9-_]+)\.(.[aA-zZ0-9-_]+)$/', ['POST'], null],
['test', function () {}, '/{test}-{date}.{_format}', '/\/(.[aA-zZ0-9-_]+)-(.[aA-zZ0-9-_]+)\.(.[aA-zZ0-9-_]+)$/', ['POST'], null],
[null, function () {}, '/{test}.{_format}', '/\/(.[aA-zZ0-9-_]+)\.(.[aA-zZ0-9-_]+)$/', null, null],
['test', null, '/{test}.{_format}', null, null, RouteInvalidConfigurationException::class],
['test', function () {}, '/test', null, null, null],
['test', null, null, null, null, RouteInvalidConfigurationException::class],
'Correct minimal route: just action and uri' => [
'routeName' => null,
'action' => $emptyRouteAction,
'uri' => '/uri',
'pattern' => null,
'methods' => null,
'allowedException' => null,
],
'Empty uri: null' => [
'routeName' => null,
'action' => $emptyRouteAction,
'uri' => null,
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'Uri can not be empty.',
],
],
],
'Empty uri: empty string' => [
'routeName' => null,
'action' => $emptyRouteAction,
'uri' => '',
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'Uri can not be empty.',
],
],
],
'Invalid route name 1' => [
'routeName' => '0invalid',
'action' => $emptyRouteAction,
'uri' => '/uri',
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".',
],
],
],
'Invalid route name 2' => [
'routeName' => '.invalid',
'action' => $emptyRouteAction,
'uri' => '/uri',
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".',
],
],
],
'Invalid route name 3' => [
'routeName' => 'invalid@again',
'action' => $emptyRouteAction,
'uri' => '/uri',
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".',
],
],
],
'Empty action' => [
'routeName' => 'test',
'action' => null,
'uri' => '/uri',
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'The route action can not be empty.',
],
],
],
'Class string action with route name' => [
'routeName' => 'index',
'action' => Action::class,
'uri' => '/uri',
'pattern' => null,
'methods' => null,
'allowedException' => null,
],
'Class string action with route name null' => [
'routeName' => null,
'action' => Action::class,
'uri' => '/uri',
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'The route action should be callable. Examples: `[object, "method|<route_name>"], [Classname, "metnod|<routeName>"], Classname::method, Classname, function() {}` Current value: '.Action::class,
],
],
],
'Class string action with empty route name' => [
'routeName' => '',
'action' => Action::class,
'uri' => '/uri',
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'The route action should be callable. Examples: `[object, "method|<route_name>"], [Classname, "metnod|<routeName>"], Classname::method, Classname, function() {}` Current value: '.Action::class,
],
],
],
'All errors at the same time 1' => [
'routeName' => '1invalid@',
'action' => null,
'uri' => null,
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'Uri can not be empty.',
'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".',
'The route action can not be empty.',
],
],
],
'All errors at the same time 2' => [
'routeName' => '',
'action' => Action::class,
'uri' => null,
'pattern' => null,
'methods' => null,
'allowedException' => [
'class' => RouteInvalidConfigurationException::class,
'messages' => [
'Uri can not be empty.',
'The route action should be callable. Examples: `[object, "method|<route_name>"], [Classname, "metnod|<routeName>"], Classname::method, Classname, function() {}` Current value: '.Action::class,
],
],
],
'Correct GET route' => [
'routeName' => 'test',
'action' => $emptyRouteAction,
'uri' => '/uri',
'pattern' => null,
'methods' => ['GET'],
'allowedException' => null
],
'Correct POST route' => [
'routeName' => 'test',
'action' => $emptyRouteAction,
'uri' => '/uri',
'pattern' => null,
'methods' => ['POST'],
'allowedException' => null
],
'Simple dynamic uri' => [
'routeName' => 'test',
'action' => $emptyRouteAction,
'uri' => '/{test}',
'pattern' => '/\/([a-zA-Z_][a-zA-Z0-9_]*)$/',
'methods' => null,
'allowedException' => null
],
'Simple dynamic uri with two placeholders' => [
'routeName' => 'test',
'action' => $emptyRouteAction,
'uri' => '/{test}_{another_test}',
'pattern' => '/\/([a-zA-Z_][a-zA-Z0-9_]*)_([a-zA-Z_][a-zA-Z0-9_]*)$/',
'methods' => null,
'allowedException' => null
],
'Placeholder with default value' => [
'routeName' => 'test',
'action' => $emptyRouteAction,
'uri' => '/{page:0}',
'pattern' => '/\/([a-zA-Z_][a-zA-Z0-9_]*)$/',
'methods' => null,
'allowedException' => null
],
// TODO: implement regex for placeholders
// 'Placeholder with regex' => [
// 'routeName' => 'test',
// 'action' => $emptyRouteAction,
// 'uri' => '/{page}',
// 'placeholders_regex' => [
// 'page' => '[1-9]\\d*'; // any number without leading zeros
// ],
// 'pattern' => '/\/([a-zA-Z_][a-zA-Z_0-9]*)$/',
// 'methods' => null,
// 'allowedException' => null
// ],
];
}

Expand All @@ -101,3 +297,10 @@ public function testClear()
$this->assertNotEquals($routeA->getName(), $routeB->getName());
}
}

/**
* @internal
*/
final class Action {
public function index() {}
}

0 comments on commit 1559307

Please sign in to comment.