Completed
Push — master ( e32a6c...5f3630 )
by Taosikai
15:51 queued 54s
created

MatcherTest   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 287
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 6
dl 0
loc 287
c 0
b 0
f 0
rs 10
1
<?php
2
namespace Slince\Routing\Tests;
3
4
use PHPUnit\Framework\TestCase;
5
use Slince\Routing\Exception\MethodNotAllowedException;
6
use Slince\Routing\Exception\RouteNotFoundException;
7
use Slince\Routing\Matcher;
8
use Slince\Routing\RouteCollection;
9
use Slince\Routing\Route;
10
use Zend\Diactoros\ServerRequest;
11
use Zend\Diactoros\ServerRequestFactory;
12
13
class MatcherTest extends TestCase
14
{
15
    public function testSimpleMatch()
16
    {
17
        $routes = new RouteCollection();
18
        $route = new Route('/foo', 'action');
19
        $routes->add($route);
20
        $matcher = new Matcher($routes);
21
        $this->assertTrue ($route === $matcher->match('/foo'));
22
    }
23
24
    public function testMatch()
25
    {
26
        // test the patterns are matched and parameters are returned
27
        $routes = new RouteCollection();
28
        $route = $routes->create('/foo/{bar}', 'action1');
29
        $matcher = new Matcher($routes);
30
        try {
31
            $matcher->match('/no-match');
32
            $this->fail();
33
        } catch (RouteNotFoundException $e) {
34
        }
35
        $this->assertTrue ($route === $matcher->match('/foo/baz'));
36
        $this->assertEquals(['bar' => 'baz'], $route->getComputedParameters());
37
38
        // test defaults
39
        $routes = new RouteCollection();
40
        $routes->create('/foo/{bar}', 'action1')
41
            ->setDefaults(['def' => 'test']);
42
        $matcher = new Matcher($routes);
43
        $route = $matcher->match('/foo/baz');
44
        $this->assertEquals(['bar' => 'baz', 'def' => 'test'], $route->getComputedParameters());
45
46
        // test that route "method" is ignored if match simple path
47
        $routes = new RouteCollection();
48
        $route = $routes->create('/foo', 'action1')->setMethods(['GET', 'HEAD']);
49
        $matcher = new Matcher($routes);
50
        $this->assertTrue ($route === $matcher->match('/foo'));
51
52
        //optional placeholder
53
        $routes = new RouteCollection();
54
        $route = $routes->create('/foo/{bar}', 'action1')
55
            ->setDefaults(['bar' => 'baz']);
56
        $matcher = new Matcher($routes);
57
        $this->assertTrue ($route === $matcher->match('/foo'));
58
    }
59
60
    public function testHeadAllowedWhenMethodGet()
61
    {
62
        $routes = new RouteCollection();
63
        $route = $routes->get('/foo', 'foo');
64
        $matcher = new Matcher($routes);
65
        $request = new ServerRequest([], [], 'http://domain.com/foo', 'HEAD');
66
        $this->assertTrue ($route === $matcher->matchRequest($request));
67
    }
68
69
    public function testMethodNotAllowedMethods()
70
    {
71
        $routes = new RouteCollection();
72
        $routes->post('/foo', 'action1');
73
        $routes->create('/foo', 'action2')->setMethods(['put', 'delete']);
74
        $matcher = new Matcher($routes);
75
        try {
76
            $request = new ServerRequest(['HTTP_REQUEST_METHOD' => 'HEAD'], [], 'http://domain.com/foo');
77
            $matcher->matchRequest($request);
78
            $this->fail();
79
        } catch (MethodNotAllowedException $e) {
80
            $this->assertEquals(['POST', 'PUT', 'DELETE'], $e->getAllowedMethods());
81
        }
82
    }
83
84
    public function testMatchWithPrefix()
85
    {
86
        $routes = new RouteCollection();
87
        $routes->group('foo', function(RouteCollection $routes){
88
            $routes->get('/bar', 'action');
89
        });
90
        $matcher = new Matcher($routes);
91
        $this->assertTrue($matcher->match('/foo/bar') === $routes->getByAction('action'));
92
    }
93
94
    public function testMatchWithDynamicPrefix()
95
    {
96
        $routes = new RouteCollection();
97
        $routes->group('{locale}', function(RouteCollection $routes){
98
            $routes->get('/{foo}', 'action');
99
        });
100
        $matcher = new Matcher($routes);
101
        $this->assertEquals(['locale' => 'en', 'foo' => 'bar'], $matcher->match('/en/bar')->getComputedParameters());
102
    }
103
104
    public function testMatchSpecialRouteName()
105
    {
106
        $routes = new RouteCollection();
107
        $routes->create('/foo', 'action1')->setName('$péß^a|');
108
        $matcher = new Matcher($routes);
109
        $this->assertEquals('$péß^a|', $matcher->match('/foo')->getName());
110
    }
111
112
    public function testMatchNonAlpha()
113
    {
114
        $routes = new RouteCollection();
115
        $chars = '!"$%éà &\'()*+,./:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[]^_`abcdefghijklmnopqrstuvwxyz{|}~-';
116
        $routes->create('/{foo}/bar', 'action')
117
            ->setRequirements([
118
                'foo' => '['.preg_quote($chars).']+'
119
            ]);
120
        $matcher = new Matcher($routes);
121
        $this->assertEquals(['foo' => $chars], $matcher->match('/'.rawurlencode($chars).'/bar')->getComputedParameters());
122
        $this->assertEquals(['foo' => $chars], $matcher->match('/'.strtr($chars, array('%' => '%25')).'/bar')->getComputedParameters());
123
    }
124
125
126
    public function testMatchRegression()
127
    {
128
        $routes = new RouteCollection();
129
        $routes->create('/foo/{foo}', 'action');
130
        $routes->create('/foo/bar/{foo}', 'action2');
131
132
        $matcher = new Matcher($routes);
133
        $this->assertEquals(['foo' => 'bar'], $matcher->match('/foo/bar/bar')->getComputedParameters());
134
135
        $routes = new RouteCollection();
136
        $routes->create('/{bar}', 'action');
137
        $matcher = new Matcher($routes);
138
        $this->expectException(RouteNotFoundException::class);
139
        $matcher->match('/');
140
    }
141
142
    public function testDefaultRequirementForOptionalVariables()
143
    {
144
        $routes = new RouteCollection();
145
        $routes->create('/{page}.{_format}', 'action')->setDefaults([
146
            '_format' => 'html'
147
        ]);
148
        $matcher = new Matcher($routes);
149
        $this->assertEquals(['page' => 'personal-page', '_format' => 'xml'], $matcher->match('/personal-page.xml')
150
            ->getComputedParameters());
151
    }
152
153
    public function testMatchingIsEager()
154
    {
155
        $routes = new RouteCollection();
156
        $routes->create('/{foo}-{bar}-', 'action')
157
            ->setRequirements([
158
                'foo' => '.+',
159
                'bar' => '.+'
160
            ]);
161
        $matcher = new Matcher($routes);
162
        $this->assertEquals(['foo' => 'text1-text2-text3', 'bar' => 'text4'],
163
            $matcher->match('/text1-text2-text3-text4-')->getComputedParameters());
164
    }
165
166
    public function testAdjacentVariables()
167
    {
168
        $routes = new RouteCollection();
169
        $route = $routes->create('/{w}{x}{y}{z}.{_format}', 'action')
170
            ->setDefaults(['z' => 'default-z', '_format' => 'html'])
171
            ->setRequirements(['y' => 'y|Y']);
172
173
        $matcher = new Matcher($routes);
174
175
        $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'Y', 'z' => 'Z', '_format' => 'xml'),
176
            $matcher->match('/wwwwwxYZ.xml')->getComputedParameters());
