Merge branch 'support/3.1.0' into feature/5324-powerportaluser-repairprofiles

This commit is contained in:
odain
2023-09-01 15:15:16 +02:00
762 changed files with 26686 additions and 20666 deletions

View File

@@ -0,0 +1,108 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class ApplicationObjectExtensionTest extends \Combodo\iTop\Test\UnitTest\ItopDataTestCase
{
const CREATE_TEST_ORG = true;
// Count the calls by name
private static array $aCalls = [];
private static int $iCalls = 0;
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceUnitTestFile('iApplicationObjectExtension/MockApplicationObjectExtensionForTest1.php');
$this->ResetApplicationObjectExtensions();
// Count all the calls to this object
MockApplicationObjectExtensionForTest1::SetCallBack([ApplicationObjectExtensionTest::class, 'IncrementCallCount']);
}
public function tearDown(): void
{
MockApplicationObjectExtensionForTest1::SetModifications('Person', 'name', 0);
MockApplicationObjectExtensionForTest1::SetCallBack(null);
parent::tearDown();
}
public static function IncrementCallCount(string $sOrigin)
{
self::$aCalls[$sOrigin] = (self::$aCalls[$sOrigin] ?? 0) + 1;
self::$iCalls++;
}
public static function ResetCallCount()
{
self::$aCalls = [];
self::$iCalls = 0;
}
public function testExtensionCalled()
{
// Check that extension is called
$oPerson = $this->CreatePerson(1);
$oPerson->Set('first_name', 'testUpdateReentranceProtection');
MockApplicationObjectExtensionForTest1::SetModifications('Person', 'name', 1);
self::ResetCallCount();
$oPerson->DBUpdate();
// Called twice, the first call will provoke the DBUpdate and call again the object extension
$this->assertEquals(2, self::$iCalls);
}
public function testUpdateReentranceProtection()
{
$oPerson = $this->CreatePerson(1);
// Check that loop limit is 10
$i = 15;
self::ResetCallCount();
MockApplicationObjectExtensionForTest1::SetModifications('Person', 'name', $i);
$oPerson->Set('first_name', 'testUpdateReentranceProtection');
$oPerson->DBUpdate();
$this->assertEquals(10, self::$iCalls);
}
public function testModificationsOnUpdate()
{
$oPerson = $this->CreatePerson(1);
$oPerson->Set('first_name', 'testUpdateReentranceProtection');
self::ResetCallCount();
MockApplicationObjectExtensionForTest1::SetModifications('Person', 'name', 1);
$oPerson->DBUpdate();
$this->assertEquals(2, self::$iCalls);
}
public function testModificationsOnInsert()
{
self::ResetCallCount();
MockApplicationObjectExtensionForTest1::SetModifications('Person', 'name', 1);
$oPerson = $this->CreatePerson(1);
$this->assertEquals(2, self::$iCalls);
}
public function testModificationsOnInsertWith2Extensions()
{
self::ResetCallCount();
$this->RequireOnceUnitTestFile('iApplicationObjectExtension/MockApplicationObjectExtensionForTest2.php');
$this->ResetApplicationObjectExtensions();
// Count all the calls to this object
MockApplicationObjectExtensionForTest2::SetCallBack([ApplicationObjectExtensionTest::class, 'IncrementCallCount']);
MockApplicationObjectExtensionForTest1::SetModifications('Person', 'name', 2);
MockApplicationObjectExtensionForTest2::SetModifications('Person', 'first_name', 2);
$oPerson = $this->CreatePerson(1);
$this->assertEquals(6, self::$iCalls);
}
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* Copyright (C) 2018 Dennis Lassiter
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace Combodo\iTop\Test\UnitTest\Application;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use DesignerXMLField;
use utils;
/**
* @covers DesignerFormField
*/
class DesignerFormFieldTest extends ItopTestCase
{
/**
* @param string $sFieldFQCN
* @param string $sInputValue
* @param string $sExpectedValue
*
* @return void
* @throws \ReflectionException
* @covers DesignerLongTextField::PrepareValueForRendering
* @dataProvider PrepareValueForRenderingProvider
*/
public function testPrepareValueForRendering(string $sFieldFQCN, string $sInputValue, string $sExpectedValue)
{
$oField = new $sFieldFQCN('field_code', 'Field label', $sInputValue);
$sTestedValue = $this->InvokeNonPublicMethod($sFieldFQCN, 'PrepareValueForRendering', $oField, []);
$this->assertEquals($sExpectedValue, $sTestedValue);
}
public function PrepareValueForRenderingProvider(): array
{
return [
'DesignerLongTextField should not double encode XML' => [
'\\DesignerLongTextField',
<<<XML
<root>
<title id="title">Foo &amp; Bar</title>
</root>
XML,
<<<HTML
&lt;root&gt;
&lt;title id=&quot;title&quot;&gt;Foo &amp; Bar&lt;/title&gt;
&lt;/root&gt;
HTML
],
'DesignerXMLField should double encode XML' => [
'\\DesignerXMLField',
<<<XML
<root>
<title id="title">Foo &amp; Bar</title>
</root>
XML,
<<<HTML
&lt;root&gt;
&lt;title id=&quot;title&quot;&gt;Foo &amp;amp; Bar&lt;/title&gt;
&lt;/root&gt;
HTML
],
];
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Test object for AbstractApplicationObjectExtension API
*/
class MockApplicationObjectExtensionForTest1 extends AbstractApplicationObjectExtension
{
protected static $iCountModify;
protected static $sClass;
protected static $sAttCodeToModify;
protected static $callBack;
public function __construct()
{
}
public static function SetCallBack($callBack)
{
static::$callBack = $callBack;
}
public static function SetModifications($sClass, $sAttCodeToModify, $iCountModify)
{
static::$sClass = $sClass;
static::$sAttCodeToModify = $sAttCodeToModify;
if (!MetaModel::IsValidClass($sClass) || !MetaModel::IsValidAttCode($sClass, $sAttCodeToModify)) {
throw new Exception("Invalid class $sClass or attcode $sAttCodeToModify");
}
static::$iCountModify = $iCountModify;
}
public function OnDBUpdate($oObject, $oChange = null)
{
if (get_class($oObject) !== static::$sClass) {
return;
}
if (!is_null(static::$callBack)) {
call_user_func(static::$callBack, 'OnDBUpdate');
}
$aPreviousValues = $oObject->ListPreviousValuesForUpdatedAttributes();
$sPreviousValues = print_r($aPreviousValues, true);
IssueLog::Info(__METHOD__." received previous values:\n$sPreviousValues");
if (static::$iCountModify > 0) {
static::$iCountModify--;
$oObject->Set(static::$sAttCodeToModify, 'Value_'.rand());
$oObject->DBUpdate();
}
}
public function OnDBInsert($oObject, $oChange = null)
{
if (get_class($oObject) !== static::$sClass) {
return;
}
if (!is_null(static::$callBack)) {
call_user_func(static::$callBack, 'OnDBInsert');
}
if (static::$iCountModify > 0) {
static::$iCountModify--;
$oObject->Set(static::$sAttCodeToModify, 'Value_'.rand());
$oObject->DBUpdate();
}
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Test object for AbstractApplicationObjectExtension API
*/
class MockApplicationObjectExtensionForTest2 extends AbstractApplicationObjectExtension
{
protected static $iCountModify;
protected static $sClass;
protected static $sAttCodeToModify;
protected static $callBack;
public function __construct()
{
}
public static function SetCallBack($callBack)
{
static::$callBack = $callBack;
}
public static function SetModifications($sClass, $sAttCodeToModify, $iCountModify)
{
static::$sClass = $sClass;
static::$sAttCodeToModify = $sAttCodeToModify;
if (!MetaModel::IsValidClass($sClass) || !MetaModel::IsValidAttCode($sClass, $sAttCodeToModify)) {
throw new Exception("Invalid class $sClass or attcode $sAttCodeToModify");
}
static::$iCountModify = $iCountModify;
}
public function OnDBUpdate($oObject, $oChange = null)
{
if (get_class($oObject) !== static::$sClass) {
return;
}
if (!is_null(static::$callBack)) {
call_user_func(static::$callBack, 'OnDBUpdate');
}
$aPreviousValues = $oObject->ListPreviousValuesForUpdatedAttributes();
$sPreviousValues = print_r($aPreviousValues, true);
IssueLog::Info(__METHOD__." received previous values:\n$sPreviousValues");
if (static::$iCountModify > 0) {
static::$iCountModify--;
$oObject->Set(static::$sAttCodeToModify, 'Value_'.rand());
$oObject->DBUpdate();
}
}
public function OnDBInsert($oObject, $oChange = null)
{
if (get_class($oObject) !== static::$sClass) {
return;
}
if (!is_null(static::$callBack)) {
call_user_func(static::$callBack, 'OnDBInsert');
}
if (static::$iCountModify > 0) {
static::$iCountModify--;
$oObject->Set(static::$sAttCodeToModify, 'Value_'.rand());
$oObject->DBUpdate();
}
}
}

View File

@@ -841,17 +841,19 @@ class utilsTest extends ItopTestCase
*
* @dataProvider escapeHtmlProvider
*/
public function testEscapeHtml($sInput, $sExpectedEscaped)
public function testEscapeHtml($sInput, $sExpectedEscaped, $bDoubleEncode = false)
{
if (is_null($sExpectedEscaped)) {
$sExpectedEscaped = $sInput;
}
$sEscaped = utils::EscapeHtml($sInput);
$sEscaped = utils::EscapeHtml($sInput, $bDoubleEncode);
self::assertSame($sExpectedEscaped, $sEscaped);
$sEscapedDecoded = utils::EscapedHtmlDecode($sEscaped);
self::assertSame($sInput, $sEscapedDecoded);
if (false === $bDoubleEncode) {
self::assertSame($sInput, $sEscapedDecoded);
}
}
public function escapeHtmlProvider()
@@ -859,8 +861,17 @@ class utilsTest extends ItopTestCase
return [
'no escape' => ['abcdefghijklmnop', null],
'&amp;' => ['abcdefghijklmnop&0123456789', 'abcdefghijklmnop&amp;0123456789'],
['"double quotes"', '&quot;double quotes&quot;'],
["'simple quotes'", '&apos;simple quotes&apos;'],
'double quotes' => ['"double quotes"', '&quot;double quotes&quot;'],
'simple quotes' => ["'simple quotes'", '&apos;simple quotes&apos;'],
'no double encode' => [
'<root><title>Foo & Bar</title></root>',
'&lt;root&gt;&lt;title&gt;Foo &amp; Bar&lt;/title&gt;&lt;/root&gt;'
],
'double encode forced (for XML mostly)' => [
'<root><title>Foo &amp; Bar</title></root>',
'&lt;root&gt;&lt;title&gt;Foo &amp;amp; Bar&lt;/title&gt;&lt;/root&gt;',
true
],
];
}
}

View File

@@ -181,4 +181,17 @@ PHP
],
];
}
public function testMakeFormField(): void
{
$oPerson = $this->CreatePerson(1);
$oPerson->Set('email', 'toto@tutu.com');
$oAttDef = MetaModel::GetAttributeDef(get_class($oPerson), 'email');
$oFormFieldWithTouchedAtt = $oAttDef->MakeFormField($oPerson);
$this->assertFalse($oFormFieldWithTouchedAtt->IsValidationDisabled(), 'email is part of modified fields, we must have field validation');
$oPerson->DBUpdate(); // reset list of changed attributes
$oFormFieldNoTouchedAtt = $oAttDef->MakeFormField($oPerson);
$this->assertTrue($oFormFieldNoTouchedAtt->IsValidationDisabled(), 'email wasn\'t modified, we must not validate the corresponding field');
}
}

