/** * Core test list * * @copyright Copyright (C) 2010-2024 Combodo SAS * @license http://opensource.org/licenses/AGPL-3.0 */ class TestBeHappy extends TestHandler // TestFunctionInOut, TestBizModel... { public static function GetName() { return 'Be happy!'; } public static function GetDescription() { return 'Sample test with success'; } protected function DoExecute() { echo "

Am I happy?

"; echo "

Yes, I am!

"; } } class TestBeSad extends TestHandler { public static function GetName() { return 'Be sad...'; } public static function GetDescription() { return 'Sample test with failure'; } protected function DoExecute() { echo "Am I happy?"; throw new Exception('jamais content'); } } class TestUserRightsMatrixItop extends TestUserRights { public static function GetName() { return 'User rights test on user rights matrix'; } public static function GetDescription() { return 'blah blah blah'; } public function DoPrepare() { parent::DoPrepare(); MetaModel::Startup('../config-test-itopv06.php'); } protected function DoExecute() { $sUser = 'Romain'; echo "

Totor: ".(UserRights::CheckCredentials('Totor', 'toto') ? 'ok' : 'NO')."

\n"; echo "

Romain: ".(UserRights::CheckCredentials('Romain', 'toto') ? 'ok' : 'NO')."

\n"; echo "

User: ".UserRights::GetUser()."

\n"; echo "

On behalf of...".UserRights::GetRealUser()."

\n"; echo "

Denis (impersonate) : ".(UserRights::Impersonate('Denis', 'tutu') ? 'ok' : 'NO')."

\n"; echo "

User: ".UserRights::GetUser()."

\n"; echo "

On behalf of...".UserRights::GetRealUser()."

\n"; $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT bizOrganization")); echo "

IsActionAllowed...".(UserRights::IsActionAllowed('bizOrganization', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

\n"; echo "

IsStimulusAllowed...".(UserRights::IsStimulusAllowed('bizOrganization', 'myStimulus', $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

\n"; echo "

IsActionAllowedOnAttribute...".(UserRights::IsActionAllowedOnAttribute('bizOrganization', 'myattribute', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

\n"; return true; } } /////////////////////////////////////////////////////////////////////////// // Test a complex biz model on the fly /////////////////////////////////////////////////////////////////////////// class TestMyBizModel extends TestBizModel { public static function GetName() { return 'A series of tests on a weird business model'; } public static function GetDescription() { return 'Attempts various operations and build complex queries'; } public static function GetConfigFile() { return '/config-test-mymodel.php'; } public function test_linksinfo() { echo "

Enum links

"; self::DumpVariable(MetaModel::EnumReferencedClasses("cmdbTeam")); self::DumpVariable(MetaModel::EnumReferencingClasses("Organization")); self::DumpVariable(MetaModel::GetLinkClasses()); self::DumpVariable(MetaModel::GetLinkLabel("Liens_entre_contacts_et_workshop", "toworkshop")); } public function test_list_attributes() { echo "

List attributes

"; foreach (MetaModel::ListAttributeDefs("cmdbTeam") as $sAttCode => $oAttDef) { echo $oAttDef->GetLabel()." / ".$oAttDef->GetDescription()." / ".$oAttDef->GetType()."
\n"; } } public function test_search() { echo "

Two searches

"; $oFilterAllDevs = new DBObjectSearch("cmdbTeam"); $oAllDevs = new DBObjectSet($oFilterAllDevs); echo "Found ".$oAllDevs->Count()." items.
\n"; while ($oDev = $oAllDevs->Fetch()) { $aValues = []; foreach (MetaModel::GetAttributesList($oAllDevs->GetClass()) as $sAttCode) { $aValues[] = MetaModel::GetLabel(get_class($oDev), $sAttCode)." (".MetaModel::GetDescription(get_class($oDev), $sAttCode).") = ".$oDev->GetAsHTML($sAttCode); } echo $oDev->GetKey()." => ".implode(", ", $aValues)."
\n"; } // a second one $oMyFilter = new DBObjectSearch("cmdbContact"); //$oMyFilter->AddCondition("name", "aii", "Finishes with"); $oMyFilter->AddCondition("name", "aii"); $this->search_and_show_list($oMyFilter); } public function test_reload() { echo "

Reload

"; $team = MetaModel::GetObject("cmdbContact", "2"); echo "Chargement de l'attribut headcount: {$team->Get("headcount")}
\n"; self::DumpVariable($team); } public function test_setattribute() { echo "

Set attribute and update

"; /** @var cmdbTeam $team */ $team = MetaModel::GetObject("cmdbTeam", "2"); $team->Set("headcount", rand(1, 1000)); $team->Set("email", "Luis ".rand(9, 250)); self::DumpVariable($team->ListChanges()); echo "New headcount = {$team->Get("headcount")}
\n"; echo "Computed name = {$team->Get("name")}
\n"; CMDBObject::SetTrackInfo('test_setattribute / Made by robot #'.rand(1, 100)); //DBSearch::StartDebugQuery(); $team->DBUpdate(); //DBSearch::StopDebugQuery(); echo "

Check the modified team

"; $oTeam = MetaModel::GetObject("cmdbTeam", "2"); self::DumpVariable($oTeam); } public function test_newobject() { echo "

Create a new object (team)

"; $oNewTeam = MetaModel::NewObject("cmdbTeam"); $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); $oNewTeam->Set("email", "machin".rand(1, 100)."@tnut.com"); $oNewTeam->Set("email", null); $oNewTeam->Set("owner", "ITOP"); $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value $iId = $oNewTeam->DBInsert(); echo "Created new team: $iId
"; echo "

Delete team #$iId

"; $oTeam = MetaModel::GetObject("cmdbTeam", $iId); $oTeam->DBDelete(); echo "Deleted team: $iId
"; self::DumpVariable($oTeam); } public function test_updatecolumn() { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); $oMyChange->Set("userinfo", "test_updatecolumn / Made by robot #".rand(1, 100)); $iChangeId = $oMyChange->DBInsert(); $sNewEmail = "updatecol".rand(9, 250)."@quedlaballe.com"; echo "

Update a the email: set to '$sNewEmail'

"; $oMyFilter = new DBObjectSearch("cmdbContact"); $oMyFilter->AddCondition("name", "o", "Contains"); echo "Candidates before:
"; $this->search_and_show_list($oMyFilter); MetaModel::BulkUpdateTracked($oMyChange, $oMyFilter, ["email" => $sNewEmail]); echo "Candidates after:
"; $this->search_and_show_list($oMyFilter); } public function test_error() { trigger_error("Stop requested", E_USER_ERROR); } public function test_changetracking() { echo '

Create a change

'; /** @var CMDBChange $oMyChange * */ $oMyChange = MetaModel::NewObject('CMDBChange'); $oMyChange->Set('date', time()); $oMyChange->Set('userinfo', 'Made by robot #'.rand(1, 100)); self::DumpVariable($oMyChange); echo '

Create a new object (team)

'; $oNewTeam = MetaModel::NewObject('cmdbTeam'); $oNewTeam->Set('name', 'ekip2choc #'.rand(1000, 2000)); $oNewTeam->Set('email', 'machin'.rand(1, 100).'@tnut.com'); $oNewTeam->Set('email', null); $oNewTeam->Set('owner', 'ITOP'); $oNewTeam->Set('headcount', '0'.rand(38000, 38999)); // should be reset to an int value $oNewTeam::SetCurrentChange($oMyChange); $iId = $oNewTeam->DBInsert(); echo "Created new team: $iId
"; echo "

Delete team #$iId

"; $oTeam = MetaModel::GetObject('cmdbTeam', $iId); $oTeam::SetCurrentChange($oMyChange); $oTeam->DBDelete(); echo "Deleted team: $iId
"; self::DumpVariable($oTeam); } public function test_zlist() { echo "

Test ZLists

"; $aZLists = MetaModel::EnumZLists(); foreach ($aZLists as $sListCode) { $aListInfos = MetaModel::GetZListInfo($sListCode); echo "

List '".$sListCode."' (".$aListInfos["description"].") of type '".$aListInfos["type"]."'

\n"; foreach (MetaModel::GetSubclasses("cmdbObjectHomeMade") as $sKlass) { $aItems = MetaModel::FlattenZlist(MetaModel::GetZListItems($sKlass, $sListCode)); if (count($aItems) == 0) { continue; } echo "$sKlass - $sListCode : {".implode(", ", $aItems)."}
\n"; } } echo "

IsAttributeInZList()...

"; echo "Liens_entre_contacts_et_workshop::ws_info in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "ws_info") ? "yes" : "no")."
\n"; echo "Liens_entre_contacts_et_workshop::toworkshop in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "toworkshop") ? "yes" : "no")."
\n"; } public function test_pkey() { echo "

Test search on pkey

"; $sExpr1 = "SELECT cmdbContact WHERE id IN (40, 42)"; $sExpr2 = "SELECT cmdbContact WHERE IN NOT IN (40, 42)"; $this->search_and_show_list_from_oql($sExpr1); $this->search_and_show_list_from_oql($sExpr2); echo "Et maintenant, on fusionne....
\n"; $oSet1 = new CMDBObjectSet(DBObjectSearch::FromOQL($sExpr1)); $oSet2 = new CMDBObjectSet(DBObjectSearch::FromOQL($sExpr2)); $oIntersect = $oSet1->CreateIntersect($oSet2); $oDelta = $oSet1->CreateDelta($oSet2); $oMerge = clone $oSet1; $oAppend->Append($oSet2); $oAppend->Append($oSet2); echo "Set1 - Found ".$oSet1->Count()." items.
\n"; echo "Set2 - Found ".$oSet2->Count()." items.
\n"; echo "Intersect - Found ".$oIntersect->Count()." items.
\n"; echo "Delta - Found ".$oDelta->Count()." items.
\n"; echo "Append - Found ".$oAppend->Count()." items.
\n"; //$this->show_list($oObjSet); } public function test_relations() { echo "

Test relations

"; //self::DumpVariable(MetaModel::EnumRelationQueries("cmdbObjectHomeMade", "Potes")); self::DumpVariable(MetaModel::EnumRelationQueries("cmdbContact", "Potes")); $iMaxDepth = 9; echo "Max depth = $iMaxDepth
\n"; $oObj = MetaModel::GetObject("cmdbContact", 18); $aRels = $oObj->GetRelatedObjectsDown("Potes", $iMaxDepth); echo $oObj->Get('name')." has some 'Potes'...
\n"; foreach ($aRels as $sClass => $aObjs) { echo "$sClass, count = ".count($aObjs)." => ".implode(', ', array_keys($aObjs))."
\n"; $oObjectSet = CMDBObjectSet::FromArray($sClass, $aObjs); $this->show_list($oObjectSet); } echo "

Test relations - same results, by the mean of a OQL

"; $this->search_and_show_list_from_oql("cmdbContact: RELATED (Potes, $iMaxDepth) TO (cmdbContact: pkey = 18)"); } public function test_linkedset() { echo "

Linked set attributes

\n"; $oObj = MetaModel::GetObject("cmdbContact", 18); echo "
Current workshops
\n"; $oSetWorkshopsCurr = $oObj->Get("myworkshops"); $this->show_list($oSetWorkshopsCurr); echo "
Setting workshops
\n"; $oNewLink = new cmdbLiens(); $oNewLink->Set('toworkshop', 2); $oNewLink->Set('function', 'mafonctioooon'); $oNewLink->Set('a1', 'tralala1'); $oNewLink->Set('a2', 'F7M'); $oSetWorkshops = CMDBObjectSet::FromArray("cmdbLiens", [$oNewLink]); $oObj->Set("myworkshops", $oSetWorkshops); $this->show_list($oSetWorkshops); echo "
New workshops
\n"; $oSetWorkshopsCurr = $oObj->Get("myworkshops"); $this->show_list($oSetWorkshopsCurr); CMDBObject::SetTrackInfo('test_linkedset / Made by robot #'.rand(1, 100)); $oObj->DBUpdate(); $oObj = MetaModel::GetObject("cmdbContact", 18); echo "
After the write
\n"; $oSetWorkshopsCurr = $oObj->Get("myworkshops"); $this->show_list($oSetWorkshopsCurr); } public function test_object_lifecycle() { echo "

Test object lifecycle

"; self::DumpVariable(MetaModel::GetStateAttributeCode("cmdbContact")); self::DumpVariable(MetaModel::EnumStates("cmdbContact")); self::DumpVariable(MetaModel::EnumStimuli("cmdbContact")); foreach (MetaModel::EnumStates("cmdbContact") as $sStateCode => $aStateDef) { echo "

Transition from $sStateCode

\n"; self::DumpVariable(MetaModel::EnumTransitions("cmdbContact", $sStateCode)); } $oObj = MetaModel::GetObject("cmdbContact", 18); echo "Current state: ".$oObj->GetState()."... let's go to school..."; self::DumpVariable($oObj->EnumTransitions()); $oObj->ApplyStimulus("toschool"); echo "New state: ".$oObj->GetState()."... let's get older..."; self::DumpVariable($oObj->EnumTransitions()); $oObj->ApplyStimulus("raise"); echo "New state: ".$oObj->GetState()."... let's try to go further... (should give an error)"; self::DumpVariable($oObj->EnumTransitions()); $oObj->ApplyStimulus("raise"); // should give an error } protected function DoExecute() { // $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); // $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); //$this->test_linksinfo(); //$this->test_list_attributes(); //$this->test_search(); //$this->test_reload(); //$this->test_newobject(); $this->test_setattribute(); //$this->test_updatecolumn(); //$this->test_error(); //$this->test_changetracking(); $this->test_zlist(); $this->test_OQL(); //$this->test_pkey(); $this->test_relations(); $this->test_linkedset(); $this->test_object_lifecycle(); } } /////////////////////////////////////////////////////////////////////////// // Test queries /////////////////////////////////////////////////////////////////////////// class TestItopEfficiency extends TestBizModel { public static function GetName() { return 'Itop - benchmark'; } public static function GetDescription() { return 'Measure time to perform the queries'; } protected function DoBenchmark($sOqlQuery) { echo "

