1<?php defined('SYSPATH') OR die('Kohana bootstrap needs to be included before tests run');
2
3/**
4 * Description of RouteTest
5 *
6 * @group kohana
7 * @group kohana.core
8 * @group kohana.core.route
9 *
10 * @package    Kohana
11 * @category   Tests
12 * @author     Kohana Team
13 * @author     BRMatt <matthew@sigswitch.com>
14 * @copyright  (c) 2008-2012 Kohana Team
15 * @license    http://kohanaframework.org/license
16 */
17
18include Kohana::find_file('tests', 'test_data/callback_routes');
19
20class Kohana_RouteTest extends Unittest_TestCase
21{
22	/**
23	 * Remove all caches
24	 */
25	// @codingStandardsIgnoreStart
26	public function setUp()
27	// @codingStandardsIgnoreEnd
28	{
29		parent::setUp();
30
31		Kohana::$config->load('url')->set('trusted_hosts', array('kohanaframework\.org'));
32
33		$this->cleanCacheDir();
34	}
35
36	/**
37	 * Removes cache files created during tests
38	 */
39	// @codingStandardsIgnoreStart
40	public function tearDown()
41	// @codingStandardsIgnoreEnd
42	{
43		parent::tearDown();
44
45		$this->cleanCacheDir();
46	}
47
48	/**
49	 * If Route::get() is asked for a route that does not exist then
50	 * it should throw a Kohana_Exception
51	 *
52	 * Note use of @expectedException
53	 *
54	 * @test
55	 * @covers Route::get
56	 * @expectedException Kohana_Exception
57	 */
58	public function test_get_throws_exception_if_route_dnx()
59	{
60		Route::get('HAHAHAHAHAHAHAHAHA');
61	}
62
63	/**
64	 * Route::all() should return all routes defined via Route::set()
65	 * and not through new Route()
66	 *
67	 * @test
68	 * @covers Route::all
69	 */
70	public function test_all_returns_all_defined_routes()
71	{
72		$defined_routes = self::readAttribute('Route', '_routes');
73
74		$this->assertSame($defined_routes, Route::all());
75	}
76
77	/**
78	 * Route::name() should fetch the name of a passed route
79	 * If route is not found then it should return FALSE
80	 *
81	 * @TODO: This test needs to segregate the Route::$_routes singleton
82	 * @test
83	 * @covers Route::name
84	 */
85	public function test_name_returns_routes_name_or_false_if_dnx()
86	{
87		$route = Route::set('flamingo_people', 'flamingo/dance');
88
89		$this->assertSame('flamingo_people', Route::name($route));
90
91		$route = new Route('dance/dance');
92
93		$this->assertFalse(Route::name($route));
94	}
95
96	/**
97	 * If Route::cache() was able to restore routes from the cache then
98	 * it should return TRUE and load the cached routes
99	 *
100	 * @test
101	 * @covers Route::cache
102	 */
103	public function test_cache_stores_route_objects()
104	{
105		$routes = Route::all();
106
107		// First we create the cache
108		Route::cache(TRUE);
109
110		// Now lets modify the "current" routes
111		Route::set('nonsensical_route', 'flabbadaga/ding_dong');
112
113		// Then try and load said cache
114		$this->assertTrue(Route::cache());
115
116		// Check the route cache flag
117		$this->assertTrue(Route::$cache);
118
119		// And if all went ok the nonsensical route should be gone...
120		$this->assertEquals($routes, Route::all());
121	}
122
123	/**
124	 * Check appending cached routes. See http://dev.kohanaframework.org/issues/4347
125	 *
126	 * @test
127	 * @covers Route::cache
128	 */
129	public function test_cache_append_routes()
130	{
131		$cached = Route::all();
132
133		// First we create the cache
134		Route::cache(TRUE);
135
136		// Now lets modify the "current" routes
137		Route::set('nonsensical_route', 'flabbadaga/ding_dong');
138
139		$modified = Route::all();
140
141		// Then try and load said cache
142		$this->assertTrue(Route::cache(NULL, TRUE));
143
144		// Check the route cache flag
145		$this->assertTrue(Route::$cache);
146
147		// And if all went ok the nonsensical route should exist with the other routes...
148		$this->assertEquals(Route::all(), $cached + $modified);
149	}
150
151	/**
152	 * Route::cache() should return FALSE if cached routes could not be found
153	 *
154	 * The cache is cleared before and after each test in setUp tearDown
155	 * by cleanCacheDir()
156	 *
157	 * @test
158	 * @covers Route::cache
159	 */
160	public function test_cache_returns_false_if_cache_dnx()
161	{
162		$this->assertSame(FALSE, Route::cache(), 'Route cache was not empty');
163
164		// Check the route cache flag
165		$this->assertFalse(Route::$cache);
166	}
167
168	/**
169	 * If the constructor is passed a NULL uri then it should assume it's
170	 * being loaded from the cache & therefore shouldn't override the cached attributes
171	 *
172	 * @test
173	 * @covers Route::__construct
174	 */
175	public function test_constructor_returns_if_uri_is_null()
176	{
177		// We use a mock object to make sure that the route wasn't recompiled
178		$route = $this->getMock('Route', array('_compile'), array(), '', FALSE);
179
180		$route
181			->expects($this->never())
182			->method('_compile');
183
184		$route->__construct(NULL,NULL);
185
186		$this->assertAttributeSame('', '_uri', $route);
187		$this->assertAttributeSame(array(), '_regex', $route);
188		$this->assertAttributeSame(array('action' => 'index', 'host' => FALSE), '_defaults', $route);
189		$this->assertAttributeSame(NULL, '_route_regex', $route);
190	}
191
192	/**
193	 * Provider for test_constructor_only_changes_custom_regex_if_passed
194	 *
195	 * @return array
196	 */
197	public function provider_constructor_only_changes_custom_regex_if_passed()
198	{
199		return array(
200			array('<controller>/<action>', '<controller>/<action>'),
201		);
202	}
203
204	/**
205	 * The constructor should only use custom regex if passed a non-empty array
206	 *
207	 * Technically we can't "test" this as the default regex is an empty array, this
208	 * is purely for improving test coverage
209	 *
210	 * @dataProvider provider_constructor_only_changes_custom_regex_if_passed
211	 *
212	 * @test
213	 * @covers Route::__construct
214	 */
215	public function test_constructor_only_changes_custom_regex_if_passed($uri, $uri2)
216	{
217		$route = new Route($uri, array());
218
219		$this->assertAttributeSame(array(), '_regex', $route);
220
221		$route = new Route($uri2, NULL);
222
223		$this->assertAttributeSame(array(), '_regex', $route);
224	}
225
226	/**
227	 * When we pass custom regex to the route's constructor it should it
228	 * in leu of the default. This does not apply to callback/lambda routes
229	 *
230	 * @test
231	 * @covers Route::__construct
232	 * @covers Route::compile
233	 */
234	public function test_route_uses_custom_regex_passed_to_constructor()
235	{
236		$regex = array('id' => '[0-9]{1,2}');
237
238		$route = new Route('<controller>(/<action>(/<id>))', $regex);
239
240		$this->assertAttributeSame($regex, '_regex', $route);
241		$this->assertAttributeContains(
242			$regex['id'],
243			'_route_regex',
244			$route
245		);
246	}
247
248	/**
249	 * Provider for test_matches_returns_false_on_failure
250	 *
251	 * @return array
252	 */
253	public function provider_matches_returns_false_on_failure()
254	{
255		return array(
256			array('projects/(<project_id>/(<controller>(/<action>(/<id>))))', 'apple/pie'),
257		);
258	}
259
260	/**
261	 * Route::matches() should return false if the route doesn't match against a uri
262	 *
263	 * @dataProvider provider_matches_returns_false_on_failure
264	 *
265	 * @test
266	 * @covers Route::matches
267	 */
268	public function test_matches_returns_false_on_failure($uri, $match)
269	{
270		$route = new Route($uri);
271
272		// Mock a request class with the $match uri
273		$stub = $this->get_request_mock($match);
274
275		$this->assertSame(FALSE, $route->matches($stub));
276	}
277
278	/**
279	 * Provider for test_matches_returns_array_of_parameters_on_successful_match
280	 *
281	 * @return array
282	 */
283	public function provider_matches_returns_array_of_parameters_on_successful_match()
284	{
285		return array(
286			array(
287				'(<controller>(/<action>(/<id>)))',
288				'welcome/index',
289				'Welcome',
290				'index',
291			),
292		);
293	}
294
295	/**
296	 * Route::matches() should return an array of parameters when a match is made
297	 * An parameters that are not matched should not be present in the array of matches
298	 *
299	 * @dataProvider provider_matches_returns_array_of_parameters_on_successful_match
300	 *
301	 * @test
302	 * @covers Route::matches
303	 */
304	public function test_matches_returns_array_of_parameters_on_successful_match($uri, $m, $c, $a)
305	{
306		$route = new Route($uri);
307
308		// Mock a request class with the $m uri
309		$request = $this->get_request_mock($m);
310
311		$matches = $route->matches($request);
312
313		$this->assertInternalType('array', $matches);
314		$this->assertArrayHasKey('controller', $matches);
315		$this->assertArrayHasKey('action', $matches);
316		$this->assertArrayNotHasKey('id', $matches);
317		// $this->assertSame(5, count($matches));
318		$this->assertSame($c, $matches['controller']);
319		$this->assertSame($a, $matches['action']);
320	}
321
322	/**
323	 * Provider for test_matches_returns_array_of_parameters_on_successful_match
324	 *
325	 * @return array
326	 */
327	public function provider_defaults_are_used_if_params_arent_specified()
328	{
329		return array(
330			array(
331				'<controller>(/<action>(/<id>))',
332				NULL,
333				array('controller' => 'Welcome', 'action' => 'index'),
334				'Welcome',
335				'index',
336				'unit/test/1',
337				array(
338					'controller' => 'unit',
339					'action' => 'test',
340					'id' => '1'
341				),
342				'Welcome',
343			),
344			array(
345				'(<controller>(/<action>(/<id>)))',
346				NULL,
347				array('controller' => 'welcome', 'action' => 'index'),
348				'Welcome',
349				'index',
350				'unit/test/1',
351				array(
352					'controller' => 'unit',
353					'action' => 'test',
354					'id' => '1'
355				),
356				'',
357			),
358		);
359	}
360
361	/**
362	 * Defaults specified with defaults() should be used if their values aren't
363	 * present in the uri
364	 *
365	 * @dataProvider provider_defaults_are_used_if_params_arent_specified
366	 *
367	 * @test
368	 * @covers Route::matches
369	 */
370	public function test_defaults_are_used_if_params_arent_specified($uri, $regex, $defaults, $c, $a, $test_uri, $test_uri_array, $default_uri)
371	{
372		$route = new Route($uri, $regex);
373		$route->defaults($defaults);
374
375		$this->assertSame($defaults, $route->defaults());
376
377		// Mock a request class
378		$request = $this->get_request_mock($default_uri);
379
380		$matches = $route->matches($request);
381
382		$this->assertInternalType('array', $matches);
383		$this->assertArrayHasKey('controller', $matches);
384		$this->assertArrayHasKey('action', $matches);
385		$this->assertArrayNotHasKey('id', $matches);
386		// $this->assertSame(4, count($matches));
387		$this->assertSame($c, $matches['controller']);
388		$this->assertSame($a, $matches['action']);
389		$this->assertSame($test_uri, $route->uri($test_uri_array));
390		$this->assertSame($default_uri, $route->uri());
391	}
392
393	/**
394	 * Provider for test_optional_groups_containing_specified_params
395	 *
396	 * @return array
397	 */
398	public function provider_optional_groups_containing_specified_params()
399	{
400		return array(
401			/**
402			 * Specifying this should cause controller and action to show up
403			 * refs #4113
404			 */
405			array(
406				'(<controller>(/<action>(/<id>)))',
407				array('controller' => 'welcome', 'action' => 'index'),
408				array('id' => '1'),
409				'welcome/index/1',
410			),
411			array(
412				'<controller>(/<action>(/<id>))',
413				array('controller' => 'welcome', 'action' => 'index'),
414				array('action' => 'foo'),
415				'welcome/foo',
416			),
417			array(
418				'<controller>(/<action>(/<id>))',
419				array('controller' => 'welcome', 'action' => 'index'),
420				array('action' => 'index'),
421				'welcome',
422			),
423			/**
424			 * refs #4630
425			 */
426			array(
427				'api(/<version>)/const(/<id>)(/<custom>)',
428				array('version' => 1),
429				NULL,
430				'api/const',
431			),
432			array(
433				'api(/<version>)/const(/<id>)(/<custom>)',
434				array('version' => 1),
435				array('version' => 9),
436				'api/9/const',
437			),
438			array(
439				'api(/<version>)/const(/<id>)(/<custom>)',
440				array('version' => 1),
441				array('id' => 2),
442				'api/const/2',
443			),
444			array(
445				'api(/<version>)/const(/<id>)(/<custom>)',
446				array('version' => 1),
447				array('custom' => 'x'),
448				'api/const/x',
449			),
450			array(
451				'(<controller>(/<action>(/<id>)(/<type>)))',
452				array('controller' => 'test', 'action' => 'index', 'type' => 'html'),
453				array('type' => 'json'),
454				'test/index/json',
455			),
456			array(
457				'(<controller>(/<action>(/<id>)(/<type>)))',
458				array('controller' => 'test', 'action' => 'index', 'type' => 'html'),
459				array('id' => 123),
460				'test/index/123',
461			),
462			array(
463				'(<controller>(/<action>(/<id>)(/<type>)))',
464				array('controller' => 'test', 'action' => 'index', 'type' => 'html'),
465				array('id' => 123, 'type' => 'html'),
466				'test/index/123',
467			),
468			array(
469				'(<controller>(/<action>(/<id>)(/<type>)))',
470				array('controller' => 'test', 'action' => 'index', 'type' => 'html'),
471				array('id' => 123, 'type' => 'json'),
472				'test/index/123/json',
473			),
474		);
475	}
476
477	/**
478	 * When an optional param is specified, the optional params leading up to it
479	 * must be in the URI.
480	 *
481	 * @dataProvider provider_optional_groups_containing_specified_params
482	 *
483	 * @ticket 4113
484	 * @ticket 4630
485	 */
486	public function test_optional_groups_containing_specified_params($uri, $defaults, $params, $expected)
487	{
488		$route = new Route($uri, NULL);
489		$route->defaults($defaults);
490
491		$this->assertSame($expected, $route->uri($params));
492	}
493
494	/**
495	 * Optional params should not be used if what is passed in is identical
496	 * to the default.
497	 *
498	 * refs #4116
499	 *
500	 * @test
501	 * @covers Route::uri
502	 */
503	public function test_defaults_are_not_used_if_param_is_identical()
504	{
505		$route = new Route('(<controller>(/<action>(/<id>)))');
506		$route->defaults(array(
507			'controller' => 'welcome',
508			'action'     => 'index'
509		));
510
511		$this->assertSame('', $route->uri(array('controller' => 'welcome')));
512		$this->assertSame('welcome2', $route->uri(array('controller' => 'welcome2')));
513	}
514
515	/**
516	 * Provider for test_required_parameters_are_needed
517	 *
518	 * @return array
519	 */
520	public function provider_required_parameters_are_needed()
521	{
522		return array(
523			array(
524				'admin(/<controller>(/<action>(/<id>)))',
525				'admin',
526				'admin/users/add',
527			),
528		);
529	}
530
531	/**
532	 * This tests that routes with required parameters will not match uris without them present
533	 *
534	 * @dataProvider provider_required_parameters_are_needed
535	 *
536	 * @test
537	 * @covers Route::matches
538	 */
539	public function test_required_parameters_are_needed($uri, $matches_route1, $matches_route2)
540	{
541		$route = new Route($uri);
542
543		// Mock a request class that will return empty uri
544		$request = $this->get_request_mock('');
545
546		$this->assertFalse($route->matches($request));
547
548		// Mock a request class that will return route1
549		$request = $this->get_request_mock($matches_route1);
550
551		$matches = $route->matches($request);
552
553		$this->assertInternalType('array', $matches);
554
555		// Mock a request class that will return route2 uri
556		$request = $this->get_request_mock($matches_route2);
557
558		$matches = $route->matches($request);
559
560		$this->assertInternalType('array', $matches);
561		// $this->assertSame(5, count($matches));
562		$this->assertArrayHasKey('controller', $matches);
563		$this->assertArrayHasKey('action', $matches);
564	}
565
566	/**
567	 * Provider for test_required_parameters_are_needed
568	 *
569	 * @return array
570	 */
571	public function provider_reverse_routing_returns_routes_uri_if_route_is_static()
572	{
573		return array(
574			array(
575				'info/about_us',
576				NULL,
577				'info/about_us',
578				array('some' => 'random', 'params' => 'to confuse'),
579			),
580		);
581	}
582
583	/**
584	 * This tests the reverse routing returns the uri specified in the route
585	 * if it's a static route
586	 *
587	 * A static route is a route without any parameters
588	 *
589	 * @dataProvider provider_reverse_routing_returns_routes_uri_if_route_is_static
590	 *
591	 * @test
592	 * @covers Route::uri
593	 */
594	public function test_reverse_routing_returns_routes_uri_if_route_is_static($uri, $regex, $target_uri, $uri_params)
595	{
596		$route = new Route($uri, $regex);
597
598		$this->assertSame($target_uri, $route->uri($uri_params));
599	}
600
601	/**
602	 * Provider for test_uri_throws_exception_if_required_params_are_missing
603	 *
604	 * @return array
605	 */
606	public function provider_uri_throws_exception_if_required_params_are_missing()
607	{
608		return array(
609			array(
610				'<controller>(/<action)',
611				NULL,
612				array('action' => 'awesome-action'),
613			),
614			/**
615			 * Optional params are required when they lead to a specified param
616			 * refs #4113
617			 */
618			array(
619				'(<controller>(/<action>))',
620				NULL,
621				array('action' => 'awesome-action'),
622			),
623		);
624	}
625
626	/**
627	 * When Route::uri is working on a uri that requires certain parameters to be present
628	 * (i.e. <controller> in '<controller(/<action)') then it should throw an exception
629	 * if the param was not provided
630	 *
631	 * @dataProvider provider_uri_throws_exception_if_required_params_are_missing
632	 *
633	 * @test
634	 * @covers Route::uri
635	 */
636	public function test_uri_throws_exception_if_required_params_are_missing($uri, $regex, $uri_array)
637	{
638		$route = new Route($uri, $regex);
639
640		$this->setExpectedException('Kohana_Exception', 'controller');
641		$route->uri($uri_array);
642	}
643
644	/**
645	 * Provider for test_uri_fills_required_uri_segments_from_params
646	 *
647	 * @return array
648	 */
649	public function provider_uri_fills_required_uri_segments_from_params()
650	{
651		return array(
652			array(
653				'<controller>/<action>(/<id>)',
654				NULL,
655				'users/edit',
656				array(
657					'controller' => 'users',
658					'action'     => 'edit',
659				),
660				'users/edit/god',
661				array(
662					'controller' => 'users',
663					'action'     => 'edit',
664					'id'         => 'god',
665				),
666			),
667		);
668	}
669
670	/**
671	 * The logic for replacing required segments is separate (but similar) to that for
672	 * replacing optional segments.
673	 *
674	 * This test asserts that Route::uri will replace required segments with provided
675	 * params
676	 *
677	 * @dataProvider provider_uri_fills_required_uri_segments_from_params
678	 *
679	 * @test
680	 * @covers Route::uri
681	 */
682	public function test_uri_fills_required_uri_segments_from_params($uri, $regex, $uri_string1, $uri_array1, $uri_string2, $uri_array2)
683	{
684		$route = new Route($uri, $regex);
685
686		$this->assertSame(
687			$uri_string1,
688			$route->uri($uri_array1)
689		);
690
691		$this->assertSame(
692			$uri_string2,
693			$route->uri($uri_array2)
694		);
695	}
696
697	/**
698	 * Provides test data for test_composing_url_from_route()
699	 * @return array
700	 */
701	public function provider_composing_url_from_route()
702	{
703		return array(
704			array('/'),
705			array('/news/view/42', array('controller' => 'news', 'action' => 'view', 'id' => 42)),
706			array('http://kohanaframework.org/news', array('controller' => 'news'), 'http')
707		);
708	}
709
710	/**
711	 * Tests Route::url()
712	 *
713	 * Checks the url composing from specific route via Route::url() shortcut
714	 *
715	 * @test
716	 * @dataProvider provider_composing_url_from_route
717	 * @param string $expected
718	 * @param array $params
719	 * @param boolean $protocol
720	 */
721	public function test_composing_url_from_route($expected, $params = NULL, $protocol = NULL)
722	{
723		Route::set('foobar', '(<controller>(/<action>(/<id>)))')
724			->defaults(array(
725				'controller' => 'welcome',
726			)
727		);
728
729		$this->setEnvironment(array(
730			'_SERVER' => array('HTTP_HOST' => 'kohanaframework.org'),
731			'Kohana::$base_url' => '/',
732			'Kohana::$index_file' => '',
733		));
734
735		$this->assertSame($expected, Route::url('foobar', $params, $protocol));
736	}
737
738	/**
739	 * Tests Route::compile()
740	 *
741	 * Makes sure that compile will use custom regex if specified
742	 *
743	 * @test
744	 * @covers Route::compile
745	 */
746	public function test_compile_uses_custom_regex_if_specificed()
747	{
748		$compiled = Route::compile(
749			'<controller>(/<action>(/<id>))',
750			array(
751				'controller' => '[a-z]+',
752				'id' => '\d+',
753			)
754		);
755
756		$this->assertSame('#^(?P<controller>[a-z]+)(?:/(?P<action>[^/.,;?\n]++)(?:/(?P<id>\d+))?)?$#uD', $compiled);
757	}
758
759	/**
760	 * Tests Route::is_external(), ensuring the host can return
761	 * whether internal or external host
762	 */
763	public function test_is_external_route_from_host()
764	{
765		// Setup local route
766		Route::set('internal', 'local/test/route')
767			->defaults(array(
768				'controller' => 'foo',
769				'action'     => 'bar'
770				)
771			);
772
773		// Setup external route
774		Route::set('external', 'local/test/route')
775			->defaults(array(
776				'controller' => 'foo',
777				'action'     => 'bar',
778				'host'       => 'http://kohanaframework.org'
779				)
780			);
781
782		// Test internal route
783		$this->assertFalse(Route::get('internal')->is_external());
784
785		// Test external route
786		$this->assertTrue(Route::get('external')->is_external());
787	}
788
789	/**
790	 * Provider for test_external_route_includes_params_in_uri
791	 *
792	 * @return array
793	 */
794	public function provider_external_route_includes_params_in_uri()
795	{
796		return array(
797			array(
798				'<controller>/<action>',
799				array(
800					'controller'  => 'foo',
801					'action'      => 'bar',
802					'host'        => 'kohanaframework.org'
803				),
804				'http://kohanaframework.org/foo/bar'
805			),
806			array(
807				'<controller>/<action>',
808				array(
809					'controller'  => 'foo',
810					'action'      => 'bar',
811					'host'        => 'http://kohanaframework.org'
812				),
813				'http://kohanaframework.org/foo/bar'
814			),
815			array(
816				'foo/bar',
817				array(
818					'controller'  => 'foo',
819					'host'        => 'http://kohanaframework.org'
820				),
821				'http://kohanaframework.org/foo/bar'
822			),
823		);
824	}
825
826	/**
827	 * Tests the external route include route parameters
828	 *
829	 * @dataProvider provider_external_route_includes_params_in_uri
830	 */
831	public function test_external_route_includes_params_in_uri($route, $defaults, $expected_uri)
832	{
833		Route::set('test', $route)
834			->defaults($defaults);
835
836		$this->assertSame($expected_uri, Route::get('test')->uri());
837	}
838
839	/**
840	 * Provider for test_route_filter_modify_params
841	 *
842	 * @return array
843	 */
844	public function provider_route_filter_modify_params()
845	{
846		return array(
847			array(
848				'<controller>/<action>',
849				array(
850					'controller'  => 'Test',
851					'action'      => 'same',
852				),
853				array('Route_Holder', 'route_filter_modify_params_array'),
854				'test/different',
855				array(
856					'controller'  => 'Test',
857					'action'      => 'modified',
858				),
859			),
860			array(
861				'<controller>/<action>',
862				array(
863					'controller'  => 'test',
864					'action'      => 'same',
865				),
866				array('Route_Holder', 'route_filter_modify_params_false'),
867				'test/fail',
868				FALSE,
869			),
870		);
871	}
872
873	/**
874	 * Tests that route filters can modify parameters
875	 *
876	 * @covers Route::filter
877	 * @dataProvider provider_route_filter_modify_params
878	 */
879	public function test_route_filter_modify_params($route, $defaults, $filter, $uri, $expected_params)
880	{
881		$route = new Route($route);
882
883		// Mock a request class
884		$request = $this->get_request_mock($uri);
885
886		$params = $route->defaults($defaults)->filter($filter)->matches($request);
887
888		$this->assertSame($expected_params, $params);
889	}
890
891	/**
892	 * Provides test data for test_route_uri_encode_parameters
893	 *
894	 * @return array
895	 */
896	public function provider_route_uri_encode_parameters()
897	{
898		return array(
899			array(
900				'article',
901				'blog/article/<article_name>',
902				array(
903					'controller' => 'home',
904					'action' => 'index'
905				),
906				'article_name',
907				'Article name with special chars \\ ##',
908				'blog/article/Article%20name%20with%20special%20chars%20\\%20%23%23'
909			)
910		);
911	}
912
913	/**
914	 * http://dev.kohanaframework.org/issues/4079
915	 *
916	 * @test
917	 * @covers Route::get
918	 * @ticket 4079
919	 * @dataProvider provider_route_uri_encode_parameters
920	 */
921	public function test_route_uri_encode_parameters($name, $uri_callback, $defaults, $uri_key, $uri_value, $expected)
922	{
923		Route::set($name, $uri_callback)->defaults($defaults);
924
925		$get_route_uri = Route::get($name)->uri(array($uri_key => $uri_value));
926
927		$this->assertSame($expected, $get_route_uri);
928	}
929
930	/**
931	 * Get a mock of the Request class with a mocked `uri` method
932	 *
933	 * We are also mocking `method` method as it conflicts with newer PHPUnit,
934	 * in order to avoid the fatal errors
935	 *
936	 * @param string $uri
937	 * @return type
938	 */
939	public function get_request_mock($uri)
940	{
941		// Mock a request class with the $uri uri
942		$request = $this->getMock('Request', array('uri', 'method'), array($uri));
943
944		// mock `uri` method
945		$request->expects($this->any())
946			->method('uri')
947		  	// Request::uri() called by Route::matches() in the tests will return $uri
948			->will($this->returnValue($uri));
949
950		// also mock `method` method
951		$request->expects($this->any())
952			->method('method')
953			->withAnyParameters();
954
955		return $request;
956	}
957
958}
959