diff --git a/application/utils.inc.php b/application/utils.inc.php index fda3d864d..012645ef1 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -999,19 +999,19 @@ class utils throw new Exception("The path to php must not be empty. Please set a value for 'php_path' in your configuration file."); } - $sAuthUser = self::ReadParam('auth_user', '', 'raw_data'); - $sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data'); - $sParamFile = self::GetParamSourceFile('auth_user'); - if (is_null($sParamFile)) - { + if (!isset($aArguments['auth_user'])) { + $sAuthUser = self::ReadParam('auth_user', '', 'raw_data'); $aArguments['auth_user'] = $sAuthUser; + } + if (!isset($aArguments['auth_pwd'])) { + $sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data'); $aArguments['auth_pwd'] = $sAuthPwd; } - else - { + if (!isset($aArguments['param_file'])) { + $sParamFile = self::ReadParam('param_file', '', 'raw_data'); $aArguments['param_file'] = $sParamFile; } - + $aArgs = array(); foreach($aArguments as $sName => $value) { diff --git a/synchro/synchro_import.php b/synchro/synchro_import.php index 5c08f57ba..2c0e658cf 100644 --- a/synchro/synchro_import.php +++ b/synchro/synchro_import.php @@ -534,11 +534,7 @@ try $aValues = array(); // Used to build the insert query foreach ($aRow as $iCol => $value) { - if ($value === null) // Source CSV: "" - { - $aValues[] = null; - } - elseif ($aIsDateToTransform[$iCol] !== false) + if ($aIsDateToTransform[$iCol] !== false) { $bDateOnly = false; $sFormat = $sDateTimeFormat; @@ -550,7 +546,7 @@ try $sDate = ChangeDateFormat($value, $sFormat, $bDateOnly); if ($sDate === false) { - $aValues[] = CMDBSource::Quote(''); + $aValues[] = ''; if ($sOutput === 'details') { $oP->add("$iRow: Wrong format for {$aIsDateToTransform[$iCol]} column $iCol: '$value' does not match the expected format: '$sFormat' (column skipped)\n"); @@ -558,15 +554,15 @@ try } else { - $aValues[] = CMDBSource::Quote($sDate); + $aValues[] = $sDate; } } else { - $aValues[] = CMDBSource::Quote($value); + $aValues[] = $value; } } - $sValues = implode(', ', $aValues); + $sValues = implode(', ', CMDBSource::Quote($aValues)); $sInsert = "INSERT INTO `$sTable` ($sInsertColumns) VALUES ($sValues)"; try { diff --git a/test/synchro/DataSynchroTest.php b/test/synchro/DataSynchroTest.php new file mode 100644 index 000000000..4f5639101 --- /dev/null +++ b/test/synchro/DataSynchroTest.php @@ -0,0 +1,568 @@ +Count() == 0) + { + $oProfileSearch = DBSearch::FromOQL('SELECT URP_Profiles WHERE name LIKE "administrator"'); + $oProfileSet = new DBObjectSet($oProfileSearch); + $oAdminProfile = $oProfileSet->fetch(); + + $oUser = MetaModel::NewObject('UserLocal', array( + 'login' => static::AUTH_USER, + 'password' => static::AUTH_PWD, + 'expiration' => UserLocal::EXPIRE_NEVER, + )); + $oProfiles = $oUser->Get('profile_list'); + $oProfiles->AddItem(MetaModel::NewObject('URP_UserProfile', array( + 'profileid' => $oAdminProfile->GetKey() + ))); + $oUser->Set('profile_list', $oProfiles); + $oUser->DBInsertNoReload(); + } + } + + protected function ExecSynchroImport($aParams) + { + $aParams['auth_user'] = static::AUTH_USER; + $aParams['auth_pwd'] = static::AUTH_PWD; + return utils::ExecITopScript('synchro/synchro_import.php', $aParams); + } + + /** + * Run a series of data synchronization through the REST API + * + * @dataProvider SynchroScenariosProvider + * + * @param $sDescription + * @param $sTargetClass + * @param $aSourceProperties + * @param $aSourceData + * @param $aTargetData + * @param $aAttributes + * + * @throws \ArchivedObjectException + * @throws \CoreCannotSaveObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \CoreWarning + * @throws \DictExceptionMissingString + * @throws \MySQLException + * @throws \OQLException + * @throws \Exception + */ + public function testSynchroImportPage($sDescription, $sTargetClass, $aSourceProperties, $aSourceData, $aTargetData, $aAttributes) + { + $sClass = $sTargetClass; + + $aTargetAttributes = array_shift($aTargetData); + $aSourceAttributes = array_shift($aSourceData); + + if (count($aSourceData) + 1 != count($aTargetData)) + { + throw new Exception("Target data must contain exactly ".(count($aSourceData) + 1)." items, found ".count($aTargetData)); + } + + // Create the data source + // + $oDataSource = new SynchroDataSource(); + $oDataSource->Set('name', 'Test data sync '.time()); + $oDataSource->Set('description', 'unit test - created automatically'); + $oDataSource->Set('status', 'production'); + $oDataSource->Set('user_id', 0); + $oDataSource->Set('scope_class', $sClass); + foreach ($aSourceProperties as $sProperty => $value) + { + $oDataSource->Set($sProperty, $value); + } + $iDataSourceId = $oDataSource->DBInsert(); + + $oAttributeSet = $oDataSource->Get('attribute_list'); + while ($oAttribute = $oAttributeSet->Fetch()) + { + if (array_key_exists($oAttribute->Get('attcode'), $aAttributes)) + { + $aAttribInfo = $aAttributes[$oAttribute->Get('attcode')]; + if (array_key_exists('reconciliation_attcode', $aAttribInfo)) + { + $oAttribute->Set('reconciliation_attcode', $aAttribInfo['reconciliation_attcode']); + } + $oAttribute->Set('update', $aAttribInfo['do_update']); + $oAttribute->Set('reconcile', $aAttribInfo['do_reconcile']); + } + else + { + $oAttribute->Set('update', false); + $oAttribute->Set('reconcile', false); + } + $oAttribute->DBUpdate(); + } + + // Prepare list of prefixes -> make sure objects are unique with regard to the reconciliation scheme + $aPrefixes = array(); // attcode => prefix + foreach($aSourceAttributes as $iDummy => $sAttCode) + { + $aPrefixes[$sAttCode] = ''; // init with something + } + foreach($aAttributes as $sAttCode => $aAttribInfo) + { + if (isset($aAttribInfo['automatic_prefix']) && $aAttribInfo['automatic_prefix']) + { + $aPrefixes[$sAttCode] = 'TEST_'.$iDataSourceId.'_'; + } + } + + // List existing objects (to be ignored in the analysis + // + $oAllObjects = new DBObjectSet(new DBObjectSearch($sClass)); + $aExisting = $oAllObjects->ToArray(true); + $sExistingIds = implode(', ', array_keys($aExisting)); + + // Create the initial object list + // + $aInitialTarget = $aTargetData[0]; + foreach($aInitialTarget as $aObjFields) + { + $oNewTarget = MetaModel::NewObject($sClass); + foreach($aTargetAttributes as $iAtt => $sAttCode) + { + $oNewTarget->Set($sAttCode, $aPrefixes[$sAttCode].$aObjFields[$iAtt]); + } + $oNewTarget->DBInsertNoReload(); + } + + foreach($aTargetData as $iRow => $aExpectedObjects) + { + // Check the status (while ignoring existing objects) + // + if (empty($sExistingIds)) + { + $oObjects = new DBObjectSet(DBObjectSearch::FromOQL("SELECT $sClass")); + } + else + { + $oObjects = new DBObjectSet(DBObjectSearch::FromOQL("SELECT $sClass WHERE id NOT IN($sExistingIds)")); + } + $aFound = $oObjects->ToArray(); + $aErrors_Unexpected = array(); + foreach($aFound as $iObj => $oObj) + { + // Is this object in the expected objects list + $bFoundMatch = false; + foreach($aExpectedObjects as $iExp => $aValues) + { + $bDoesMatch = true; + foreach($aTargetAttributes as $iCol => $sAttCode) + { + if ($oObj->Get($sAttCode) != $aPrefixes[$sAttCode].$aValues[$iCol]) + { + $bDoesMatch = false; + break; + } + } + if ($bDoesMatch) + { + $bFoundMatch = true; + unset($aExpectedObjects[$iExp]); + break; + } + } + if (!$bFoundMatch) + { + $aObjDesc = array(); + foreach($aTargetAttributes as $iCol => $sAttCode) + { + $aObjDesc[$sAttCode] = $oObj->Get($sAttCode); + } + $aErrors_Unexpected[get_class($oObj).'::'.$oObj->GetKey()] = $aObjDesc; + } + } + + // Display the current status + // + $aErrors = array(); + if (count($aErrors_Unexpected) > 0) { + $aErrors[] = "Unexpected objects found in iTop DB after step $iRow (starting at 0):\n".print_r($aErrors_Unexpected, true); + } + if (count($aExpectedObjects) > 0) { + $aErrors[] = "Expected objects NOT found in iTop DB after step $iRow (starting at 0)\n".print_r($aExpectedObjects, true); + } + if (count($aErrors) > 0) { + static::fail(implode("\n", $aErrors)); + } + else { + static::assertTrue(true); + } + + // If not on the final row, run a data exchange sequence + // + if (array_key_exists($iRow, $aSourceData)) + { + $aToBeLoaded = $aSourceData[$iRow]; + + // First line + $sCsvData = implode(';', $aSourceAttributes)."\n"; + + $sTextQualifier = '"'; + + foreach($aToBeLoaded as $aDataRow) + { + $aFinalData = array(); + foreach($aDataRow as $iCol => $value) + { + $sAttCode = $aSourceAttributes[$iCol]; + $sRawValue = $aPrefixes[$sAttCode].$value; + + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sCSVValue = $sTextQualifier.str_replace($sFrom, $sTo, (string)$sRawValue).$sTextQualifier; + + $aFinalData[] = $sCSVValue; + } + $sCsvData .= implode(';', $aFinalData)."\n"; + } + $sCSVTmpFile = tempnam(sys_get_temp_dir(), "CSV"); + file_put_contents($sCSVTmpFile, $sCsvData); + + $aParams = array( + 'csvfile' => $sCSVTmpFile, + 'data_source_id' => $iDataSourceId, + 'separator' => ';', + 'simulate' => 0, + 'output' => 'details', + ); + list($iRetCode, $aOutputLines) = static::ExecSynchroImport($aParams); + + unlink($sCSVTmpFile); + + // Report the load results + // + 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 "Input Data:\n"; + echo $sCsvDataViewable; + echo "\n"; + + $sResultsViewable = '| '.implode("\n| ", $aOutputLines); + + echo "Results:\n"; + echo $sResultsViewable; + echo "\n"; + + if ($iRetCode != 0) + { + static::fail("Execution of synchro_import failing with code '$iRetCode', see error.log for more details"); + } + + if (stripos($sResultsViewable, 'exception') !== false) + { + self::fail('Encountered an Exception during the last import/synchro'); + } + } + } + } + + public function SynchroScenariosProvider() + { + $aTestCases = array(); + $aTestCases['Load user logins'] = array( + 'desc' => 'Load user logins', + 'target_class' => 'UserLocal', + 'source_properties' => array( + 'full_load_periodicity' => 3600, // should be ignored in this case + 'reconciliation_policy' => 'use_attributes', + 'action_on_zero' => 'create', + 'action_on_one' => 'update', + 'action_on_multiple' => 'error', + 'delete_policy' => 'delete', + 'delete_policy_update' => '', + 'delete_policy_retention' => 0, + ), + 'source_data' => array( + array('primary_key', 'login', 'password', 'profile_list'), + array( + array('user_A', 'login_A', 'password_A', 'profileid:10;reason:he/she is managing services'), + ), + ), + 'target_data' => array( + array('login'), + array( + // Initial state + ), + array( + array('login_A'), + ), + ), + 'attributes' => array( + 'login' => array( + 'do_reconcile' => true, + 'do_update' => true, + 'automatic_prefix' => true, // unique id (for unit testing) + ), + 'password' => array( + 'do_reconcile' => false, + 'do_update' => true, + ), + 'profile_list' => array( + 'do_reconcile' => false, + 'do_update' => true, + ), + ) + ); + $aTestCases['Simple scenario with delete option (and extkey given as org/name)'] = array( + 'desc' => 'Simple scenario with delete option (and extkey given as org/name)', + 'target_class' => 'ApplicationSolution', + 'source_properties' => array( + 'full_load_periodicity' => 1, + 'reconciliation_policy' => 'use_attributes', + 'action_on_zero' => 'create', + 'action_on_one' => 'update', + 'action_on_multiple' => 'error', + 'delete_policy' => 'delete', + 'delete_policy_update' => '', + 'delete_policy_retention' => 0, + ), + 'source_data' => array( + array('primary_key', 'org_id', 'name', 'status'), + array( + array('obj_A', '', 'obj_A', 'active'), // org_id unchanged + array('obj_B', '_DUMMY_', 'obj_B', 'active'), // error, '_DUMMY_' unknown + array('obj_C', 'SOMECODE', 'obj_C', 'active'), + array('obj_D', 'SOMECODE', 'obj_D', 'active'), + array('obj_E', 'SOMECODE', 'obj_E', 'active'), + ), + array( + array('obj_D', 'SOMECODE', 'obj_D', 'inactive'), + array('obj_E', 'SOMECODE', 'obj_E', ''), + ), + ), + 'target_data' => array( + array('org_id', 'name', 'status'), + array( + // Initial state + array(2, 'obj_A', 'active'), + array(2, 'obj_B', 'active'), + ), + array( + array(2, 'obj_A', 'active'), + array(2, 'obj_B', 'active'), + array(1, 'obj_C', 'active'), + array(1, 'obj_D', 'active'), + array(1, 'obj_E', 'active'), + ), + array( + array(2, 'obj_A', 'active'), + array(2, 'obj_B', 'active'), + array(1, 'obj_D', 'inactive'), + array(1, 'obj_E', 'active'), + ), + ), + 'attributes' => array( + 'org_id' => array( + 'do_reconcile' => false, + 'do_update' => true, + 'reconciliation_attcode' => 'code', + ), + 'name' => array( + 'do_reconcile' => true, + 'do_update' => true, + 'automatic_prefix' => true, // unique id + ), + 'status' => array( + 'do_reconcile' => false, + 'do_update' => true, + ), + ), + ); + $aTestCases['Update then delete with retention (to complete with manual testing) and reconciliation on org/name'] = array( + 'desc' => 'Update then delete with retention (to complete with manual testing) and reconciliation on org/name', + 'target_class' => 'ApplicationSolution', + 'source_properties' => array( + 'full_load_periodicity' => 0, + 'reconciliation_policy' => 'use_attributes', + 'action_on_zero' => 'create', + 'action_on_one' => 'update', + 'action_on_multiple' => 'error', + 'delete_policy' => 'update_then_delete', + 'delete_policy_update' => 'status:inactive', + 'delete_policy_retention' => 15, + ), + 'source_data' => array( + array('primary_key', 'org_id', 'name', 'status'), + array( + array('obj_A', 'Demo', 'obj_A', 'active'), + ), + array( + ), + ), + 'target_data' => array( + array('org_id', 'name', 'status'), + array( + // Initial state + ), + array( + array(3, 'obj_A', 'active'), + ), + array( + array(3, 'obj_A', 'inactive'), + // deleted ! + ), + ), + 'attributes' => array( + 'org_id' => array( + 'do_reconcile' => true, + 'do_update' => true, + 'reconciliation_attcode' => 'name', + ), + 'name' => array( + 'do_reconcile' => true, + 'do_update' => true, + 'automatic_prefix' => true, // unique id + ), + 'status' => array( + 'do_reconcile' => false, + 'do_update' => true, + ), + ), + ); + $aTestCases['Simple scenario loading a few ApplicationSolution'] = array( + 'desc' => 'Simple scenario loading a few ApplicationSolution', + 'target_class' => 'ApplicationSolution', + 'source_properties' => array( + 'full_load_periodicity' => 0, + 'reconciliation_policy' => 'use_attributes', + 'action_on_zero' => 'create', + 'action_on_one' => 'update', + 'action_on_multiple' => 'error', + 'delete_policy' => 'update', + 'delete_policy_update' => 'status:inactive', + 'delete_policy_retention' => 0, + + ), + 'source_data' => array( + array('primary_key', 'org_id', 'name', 'status'), + array( + array('obj_A', 2, 'obj_A', 'active'), + array('obj_B', 2, 'obj_B', 'inactive'), + array('obj_C', 2, 'obj_C', 'inactive'), + ), + array( + array('obj_A', 2, 'obj_A', 'active'), + array('obj_B', 2, 'obj_B', 'inactive'), + array('obj_C', 2, 'obj_C', 'inactive'), + ), + array( + array('obj_A', 2, 'obj_A', 'active'), + array('obj_C', 2, 'obj_C', 'inactive'), + array('obj_D', 2, 'obj_D', 'inactive'), + ), + array( + array('obj_C', 2, 'obj_C', 'active'), + ), + array( + array('obj_C', 2, 'obj_C', 'active'), + ), + ), + 'target_data' => array( + array('org_id', 'name', 'status'), + array( + // Initial state + array(2, 'obj_A', 'inactive'), + array(2, 'obj_B', 'active'), + array(2, 'obj_B', 'inactive'), + ), + array( + array(2, 'obj_A', 'active'), + array(2, 'obj_B', 'active'), + array(2, 'obj_B', 'inactive'), + array(2, 'obj_C', 'inactive'), + ), + array( + array(2, 'obj_A', 'active'), + array(2, 'obj_B', 'active'), + array(2, 'obj_B', 'inactive'), + array(2, 'obj_C', 'inactive'), + ), + array( + array(2, 'obj_A', 'active'), + array(2, 'obj_B', 'active'), + array(2, 'obj_B', 'inactive'), + array(2, 'obj_C', 'inactive'), + array(2, 'obj_D', 'inactive'), + ), + array( + array(2, 'obj_A', 'inactive'), + array(2, 'obj_B', 'active'), + array(2, 'obj_B', 'inactive'), + array(2, 'obj_C', 'active'), + array(2, 'obj_D', 'inactive'), + ), + array( + array(2, 'obj_A', 'inactive'), + array(2, 'obj_B', 'active'), + array(2, 'obj_B', 'inactive'), + array(2, 'obj_C', 'active'), + array(2, 'obj_D', 'inactive'), + ), + ), + 'attributes' => array( + 'org_id' => array( + 'do_reconcile' => false, + 'do_update' => true, + ), + 'name' => array( + 'do_reconcile' => true, + 'do_update' => true, + 'automatic_prefix' => true, // unique id + ), + 'status' => array( + 'do_reconcile' => false, + 'do_update' => true, + ), + ), + ); + return $aTestCases; + } +} diff --git a/test/testlist.inc.php b/test/testlist.inc.php index fd53907fe..6ee28106c 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -1414,546 +1414,6 @@ class TestImportRESTMassive extends TestImportREST } } -/////////////////////////////////////////////////////////////////////////// -// Test data exchange -/////////////////////////////////////////////////////////////////////////// - -class TestDataExchange extends TestBizModel -{ - static public function GetName() - { - return 'Data exchange'; - } - - static public function GetDescription() - { - return 'Test REST services: synchro_import and synchro_exec'; - } - - protected function DoExecScenario($aSingleScenario) - { - echo "
\n"; - echo "

{$aSingleScenario['desc']}

\n"; - - $sClass = $aSingleScenario['target_class']; - - $aTargetData = $aSingleScenario['target_data']; - $aSourceData = $aSingleScenario['source_data']; - - $aTargetAttributes = array_shift($aTargetData); - $aSourceAttributes = array_shift($aSourceData); - - if (count($aSourceData) + 1 != count($aTargetData)) - { - throw new Exception("Target data must contain exactly ".(count($aSourceData) + 1)." items, found ".count($aTargetData)); - } - - // Create the data source - // - $oDataSource = new SynchroDataSource(); - $oDataSource->Set('name', 'Test data sync '.time()); - $oDataSource->Set('description', 'unit test - created automatically'); - $oDataSource->Set('status', 'production'); - $oDataSource->Set('user_id', 0); - $oDataSource->Set('scope_class', $sClass); - $oDataSource->Set('scope_restriction', ''); - $oDataSource->Set('full_load_periodicity', $aSingleScenario['full_load_periodicity']); - $oDataSource->Set('reconciliation_policy', $aSingleScenario['reconciliation_policy']); - $oDataSource->Set('action_on_zero', $aSingleScenario['action_on_zero']); - $oDataSource->Set('action_on_one', $aSingleScenario['action_on_one']); - $oDataSource->Set('action_on_multiple', $aSingleScenario['action_on_multiple']); - $oDataSource->Set('delete_policy', $aSingleScenario['delete_policy']); - $oDataSource->Set('delete_policy_update', $aSingleScenario['delete_policy_update']); - $oDataSource->Set('delete_policy_retention', $aSingleScenario['delete_policy_retention']); - $iDataSourceId = $this->ObjectToDB($oDataSource, true /* reload */); - - $oAttributeSet = $oDataSource->Get('attribute_list'); - while ($oAttribute = $oAttributeSet->Fetch()) - { - if (array_key_exists($oAttribute->Get('attcode'), $aSingleScenario['attributes'])) - { - $aAttribInfo = $aSingleScenario['attributes'][$oAttribute->Get('attcode')]; - if (array_key_exists('reconciliation_attcode', $aAttribInfo)) - { - $oAttribute->Set('reconciliation_attcode', $aAttribInfo['reconciliation_attcode']); - } - $oAttribute->Set('update', $aAttribInfo['do_update']); - $oAttribute->Set('reconcile', $aAttribInfo['do_reconcile']); - } - else - { - $oAttribute->Set('update', false); - $oAttribute->Set('reconcile', false); - } - $this->UpdateObjectInDB($oAttribute); - } - - // Prepare list of prefixes -> make sure objects are unique with regard to the reconciliation scheme - $aPrefixes = array(); // attcode => prefix - foreach($aSourceAttributes as $iDummy => $sAttCode) - { - $aPrefixes[$sAttCode] = ''; // init with something - } - foreach($aSingleScenario['attributes'] as $sAttCode => $aAttribInfo) - { - if (isset($aAttribInfo['automatic_prefix']) && $aAttribInfo['automatic_prefix']) - { - $aPrefixes[$sAttCode] = 'TEST_'.$iDataSourceId.'_'; - } - } - - // List existing objects (to be ignored in the analysis - // - $oAllObjects = new DBObjectSet(new DBObjectSearch($sClass)); - $aExisting = $oAllObjects->ToArray(true); - $sExistingIds = implode(', ', array_keys($aExisting)); - - // Create the initial object list - // - $aInitialTarget = $aTargetData[0]; - foreach($aInitialTarget as $aObjFields) - { - $oNewTarget = MetaModel::NewObject($sClass); - foreach($aTargetAttributes as $iAtt => $sAttCode) - { - $oNewTarget->Set($sAttCode, $aPrefixes[$sAttCode].$aObjFields[$iAtt]); - } - $this->ObjectToDB($oNewTarget); - } - - foreach($aTargetData as $iRow => $aExpectedObjects) - { - sleep(2); - // Check the status (while ignoring existing objects) - // - if (empty($sExistingIds)) - { - $oObjects = new DBObjectSet(DBObjectSearch::FromOQL("SELECT $sClass")); - } - else - { - $oObjects = new DBObjectSet(DBObjectSearch::FromOQL("SELECT $sClass WHERE id NOT IN($sExistingIds)")); - } - $aFound = $oObjects->ToArray(); - $aErrors_Unexpected = array(); - foreach($aFound as $iObj => $oObj) - { - // Is this object in the expected objects list - $bFoundMatch = false; - foreach($aExpectedObjects as $iExp => $aValues) - { - $bDoesMatch = true; - foreach($aTargetAttributes as $iCol => $sAttCode) - { - if ($oObj->Get($sAttCode) != $aPrefixes[$sAttCode].$aValues[$iCol]) - { - $bDoesMatch = false; - break; - } - } - if ($bDoesMatch) - { - $bFoundMatch = true; - unset($aExpectedObjects[$iExp]); - break; - } - } - if (!$bFoundMatch) - { - $aErrors_Unexpected[] = $oObj->GetKey(); - } - } - - // Display the current status - // - echo "

Status at step $iRow

\n"; - $aCurrentDataSet = array(); - foreach($aFound as $iObj => $oObj) - { - $aObjDesc = array( - 'Status' => (in_array($iObj, $aErrors_Unexpected) ? 'unexpected' : 'ok'), - 'Object' => $oObj->GetHyperLink() - ); - foreach($aTargetAttributes as $iCol => $sAttCode) - { - $aObjDesc[$sAttCode] = $oObj->Get($sAttCode); - } - $aCurrentDataSet[] = $aObjDesc; - } - if (count($aExpectedObjects) > 0) - { - foreach($aExpectedObjects as $iExp => $aValues) - { - $aObjDesc = array( - 'Status' => 'missing', - 'Object' => 'n/a' - ); - foreach($aTargetAttributes as $iCol => $sAttCode) - { - $aObjDesc[$sAttCode] = $aPrefixes[$sAttCode].$aValues[$iCol]; - } - $aCurrentDataSet[] = $aObjDesc; - } - } - echo MyHelpers::make_table_from_assoc_array($aCurrentDataSet); - - if ((count($aErrors_Unexpected) > 0) || (count($aExpectedObjects) > 0)) - { - throw new UnitTestException("The current status in iTop does not match the expectations"); - } - - // If not on the final row, run a data exchange sequence - // - if (array_key_exists($iRow, $aSourceData)) - { - $aToBeLoaded = $aSourceData[$iRow]; - - $sCsvData = implode(';', $aSourceAttributes)."\n"; - foreach($aToBeLoaded as $aDataRow) - { - $aFinalData = array(); - foreach($aDataRow as $iCol => $value) - { - if (is_null($value)) - { - $aFinalData[] = ''; - } - else - { - $sAttCode = $aSourceAttributes[$iCol]; - $aFinalData[] = $aPrefixes[$sAttCode].$value; - } - } - $sCsvData .= implode(';', $aFinalData)."\n"; - } - $aPostData = array('csvdata' => $sCsvData); - - $aImportArgs = array( - 'data_source_id' => $iDataSourceId, - 'separator' => ';', - 'simulate' => 0, - 'output' => 'details', - ); - - $aGetParams = array(); - $aGetParamReport = array(); - foreach($aImportArgs as $sArg => $sValue) - { - $aGetParams[] = $sArg.'='.urlencode($sValue); - $aGetParamReport[] = $sArg.'='.$sValue; - } - $sGetParams = implode('&', $aGetParams); - $sLogin = isset($aSingleScenario['login']) ? $aSingleScenario['login'] : 'admin'; - $sPassword = isset($aSingleScenario['password']) ? $aSingleScenario['password'] : 'admin'; - - $sRes = self::DoPostRequestAuth('../synchro/synchro_import.php?'.$sGetParams, $aPostData, $sLogin, $sPassword); - - // Report the load results - // - 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; - } - $sCsvDataViewable = htmlentities($sCsvDataViewable, ENT_QUOTES, 'UTF-8'); - - echo "
\n"; - echo "
$sCsvDataViewable
\n"; - echo "
\n"; - - echo "
$sRes
\n"; - if (stripos($sRes, 'exception') !== false) - { - throw new UnitTestException('Encountered an Exception during the last import/synchro'); - } - } - } - return; - - echo "
\n"; - } - - protected function DoExecute() - { -/* - $aScenarios = array( - array( - 'desc' => 'Load user logins', - 'login' => 'admin', - 'password' => 'admin', - 'target_class' => 'UserLocal', - 'full_load_periodicity' => 3600, // should be ignored in this case - 'reconciliation_policy' => 'use_attributes', - 'action_on_zero' => 'create', - 'action_on_one' => 'update', - 'action_on_multiple' => 'error', - 'delete_policy' => 'delete', - 'delete_policy_update' => '', - 'delete_policy_retention' => 0, - 'source_data' => array( - array('primary_key', 'login', 'password', 'profile_list'), - array( - array('user_A', 'login_A', 'password_A', 'profileid:10;reason:he/she is managing services'), - ), - ), - 'target_data' => array( - array('login'), - array( - // Initial state - ), - array( - array('login_A'), - ), - ), - 'attributes' => array( - 'login' => array( - 'do_reconcile' => true, - 'do_update' => true, - 'automatic_prefix' => true, // unique id (for unit testing) - ), - 'password' => array( - 'do_reconcile' => false, - 'do_update' => true, - ), - 'profile_list' => array( - 'do_reconcile' => false, - 'do_update' => true, - ), - ), - ), - ); -*/ - $aScenarios = array( - array( - 'desc' => 'Simple scenario with delete option (and extkey given as org/name)', - 'login' => 'admin', - 'password' => 'admin', - 'target_class' => 'ApplicationSolution', - 'full_load_periodicity' => 3600, // should be ignored in this case - 'reconciliation_policy' => 'use_attributes', - 'action_on_zero' => 'create', - 'action_on_one' => 'update', - 'action_on_multiple' => 'error', - 'delete_policy' => 'delete', - 'delete_policy_update' => '', - 'delete_policy_retention' => 0, - 'source_data' => array( - array('primary_key', 'org_id', 'name', 'status'), - array( - array('obj_A', null, 'obj_A', 'production'), // org_id unchanged - array('obj_B', '_DUMMY_', 'obj_B', 'production'), // error, '_DUMMY_' unknown - array('obj_C', 'SOMECODE', 'obj_C', 'production'), - array('obj_D', null, 'obj_D', 'production'), - array('obj_E', '_DUMMY_', 'obj_E', 'production'), - ), - array( - ), - array( - ), - ), - 'target_data' => array( - array('org_id', 'name', 'status'), - array( - // Initial state - array(2, 'obj_A', 'production'), - array(2, 'obj_B', 'production'), - ), - array( - array(2, 'obj_A', 'production'), - array(2, 'obj_B', 'production'), - array(1, 'obj_C', 'production'), - ), - array( - array(2, 'obj_A', 'production'), - array(2, 'obj_B', 'production'), - // deleted ! - ), - // The only diff here is into the log - array( - array(2, 'obj_A', 'production'), - array(2, 'obj_B', 'production'), - // deleted ! - ), - ), - 'attributes' => array( - 'org_id' => array( - 'do_reconcile' => false, - 'do_update' => true, - 'reconciliation_attcode' => 'code', - ), - 'name' => array( - 'do_reconcile' => true, - 'do_update' => true, - 'automatic_prefix' => true, // unique id - ), - 'status' => array( - 'do_reconcile' => false, - 'do_update' => true, - ), - ), - ), -// ); -// $aXXXXScenarios = array( - array( - 'desc' => 'Update then delete with retention (to complete with manual testing) and reconciliation on org/name', - 'login' => 'admin', - 'password' => 'admin', - 'target_class' => 'ApplicationSolution', - 'full_load_periodicity' => 3600, - 'reconciliation_policy' => 'use_attributes', - 'action_on_zero' => 'create', - 'action_on_one' => 'update', - 'action_on_multiple' => 'error', - 'delete_policy' => 'update_then_delete', - 'delete_policy_update' => 'status:obsolete', - 'delete_policy_retention' => 15, - 'source_data' => array( - array('primary_key', 'org_id', 'name', 'status'), - array( - array('obj_A', 'Demo', 'obj_A', 'production'), - ), - array( - ), - ), - 'target_data' => array( - array('org_id', 'name', 'status'), - array( - // Initial state - ), - array( - array(2, 'obj_A', 'production'), - ), - array( - array(2, 'obj_A', 'obsolete'), - // deleted ! - ), - ), - 'attributes' => array( - 'org_id' => array( - 'do_reconcile' => true, - 'do_update' => true, - 'reconciliation_attcode' => 'name', - ), - 'name' => array( - 'do_reconcile' => true, - 'do_update' => true, - 'automatic_prefix' => true, // unique id - ), - 'status' => array( - 'do_reconcile' => false, - 'do_update' => true, - ), - ), - ), - //); - //$aXXScenarios = array( - array( - 'desc' => 'Simple scenario loading a few ApplicationSolution', - 'login' => 'admin', - 'password' => 'admin', - 'target_class' => 'ApplicationSolution', - 'full_load_periodicity' => 3600, - 'reconciliation_policy' => 'use_attributes', - 'action_on_zero' => 'create', - 'action_on_one' => 'update', - 'action_on_multiple' => 'error', - 'delete_policy' => 'update', - 'delete_policy_update' => 'status:obsolete', - 'delete_policy_retention' => 0, - 'source_data' => array( - array('primary_key', 'org_id', 'name', 'status'), - array( - array('obj_A', 2, 'obj_A', 'production'), - array('obj_B', 2, 'obj_B', 'implementation'), - array('obj_C', 2, 'obj_C', 'implementation'), - ), - array( - array('obj_A', 2, 'obj_A', 'production'), - array('obj_B', 2, 'obj_B', 'implementation'), - array('obj_C', 2, 'obj_C', 'implementation'), - ), - array( - array('obj_A', 2, 'obj_A', 'production'), - array('obj_C', 2, 'obj_C', 'implementation'), - array('obj_D', 2, 'obj_D', 'implementation'), - ), - array( - array('obj_C', 2, 'obj_C', 'production'), - ), - array( - array('obj_C', 2, 'obj_C', 'production'), - ), - ), - 'target_data' => array( - array('org_id', 'name', 'status'), - array( - // Initial state - array(2, 'obj_A', 'implementation'), - array(2, 'obj_B', 'production'), - array(2, 'obj_B', 'implementation'), - ), - array( - array(2, 'obj_A', 'production'), - array(2, 'obj_B', 'production'), - array(2, 'obj_B', 'implementation'), - array(2, 'obj_C', 'implementation'), - ), - array( - array(2, 'obj_A', 'production'), - array(2, 'obj_B', 'production'), - array(2, 'obj_B', 'implementation'), - array(2, 'obj_C', 'implementation'), - ), - array( - array(2, 'obj_A', 'production'), - array(2, 'obj_B', 'production'), - array(2, 'obj_B', 'implementation'), - array(2, 'obj_C', 'implementation'), - array(2, 'obj_D', 'implementation'), - ), - array( - array(2, 'obj_A', 'obsolete'), - array(2, 'obj_B', 'production'), - array(2, 'obj_B', 'implementation'), - array(2, 'obj_C', 'production'), - array(2, 'obj_D', 'obsolete'), - ), - array( - array(2, 'obj_A', 'obsolete'), - array(2, 'obj_B', 'production'), - array(2, 'obj_B', 'implementation'), - array(2, 'obj_C', 'production'), - array(2, 'obj_D', 'obsolete'), - ), - ), - 'attributes' => array( - 'org_id' => array( - 'do_reconcile' => false, - 'do_update' => true, - ), - 'name' => array( - 'do_reconcile' => true, - 'do_update' => true, - 'automatic_prefix' => true, // unique id - ), - 'status' => array( - 'do_reconcile' => false, - 'do_update' => true, - ), - ), - ), - ); - - foreach ($aScenarios as $aSingleScenario) - { - $this->DoExecScenario($aSingleScenario); - } - } -} - /////////////////////////////////////////////////////////////////////////// // Test SOAP services ///////////////////////////////////////////////////////////////////////////