Testing query: $sOqlQuery

"; $fStart = MyHelpers::getmicrotime(); for ($i = 0 ; $i < COUNT_BENCHMARK ; $i++) { $oFilter = DBObjectSearch::FromOQL($sOqlQuery); } $fDuration = MyHelpers::getmicrotime() - $fStart; $fParsingDuration = $fDuration / COUNT_BENCHMARK; $fStart = MyHelpers::getmicrotime(); for ($i = 0 ; $i < COUNT_BENCHMARK ; $i++) { $sSQL = $oFilter->MakeSelectQuery(); } $fDuration = MyHelpers::getmicrotime() - $fStart; $fBuildDuration = $fDuration / COUNT_BENCHMARK; $fStart = MyHelpers::getmicrotime(); for ($i = 0 ; $i < COUNT_BENCHMARK ; $i++) { $res = CMDBSource::Query($sSQL); } $fDuration = MyHelpers::getmicrotime() - $fStart; $fQueryDuration = $fDuration / COUNT_BENCHMARK; // The fetch could not be repeated with the same results // But we've seen so far that is was very very quick to exec // So it makes sense to benchmark it a single time $fStart = MyHelpers::getmicrotime(); $aRow = CMDBSource::FetchArray($res); $fDuration = MyHelpers::getmicrotime() - $fStart; $fFetchDuration = $fDuration; $fStart = MyHelpers::getmicrotime(); for ($i = 0 ; $i < COUNT_BENCHMARK ; $i++) { $sOql = $oFilter->ToOQL(); } $fDuration = MyHelpers::getmicrotime() - $fStart; $fToOqlDuration = $fDuration / COUNT_BENCHMARK; echo "\n"; // Everything but the ToOQL (wich is interesting, anyhow) $fTotal = $fParsingDuration + $fBuildDuration + $fQueryDuration + $fFetchDuration; if ($fTotal == 0) { $aRet = [ 'rows' => CMDBSource::NbRows($res), 'duration (s)' => '0 (negligeable)', 'parsing (%)' => '?', 'build SQL (%)' => '?', 'query exec (%)' => '?', 'fetch (%)' => '?', 'to OQL (%)' => '?', 'parsing+build (%)' => '?', ]; } else { $aRet = [ 'rows' => CMDBSource::NbRows($res), 'duration (s)' => round($fTotal, 4), 'parsing (%)' => round(100 * $fParsingDuration / $fTotal, 1), 'build SQL (%)' => round(100 * $fBuildDuration / $fTotal, 1), 'query exec (%)' => round(100 * $fQueryDuration / $fTotal, 1), 'fetch (%)' => round(100 * $fFetchDuration / $fTotal, 1), 'to OQL (%)' => round(100 * $fToOqlDuration / $fTotal, 1), 'parsing+build (%)' => round(100 * ($fParsingDuration + $fBuildDuration) / $fTotal, 1), ]; } return $aRet; } protected function DoExecute() { define('COUNT_BENCHMARK', 3); echo "

The test will be repeated ".COUNT_BENCHMARK." times

"; $aQueries = [ 'SELECT CMDBChangeOpSetAttribute', 'SELECT CMDBChangeOpSetAttribute WHERE id=10', 'SELECT CMDBChangeOpSetAttribute WHERE id=123456789', 'SELECT CMDBChangeOpSetAttribute WHERE CMDBChangeOpSetAttribute.id=10', 'SELECT Ticket', 'SELECT Ticket WHERE id=1', 'SELECT Person', 'SELECT Person WHERE id=1', 'SELECT Server', 'SELECT Server WHERE id=1', 'SELECT UserRequest JOIN Person ON UserRequest.agent_id = Person.id WHERE Person.id = 5', ]; $aStats = []; foreach ($aQueries as $sOQL) { $aStats[$sOQL] = $this->DoBenchmark($sOQL); } $aData = []; foreach ($aStats as $sOQL => $aResults) { $aValues = []; $aValues['OQL'] = htmlentities($sOQL, ENT_QUOTES, 'UTF-8'); foreach ($aResults as $sDesc => $sInfo) { $aValues[$sDesc] = htmlentities($sInfo, ENT_QUOTES, 'UTF-8'); } $aData[] = $aValues; } echo MyHelpers::make_table_from_assoc_array($aData); } } /////////////////////////////////////////////////////////////////////////// // Benchmark queries /////////////////////////////////////////////////////////////////////////// class TestQueries extends TestBizModel { public static function GetName() { return 'Itop - queries'; } public static function GetDescription() { return 'Try as many queries as possible'; } protected function DoBenchmark($sOqlQuery) { echo "
Testing query: $sOqlQuery
"; $fStart = MyHelpers::getmicrotime(); $oFilter = DBObjectSearch::FromOQL($sOqlQuery); $fParsingDuration = MyHelpers::getmicrotime() - $fStart; $fStart = MyHelpers::getmicrotime(); $sSQL = $oFilter->MakeSelectQuery(); $fBuildDuration = MyHelpers::getmicrotime() - $fStart; $iJoins = preg_match_all('/JOIN/', $sSQL) + 1; $fStart = MyHelpers::getmicrotime(); $res = CMDBSource::Query($sSQL); $fQueryDuration = MyHelpers::getmicrotime() - $fStart; // The fetch could not be repeated with the same results // But we've seen so far that is was very very quick to exec // So it makes sense to benchmark it a single time $fStart = MyHelpers::getmicrotime(); $aRow = CMDBSource::FetchArray($res); $fDuration = MyHelpers::getmicrotime() - $fStart; $fFetchDuration = $fDuration; $fStart = MyHelpers::getmicrotime(); $sOql = $oFilter->ToOQL(); $fToOqlDuration = MyHelpers::getmicrotime() - $fStart; // Everything but the ToOQL (which is interesting, anyhow) $fTotal = $fParsingDuration + $fBuildDuration + $fQueryDuration + $fFetchDuration; if ($fTotal == 0) { $aRet = [ 'rows' => CMDBSource::NbRows($res), 'duration (s)' => '0 (negligeable)', 'parsing (%)' => '?', 'build SQL (%)' => '?', 'query exec (%)' => '?', 'fetch (%)' => '?', 'to OQL (%)' => '?', 'parsing+build (%)' => '?', 'joins' => $iJoins, ]; } else { $aRet = [ 'rows' => CMDBSource::NbRows($res), 'duration (s)' => round($fTotal, 4), 'parsing (%)' => round(100 * $fParsingDuration / $fTotal, 1), 'build SQL (%)' => round(100 * $fBuildDuration / $fTotal, 1), 'query exec (%)' => round(100 * $fQueryDuration / $fTotal, 1), 'fetch (%)' => round(100 * $fFetchDuration / $fTotal, 1), 'to OQL (%)' => round(100 * $fToOqlDuration / $fTotal, 1), 'parsing+build (%)' => round(100 * ($fParsingDuration + $fBuildDuration) / $fTotal, 1), 'joins' => $iJoins, ]; } return $aRet; } protected function DoExecute() { $aQueries = []; foreach (MetaModel::GetClasses() as $sClass) { $aQueries[] = 'SELECT '.$sClass; $aQueries[] = 'SELECT '.$sClass.' WHERE id = 1'; } $aStats = []; foreach ($aQueries as $sOQL) { $aStats[$sOQL] = $this->DoBenchmark($sOQL); } $aData = []; foreach ($aStats as $sOQL => $aResults) { $aValues = []; $aValues['OQL'] = htmlentities($sOQL, ENT_QUOTES, 'UTF-8'); foreach ($aResults as $sDesc => $sInfo) { $aValues[$sDesc] = htmlentities($sInfo, ENT_QUOTES, 'UTF-8'); } $aData[] = $aValues; } echo MyHelpers::make_table_from_assoc_array($aData); } } /////////////////////////////////////////////////////////////////////////// // Check programmaticaly built queries /////////////////////////////////////////////////////////////////////////// class TestQueriesByAPI extends TestBizModel { public static function GetName() { return 'Itop - queries build programmaticaly'; } public static function GetDescription() { return 'Validate the DBObjectSearch API, through a set of complex (though realistic cases)'; } protected function DoExecute() { // Note: relying on eval() - after upgrading to PHP 5.3 we can move to closure (aka anonymous functions) $aQueries = [ 'Basic (validate the test)' => [ 'search' => ' $oSearch = DBObjectSearch::FromOQL("SELECT P FROM Organization AS O JOIN Person AS P ON P.org_id = O.id WHERE org_id = 2"); ', 'oql' => 'SELECT P FROM Organization AS O JOIN Person AS P ON P.org_id = O.id WHERE P.org_id = 2', ], 'Double constraint' => [ 'search' => ' $oSearch = DBObjectSearch::FromOQL("SELECT Contact AS c"); $sClass = $oSearch->GetClass(); $sFilterCode = "org_id"; $oAttDef = MetaModel::GetAttributeDef($sClass, $sFilterCode); if ($oAttDef->IsExternalKey()) { $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass()); if ($sHierarchicalKeyCode !== false) { $oFilter = new DBObjectSearch($oAttDef->GetTargetClass(), "ORGA"); $oFilter->AddCondition("id", 2); $oHKFilter = new DBObjectSearch($oAttDef->GetTargetClass(), "ORGA"); $oHKFilter->AddCondition_PointingTo(clone $oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); $oSearch->AddCondition_PointingTo(clone $oHKFilter, $sFilterCode); $oFilter = new DBObjectSearch($oAttDef->GetTargetClass(), "ORGA"); $oFilter->AddCondition("id", 2); $oHKFilter = new DBObjectSearch($oAttDef->GetTargetClass(), "ORGA"); $oHKFilter->AddCondition_PointingTo(clone $oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); $oSearch->AddCondition_PointingTo(clone $oHKFilter, $sFilterCode); } } ', 'oql' => 'SELECT Contact AS C JOIN Organization ???', ], 'Simplified issue' => [ 'search' => ' $oSearch = DBObjectSearch::FromOQL("SELECT P FROM Organization AS O JOIN Person AS P ON P.org_id = O.id WHERE O.id = 2"); $oOrgSearch = new DBObjectSearch("Organization", "O2"); $oOrgSearch->AddCondition("id", 2); $oSearch->AddCondition_PointingTo($oOrgSearch, "org_id"); ', 'oql' => 'SELECT P FROM Organization AS O JOIN Person AS P ON P.org_id = O.id JOIN Organization AS O2 ON P.org_id = O2.id WHERE O.id = 2 AND O2.id = 2', ], ]; foreach ($aQueries as $sQueryDesc => $aQuerySpec) { echo "

Query $sQueryDesc

\n"; echo "

Using code: ".highlight_string("', true)."

\n"; echo "

Expected OQL: ".$aQuerySpec['oql']."

\n"; if (isset($oSearch)) { unset($oSearch); } eval($aQuerySpec['search']); $sResOQL = $oSearch->ToOQL(); echo "

Resulting OQL: ".$sResOQL."

\n"; echo "
";
			print_r($oSearch);
			echo "
"; $sSQL = $oSearch->MakeSelectQuery(); $res = CMDBSource::Query($sSQL); foreach (CMDBSource::ExplainQuery($sSQL) as $aRow) { } } // throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); } } /////////////////////////////////////////////////////////////////////////// // Test data load /////////////////////////////////////////////////////////////////////////// class TestImportREST extends TestWebServices { public static function GetName() { return 'CSV import (REST)'; } public static function GetDescription() { return 'Test various options and fonctionality of import.php'; } protected function DoExecSingleLoad($aLoadSpec, $iTestId = null) { $sCsvData = $aLoadSpec['csvdata']; echo "
\n"; if (is_null($iTestId)) { echo "

{$aLoadSpec['desc']}

\n"; } else { echo "

$iTestId - {$aLoadSpec['desc']}

