collection = new RouteCollection(); } /** * Test parse() throws an error on unknown routes. * */ public function testParseMissingRoute() { $this->expectException(\Cake\Routing\Exception\MissingRouteException::class); $this->expectExceptionMessage('A route matching "/" could not be found'); $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']); $routes->connect('/', ['controller' => 'Articles']); $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']); $result = $this->collection->parse('/'); $this->assertEquals([], $result, 'Should not match, missing /b'); } /** * Test parse() throws an error on known routes called with unknown methods. * */ public function testParseMissingRouteMethod() { $this->expectException(\Cake\Routing\Exception\MissingRouteException::class); $this->expectExceptionMessage('A "POST" route matching "/b" could not be found'); $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']); $routes->connect('/', ['controller' => 'Articles', '_method' => ['GET']]); $result = $this->collection->parse('/b', 'GET'); $this->assertNotEmpty($result, 'Route should be found'); $result = $this->collection->parse('/b', 'POST'); $this->assertEquals([], $result, 'Should not match with missing method'); } /** * Test parsing routes. * * @return void */ public function testParse() { $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']); $routes->connect('/', ['controller' => 'Articles']); $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']); $routes->connect('/media/search/*', ['controller' => 'Media', 'action' => 'search']); $result = $this->collection->parse('/b/'); $expected = [ 'controller' => 'Articles', 'action' => 'index', 'pass' => [], 'plugin' => null, 'key' => 'value', '_matchedRoute' => '/b', ]; $this->assertEquals($expected, $result); $result = $this->collection->parse('/b/the-thing?one=two'); $expected = [ 'controller' => 'Articles', 'action' => 'view', 'id' => 'the-thing', 'pass' => [], 'plugin' => null, 'key' => 'value', '?' => ['one' => 'two'], '_matchedRoute' => '/b/:id', ]; $this->assertEquals($expected, $result); $result = $this->collection->parse('/b/media/search'); $expected = [ 'key' => 'value', 'pass' => [], 'plugin' => null, 'controller' => 'Media', 'action' => 'search', '_matchedRoute' => '/b/media/search/*', ]; $this->assertEquals($expected, $result); $result = $this->collection->parse('/b/media/search/thing'); $expected = [ 'key' => 'value', 'pass' => ['thing'], 'plugin' => null, 'controller' => 'Media', 'action' => 'search', '_matchedRoute' => '/b/media/search/*', ]; $this->assertEquals($expected, $result); } /** * Test parsing routes with and without _name. * * @return void */ public function testParseWithNameParameter() { $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']); $routes->connect('/', ['controller' => 'Articles']); $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']); $routes->connect('/media/search/*', ['controller' => 'Media', 'action' => 'search'], ['_name' => 'media_search']); $result = $this->collection->parse('/b/'); $expected = [ 'controller' => 'Articles', 'action' => 'index', 'pass' => [], 'plugin' => null, 'key' => 'value', '_matchedRoute' => '/b', ]; $this->assertEquals($expected, $result); $result = $this->collection->parse('/b/the-thing?one=two'); $expected = [ 'controller' => 'Articles', 'action' => 'view', 'id' => 'the-thing', 'pass' => [], 'plugin' => null, 'key' => 'value', '?' => ['one' => 'two'], '_matchedRoute' => '/b/:id', ]; $this->assertEquals($expected, $result); $result = $this->collection->parse('/b/media/search'); $expected = [ 'key' => 'value', 'pass' => [], 'plugin' => null, 'controller' => 'Media', 'action' => 'search', '_matchedRoute' => '/b/media/search/*', '_name' => 'media_search', ]; $this->assertEquals($expected, $result); $result = $this->collection->parse('/b/media/search/thing'); $expected = [ 'key' => 'value', 'pass' => ['thing'], 'plugin' => null, 'controller' => 'Media', 'action' => 'search', '_matchedRoute' => '/b/media/search/*', '_name' => 'media_search', ]; $this->assertEquals($expected, $result); } /** * Test that parse decodes URL data before matching. * This is important for multibyte URLs that pass through URL rewriting. * * @return void */ public function testParseEncodedBytesInFixedSegment() { $routes = new RouteBuilder($this->collection, '/'); $routes->connect('/ден/:day-:month', ['controller' => 'Events', 'action' => 'index']); $url = '/%D0%B4%D0%B5%D0%BD/15-%D0%BE%D0%BA%D1%82%D0%BE%D0%BC%D0%B2%D1%80%D0%B8?test=foo'; $result = $this->collection->parse($url); $expected = [ 'pass' => [], 'plugin' => null, 'controller' => 'Events', 'action' => 'index', 'day' => '15', 'month' => 'октомври', '?' => ['test' => 'foo'], '_matchedRoute' => '/ден/:day-:month', ]; $this->assertEquals($expected, $result); $request = new ServerRequest(['url' => $url]); $result = $this->collection->parseRequest($request); $this->assertEquals($expected, $result); } /** * Test that parsing checks all the related path scopes. * * @return void */ public function testParseFallback() { $routes = new RouteBuilder($this->collection, '/', []); $routes->resources('Articles'); $routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']); $routes->connect('/:controller/:action', [], ['routeClass' => 'InflectedRoute']); $result = $this->collection->parse('/articles/add'); $expected = [ 'controller' => 'Articles', 'action' => 'add', 'plugin' => null, 'pass' => [], '_matchedRoute' => '/:controller/:action', ]; $this->assertEquals($expected, $result); } /** * Test parseRequest() throws an error on unknown routes. * */ public function testParseRequestMissingRoute() { $this->expectException(\Cake\Routing\Exception\MissingRouteException::class); $this->expectExceptionMessage('A route matching "/" could not be found'); $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']); $routes->connect('/', ['controller' => 'Articles']); $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']); $request = new ServerRequest(['url' => '/']); $result = $this->collection->parseRequest($request); $this->assertEquals([], $result, 'Should not match, missing /b'); } /** * Test parseRequest() checks host conditions * * @return void */ public function testParseRequestCheckHostCondition() { $routes = new RouteBuilder($this->collection, '/'); $routes->connect( '/fallback', ['controller' => 'Articles', 'action' => 'index'], ['_host' => '*.example.com'] ); $request = new ServerRequest([ 'environment' => [ 'HTTP_HOST' => 'a.example.com', 'PATH_INFO' => '/fallback', ], ]); $result = $this->collection->parseRequest($request); $expected = [ 'controller' => 'Articles', 'action' => 'index', 'pass' => [], 'plugin' => null, '_matchedRoute' => '/fallback', ]; $this->assertEquals($expected, $result, 'Should match, domain is correct'); $request = new ServerRequest([ 'environment' => [ 'HTTP_HOST' => 'foo.bar.example.com', 'PATH_INFO' => '/fallback', ], ]); $result = $this->collection->parseRequest($request); $this->assertEquals($expected, $result, 'Should match, domain is a matching subdomain'); $request = new ServerRequest([ 'environment' => [ 'HTTP_HOST' => 'example.test.com', 'PATH_INFO' => '/fallback', ], ]); try { $this->collection->parseRequest($request); $this->fail('No exception raised'); } catch (MissingRouteException $e) { $this->assertContains('/fallback', $e->getMessage()); } } /** * Get a list of hostnames * * @return array */ public static function hostProvider() { return [ ['wrong.example'], ['example.com'], ['aexample.com'], ]; } /** * Test parseRequest() checks host conditions * * @dataProvider hostProvider */ public function testParseRequestCheckHostConditionFail($host) { $this->expectException(\Cake\Routing\Exception\MissingRouteException::class); $this->expectExceptionMessage('A route matching "/fallback" could not be found'); $routes = new RouteBuilder($this->collection, '/'); $routes->connect( '/fallback', ['controller' => 'Articles', 'action' => 'index'], ['_host' => '*.example.com'] ); $request = new ServerRequest([ 'environment' => [ 'HTTP_HOST' => $host, 'PATH_INFO' => '/fallback', ], ]); $this->collection->parseRequest($request); } /** * Test parsing routes. * * @return void */ public function testParseRequest() { $routes = new RouteBuilder($this->collection, '/b', ['key' => 'value']); $routes->connect('/', ['controller' => 'Articles']); $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']); $routes->connect('/media/search/*', ['controller' => 'Media', 'action' => 'search']); $request = new ServerRequest(['url' => '/b/']); $result = $this->collection->parseRequest($request); $expected = [ 'controller' => 'Articles', 'action' => 'index', 'pass' => [], 'plugin' => null, 'key' => 'value', '_matchedRoute' => '/b', ]; $this->assertEquals($expected, $result); $request = new ServerRequest(['url' => '/b/media/search']); $result = $this->collection->parseRequest($request); $expected = [ 'key' => 'value', 'pass' => [], 'plugin' => null, 'controller' => 'Media', 'action' => 'search', '_matchedRoute' => '/b/media/search/*', ]; $this->assertEquals($expected, $result); $request = new ServerRequest(['url' => '/b/media/search/thing']); $result = $this->collection->parseRequest($request); $expected = [ 'key' => 'value', 'pass' => ['thing'], 'plugin' => null, 'controller' => 'Media', 'action' => 'search', '_matchedRoute' => '/b/media/search/*', ]; $this->assertEquals($expected, $result); $request = new ServerRequest(['url' => '/b/the-thing?one=two']); $result = $this->collection->parseRequest($request); $expected = [ 'controller' => 'Articles', 'action' => 'view', 'id' => 'the-thing', 'pass' => [], 'plugin' => null, 'key' => 'value', '?' => ['one' => 'two'], '_matchedRoute' => '/b/:id', ]; $this->assertEquals($expected, $result); } /** * Test parsing routes that match non-ascii urls * * @return void */ public function testParseRequestUnicode() { $routes = new RouteBuilder($this->collection, '/b', []); $routes->connect('/alta/confirmación', ['controller' => 'Media', 'action' => 'confirm']); $request = new ServerRequest(['url' => '/b/alta/confirmaci%C3%B3n']); $result = $this->collection->parseRequest($request); $expected = [ 'controller' => 'Media', 'action' => 'confirm', 'pass' => [], 'plugin' => null, '_matchedRoute' => '/b/alta/confirmación', ]; $this->assertEquals($expected, $result); $request = new ServerRequest(['url' => '/b/alta/confirmación']); $result = $this->collection->parseRequest($request); $this->assertEquals($expected, $result); } /** * Test match() throws an error on unknown routes. * */ public function testMatchError() { $this->expectException(\Cake\Routing\Exception\MissingRouteException::class); $this->expectExceptionMessage('A route matching "array ('); $context = [ '_base' => '/', '_scheme' => 'http', '_host' => 'example.org', ]; $routes = new RouteBuilder($this->collection, '/b'); $routes->connect('/', ['controller' => 'Articles']); $this->collection->match(['plugin' => null, 'controller' => 'Articles', 'action' => 'add'], $context); } /** * Test matching routes. * * @return void */ public function testMatch() { $context = [ '_base' => '/', '_scheme' => 'http', '_host' => 'example.org', ]; $routes = new RouteBuilder($this->collection, '/b'); $routes->connect('/', ['controller' => 'Articles']); $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view']); $result = $this->collection->match(['plugin' => null, 'controller' => 'Articles', 'action' => 'index'], $context); $this->assertEquals('b', $result); $result = $this->collection->match( ['id' => 'thing', 'plugin' => null, 'controller' => 'Articles', 'action' => 'view'], $context ); $this->assertEquals('b/thing', $result); } /** * Test matching routes with names * * @return void */ public function testMatchNamed() { $context = [ '_base' => '/', '_scheme' => 'http', '_host' => 'example.org', ]; $routes = new RouteBuilder($this->collection, '/b'); $routes->connect('/', ['controller' => 'Articles']); $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view'], ['_name' => 'article:view']); $result = $this->collection->match(['_name' => 'article:view', 'id' => '2'], $context); $this->assertEquals('/b/2', $result); $result = $this->collection->match(['plugin' => null, 'controller' => 'Articles', 'action' => 'view', 'id' => '2'], $context); $this->assertEquals('b/2', $result); } /** * Test match() throws an error on named routes that fail to match * */ public function testMatchNamedError() { $this->expectException(\Cake\Routing\Exception\MissingRouteException::class); $this->expectExceptionMessage('A named route was found for "fail", but matching failed'); $context = [ '_base' => '/', '_scheme' => 'http', '_host' => 'example.org', ]; $routes = new RouteBuilder($this->collection, '/b'); $routes->connect('/:lang/articles', ['controller' => 'Articles'], ['_name' => 'fail']); $this->collection->match(['_name' => 'fail'], $context); } /** * Test matching routes with names and failing * * @return void */ public function testMatchNamedMissingError() { $this->expectException(\Cake\Routing\Exception\MissingRouteException::class); $context = [ '_base' => '/', '_scheme' => 'http', '_host' => 'example.org', ]; $routes = new RouteBuilder($this->collection, '/b'); $routes->connect('/:id', ['controller' => 'Articles', 'action' => 'view'], ['_name' => 'article:view']); $this->collection->match(['_name' => 'derp'], $context); } /** * Test matching plugin routes. * * @return void */ public function testMatchPlugin() { $context = [ '_base' => '/', '_scheme' => 'http', '_host' => 'example.org', ]; $routes = new RouteBuilder($this->collection, '/contacts', ['plugin' => 'Contacts']); $routes->connect('/', ['controller' => 'Contacts']); $result = $this->collection->match(['plugin' => 'Contacts', 'controller' => 'Contacts', 'action' => 'index'], $context); $this->assertEquals('contacts', $result); } /** * Test that prefixes increase the specificity of a route match. * * Connect the admin route after the non prefixed version, this means * the non-prefix route would have a more specific name (users:index) * * @return void */ public function testMatchPrefixSpecificity() { $context = [ '_base' => '/', '_scheme' => 'http', '_host' => 'example.org', ]; $routes = new RouteBuilder($this->collection, '/'); $routes->connect('/:action/*', ['controller' => 'Users']); $routes->connect('/admin/{controller}', ['prefix' => 'admin', 'action' => 'index']); $url = [ 'plugin' => null, 'prefix' => 'admin', 'controller' => 'Users', 'action' => 'index', ]; $result = $this->collection->match($url, $context); $this->assertEquals('admin/Users', $result); $url = [ 'plugin' => null, 'controller' => 'Users', 'action' => 'index', ]; $result = $this->collection->match($url, $context); $this->assertEquals('index', $result); } /** * Test getting named routes. * * @return void */ public function testNamed() { $routes = new RouteBuilder($this->collection, '/l'); $routes->connect('/:controller', ['action' => 'index'], ['_name' => 'cntrl']); $routes->connect('/:controller/:action/*'); $all = $this->collection->named(); $this->assertCount(1, $all); $this->assertInstanceOf('Cake\Routing\Route\Route', $all['cntrl']); $this->assertEquals('/l/:controller', $all['cntrl']->template); } /** * Test the add() and routes() method. * * @return void */ public function testAddingRoutes() { $one = new Route('/pages/*', ['controller' => 'Pages', 'action' => 'display']); $two = new Route('/', ['controller' => 'Dashboards', 'action' => 'display']); $this->collection->add($one); $this->collection->add($two); $routes = $this->collection->routes(); $this->assertCount(2, $routes); $this->assertSame($one, $routes[0]); $this->assertSame($two, $routes[1]); } /** * Test the add() with some _name. * * * @return void */ public function testAddingDuplicateNamedRoutes() { $this->expectException(\Cake\Routing\Exception\DuplicateNamedRouteException::class); $one = new Route('/pages/*', ['controller' => 'Pages', 'action' => 'display']); $two = new Route('/', ['controller' => 'Dashboards', 'action' => 'display']); $this->collection->add($one, ['_name' => 'test']); $this->collection->add($two, ['_name' => 'test']); } /** * Test combined get/set method. * * @group deprecated * @return void */ public function testExtensions() { $this->deprecated(function () { $this->assertEquals([], $this->collection->extensions()); $this->collection->extensions('json'); $this->assertEquals(['json'], $this->collection->extensions()); $this->collection->extensions(['rss', 'xml']); $this->assertEquals(['json', 'rss', 'xml'], $this->collection->extensions()); $this->collection->extensions(['csv'], false); $this->assertEquals(['csv'], $this->collection->extensions()); }); } /** * Test basic setExtension and its getter. * * @return void */ public function testSetExtensions() { $this->assertEquals([], $this->collection->getExtensions()); $this->collection->setExtensions(['json']); $this->assertEquals(['json'], $this->collection->getExtensions()); $this->collection->setExtensions(['rss', 'xml']); $this->assertEquals(['json', 'rss', 'xml'], $this->collection->getExtensions()); $this->collection->setExtensions(['csv'], false); $this->assertEquals(['csv'], $this->collection->getExtensions()); } /** * Test adding middleware to the collection. * * @return void */ public function testRegisterMiddleware() { $result = $this->collection->registerMiddleware('closure', function () { }); $this->assertSame($result, $this->collection); $mock = $this->getMockBuilder('\stdClass') ->setMethods(['__invoke']) ->getMock(); $result = $this->collection->registerMiddleware('callable', $mock); $this->assertSame($result, $this->collection); $this->assertTrue($this->collection->hasMiddleware('closure')); $this->assertTrue($this->collection->hasMiddleware('callable')); $this->collection->registerMiddleware('class', 'Dumb'); } /** * Test adding a middleware group to the collection. * * @return void */ public function testMiddlewareGroup() { $this->collection->registerMiddleware('closure', function () { }); $mock = $this->getMockBuilder('\stdClass') ->setMethods(['__invoke']) ->getMock(); $result = $this->collection->registerMiddleware('callable', $mock); $this->collection->registerMiddleware('callable', $mock); $this->collection->middlewareGroup('group', ['closure', 'callable']); $this->assertTrue($this->collection->hasMiddlewareGroup('group')); } /** * Test adding a middleware group with the same name overwrites the original list * * @return void */ public function testMiddlewareGroupOverwrite() { $stub = function () { }; $this->collection->registerMiddleware('closure', $stub); $result = $this->collection->registerMiddleware('callable', $stub); $this->collection->registerMiddleware('callable', $stub); $this->collection->middlewareGroup('group', ['callable']); $this->collection->middlewareGroup('group', ['closure', 'callable']); $this->assertSame([$stub, $stub], $this->collection->getMiddleware(['group'])); } /** * Test adding ab unregistered middleware to a middleware group fails. * * @return void */ public function testMiddlewareGroupUnregisteredMiddleware() { $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Cannot add \'bad\' middleware to group \'group\'. It has not been registered.'); $this->collection->middlewareGroup('group', ['bad']); } }