177
178
        // As 'y' has custom requirement and can only be of value 'y|Y', it will leave  'ZZZ' to variable z.
179
        // So with carefully chosen requirements adjacent variables, can be useful.
180
        $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'ZZZ', '_format' => 'html'),
181
            $matcher->match('/wwwwwxyZZZ')->getComputedParameters());
182
183
        // z and _format are optional.
184
        $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'default-z', '_format' => 'html'),
185
            $matcher->match('/wwwwwxy')->getComputedParameters());
186
187
        $this->assertEquals(array('w' => 'w', 'x' => 'x', 'y' => 'y', 'z' => 'default-z', '_format' => 'xml'),
188
            $matcher->match('/wxy.xml')->getComputedParameters());
189
    }
190
191
    public function testOptionalVariableWithNoRealSeparator()
192
    {
193
        $routes = new RouteCollection();
194
        $routes->create('/get{what}', 'action')
195
            ->setDefaults(['what' => 'All']);
196
197
        $matcher = new Matcher($routes);
198
199
        $this->assertEquals(['what' => 'All'], $matcher->match('/get')->getComputedParameters());
200
        $this->assertEquals(['what' => 'Sites'], $matcher->match('/getSites')->getComputedParameters());
201
        $this->expectException(RouteNotFoundException::class);
202
        $matcher->match('/ge');
203
    }