\n"; } $aPostData = ['csvdata' => $sCsvData]; $aGetParams = []; $aGetParamReport = []; foreach ($aLoadSpec['args'] as $sArg => $sValue) { $aGetParams[] = $sArg.'='.urlencode($sValue); $aGetParamReport[] = $sArg.'='.$sValue; } $sGetParams = implode('&', $aGetParams); $sLogin = isset($aLoadSpec['login']) ? $aLoadSpec['login'] : 'admin'; $sPassword = isset($aLoadSpec['password']) ? $aLoadSpec['password'] : 'admin'; $sRes = self::DoPostRequestAuth('../webservices/import.php?'.$sGetParams, $aPostData, $sLogin, $sPassword); $sArguments = implode('
', $aGetParamReport); if (strlen($sCsvData) > 5000) { $sCsvDataViewable = 'INPUT TOO LONG TO BE DISPLAYED ('.strlen($sCsvData).")\n".substr($sCsvData, 0, 500)."\n... TO BE CONTINUED"; } else { $sCsvDataViewable = $sCsvData; } echo "
\n"; echo "
\n"; echo " $sArguments\n"; echo "
\n"; echo "
\n"; echo "
$sCsvDataViewable
\n"; echo "
\n"; echo "
\n"; echo "
$sRes
\n"; echo "
\n"; } protected function DoExecute() { $aLoads = [ [ 'desc' => 'Missing class', 'login' => 'admin', 'password' => 'admin', 'args' => [ ], 'csvdata' => "xxx", ], [ 'desc' => 'Wrong class', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'toto', ], 'csvdata' => "xxx", ], [ 'desc' => 'Wrong output type', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'NetworkDevice', 'output' => 'onthefly', ], 'csvdata' => "xxx", ], [ 'desc' => 'Weird format, working anyhow...', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Server', 'output' => 'details', 'separator' => '*', 'qualifier' => '@', 'reconciliationkeys' => 'org_id,name', ], 'csvdata' => 'name*org_id server01*2 @server02@@combodo@* 2 server45*99', ], [ 'desc' => 'Load an organization', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Organization', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "name;code\nWorldCompany;WCY", ], [ 'desc' => 'Load a location', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "name;org_id;address\nParis;1;Centre de la Franca", ], [ 'desc' => 'Load a person', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Person', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "email;name;first_name;org_id;phone\njohn.foo@starac.com;Foo;John;1;+33(1)23456789", ], [ 'desc' => 'Load a person - wrong email format', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Person', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "email;name;first_name;org_id\nemailPASbon;Foo;John;1", ], [ 'desc' => 'Load a team', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Team', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris", ], [ 'desc' => 'Load server', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Server', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "name;status;owner_name;location_name;location_id->org_name;os_family;os_version;management_ip;cpu;ram;brand;model;serial_number\nlocalhost.;production;Demo;Grenoble;Demo;Ubuntu 9.10;2.6.31-19-generic-#56-Ubuntu SMP Thu Jan 28 01:26:53 UTC 2010;16.16.230.232;Intel(R) Core(TM)2 Duo CPU T7100 @ 1.80GHz;2005;Hewlett-Packard;HP Compaq 6510b (GM108UC#ABF);CNU7370BNP", ], [ 'desc' => 'Load server (column header localized in english)', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Server', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "Name;Status;Owner Organization;Location;location_id->org_name;OS Family;OS Version;Management IP;CPU;RAM;Brand;Model;Serial Number\nlocalhost.;production;Demo;Grenoble;Demo;Ubuntu 9.10;2.6.31-19-generic-#56-Ubuntu SMP Thu Jan 28 01:26:53 UTC 2010;16.16.230.232;Intel(R) Core(TM)2 Duo CPU T7100 @ 1.80GHz;2005;Hewlett-Packard;HP Compaq 6510b (GM108UC#ABF);CNU7370BNP", ], [ 'desc' => 'Load server (directly from Export results)', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Server', 'output' => 'details', 'reconciliationkeys' => '', ], 'csvdata' => 'id,Name,Status,Owner organization,Owner organization->Name,Business criticity,Brand,Model,Serial Number,Asset Reference,Description,Location,Location->Name,Location details,Management IP,Default Gateway,CPU,RAM,Hard Disk,OS Family,OS Version 1,"dbserver1.demo.com","production",2,"Demo","medium","HP","DL380","","","ouille [[Server:webserver.demo.com]]",1,"Grenoble","","10.1.1.10","255.255.255.0","2","16Gb","120Gb","Linux","Debian (Lenny)"', ], [ 'desc' => 'Load server - wrong value for status', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Server', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "name;status;owner_name;location_name;location_id->org_name;os_family;os_version;management_ip;cpu;ram;brand;model;serial_number\nlocalhost.;Production;Demo;Grenoble;Demo;Ubuntu 9.10;2.6.31-19-generic-#56-Ubuntu SMP Thu Jan 28 01:26:53 UTC 2010;16.16.230.232;Intel(R) Core(TM)2 Duo CPU T7100 @ 1.80GHz;2005;Hewlett-Packard;HP Compaq 6510b (GM108UC#ABF);CNU7370BNP", ], [ 'desc' => 'Load NW if', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'NetworkInterface', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => '', ], 'csvdata' => "name;status;org_id;device_name;physical_type;ip_address;ip_mask;mac_address;speed\neth0;implementation;2;localhost.;ethernet;16.16.230.232;255.255.240.0;00:1a:4b:68:e3:97;\nlo;implementation;2;localhost.;ethernet;127.0.0.1;255.0.0.0;;", ], // Data Bruno [ 'desc' => 'Load NW devices from real life', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'NetworkDevice', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => 'org_id,Name', ], 'csvdata' => 'name;management_ip;importance;Owner organization->Name;type truc-machin-bidule;172.15.255.150;high;My Company/Department;switch 10.15.255.222;10.15.255.222;high;My Company/Department;switch', ], [ 'desc' => 'Load NW ifs', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'NetworkInterface', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => 'device_id->name,name', ], 'csvdata' => 'device_id->name;org_id->name;name;ip_address;ip_mask;speed;link_type;mac_address;physical_type truc-machin-bidule;My Company/Department;"GigabitEthernet44";;;0;downlink;00 12 F2 CB C4 EB ;ethernet truc-machin-bidule;My Company/Department;"GigabitEthernet38";;;0;downlink;00 12 F2 CB C4 E5 ;ethernet un-autre;My Company/Department;"GigabitEthernet2/3";;;1000000000;uplink;00 12 F2 20 0F 1A ;ethernet', ], [ 'desc' => 'The simplest data load', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', ], 'csvdata' => "name\nParis", ], [ 'desc' => 'The simplest data load + org', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', ], 'csvdata' => "name;org_id\nParis;2", ], [ 'desc' => 'The simplest data load + org (name)', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', ], 'csvdata' => "name;org_name\nParis;Demo", ], [ 'desc' => 'The simplest data load + org (code)', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', ], 'csvdata' => "name;org_id->code\nParis;DEMO", ], [ 'desc' => 'Ouput: summary', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'summary', 'separator' => ';', ], 'csvdata' => "name;org_id->code\nParis;DEMO", ], [ 'desc' => 'Ouput: retcode', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'retcode', 'separator' => ';', ], 'csvdata' => "name;org_id->code\nParis;DEMO", ], [ 'desc' => 'Error in reconciliation list', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', 'reconciliationkeys' => 'org_id', ], 'csvdata' => "org_name;name\nDemo;Paris", ], [ 'desc' => 'Error in attribute list that does not allow to compute reconciliation scheme', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', ], 'csvdata' => "org_name;country\nDemo;France", ], [ 'desc' => 'Error in attribute list - case A', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', ], 'csvdata' => "name;org\nParis;2", ], [ 'desc' => 'Error in attribute list - case B1 (key->attcode)', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', ], 'csvdata' => "name;org->code\nParis;DEMO", ], [ 'desc' => 'Error in attribute list - case B2 (key->attcode)', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', ], 'csvdata' => "name;org_id->duns\nParis;DEMO", ], [ 'desc' => 'Always changing... special comment in change tracking', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', 'comment' => 'automated testing', ], 'csvdata' => "org_name;name;address\nDemo;Le pantheon;Addresse bidon:".((string)microtime(true)), ], [ 'desc' => 'Always changing... but "simulate"', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'Location', 'output' => 'details', 'separator' => ';', 'simulate' => '1', 'comment' => 'SHOULD NEVER APPEAR IN THE HISTORY', ], 'csvdata' => "org_name;name;address\nDemo;Le pantheon;restore address?", ], [ 'desc' => 'Load a user account', 'login' => 'admin', 'password' => 'admin', 'args' => [ 'class' => 'UserLocal', 'output' => 'details', 'separator' => ',', 'simulate' => '0', 'comment' => 'automated testing', ], 'csvdata' => "login,password,profile_list\nby_import_csv,fakepwd,profileid->name:Configuration Manager|profileid:10;reason:direct id", ], ]; $sSubTests = utils::ReadParam('subtests', null, true, 'raw_data'); if (is_null($sSubTests)) { foreach ($aLoads as $iTestId => $aLoadSpec) { $this->DoExecSingleLoad($aLoadSpec, $iTestId); } } else { $aSubTests = explode(',', $sSubTests); foreach ($aSubTests as $iTestId) { $this->DoExecSingleLoad($aLoads[$iTestId], $iTestId); } } } } /////////////////////////////////////////////////////////////////////////// // Test massive data load /////////////////////////////////////////////////////////////////////////// define('IMPORT_COUNT', 4000); class TestImportRESTMassive extends TestImportREST { public static function GetName() { return 'CSV import (REST) - HUGE data set ('.IMPORT_COUNT.' PCs)'; } public static function GetDescription() { return 'Stress import.php'; } protected function DoExecute() { $aLoadSpec = [ 'desc' => 'Loading PCs: '.IMPORT_COUNT, 'args' => [ 'class' => 'PC', 'output' => 'summary', ], 'csvdata' => "name;org_id;brand\n", ]; for ($i = 0 ; $i <= IMPORT_COUNT ; $i++) { $aLoadSpec['csvdata'] .= "pc.import.$i;2;Combodo\n"; } $this->DoExecSingleLoad($aLoadSpec); } } /////////////////////////////////////////////////////////////////////////// // Test SOAP services /////////////////////////////////////////////////////////////////////////// $aCreateTicketSpecs = [ [ 'service_category' => 'BasicServices', 'verb' => 'GetVersion', // 'expected result' => '1.0.1', 'expected result' => '$ITOP_VERSION$ [dev]', 'explain result' => 'no comment!', 'args' => [], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => true, 'explain result' => 'link attribute unknown + a CI not found', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'desc of ticket', /* sDescription */ 'initial situation blah blah blah', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Telecom and connectivity')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Network Troubleshooting')]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ new SOAPLinkCreationSpec( 'InfrastructureCI', [new SOAPSearchCondition('name', 'dbserver1.demo.com')], [new SOAPAttributeValue('impacting', 'very critical')] ), new SOAPLinkCreationSpec( 'NetworkDevice', [new SOAPSearchCondition('name', 'switch01')], [new SOAPAttributeValue('impact', 'who cares')] ), new SOAPLinkCreationSpec( 'Server', [new SOAPSearchCondition('name', 'thisone')], [new SOAPAttributeValue('impact', 'our lives')] ), ], /* aImpact */ '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => true, 'explain result' => 'caller not specified', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'PC burning', /* sDescription */ 'The power supply suddenly started to warm up', /* sInitialSituation */ new SOAPExternalKeySearch(), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ new SOAPLinkCreationSpec( 'InfrastructureCI', [new SOAPSearchCondition('name', 'dbserver1.demo.com')], [] ), /* aImpact */ ], '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => false, 'explain result' => 'wrong class on CI to attach', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'PC burning', /* sDescription */ 'The power supply suddenly started to warm up', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ new SOAPLinkCreationSpec( 'logInfra', [new SOAPSearchCondition('dummyfiltercode', 2)], [new SOAPAttributeValue('impact', 'very critical')] ), ], /* aImpact */ '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => false, 'explain result' => 'wrong search condition on CI to attach', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'PC burning', /* sDescription */ 'The power supply suddenly started to warm up', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ new SOAPLinkCreationSpec( 'InfrastructureCI', [new SOAPSearchCondition('dummyfiltercode', 2)], [new SOAPAttributeValue('impact', 'very critical')] ), ], /* aImpact */ '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => true, 'explain result' => 'no CI to attach (empty array)', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'Houston not reachable', /* sDescription */ 'Tried to join the shuttle', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ ], /* aImpact */ '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => true, 'explain result' => 'no CI to attach (null)', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'Houston not reachable', /* sDescription */ 'Tried to join the shuttle', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ null, /* aImpact */ '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => true, 'explain result' => 'caller unknown', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'Houston not reachable', /* sDescription */ 'Tried to join the shuttle', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1000)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ ], /* aImpact */ '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => false, 'explain result' => 'wrong values for impact and urgency', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'Houston not reachable', /* sDescription */ 'Tried to join the shuttle', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ ], /* aImpact */ '6', /* sImpact */ '7', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => false, 'explain result' => 'wrong password', 'args' => [ 'admin', /* sLogin */ 'xxxxx', /* sPassword */ 'Houston not reachable', /* sDescription */ 'Tried to join the shuttle', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ ], /* aImpact */ '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'CreateIncidentTicket', 'expected result' => false, 'explain result' => 'wrong login', 'args' => [ 'xxxxx', /* sLogin */ 'yyyyy', /* sPassword */ 'Houston not reachable', /* sDescription */ 'Tried to join the shuttle', /* sInitialSituation */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aCallerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Demo')]), /* aCustomerDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Computers and peripherals')]), /* aServiceDesc */ new SOAPExternalKeySearch([new SOAPSearchCondition('id', 1)]), /* aServiceSubcategoryDesc */ 'sub product of the service', /* sProduct */ new SOAPExternalKeySearch([new SOAPSearchCondition('name', 'Hardware support')]), /* aWorkgroupDesc */ [ ], /* aImpact */ '1', /* sImpact */ '1', /* sUrgency */ ], ], [ 'service_category' => '', 'verb' => 'SearchObjects', 'expected result' => true, 'explain result' => '', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'SELECT Incident WHERE id > 20', /* sOQL */ ], ], [ 'service_category' => '', 'verb' => 'SearchObjects', 'expected result' => false, 'explain result' => 'wrong OQL', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'SELECT ThisClassDoesNotExist', /* sOQL */ ], ], ]; $aManageCloudUsersSpecs = [ [ 'service_category' => '', 'verb' => 'SearchObjects', 'expected result' => false, 'explain result' => 'wrong OQL', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'SELECT ThisClassDoesNotExist', /* sOQL */ ], ], [ 'service_category' => '', 'verb' => 'SearchObjects', 'expected result' => true, 'explain result' => 'ok', 'args' => [ 'admin', /* sLogin */ 'admin', /* sPassword */ 'SELECT Organization', /* sOQL */ ], ], [ 'service_category' => 'CloudUsersManagementService', 'verb' => 'CreateAccount', 'expected result' => true, 'explain result' => 'ok', 'args' => [ 'admin', /* sAdminLogin */ 'admin', /* sAdminPassword */ 'http://myserver.mydomain.fr:8080', /* sCloudMgrUrl */ 'andros@combodo.com', /* sLogin */ 'André', /* sFirstName */ 'Dupont', /* sLastName */ 1, /* iOrgId */ 'FR FR', /* sLanguage */ [ [ new SOAPKeyValue('profile_id', '2'), new SOAPKeyValue('reason', 'whynot'), ], [ new SOAPKeyValue('profile_id', '3'), new SOAPKeyValue('reason', 'because'), ], ], /* aProfiles (array of key/value pairs) */ [ ], /* aAllowedOrgs (array of key/value pairs) */ 'comment on the creation operation', /* sComment */ ], ], [ 'service_category' => 'CloudUsersManagementService', 'verb' => 'ModifyAccount', 'expected result' => true, 'explain result' => 'ok', 'args' => [ 'admin', /* sAdminLogin */ 'admin', /* sAdminPassword */ 'andros@combodo.com', /* sLogin */ 'nono', /* sFirstName */ 'robot', /* sLastName */ 2, /* iOrgId */ 'EN US', /* sLanguage */ [ [ new SOAPKeyValue('profile_id', '3'), new SOAPKeyValue('reason', 'because'), ], ], /* aProfiles (array of key/value pairs) */ [ ], /* aAllowedOrgs (array of key/value pairs) */ 'comment on the modify operation', /* sComment */ ], ], [ 'service_category' => 'CloudUsersManagementService', 'verb' => 'DeleteAccount', 'expected result' => true, 'explain result' => '', 'args' => [ 'admin', /* sAdminLogin */ 'admin', /* sAdminPassword */ 'andros@combodo.com', /* sLogin */ 'comment on the deletion operation', /* sComment */ ], ], [ 'service_category' => 'CloudUsersManagementService', 'verb' => 'DeleteAccount', 'expected result' => false, 'explain result' => 'wrong login', 'args' => [ 'admin', /* sAdminLogin */ 'admin', /* sAdminPassword */ 'taratatata@sdf.com', /* sLogin */ 'comment on the deletion operation', /* sComment */ ], ], ]; abstract class TestSoap extends TestSoapWebService { public static function GetName() { return 'Test SOAP'; } public static function GetDescription() { return 'Do basic stuff to test the SOAP capability'; } protected $m_aTestSpecs; protected function DoExecute() { echo "

