Files
yii2/tests/framework/web/UrlRuleTest.php
2022-08-03 12:32:18 +03:00

1389 lines
54 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @link https://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license https://www.yiiframework.com/license/
*/
namespace yiiunit\framework\web;
use Yii;
use yii\helpers\VarDumper;
use yii\web\NotFoundHttpException;
use yii\web\Request;
use yii\web\UrlManager;
use yii\web\UrlNormalizer;
use yii\web\UrlNormalizerRedirectException;
use yii\web\UrlRule;
use yiiunit\TestCase;
/**
* @group web
*/
class UrlRuleTest extends TestCase
{
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
public function testCreateUrl()
{
$manager = new UrlManager(['cache' => null]);
$suites = $this->getTestsForCreateUrl();
foreach ($suites as $i => $suite) {
list($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
list($route, $params, $expected) = $test;
$url = $rule->createUrl($manager, $route, $params);
$this->assertSame($expected, $url, "Test#$i-$j: $name");
}
}
}
public function testParseRequest()
{
$manager = new UrlManager([
'cache' => null,
'normalizer' => false,
]);
$request = new Request(['hostInfo' => 'http://en.example.com']);
$suites = $this->getTestsForParseRequest();
foreach ($suites as $i => $suite) {
list($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
$request->pathInfo = $test[0];
$expected = $test[1];
$result = $rule->parseRequest($manager, $request);
if ($expected === false) {
$this->assertFalse($result, "Test#$i-$j: $name");
} else {
$this->assertEquals($expected, $result, "Test#$i-$j: $name");
}
}
}
}
public function testParseRequestWithNormalizer()
{
$manager = new UrlManager([
'cache' => null,
'normalizer' => UrlNormalizer::className(),
]);
$request = new Request(['hostInfo' => 'http://en.example.com']);
$suites = $this->getTestsForParseRequest();
foreach ($suites as $i => $suite) {
list($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
$request->pathInfo = $test[0];
$expected = isset($test[2]) ? $test[2] : $test[1];
try {
$result = $rule->parseRequest($manager, $request);
if ($expected === false) {
$this->assertFalse($result, "Test#$i-$j: $name");
} else {
$this->assertEquals($expected, $result, "Test#$i-$j: $name");
}
} catch (UrlNormalizerRedirectException $exc) {
$this->assertEquals([$expected[0]] + $expected[1], $exc->url, "Test#$i-$j: $name");
}
}
}
}
public function testParseRequestWithUrlManagerCustomNormalizer()
{
$manager = new UrlManager([
'cache' => null,
'normalizer' => [
'class' => UrlNormalizer::className(),
'action' => UrlNormalizer::ACTION_REDIRECT_PERMANENT,
],
]);
$request = new Request(['hostInfo' => 'http://en.example.com']);
$suites = $this->getTestsForParseRequest();
foreach ($suites as $i => $suite) {
list($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
$request->pathInfo = $test[0];
$expected = isset($test[2]) ? $test[2] : $test[1];
try {
$result = $rule->parseRequest($manager, $request);
if ($expected === false) {
$this->assertFalse($result, "Test#$i-$j: $name");
} else {
$this->assertEquals($expected, $result, "Test#$i-$j: $name");
}
} catch (UrlNormalizerRedirectException $exc) {
$this->assertEquals(UrlNormalizer::ACTION_REDIRECT_PERMANENT, $exc->statusCode, "Test-statusCode#$i-$j: $name");
$this->assertEquals([$expected[0]] + $expected[1], $exc->url, "Test#$i-$j: $name");
}
}
}
$manager = new UrlManager([
'cache' => null,
'normalizer' => [
'class' => UrlNormalizer::className(),
'action' => UrlNormalizer::ACTION_REDIRECT_TEMPORARY,
],
]);
$request = new Request(['hostInfo' => 'http://en.example.com']);
$suites = $this->getTestsForParseRequest();
foreach ($suites as $i => $suite) {
list($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
$request->pathInfo = $test[0];
$expected = isset($test[2]) ? $test[2] : $test[1];
try {
$result = $rule->parseRequest($manager, $request);
if ($expected === false) {
$this->assertFalse($result, "Test#$i-$j: $name");
} else {
$this->assertEquals($expected, $result, "Test#$i-$j: $name");
}
} catch (UrlNormalizerRedirectException $exc) {
$this->assertEquals(UrlNormalizer::ACTION_REDIRECT_TEMPORARY, $exc->statusCode, "Test-statusCode#$i-$j: $name");
$this->assertEquals([$expected[0]] + $expected[1], $exc->url, "Test#$i-$j: $name");
}
}
}
$manager = new UrlManager([
'cache' => null,
'normalizer' => [
'class' => UrlNormalizer::className(),
'action' => UrlNormalizer::ACTION_NOT_FOUND,
],
]);
$request = new Request(['hostInfo' => 'http://en.example.com']);
$suites = $this->getTestsForParseRequest();
foreach ($suites as $i => $suite) {
list($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
$request->pathInfo = $test[0];
$expected = $test[1];
try {
$result = $rule->parseRequest($manager, $request);
if ($expected === false) {
$this->assertFalse($result, "Test#$i-$j: $name");
} else {
$this->assertEquals($expected, $result, "Test#$i-$j: $name");
}
} catch (NotFoundHttpException $exc) {
$this->assertFalse($expected, "Test#$i-$j: $name");
}
}
}
$manager = new UrlManager([
'cache' => null,
'normalizer' => [
'class' => UrlNormalizer::className(),
'action' => null,
],
]);
$request = new Request(['hostInfo' => 'http://en.example.com']);
$suites = $this->getTestsForParseRequest();
foreach ($suites as $i => $suite) {
list($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
$request->pathInfo = $test[0];
$expected = isset($test[2]) ? $test[2] : $test[1];
$result = $rule->parseRequest($manager, $request);
if ($expected === false) {
$this->assertFalse($result, "Test#$i-$j: $name");
} else {
$this->assertEquals($expected, $result, "Test#$i-$j: $name");
}
}
}
$normalizerAction = function ($route) {
$route[1]['oldRoute'] = $route[0];
$route[0] = 'site/myCustomRoute';
return $route;
};
$manager = new UrlManager([
'cache' => null,
'normalizer' => [
'class' => UrlNormalizer::className(),
'action' => $normalizerAction,
],
]);
$request = new Request(['hostInfo' => 'http://en.example.com']);
$suites = $this->getTestsForParseRequest();
foreach ($suites as $i => $suite) {
list($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
$request->pathInfo = $test[0];
$expected = isset($test[2]) ? $normalizerAction($test[2]) : $test[1];
$result = $rule->parseRequest($manager, $request);
if ($expected === false) {
$this->assertFalse($result, "Test#$i-$j: $name");
} else {
$this->assertEquals($expected, $result, "Test#$i-$j: $name");
}
}
}
}
public function testParseRequestWithUrlRuleCustomNormalizer()
{
$manager = new UrlManager([
'cache' => null,
]);
$request = new Request([
'hostInfo' => 'http://en.example.com',
'pathInfo' => 'post/1-a/',
]);
$rule = new UrlRule([
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
'normalizer' => false,
]);
$result = $rule->parseRequest($manager, $request);
$this->assertFalse($result);
$rule = new UrlRule([
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
'normalizer' => [
'class' => 'yii\web\UrlNormalizer',
'normalizeTrailingSlash' => false,
],
]);
$result = $rule->parseRequest($manager, $request);
$this->assertFalse($result);
$rule = new UrlRule([
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
'normalizer' => [
'class' => 'yii\web\UrlNormalizer',
'normalizeTrailingSlash' => true,
'action' => null,
],
]);
$result = $rule->parseRequest($manager, $request);
$this->assertEquals(['post/index', ['page' => 1, 'tag' => 'a']], $result);
}
public function testToString()
{
$suites = $this->getTestsForToString();
foreach ($suites as $i => $suite) {
list($name, $config, $test) = $suite;
$rule = new UrlRule($config);
$this->assertEquals($rule->__toString(), $test, "Test#$i: $name");
}
}
protected function getTestsForCreateUrl()
{
// structure of each test
// message for the test
// config for the URL rule
// list of inputs and outputs
// route
// params
// expected output
return [
[
'empty pattern',
[
'pattern' => '',
'route' => 'post/index',
],
[
['post/index', [], ''],
['comment/index', [], false],
['post/index', ['page' => 1], '?page=1'],
],
],
[
'without param',
[
'pattern' => 'posts',
'route' => 'post/index',
],
[
['post/index', [], 'posts'],
['comment/index', [], false],
['post/index', ['page' => 1], 'posts?page=1'],
],
],
[
'parsing only',
[
'pattern' => 'posts',
'route' => 'post/index',
'mode' => UrlRule::PARSING_ONLY,
],
[
['post/index', [], false],
],
],
[
'with param',
[
'pattern' => 'post/<page>',
'route' => 'post/index',
],
[
['post/index', [], false],
['comment/index', [], false],
['post/index', ['page' => 1], 'post/1'],
['post/index', ['page' => 1, 'tag' => 'a'], 'post/1?tag=a'],
],
],
[
'with param requirement',
[
'pattern' => 'post/<page:\d+>',
'route' => 'post/index',
],
[
['post/index', ['page' => 'abc'], false],
['post/index', ['page' => 1], 'post/1'],
['post/index', ['page' => 1, 'tag' => 'a'], 'post/1?tag=a'],
],
],
[
'with multiple params',
[
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
],
[
['post/index', ['page' => '1abc'], false],
['post/index', ['page' => 1], false],
['post/index', ['page' => 1, 'tag' => 'a'], 'post/1-a'],
],
],
[
'multiple params with special chars',
[
'pattern' => 'post/<page-number:\d+>/<per_page:\d+>/<author.login>',
'route' => 'post/index',
],
[
['post/index', [], false],
['post/index', ['page-number' => '1', 'per_page' => '25'], false],
['post/index', ['page-number' => '1', 'per_page' => '25', 'author.login' => 'yiiuser'], 'post/1/25/yiiuser'],
],
],
[
'multiple params with leading non-letter chars',
[
'pattern' => 'post/<1page-number:\d+>/<-per_page:\d+>/<_author.login>',
'route' => 'post/index',
],
[
['post/index', [], false],
['post/index', ['1page-number' => '1', '-per_page' => '25'], false],
['post/index', ['1page-number' => '1', '-per_page' => '25', '_author.login' => 'yiiuser'], 'post/1/25/yiiuser'],
],
],
[
'with optional param',
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/index', ['page' => 1], false],
['post/index', ['page' => '1abc', 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a'], 'post/a'],
['post/index', ['page' => 2, 'tag' => 'a'], 'post/2/a'],
],
],
[
'with optional param not in pattern',
[
'pattern' => 'post/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/index', ['page' => 1], false],
['post/index', ['page' => '1abc', 'tag' => 'a'], false],
['post/index', ['page' => 2, 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a'], 'post/a'],
],
],
[
'multiple optional params',
[
'pattern' => 'post/<page:\d+>/<tag>/<sort:yes|no>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'sort' => 'yes'],
],
[
['post/index', ['page' => 1], false],
['post/index', ['page' => '1abc', 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'YES'], false],
['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'yes'], 'post/a'],
['post/index', ['page' => 2, 'tag' => 'a', 'sort' => 'yes'], 'post/2/a'],
['post/index', ['page' => 2, 'tag' => 'a', 'sort' => 'no'], 'post/2/a/no'],
['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'no'], 'post/a/no'],
],
],
[
'optional param and required param separated by dashes',
[
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/index', ['page' => 1], false],
['post/index', ['page' => '1abc', 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a'], 'post/-a'],
['post/index', ['page' => 2, 'tag' => 'a'], 'post/2-a'],
],
],
[
'optional param at the end',
[
'pattern' => 'post/<tag>/<page:\d+>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/index', ['page' => 1], false],
['post/index', ['page' => '1abc', 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a'], 'post/a'],
['post/index', ['page' => 2, 'tag' => 'a'], 'post/a/2'],
],
],
[
'optional param at the beginning',
[
'pattern' => '<language>/<category>',
'route' => 'site/category',
'defaults' => ['language' => 'en'],
],
[
['site/category', ['language' => 'en', 'category' => 'books'], 'books'],
['site/category', ['language' => 'pl', 'category' => 'books'], 'pl/books'],
],
],
[
'two optional params at the beginning',
[
'pattern' => '<language>/<category>',
'route' => 'site/category',
'defaults' => ['language' => 'en', 'category' => 'books'],
],
[
['site/category', ['language' => 'en', 'category' => 'books'], ''],
['site/category', ['language' => 'en', 'category' => 'games'], 'games'],
['site/category', ['language' => 'pl', 'category' => 'games'], 'pl/games'],
],
],
[
'optional param at the beginning with suffix',
[
'pattern' => '<page>',
'route' => 'page/view',
'defaults' => ['page' => 'index'],
'suffix' => '/',
],
[
['page/view', ['page' => 'index'], ''],
['page/view', ['page' => 'news'], 'news/'],
],
],
[
'consecutive optional params',
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'tag' => 'a'],
],
[
['post/index', ['page' => 1], false],
['post/index', ['page' => '1abc', 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a'], 'post'],
['post/index', ['page' => 2, 'tag' => 'a'], 'post/2'],
['post/index', ['page' => 1, 'tag' => 'b'], 'post/b'],
['post/index', ['page' => 2, 'tag' => 'b'], 'post/2/b'],
],
],
[
'consecutive optional params separated by dash',
[
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'tag' => 'a'],
],
[
['post/index', ['page' => 1], false],
['post/index', ['page' => '1abc', 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a'], 'post/-'],
['post/index', ['page' => 1, 'tag' => 'b'], 'post/-b'],
['post/index', ['page' => 2, 'tag' => 'a'], 'post/2-'],
['post/index', ['page' => 2, 'tag' => 'b'], 'post/2-b'],
],
],
[
'optional params - example from guide',
[
'pattern' => 'posts/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'tag' => ''],
],
[
['post/index', ['page' => 1, 'tag' => ''], 'posts'],
['post/index', ['page' => 2, 'tag' => ''], 'posts/2'],
['post/index', ['page' => 2, 'tag' => 'news'], 'posts/2/news'],
['post/index', ['page' => 1, 'tag' => 'news'], 'posts/news'],
// allow skip empty params on URL creation
['post/index', [], false],
['post/index', ['tag' => ''], false],
['post/index', ['page' => 1], 'posts'],
['post/index', ['page' => 2], 'posts/2'],
],
],
[
'required params',
[
'pattern' => 'about-me',
'route' => 'site/page',
'defaults' => ['id' => 1],
],
[
['site/page', ['id' => 1], 'about-me'],
['site/page', ['id' => 2], false],
],
],
[
'required default param',
[
'pattern' => '',
'route' => 'site/home',
'defaults' => ['lang' => 'en'],
],
[
['site/home', ['lang' => 'en'], ''],
['site/home', ['lang' => ''], false],
['site/home', [], false],
],
],
[
'required default empty param',
[
'pattern' => '',
'route' => 'site/home',
'defaults' => ['lang' => ''],
],
[
['site/home', ['lang' => ''], ''],
['site/home', ['lang' => 'en'], false],
['site/home', [], false],
],
],
[
'route has parameters',
[
'pattern' => '<controller>/<action>',
'route' => '<controller>/<action>',
'defaults' => [],
],
[
['post/index', ['page' => 1], 'post/index?page=1'],
['module/post/index', [], false],
],
],
[
'route has parameters with regex',
[
'pattern' => '<controller:post|comment>/<action>',
'route' => '<controller>/<action>',
'defaults' => [],
],
[
['post/index', ['page' => 1], 'post/index?page=1'],
['comment/index', ['page' => 1], 'comment/index?page=1'],
['test/index', ['page' => 1], false],
['post', [], false],
['module/post/index', [], false],
['post/index', ['controller' => 'comment'], 'post/index?controller=comment'],
],
],
[
'route has default parameter',
[
'pattern' => '<controller:post|comment>/<action>',
'route' => '<controller>/<action>',
'defaults' => ['action' => 'index'],
],
[
['post/view', ['page' => 1], 'post/view?page=1'],
['comment/view', ['page' => 1], 'comment/view?page=1'],
['test/view', ['page' => 1], false],
['test/index', ['page' => 1], false],
['post/index', ['page' => 1], 'post?page=1'],
],
],
[
'empty pattern with suffix',
[
'pattern' => '',
'route' => 'post/index',
'suffix' => '.html',
],
[
['post/index', [], ''],
['comment/index', [], false],
['post/index', ['page' => 1], '?page=1'],
],
],
[
'regular pattern with suffix',
[
'pattern' => 'posts',
'route' => 'post/index',
'suffix' => '.html',
],
[
['post/index', [], 'posts.html'],
['comment/index', [], false],
['post/index', ['page' => 1], 'posts.html?page=1'],
],
],
[
'empty pattern with slash suffix',
[
'pattern' => '',
'route' => 'post/index',
'suffix' => '/',
],
[
['post/index', [], ''],
['comment/index', [], false],
['post/index', ['page' => 1], '?page=1'],
],
],
[
'regular pattern with slash suffix',
[
'pattern' => 'posts',
'route' => 'post/index',
'suffix' => '/',
],
[
['post/index', [], 'posts/'],
['comment/index', [], false],
['post/index', ['page' => 1], 'posts/?page=1'],
],
],
[
'with host info',
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
'host' => 'http://<lang:en|fr>.example.com',
],
[
['post/index', ['page' => 1, 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], 'http://en.example.com/post/a'],
],
],
[
'with host info in pattern',
[
'pattern' => 'http://<lang:en|fr>.example.com/post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/index', ['page' => 1, 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], 'http://en.example.com/post/a'],
],
],
[
'with relative host info',
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
'host' => '//<lang:en|fr>.example.com',
],
[
['post/index', ['page' => 1, 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], '//en.example.com/post/a'],
],
],
[
'with relative host info in pattern',
[
'pattern' => '//<lang:en|fr>.example.com/post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/index', ['page' => 1, 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], '//en.example.com/post/a'],
],
],
[
'with unicode',
[
'pattern' => '/blog/search/<tag:[a-zA-Zа-яА-Я0-9\_\+\-]{1,255}>',
'route' => 'blog/search',
],
[
['blog/search', ['tag' => 'метра'], 'blog/search/%D0%BC%D0%B5%D1%82%D1%80%D0%B0'],
],
],
];
}
protected function getTestsForParseRequest()
{
// structure of each test
// message for the test
// config for the URL rule
// list of inputs and outputs
// pathInfo
// expected result (in format [route, params]) with normalization disabled, or false if the rule doesn't apply
// expected result if noralizer is enabled, or not set if result should be the same as without normalizer
return [
[
'empty pattern',
[
'pattern' => '',
'route' => 'post/index',
],
[
['', ['post/index', []]],
['a', false],
],
],
[
'without param',
[
'pattern' => 'posts',
'route' => 'post/index',
],
[
['posts', ['post/index', []]],
['a', false],
],
],
[
'with dot', // https://github.com/yiisoft/yii/issues/2945
[
'pattern' => 'posts.html',
'route' => 'post/index',
],
[
['posts.html', ['post/index', []]],
['postsahtml', false],
],
],
[
'creation only',
[
'pattern' => 'posts',
'route' => 'post/index',
'mode' => UrlRule::CREATION_ONLY,
],
[
['posts', false],
],
],
[
'with param',
[
'pattern' => 'post/<page>',
'route' => 'post/index',
],
[
['post/1', ['post/index', ['page' => '1']]],
['post/a', ['post/index', ['page' => 'a']]],
['post', false],
['posts', false],
],
],
[
'with param requirement',
[
'pattern' => 'post/<page:\d+>',
'route' => 'post/index',
],
[
['post/1', ['post/index', ['page' => '1']]],
['post/a', false],
['post/1/a', false],
],
],
[
'with multiple params',
[
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
],
[
['post/1-a', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/a', false],
['post/1', false],
['post/1/a', false],
],
],
[
'multiple params with special chars',
[
'pattern' => 'post/<page-number:\d+>/<per_page:\d+>/<author.login>',
'route' => 'post/index',
],
[
['post/1/25/yiiuser', ['post/index', ['page-number' => '1', 'per_page' => '25', 'author.login' => 'yiiuser']]],
['post/1/25', false],
['post', false],
],
],
[
'multiple params with special chars',
[
'pattern' => 'post/<1page-number:\d+>/<-per_page:\d+>/<_author.login>',
'route' => 'post/index',
],
[
['post/1/25/yiiuser', ['post/index', ['1page-number' => '1', '-per_page' => '25', '_author.login' => 'yiiuser']]],
['post/1/25', false],
['post', false],
],
],
[
'with optional param',
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/1/a', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/2/a', ['post/index', ['page' => '2', 'tag' => 'a']]],
['post/a', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/1', ['post/index', ['page' => '1', 'tag' => '1']]],
],
],
[
'with optional param not in pattern',
[
'pattern' => 'post/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/a', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/1', ['post/index', ['page' => '1', 'tag' => '1']]],
['post', false],
],
],
[
'multiple optional params',
[
'pattern' => 'post/<page:\d+>/<tag>/<sort:yes|no>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'sort' => 'yes'],
],
[
['post/1/a/yes', ['post/index', ['page' => '1', 'tag' => 'a', 'sort' => 'yes']]],
['post/1/a/no', ['post/index', ['page' => '1', 'tag' => 'a', 'sort' => 'no']]],
['post/2/a/no', ['post/index', ['page' => '2', 'tag' => 'a', 'sort' => 'no']]],
['post/2/a', ['post/index', ['page' => '2', 'tag' => 'a', 'sort' => 'yes']]],
['post/a/no', ['post/index', ['page' => '1', 'tag' => 'a', 'sort' => 'no']]],
['post/a', ['post/index', ['page' => '1', 'tag' => 'a', 'sort' => 'yes']]],
['post', false],
],
],
[
'optional param and required param separated by dashes',
[
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/1-a', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/2-a', ['post/index', ['page' => '2', 'tag' => 'a']]],
['post/-a', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/a', false],
['post-a', false],
],
],
[
'optional param at the beginning',
[
'pattern' => '<language>/<category>',
'route' => 'site/category',
'defaults' => ['language' => 'en'],
],
[
['books', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en/books', ['site/category', ['language' => 'en', 'category' => 'books']]],
],
],
[
'two optional params at the beginning',
[
'pattern' => '<language:(en|pl)>/<category>',
'route' => 'site/category',
'defaults' => ['language' => 'en', 'category' => 'books'],
],
[
['', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en/books', ['site/category', ['language' => 'en', 'category' => 'books']]],
],
],
[
'two optional params at the beginning followed by placeholder',
[
'pattern' => '<language:(en|pl)>/<category>/test',
'route' => 'site/category',
'defaults' => ['language' => 'en', 'category' => 'books'],
],
[
['test', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en/test', ['site/category', ['language' => 'en', 'category' => 'books']]],
['books/test', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en/books/test', ['site/category', ['language' => 'en', 'category' => 'books']]],
],
],
[
'two optional params at the beginning separated by placeholder',
[
'pattern' => '<language:(en|pl)>/test/<category>',
'route' => 'site/category',
'defaults' => ['language' => 'en', 'category' => 'books'],
],
[
['test', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en/test', ['site/category', ['language' => 'en', 'category' => 'books']]],
['test/books', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en/test/books', ['site/category', ['language' => 'en', 'category' => 'books']]],
],
],
[
'three optional params at the beginning separated by placeholder',
[
'pattern' => '<language:(en|pl)>/test/<category>/<id:\d+>',
'route' => 'site/category',
'defaults' => ['language' => 'en', 'category' => 'books', 'id' => 1],
],
[
['test', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
['en/test', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
['test/books', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
['en/test/books', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
],
],
[
'two optional params at the beginning separated by dash',
[
'pattern' => '<language:(en|pl)>-<category>',
'route' => 'site/category',
'defaults' => ['language' => 'en', 'category' => 'books'],
],
[
['-', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en-', ['site/category', ['language' => 'en', 'category' => 'books']]],
['-books', ['site/category', ['language' => 'en', 'category' => 'books']]],
['en-books', ['site/category', ['language' => 'en', 'category' => 'books']]],
],
],
[
'three optional params at the beginning separated by dash',
[
'pattern' => '<language:(en|pl)>-<category>/<id:\d+>',
'route' => 'site/category',
'defaults' => ['language' => 'en', 'category' => 'books', 'id' => 1],
],
[
['-', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
['en-', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
['-books', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
['en-books', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
['en-books/1', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 1]]],
['en-books/2', ['site/category', ['language' => 'en', 'category' => 'books', 'id' => 2]]],
],
],
[
'optional param at the end',
[
'pattern' => 'post/<tag>/<page:\d+>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/a/1', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/a/2', ['post/index', ['page' => '2', 'tag' => 'a']]],
['post/a', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/2', ['post/index', ['page' => '1', 'tag' => '2']]],
['post', false],
],
],
[
'consecutive optional params',
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'tag' => 'a'],
],
[
['post/2/b', ['post/index', ['page' => '2', 'tag' => 'b']]],
['post/2', ['post/index', ['page' => '2', 'tag' => 'a']]],
['post', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post/b', ['post/index', ['page' => '1', 'tag' => 'b']]],
['post//b', false, ['post/index', ['page' => '1', 'tag' => 'b']]],
],
],
[
'consecutive optional params separated by dash',
[
'pattern' => 'post/<page:\d+>-<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'tag' => 'a'],
],
[
['post/2-b', ['post/index', ['page' => '2', 'tag' => 'b']]],
['post/2-', ['post/index', ['page' => '2', 'tag' => 'a']]],
['post/-b', ['post/index', ['page' => '1', 'tag' => 'b']]],
['post/-', ['post/index', ['page' => '1', 'tag' => 'a']]],
['post', false],
],
],
[
'route has parameters',
[
'pattern' => '<controller>/<action>',
'route' => '<controller>/<action>',
'defaults' => [],
],
[
['post/index', ['post/index', []]],
['module/post/index', false],
],
],
[
'route has parameters with regex',
[
'pattern' => '<controller:post|comment>/<action>',
'route' => '<controller>/<action>',
'defaults' => [],
],
[
['post/index', ['post/index', []]],
['comment/index', ['comment/index', []]],
['test/index', false],
['post', false],
['module/post/index', false],
],
],
[
'route has default parameter',
[
'pattern' => '<controller:post|comment>/<action>',
'route' => '<controller>/<action>',
'defaults' => ['action' => 'index'],
],
[
['post/view', ['post/view', []]],
['comment/view', ['comment/view', []]],
['test/view', false],
['post', ['post/index', []]],
['posts', false],
['test', false],
['index', false],
],
],
[
'empty pattern with suffix',
[
'pattern' => '',
'route' => 'post/index',
'suffix' => '.html',
],
[
['', ['post/index', []]],
['.html', false],
['a.html', false],
],
],
[
'regular pattern with suffix',
[
'pattern' => 'posts',
'route' => 'post/index',
'suffix' => '.html',
],
[
['posts.html', ['post/index', []]],
['posts', false],
['posts.HTML', false],
['a.html', false],
['a', false],
],
],
[
'empty pattern with slash suffix',
[
'pattern' => '',
'route' => 'post/index',
'suffix' => '/',
],
[
['', ['post/index', []]],
['a', false],
],
],
[
'regular pattern with slash suffix',
[
'pattern' => 'posts',
'route' => 'post/index',
'suffix' => '/',
],
[
['posts/', ['post/index', []]],
['posts', false, ['post/index', []]],
['a', false],
],
],
[
'with host info',
[
'pattern' => 'post/<page:\d+>',
'route' => 'post/index',
'host' => 'http://<lang:en|fr>.example.com',
],
[
['post/1', ['post/index', ['page' => '1', 'lang' => 'en']]],
['post/a', false],
['post/1/a', false],
],
],
[
'with host info in pattern',
[
'pattern' => 'http://<lang:en|fr>.example.com/post/<page:\d+>',
'route' => 'post/index',
],
[
['post/1', ['post/index', ['page' => '1', 'lang' => 'en']]],
['post/a', false],
['post/1/a', false],
],
],
[
'host info + defaults', // https://github.com/yiisoft/yii2/issues/6871
[
'pattern' => 'http://en.example.com/<page>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['', ['post/index', ['page' => 1]]],
['2', ['post/index', ['page' => 2]]],
],
],
[
'with relative host info',
[
'pattern' => 'post/<page:\d+>',
'route' => 'post/index',
'host' => '//<lang:en|fr>.example.com',
],
[
['post/1', ['post/index', ['page' => '1', 'lang' => 'en']]],
['post/a', false],
['post/1/a', false],
],
],
[
'with relative host info in pattern',
[
'pattern' => '//<lang:en|fr>.example.com/post/<page:\d+>',
'route' => 'post/index',
],
[
['post/1', ['post/index', ['page' => '1', 'lang' => 'en']]],
['post/a', false],
['post/1/a', false],
],
],
];
}
protected function getTestsForToString()
{
return [
[
'empty pattern',
[
'pattern' => '',
'route' => 'post/index',
],
'/',
],
[
'multiple params with special chars',
[
'pattern' => 'post/<page-number:\d+>/<per_page:\d+>/<author.login>',
'route' => 'post/index',
],
'post/<page-number:\d+>/<per_page:\d+>/<author.login>',
],
[
'with host info',
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
'host' => 'http://<lang:en|fr>.example.com',
],
'http://<lang:en|fr>.example.com/post/<page:\d+>/<tag>',
],
[
'with host info in pattern',
[
'pattern' => 'http://<lang:en|fr>.example.com/post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
'http://<lang:en|fr>.example.com/post/<page:\d+>/<tag>',
],
[
'with verb',
[
'verb' => ['POST'],
'pattern' => 'post/<id:\d+>',
'route' => 'post/index',
],
'POST post/<id:\d+>',
],
[
'with verbs',
[
'verb' => ['PUT', 'POST'],
'pattern' => 'post/<id:\d+>',
'route' => 'post/index',
],
'PUT,POST post/<id:\d+>',
],
];
}
/**
* @dataProvider testGetCreateUrlStatusProvider
* @param array $config
* @param array $tests
*/
public function testGetCreateUrlStatus($config, $tests)
{
foreach ($tests as $test) {
list($route, $params, $expected, $status) = $test;
$this->mockWebApplication();
Yii::$app->set('request', new Request(['hostInfo' => 'http://example.com', 'scriptUrl' => '/index.php']));
$manager = new UrlManager([
'cache' => null,
]);
$rule = new UrlRule($config);
$errorMessage = 'Failed test: ' . VarDumper::dumpAsString($test);
$this->assertSame($expected, $rule->createUrl($manager, $route, $params), $errorMessage);
$this->assertNotNull($status, $errorMessage);
if ($status > 0) {
$this->assertSame($status, $rule->getCreateUrlStatus() & $status, $errorMessage);
} else {
$this->assertSame($status, $rule->getCreateUrlStatus(), $errorMessage);
}
}
}
/**
* Provides test cases for getCreateUrlStatus() method.
*
* - first param are properties of the UrlRule
* - second param is an array of test cases, containing two element arrays:
* - first element is the route to create
* - second element is the array of params
* - third element is the expected URL
* - fourth element is the expected result of getCreateUrlStatus() method
*/
public function testGetCreateUrlStatusProvider()
{
return [
'route' => [
// rule properties
[
'pattern' => 'post/<page:\d+>/<tag>/<sort:yes|no>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'sort' => 'yes'],
],
// test cases: route, params, expected, createStatus
[
['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'yes'], 'post/a', UrlRule::CREATE_STATUS_SUCCESS],
['module/post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'yes'], false, UrlRule::CREATE_STATUS_ROUTE_MISMATCH],
['post/index/action', ['page' => 1, 'tag' => 'a', 'sort' => 'yes'], false, UrlRule::CREATE_STATUS_ROUTE_MISMATCH],
],
],
'optional params' => [
// rule properties
[
'pattern' => 'post/<page:\d+>/<tag>/<sort:yes|no>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'sort' => 'yes'],
],
// test cases: route, params, expected, createStatus
[
['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'yes'], 'post/a', UrlRule::CREATE_STATUS_SUCCESS],
['post/index', ['page' => 2, 'tag' => 'a', 'sort' => 'yes'], 'post/2/a', UrlRule::CREATE_STATUS_SUCCESS],
['post/index', ['page' => 2, 'tag' => 'a', 'sort' => 'no'], 'post/2/a/no', UrlRule::CREATE_STATUS_SUCCESS],
['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'no'], 'post/a/no', UrlRule::CREATE_STATUS_SUCCESS],
[
'post/index',
['page' => 1, 'tag' => 'a', 'sort' => 'no', 'category' => 'my-category'],
'post/a/no?category=my-category',
UrlRule::CREATE_STATUS_SUCCESS,
],
['post/index', ['page' => 1], false, UrlRule::CREATE_STATUS_PARAMS_MISMATCH],
['post/index', ['page' => '1abc', 'tag' => 'a'], false, UrlRule::CREATE_STATUS_PARAMS_MISMATCH],
['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'YES'], false, UrlRule::CREATE_STATUS_PARAMS_MISMATCH],
],
],
'parsing only' => [
// rule properties
[
'pattern' => 'post/<page:\d+>/<tag>/<sort:yes|no>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'sort' => 'yes'],
'mode' => UrlRule::PARSING_ONLY,
],
// test cases: route, params, expected, createStatus
[
['post/index', ['page' => 1, 'tag' => 'a', 'sort' => 'yes'], false, UrlRule::CREATE_STATUS_PARSING_ONLY],
],
],
];
}
}