Skip to content

Commit 90cc411

Browse files
committed
Fix: more precise tests
- Added more tests; - Fixed RouteBuilder according to new tests;
1 parent 19ec43f commit 90cc411

3 files changed

Lines changed: 235 additions & 26 deletions

File tree

src/Business/Route/RouteBuilder.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace Micro\Plugin\Http\Business\Route;
1515

1616
use Micro\Plugin\Http\Exception\RouteInvalidConfigurationException;
17+
use Symfony\Component\HttpFoundation\Request;
1718

1819
/**
1920
* @author Stanislau Komar <kost@micro-php.net>
@@ -39,7 +40,11 @@ class RouteBuilder implements RouteBuilderInterface
3940
*/
4041
public function __construct(
4142
private readonly array $methodsByDefault = [
42-
'PUT', 'POST', 'PATCH', 'GET', 'DELETE',
43+
Request::METHOD_GET,
44+
Request::METHOD_POST,
45+
Request::METHOD_PUT,
46+
Request::METHOD_PATCH,
47+
Request::METHOD_DELETE,
4348
],
4449
) {
4550
$this->name = null;
@@ -114,17 +119,15 @@ public function build(): RouteInterface
114119
$exceptions = [];
115120

116121
if (!$this->uri) {
117-
$this->uri = '';
118-
119122
$exceptions[] = 'Uri can not be empty.';
120123
}
121124

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

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

130133
if (
@@ -138,7 +141,7 @@ public function build(): RouteInterface
138141
}
139142

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

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

161164
foreach ($matches[0] as $replaced) {
162-
$pattern = str_replace($replaced, '(.[aA-zZ0-9-_]+)', $pattern);
165+
$pattern = str_replace($replaced, '([a-zA-Z_][a-zA-Z0-9_]*)', $pattern);
163166
}
164167
}
165168
/**

src/Exception/RouteInvalidConfigurationException.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ public function __construct(string $routeName, array $messages, int $code = 0, ?
3030
{
3131
$message = <<<EOF
3232
Invalid route "%s" configuration:
33-
* %s
34-
EOF;
33+
* %s
34+
EOF;
3535

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

3838
$this->messages = $messages;
3939
parent::__construct(

tests/Unit/Business/Route/RouteBuilderTest.php

Lines changed: 221 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,20 @@
1818
class RouteBuilderTest extends TestCase
1919
{
2020
public const METHOD_DEFAULTS = [
21-
'PUT', 'POST', 'PATCH', 'GET', 'DELETE',
21+
'GET', 'POST', 'PUT', 'PATCH', 'DELETE',
2222
];
2323

2424
/**
2525
* @dataProvider dataProvider
26-
*
27-
* @return void
2826
*/
2927
public function testBuild(
3028
string|null $routeName,
31-
callable|null $action,
29+
callable|string|null $action,
3230
string|null $uri,
3331
string|null $pattern,
3432
array|null $methods,
35-
string|null $allowedException
36-
) {
33+
array|null $allowedException
34+
): void {
3735
$builder = new RouteBuilder();
3836

3937
if ($routeName) {
@@ -53,33 +51,231 @@ public function testBuild(
5351
}
5452

5553
if ($allowedException) {
56-
$this->expectException($allowedException);
54+
$this->expectException($allowedException['class']);
5755
}
5856

5957
try {
6058
$route = $builder->build();
6159
} catch (RouteInvalidConfigurationException $configurationException) {
6260
$this->assertNotEmpty($configurationException->getMessages());
61+
$this->assertEquals($allowedException['messages'], $configurationException->getMessages());
6362

6463
throw $configurationException;
6564
}
6665

67-
$this->assertIsCallable($route->getController());
66+
$this->assertEquals($action, $route->getController());
6867
$this->assertEquals($uri, $route->getUri());
6968
$this->assertEquals($methods ?: self::METHOD_DEFAULTS, $route->getMethods());
7069
$this->assertEquals($pattern, $route->getPattern());
7170
$this->assertEquals($routeName, $route->getName());
7271
}
7372

74-
public function dataProvider()
73+
public static function dataProvider(): array
7574
{
75+
$emptyRouteAction = static function () {};
76+
7677
return [
77-
['test', function () {}, '/{test}.{_format}', '/\/(.[aA-zZ0-9-_]+)\.(.[aA-zZ0-9-_]+)$/', ['POST'], null],
78-
['test', function () {}, '/{test}-{date}.{_format}', '/\/(.[aA-zZ0-9-_]+)-(.[aA-zZ0-9-_]+)\.(.[aA-zZ0-9-_]+)$/', ['POST'], null],
79-
[null, function () {}, '/{test}.{_format}', '/\/(.[aA-zZ0-9-_]+)\.(.[aA-zZ0-9-_]+)$/', null, null],
80-
['test', null, '/{test}.{_format}', null, null, RouteInvalidConfigurationException::class],
81-
['test', function () {}, '/test', null, null, null],
82-
['test', null, null, null, null, RouteInvalidConfigurationException::class],
78+
'Correct minimal route: just action and uri' => [
79+
'routeName' => null,
80+
'action' => $emptyRouteAction,
81+
'uri' => '/uri',
82+
'pattern' => null,
83+
'methods' => null,
84+
'allowedException' => null,
85+
],
86+
'Empty uri: null' => [
87+
'routeName' => null,
88+
'action' => $emptyRouteAction,
89+
'uri' => null,
90+
'pattern' => null,
91+
'methods' => null,
92+
'allowedException' => [
93+
'class' => RouteInvalidConfigurationException::class,
94+
'messages' => [
95+
'Uri can not be empty.',
96+
],
97+
],
98+
],
99+
'Empty uri: empty string' => [
100+
'routeName' => null,
101+
'action' => $emptyRouteAction,
102+
'uri' => '',
103+
'pattern' => null,
104+
'methods' => null,
105+
'allowedException' => [
106+
'class' => RouteInvalidConfigurationException::class,
107+
'messages' => [
108+
'Uri can not be empty.',
109+
],
110+
],
111+
],
112+
'Invalid route name 1' => [
113+
'routeName' => '0invalid',
114+
'action' => $emptyRouteAction,
115+
'uri' => '/uri',
116+
'pattern' => null,
117+
'methods' => null,
118+
'allowedException' => [
119+
'class' => RouteInvalidConfigurationException::class,
120+
'messages' => [
121+
'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".',
122+
],
123+
],
124+
],
125+
'Invalid route name 2' => [
126+
'routeName' => '.invalid',
127+
'action' => $emptyRouteAction,
128+
'uri' => '/uri',
129+
'pattern' => null,
130+
'methods' => null,
131+
'allowedException' => [
132+
'class' => RouteInvalidConfigurationException::class,
133+
'messages' => [
134+
'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".',
135+
],
136+
],
137+
],
138+
'Invalid route name 3' => [
139+
'routeName' => 'invalid@again',
140+
'action' => $emptyRouteAction,
141+
'uri' => '/uri',
142+
'pattern' => null,
143+
'methods' => null,
144+
'allowedException' => [
145+
'class' => RouteInvalidConfigurationException::class,
146+
'messages' => [
147+
'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".',
148+
],
149+
],
150+
],
151+
'Empty action' => [
152+
'routeName' => 'test',
153+
'action' => null,
154+
'uri' => '/uri',
155+
'pattern' => null,
156+
'methods' => null,
157+
'allowedException' => [
158+
'class' => RouteInvalidConfigurationException::class,
159+
'messages' => [
160+
'The route action can not be empty.',
161+
],
162+
],
163+
],
164+
'Class string action with route name' => [
165+
'routeName' => 'index',
166+
'action' => Action::class,
167+
'uri' => '/uri',
168+
'pattern' => null,
169+
'methods' => null,
170+
'allowedException' => null,
171+
],
172+
'Class string action with route name null' => [
173+
'routeName' => null,
174+
'action' => Action::class,
175+
'uri' => '/uri',
176+
'pattern' => null,
177+
'methods' => null,
178+
'allowedException' => [
179+
'class' => RouteInvalidConfigurationException::class,
180+
'messages' => [
181+
'The route action should be callable. Examples: `[object, "method|<route_name>"], [Classname, "metnod|<routeName>"], Classname::method, Classname, function() {}` Current value: '.Action::class,
182+
],
183+
],
184+
],
185+
'Class string action with empty route name' => [
186+
'routeName' => '',
187+
'action' => Action::class,
188+
'uri' => '/uri',
189+
'pattern' => null,
190+
'methods' => null,
191+
'allowedException' => [
192+
'class' => RouteInvalidConfigurationException::class,
193+
'messages' => [
194+
'The route action should be callable. Examples: `[object, "method|<route_name>"], [Classname, "metnod|<routeName>"], Classname::method, Classname, function() {}` Current value: '.Action::class,
195+
],
196+
],
197+
],
198+
'All errors at the same time 1' => [
199+
'routeName' => '1invalid@',
200+
'action' => null,
201+
'uri' => null,
202+
'pattern' => null,
203+
'methods' => null,
204+
'allowedException' => [
205+
'class' => RouteInvalidConfigurationException::class,
206+
'messages' => [
207+
'Uri can not be empty.',
208+
'The route name must match "[a-zA-Z][a-zA-Z0-9_]*".',
209+
'The route action can not be empty.',
210+
],
211+
],
212+
],
213+
'All errors at the same time 2' => [
214+
'routeName' => '',
215+
'action' => Action::class,
216+
'uri' => null,
217+
'pattern' => null,
218+
'methods' => null,
219+
'allowedException' => [
220+
'class' => RouteInvalidConfigurationException::class,
221+
'messages' => [
222+
'Uri can not be empty.',
223+
'The route action should be callable. Examples: `[object, "method|<route_name>"], [Classname, "metnod|<routeName>"], Classname::method, Classname, function() {}` Current value: '.Action::class,
224+
],
225+
],
226+
],
227+
'Correct GET route' => [
228+
'routeName' => 'test',
229+
'action' => $emptyRouteAction,
230+
'uri' => '/uri',
231+
'pattern' => null,
232+
'methods' => ['GET'],
233+
'allowedException' => null,
234+
],
235+
'Correct POST route' => [
236+
'routeName' => 'test',
237+
'action' => $emptyRouteAction,
238+
'uri' => '/uri',
239+
'pattern' => null,
240+
'methods' => ['POST'],
241+
'allowedException' => null,
242+
],
243+
'Simple dynamic uri' => [
244+
'routeName' => 'test',
245+
'action' => $emptyRouteAction,
246+
'uri' => '/{test}',
247+
'pattern' => '/\/([a-zA-Z_][a-zA-Z0-9_]*)$/',
248+
'methods' => null,
249+
'allowedException' => null,
250+
],
251+
'Simple dynamic uri with two placeholders' => [
252+
'routeName' => 'test',
253+
'action' => $emptyRouteAction,
254+
'uri' => '/{test}_{another_test}',
255+
'pattern' => '/\/([a-zA-Z_][a-zA-Z0-9_]*)_([a-zA-Z_][a-zA-Z0-9_]*)$/',
256+
'methods' => null,
257+
'allowedException' => null,
258+
],
259+
'Placeholder with default value' => [
260+
'routeName' => 'test',
261+
'action' => $emptyRouteAction,
262+
'uri' => '/{page:0}',
263+
'pattern' => '/\/([a-zA-Z_][a-zA-Z0-9_]*)$/',
264+
'methods' => null,
265+
'allowedException' => null,
266+
],
267+
// TODO: implement regex for placeholders
268+
// 'Placeholder with regex' => [
269+
// 'routeName' => 'test',
270+
// 'action' => $emptyRouteAction,
271+
// 'uri' => '/{page}',
272+
// 'placeholders_regex' => [
273+
// 'page' => '[1-9]\\d*'; // any number without leading zeros
274+
// ],
275+
// 'pattern' => '/\/([a-zA-Z_][a-zA-Z_0-9]*)$/',
276+
// 'methods' => null,
277+
// 'allowedException' => null
278+
// ],
83279
];
84280
}
85281

@@ -101,3 +297,13 @@ public function testClear()
101297
$this->assertNotEquals($routeA->getName(), $routeB->getName());
102298
}
103299
}
300+
301+
/**
302+
* @internal
303+
*/
304+
final class Action
305+
{
306+
public function index()
307+
{
308+
}
309+
}

0 commit comments

Comments
 (0)