Note: You may also want to try the sample SOAP client itopsoap.examples.php

\n"; $aSOAPMapping = SOAPMapping::GetMapping(); // this file is generated dynamically with location = here $sWsdlUri = 'http'.(utils::IsConnectionSecure() ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php'; ini_set("soap.wsdl_cache_enabled", "0"); foreach ($this->m_aTestSpecs as $iPos => $aWebService) { echo "

SOAP call #$iPos - {$aWebService['verb']}

\n"; echo "

Using WSDL: $sWsdlUriForService

\n"; echo "

{$aWebService['explain result']}

\n"; $sWsdlUriForService = $sWsdlUri.'?service_category='.$aWebService['service_category']; $this->m_SoapClient = new SoapClient( $sWsdlUriForService, [ 'classmap' => $aSOAPMapping, 'trace' => 1, ] ); if (false) { self::DumpVariable($this->m_SoapClient->__getTypes()); } try { $oRes = call_user_func_array([$this->m_SoapClient, $aWebService['verb']], $aWebService['args']); } catch (SoapFault $e) { print "
\n";
				print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest())."\n";
				print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n";
				print "
"; print "Response in HTML:

".$this->m_SoapClient->__getLastResponse()."

"; throw $e; } self::DumpVariable($oRes); print "
\n";
			print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest())."\n";
			print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n";
			print "
"; if ($oRes instanceof SOAPResult) { $res = $oRes->status; } elseif ($oRes instanceof SOAPSimpleResult) { $res = $oRes->status; } else { $res = $oRes; } if ($res != $aWebService['expected result']) { echo "Expecting:
\n"; var_dump($aWebService['expected result']); echo "Obtained:
\n"; var_dump($res); throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); } } } } abstract class TestSoapDirect extends TestBizModel { public static function GetName() { return 'Test web services locally'; } public static function GetDescription() { return 'Invoke the service directly (troubleshooting)'; } protected $m_aTestSpecs; protected function DoExecute() { foreach ($this->m_aTestSpecs as $iPos => $aWebService) { $sServiceClass = $aWebService['service_category']; if (empty($sServiceClass)) { $sServiceClass = 'BasicServices'; } $oWebServices = new $sServiceClass(); echo "

SOAP call #$iPos - {$aWebService['verb']}

\n"; echo "

{$aWebService['explain result']}

\n"; $oRes = call_user_func_array([$oWebServices, $aWebService['verb']], $aWebService['args']); self::DumpVariable($oRes); if ($oRes instanceof SOAPResult) { $res = $oRes->status; } elseif ($oRes instanceof SOAPSimpleResult) { $res = $oRes->status; } else { $res = $oRes; } if ($res != $aWebService['expected result']) { echo "Expecting:
\n"; var_dump($aWebService['expected result']); echo "Obtained:
\n"; var_dump($res); throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); } } return true; } } class TestSoap_Tickets extends TestSoap { public static function GetName() { return 'Test SOAP - create ticket'; } protected function DoExecute() { global $aCreateTicketSpecs; $this->m_aTestSpecs = $aCreateTicketSpecs; return parent::DoExecute(); } } class TestSoapDirect_Tickets extends TestSoapDirect { public static function GetName() { return 'Test SOAP without SOAP - create ticket'; } protected function DoExecute() { global $aCreateTicketSpecs; $this->m_aTestSpecs = $aCreateTicketSpecs; return parent::DoExecute(); } } class TestSoap_ManageCloudUsers extends TestSoap { public static function GetName() { return 'Test SOAP - manage Cloud Users'; } protected function DoExecute() { global $aManageCloudUsersSpecs; $this->m_aTestSpecs = $aManageCloudUsersSpecs; return parent::DoExecute(); } } class TestSoapDirect_ManageCloudUsers extends TestSoapDirect { public static function GetName() { return 'Test SOAP without SOAP - manage Cloud Users'; } protected function DoExecute() { global $aManageCloudUsersSpecs; $this->m_aTestSpecs = $aManageCloudUsersSpecs; return parent::DoExecute(); } } ////////////////////// End of SOAP TESTS class TestTriggerAndEmail extends TestBizModel { public static function GetName() { return 'Test trigger and email'; } public static function GetDescription() { return 'Create a trigger and an email, then activates the trigger'; } protected function CreateEmailSpec($oTrigger, $sStatus, $sTo, $sCC, $sTesterEmail) { $oAction = MetaModel::NewObject("ActionEmail"); $oAction->Set("status", $sStatus); $oAction->Set("name", "New server"); $oAction->Set("test_recipient", $sTesterEmail); $oAction->Set("from", $sTesterEmail); $oAction->Set("reply_to", $sTesterEmail); $oAction->Set("to", $sTo); $oAction->Set("cc", $sCC); $oAction->Set("bcc", ""); $oAction->Set("subject", "New server: '\$this->name()$'"); $oAction->Set("body", "

Dear customer,

We have created the server \$this->hyperlink()$ in the IT infrastructure database.

You will be further notified when it is in Production.

The IT infrastructure management team.

Here are some accentuated characters for french people: 'ééà'

"); $oAction->Set("importance", "low"); $iActionId = $this->ObjectToDB($oAction, true); $oLink = MetaModel::NewObject("lnkTriggerAction"); $oLink->Set("trigger_id", $oTrigger->GetKey()); $oLink->Set("action_id", $iActionId); $oLink->Set("order", "1"); $iLink = $this->ObjectToDB($oLink, true); } protected function DoExecute() { $oMyPerson = MetaModel::NewObject("Person"); $oMyPerson->Set("name", "testemail1"); $oMyPerson->Set("first_name", "theodore"); $oMyPerson->Set("org_id", "1"); $oMyPerson->Set("email", "romain.quetiez@combodo.com"); $iPersonId = $this->ObjectToDB($oMyPerson, true); $oMyPerson = MetaModel::NewObject("Person"); $oMyPerson->Set("name", "testemail2"); $oMyPerson->Set("first_name", "theodore"); $oMyPerson->Set("org_id", "1"); $oMyPerson->Set("email", "denis.flaven@combodo.com"); $iPersonId = $this->ObjectToDB($oMyPerson, true); $oMyPerson = MetaModel::NewObject("Person"); $oMyPerson->Set("name", "testemail3"); $oMyPerson->Set("first_name", "theodore"); $oMyPerson->Set("org_id", "1"); $oMyPerson->Set("email", "erwan.taloc@combodo.com"); $iPersonId = $this->ObjectToDB($oMyPerson, true); $oMyServer = MetaModel::NewObject("Server"); $oMyServer->Set("name", "wfr.terminator.com"); $oMyServer->Set("status", "production"); $oMyServer->Set("org_id", 2); $iServerId = $this->ObjectToDB($oMyServer, true); $oMyTrigger = MetaModel::NewObject("TriggerOnStateEnter"); $oMyTrigger->Set("description", "Testor"); $oMyTrigger->Set("target_class", "Server"); $oMyTrigger->Set("state", "Shipped"); $iTriggerId = $this->ObjectToDB($oMyTrigger, true); // Error in OQL field(s) // $this->CreateEmailSpec( $oMyTrigger, 'test', "SELECT Person WHERE naime = 'Dali'", "SELECT Server", 'romain.quetiez@combodo.com' ); // Error: no recipient // $this->CreateEmailSpec( $oMyTrigger, 'test', "", "", 'romain.quetiez@combodo.com' ); // Test // $this->CreateEmailSpec( $oMyTrigger, 'test', "SELECT Person WHERE name LIKE 'testemail%'", "SELECT Person", 'romain.quetiez@combodo.com' ); // Test failing because of a wrong test recipient address // $this->CreateEmailSpec( $oMyTrigger, 'test', "SELECT Person WHERE name LIKE 'testemail%'", "", 'toto@walibi.bg' ); // Normal behavior // $this->CreateEmailSpec( $oMyTrigger, 'enabled', "SELECT Person WHERE name LIKE 'testemail%'", "", 'romain.quetiez@combodo.com' ); // Does nothing, because it is disabled // $this->CreateEmailSpec( $oMyTrigger, 'disabled', "SELECT Person WHERE name = 'testemail%'", "", 'romain.quetiez@combodo.com' ); $oMyTrigger->DoActivate($oMyServer->ToArgs('this')); return true; } } class TestDBProperties extends TestBizModel { public static function GetName() { return 'Itop - DB Properties'; } public static function GetDescription() { return 'Write and read a dummy property'; } protected function DoExecute() { $sName = 'test'; DBProperty::SetProperty($sName, 'unix time:'.time(), 'means nothing', 'done with the automated test utility'); $sValue = DBProperty::GetProperty($sName, 'defaults to this because the table has not been created (1.0.1 install?)'); echo "

Write... then read property $sName, found: '$sValue'

\n"; } } class TestCreateObjects extends TestBizModel { public static function GetName() { return 'Itop - create objects'; } public static function GetDescription() { return 'Create weird objects (reproduce a bug?)'; } protected function DoExecute() { $oMyObj = MetaModel::NewObject("Server"); $oMyObj->Set("name", "test".rand(1, 1000)); $oMyObj->Set("org_id", 2); $oMyObj->Set("status", 'production'); $this->ObjectToDB($oMyObj, $bReload = true); echo "

Created: {$oMyObj->GetHyperLink()}

"; $sTicketRef = "I-abcdef"; echo "

Creating: $sTicketRef

"; $oMyObj = MetaModel::NewObject("Incident"); $oMyObj->Set("ref", $sTicketRef); $oMyObj->Set("title", "my title"); $oMyObj->Set("description", "my description"); $oMyObj->Set("ticket_log", "my ticket log"); $oMyObj->Set("start_date", "2010-03-08 17:37:00"); $oMyObj->Set("status", "resolved"); $oMyObj->Set("caller_id", 1); $oMyObj->Set("org_id", 1); $oMyObj->Set("urgency", 3); $oMyObj->Set("agent_id", 1); $oMyObj->Set("close_date", "0000-00-00 00:00:00"); $oMyObj->Set("last_update", "2010-04-08 16:47:29"); $oMyObj->Set("solution", "branche ton pc!"); // External key given as a string -> should be casted to an integer $oMyObj->Set("service_id", "1"); $oMyObj->Set("servicesubcategory_id", "1"); $oMyObj->Set("product", ""); $oMyObj->Set("impact", 2); $oMyObj->Set("priority", 3); $oMyObj->Set("related_problem_id", 0); $oMyObj->Set("related_change_id", 0); $oMyObj->Set("assignment_date", ""); $oMyObj->Set("resolution_date", ""); $oMyObj->Set("tto_escalation_deadline", ""); $oMyObj->Set("ttr_escalation_deadline", ""); $oMyObj->Set("closure_deadline", ""); $oMyObj->Set("resolution_code", "fixed"); $oMyObj->Set("user_satisfaction", ""); $oMyObj->Set("user_commment", ""); $oMyObj->Set("workgroup_id", 4); $this->ObjectToDB($oMyObj, $bReload = true); echo "

Created: {$oMyObj->GetHyperLink()}

"; } } class TestSetLinkset extends TestBizModel { public static function GetName() { return 'Itop - Link set from a string'; } public static function GetDescription() { return 'Create a user account, setting its profile by the mean of a string (prerequisite to CSV import of linksets)'; } protected function DoExecute() { $oUser = new UserLocal(); $oUser->Set('login', 'patator'.time()); $oUser->Set('password', 'patator'); //$oUser->Set('contactid', 0); //$oUser->Set('language', $sLanguage); $sLinkSetSpec = "profileid:10;reason:service manager|profileid->name:Problem Manager;'reason:problem manager;glandeur"; $oAttDef = MetaModel::GetAttributeDef('UserLocal', 'profile_list'); $oSet = $oAttDef->MakeValueFromString($sLinkSetSpec, $bLocalizedValue = false); $oUser->Set('profile_list', $oSet); // Create a change to record the history of the User object $this->ObjectToDB($oUser, $bReload = true); echo "

Created: {$oUser->GetHyperLink()}

