1 | <?php |
||
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 | } |