mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-23 18:48:51 +02:00
Merge remote-tracking branch 'origin/support/3.1' into develop
This commit is contained in:
@@ -79,6 +79,22 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
const USE_TRANSACTION = true;
|
||||
const CREATE_TEST_ORG = false;
|
||||
|
||||
protected static $aURP_Profiles = [
|
||||
'Administrator' => 1,
|
||||
'Portal user' => 2,
|
||||
'Configuration Manager' => 3,
|
||||
'Service Desk Agent' => 4,
|
||||
'Support Agent' => 5,
|
||||
'Problem Manager' => 6,
|
||||
'Change Implementor' => 7,
|
||||
'Change Supervisor' => 8,
|
||||
'Change Approver' => 9,
|
||||
'Service Manager' => 10,
|
||||
'Document author' => 11,
|
||||
'Portal power user' => 12,
|
||||
'REST Services User' => 1024,
|
||||
];
|
||||
|
||||
/**
|
||||
* This method is called before the first test of this test class is run (in the current process).
|
||||
*/
|
||||
|
||||
@@ -1,82 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
const USE_TRANSACTION = true;
|
||||
const CREATE_TEST_ORG = true;
|
||||
// Count the events by name
|
||||
private static array $aEventCalls = [];
|
||||
private static int $iEventCalls = 0;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::$aEventCalls = [];
|
||||
static::$iEventCalls = 0;
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public static function IncrementCallCount(string $sEvent)
|
||||
{
|
||||
self::$aEventCalls[$sEvent] = (self::$aEventCalls[$sEvent] ?? 0) + 1;
|
||||
self::$iEventCalls++;
|
||||
}
|
||||
|
||||
public function testCheckLinkModifications() {
|
||||
$aLinkModificationsStack = $this->GetObjectsAwaitingFireEventDbLinksChanged();
|
||||
$this->assertSame([], $aLinkModificationsStack);
|
||||
|
||||
// retain events
|
||||
cmdbAbstractObject::SetEventDBLinksChangedBlocked(true);
|
||||
|
||||
// Create the person
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
// Create the team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
// contact types
|
||||
$oContactType1 = MetaModel::NewObject(ContactType::class, ['name' => 'test_'.rand(10000, 99999)]);
|
||||
$oContactType1->DBInsert();
|
||||
$oContactType2 = MetaModel::NewObject(ContactType::class, ['name' => 'test_'.rand(10000, 99999)]);
|
||||
$oContactType2->DBInsert();
|
||||
|
||||
// Prepare the link for the insertion with the team
|
||||
|
||||
$aValues = [
|
||||
'person_id' => $oPerson->GetKey(),
|
||||
'role_id' => $oContactType1->GetKey(),
|
||||
'team_id' => $oTeam->GetKey(),
|
||||
];
|
||||
$oLinkPersonToTeam1 = MetaModel::NewObject(lnkPersonToTeam::class, $aValues);
|
||||
$oLinkPersonToTeam1->DBInsert();
|
||||
|
||||
$aLinkModificationsStack = $this->GetObjectsAwaitingFireEventDbLinksChanged();
|
||||
self::assertCount(3, $aLinkModificationsStack);
|
||||
$aExpectedLinkStack = [
|
||||
'Team' => [$oTeam->GetKey() => 1],
|
||||
'Person' => [$oPerson->GetKey() => 1],
|
||||
'ContactType' => [$oContactType1->GetKey() => 1],
|
||||
];
|
||||
self::assertSame($aExpectedLinkStack, $aLinkModificationsStack);
|
||||
|
||||
$oLinkPersonToTeam1->Set('role_id', $oContactType2->GetKey());
|
||||
$oLinkPersonToTeam1->DBWrite();
|
||||
$aLinkModificationsStack = $this->GetObjectsAwaitingFireEventDbLinksChanged();
|
||||
self::assertCount(3, $aLinkModificationsStack);
|
||||
$aExpectedLinkStack = [
|
||||
'Team' => [$oTeam->GetKey() => 2],
|
||||
'Person' => [$oPerson->GetKey() => 2],
|
||||
'ContactType' => [
|
||||
$oContactType1->GetKey() => 2,
|
||||
$oContactType2->GetKey() => 1,
|
||||
],
|
||||
];
|
||||
self::assertSame($aExpectedLinkStack, $aLinkModificationsStack);
|
||||
}
|
||||
|
||||
public function testProcessClassIdDeferredUpdate()
|
||||
{
|
||||
// Create the team
|
||||
@@ -152,288 +86,4 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
{
|
||||
$this->SetNonPublicStaticProperty(cmdbAbstractObject::class, 'aObjectsAwaitingEventDbLinksChanged', $aObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are not sent to the current updated/created object (Team)
|
||||
* the events are sent to the other side (Person)
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testDBInsertTeam()
|
||||
{
|
||||
// Prepare the link set
|
||||
$sLinkedClass = lnkPersonToTeam::class;
|
||||
$aLinkedObjectsArray = [];
|
||||
$oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray);
|
||||
$oLinkSet = new ormLinkSet(Team::class, 'persons_list', $oSet);
|
||||
|
||||
// Create the 3 persons
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$oPerson = $this->CreatePerson($i);
|
||||
$this->assertIsObject($oPerson);
|
||||
// Add the person to the link
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey()]);
|
||||
$oLinkSet->AddItem($oLink);
|
||||
}
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'persons_list' => $oLinkSet, 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
// 3 links added to person + 1 for the Team
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when creating a new lnk object
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testAddLinkToTeam()
|
||||
{
|
||||
// Create a person
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
// Create a Team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link creation will signal both the Person an the Team
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLink->DBInsert();
|
||||
|
||||
// 2 events one for Person and One for Team
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when updating an existing lnk object
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUpdateLinkRole()
|
||||
{
|
||||
// Create a person
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
// Create a Team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
// Create the link
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link update will signal both the Person, the Team and the ContactType
|
||||
// Change the role
|
||||
$oContactType = MetaModel::NewObject(ContactType::class, ['name' => 'test_'.$oLink->GetKey()]);
|
||||
$oContactType->DBInsert();
|
||||
$oLink->Set('role_id', $oContactType->GetKey());
|
||||
$oLink->DBUpdate();
|
||||
|
||||
// 3 events one for Person, one for Team and one for ContactType
|
||||
$this->assertEquals(3, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that when a link changes from an object to another, then both objects are notified
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUpdateLinkPerson()
|
||||
{
|
||||
// Create 2 person
|
||||
$oPerson1 = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson1);
|
||||
|
||||
$oPerson2 = $this->CreatePerson(2);
|
||||
$this->assertIsObject($oPerson2);
|
||||
|
||||
// Create a Team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
// Create the link between Person1 and Team
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson1->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link update will signal both the Persons and the Team
|
||||
// Change the person
|
||||
$oLink->Set('person_id', $oPerson2->GetKey());
|
||||
$oLink->DBUpdate();
|
||||
|
||||
// 3 events 2 for Person, one for Team
|
||||
$this->assertEquals(3, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when deleting an existing lnk object
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testDeleteLink()
|
||||
{
|
||||
// Create a person
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
// Create a Team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
// Create the link
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link delete will signal both the Person an the Team
|
||||
$oLink->DBDelete();
|
||||
|
||||
// 3 events one for Person, one for Team
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug called by event receivers
|
||||
*
|
||||
* @param $sMsg
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function DebugStatic($sMsg)
|
||||
{
|
||||
if (static::$DEBUG_UNIT_TEST) {
|
||||
if (is_string($sMsg)) {
|
||||
echo "$sMsg\n";
|
||||
} else {
|
||||
print_r($sMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Count events received
|
||||
* And allow callbacks on events
|
||||
*/
|
||||
class LinksEventReceiver
|
||||
{
|
||||
private $oTestCase;
|
||||
private $aCallbacks = [];
|
||||
|
||||
public static $bIsObjectInCrudStack;
|
||||
|
||||
public function __construct(ItopDataTestCase $oTestCase)
|
||||
{
|
||||
$this->oTestCase = $oTestCase;
|
||||
}
|
||||
|
||||
public function AddCallback(string $sEvent, string $sClass, string $sFct, int $iCount = 1): void
|
||||
{
|
||||
$this->aCallbacks[$sEvent][$sClass] = [
|
||||
'callback' => [$this, $sFct],
|
||||
'count' => $iCount,
|
||||
];
|
||||
}
|
||||
|
||||
public function CleanCallbacks()
|
||||
{
|
||||
$this->aCallbacks = [];
|
||||
}
|
||||
|
||||
// Event callbacks
|
||||
public function OnEvent(EventData $oData)
|
||||
{
|
||||
$sEvent = $oData->GetEvent();
|
||||
$oObject = $oData->Get('object');
|
||||
$sClass = get_class($oObject);
|
||||
$iKey = $oObject->GetKey();
|
||||
$this->Debug(__METHOD__.": received event '$sEvent' for $sClass::$iKey");
|
||||
cmdbAbstractObjectTest::IncrementCallCount($sEvent);
|
||||
|
||||
if (isset($this->aCallbacks[$sEvent][$sClass])) {
|
||||
$aCallBack = $this->aCallbacks[$sEvent][$sClass];
|
||||
if ($aCallBack['count'] > 0) {
|
||||
$this->aCallbacks[$sEvent][$sClass]['count']--;
|
||||
call_user_func($this->aCallbacks[$sEvent][$sClass]['callback'], $oObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function RegisterCRUDListeners(string $sEvent = null, $mEventSource = null)
|
||||
{
|
||||
$this->Debug('Registering Test event listeners');
|
||||
if (is_null($sEvent)) {
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_LINKS_CHANGED, [$this, 'OnEvent']);
|
||||
return;
|
||||
}
|
||||
$this->oTestCase->EventService_RegisterListener($sEvent, [$this, 'OnEvent'], $mEventSource);
|
||||
}
|
||||
|
||||
private function Debug($msg)
|
||||
{
|
||||
cmdbAbstractObjectTest::DebugStatic($msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ class QueryTest extends ItopDataTestCase
|
||||
}
|
||||
|
||||
$oQuery->DBInsert();
|
||||
$this->assertFalse($oQuery->IsNew());
|
||||
|
||||
return $oQuery;
|
||||
}
|
||||
|
||||
@@ -208,4 +208,34 @@ PHP
|
||||
$oFormFieldNoTouchedAtt = $oAttDef->MakeFormField($oPerson);
|
||||
$this->assertTrue($oFormFieldNoTouchedAtt->IsValidationDisabled(), 'email wasn\'t modified, we must not validate the corresponding field');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithConstraintParameterProvider
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param bool $bConstraintExpected
|
||||
* @param bool $bComputationExpected
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testWithConstraintAndComputationParameters(string $sClass, string $sAttCode, bool $bConstraintExpected, bool $bComputationExpected)
|
||||
{
|
||||
$oAttDef = \MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$sConstraintExpected = $bConstraintExpected ? 'true' : 'false';
|
||||
$sComputationExpected = $bComputationExpected ? 'true' : 'false';
|
||||
$this->assertEquals($bConstraintExpected, $oAttDef->HasPHPConstraint(), "Standard DataModel should be configured with property 'has_php_constraint'=$sConstraintExpected for $sClass:$sAttCode");
|
||||
$this->assertEquals($bComputationExpected, $oAttDef->HasPHPComputation(), "Standard DataModel should be configured with property 'has_php_computation'=$sComputationExpected for $sClass:$sAttCode");
|
||||
}
|
||||
|
||||
public function WithConstraintParameterProvider()
|
||||
{
|
||||
return [
|
||||
['User', 'profile_list', true, false],
|
||||
['User', 'allowed_org_list', true, false],
|
||||
['Person', 'team_list', false, false],
|
||||
['Ticket', 'functionalcis_list', false, true],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
namespace Combodo\iTop\Test\UnitTest\Core\CRUD;
|
||||
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ContactType;
|
||||
use CoreException;
|
||||
@@ -21,6 +20,7 @@ use ormLinkSet;
|
||||
use Person;
|
||||
use Team;
|
||||
use utils;
|
||||
use const EVENT_DB_LINKS_CHANGED;
|
||||
|
||||
class CRUDEventTest extends ItopDataTestCase
|
||||
{
|
||||
@@ -339,8 +339,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(4, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
$this->assertEquals(20, self::$iEventCalls);
|
||||
$this->assertArrayNotHasKey(EVENT_DB_LINKS_CHANGED, self::$aEventCalls, 'no relation with the with_php_compute attribute !');
|
||||
$this->assertEquals(16, self::$iEventCalls);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,8 +388,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(3, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
$this->assertEquals(19, self::$iEventCalls);
|
||||
$this->assertArrayNotHasKey(EVENT_DB_LINKS_CHANGED, self::$aEventCalls, 'no relation with the with_php_compute attribute !');
|
||||
$this->assertEquals(16, 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()]);
|
||||
@@ -495,7 +495,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oLnk = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLnk->DBInsert();
|
||||
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
$this->assertArrayNotHasKey(EVENT_DB_LINKS_CHANGED, self::$aEventCalls, 'no relation with the with_php_compute attribute !');
|
||||
}
|
||||
|
||||
public function testLinksDeleted()
|
||||
@@ -517,7 +517,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
|
||||
$oLnk->DBDelete();
|
||||
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
$this->assertArrayNotHasKey(EVENT_DB_LINKS_CHANGED, self::$aEventCalls, 'no relation with the with_php_compute attribute !');
|
||||
}
|
||||
|
||||
// Tests with MockDBObject
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core\DBObject;
|
||||
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use URP_UserProfile;
|
||||
use UserLocal;
|
||||
|
||||
class CheckToWritePropagationTest extends ItopDataTestCase
|
||||
{
|
||||
private static array $aEventCalls;
|
||||
|
||||
public function testCascadeCheckToWrite()
|
||||
{
|
||||
$sLogin = 'testCascadeCheckToWrite-'.uniqid('', true);
|
||||
$oUser1 = $this->CreateUser($sLogin, self::$aURP_Profiles['Administrator'], 'ABCD1234@gabuzomeu');
|
||||
$sUserId1 = $oUser1->GetKey();
|
||||
$sLogin = 'testCascadeCheckToWrite-'.uniqid('', true);
|
||||
$oUser2 = $this->CreateUser($sLogin, self::$aURP_Profiles['Administrator'], 'ABCD1234@gabuzomeu');
|
||||
$sUserId2 = $oUser2->GetKey();
|
||||
|
||||
$this->EventService_RegisterListener(EVENT_DB_CHECK_TO_WRITE, [$this, 'CheckToWriteEventListener'], 'User');
|
||||
$sEventKeyUser1 = $this->GetEventKey(EVENT_DB_CHECK_TO_WRITE, UserLocal::class, $sUserId1);
|
||||
$sEventKeyUser2 = $this->GetEventKey(EVENT_DB_CHECK_TO_WRITE, UserLocal::class, $sUserId2);
|
||||
|
||||
// Add URP_UserProfile
|
||||
self::$aEventCalls = [];
|
||||
$oURPUserProfile = new URP_UserProfile();
|
||||
$oURPUserProfile->Set('profileid', self::$aURP_Profiles['Support Agent']);
|
||||
$oURPUserProfile->Set('userid', $sUserId1);
|
||||
$oURPUserProfile->Set('reason', 'UNIT Tests');
|
||||
$oURPUserProfile->DBInsert();
|
||||
$this->assertArrayHasKey($sEventKeyUser1, self::$aEventCalls, 'User checkToWrite should be called when a URP_UserProfile is created');
|
||||
|
||||
// Update URP_UserProfile (change profile)
|
||||
self::$aEventCalls = [];
|
||||
$oURPUserProfile->Set('profileid', self::$aURP_Profiles['Problem Manager']);
|
||||
$oURPUserProfile->DBUpdate();
|
||||
$this->assertArrayHasKey($sEventKeyUser1, self::$aEventCalls, 'User checkToWrite should be called when a URP_UserProfile is updated');
|
||||
|
||||
// Update URP_UserProfile (move from User1 to User2)
|
||||
self::$aEventCalls = [];
|
||||
$oURPUserProfile->Set('userid', $sUserId2);
|
||||
$oURPUserProfile->DBUpdate();
|
||||
$this->assertCount(2, self::$aEventCalls, 'Previous User and new User checkToWrite should be called when a URP_UserProfile is moved from a User to another');
|
||||
$this->assertArrayHasKey($sEventKeyUser1, self::$aEventCalls, 'Previous User checkToWrite should be called when a URP_UserProfile is moved from a User to another');
|
||||
$this->assertArrayHasKey($sEventKeyUser2, self::$aEventCalls, 'New User checkToWrite should be called when a URP_UserProfile is moved from a User to another');
|
||||
|
||||
// Delete URP_UserProfile from User2
|
||||
self::$aEventCalls = [];
|
||||
$oURPUserProfile->DBDelete();
|
||||
$this->assertArrayHasKey($sEventKeyUser2, self::$aEventCalls, 'User checkToWrite should be called when a URP_UserProfile is deleted');
|
||||
|
||||
$oUser1->DBDelete();
|
||||
$oUser2->DBDelete();
|
||||
}
|
||||
|
||||
public function CheckToWriteEventListener(EventData $oEventData)
|
||||
{
|
||||
$oObject = $oEventData->GetEventData()['object'];
|
||||
$sEvent = $oEventData->GetEvent();
|
||||
$sClass = get_class($oObject);
|
||||
$sId = $oObject->GetKey();
|
||||
self::$aEventCalls[$this->GetEventKey($sEvent, $sClass, $sId)] = true;
|
||||
}
|
||||
|
||||
private function GetEventKey($sEvent, $sClass, $sId)
|
||||
{
|
||||
return "event: $sEvent, class: $sClass, id: $sId";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core\DBObject;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use URP_UserProfile;
|
||||
use User;
|
||||
use UserLocal;
|
||||
|
||||
class CustomCheckToWriteTest extends ItopDataTestCase
|
||||
{
|
||||
public function PortaPowerUserProvider()
|
||||
{
|
||||
return [
|
||||
'No profile' => [
|
||||
'aProfiles' => [],
|
||||
'bExpectedCheckStatus' => false,
|
||||
],
|
||||
'Portal power user' => [
|
||||
'aProfiles' => ['Portal power user',],
|
||||
'bExpectedCheckStatus' => true,
|
||||
],
|
||||
'Portal power user + Configuration Manager' => [
|
||||
'aProfiles' => ['Portal power user', 'Configuration Manager',],
|
||||
'bExpectedCheckStatus' => true,
|
||||
],
|
||||
'Portal power user + Configuration Manager + Admin' => [
|
||||
'aProfiles' => ['Portal power user', 'Configuration Manager', 'Administrator',],
|
||||
'bExpectedCheckStatus' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider PortaPowerUserProvider
|
||||
* @covers User::CheckPortalProfiles
|
||||
*/
|
||||
public function testUserLocalCheckPortalProfiles($aProfiles, $bExpectedCheckStatus)
|
||||
{
|
||||
$oUser = new UserLocal();
|
||||
$sLogin = 'testUserLocalCreationWithPortalPowerUserProfile-'.uniqid('', true);
|
||||
$oUser->Set('login', $sLogin);
|
||||
$oUser->Set('password', 'ABCD1234@gabuzomeu');
|
||||
$oUser->Set('language', 'EN US');
|
||||
$oProfileList = $oUser->Get('profile_list');
|
||||
|
||||
foreach ($aProfiles as $sProfileName) {
|
||||
$oAdminUrpProfile = new URP_UserProfile();
|
||||
$oAdminUrpProfile->Set('profileid', self::$aURP_Profiles[$sProfileName]);
|
||||
$oAdminUrpProfile->Set('reason', 'UNIT Tests');
|
||||
$oProfileList->AddItem($oAdminUrpProfile);
|
||||
}
|
||||
|
||||
$oUser->Set('profile_list', $oProfileList);
|
||||
|
||||
[$bCheckStatus, $aCheckIssues, $bSecurityIssue] = $oUser->CheckToWrite();
|
||||
$this->assertEquals($bExpectedCheckStatus, $bCheckStatus);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,29 @@ class MetaModelTest extends ItopDataTestCase
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MetaModel::GetObjectByName
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function testGetFinalClassName()
|
||||
{
|
||||
// Standalone classes
|
||||
$this->assertEquals('Organization', MetaModel::GetFinalClassName('Organization', 1), 'Should work with standalone classes');
|
||||
$this->assertEquals('SynchroDataSource', MetaModel::GetFinalClassName('SynchroDataSource', 1), 'Should work with standalone classes');
|
||||
|
||||
// 2 levels hierarchy
|
||||
$this->assertEquals('Person', MetaModel::GetFinalClassName('Contact', 1));
|
||||
$this->assertEquals('Person', MetaModel::GetFinalClassName('Person', 1));
|
||||
|
||||
// multi-level hierarchy
|
||||
$oServer1 = MetaModel::GetObjectByName('Server', 'Server1');
|
||||
$sServer1Id = $oServer1->GetKey();
|
||||
foreach (MetaModel::EnumParentClasses('Server',ENUM_PARENT_CLASSES_ALL) as $sClass) {
|
||||
$this->assertEquals('Server', MetaModel::GetFinalClassName($sClass, $sServer1Id), 'Should return Server for all the classes in the hierarchy');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group itopRequestMgmt
|
||||
* @covers MetaModel::ApplyParams()
|
||||
|
||||
@@ -488,29 +488,4 @@ class UserRightsTest extends ItopDataTestCase
|
||||
'with Admins hidden' => [true, 0],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithConstraintParameterProvider
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param bool $bExpected
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testWithConstraintParameter(string $sClass, string $sAttCode, bool $bExpected)
|
||||
{
|
||||
$oAttDef = \MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$this->assertTrue(method_exists($oAttDef, "GetHasConstraint"));
|
||||
$this->assertEquals($bExpected, $oAttDef->GetHasConstraint());
|
||||
}
|
||||
|
||||
public function WithConstraintParameterProvider()
|
||||
{
|
||||
return [
|
||||
['User', 'profile_list', true],
|
||||
['User', 'allowed_org_list', true],
|
||||
['Person', 'team_list', false],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\SessionTracker;
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\SessionTracker\SessionHandler;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ContextTag;
|
||||
|
||||
class SessionHandlerTest extends ItopDataTestCase
|
||||
{
|
||||
private $aFiles ;
|
||||
private $oTag ;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->aFiles=[];
|
||||
$this->oTag = new ContextTag(ContextTag::TAG_REST);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
$this->oTag = null;
|
||||
|
||||
foreach ($this->aFiles as $sFile){
|
||||
if (is_file($sFile)){
|
||||
@unlink($sFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function CreateUserAndLogIn() : ? string {
|
||||
$_SESSION = [];
|
||||
$oUser = $this->CreateContactlessUser("admin" . uniqid(), 1, "1234@Abcdefg");
|
||||
|
||||
\UserRights::Login($oUser->Get('login'));
|
||||
return $oUser->GetKey();
|
||||
}
|
||||
|
||||
private function GenerateSessionContent(SessionHandler $oSessionHandler, ?string $sPreviousFileVersionContent) : ?string {
|
||||
return $this->InvokeNonPublicMethod(SessionHandler::class, "generate_session_content", $oSessionHandler, $aArgs = [$sPreviousFileVersionContent]);
|
||||
}
|
||||
|
||||
/*
|
||||
* @covers SessionHandler::generate_session_content
|
||||
*/
|
||||
public function testGenerateSessionContentNoUserLoggedIn(){
|
||||
$oSessionHandler = new SessionHandler();
|
||||
$sContent = $this->GenerateSessionContent($oSessionHandler, null);
|
||||
$this->assertNull($sContent, "Session content should be null when there is no user logged in");
|
||||
}
|
||||
|
||||
public function GenerateSessionContentCorruptedPreviousFileContentProvider() {
|
||||
return [
|
||||
'not a json' => [ "not a json" ],
|
||||
'not an array' => [ json_encode("not an array") ],
|
||||
'array without creation_time key' => [ json_encode([]) ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers SessionHandler::generate_session_content
|
||||
* @dataProvider GenerateSessionContentCorruptedPreviousFileContentProvider
|
||||
*/
|
||||
public function testGenerateSessionContent_SessionFileRepairment(?string $sFileContent){
|
||||
$sUserId = $this->CreateUserAndLogIn();
|
||||
|
||||
$oSessionHandler = new SessionHandler();
|
||||
Session::Set('login_mode', 'foo_login_mode');
|
||||
|
||||
$sContent = $this->GenerateSessionContent($oSessionHandler, $sFileContent);
|
||||
|
||||
$this->assertNotNull($sContent, 'Should not return null');
|
||||
$aJson = json_decode($sContent, true);
|
||||
$this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sContent);
|
||||
$this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sContent");
|
||||
$this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sContent");
|
||||
$this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sContent");
|
||||
$this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sContent");
|
||||
}
|
||||
|
||||
/*
|
||||
* @covers SessionHandler::generate_session_content
|
||||
*/
|
||||
public function testGenerateSessionContent(){
|
||||
$sUserId = $this->CreateUserAndLogIn();
|
||||
|
||||
$oSessionHandler = new SessionHandler();
|
||||
Session::Set('login_mode', 'foo_login_mode');
|
||||
|
||||
//first time
|
||||
$sFirstContent = $this->GenerateSessionContent($oSessionHandler, null);
|
||||
|
||||
$this->assertNotNull($sFirstContent, 'Should not return null');
|
||||
$aJson = json_decode($sFirstContent, true);
|
||||
$this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sFirstContent);
|
||||
$this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sFirstContent");
|
||||
$this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sFirstContent");
|
||||
$this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sFirstContent");
|
||||
$this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sFirstContent");
|
||||
|
||||
$iFirstSessionCreationTime = $aJson['creation_time'];
|
||||
|
||||
// Switch context + change user id via impersonation
|
||||
// check it is still tracked in session files
|
||||
$oOtherUser = $this->CreateContactlessUser("admin" . uniqid(), 1, "1234@Abcdefg");
|
||||
$this->assertTrue(\UserRights::Impersonate($oOtherUser->Get('login')), "Failed to execute impersonate on: ".$oOtherUser->Get('login'));
|
||||
$oTag2 = new ContextTag(ContextTag::TAG_SYNCHRO);
|
||||
$sNewContent = $this->GenerateSessionContent($oSessionHandler, $sFirstContent);
|
||||
$this->assertNotNull($sNewContent, 'Should not return null');
|
||||
$aJson = json_decode($sNewContent, true);
|
||||
$this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sNewContent);
|
||||
$this->assertEquals(ContextTag::TAG_REST . '|' . ContextTag::TAG_SYNCHRO, $aJson['context'] ?? '', "After impersonation, should report the new context tags in [context]: $sNewContent");
|
||||
$this->assertEquals($iFirstSessionCreationTime, $aJson['creation_time'] ?? '', "After impersonation, should still report the the session start timestamp in [creation_time]: $sNewContent");
|
||||
$this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "After impersonation, should still report the login mode in [login_mode]: $sNewContent");
|
||||
$this->assertEquals($oOtherUser->GetKey(), $aJson['user_id'] ?? '', "Should report the impersonate user in [user_id]: $sNewContent");
|
||||
}
|
||||
|
||||
private function touchSessionFile(SessionHandler $oSessionHandler, $session_id) : ?string {
|
||||
$sRes = $this->InvokeNonPublicMethod(SessionHandler::class, "touch_session_file", $oSessionHandler, $aArgs = [$session_id]);
|
||||
if (!is_null($sRes) && is_file($sRes)) {
|
||||
// Record the file for cleanup on tearDown
|
||||
$this->aFiles[] = $sRes;
|
||||
}
|
||||
clearstatcache();
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
/*
|
||||
* @covers SessionHandler::touch_session_file
|
||||
*/
|
||||
public function testTouchSessionFile_NoUserLoggedIn(){
|
||||
$oSessionHandler = new SessionHandler();
|
||||
$session_id = uniqid();
|
||||
$sFile = $this->touchSessionFile($oSessionHandler, $session_id);
|
||||
$this->assertEquals(true, is_file($sFile), "Should return a file name: '$sFile' is not a valid file name");
|
||||
$sContent = file_get_contents($sFile);
|
||||
$this->assertEquals(null, $sContent, 'Should create an empty file, found: '.$sContent);
|
||||
}
|
||||
|
||||
/*
|
||||
* @covers SessionHandler::touch_session_file
|
||||
*/
|
||||
public function testTouchSessionFile_UserLoggedIn(){
|
||||
$sUserId = $this->CreateUserAndLogIn();
|
||||
Session::Set('login_mode', 'foo_login_mode');
|
||||
|
||||
$oSessionHandler = new SessionHandler();
|
||||
$session_id = uniqid();
|
||||
$sFile = $this->touchSessionFile($oSessionHandler, $session_id);
|
||||
$this->assertEquals(true, is_file($sFile), "Should return a file name: '$sFile' is not a valid file name");
|
||||
$sFirstContent = file_get_contents($sFile);
|
||||
|
||||
$iFirstCTime = filectime($sFile) - 1;
|
||||
// Set it in the past to check that it will be further updated (without the need to sleep...)
|
||||
touch($sFile, $iFirstCTime);
|
||||
|
||||
$this->assertNotNull($sFirstContent, 'Should not return null');
|
||||
$aJson = json_decode($sFirstContent, true);
|
||||
$this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sFirstContent);
|
||||
$this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sFirstContent");
|
||||
$this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sFirstContent");
|
||||
$this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sFirstContent");
|
||||
$this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sFirstContent");
|
||||
|
||||
$this->touchSessionFile($oSessionHandler, $session_id);
|
||||
$sNewContent = file_get_contents($sFile);
|
||||
$this->assertEquals($sFirstContent, $sNewContent, 'On successive calls, should not modify an existing session file');
|
||||
$this->assertGreaterThan($iFirstCTime, filectime($sFile), 'On successive calls, should have changed the file ctime');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers SessionHandler::touch_session_file
|
||||
*/
|
||||
public function testTouchSessionFileWithEmptySessionId() {
|
||||
$this->CreateUserAndLogIn();
|
||||
Session::Set('login_mode', 'toto');
|
||||
|
||||
$oSessionHandler = new SessionHandler();
|
||||
$this->assertNull($this->touchSessionFile($oSessionHandler, ''), 'Should return null when session id is an empty string');
|
||||
$this->assertNull($this->touchSessionFile($oSessionHandler, false), 'Should return null when session id (boolean) false');
|
||||
}
|
||||
|
||||
private function GetFilePath(SessionHandler $oSessionHandler, $session_id) : string {
|
||||
$sFile = $this->InvokeNonPublicMethod(SessionHandler::class, "get_file_path", $oSessionHandler, $aArgs = [$session_id]);
|
||||
// Record file for cleanup on tearDown
|
||||
$this->aFiles[] = $sFile;
|
||||
return $sFile;
|
||||
}
|
||||
|
||||
public function GgcWithTimeLimitProvider(){
|
||||
return [
|
||||
'no cleanup time limit' => [
|
||||
'iTimeLimit' => -1,
|
||||
'iExpectedProcessed' => 2
|
||||
],
|
||||
'cleanup time limit in the pass => first file removed only' => [
|
||||
'iTimeLimit' => time() - 1,
|
||||
'iExpectedProcessed' => 1
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers SessionHandler::gc_with_time_limit
|
||||
* @covers SessionHandler::list_session_files
|
||||
* @dataProvider GgcWithTimeLimitProvider
|
||||
*/
|
||||
public function testGgcWithTimeLimit($iTimeLimit, $iExpectedProcessed) {
|
||||
$oSessionHandler = new SessionHandler();
|
||||
//remove all first
|
||||
$oSessionHandler->gc_with_time_limit(-1);
|
||||
$this->assertEquals([], $oSessionHandler->list_session_files(), 'list_session_files should report no file at startup');
|
||||
|
||||
$max_lifetime = 1440;
|
||||
$iNbExpiredFiles = 2;
|
||||
$iNbFiles = 5;
|
||||
$iExpiredTimeStamp = time() - $max_lifetime - 1;
|
||||
for($i=0; $i<$iNbFiles; $i++) {
|
||||
$sFile = $this->GetFilePath($oSessionHandler, uniqid());
|
||||
file_put_contents($sFile, "fakedata");
|
||||
|
||||
if ($iNbExpiredFiles > 0){
|
||||
$iNbExpiredFiles--;
|
||||
touch($sFile, $iExpiredTimeStamp);
|
||||
}
|
||||
}
|
||||
|
||||
$aFoundSessionFiles = $oSessionHandler->list_session_files();
|
||||
$this->assertEquals($iNbFiles, sizeof($aFoundSessionFiles), 'list_session_files should reports all files');
|
||||
foreach ($aFoundSessionFiles as $sFile){
|
||||
$this->assertTrue(is_file($sFile), 'list_session_files should return a valid file paths, found: '.$sFile);
|
||||
}
|
||||
|
||||
$iProcessed = $oSessionHandler->gc_with_time_limit($max_lifetime, $iTimeLimit);
|
||||
$this->assertEquals($iExpectedProcessed, $iProcessed, 'gc_with_time_limit should report the count of expired files');
|
||||
$this->assertEquals($iNbFiles - $iExpectedProcessed, sizeof($oSessionHandler->list_session_files()), 'gc_with_time_limit should actually remove all processed files');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user