"; } } abstract class TestLinkSet extends TestBizModel { protected function StandardizedDump($oSet, $sAttPrefixToIgnore) { if (!$oSet->m_bLoaded) { $oSet->Load(); } $oSet->Rewind(); $aRet = []; while ($oObject = $oSet->Fetch()) { $aValues = []; foreach (MetaModel::ListAttributeDefs(get_class($oObject)) as $sAttCode => $oAttDef) { //if (!$oAttDef->IsPartOfFingerprint()) continue; //if ($oAttDef->IsMagic()) continue; if ($sAttCode == 'friendlyname') { continue; } if (substr($sAttCode, -strlen('_archive_flag')) == '_archive_flag') { continue; } if (substr($sAttCode, -strlen('_obsolescence_flag')) == '_obsolescence_flag') { continue; } if (substr($sAttCode, 0, strlen($sAttPrefixToIgnore)) == $sAttPrefixToIgnore) { continue; } if ($oAttDef->IsScalar()) { $aValues[] = $oObject->Get($sAttCode); } } $aRet[] = implode(', ', $aValues); } sort($aRet); return $aRet; } } class TestLinkSetRecording_NN_WithDuplicates extends TestLinkSet { public static function GetName() { return 'Linkset N-N having duplicated allowed (Connectable CI to Network Device)'; } public static function GetDescription() { return 'Simulate CSV/data synchro type of recording. Check the values and the history. Lots of issues there: #1145, #1146 and #1147'; } protected function DoExecute() { CMDBSource::Query('START TRANSACTION'); //CMDBSource::Query('ROLLBACK'); automatique ! //////////////////////////////////////////////////////////////////////////////// // Set the stage // $oServer = MetaModel::NewObject('Server'); $oServer->Set('name', 'unit test linkset'); $oServer->Set('org_id', 3); $oServer->DBInsert(); $iServer = $oServer->GetKey(); $oTypes = new DBObjectSet(DBObjectSearch::FromOQL('SELECT NetworkDeviceType WHERE name = "Router"')); $oType = $oTypes->fetch(); $oDevice = MetaModel::NewObject('NetworkDevice'); $oDevice->Set('name', 'test device A'); $oDevice->Set('org_id', 3); $oDevice->Set('networkdevicetype_id', $oType->GetKey()); $oDevice->DBInsert(); $iDev1 = $oDevice->GetKey(); $oDevice = MetaModel::NewObject('NetworkDevice'); $oDevice->Set('name', 'test device B'); $oDevice->Set('org_id', 3); $oDevice->Set('networkdevicetype_id', $oType->GetKey()); $oDevice->DBInsert(); $iDev2 = $oDevice->GetKey(); //////////////////////////////////////////////////////////////////////////////// // Scenarii // $aScenarii = [ [ 'description' => 'Add the first item', 'links' => [ [ 'networkdevice_id' => $iDev1, 'connectableci_id' => $iServer, 'network_port' => '', 'device_port' => '', ], ], 'expected-res' => [ "$iDev1, test device A, unit test linkset, , , downlink, test device A", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Modify the unique item', 'links' => [ [ 'networkdevice_id' => $iDev1, 'connectableci_id' => $iServer, 'network_port' => 'devTagada', 'device_port' => '', ], ], 'expected-res' => [ "$iDev1, test device A, unit test linkset, devTagada, , downlink, test device A", ], 'history_added' => 1, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'Modify again the original item and add a second item', 'links' => [ [ 'networkdevice_id' => $iDev1, 'connectableci_id' => $iServer, 'network_port' => '', 'device_port' => '', ], [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => '', 'device_port' => '', ], ], 'expected-res' => [ "$iDev1, test device A, unit test linkset, , , downlink, test device A", "$iDev2, test device B, unit test linkset, , , downlink, test device B", ], 'history_added' => 2, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'No change, the links are added in the reverse order', 'links' => [ [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => '', 'device_port' => '', ], [ 'networkdevice_id' => $iDev1, 'connectableci_id' => $iServer, 'network_port' => '', 'device_port' => '', ], ], 'expected-res' => [ "$iDev1, test device A, unit test linkset, , , downlink, test device A", "$iDev2, test device B, unit test linkset, , , downlink, test device B", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Change on attribute on both links at the same time', 'links' => [ [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'PortDev B', 'device_port' => '', ], [ 'networkdevice_id' => $iDev1, 'connectableci_id' => $iServer, 'network_port' => 'PortDev A', 'device_port' => '', ], ], 'expected-res' => [ "$iDev1, test device A, unit test linkset, PortDev A, , downlink, test device A", "$iDev2, test device B, unit test linkset, PortDev B, , downlink, test device B", ], 'history_added' => 2, 'history_removed' => 2, 'history_modified' => 0, ], [ 'description' => 'Removing A', 'links' => [ [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'PortDev B', 'device_port' => '', ], ], 'expected-res' => [ "$iDev2, test device B, unit test linkset, PortDev B, , downlink, test device B", ], 'history_added' => 0, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'Adding B again - with a different port (duplicate!)', 'links' => [ [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'port_123', 'device_port' => '', ], [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'port_456', 'device_port' => '', ], ], 'expected-res' => [ "$iDev2, test device B, unit test linkset, port_123, , downlink, test device B", "$iDev2, test device B, unit test linkset, port_456, , downlink, test device B", ], 'history_added' => 2, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'No change (creating a set with the reloaded links, like in the UI)', 'links' => [ [ 'id' => "SELECT lnkConnectableCIToNetworkDevice WHERE networkdevice_id = $iDev2 AND connectableci_id = $iServer AND network_port = 'port_123'", 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'port_123', 'device_port' => '', ], [ 'id' => "SELECT lnkConnectableCIToNetworkDevice WHERE networkdevice_id = $iDev2 AND connectableci_id = $iServer AND network_port = 'port_456'", 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'port_456', 'device_port' => '', ], ], 'expected-res' => [ "$iDev2, test device B, unit test linkset, port_123, , downlink, test device B", "$iDev2, test device B, unit test linkset, port_456, , downlink, test device B", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Change an attribute on one link (based on reloaded links, like in the UI)', 'links' => [ [ 'id' => "SELECT lnkConnectableCIToNetworkDevice WHERE networkdevice_id = $iDev2 AND connectableci_id = $iServer AND network_port = 'port_123'", 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'port_123_modified', 'device_port' => '', ], [ 'id' => "SELECT lnkConnectableCIToNetworkDevice WHERE networkdevice_id = $iDev2 AND connectableci_id = $iServer AND network_port = 'port_456'", 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'port_456', 'device_port' => '', ], ], 'expected-res' => [ "$iDev2, test device B, unit test linkset, port_123_modified, , downlink, test device B", "$iDev2, test device B, unit test linkset, port_456, , downlink, test device B", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 1, ], [ 'description' => 'Remove the second link (set based on reloaded links, like in the UI)', 'links' => [ [ 'id' => "SELECT lnkConnectableCIToNetworkDevice WHERE networkdevice_id = $iDev2 AND connectableci_id = $iServer AND network_port = 'port_123_modified'", 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'port_123_modified', 'device_port' => '', ], ], 'expected-res' => [ "$iDev2, test device B, unit test linkset, port_123_modified, , downlink, test device B", ], 'history_added' => 0, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'Remove all', 'links' => [ ], 'expected-res' => [ ], 'history_added' => 0, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'Create one link from scratch, no port, to prepare for the next test case', 'links' => [ [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'portX', 'device_port' => '', ], ], 'expected-res' => [ "$iDev2, test device B, unit test linkset, portX, , downlink, test device B", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Device B twice (same characteristics) - known issue #1145 (test failing until we fix it)', 'links' => [ [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'portX', 'device_port' => '', ], [ 'networkdevice_id' => $iDev2, 'connectableci_id' => $iServer, 'network_port' => 'portX', 'device_port' => '', ], ], 'expected-res' => [ "$iDev2, test device B, unit test linkset, portX, , downlink, test device B", "$iDev2, test device B, unit test linkset, portX, , downlink, test device B", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 0, ], ]; foreach ($aScenarii as $aScenario) { echo "

".$aScenario['description']."

\n"; $oChange = MetaModel::NewObject("CMDBChange"); $oChange->Set("date", time()); $oChange->Set("userinfo", CMDBChange::GetCurrentUserName()); $oChange->Set("origin", 'custom-extension'); $oChange->DBInsert(); CMDBObject::SetCurrentChange($oChange); $iChange = $oChange->GetKey(); // Prepare set $oLinkset = DBObjectSet::FromScratch('lnkConnectableCIToNetworkDevice'); foreach ($aScenario['links'] as $aLinkData) { if (array_key_exists('id', $aLinkData)) { $sOQL = $aLinkData['id']; $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL)); $oLink1 = $oSet->Fetch(); if (!is_object($oLink1)) { throw new Exception('Failed to find the lnkConnectableCIToNetworkDevice: '.$sOQL); } } else { $oLink1 = MetaModel::NewObject('lnkConnectableCIToNetworkDevice'); } foreach ($aLinkData as $sAttCode => $value) { if ($sAttCode == 'id') { continue; } $oLink1->Set($sAttCode, $value); } $oLinkset->AddObject($oLink1); } // Write $oServer = MetaModel::GetObject('Server', $iServer); $oServer->Set('networkdevice_list', $oLinkset); $oServer->DBWrite(); // Check Results $bFoundIssue = false; $oServer = MetaModel::GetObject('Server', $iServer); $oLinkset = $oServer->Get('networkdevice_list'); $aRes = $this->StandardizedDump($oLinkset, 'connectableci_id'); $sRes = var_export($aRes, true); echo "Found:
".$sRes."
\n"; $sExpectedRes = var_export($aScenario['expected-res'], true); if ($sRes != $sExpectedRes) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting:
".$sExpectedRes."
\n"; } // Check History $aQueryParams = ['change' => $iChange, 'objclass' => get_class($oServer), 'objkey' => $oServer->GetKey()]; $oAdded = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksAddRemove WHERE objclass = :objclass AND objkey = :objkey AND change = :change AND type = 'added'"), [], $aQueryParams); echo "added: ".$oAdded->Count()."
\n"; if ($aScenario['history_added'] != $oAdded->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_added']."
\n"; } $oRemoved = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksAddRemove WHERE objclass = :objclass AND objkey = :objkey AND change = :change AND type = 'removed'"), [], $aQueryParams); echo "removed: ".$oRemoved->Count()."
\n"; if ($aScenario['history_removed'] != $oRemoved->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_removed']."
\n"; } $oModified = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksTune WHERE objclass = :objclass AND objkey = :objkey AND change = :change"), [], $aQueryParams); echo "modified: ".$oModified->Count()."
\n"; if ($aScenario['history_modified'] != $oModified->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_modified']."
\n"; } if ($bFoundIssue) { throw new Exception('Stopping on failed scenario'); } } } } class TestLinkSetRecording_NN_NoDuplicates extends TestLinkSet { public static function GetName() { return 'Linksets N-N in general (99% of them)'; } public static function GetDescription() { return 'Simulate CSV/data synchro type of recording. Check the values and the history.'; } protected function DoExecute() { CMDBSource::Query('START TRANSACTION'); //CMDBSource::Query('ROLLBACK'); automatique ! //////////////////////////////////////////////////////////////////////////////// // Set the stage // $oTeam = MetaModel::NewObject('Team'); $oTeam->Set('name', 'unit test linkset'); $oTeam->Set('org_id', 3); $oTeam->DBInsert(); $iTeam = $oTeam->GetKey(); $oPerson = MetaModel::NewObject('Person'); $oPerson->Set('name', 'test person A'); $oPerson->Set('first_name', 'totoche'); $oPerson->Set('org_id', 3); $oPerson->DBInsert(); $iPerson1 = $oPerson->GetKey(); $oPerson = MetaModel::NewObject('Person'); $oPerson->Set('name', 'test person B'); $oPerson->Set('first_name', 'totoche'); $oPerson->Set('org_id', 3); $oPerson->DBInsert(); $iPerson2 = $oPerson->GetKey(); $oTypes = new DBObjectSet(DBSearch::FromOQL('SELECT ContactType WHERE name="Manager"')); $iRole = $oTypes->Fetch()->GetKey(); //////////////////////////////////////////////////////////////////////////////// // Scenarii // $aScenarii = [ [ 'description' => 'Add the first item', 'links' => [ [ 'person_id' => $iPerson1, 'team_id' => $iTeam, 'role_id' => 0, ], ], 'expected-res' => [ "unit test linkset, $iPerson1, test person A, 0, , totoche test person A, ", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Modify the unique item', 'links' => [ [ 'person_id' => $iPerson1, 'team_id' => $iTeam, 'role_id' => $iRole, ], ], 'expected-res' => [ "unit test linkset, $iPerson1, test person A, $iRole, Manager, totoche test person A, Manager", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 1, ], [ 'description' => 'Modify again the original item and add a second item', 'links' => [ [ 'person_id' => $iPerson1, 'team_id' => $iTeam, 'role_id' => 0, ], [ 'person_id' => $iPerson2, 'team_id' => $iTeam, 'role_id' => 0, ], ], 'expected-res' => [ "unit test linkset, $iPerson1, test person A, 0, , totoche test person A, ", "unit test linkset, $iPerson2, test person B, 0, , totoche test person B, ", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 1, ], [ 'description' => 'No change, the links are added in the reverse order', 'links' => [ [ 'person_id' => $iPerson2, 'team_id' => $iTeam, 'role_id' => 0, ], [ 'person_id' => $iPerson1, 'team_id' => $iTeam, 'role_id' => 0, ], ], 'expected-res' => [ "unit test linkset, $iPerson1, test person A, 0, , totoche test person A, ", "unit test linkset, $iPerson2, test person B, 0, , totoche test person B, ", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Removing A', 'links' => [ [ 'person_id' => $iPerson2, 'team_id' => $iTeam, 'role_id' => 0, ], ], 'expected-res' => [ "unit test linkset, $iPerson2, test person B, 0, , totoche test person B, ", ], 'history_added' => 0, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'Adding B again (duplicate!)', 'links' => [ [ 'person_id' => $iPerson2, 'team_id' => $iTeam, 'role_id' => 0, ], [ 'person_id' => $iPerson2, 'team_id' => $iTeam, 'role_id' => 0, ], ], 'expected-res' => [ "unit test linkset, $iPerson2, test person B, 0, , totoche test person B, ", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Remove all', 'links' => [ ], 'expected-res' => [ ], 'history_added' => 0, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'Add the first item (again)', 'links' => [ [ 'person_id' => $iPerson1, 'team_id' => $iTeam, 'role_id' => 0, ], ], 'expected-res' => [ "unit test linkset, $iPerson1, test person A, 0, , totoche test person A, ", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Set the role (based on reloaded links, like in the UI)', 'links' => [ [ 'id' => "SELECT lnkPersonToTeam WHERE person_id=$iPerson1 AND team_id=$iTeam", 'person_id' => $iPerson1, 'team_id' => $iTeam, 'role_id' => $iRole, ], ], 'expected-res' => [ "unit test linkset, $iPerson1, test person A, 14, Manager, totoche test person A, Manager", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 1, ], [ 'description' => 'Clear the role and add another person with a role (based on reloaded links, like in the UI)', 'links' => [ [ 'id' => "SELECT lnkPersonToTeam WHERE person_id=$iPerson1 AND team_id=$iTeam", 'person_id' => $iPerson1, 'team_id' => $iTeam, 'role_id' => 0, ], [ 'person_id' => $iPerson2, 'team_id' => $iTeam, 'role_id' => $iRole, ], ], 'expected-res' => [ "unit test linkset, $iPerson1, test person A, 0, , totoche test person A, ", "unit test linkset, $iPerson2, test person B, 14, Manager, totoche test person B, Manager", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 1, ], ]; foreach ($aScenarii as $aScenario) { echo "

