diff --git a/test/testlist.inc.php b/test/testlist.inc.php
index 7ad797ddd..c31ab904c 100644
--- a/test/testlist.inc.php
+++ b/test/testlist.inc.php
@@ -24,6 +24,43 @@
*/
+class TestBeHappy extends TestHandler // TestFunctionInOut, TestBizModel...
+{
+ static public function GetName()
+ {
+ return 'Be happy!';
+ }
+
+ static public function GetDescription()
+ {
+ return 'Sample test with success';
+ }
+
+ protected function DoExecute()
+ {
+ echo "
Am I happy?
";
+ echo "Yes, I am!
";
+ }
+}
+class TestBeSad extends TestHandler
+{
+ static public function GetName()
+ {
+ return 'Be sad...';
+ }
+
+ static public function GetDescription()
+ {
+ return 'Sample test with failure';
+ }
+
+ protected function DoExecute()
+ {
+ echo "Am I happy?";
+ throw new Exception('jamais content');
+ }
+}
+
class TestSQLQuery extends TestScenarioOnDB
{
static public function GetName() {return 'SQLQuery';}
@@ -3560,4 +3597,589 @@ class TestEmailAsynchronous extends TestBizModel
}
}
-?>
+class TestLinkSetRecording_NN_WithDuplicates extends TestBizModel
+{
+ static public function GetName()
+ {
+ return 'Linkset N-N having duplicated allowed (Connectable CI to Network Device)';
+ }
+
+ static public 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 = array(
+ array(
+ 'description' => 'Add the first item',
+ 'links' => array(
+ array(
+ 'networkdevice_id' => $iDev1,
+ 'connectableci_id' => $iServer,
+ 'network_port' => '',
+ 'device_port' => '',
+ ),
+ ),
+ 'expected-res' => array (
+ "$iDev1, test device A, unit test linkset, , , downlink, test device A",
+ ),
+ 'history_added' => 1,
+ 'history_removed' => 0,
+ 'history_modified' => 0,
+ ),
+ array(
+ 'description' => 'Modify the unique item',
+ 'links' => array(
+ array(
+ 'networkdevice_id' => $iDev1,
+ 'connectableci_id' => $iServer,
+ 'network_port' => 'devTagada',
+ 'device_port' => '',
+ ),
+ ),
+ 'expected-res' => array (
+ "$iDev1, test device A, unit test linkset, devTagada, , downlink, test device A",
+ ),
+ 'history_added' => 1,
+ 'history_removed' => 1,
+ 'history_modified' => 0,
+ ),
+ array(
+ 'description' => 'Modify again the original item and add a second item',
+ 'links' => array(
+ array(
+ 'networkdevice_id' => $iDev1,
+ 'connectableci_id' => $iServer,
+ 'network_port' => '',
+ 'device_port' => '',
+ ),
+ array(
+ 'networkdevice_id' => $iDev2,
+ 'connectableci_id' => $iServer,
+ 'network_port' => '',
+ 'device_port' => '',
+ ),
+ ),
+ 'expected-res' => array (
+ "$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,
+ ),
+ array(
+ 'description' => 'No change, the links are added in the reverse order',
+ 'links' => array(
+ array(
+ 'networkdevice_id' => $iDev2,
+ 'connectableci_id' => $iServer,
+ 'network_port' => '',
+ 'device_port' => '',
+ ),
+ array(
+ 'networkdevice_id' => $iDev1,
+ 'connectableci_id' => $iServer,
+ 'network_port' => '',
+ 'device_port' => '',
+ ),
+ ),
+ 'expected-res' => array (
+ "$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,
+ ),
+ array(
+ 'description' => 'Removing A',
+ 'links' => array(
+ array(
+ 'networkdevice_id' => $iDev2,
+ 'connectableci_id' => $iServer,
+ 'network_port' => '',
+ 'device_port' => '',
+ ),
+ ),
+ 'expected-res' => array (
+ "$iDev2, test device B, unit test linkset, , , downlink, test device B",
+ ),
+ 'history_added' => 0,
+ 'history_removed' => 1,
+ 'history_modified' => 0,
+ ),
+ array(
+ 'description' => 'Adding B again (duplicate!)',
+ 'links' => array(
+ array(
+ 'networkdevice_id' => $iDev2,
+ 'connectableci_id' => $iServer,
+ 'network_port' => 'port_123',
+ 'device_port' => '',
+ ),
+ array(
+ 'networkdevice_id' => $iDev2,
+ 'connectableci_id' => $iServer,
+ 'network_port' => 'port_456',
+ 'device_port' => '',
+ ),
+ ),
+ 'expected-res' => array (
+ "$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,
+ ),
+ array(
+ 'description' => 'Remove all',
+ 'links' => array(
+ ),
+ 'expected-res' => array (
+ ),
+ 'history_added' => 0,
+ 'history_removed' => 2,
+ 'history_modified' => 0,
+ ),
+ array(
+ 'description' => 'Device B twice (same characteristics) - known issue #1145',
+ 'links' => array(
+ array(
+ 'networkdevice_id' => $iDev2,
+ 'connectableci_id' => $iServer,
+ 'network_port' => '',
+ 'device_port' => '',
+ ),
+ array(
+ 'networkdevice_id' => $iDev2,
+ 'connectableci_id' => $iServer,
+ 'network_port' => '',
+ 'device_port' => '',
+ ),
+ ),
+ 'expected-res' => array (
+ "$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)
+ {
+ $oLink1 = MetaModel::NewObject('lnkConnectableCIToNetworkDevice');
+ foreach ($aLinkData as $sAttCode => $value)
+ {
+ $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 = array('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'"), array(), $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'"), array(), $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"), array(), $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');
+ }
+ }
+ }
+
+ protected function StandardizedDump($oSet, $sAttPrefixToIgnore)
+ {
+ if (!$oSet->m_bLoaded) $oSet->Load();
+ $oSet->Rewind();
+
+ $aRet = array();
+ while($oObject = $oSet->Fetch())
+ {
+ $aValues = array();
+ foreach(MetaModel::ListAttributeDefs(get_class($oObject)) as $sAttCode => $oAttDef)
+ {
+ if ($sAttCode == 'friendlyname') 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_NoDuplicates extends TestBizModel
+{
+ static public function GetName()
+ {
+ return 'Linksets N-N in general (99% of them)';
+ }
+
+ static public 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();
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Scenarii
+ //
+ $aScenarii = array(
+ array(
+ 'description' => 'Add the first item',
+ 'links' => array(
+ array(
+ 'person_id' => $iPerson1,
+ 'team_id' => $iTeam,
+ 'role_id' => 0,
+ ),
+ ),
+ 'expected-res' => array (
+ "unit test linkset, $iPerson1, test person A, 0, , totoche test person A, ",
+ ),
+ 'history_added' => 1,
+ 'history_removed' => 0,
+ 'history_modified' => 0,
+ ),
+ array(
+ 'description' => 'Modify the unique item',
+ 'links' => array(
+ array(
+ 'person_id' => $iPerson1,
+ 'team_id' => $iTeam,
+ 'role_id' => $iRole,
+ ),
+ ),
+ 'expected-res' => array (
+ "unit test linkset, $iPerson1, test person A, $iRole, Manager, totoche test person A, ",
+ ),
+ 'history_added' => 0,
+ 'history_removed' => 0,
+ 'history_modified' => 1,
+ ),
+ array(
+ 'description' => 'Modify again the original item and add a second item',
+ 'links' => array(
+ array(
+ 'person_id' => $iPerson1,
+ 'team_id' => $iTeam,
+ 'role_id' => 0,
+ ),
+ array(
+ 'person_id' => $iPerson2,
+ 'team_id' => $iTeam,
+ 'role_id' => 0,
+ ),
+ ),
+ 'expected-res' => array (
+ "unit test linkset, $iPerson1, test person A, 0, , totoche test person A, ",
+ "unit test linkset, $iPerson2, test person A, 0, , totoche test person A, ",
+ ),
+ 'history_added' => 1,
+ 'history_removed' => 0,
+ 'history_modified' => 1,
+ ),
+ array(
+ 'description' => 'No change, the links are added in the reverse order',
+ 'links' => array(
+ array(
+ 'person_id' => $iPerson2,
+ 'team_id' => $iTeam,
+ 'role_id' => 0,
+ ),
+ array(
+ 'person_id' => $iPerson1,
+ 'team_id' => $iTeam,
+ 'role_id' => 0,
+ ),
+ ),
+ 'expected-res' => array (
+ "unit test linkset, $iPerson1, test person A, 0, , totoche test person A, ",
+ "unit test linkset, $iPerson2, test person A, 0, , totoche test person A, ",
+ ),
+ 'history_added' => 0,
+ 'history_removed' => 0,
+ 'history_modified' => 0,
+ ),
+ array(
+ 'description' => 'Removing A',
+ 'links' => array(
+ array(
+ 'person_id' => $iPerson2,
+ 'team_id' => $iTeam,
+ 'role_id' => 0,
+ ),
+ ),
+ 'expected-res' => array (
+ "unit test linkset, $iPerson2, test person A, 0, , totoche test person A, ",
+ ),
+ 'history_added' => 0,
+ 'history_removed' => 1,
+ 'history_modified' => 0,
+ ),
+ array(
+ 'description' => 'Adding B again (duplicate!)',
+ 'links' => array(
+ array(
+ 'person_id' => $iPerson2,
+ 'team_id' => $iTeam,
+ 'role_id' => 0,
+ ),
+ array(
+ 'person_id' => $iPerson2,
+ 'team_id' => $iTeam,
+ 'role_id' => 0,
+ ),
+ ),
+ 'expected-res' => array (
+ "unit test linkset, $iPerson2, test person A, 0, , totoche test person A, ",
+ ),
+ 'history_added' => 0,
+ 'history_removed' => 0,
+ 'history_modified' => 0,
+ ),
+ array(
+ 'description' => 'Remove all',
+ 'links' => array(
+ ),
+ 'expected-res' => array (
+ ),
+ 'history_added' => 0,
+ 'history_removed' => 1,
+ '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('lnkPersonToTeam');
+ foreach ($aScenario['links'] as $aLinkData)
+ {
+ $oLink1 = MetaModel::NewObject('lnkPersonToTeam');
+ foreach ($aLinkData as $sAttCode => $value)
+ {
+ $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 = array('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'"), array(), $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'"), array(), $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"), array(), $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');
+ }
+ }
+ }
+
+ protected function StandardizedDump($oSet, $sAttPrefixToIgnore)
+ {
+ if (!$oSet->m_bLoaded) $oSet->Load();
+ $oSet->Rewind();
+
+ $aRet = array();
+ while($oObject = $oSet->Fetch())
+ {
+ $aValues = array();
+ foreach(MetaModel::ListAttributeDefs(get_class($oObject)) as $sAttCode => $oAttDef)
+ {
+ if ($sAttCode == 'friendlyname') continue;
+ if (substr($sAttCode, 0, strlen($sAttPrefixToIgnore)) == $sAttPrefixToIgnore) continue;
+ if ($oAttDef->IsScalar())
+ {
+ $aValues[] = $oObject->Get($sAttCode);
+ }
+ }
+ $aRet[] = implode(', ', $aValues);
+ }
+ sort($aRet);
+ return $aRet;
+ }
+}