204
205
    public function testRequiredVariableWithNoRealSeparator()
206
    {
207
        $routes = new RouteCollection();
208
        $routes->create('/get{what}Suffix', 'action');
209
        $matcher = new Matcher($routes);
210
        $this->assertEquals(['what' => 'Sites'], $matcher->match('/getSitesSuffix')->getComputedParameters());
211
    }
212
213
    public function testDefaultRequirementOfVariableDisallowsSlash()
214
    {
215
        $routes = new RouteCollection();
216
        $routes->create('/{page}.{_format}', 'action');
217
        $matcher = new Matcher($routes);
218
        $this->expectException(RouteNotFoundException::class);
219
        $matcher->match('/index.sl/ash');
220
    }
221
222
    public function testDefaultRequirementOfVariableDisallowsNextSeparator()
223
    {
224
        $routes = new RouteCollection();
225
        $routes->create('/{page}.{_format}', 'action')
226
            ->setDefaults(['_format' => 'html|xml']);
227
        $matcher = new Matcher($routes);
228
        $this->expectException(RouteNotFoundException::class);
229
        $matcher->match('/do.t.html');
230
    }
231
232
    public function testSchemeRequirement()
233
    {
234
        $routes = new RouteCollection();
235
        $routes->https('/foo', 'action')
236
            ->setDefaults(['_format' => 'html|xml']);
237
        $matcher = new Matcher($routes);
238
        $this->expectException(RouteNotFoundException::class);
239
        $request = new ServerRequest([], [], 'http://www.domain.com/foo');
240
        $matcher->matchRequest($request);
241
    }
242
243
    public function testCondition()
244
    {
245
        $routes = new RouteCollection();
246
        $routes->post('/foo', 'action');
247
        $matcher = new Matcher($routes);
248
        $this->expectException(MethodNotAllowedException::class);
249
        $request = new ServerRequest(['HTTP_REQUEST_METHOD' => 'post'], [], 'http://www.domain.com/foo');
250
        $matcher->matchRequest($request);
251
    }
252
253
    public function testDecodeOnce()
254
    {
255
        $routes = new RouteCollection();
256
        $routes->create('/foo/{foo}', 'action');
257
        $matcher = new Matcher($routes);
258
        $this->assertEquals(['foo' => 'bar%23'], $matcher->match('/foo/bar%2523')->getComputedParameters());
259
    }
260
261
    public function testWithHost()
262
    {
263
        $routes = new RouteCollection();
264
        $routes->create('/foo/{foo}', 'action')->setHost('{locale}.example.com');
265
266
        $matcher = new Matcher($routes);
267
        $request = new ServerRequest(['HTTP_REQUEST_METHOD' => 'post'], [], 'http://en.example.com/foo/bar');
268
        $this->assertEquals(['foo' => 'bar', 'locale' => 'en'], $matcher->matchRequest($request)->getComputedParameters());
269
    }
270
271
    public function testWithOutHostHostDoesNotMatch()
272
    {
273
        $routes = new RouteCollection();
274
        $routes->create('/foo/{foo}', 'action')->setHost('{locale}.example.com');
275
        $matcher = new Matcher($routes);
276
        $request = new ServerRequest(['HTTP_REQUEST_METHOD' => 'post'], [], 'http://example.com/foo/bar');
277
        $this->expectException(RouteNotFoundException::class);
278
        $matcher->matchRequest($request);
279
    }
280
281
    public function testPathIsCaseSensitive()
282
    {
283
        $routes = new RouteCollection();
284
        $routes->create('/locale','action')->setRequirements(['locale' => 'EN|FR|DE']);
285
        $matcher = new Matcher($routes);
286
        $this->expectException(RouteNotFoundException::class);
287
        $matcher->match('/en');
288
    }
289
290
    public function testHostIsCaseInsensitive()
291
    {
292
        $routes = new RouteCollection();
293
        $routes->create('/','action')->setRequirements(['locale' => 'EN|FR|DE'])
294
            ->setHost('{locale}.example.com');
295
        $matcher = new Matcher($routes);
296
        $request = new ServerRequest(['HTTP_REQUEST_METHOD' => 'post'], [], 'http://en.example.com/');
297
        $this->assertEquals(array('locale' => 'en'), $matcher->matchRequest($request)->getComputedParameters());
298
    }
299
}