".$aScenario['description']."

\n"; $oChange = MetaModel::NewObject("CMDBChange"); $oChange->Set("date", time()); $oChange->Set("userinfo", CMDBChange::GetCurrentUserName()); $oChange->Set("origin", 'custom-extension'); $oChange->DBInsert(); CMDBObject::SetCurrentChange($oChange); $iChange = $oChange->GetKey(); // Prepare set $oLinkset = DBObjectSet::FromScratch('lnkPersonToTeam'); foreach ($aScenario['links'] as $aLinkData) { if (array_key_exists('id', $aLinkData)) { $sOQL = $aLinkData['id']; $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL)); $oLink1 = $oSet->Fetch(); if (!is_object($oLink1)) { throw new Exception('Failed to find the lnkPersonToTeam: '.$sOQL); } } else { $oLink1 = MetaModel::NewObject('lnkPersonToTeam'); } foreach ($aLinkData as $sAttCode => $value) { if ($sAttCode == 'id') { continue; } $oLink1->Set($sAttCode, $value); } $oLinkset->AddObject($oLink1); } // Write $oTeam = MetaModel::GetObject('Team', $iTeam); $oTeam->Set('persons_list', $oLinkset); $oTeam->DBWrite(); // Check Results $bFoundIssue = false; $oTeam = MetaModel::GetObject('Team', $iTeam); $oLinkset = $oTeam->Get('persons_list'); $aRes = $this->StandardizedDump($oLinkset, 'team_id'); $sRes = var_export($aRes, true); echo "Found:
".$sRes."
\n"; $sExpectedRes = var_export($aScenario['expected-res'], true); if ($sRes != $sExpectedRes) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting:
".$sExpectedRes."
\n"; } // Check History $aQueryParams = ['change' => $iChange, 'objclass' => get_class($oTeam), 'objkey' => $oTeam->GetKey()]; $oAdded = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksAddRemove WHERE objclass = :objclass AND objkey = :objkey AND change = :change AND type = 'added'"), [], $aQueryParams); echo "added: ".$oAdded->Count()."
\n"; if ($aScenario['history_added'] != $oAdded->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_added']."
\n"; } $oRemoved = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksAddRemove WHERE objclass = :objclass AND objkey = :objkey AND change = :change AND type = 'removed'"), [], $aQueryParams); echo "removed: ".$oRemoved->Count()."
\n"; if ($aScenario['history_removed'] != $oRemoved->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_removed']."
\n"; } $oModified = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksTune WHERE objclass = :objclass AND objkey = :objkey AND change = :change"), [], $aQueryParams); echo "modified: ".$oModified->Count()."
\n"; if ($aScenario['history_modified'] != $oModified->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_modified']."
\n"; } if ($bFoundIssue) { throw new Exception('Stopping on failed scenario'); } } } } class TestLinkSetRecording_1N extends TestLinkSet { public static function GetName() { return 'Linkset 1-N (Network Interface vs Server: Edit in-place)'; } public static function GetDescription() { return 'Simulate CSV/data synchro type of recording. Check the values and the history.'; } protected function DoExecute() { CMDBSource::Query('START TRANSACTION'); //CMDBSource::Query('ROLLBACK'); automatique ! //////////////////////////////////////////////////////////////////////////////// // Set the stage // $oServer = MetaModel::NewObject('Server'); $oServer->Set('name', 'unit test linkset'); $oServer->Set('org_id', 3); $oServer->DBInsert(); $iServer = $oServer->GetKey(); //////////////////////////////////////////////////////////////////////////////// // Scenarii // $aScenarii = [ [ 'description' => 'Add the first interface', 'interfaces' => [ [ 'connectableci_id' => $iServer, 'name' => 'eth0', 'speed' => '1000.00', ], ], 'expected-res' => [ "eth0, , , , , , 1000.00, $iServer, unit test linkset, PhysicalInterface, unit test linkset, Server", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Add a second interface', 'interfaces' => [ [ 'connectableci_id' => $iServer, 'name' => 'eth0', 'speed' => '1000.00', ], [ 'connectableci_id' => $iServer, 'name' => 'eth1', 'speed' => '1000.00', ], ], 'expected-res' => [ "eth0, , , , , , 1000.00, $iServer, unit test linkset, PhysicalInterface, unit test linkset, Server", "eth1, , , , , , 1000.00, $iServer, unit test linkset, PhysicalInterface, unit test linkset, Server", ], 'history_added' => 1, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Change the speed of an interface', 'interfaces' => [ [ 'connectableci_id' => $iServer, 'name' => 'eth0', 'speed' => '100.00', ], [ 'connectableci_id' => $iServer, 'name' => 'eth1', 'speed' => '1000.00', ], ], 'expected-res' => [ "eth0, , , , , , 100.00, $iServer, unit test linkset, PhysicalInterface, unit test linkset, Server", "eth1, , , , , , 1000.00, $iServer, unit test linkset, PhysicalInterface, unit test linkset, Server", ], 'history_added' => 1, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'Change the name of an interface', 'interfaces' => [ [ 'connectableci_id' => $iServer, 'name' => 'eth0-renamed', 'speed' => '1000.00', ], [ 'connectableci_id' => $iServer, 'name' => 'eth1', 'speed' => '1000.00', ], ], 'expected-res' => [ "eth0-renamed, , , , , , 1000.00, $iServer, unit test linkset, PhysicalInterface, unit test linkset, Server", "eth1, , , , , , 1000.00, $iServer, unit test linkset, PhysicalInterface, unit test linkset, Server", ], 'history_added' => 1, 'history_removed' => 1, 'history_modified' => 0, ], [ 'description' => 'Remove all interfaces', 'interfaces' => [ ], 'expected-res' => [ ], 'history_added' => 0, 'history_removed' => 2, 'history_modified' => 0, ], ]; foreach ($aScenarii as $aScenario) { echo "

".$aScenario['description']."

\n"; $oChange = MetaModel::NewObject("CMDBChange"); $oChange->Set("date", time()); $oChange->Set("userinfo", CMDBChange::GetCurrentUserName()); $oChange->Set("origin", 'custom-extension'); $oChange->DBInsert(); CMDBObject::SetCurrentChange($oChange); $iChange = $oChange->GetKey(); // Prepare set $oLinkset = DBObjectSet::FromScratch('PhysicalInterface'); foreach ($aScenario['interfaces'] as $aIntfData) { $oInterface = MetaModel::NewObject('PhysicalInterface'); foreach ($aIntfData as $sAttCode => $value) { $oInterface->Set($sAttCode, $value); } $oLinkset->AddObject($oInterface); } // Write $oServer = MetaModel::GetObject('Server', $iServer); $oServer->Set('physicalinterface_list', $oLinkset); $oServer->DBWrite(); // Check Results $bFoundIssue = false; $oServer = MetaModel::GetObject('Server', $iServer); $oLinkset = $oServer->Get('physicalinterface_list'); $aRes = $this->StandardizedDump($oLinkset, 'zzz'); $sRes = var_export($aRes, true); echo "Found:
".$sRes."
\n"; $sExpectedRes = var_export($aScenario['expected-res'], true); if ($sRes != $sExpectedRes) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting:
".$sExpectedRes."
\n"; } // Check History $aQueryParams = ['change' => $iChange, 'objclass' => get_class($oServer), 'objkey' => $oServer->GetKey()]; $oAdded = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksAddRemove WHERE objclass = :objclass AND objkey = :objkey AND change = :change AND type = 'added'"), [], $aQueryParams); echo "added: ".$oAdded->Count()."
\n"; if ($aScenario['history_added'] != $oAdded->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_added']."
\n"; } $oRemoved = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksAddRemove WHERE objclass = :objclass AND objkey = :objkey AND change = :change AND type = 'removed'"), [], $aQueryParams); echo "removed: ".$oRemoved->Count()."
\n"; if ($aScenario['history_removed'] != $oRemoved->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_removed']."
\n"; } $oModified = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksTune WHERE objclass = :objclass AND objkey = :objkey AND change = :change"), [], $aQueryParams); echo "modified: ".$oModified->Count()."
\n"; if ($aScenario['history_modified'] != $oModified->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_modified']."
\n"; } if ($bFoundIssue) { throw new Exception('Stopping on failed scenario'); } } } } class TestLinkSetRecording_1NAdd_Remove extends TestLinkSet { public static function GetName() { return 'Linkset 1-N (Delivery Model vs Organization: Edit Add/Remove)'; } public static function GetDescription() { return 'Simulate CSV/data synchro type of recording. Check the values and the history.'; } protected function DoExecute() { CMDBSource::Query('START TRANSACTION'); //CMDBSource::Query('ROLLBACK'); automatique ! //////////////////////////////////////////////////////////////////////////////// // Set the stage // $oProvider = new Organization(); $oProvider->Set('name', 'Test-Provider1'); $oProvider->DBInsert(); $iProvider = $oProvider->GetKey(); $oDM1 = new DeliveryModel(); $oDM1->Set('name', 'Test-DM-1'); $oDM1->Set('org_id', $iProvider); $oDM1->DBInsert(); $iDM1 = $oDM1->GetKey(); $oDM2 = new DeliveryModel(); $oDM2->Set('name', 'Test-DM-2'); $oDM2->Set('org_id', $iProvider); $oDM2->DBInsert(); $iDM2 = $oDM2->GetKey(); //////////////////////////////////////////////////////////////////////////////// // Scenarii // $aScenarii = [ [ 'description' => 'Add the first customer', 'organizations' => [ [ 'deliverymodel_id' => $iDM1, 'name' => 'Test-Customer-1', ], ], 'expected-res' => [ "Test-Customer-1, , active, 0, , $iDM1, Test-DM-1, , Test-DM-1", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Remove the customer by loading an empty set', 'organizations' => [ ], 'expected-res' => [ ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Create two customers at once', 'organizations' => [ [ 'deliverymodel_id' => $iDM1, 'name' => 'Test-Customer-1', ], [ 'deliverymodel_id' => $iDM1, 'name' => 'Test-Customer-2', ], ], 'expected-res' => [ "Test-Customer-1, , active, 0, , $iDM1, Test-DM-1, , Test-DM-1", "Test-Customer-2, , active, 0, , $iDM1, Test-DM-1, , Test-DM-1", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Move Customer-1 to the second Delivery Model', 'organizations' => [ [ 'id' => "SELECT Organization WHERE name='Test-Customer-1'", 'deliverymodel_id' => $iDM2, 'name' => 'Test-Customer-1', ], [ 'deliverymodel_id' => $iDM1, 'name' => 'Test-Customer-2', ], ], 'expected-res' => [ "Test-Customer-2, , active, 0, , $iDM1, Test-DM-1, , Test-DM-1", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], [ 'description' => 'Move Customer-1 back to the first Delivery Model and reset Customer-2 (no Delivery Model)', 'organizations' => [ [ 'id' => "SELECT Organization WHERE name='Test-Customer-1'", 'deliverymodel_id' => $iDM1, 'name' => 'Test-Customer-1', ], [ 'id' => "SELECT Organization WHERE name='Test-Customer-2'", 'deliverymodel_id' => 0, 'name' => 'Test-Customer-2', ], ], 'expected-res' => [ "Test-Customer-1, , active, 0, , $iDM1, Test-DM-1, , Test-DM-1", ], 'history_added' => 0, 'history_removed' => 0, 'history_modified' => 0, ], ]; foreach ($aScenarii as $aScenario) { echo "

".$aScenario['description']."

