mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-26 13:44:19 +01:00
434 lines
12 KiB
PHP
434 lines
12 KiB
PHP
<?php
|
|
|
|
/*
|
|
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
|
* @license http://opensource.org/licenses/AGPL-3.0
|
|
*/
|
|
|
|
namespace Combodo\iTop\Test\UnitTest\Service\Router;
|
|
|
|
use Combodo\iTop\Service\Router\Exception\RouteNotFoundException;
|
|
use Combodo\iTop\Service\Router\Router;
|
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
|
use utils;
|
|
|
|
/**
|
|
* Class RouterTest
|
|
*
|
|
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
|
* @since 3.1.0
|
|
* @covers \Combodo\iTop\Service\Router\Router
|
|
*/
|
|
class RouterTest extends ItopDataTestCase
|
|
{
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->RequireOnceItopFile('setup/setuputils.class.inc.php');
|
|
|
|
// Speedup test by forcing the use of the cache, even on a development environment
|
|
$oRouter = Router::GetInstance();
|
|
$oRouter->SetUseCache(true);
|
|
}
|
|
|
|
/**
|
|
* @covers \Combodo\iTop\Service\Router\Router::GenerateUrl
|
|
* @dataProvider GenerateUrlProvider
|
|
*
|
|
* @param string $sExpectedUrl URL contains a <APP_ROOT_URL> placeholder that will be replaced with the real app root url at run time
|
|
* @param bool $bValid
|
|
* @param string $sRoute
|
|
* @param array $aParams
|
|
* @param bool $bAbsoluteUrl
|
|
*
|
|
* @return void
|
|
* @throws \Exception
|
|
*/
|
|
public function testGenerateUrl(string $sExpectedUrl, bool $bValid, string $sRoute, array $aParams, bool $bAbsoluteUrl = true): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
|
|
if (false === $bValid) {
|
|
$this->expectException(RouteNotFoundException::class);
|
|
}
|
|
$sTestedUrl = $oRouter->GenerateUrl($sRoute, $aParams, $bAbsoluteUrl);
|
|
$sExpectedUrl = str_ireplace('<APP_ROOT_URL>', utils::GetAbsoluteUrlAppRoot(), $sExpectedUrl);
|
|
|
|
$this->assertEquals($sTestedUrl, $sExpectedUrl, 'Generated URL does not match');
|
|
}
|
|
|
|
public function GenerateUrlProvider(): array
|
|
{
|
|
return [
|
|
'invalid route' => [
|
|
'',
|
|
false,
|
|
'foo.bar',
|
|
[],
|
|
true,
|
|
],
|
|
'relative route with no params' => [
|
|
'pages/UI.php?route=object.modify',
|
|
true,
|
|
'object.modify',
|
|
[],
|
|
false,
|
|
],
|
|
'absolute route with no params' => [
|
|
'<APP_ROOT_URL>pages/UI.php?route=object.modify',
|
|
true,
|
|
'object.modify',
|
|
[],
|
|
true,
|
|
],
|
|
'absolute route with scalar params' => [
|
|
'<APP_ROOT_URL>pages/UI.php?route=object.modify&class=Person&id=123',
|
|
true,
|
|
'object.modify',
|
|
[
|
|
'class' => 'Person',
|
|
'id' => 123,
|
|
],
|
|
true,
|
|
],
|
|
'absolute route with 1 dimension array params' => [
|
|
'<APP_ROOT_URL>pages/UI.php?route=object.modify&class=Person&id=123&default%5Bname%5D=Castor&default%5Bfirst_name%5D=P%C3%A8re',
|
|
true,
|
|
'object.modify',
|
|
[
|
|
'class' => 'Person',
|
|
'id' => 123,
|
|
'default' => [
|
|
'name' => 'Castor',
|
|
'first_name' => 'Père',
|
|
],
|
|
],
|
|
true,
|
|
],
|
|
'absolute route with 2 dimensions array params' => [
|
|
'<APP_ROOT_URL>pages/UI.php?route=object.modify&class=Person&id=123&default%5Bname%5D=Castor&default%5Bfirst_name%5D=P%C3%A8re&foo%5Bfirst%5D%5B0%5D=10&foo%5Bfirst%5D%5B1%5D=20&foo%5Bsecond%5D%5B0%5D=30&foo%5Bsecond%5D%5B1%5D=40',
|
|
true,
|
|
'object.modify',
|
|
[
|
|
'class' => 'Person',
|
|
'id' => 123,
|
|
'default' => [
|
|
'name' => 'Castor',
|
|
'first_name' => 'Père',
|
|
],
|
|
'foo' => [
|
|
'first' => ['10', '20'],
|
|
'second' => ['30', '40'],
|
|
],
|
|
],
|
|
true,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider CanDispatchRouteProvider
|
|
* @covers \Combodo\iTop\Service\Router\Router::CanDispatchRoute
|
|
*
|
|
* @param string $sRoute
|
|
* @param $bExpectedResult
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testCanDispatchRoute(string $sRoute, $bExpectedResult): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
$bTestedResult = $oRouter->CanDispatchRoute($sRoute);
|
|
|
|
$this->assertEquals($bExpectedResult, $bTestedResult, "Dispatch capability for '$sRoute' was not the expected one. Got ".var_export($bTestedResult, true).", expected ".var_export($bExpectedResult, true));
|
|
}
|
|
|
|
public function CanDispatchRouteProvider(): array
|
|
{
|
|
return [
|
|
'Existing handler' => [
|
|
'object.modify',
|
|
true,
|
|
],
|
|
'Existing controller but unknown operation' => [
|
|
'object.modify_me_please',
|
|
false,
|
|
],
|
|
'Unknown controller' => [
|
|
'foo.bar',
|
|
false,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider GetRoutesProvider
|
|
* @covers \Combodo\iTop\Service\Router\Router::GetRoutes
|
|
*
|
|
* @param string $sRoute
|
|
* @param bool $bShouldBePresent
|
|
*
|
|
* @return void
|
|
* @throws \ReflectionException
|
|
*/
|
|
public function testGetRoutes(string $sRoute, bool $bShouldBePresent): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
$aTestedRoutes = $this->InvokeNonPublicMethod(Router::class, 'GetRoutes', $oRouter, []);
|
|
|
|
$bIsPresent = array_key_exists($sRoute, $aTestedRoutes);
|
|
$this->assertEquals($bShouldBePresent, $bIsPresent, "Route '$sRoute' was not expected amongst the available routes.");
|
|
}
|
|
|
|
/**
|
|
* @covers \Combodo\iTop\Service\Router\Router::GetRoutes
|
|
* @return void
|
|
*
|
|
* @since N°6618 Covers that the cache isn't re-generated at each call of the GetRoutes method
|
|
*/
|
|
public function testGetRoutesCacheGeneratedOnlyOnce(): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
$sRoutesCacheFilePath = $this->InvokeNonPublicMethod(Router::class, 'GetCacheFileAbsPath', $oRouter, []);
|
|
|
|
// Developer mode must be disabled for the routes cache to be used
|
|
$oConf = utils::GetConfig();
|
|
$mDeveloperModePreviousValue = $oConf->Get('developer_mode.enabled');
|
|
$oConf->Set('developer_mode.enabled', false);
|
|
|
|
// Generate cache for first time
|
|
$this->InvokeNonPublicMethod(Router::class, 'GetRoutes', $oRouter, []);
|
|
|
|
// Check that file exists and retrieve modification timestamp
|
|
if (false === is_file($sRoutesCacheFilePath)) {
|
|
$this->fail("Cache file was not generated ($sRoutesCacheFilePath)");
|
|
}
|
|
|
|
// Set its modification date in the past so that regenerating it will result in a new modification date without any doubt
|
|
$iFirstModificationTimestamp = time() - 2;
|
|
touch($sRoutesCacheFilePath, $iFirstModificationTimestamp);
|
|
|
|
// Call GetRoutes() again to see if cache gets re-generated or not
|
|
$this->InvokeNonPublicMethod(Router::class, 'GetRoutes', $oRouter, []);
|
|
|
|
// Check that file still exists and that modification timestamp has not changed
|
|
if (false === is_file($sRoutesCacheFilePath)) {
|
|
$this->fail("Cache file is no longer present, that should not happen! ($sRoutesCacheFilePath)");
|
|
}
|
|
|
|
clearstatcache();
|
|
$iSecondModificationTimestamp = filemtime($sRoutesCacheFilePath);
|
|
$this->debug("Second timestamp: $iSecondModificationTimestamp");
|
|
|
|
$this->assertSame($iFirstModificationTimestamp, $iSecondModificationTimestamp, "Cache file timestamp changed, seems like cache is not working and was re-generated when it should not!");
|
|
|
|
// Restore previous value for following tests
|
|
$oConf->Set('developer_mode.enabled', $mDeveloperModePreviousValue);
|
|
}
|
|
|
|
/**
|
|
* @covers \Combodo\iTop\Service\Router\Router::GetRoutes
|
|
* @return void
|
|
*
|
|
* @since N°6618 Covers that the cache is re-generated correctly if corrupted
|
|
*/
|
|
public function testGetRoutesCacheRegeneratedCorrectlyIfCorrupted(): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
$sRoutesCacheFilePath = $this->InvokeNonPublicMethod(Router::class, 'GetCacheFileAbsPath', $oRouter, []);
|
|
|
|
// Developer mode must be disabled for the routes cache to be used
|
|
$oConf = utils::GetConfig();
|
|
$mDeveloperModePreviousValue = $oConf->Get('developer_mode.enabled');
|
|
$oConf->Set('developer_mode.enabled', false);
|
|
|
|
// Generate corrupted cache manually
|
|
$sFaultyStatement = 'return 1;';
|
|
file_put_contents(
|
|
$sRoutesCacheFilePath,
|
|
<<<PHP
|
|
<?php
|
|
|
|
{$sFaultyStatement}
|
|
PHP
|
|
);
|
|
|
|
// Retrieve routes to access / fix cache in the process
|
|
$aRoutes = $this->InvokeNonPublicMethod(Router::class, 'GetRoutes', $oRouter, []);
|
|
|
|
// Check that routes are an array
|
|
$this->assertTrue(is_array($aRoutes));
|
|
|
|
// Check that file content doesn't contain `return 1`
|
|
clearstatcache();
|
|
$this->assertStringNotContainsString($sFaultyStatement, file_get_contents($sRoutesCacheFilePath), "Cache file still contains the faulty statement ($sFaultyStatement)");
|
|
|
|
// Restore previous value for following tests
|
|
$oConf->Set('developer_mode.enabled', $mDeveloperModePreviousValue);
|
|
}
|
|
|
|
public function GetRoutesProvider(): array
|
|
{
|
|
return [
|
|
'Valid route' => [
|
|
'object.modify',
|
|
true,
|
|
],
|
|
// eg. a route from a controller that has no ROUTE_NAMESPACE
|
|
'Invalid route' => [
|
|
'.save_state',
|
|
false,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider GetRoutePartsProvider
|
|
* @covers \Combodo\iTop\Service\Router\Router::GetRouteParts
|
|
*
|
|
* @param string $sRoute
|
|
* @param array|null $aExpectedParts
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testGetRouteParts(string $sRoute, ?array $aExpectedParts): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
$aTestedParts = $this->InvokeNonPublicMethod(Router::class, 'GetRouteParts', $oRouter, [$sRoute]);
|
|
|
|
$this->assertEquals($aExpectedParts, $aTestedParts, "Parts found for '$sRoute' were not the expected ones. Got '".print_r($aTestedParts, true)."', expected '".print_r($aExpectedParts, true)."'.");
|
|
}
|
|
|
|
public function GetRoutePartsProvider(): array
|
|
{
|
|
return [
|
|
'Empty route' => [
|
|
'',
|
|
null,
|
|
],
|
|
// eg. controller implmenting iController but without the ROUTE_NAMESPACE content. This is for BC compatibility
|
|
'Route with no namespace' => [
|
|
'.some_operation',
|
|
null,
|
|
],
|
|
'Route with no operation' => [
|
|
'some_namespace.',
|
|
null,
|
|
],
|
|
'Valid route' => [
|
|
'some_namespace.some_operation',
|
|
['namespace' => 'some_namespace', 'operation' => 'some_operation'],
|
|
],
|
|
'Valid route with deep namespace' => [
|
|
'some.deep.namespace.some_operation',
|
|
['namespace' => 'some.deep.namespace', 'operation' => 'some_operation'],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider GetRouteNamespaceProvider
|
|
* @covers \Combodo\iTop\Service\Router\Router::GetRouteNamespace
|
|
*
|
|
* @param string $sRoute
|
|
* @param string|null $sExpectedNamespace
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testGetRouteNamespace(string $sRoute, ?string $sExpectedNamespace): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
$sTestedNamespace = $this->InvokeNonPublicMethod(Router::class, 'GetRouteNamespace', $oRouter, [$sRoute]);
|
|
|
|
$this->assertEquals($sExpectedNamespace, $sTestedNamespace, "Namespace found for '$sRoute' was not the expected one. Got '$sTestedNamespace', expected '$sExpectedNamespace'.");
|
|
}
|
|
|
|
public function GetRouteNamespaceProvider(): array
|
|
{
|
|
return [
|
|
'Operation without namespace' => [
|
|
'some_operation',
|
|
null,
|
|
],
|
|
'Operation with namespace' => [
|
|
'some_namespace.some_operation',
|
|
'some_namespace',
|
|
],
|
|
'Operation with multi-levels namespace' => [
|
|
'some.deep.namespace.some_operation',
|
|
'some.deep.namespace',
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider GetRouteOperationProvider
|
|
* @covers \Combodo\iTop\Service\Router\Router::GetRouteOperation
|
|
*
|
|
* @param string $sRoute
|
|
* @param string|null $sExpectedOperation
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testGetRouteOperation(string $sRoute, ?string $sExpectedOperation): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
$sTestedOperation = $this->InvokeNonPublicMethod(Router::class, 'GetRouteOperation', $oRouter, [$sRoute]);
|
|
|
|
$this->assertEquals($sExpectedOperation, $sTestedOperation, "Operation found for '$sRoute' was not the expected one. Got '$sTestedOperation', expected '$sExpectedOperation'.");
|
|
}
|
|
|
|
public function GetRouteOperationProvider(): array
|
|
{
|
|
return [
|
|
'Operation without namespace' => [
|
|
'some_operation',
|
|
null,
|
|
],
|
|
'Operation with namespace' => [
|
|
'some_namespace.some_operation',
|
|
'some_operation',
|
|
],
|
|
'Operation with multi-levels namespace' => [
|
|
'some.deep.namespace.some_operation',
|
|
'some_operation',
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider FindHandlerFromRouteProvider
|
|
* @covers \Combodo\iTop\Service\Router\Router::FindHandlerFromRoute
|
|
*
|
|
* @param string $sRouteNamespace
|
|
* @param string $sExpectedHandlerFQCN
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testFindHandlerFromRoute(string $sRoute, ?string $sExpectedHandlerFQCN): void
|
|
{
|
|
$oRouter = Router::GetInstance();
|
|
$sTestedHandlerFQCN = $this->InvokeNonPublicMethod(Router::class, 'FindHandlerFromRoute', $oRouter, [$sRoute]);
|
|
|
|
$this->assertEquals($sExpectedHandlerFQCN, $sTestedHandlerFQCN, "Handler found for '$sRoute' was not the expected one. Got '$sTestedHandlerFQCN', expected '$sExpectedHandlerFQCN'.");
|
|
}
|
|
|
|
public function FindHandlerFromRouteProvider(): array
|
|
{
|
|
return [
|
|
'Object controller' => [
|
|
'object.modify',
|
|
'Combodo\iTop\Controller\Base\Layout\ObjectController::OperationModify',
|
|
],
|
|
'Unknown controller' => [
|
|
'something_that_should_not_exist_in_the_default_package.foo',
|
|
null,
|
|
],
|
|
];
|
|
}
|
|
}
|