View File

@@ -284,8 +284,6 @@ class CRUDEventTest extends ItopDataTestCase
*/
public function testInfiniteUpdateDoneLoop()
{
$this->markTestSkipped('TEST Skipped: Protection not working.');
$oPerson = $this->CreatePerson(1);
$this->assertIsObject($oPerson);
@@ -343,7 +341,8 @@ class CRUDEventTest extends ItopDataTestCase
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_CHECK_TO_WRITE]);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_BEFORE_WRITE]);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_AFTER_WRITE]);
$this->assertEquals(16, self::$iEventCalls);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
$this->assertEquals(20, self::$iEventCalls);
}
/**
@@ -391,7 +390,8 @@ class CRUDEventTest extends ItopDataTestCase
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_CHECK_TO_WRITE]);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_BEFORE_WRITE]);
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_AFTER_WRITE]);
$this->assertEquals(16, self::$iEventCalls);
$this->assertEquals(3, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
$this->assertEquals(19, self::$iEventCalls);
// Read the object explicitly from the DB to check that the role has been set
$oSet = new DBObjectSet(DBSearch::FromOQL('SELECT Team WHERE id=:id'), [], ['id' => $oTeam->GetKey()]);
@@ -480,9 +480,52 @@ class CRUDEventTest extends ItopDataTestCase
$this->assertTrue(CRUDEventReceiver::$bIsObjectInCrudStack);
}
public function testLinksAdded()
{
// Create a Person
$oPerson = $this->CreatePerson(1);
// Create a Team
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeamWithLinkToAPerson', 'org_id' => $this->getTestOrgId()]);
$oTeam->DBInsert();
// Start receiving events
$oEventReceiver = new CRUDEventReceiver();
$oEventReceiver->RegisterCRUDListeners();
// Create a link between Person and Team => generate 2 EVENT_DB_LINKS_CHANGED
$oLnk = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
$oLnk->DBInsert();
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
}
public function testLinksDeleted()
{
// Create a Person
$oPerson = $this->CreatePerson(1);
// Create a Team
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeamWithLinkToAPerson', 'org_id' => $this->getTestOrgId()]);
$oTeam->DBInsert();
// Create a link between Person and Team => generate 2 EVENT_DB_LINKS_CHANGED
$oLnk = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
$oLnk->DBInsert();
// Start receiving events
$oEventReceiver = new CRUDEventReceiver();
$oEventReceiver->RegisterCRUDListeners();
$oLnk->DBDelete();
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
}
}
/**
* Add debug feature to test support class
*/
class ClassesWithDebug
{
/**
@@ -505,7 +548,7 @@ class ClassesWithDebug
}
/**
* Count events received
* Test support class used to count events received
* And allow callbacks on events
*/
class CRUDEventReceiver extends ClassesWithDebug
@@ -514,6 +557,18 @@ class CRUDEventReceiver extends ClassesWithDebug
public static $bIsObjectInCrudStack;
//
/**
* Add a specific callback for an event
*
* @param string $sEvent event name
* @param string $sClass event source class name
* @param string $sFct function to call on CRUDEventReceiver object
* @param int $iCount limit the number of calls to the callback
*
* @return void
*/
public function AddCallback(string $sEvent, string $sClass, string $sFct, int $iCount = 1): void
{
$this->aCallbacks[$sEvent][$sClass] = [
@@ -527,7 +582,15 @@ class CRUDEventReceiver extends ClassesWithDebug
$this->aCallbacks = [];
}
// Event callbacks
/**
* Event callbacks => this function counts the received events by event name and source class
* If AddCallback() method has been called a specific callback is called, else only the count is done
*
* @param \Combodo\iTop\Service\Events\EventData $oData
*
* @return void
*/
public function OnEvent(EventData $oData)
{
$sEvent = $oData->GetEvent();
@@ -556,6 +619,7 @@ class CRUDEventReceiver extends ClassesWithDebug
EventService::RegisterListener(EVENT_DB_BEFORE_WRITE, [$this, 'OnEvent']);
EventService::RegisterListener(EVENT_DB_AFTER_WRITE, [$this, 'OnEvent']);
EventService::RegisterListener(EVENT_DB_AFTER_DELETE, [$this, 'OnEvent']);
EventService::RegisterListener(EVENT_DB_LINKS_CHANGED, [$this, 'OnEvent']);
return;
}

View File

@@ -116,13 +116,13 @@ class DBObjectTest extends ItopDataTestCase
$oOrg->DBUpdate();
$this->assertCount(0, $oOrg->ListChanges());
$this->assertCount(0, $oOrg->ListPreviousValuesForUpdatedAttributes());
$this->assertCount(1, $oOrg->ListPreviousValuesForUpdatedAttributes());
$oOrg->Set('name', $oOrg->Get('name'));
$this->assertCount(0, $oOrg->ListChanges());
$oOrg->DBUpdate();
$this->assertCount(0, $oOrg->ListChanges());
$this->assertCount(0, $oOrg->ListPreviousValuesForUpdatedAttributes());
$this->assertCount(1, $oOrg->ListPreviousValuesForUpdatedAttributes());
$oOrg->DBDelete();
@@ -132,7 +132,7 @@ class DBObjectTest extends ItopDataTestCase
$oOrg->Set('code', strtoupper('testListPreviousValuesForUpdatedAttributes'));
$oOrg->DBUpdate();
$oOrg->DBUpdate();
$this->assertCount(0, $oOrg->ListPreviousValuesForUpdatedAttributes());
$this->assertCount(1, $oOrg->ListPreviousValuesForUpdatedAttributes());
}
/**
@@ -140,7 +140,7 @@ class DBObjectTest extends ItopDataTestCase
* @covers DBObject::Get
* @covers DBObject::Set
*/
public function testAttributeRefresh_FriendlyName()
public function testAttributeRefresh_FriendlyNameWithoutCascade()
{
$oObject = \MetaModel::NewObject('Person', array('name' => 'Foo', 'first_name' => 'John', 'org_id' => 3, 'location_id' => 2));
@@ -149,6 +149,21 @@ class DBObjectTest extends ItopDataTestCase
static::assertEquals('John Who', $oObject->Get('friendlyname'));
}
/**
* @covers DBObject::NewObject
* @covers DBObject::Get
* @covers DBObject::Set
*/
public function testAttributeRefresh_FriendlyNameWithCascade()
{
$oServer = \MetaModel::NewObject('Server', ['name' => 'ServerTest', 'org_id' => 3]);
$oServer->DBInsert();
$oDBServer = \MetaModel::NewObject('DBServer', ['name' => 'DBServerTest', 'org_id' => 3, 'system_id' => $oServer]);
static::assertEquals('ServerTest', $oDBServer->Get('system_name'));
static::assertEquals('DBServerTest ServerTest', $oDBServer->Get('friendlyname'));
}
/**
* @covers MetaModel::GetObject
* @covers DBObject::Get
@@ -174,8 +189,7 @@ class DBObjectTest extends ItopDataTestCase
public function testPartialAttributeEvaluation()
{
$oObject = \MetaModel::NewObject('Person', array('name' => 'Foo', 'org_id' => 3, 'location_id' => 2));
static::assertEquals('', $oObject->Get('friendlyname'));
static::assertEquals(' Foo', $oObject->Get('friendlyname'));
}
/**
@@ -186,7 +200,7 @@ class DBObjectTest extends ItopDataTestCase
{
$oObject = \MetaModel::NewObject('Person', array('org_id' => 3, 'location_id' => 2));
static::assertEquals('', $oObject->Get('friendlyname'));
static::assertEquals(' ', $oObject->Get('friendlyname'));
}
/**
@@ -198,7 +212,7 @@ class DBObjectTest extends ItopDataTestCase
$oUserProfile = new \URP_UserProfile();
$oUserProfile->Set('profileid', 2);
static::assertEquals('', $oUserProfile->Get('friendlyname'));
static::assertEquals('Link between and Portal user', $oUserProfile->Get('friendlyname'));
}
/**
@@ -206,7 +220,7 @@ class DBObjectTest extends ItopDataTestCase
* @covers DBObject::Get
* @covers DBObject::Set
*/
public function testAttributeRefresh_ObsolescenceFlag()
public function testAttributeRefresh_ObsolescenceFlagWithoutCascade()
{
$oObject = \MetaModel::NewObject('Person', array('name' => 'Foo', 'first_name' => 'John', 'org_id' => 3, 'location_id' => 2));
@@ -215,6 +229,24 @@ class DBObjectTest extends ItopDataTestCase
static::assertEquals(true, (bool)$oObject->Get('obsolescence_flag'));
}
/**
* @covers DBObject::NewObject
* @covers DBObject::Get
* @covers DBObject::Set
*/
public function testAttributeRefresh_ObsolescenceFlagWithCascade()
{
$this->markTestSkipped('Postponed');
// Necessary ext. key for $oDBServer
$oServer = \MetaModel::NewObject('Server', ['name' => 'ServerTest', 'org_id' => 3]);
$oServer->DBInsert();
$oDBServer = \MetaModel::NewObject('DBServer', ['name' => 'DBServerTest', 'org_id' => 3, 'system_id' => $oServer, 'status' => 'inactive']);
$oDBServer->DBInsert();
$oDBSchema = \MetaModel::NewObject('DatabaseSchema', ['name' => 'DBSchemaTest', 'org_id' => 3, 'dbserver_id' => $oDBServer]);
static::assertEquals(true, $oDBSchema->Get('obsolescence_flag'));
}
/**
* @covers DBObject::NewObject
* @covers DBObject::Get
@@ -520,7 +552,8 @@ class DBObjectTest extends ItopDataTestCase
$oPerson = $this->CreatePersonInstance();
// Insert without Reload
$oPerson->DBInsert();
$key = $oPerson->DBInsert();
$this->assertSame($key, $oPerson->GetKey());
// Get initial values
$aValues1 = [];
@@ -539,6 +572,9 @@ class DBObjectTest extends ItopDataTestCase
// 1st Reload
$oPerson->Reload(true);
// N°6281 - Rest API core/create key value is no more between quote
$this->assertSame($key, $oPerson->GetKey());
$sPerson2 = print_r($oPerson, true);
$this->assertNotEquals($sPerson1, $sPerson2);

View File

@@ -319,7 +319,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
['URP_UserProfile', ['profileid' => 2], 'friendlyname', ''],
['Location', ['name' => 'Grenoble', 'org_id' => 2], 'name', 'Grenoble'],
['Location', ['name' => 'Grenoble', 'org_id' => 2], 'friendlyname', ''],
['Location', ['name' => 'Grenoble', 'org_id' => 2], 'org_name', ''],
['Location', ['name' => 'Grenoble', 'org_id' => 2], 'org_name', 'IT Department'],
['Location', ['name' => 'Grenoble', 'org_id' => 2], 'org_id_friendlyname', ''],
['Location', ['name' => 'Grenoble', 'org_id' => 2], 'org_id', 2],
['Location', ['name' => 'Grenoble', 'org_id' => 2], 'CONCAT(SUBSTR(name, 4), " cause")', 'noble cause'],

View File

@@ -1,66 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1">
<classes>
<class id="ClassWithAttributeLinkedSetEditModeNone">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>none</edit_mode>
<relation_type>link</relation_type>
<read_only>true</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeAddOnly">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>add_only</edit_mode>
<relation_type>link</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeAddRemove">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>add_remove</edit_mode>
<relation_type>link</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeActions">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>actions</edit_mode>
<relation_type>link</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeInPlace">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>in_place</edit_mode>
<relation_type>property</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetNoEditMode">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<linked_class>Ticket</linked_class>
<relation_type>link</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetIndirect">
<fields>
<field id="status" xsi:type="AttributeLinkedSetIndirect">
<read_only>false</read_only>
</field>
</fields>
</class>
</classes>
</itop_design>

View File

@@ -1,52 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
<classes>
<class id="ClassWithAttributeLinkedSetEditModeNone">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>none</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeAddOnly">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>add_only</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeAddRemove">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>add_remove</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeActions">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>actions</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeInPlace">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>in_place</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetNoEditMode">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<linked_class>Ticket</linked_class>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetIndirect">
<fields>
<field id="status" xsi:type="AttributeLinkedSetIndirect"/>
</fields>
</class>
</classes>
</itop_design>

View File

@@ -4,53 +4,6 @@
<class id="ClassWithCustomZlist">
<presentation/>
</class>
<class id="ClassWithAttributeLinkedSetEditModeNone">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>none</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeAddOnly">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>add_only</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeAddRemove">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>add_remove</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeActions">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>actions</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeInPlace">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>in_place</edit_mode>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetNoEditMode">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<linked_class>Ticket</linked_class>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetIndirect">
<fields>
<field id="status" xsi:type="AttributeLinkedSetIndirect"/>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetDisplayStyle">
<fields>
<field id="status" xsi:type="AttributeLinkedSet"/>

View File

@@ -14,67 +14,6 @@
</custom_presentations>
</presentation>
</class>
<class id="ClassWithAttributeLinkedSetEditModeNone">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>none</edit_mode>
<relation_type>link</relation_type>
<read_only>true</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeAddOnly">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>add_only</edit_mode>
<relation_type>link</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeAddRemove">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>add_remove</edit_mode>
<relation_type>link</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeActions">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>actions</edit_mode>
<relation_type>link</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetEditModeInPlace">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<edit_mode>in_place</edit_mode>
<relation_type>property</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetNoEditMode">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">
<linked_class>Ticket</linked_class>
<relation_type>link</relation_type>
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetIndirect">
<fields>
<field id="status" xsi:type="AttributeLinkedSetIndirect">
<read_only>false</read_only>
</field>
</fields>
</class>
<class id="ClassWithAttributeLinkedSetDisplayStyle">
<fields>
<field id="status" xsi:type="AttributeLinkedSet">

View File

@@ -0,0 +1,127 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Sources\Form\Field;
use Combodo\iTop\Form\Field\StringField;
use Combodo\iTop\Form\Field\SubFormField;
use Combodo\iTop\Form\Validator\CustomRegexpValidator;
use Combodo\iTop\Form\Validator\IntegerValidator;
use Combodo\iTop\Form\Validator\MandatoryValidator;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
class FieldTest extends ItopTestCase
{
public function testIsValidationDisabled(): void
{
$oField = new StringField('test');
$oField->SetCurrentValue('toto@johny.invalid');
$sDumbEmailValidatorErrorMessage = 'dumb email validator error message';
$oDumbEmailValidator = new CustomRegexpValidator('\d+@\d+\.\d{2,3}', $sDumbEmailValidatorErrorMessage);
$oField->AddValidator($oDumbEmailValidator);
$bIsFieldValid = $oField->Validate();
$this->assertFalse($bIsFieldValid);
$this->assertCount(1, $oField->GetErrorMessages());
$this->assertSame($sDumbEmailValidatorErrorMessage, $oField->GetErrorMessages()[0]);
/** @noinspection PhpRedundantOptionalArgumentInspection */
$oField->SetValidationDisabled(true);
$this->assertTrue($oField->Validate());
}
public function testSetMandatory(): void
{
$oField = new StringField('test');
$this->assertCount(0, $oField->GetValidators());
$oField->SetMandatory(true);
$aValidatorsAfterSetMandatoryTrue = $oField->GetValidators();
$this->assertCount(1, $aValidatorsAfterSetMandatoryTrue);
$this->assertIsObject($aValidatorsAfterSetMandatoryTrue[0]);
$this->assertSame(MandatoryValidator::class, get_class($aValidatorsAfterSetMandatoryTrue[0]));
$oField->SetMandatory(false);
$this->assertCount(0, $oField->GetValidators());
}
public function testAddValidator(): void
{
$oField = new StringField('test');
$this->assertCount(0, $oField->GetValidators());
$oField->SetCurrentValue('not a numeric value');
$this->assertTrue($oField->Validate());
$oField->AddValidator(new IntegerValidator());
$aValidatorsAfterAddingIntegerValidator = $oField->GetValidators();
$this->assertCount(1, $aValidatorsAfterAddingIntegerValidator);
$this->assertIsObject($aValidatorsAfterAddingIntegerValidator[0]);
$this->assertSame(IntegerValidator::class, get_class($aValidatorsAfterAddingIntegerValidator[0]));
$this->assertFalse($oField->Validate());
$this->assertCount(1, $oField->GetErrorMessages());
}
public function testValidateWithTwoValidatorsFirstWrong(): void
{
$oField = new StringField('test');
$oField->SetCurrentValue('string with spaces');
$sFirstValidatorInvalidResultErrorMsg = 'dumb email validator error message';
$oFirstValidatorInvalidResult = new CustomRegexpValidator('^[a-z]+$', $sFirstValidatorInvalidResultErrorMsg);
$oField->AddValidator($oFirstValidatorInvalidResult);
$oSecondValidatorValidResult = new CustomRegexpValidator('^.+$', 'valid');
$oField->AddValidator($oSecondValidatorValidResult);
$this->assertFalse($oField->Validate());
$this->assertCount(1, $oField->GetErrorMessages());
$this->assertSame($sFirstValidatorInvalidResultErrorMsg, $oField->GetErrorMessages()[0]);
}
public function testSubFormFieldValidation(): void
{
$oSubFormField = new SubFormField('test_subformfield');
$oField = new StringField('test_field');
$oField->SetMandatory(true);
$oSubFormField->GetForm()->AddField($oField);
$bIsSubFormFieldValid = $oSubFormField->Validate();
$this->assertFalse($bIsSubFormFieldValid);
$this->assertCount(1, $oSubFormField->GetErrorMessages());
$oField->SetCurrentValue('test string');
$bIsSubFormFieldValidAfterFieldUpdate = $oSubFormField->Validate();
$this->assertTrue($bIsSubFormFieldValidAfterFieldUpdate);
$this->assertCount(0, $oSubFormField->GetErrorMessages());
}
public function testRemoveValidatorsOfClass(): void {
$oField = new StringField('test');
$this->assertCount(0, $oField->GetValidators());
$oField->RemoveValidatorsOfClass(CustomRegexpValidator::class);
$this->assertCount(0, $oField->GetValidators());
$oField->AddValidator(new IntegerValidator());
$this->assertCount(1, $oField->GetValidators());
$oField->RemoveValidatorsOfClass(CustomRegexpValidator::class);
$this->assertCount(1, $oField->GetValidators());
$oField->AddValidator(new CustomRegexpValidator('^.*$'));
$this->assertCount(2, $oField->GetValidators());
$oField->RemoveValidatorsOfClass(CustomRegexpValidator::class);
$this->assertCount(1, $oField->GetValidators());
$oField->AddValidator(new CustomRegexpValidator('^.*$'));
$oField->AddValidator(new CustomRegexpValidator('^.*$'));
$this->assertCount(3, $oField->GetValidators());
$oField->RemoveValidatorsOfClass(CustomRegexpValidator::class);
$this->assertCount(1, $oField->GetValidators());
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Form;
use Combodo\iTop\Form\Field\LinkedSetField;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ormLinkSet;
use Person;
class LinkedSetFieldTest extends ItopDataTestCase {
public function testValidate(): void {
$sLinkedClass = Ticket::class;
$oLinkedSetField = new LinkedSetField('test');
$oLinkedSetField->SetIndirect(false);
$oLinkedSetField->SetTargetClass($sLinkedClass);
$oLinkedSetField->SetLinkedClass($sLinkedClass);
$oLinkedSetField->SetLnkAttributesToDisplay(['title' => 'title']);
$oSetThreeExistingTickets = new ormLinkSet(Person::class, 'tickets_list');
$this->CreateTestOrganization();
$oUserRequest1 = $this->CreateUserRequest(1);
$oUserRequest2 = $this->CreateUserRequest(2);
$oUserRequest3 = $this->CreateUserRequest(3);
$oSetThreeExistingTickets->AddItem($oUserRequest1);
$oSetThreeExistingTickets->AddItem($oUserRequest2);
$oSetThreeExistingTickets->AddItem($oUserRequest3);
$oLinkedSetField->SetCurrentValue($oSetThreeExistingTickets);
$this->assertTrue($oLinkedSetField->Validate(), 'A set with existing objects and no modifications must be OK');
$oUserRequest1->Set('title', 'this a modified title !');
$this->assertTrue($oLinkedSetField->Validate(), 'A set with existing objects and a valid modification must be OK');
$oUserRequest1->Set('title', '');
$this->assertFalse($oLinkedSetField->Validate(), 'A set with existing objects and an invalid modification must be KO');
}
}

View File

@@ -0,0 +1,107 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Form;
use Combodo\iTop\Form\Field\MultipleChoicesField;
use Combodo\iTop\Form\Field\MultipleSelectField;
use Combodo\iTop\Form\Field\SelectField;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use ContextTag;
class MultipleChoicesFieldTest extends ItopTestCase
{
public function testValidateForMultipleSelectField(): void
{
$oMultipleChoicesField = new MultipleSelectField('test');
$oMultipleChoicesField->AddChoice('A');
$oMultipleChoicesField->AddChoice('B');
$oMultipleChoicesField->AddChoice('C');
$oMultipleChoicesField->AddChoice('D');
// N°1150 the control was added for the REST API initially and was only triggered with the corresponding ContextTag
$oRestContext = new ContextTag(ContextTag::TAG_REST);
$this->ValidateMultipleSelectField($oMultipleChoicesField);
// retrying without REST context
unset($oRestContext);
$this->ValidateMultipleSelectField($oMultipleChoicesField);
}
private function ValidateMultipleSelectField(MultipleChoicesField $oMultipleChoicesField): void
{
$oMultipleChoicesField->SetCurrentValue(null);
$this->assertTrue($oMultipleChoicesField->Validate(), 'No value must be valid');
$sExistingValue = 'A';
$sNonExistingValue = 'Non existing choice';
$sNonExistingValue1 = 'Non existing choice 1';
$sNonExistingValue2 = 'Non existing choice 2';
$oMultipleChoicesField->SetCurrentValue($sExistingValue);
$this->assertTrue($oMultipleChoicesField->Validate(), 'Value among possible ones is valid');
$oMultipleChoicesField->SetCurrentValue($sNonExistingValue);
$this->assertFalse($oMultipleChoicesField->Validate(), 'Value not among possible ones is invalid');
$this->assertCount(1, $oMultipleChoicesField->GetErrorMessages());
$this->assertStringContainsString($sNonExistingValue, $oMultipleChoicesField->GetErrorMessages()[0]);
$oMultipleChoicesField->SetCurrentValue([$sNonExistingValue1, $sNonExistingValue2]);
$this->assertFalse($oMultipleChoicesField->Validate(), 'Multiple values not among possible ones is invalid');
$this->assertCount(2, $oMultipleChoicesField->GetErrorMessages());
$this->assertStringContainsString($sNonExistingValue1, $oMultipleChoicesField->GetErrorMessages()[0]);
$this->assertStringContainsString($sNonExistingValue2, $oMultipleChoicesField->GetErrorMessages()[1]);
$oMultipleChoicesField->SetCurrentValue([$sExistingValue, $sNonExistingValue]);
$this->assertFalse($oMultipleChoicesField->Validate(), 'Valid value + Value not among possible ones is invalid');
$this->assertCount(1, $oMultipleChoicesField->GetErrorMessages());
$this->assertStringContainsString($sNonExistingValue, $oMultipleChoicesField->GetErrorMessages()[0]);
$oMultipleChoicesField->SetCurrentValue([$sExistingValue, $sNonExistingValue1, $sNonExistingValue2]);
$this->assertFalse($oMultipleChoicesField->Validate(), 'Valid value + Multiple values not among possible ones is invalid');
$this->assertCount(2, $oMultipleChoicesField->GetErrorMessages());
$this->assertStringContainsString($sNonExistingValue1, $oMultipleChoicesField->GetErrorMessages()[0]);
$this->assertStringContainsString($sNonExistingValue2, $oMultipleChoicesField->GetErrorMessages()[1]);
}
public function testValidateForSelectField(): void
{
$oMultipleChoicesField = new SelectField('test');
$oMultipleChoicesField->AddChoice('A');
$oMultipleChoicesField->AddChoice('B');
$oMultipleChoicesField->AddChoice('C');
$oMultipleChoicesField->AddChoice('D');
// N°1150 the control was added for the REST API initially and was only triggered with the corresponding ContextTag
$oRestContext = new ContextTag(ContextTag::TAG_REST);
$this->ValidateSelectField($oMultipleChoicesField);
// retrying without REST context
unset($oRestContext);
$this->ValidateSelectField($oMultipleChoicesField);
$oMultipleChoicesField = new SelectField('test');
$oMultipleChoicesField->SetChoices(['A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D']);
$this->ValidateSelectField($oMultipleChoicesField);
}
private function ValidateSelectField(MultipleChoicesField $oMultipleChoicesField): void
{
$oMultipleChoicesField->SetCurrentValue(null);
$this->assertTrue($oMultipleChoicesField->Validate(), 'No value must be valid');
$sExistingValue = 'A';
$sNonExistingValue = 'Non existing choice';
$oMultipleChoicesField->SetCurrentValue($sExistingValue);
$this->assertTrue($oMultipleChoicesField->Validate(), 'Value among possible ones is valid');
$oMultipleChoicesField->SetCurrentValue($sNonExistingValue);
$this->assertFalse($oMultipleChoicesField->Validate(), 'Value not among possible ones is invalid');
$this->assertCount(1, $oMultipleChoicesField->GetErrorMessages());
$this->assertStringContainsString($sNonExistingValue, $oMultipleChoicesField->GetErrorMessages()[0]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Form;
use Combodo\iTop\Form\Field\SelectObjectField;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ContextTag;
use DBObjectSearch;
use Organization;
class SelectObjectFieldTest extends ItopDataTestCase {
public function testValidate(): void {
$oSelectObjectField = new SelectObjectField('test');
$oSelectObjectField->SetSearch(DBObjectSearch::FromOQL('SELECT '.Organization::class));
// N°1150 the control was added for the REST API initially and was only triggered with the corresponding ContextTag
$oRestContext = new ContextTag(ContextTag::TAG_REST);
$this->ValidateSelectObjectField($oSelectObjectField);
// retrying without REST context
unset($oRestContext);
$this->ValidateSelectObjectField($oSelectObjectField);
}
private function ValidateSelectObjectField(SelectObjectField $oSelectObjectField): void {
$oSelectObjectField->SetCurrentValue(null);
$this->assertTrue($oSelectObjectField->Validate(), 'No value must be valid');
$sExistingOrganizationId = 1;
$oSelectObjectField->SetCurrentValue($sExistingOrganizationId);
$this->assertTrue($oSelectObjectField->Validate(), 'An existing object id must be valid');
$sNonExistingOrganizationId = 999999;
$oSelectObjectField->SetCurrentValue($sNonExistingOrganizationId);
$this->assertFalse($oSelectObjectField->Validate(), 'An non existing object id must be invalid');
$this->assertCount(1, $oSelectObjectField->GetErrorMessages());
$this->assertStringContainsString($sNonExistingOrganizationId, $oSelectObjectField->GetErrorMessages()[0]);
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Sources\Form\Validator;
use Combodo\iTop\Form\Field\StringField;
use Combodo\iTop\Form\Validator\MandatoryValidator;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
class ValidatorTest extends ItopTestCase
{
public function testMandatoryValidator()
{
$oField = new StringField('test');
$oField->SetMandatory(true);
$oField->SetCurrentValue('there is a value !');
$bIsMandatoryFieldValidWithExistingValue = $oField->Validate();
$this->assertTrue($bIsMandatoryFieldValidWithExistingValue);
$oField->SetCurrentValue(null);
$bIsMandatoryFieldValidWithNoValue = $oField->Validate();
$this->assertFalse($bIsMandatoryFieldValidWithNoValue);
$this->assertNotEmpty($oField->GetErrorMessages());
$this->assertCount(1, $oField->GetErrorMessages());
$this->assertStringContainsString(MandatoryValidator::DEFAULT_ERROR_MESSAGE, $oField->GetErrorMessages()[0]);
}
}

View File

@@ -20,6 +20,16 @@ use utils;
*/
class RouterTest extends ItopTestCase
{
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceItopFile('setup/setuputils.class.inc.php');
}
/**
* @covers \Combodo\iTop\Service\Router\Router::GenerateUrl
* @dataProvider GenerateUrlProvider
@@ -169,6 +179,94 @@ class RouterTest extends ItopTestCase
$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)");
}
clearstatcache();
$iFirstModificationTimestamp = filemtime($sRoutesCacheFilePath);
$this->debug("Initial timestamp: $iFirstModificationTimestamp");
// Wait for just 1s to ensure timestamps would be different is the file is re-generated
sleep(1);
// 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 [

View File

@@ -0,0 +1,172 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Service\TemporaryObjects;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectConfig;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectRepository;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class TemporaryObjectManagerTest extends ItopDataTestCase
{
const USE_TRANSACTION = true;
const CREATE_TEST_ORG = false;
private TemporaryObjectConfig $oConfig;
private $oManager;
protected function setUp(): void
{
parent::setUp();
$this->oConfig = TemporaryObjectConfig::GetInstance();
$this->oManager = TemporaryObjectManager::GetInstance();
}
public function testCreateTemporaryObject()
{
$sTempId = 'testCreateTemporaryObject';
$this->oConfig->SetConfigTemporaryLifetime(3000);
$this->oConfig->SetConfigTemporaryForce(true);
$oDescriptor = $this->oManager->CreateTemporaryObject($sTempId, 'FakedClass', -1, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertNull( $oDescriptor);
$oOrg = $this->CreateTestOrganization();
$oDescriptor = $this->CreateTemporaryObject($sTempId, $oOrg, 3000, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertNotNull( $oDescriptor);
}
public function testCancelAllTemporaryObjects()
{
$sTempId = 'testCancelAllTemporaryObjects';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->oManager->CancelAllTemporaryObjects($sTempId);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000, TemporaryObjectHelper::OPERATION_CREATE);
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(2, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->oManager->CancelAllTemporaryObjects($sTempId);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
}
public function testExtendTemporaryObjectsLifetime()
{
$sTempId = 'testExtendTemporaryObjectsLifetime';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->assertEquals(1, $oRepository->SearchByExpired()->Count());
$this->oConfig->SetConfigTemporaryLifetime(3000);
$this->oManager->ExtendTemporaryObjectsLifetime($sTempId);
$this->assertEquals(0, $oRepository->SearchByExpired()->Count());
}
public function testGarbageExpiredTemporaryObjects()
{
$sTempId = 'testGarbageExpiredTemporaryObjects';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->assertEquals(1, $oRepository->SearchByExpired()->Count());
$this->oManager->GarbageExpiredTemporaryObjects();
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1, TemporaryObjectHelper::OPERATION_CREATE);
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(2, $oRepository->SearchByExpired()->Count());
$this->oManager->GarbageExpiredTemporaryObjects();
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->oManager->GarbageExpiredTemporaryObjects();
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
}
public function testHandleCreatedTemporaryObjects()
{
$sTempId = 'testHandleTemporaryObjects';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$oOrgTemp = $this->CreateTestOrganization();
$oOrg->Set('parent_id', $oOrgTemp->GetKey());
$oOrg->DBUpdate();
$aContext = ['create' => ['transaction_id' => $sTempId, 'host_class' => get_class($oOrg), 'host_att_code' => 'parent_id',]];
$this->oConfig->SetConfigTemporaryForce(true);
$this->oConfig->SetConfigTemporaryLifetime(3000);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->oManager->HandleTemporaryObjects($oOrg, $aContext);
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$aContext = ['finalize' => ['transaction_id' => $sTempId,]];
$this->oManager->HandleTemporaryObjects($oOrg, $aContext);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
}
public function testHandleDeletedTemporaryObjects()
{
$sTempId = 'testHandleTemporaryObjectsDelete';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$oOrgTemp = $this->CreateTestOrganization();
$oOrg->Set('parent_id', $oOrgTemp->GetKey());
$oOrg->DBUpdate();
// Create a temporary delete
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$oTemporaryObjectDescriptor = TemporaryObjectManager::GetInstance()->CreateTemporaryObject($sTempId, get_class($oOrgTemp), $oOrgTemp->Get('id'), TemporaryObjectHelper::OPERATION_DELETE);
$oTemporaryObjectDescriptor->Set('host_class', get_class($oOrg));
$oTemporaryObjectDescriptor->Set('host_id', $oOrg->GetKey());
$oTemporaryObjectDescriptor->Set('host_att_code', 'parent_id');
$oTemporaryObjectDescriptor->DBUpdate();
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$aContext = ['finalize' => ['transaction_id' => $sTempId,]];
$this->oManager->HandleTemporaryObjects($oOrg, $aContext);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$oDeletedObject = \MetaModel::GetObject(get_class($oOrgTemp), $oOrgTemp->Get('id'), false);
$this->assertNull($oDeletedObject);
}
private function CreateTemporaryObject($sTempId, $oDBObject, int $iLifetime, string $sOperation)
{
$this->oConfig->SetConfigTemporaryLifetime($iLifetime);
$this->oConfig->SetConfigTemporaryForce(true);
return $this->oManager->CreateTemporaryObject($sTempId, get_class($oDBObject), $oDBObject->GetKey(), $sOperation);
}
}

View File

@@ -0,0 +1,103 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Service\TemporaryObjects;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectConfig;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectRepository;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DBObject;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class TemporaryObjectRepositoryTest extends ItopDataTestCase
{
const USE_TRANSACTION = true;
const CREATE_TEST_ORG = false;
private TemporaryObjectConfig $oTemporaryObjectConfig;
protected function setUp(): void
{
parent::setUp();
$this->oTemporaryObjectConfig = TemporaryObjectConfig::GetInstance();
}
public function testSearchByExpired()
{
$sTempId = 'testSearchByExpired';
$oOrg = $this->CreateTestOrganization();
$oRepository = TemporaryObjectRepository::GetInstance();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$oObjectSet = $oRepository->SearchByExpired();
$this->assertEquals(0, $oObjectSet->Count());
$this->CreateTemporaryObject($sTempId, $oOrg, -1);
$oObjectSet = $oRepository->SearchByExpired();
$this->assertEquals(1, $oObjectSet->Count());
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1);
$oObjectSet = $oRepository->SearchByExpired();
$this->assertEquals(2, $oObjectSet->Count());
}
public function testSearchByTempId()
{
$sTempId = 'testSearchByTempId';
// First temp object
$oOrg = $this->CreateTestOrganization();
$oDescriptor = $this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$oRepository = TemporaryObjectRepository::GetInstance();
$oObjectSet = $oRepository->SearchByTempId($sTempId);
$this->assertEquals(1, $oObjectSet->Count());
$oDBObject = $oObjectSet->Fetch();
$this->assertEquals($oDescriptor->GetKey(), $oDBObject->GetKey());
$this->assertEquals(get_class($oDescriptor), get_class($oDBObject));
// Second temp object
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$oObjectSet = $oRepository->SearchByTempId($sTempId);
$this->assertEquals(2, $oObjectSet->Count());
}
public function testCountTemporaryObjectsByTempId()
{
$sTempId = 'testCountTemporaryObjectsByTempId';
// First temp object
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$oRepository = TemporaryObjectRepository::GetInstance();
$iCount = $oRepository->CountTemporaryObjectsByTempId($sTempId);
$this->assertEquals(1, $iCount);
// Second temp object
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$iCount = $oRepository->CountTemporaryObjectsByTempId($sTempId);
$this->assertEquals(2, $iCount);
}
private function CreateTemporaryObject($sTempId, DBObject $oDBObject, int $iLifetime)
{
$this->oTemporaryObjectConfig->SetConfigTemporaryLifetime($iLifetime);
$this->oTemporaryObjectConfig->SetConfigTemporaryForce(true);
$oManager = TemporaryObjectManager::GetInstance();
return $oManager->CreateTemporaryObject($sTempId, get_class($oDBObject), $oDBObject->GetKey(), TemporaryObjectHelper::OPERATION_CREATE);
}
}

View File

@@ -106,7 +106,7 @@ JSON;
$iId = $aJson['objects'][$sUserRequestKey]['key'];
$sExpectedJsonOuput = <<<JSON
{"objects":{"UserRequest::$iId":{"code":0,"message":"created","class":"UserRequest","key":$iId,"fields":{"id":$iId}}},"code":0,"message":null}
{"objects":{"UserRequest::$iId":{"code":0,"message":"created","class":"UserRequest","key":"$iId","fields":{"id":"$iId"}}},"code":0,"message":null}
JSON;
$this->assertJsonStringEqualsJsonString($sExpectedJsonOuput, $sOutputJson);