\n"; $oChange = MetaModel::NewObject("CMDBChange"); $oChange->Set("date", time()); $oChange->Set("userinfo", CMDBChange::GetCurrentUserName()); $oChange->Set("origin", 'custom-extension'); $oChange->DBInsert(); CMDBObject::SetCurrentChange($oChange); $iChange = $oChange->GetKey(); // Prepare set $oLinkset = DBObjectSet::FromScratch('Organization'); foreach ($aScenario['organizations'] as $aOrgData) { if (array_key_exists('id', $aOrgData)) { $sOQL = $aOrgData['id']; $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL)); $oOrg = $oSet->Fetch(); if (!is_object($oOrg)) { throw new Exception('Failed to find the Organization: '.$sOQL); } } else { $oOrg = MetaModel::NewObject('Organization'); } foreach ($aOrgData as $sAttCode => $value) { if ($sAttCode == 'id') { continue; } $oOrg->Set($sAttCode, $value); } $oLinkset->AddObject($oOrg); } // Write $oDM = MetaModel::GetObject('DeliveryModel', $iDM1); $oDM->Set('customers_list', $oLinkset); $oDM->DBWrite(); // Check Results $bFoundIssue = false; $oDM = MetaModel::GetObject('DeliveryModel', $iDM1); $oLinkset = $oDM->Get('customers_list'); $aRes = $this->StandardizedDump($oLinkset, 'zzz'); $sRes = var_export($aRes, true); echo "Found:
".$sRes."
\n"; $sExpectedRes = var_export($aScenario['expected-res'], true); if ($sRes != $sExpectedRes) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting:
".$sExpectedRes."
\n"; } // Check History $aQueryParams = ['change' => $iChange, 'objclass' => get_class($oDM), 'objkey' => $oDM->GetKey()]; $oAdded = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksAddRemove WHERE objclass = :objclass AND objkey = :objkey AND change = :change AND type = 'added'"), [], $aQueryParams); echo "added: ".$oAdded->Count()."
\n"; if ($aScenario['history_added'] != $oAdded->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_added']."
\n"; } $oRemoved = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksAddRemove WHERE objclass = :objclass AND objkey = :objkey AND change = :change AND type = 'removed'"), [], $aQueryParams); echo "removed: ".$oRemoved->Count()."
\n"; if ($aScenario['history_removed'] != $oRemoved->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_removed']."
\n"; } $oModified = new DBObjectSet(DBSearch::FromOQL("SELECT CMDBChangeOpSetAttributeLinksTune WHERE objclass = :objclass AND objkey = :objkey AND change = :change"), [], $aQueryParams); echo "modified: ".$oModified->Count()."
\n"; if ($aScenario['history_modified'] != $oModified->Count()) { $bFoundIssue = true; echo "NOT COMPLIANT!!! Expecting: ".$aScenario['history_modified']."
\n"; } if ($bFoundIssue) { throw new Exception('Stopping on failed scenario'); } } } } class TestDateTimeFormats extends TestBizModel { public static function GetName() { return 'Check Date & Time formating and parsing'; } public static function GetDescription() { return 'Check the formating and parsing of dates for various formats'; } public function DoExecute() { require_once(APPROOT.'core/datetimeformat.class.inc.php'); $bRet = true; $aTestFormats = [ 'French (short)' => 'd/m/Y H:i:s', 'French (short - no seconds)' => 'd/m/Y H:i', 'French (long)' => 'd/m/Y H\\h i\\m\\i\\n s\\s', 'English US' => 'm/d/Y H:i:s', 'English US (12 hours)' => 'm/d/Y h:i:s a', 'English US (12 hours, short)' => 'n/j/Y g:i:s a', 'English UK' => 'd/m/Y H:i:s', 'German' => 'd.m.Y H:i:s', 'SQL' => 'Y-m-d H:i:s', ]; // Valid date and times, all tests should pass $aTestDates = ['2015-01-01 00:00:00', '2015-12-31 23:59:00', '2016-01-01 08:21:00', '2016-02-28 12:30:00', '2016-02-29 16:47:00', /*'2016-02-29 14:30:17'*/]; foreach ($aTestFormats as $sDesc => $sFormat) { $this->ReportSuccess("Test of the '$sDesc' format: '$sFormat':"); $oFormat = new DateTimeFormat($sFormat); foreach ($aTestDates as $sTestDate) { $oDate = new DateTime($sTestDate); $sFormattedDate = $oFormat->Format($oDate); $oParsedDate = $oFormat->Parse($sFormattedDate); $sPattern = $oFormat->ToRegExpr('/'); $bParseOk = ($oParsedDate->format('Y-m-d H:i:s') == $sTestDate); if (!$bParseOk) { $this->ReportError('Parsed ('.$sFormattedDate.') date different from initial date (difference of '.((int)$oParsedDate->format('U') - (int)$oDate->format('U')).'s)'); $bRet = false; } $bValidateOk = preg_match($sPattern, $sFormattedDate); if (!$bValidateOk) { $this->ReportError('Formatted date ('.$sFormattedDate.') does not match the validation pattern ('.$sPattern.')'); $bRet = false; } $this->ReportSuccess("Formatted date: $sFormattedDate - Parsing: ".($bParseOk ? 'Ok' : 'KO')." - Validation: ".($bValidateOk ? 'Ok' : 'KO')); } echo "

\n"; } // Invalid date & time strings, all regexpr validation should fail $aInvalidTestDates = [ 'SQL' => ['2015-13-01 00:00:00', '2015-12-51 23:59:00', '2016-01-01 +08:21:00', '2016-02-28 24:30:00', '2016-02-29 16:67:88'], 'French (short)' => ['01/01/20150 00:00:00', '01/01/20150 00:00:00', '01/13/2015 00:00:00', '01/01/2015 40:00:00', '01/01/2015 00:99:00'], 'English US (12 hours)' => ['13/01/2015 12:00:00 am', '12/33/2015 12:00:00 am', '12/23/215 12:00:00 am', '05/04/2016 16:00:00 am', '05/04/2016 10:00:00 ap'], ]; foreach ($aInvalidTestDates as $sFormatName => $aDatesToParse) { $sFormat = $aTestFormats[$sFormatName]; $oFormat = new DateTimeFormat($sFormat); $this->ReportSuccess("Test of the '$sFormatName' format: '$sFormat':"); foreach ($aDatesToParse as $sDate) { $sPattern = $oFormat->ToRegExpr('/'); $bValidateOk = preg_match($sPattern, $sDate); if ($bValidateOk) { $this->ReportError('Formatted date ('.$sFormattedDate.') matches the validation pattern ('.$sPattern.') whereas it should not!'); $bRet = false; } $this->ReportSuccess("Formatted date: $sDate - Validation: ".($bValidateOk ? 'KO' : 'rejected, Ok.')); } } return $bRet; } } class TestExecActions extends TestBizModel { public static function GetName() { return 'Scripted actions API DBObject::ExecAction - syntax errors'; } public static function GetDescription() { return 'Check that wrong arguments are correclty reported'; } protected function DoExecute() { $oSource = new UserRequest(); $oSource->Set('title', 'Houston!'); $oSource->Set('description', 'Looks like we have a problem'); $oTarget = new Server(); //////////////////////////////////////////////////////////////////////////////// // Scenarii // $aScenarii = [ [ 'action' => 'set', 'error' => 'Action: set - Invalid syntax', ], [ 'action' => 'smurf()', 'error' => 'Action: smurf() - Invalid verb', ], [ 'action' => ' smurf () ', 'error' => 'Action: smurf () - Invalid syntax', ], [ 'action' => 'clone(some_att_code, another_one)', 'error' => 'Action: clone(some_att_code, another_one) - Unknown attribute Server::some_att_code', ], [ 'action' => 'copy(toto, titi)', 'error' => 'Action: copy(toto, titi) - Unknown attribute Server::titi', ], [ 'action' => 'copy(toto, name)', 'error' => 'Action: copy(toto, name) - Unknown attribute UserRequest::toto', ], [ 'action' => 'copy()', 'error' => 'Action: copy() - Missing argument #1: source attribute', ], [ 'action' => 'copy(title)', 'error' => 'Action: copy(title) - Missing argument #2: target attribute', ], [ 'action' => 'set(toto)', 'error' => 'Action: set(toto) - Unknown attribute Server::toto', ], [ 'action' => 'set(toto, something)', 'error' => 'Action: set(toto, something) - Unknown attribute Server::toto', ], [ 'action' => 'set()', 'error' => 'Action: set() - Missing argument #1: target attribute', ], [ 'action' => 'reset(toto)', 'error' => 'Action: reset(toto) - Unknown attribute Server::toto', ], [ 'action' => 'reset()', 'error' => 'Action: reset() - Missing argument #1: target attribute', ], [ 'action' => 'nullify(toto)', 'error' => 'Action: nullify(toto) - Unknown attribute Server::toto', ], [ 'action' => 'nullify()', 'error' => 'Action: nullify() - Missing argument #1: target attribute', ], [ 'action' => 'append(toto, something)', 'error' => 'Action: append(toto, something) - Unknown attribute Server::toto', ], [ 'action' => 'append(name)', 'error' => 'Action: append(name) - Missing argument #2: value to append', ], [ 'action' => 'append()', 'error' => 'Action: append() - Missing argument #1: target attribute', ], [ 'action' => 'add_to_list(toto, titi)', 'error' => 'Action: add_to_list(toto, titi) - Unknown attribute UserRequest::toto', ], [ 'action' => 'add_to_list(caller_id, titi)', 'error' => 'Action: add_to_list(caller_id, titi) - Unknown attribute Server::titi', ], [ 'action' => 'add_to_list(caller_id)', 'error' => 'Action: add_to_list(caller_id) - Missing argument #2: target attribute (link set)', ], [ 'action' => 'add_to_list()', 'error' => 'Action: add_to_list() - Missing argument #1: source attribute', ], [ 'action' => 'apply_stimulus(toto)', 'error' => 'Action: apply_stimulus(toto) - Unknown stimulus Server::toto', ], [ 'action' => 'apply_stimulus()', 'error' => 'Action: apply_stimulus() - Missing argument #1: stimulus', ], [ 'action' => 'call_method(toto)', 'error' => 'Action: call_method(toto) - Unknown method Server::toto()', ], [ 'action' => 'call_method()', 'error' => 'Action: call_method() - Missing argument #1: method name', ], ]; foreach ($aScenarii as $aScenario) { echo "

".htmlentities($aScenario['action'], ENT_QUOTES, 'UTF-8')."

\n"; $sMessage = ''; try { $oTarget->ExecActions([$aScenario['action']], ['source' => $oSource]); $sMessage = 'Expecting an exception... none has been thrown!'; } catch (Exception $e) { if ($e->getMessage() != $aScenario['error']) { $sMessage = 'Wrong message: expecting "'.$aScenario['error'].'" and got "'.$e->getMessage().'"'; } } if ($sMessage != '') { throw new Exception($sMessage); } } } } class TestParsingOptimization extends TestBizModel { public static function GetName() { return 'Query optimizations (Merging joins on OQL parsing)'; } public static function GetDescription() { return 'Checking a few queries that do involve query optimizations (implemented for the sake of optimizing the portal)'; } protected function DoExecute() { $aQueries = [ "SELECT UserRequest AS u JOIN Person AS p1 ON u.caller_id=p1.id JOIN Organization AS o1 ON p1.org_id=o1.id JOIN Person AS p2 ON u.caller_id=p2.id WHERE p2.status='active' AND p1.status='inactive'", "SELECT UserRequest AS u JOIN Person AS p1 ON u.caller_id=p1.id JOIN Person AS p2 ON u.caller_id=p2.id WHERE p2.status='active' AND p1.status='inactive'", "SELECT UserRequest AS u JOIN Person AS p1 ON u.caller_id=p1.id JOIN Organization AS o1 ON p1.org_id=o1.id JOIN Person ON u.caller_id=Person.id JOIN Location AS l ON Person.location_id = l.id WHERE Person.status='active' AND p1.status='inactive' AND l.country='France'", ]; foreach ($aQueries as $sQuery) { echo "
To Parse: ".htmlentities($sQuery, ENT_QUOTES, 'UTF-8')."
\n"; $oSearch = DBSearch::FromOQL($sQuery); $sQueryOpt = $oSearch->ToOQL(); echo "
Optimized: ".htmlentities($sQueryOpt, ENT_QUOTES, 'UTF-8')."
\n"; CMDBSource::TestQuery($oSearch->MakeSelectQuery()); echo "

Successfully tested the SQL query.

\n"; } } } class TestUnions extends TestBizModel { public static function GetName() { return 'Unions'; } public static function GetDescription() { return 'Checking a few UNION queries'; } protected function DoExecute() { // The two first items did reveal an issue with the query cache, //because SELECT Person on the second line must not give the same query as SELECT Person on the first line $aQueries = [ "SELECT Person UNION SELECT Person" => true, "SELECT Person UNION SELECT Team" => true, "SELECT Person UNION SELECT Contact" => true, "SELECT Contact UNION SELECT Person" => true, "SELECT Person UNION SELECT Organization" => false, ]; foreach ($aQueries as $sQuery => $bSuccess) { echo "
To Parse: ".htmlentities($sQuery, ENT_QUOTES, 'UTF-8')."
\n"; try { $oSearch = DBSearch::FromOQL($sQuery); if (!$bSuccess) { throw new Exception('This query should not be parsable!'); } CMDBSource::TestQuery($oSearch->MakeSelectQuery()); echo "

Successfully tested the SQL query.

\n"; } catch (OQLException $e) { if ($bSuccess) { throw $e; } echo "

Failed as expected.

\n"; } } } } class TestImplicitAlias extends TestBizModel { public static function GetName() { return 'OQLImplicitAliases'; } public static function GetDescription() { return 'Checking implicit aliases resolution'; } protected function DoExecute() { // The two first items did reveal an issue with the query cache, //because SELECT Person on the second line must not give the same query as SELECT Person on the first line $aQueries = [ "SELECT Person WHERE org_id = 1" => true, "SELECT Person WHERE s.org_id = 1" => false, "SELECT Person AS p WHERE p.org_id = 1" => true, "SELECT Person AS p WHERE Person.org_id = 1" => false, "SELECT P FROM Organization AS O JOIN Person AS P ON P.org_id = O.id WHERE org_id = 2" => true, // Bug N.539 "SELECT Server JOIN Location ON Server.location_id = Location.id" => true, "SELECT Server JOIN Location ON Server.location_id = id" => false, "SELECT Server JOIN Location ON Server = Location.id" => false, "SELECT Server JOIN Location ON Server.location_id = Location.id WHERE Server.org_id = 1" => true, "SELECT Server JOIN Location ON Server.location_id = Location.id WHERE org_id = 1" => false, ]; foreach ($aQueries as $sQuery => $bSuccess) { echo "
To Parse: ".htmlentities($sQuery, ENT_QUOTES, 'UTF-8')."
\n"; try { $oSearch = DBSearch::FromOQL($sQuery); if (!$bSuccess) { throw new Exception('This query should not be parsable!'); } CMDBSource::TestQuery($oSearch->MakeSelectQuery()); echo "

Successfully tested the SQL query.

\n"; } catch (OQLException $e) { if ($bSuccess) { throw $e; } echo "

Failed as expected.

\n"; } } } } class TestBug609 extends TestBizModel { public static function GetName() { return 'UNION with JOINS ordered differently'; } public static function GetDescription() { return '(N.609) Inconsistent SQL query (various symptoms, must mostly in the form of "Class \'IT Department\' not found"'; } protected function DoExecute() { $sQueryA = 'SELECT t,o FROM Team AS t JOIN Organization AS o ON t.org_id = o.id'; $sQueryB = 'SELECT t,o FROM Organization AS o JOIN Team AS t ON t.org_id = o.id'; $oSearch = DBSearch::FromOQL("$sQueryB UNION $sQueryA"); $oSet = new DBObjectSet($oSearch); while ($oObject = $oSet->Fetch()) { echo "Successfull load for ".$oObject->GetName()."
\n"; } } } class TestBug788 extends TestBizModel { public static function GetName() { return 'Graph - delete nodes'; } public static function GetDescription() { return '(N.788) Graph not refreshed when unchecking some classes'; } protected function DoExecute() { $oGraph = new SimpleGraph(); $a = new GraphNode($oGraph, 'A'); $b = new GraphNode($oGraph, 'B'); $c = new GraphNode($oGraph, 'C'); new GraphEdge($oGraph, 'A--B', $a, $b); new GraphEdge($oGraph, 'B--C', $b, $c); new GraphEdge($oGraph, 'C--B', $c, $b); echo "
Graphe initial
"; echo $oGraph->DumpAsHtmlImage(); echo $oGraph->DumpAsHTMLText(); echo "
Removing C
"; $oGraph->FilterNode($c); unset($c); echo $oGraph->DumpAsHtmlImage(); echo $oGraph->DumpAsHTMLText(); if ((count($oGraph->_GetNodes()) != 2) || (count($oGraph->_GetEdges()) != 1)) { throw new Exception('The graph should be made of 2 nodes and 1 edge'); } echo "
Removing B
"; $oGraph->FilterNode($b); unset($b); echo $oGraph->DumpAsHtmlImage(); echo $oGraph->DumpAsHTMLText(); if ((count($oGraph->_GetNodes()) != 1) || (count($oGraph->_GetEdges()) != 0)) { throw new Exception('The graph should contain only the node A'); } } } class WhereIsThe61TablesThreat extends TestBizModel { public static function GetName() { return '61 tables'; } public static function GetDescription() { return 'Evaluate where is the 61 tables limit threat'; } protected function DoExecute() { $aClassToCount_Full = []; $aDistribution = []; $iTotalClasses = 0; foreach (MetaModel::GetClasses() as $sClass) { if (MetaModel::IsAbstract($sClass)) { continue; } $iTotalClasses++; $oSearch = DBSearch::FromOQL("SELECT $sClass WHERE id = 1"); $oSql = $oSearch->GetSQLQueryStructure([], false, null); $iCount = $oSql->CountTables(); $aClassToCount_Full[$sClass] = $iCount; if (array_key_exists($iCount, $aDistribution)) { $aDistribution[$iCount]++; } else { $aDistribution[$iCount] = 1; } } arsort($aClassToCount_Full); $iHighestCount = max($aClassToCount_Full); for ($i = 1; $i < $iHighestCount ; $i++) { if (!array_key_exists($i, $aDistribution)) { $aDistribution[$i] = 0; } } ksort($aDistribution); $i = 0; $iLimit = 15; $iCountThreshold = 10; echo "
TOP $iLimit offenders (+ those exceeding $iCountThreshold tables)
"; foreach ($aClassToCount_Full as $sClass => $iCountFull) { $i++; if (($iCountFull <= $iCountThreshold) && ($i >= $iLimit)) { break; } echo "$sClass: $iCountFull tables
"; } echo "
Distribution of table counts
"; echo "

Over a total of $iTotalClasses instantiable classes.

"; echo ""; echo ""; foreach ($aDistribution as $iTableCount => $iClassCount) { echo ""; } echo "
Table countClasses
$iTableCount$iClassCount
"; } } class TestBug689 extends TestBizModel { public static function GetName() { return 'An OQL failing to export in XML'; } public static function GetDescription() { return '(N.689) Reaching the limit of 61 tables'; } protected function DoExecute() { $sOql = 'SELECT child, parent, s1, p, o FROM UserRequest AS child JOIN UserRequest AS parent ON child.parent_request_id = parent.id JOIN lnkFunctionalCIToTicket AS l1 ON l1.ticket_id = child.id JOIN Server AS s1 ON l1.functionalci_id = s1.id JOIN Person AS p ON child.caller_id = p.id JOIN Organization AS o ON p.org_id = o.id'; $oSearch = DBSearch::FromOQL($sOql); $oSql = $oSearch->GetSQLQueryStructure([], false, null); //$sSql = $oSql->RenderSelect(); echo '

'.$sOql.'

'; echo '

This query rendered with all columns give a MySQL query having '.$oSql->CountTables().' tables... let\'s try it with the DBObjectSet API:

'; $oSet = new DBObjectSet($oSearch); $oObj = $oSet->Fetch(); echo '

Well done, this is working fine! Some magic happened in the background!

'; } } class TestDBObjectLinkedObjects extends TestBizModel { public static function GetName() { return 'DBObject Linked objects API'; } public static function GetDescription() { return 'Add/Remove/Modify linked objects (recorded as a delta within DBObject, later recorded in DB)'; } protected function DoExecute() { CMDBSource::Query('START TRANSACTION'); //CMDBSource::Query('ROLLBACK'); automatique ! //////////////////////////////////////////////////////////////////////////////// // Set the stage // $oTypes = new DBObjectSet(DBObjectSearch::FromOQL('SELECT NetworkDeviceType WHERE name = "Router"')); $oType = $oTypes->fetch(); $oDevice1 = MetaModel::NewObject('NetworkDevice'); $oDevice1->Set('name', 'test device 1'); $oDevice1->Set('org_id', 3); $oDevice1->Set('networkdevicetype_id', $oType->GetKey()); $oDevice1->DBInsert(); $iDev1 = $oDevice1->GetKey(); $oDevice2 = MetaModel::NewObject('NetworkDevice'); $oDevice2->Set('name', 'test device 2'); $oDevice2->Set('org_id', 3); $oDevice2->Set('networkdevicetype_id', $oType->GetKey()); $oDevice2->DBInsert(); $iDev2 = $oDevice2->GetKey(); $oServer = MetaModel::NewObject('Server'); $oServer->Set('name', 'unit test linkset'); $oServer->Set('org_id', 3); $oLinkSet = $oServer->Get('networkdevice_list'); $oLinkSet->AddItem(MetaModel::NewObject('lnkConnectableCIToNetworkDevice', ['networkdevice_id' => $iDev1])); $oServer->Set('networkdevice_list', $oLinkSet); assert($oServer->IsModified(), 'Server is modified'); $oServer->DBInsert(); $iServer = $oServer->GetKey(); $oServer = MetaModel::GetObject('Server', $iServer); $oLinkSet = $oServer->Get('networkdevice_list'); assert($oLinkSet->Count() == 1, 'One NW Dev attached'); $oLink = $oLinkSet->Fetch(); assert($oLink->Get('networkdevice_id') == $iDev1, 'New device correctly attached'); $oLinkSet = $oServer->Get('networkdevice_list'); $oLinkSet->AddItem(MetaModel::NewObject('lnkConnectableCIToNetworkDevice', ['networkdevice_id' => $iDev2])); $oServer->Set('networkdevice_list', $oLinkSet); assert($oServer->IsModified(), 'Server is modified'); $oServer->DBUpdate(); $oServer = MetaModel::GetObject('Server', $iServer); $oLinkSet = $oServer->Get('networkdevice_list'); assert($oLinkSet->Count() == 2, 'Two NW Dev attached'); $oNewLinkSet = clone $oLinkSet; while ($oLink = $oLinkSet->Fetch()) { $iLinkId = $oLink->Get('networkdevice_id'); if ($iLinkId == $iDev1) { $oNewLinkSet->RemoveItem($oLink->GetKey()); } elseif ($iLinkId == $iDev2) { $oLink->Set('network_port', 'lePortSalut'); $oNewLinkSet->ModifyItem($oLink); } } $oServer->Set('networkdevice_list', $oNewLinkSet); assert($oServer->IsModified(), 'Server is modified'); $oServer->DBUpdate(); $oServer = MetaModel::GetObject('Server', $iServer); $oLinkSet = $oServer->Get('networkdevice_list'); assert($oLinkSet->Count() == 1, 'One NW Dev attached'); $oLink = $oLinkSet->Fetch(); assert($oLink->Get('networkdevice_id') == $iDev2, 'Dev2 remained attached'); assert($oLink->Get('network_port') == 'lePortSalut', 'Port has been changed'); } } class TestDBObjectLinkedObjectsLegacy extends TestBizModel { public static function GetName() { return 'DBObject Linked objects API (legacy usage)'; } public static function GetDescription() { return 'Alter a link set by redefining the whole list of links (not recommended!)'; } protected function DoExecute() { CMDBSource::Query('START TRANSACTION'); //CMDBSource::Query('ROLLBACK'); automatique ! //////////////////////////////////////////////////////////////////////////////// // Set the stage // $oTypes = new DBObjectSet(DBObjectSearch::FromOQL('SELECT NetworkDeviceType WHERE name = "Router"')); $oType = $oTypes->fetch(); $oDevice1 = MetaModel::NewObject('NetworkDevice'); $oDevice1->Set('name', 'test device 1'); $oDevice1->Set('org_id', 3); $oDevice1->Set('networkdevicetype_id', $oType->GetKey()); $oDevice1->DBInsert(); $iDev1 = $oDevice1->GetKey(); $oDevice2 = MetaModel::NewObject('NetworkDevice'); $oDevice2->Set('name', 'test device 2'); $oDevice2->Set('org_id', 3); $oDevice2->Set('networkdevicetype_id', $oType->GetKey()); $oDevice2->DBInsert(); $iDev2 = $oDevice2->GetKey(); $oServer = MetaModel::NewObject('Server'); $oServer->Set('name', 'unit test linkset'); $oServer->Set('org_id', 3); $oLinkSet = $oServer->Get('networkdevice_list'); $oNewLinkSet = DBObjectSet::FromScratch('lnkConnectableCIToNetworkDevice'); while ($oLink = $oLinkSet->Fetch()) { $oNewLinkSet->AddObject($oLink); } $oNewLinkSet->AddObject(MetaModel::NewObject('lnkConnectableCIToNetworkDevice', ['networkdevice_id' => $iDev1])); $oServer->Set('networkdevice_list', $oNewLinkSet); assert($oServer->IsModified(), 'Server is modified'); $oServer->DBInsert(); $iServer = $oServer->GetKey(); $oServer = MetaModel::GetObject('Server', $iServer); $oLinkSet = $oServer->Get('networkdevice_list'); assert($oLinkSet->Count() == 1, 'One NW Dev attached'); $oLink = $oLinkSet->Fetch(); assert($oLink->Get('networkdevice_id') == $iDev1, 'New device correctly attached'); $oNewLinkSet = DBObjectSet::FromScratch('lnkConnectableCIToNetworkDevice'); $oLinkSet->Rewind(); while ($oLink = $oLinkSet->Fetch()) { $oNewLinkSet->AddObject($oLink); } $oNewLinkSet->AddObject(MetaModel::NewObject('lnkConnectableCIToNetworkDevice', ['networkdevice_id' => $iDev2])); $oServer->Set('networkdevice_list', $oNewLinkSet); assert($oServer->IsModified(), 'Server is modified'); $oServer->DBUpdate(); $oServer = MetaModel::GetObject('Server', $iServer); $oLinkSet = $oServer->Get('networkdevice_list'); assert($oLinkSet->Count() == 2, 'Two NW Dev attached'); $oNewLinkSet = DBObjectSet::FromScratch('lnkConnectableCIToNetworkDevice'); $oServer->Set('networkdevice_list', $oNewLinkSet); while ($oLink = $oLinkSet->Fetch()) { $iLinkId = $oLink->Get('networkdevice_id'); if ($iLinkId == $iDev1) { // Remove...ie do not add it! } elseif ($iLinkId == $iDev2) { $oLink->Set('network_port', 'lePortSalut'); $oNewLinkSet->AddObject($oLink); } else { $oNewLinkSet->AddObject($oLink); } } $oServer->Set('networkdevice_list', $oNewLinkSet); assert($oServer->IsModified(), 'Server is modified'); $oServer->DBUpdate(); $oServer = MetaModel::GetObject('Server', $iServer); $oLinkSet = $oServer->Get('networkdevice_list'); assert($oLinkSet->Count() == 1, 'One NW Dev attached'); $oLink = $oLinkSet->Fetch(); assert($oLink->Get('networkdevice_id') == $iDev2, 'Dev2 remained attached'); assert($oLink->Get('network_port') == 'lePortSalut', 'Port has been changed'); } }