diff --git a/config-dist.php b/config-dist.php deleted file mode 100644 index b87bff0c2..000000000 --- a/config-dist.php +++ /dev/null @@ -1,37 +0,0 @@ - 'localhost', - 'db_user' => 'itop', - 'db_pwd' => '1T0p', - 'db_name' => 'itopv06', - 'db_subname' => '', // use it to differentiate two applications instances running on the same DB -); - -// Modules: file names should be specified as a absolute paths - -$MyModules = array( - 'application' => array ( - '../application/menunode.class.inc.php', - '../application/audit.rule.class.inc.php', - // to be continued... - ), - 'business' => array ( - // to be continued... - ), - 'addons' => array ( - 'user rights' => '../addons/userrights/userrightsprofile.class.inc.php', - // other modules to come later - ) -); - - -?> diff --git a/config-test-farm.php b/test/config-test-farm.php similarity index 100% rename from config-test-farm.php rename to test/config-test-farm.php diff --git a/test/exclude.txt b/test/exclude.txt new file mode 100644 index 000000000..1e327f0fd --- /dev/null +++ b/test/exclude.txt @@ -0,0 +1,8 @@ +# +# The following source files are not re-distributed with the "build" of the application +# since they are used solely for constructing other files during the build process +# +config-test-farm.php +test.class.inc.php +test.php +testlist.inc.php diff --git a/test/test.class.inc.php b/test/test.class.inc.php new file mode 100644 index 000000000..5ac7741c7 --- /dev/null +++ b/test/test.class.inc.php @@ -0,0 +1,557 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +require_once('../core/coreexception.class.inc.php'); +require_once('../core/attributedef.class.inc.php'); +require_once('../core/filterdef.class.inc.php'); +require_once('../core/stimulus.class.inc.php'); +require_once('../core/MyHelpers.class.inc.php'); + +require_once('../core/expression.class.inc.php'); +require_once('../core/cmdbsource.class.inc.php'); +require_once('../core/sqlquery.class.inc.php'); + +require_once('../core/log.class.inc.php'); +require_once('../core/kpi.class.inc.php'); + +require_once('../core/dbobject.class.php'); +require_once('../core/dbobjectsearch.class.php'); +require_once('../core/dbobjectset.class.php'); + +require_once('../application/cmdbabstract.class.inc.php'); + +require_once('../core/userrights.class.inc.php'); + +require_once('../webservices/webservices.class.inc.php'); + + +// Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?) +class UnitTestException extends Exception +{} + + +/** + * Improved display of the backtrace + * + * @package iTopORM + */ +class ExceptionFromError extends Exception +{ + public function getTraceAsHtml() + { + $aBackTrace = $this->getTrace(); + return MyHelpers::get_callstack_html(0, $this->getTrace()); + // return "
\n".$this->getTraceAsString()."
\n"; + } +} + + +/** + * Test handler API and basic helpers + * + * @package iTopORM + */ +abstract class TestHandler +{ + protected $m_aSuccesses; + protected $m_aWarnings; + protected $m_aErrors; + protected $m_sOutput; + + public function __construct() + { + $this->m_aSuccesses = array(); + $this->m_aWarnings = array(); + $this->m_aErrors = array(); + } + + static public function GetName() {return "fooname";} + static public function GetDescription(){return "foodesc";} + + protected function DoPrepare() {return true;} + abstract protected function DoExecute(); + protected function DoCleanup() {return true;} + + protected static function DumpVariable($var) + { + echo "
\n"; 
+		print_r($var);
+		echo "
\n"; + } + + protected function ReportSuccess($sMessage, $sSubtestId = '') + { + $this->m_aSuccesses[] = $sMessage; + } + + protected function ReportWarning($sMessage, $sSubtestId = '') + { + $this->m_aWarnings[] = $sMessage; + } + + protected function ReportError($sMessage, $sSubtestId = '') + { + $this->m_aErrors[] = $sMessage; + } + + public function GetResults() + { + return $this->m_aSuccesses; + } + + public function GetWarnings() + { + return $this->m_aWarnings; + } + + public function GetErrors() + { + return $this->m_aErrors; + } + + public function GetOutput() + { + return $this->m_sOutput; + } + + public function error_handler($errno, $errstr, $errfile, $errline) + { + // Note: return false to call the default handler (stop the program if an error) + + switch ($errno) + { + case E_USER_ERROR: + $this->ReportError($errstr); + //throw new ExceptionFromError("Fatal error in line $errline of file $errfile: $errstr"); + break; + case E_USER_WARNING: + $this->ReportWarning($errstr); + break; + case E_USER_NOTICE: + $this->ReportWarning($errstr); + break; + default: + $this->ReportWarning("Unknown error type: [$errno] $errstr in $errfile at $errline"); + echo "Unknown error type: [$errno] $errstr in $errfile at $errline
\n"; + break; + } + return true; // do not call the default handler + } + + public function Execute() + { + ob_start(); + set_error_handler(array($this, 'error_handler')); + try + { + $this->DoPrepare(); + $this->DoExecute(); + } + catch (ExceptionFromError $e) + { + $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml()); + } + catch (CoreException $e) + { + //$this->ReportError($e->getMessage()); + //$this->ReportError($e->__tostring()); + $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml()); + } + catch (Exception $e) + { + //$this->ReportError($e->getMessage()); + //$this->ReportError($e->__tostring()); + $this->ReportError('class '.get_class($e).' --- '.$e->getMessage().' - '.$e->getTraceAsString()); + } + restore_error_handler(); + $this->m_sOutput = ob_get_clean(); + return (count($this->GetErrors()) == 0); + } +} + + + + +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + */ +abstract class TestFunction extends TestHandler +{ + // simply overload DoExecute (temporary) +} + + +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + */ +abstract class TestWebServices extends TestHandler +{ + // simply overload DoExecute (temporary) + + static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = 'admin', $sOptionnalHeaders = null) + { + $aDataAndAuth = $aData; +// To be changed to use basic authentication + $aDataAndAuth['operation'] = 'login'; + $aDataAndAuth['auth_user'] = $sLogin; + $aDataAndAuth['auth_pwd'] = $sPassword; + $sHost = $_SERVER['HTTP_HOST']; + $sRawPath = $_SERVER['SCRIPT_NAME']; + $sPath = dirname($sRawPath); + $sUrl = "http://$sHost/$sPath/$sRelativeUrl"; + + return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders); + } + + // Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl + // originaly named after do_post_request + // Partially adapted to our coding conventions + static protected function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null) + { + // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. + + $sData = http_build_query($aData); + + $aParams = array('http' => array( + 'method' => 'POST', + 'content' => $sData, + 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", + )); + if ($sOptionnalHeaders !== null) + { + $aParams['http']['header'] .= $sOptionnalHeaders; + } + $ctx = stream_context_create($aParams); + + $fp = @fopen($sUrl, 'rb', false, $ctx); + if (!$fp) + { + global $php_errormsg; + if (isset($php_errormsg)) + { + throw new Exception("Problem with $sUrl, $php_errormsg"); + } + else + { + throw new Exception("Problem with $sUrl"); + } + } + $response = @stream_get_contents($fp); + if ($response === false) + { + throw new Exception("Problem reading data from $sUrl, $php_errormsg"); + } + return $response; + } +} + +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + */ +abstract class TestSoapWebService extends TestHandler +{ + // simply overload DoExecute (temporary) + + function __construct() + { + parent::__construct(); + } +} + +/** + * Test to check that a function outputs some values depending on its input + * + * @package iTopORM + */ +abstract class TestFunctionInOut extends TestFunction +{ +// abstract static public function GetCallSpec(); // parameters to call_user_func +// abstract static public function GetInOut(); // array of input => output + + protected function DoExecute() + { + $aTests = $this->GetInOut(); + if (is_array($aTests)) + { + foreach ($aTests as $iTestId => $aTest) + { + $ret = call_user_func_array($this->GetCallSpec(), $aTest['args']); + if ($ret != $aTest['output']) + { + // Note: to be improved to cope with non string parameters + $this->ReportError("Found '$ret' while expecting '".$aTest['output']."'", $iTestId); + } + else + { + $this->ReportSuccess("Found the expected output '$ret'", $iTestId); + } + } + } + else + { + $ret = call_user_func($this->GetCallSpec()); + $this->ReportSuccess('Finished successfully'); + } + } +} + + +/** + * Test to check an URL (Searches for Error/Warning/Etc keywords) + * + * @package iTopORM + */ +abstract class TestUrl extends TestHandler +{ +// abstract static public function GetUrl(); +// abstract static public function GetErrorKeywords(); +// abstract static public function GetWarningKeywords(); + + protected function DoExecute() + { + return true; + } +} + + +/** + * Test to check a user management module + * + * @package iTopORM + */ +abstract class TestUserRights extends TestHandler +{ + protected function DoExecute() + { + return true; + } +} + + +/** + * Test to execute a scenario on a given DB + * + * @package iTopORM + */ +abstract class TestScenarioOnDB extends TestHandler +{ +// abstract static public function GetDBHost(); +// abstract static public function GetDBUser(); +// abstract static public function GetDBPwd(); +// abstract static public function GetDBName(); + + protected function DoPrepare() + { + $sDBHost = $this->GetDBHost(); + $sDBUser = $this->GetDBUser(); + $sDBPwd = $this->GetDBPwd(); + $sDBName = $this->GetDBName(); + + CMDBSource::Init($sDBHost, $sDBUser, $sDBPwd); + CMDBSource::SetCharacterSet(); + if (CMDBSource::IsDB($sDBName)) + { + CMDBSource::DropDB($sDBName); + } + CMDBSource::CreateDB($sDBName); + } + + protected function DoCleanup() + { + // CMDBSource::DropDB($this->GetDBName()); + } +} + + +/** + * Test to use a business model on a given DB + * + * @package iTopORM + */ +abstract class TestBizModel extends TestHandler +{ +// abstract static public function GetDBSubName(); +// abstract static public function GetBusinessModelFile(); +// abstract static public function GetConfigFile(); + + protected function DoPrepare() + { + MetaModel::Startup($this->GetConfigFile()); +// #@# Temporary disabled by Romain +// MetaModel::CheckDefinitions(); + + // something here to create records... but that's another story + } + + protected $m_oChange; + protected function ObjectToDB($oNew, $bReload = false) + { + list($bRes, $aIssues) = $oNew->CheckToWrite(); + if (!$bRes) + { + throw new CoreException('Could not create object, unexpected values', array('issues' => $aIssues)); + } + if ($oNew instanceof CMDBObject) + { + if (!isset($this->m_oChange)) + { + new CMDBChange(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Someone doing some tests"); + $iChangeId = $oMyChange->DBInsertNoReload(); + $this->m_oChange = $oMyChange; + } + if ($bReload) + { + $iId = $oNew->DBInsertTracked($this->m_oChange); + } + else + { + $iId = $oNew->DBInsertTrackedNoReload($this->m_oChange); + } + } + else + { + if ($bReload) + { + $iId = $oNew->DBInsert(); + } + else + { + $iId = $oNew->DBInsertNoReload(); + } + } + return $iId; + } + + protected function ResetDB() + { + if (MetaModel::DBExists(false)) + { + MetaModel::DBDrop(); + } + MetaModel::DBCreate(); + } + + static protected function show_list($oObjectSet) + { + $oObjectSet->Rewind(); + $aData = array(); + while ($oItem = $oObjectSet->Fetch()) + { + $aValues = array(); + foreach(MetaModel::GetAttributesList(get_class($oItem)) as $sAttCode) + { + $aValues[$sAttCode] = $oItem->GetAsHTML($sAttCode); + } + //echo $oItem->GetKey()." => ".implode(", ", $aValues)."
\n"; + $aData[] = $aValues; + } + echo MyHelpers::make_table_from_assoc_array($aData); + } + + static protected function search_and_show_list(DBObjectSearch $oMyFilter) + { + $oObjSet = new CMDBObjectSet($oMyFilter); + echo $oMyFilter->__DescribeHTML()."' - Found ".$oObjSet->Count()." items.
\n"; + self::show_list($oObjSet); + } + + static protected function search_and_show_list_from_oql($sOQL) + { + echo $sOQL."...
\n"; + $oNewFilter = DBObjectSearch::FromOQL($sOQL); + self::search_and_show_list($oNewFilter); + } +} + + +/** + * Test to execute a scenario common to any business model (tries to build all the possible queries, etc.) + * + * @package iTopORM + */ +abstract class TestBizModelGeneric extends TestBizModel +{ + static public function GetName() + { + return 'Full test on a given business model'; + } + + static public function GetDescription() + { + return 'Systematic tests: gets each and every existing class and tries every attribute, search filters, etc.'; + } + + protected function DoPrepare() + { + parent::DoPrepare(); + + if (!MetaModel::DBExists(false)) + { + MetaModel::DBCreate(); + } + // something here to create records... but that's another story + } + + protected function DoExecute() + { + foreach(MetaModel::GetClasses() as $sClassName) + { + if (MetaModel::HasTable($sClassName)) continue; + + $oNobody = MetaModel::GetObject($sClassName, 123); + $oBaby = new $sClassName; + $oFilter = new DBObjectSearch($sClassName); + + // Challenge reversibility of OQL / filter object + // + $sExpr1 = $oFilter->ToOQL(); + $oNewFilter = DBObjectSearch::FromOQL($sExpr1); + $sExpr2 = $oNewFilter->ToOQL(); + if ($sExpr1 != $sExpr2) + { + $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); + } + + // Use the filter (perform the query) + // + $oSet = new CMDBObjectSet($oFilter); + $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + } + return true; + } +} + + +?> diff --git a/test/test.php b/test/test.php new file mode 100644 index 000000000..0664aac9b --- /dev/null +++ b/test/test.php @@ -0,0 +1,157 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +?> + +Missing mandatory argument $sName

"; + exit; + } + return $value; +} + +function IsAValidTestClass($sClassName) +{ + // Must be a child of TestHandler + // + if (!is_subclass_of($sClassName, 'TestHandler')) return false; + + // Must not be abstract + // + $oReflectionClass = new ReflectionClass($sClassName); + if (!$oReflectionClass->isInstantiable()) return false; + + return true; +} + +function DisplayEvents($aEvents, $sTitle) +{ + echo "

$sTitle

\n"; + if (count($aEvents) > 0) + { + echo "
    \n"; + foreach ($aEvents as $sEvent) + { + echo "
  • $sEvent
  • \n"; + } + echo "
\n"; + } + else + { + echo "

none

\n"; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Main +/////////////////////////////////////////////////////////////////////////////// + + +require_once('../application/utils.inc.php'); +require_once('../core/test.class.inc.php'); +require_once('./testlist.inc.php'); + +require_once('../core/cmdbobject.class.inc.php'); + +$sTodo = utils::ReadParam("todo", ""); +if ($sTodo == '') +{ + // Show the list of tests + // + echo "

Existing tests

\n"; + echo "
    \n"; + foreach (get_declared_classes() as $sClassName) + { + if (!IsAValidTestClass($sClassName)) continue; + + $sName = call_user_func(array($sClassName, 'GetName')); + $sDescription = call_user_func(array($sClassName, 'GetDescription')); + echo "
  • $sName ($sDescription)\n"; +} +else if ($sTodo == 'exec') +{ + // Execute a test + // + $sTestClass = ReadMandatoryParam("testid"); + + if (!IsAValidTestClass($sTestClass)) + { + echo "

    Wrong value for testid, expecting a valid class name

    \n"; + } + else + { + $oTest = new $sTestClass(); + echo "

    Testing: ".$oTest->GetName()."

    \n"; + $bRes = $oTest->Execute(); + } + +/* +MyHelpers::var_dump_html($oTest->GetResults()); +MyHelpers::var_dump_html($oTest->GetWarnings()); +MyHelpers::var_dump_html($oTest->GetErrors()); +*/ + + if ($bRes) + { + echo "

    Success :-)

    \n"; + DisplayEvents($oTest->GetResults(), 'Results'); + } + else + { + echo "

    Failure :-(

    \n"; + } + DisplayEvents($oTest->GetErrors(), 'Errors'); + DisplayEvents($oTest->GetWarnings(), 'Warnings'); + + // Render the output + // + echo "

    Actual output

    \n"; + echo "
    \n"; + echo $oTest->GetOutput(); + echo "
    \n"; +} +else +{ +} + + +?> diff --git a/test/testlist.inc.php b/test/testlist.inc.php new file mode 100644 index 000000000..55c8872d5 --- /dev/null +++ b/test/testlist.inc.php @@ -0,0 +1,2104 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +class TestSQLQuery extends TestScenarioOnDB +{ + static public function GetName() {return 'SQLQuery';} + static public function GetDescription() {return 'SQLQuery does not depend on the rest of the framework, therefore it makes sense to have a separate test framework for it';} + + static public function GetDBHost() {return 'localhost';} + static public function GetDBUser() {return 'root';} + static public function GetDBPwd() {return '';} + static public function GetDBName() {return 'TestSQLQuery';} + static public function GetDBSubName() {return 'taratata';} + + + protected function DoPrepare() + { + parent::DoPrepare(); + cmdbSource::CreateTable('CREATE TABLE `myTable` (myKey INT(11) NOT NULL auto_increment, column1 VARCHAR(255), column2 VARCHAR(255), PRIMARY KEY (`myKey`)) ENGINE = '.MYSQL_ENGINE); + cmdbSource::CreateTable('CREATE TABLE `myTable1` (myKey1 INT(11) NOT NULL auto_increment, column1_1 VARCHAR(255), column1_2 VARCHAR(255), PRIMARY KEY (`myKey1`)) ENGINE = '.MYSQL_ENGINE); + cmdbSource::CreateTable('CREATE TABLE `myTable2` (myKey2 INT(11) NOT NULL auto_increment, column2_1 VARCHAR(255), column2_2 VARCHAR(255), PRIMARY KEY (`myKey2`)) ENGINE = '.MYSQL_ENGINE); + } + + protected function DoExecute() + { + $oQuery = new SQLQuery( + $sTable = 'myTable', + $sTableAlias = 'myTableAlias', + $aFields = array('column1'=>new FieldExpression('column1', 'myTableAlias'), 'column2'=>new FieldExpression('column2', 'myTableAlias')), + $oCondition = new BinaryExpression(new FieldExpression('column1', 'myTableAlias'), 'LIKE', new ScalarExpression('trash')), + $aFullTextNeedles = array('column1'), + $bToDelete = false, + $aValues = array() + ); + $oQuery->AddCondition(Expression::FromOQL('DATE(NOW() - 1200 * 2) > \'2008-07-31\'')); + + $oSubQuery1 = new SQLQuery( + $sTable = 'myTable1', + $sTableAlias = 'myTable1Alias', + $aFields = array('column1_1'=>new FieldExpression('column1', 'myTableAlias'), 'column1_2'=>new FieldExpression('column1', 'myTableAlias')), + $oCondition = new TrueSQLExpression, + $aFullTextNeedles = array(), + $bToDelete = false, + $aValues = array() + ); + + $oSubQuery2 = new SQLQuery( + $sTable = 'myTable2', + $sTableAlias = 'myTable2Alias', + $aFields = array('column2_1'=>new FieldExpression('column2', 'myTableAlias'), 'column2_2'=>new FieldExpression('column2', 'myTableAlias')), + $oCondition = new TrueSQLExpression, + $aFullTextNeedles = array(), + $bToDelete = false, + $aValues = array() + ); + + $oQuery->AddInnerJoin($oSubQuery1, 'column1', 'column1_1'); + $oQuery->AddLeftJoin($oSubQuery2, 'column2', 'column2_2'); + + $oQuery->DisplayHtml(); + $oQuery->RenderDelete(); + $oQuery->RenderUpdate(); + echo '

    '.$oQuery->RenderSelect().'

    '; + $oQuery->RenderSelect(array('column1')); + $oQuery->RenderSelect(array('column1', 'column2')); + } +} + +class TestOQLParser extends TestFunction +{ + static public function GetName() {return 'Check OQL parsing';} + static public function GetDescription() {return 'Attempts a series of queries, and in particular those with a bad syntax';} + + protected function CheckQuery($sQuery, $bIsCorrectQuery) + { + $oOql = new OqlInterpreter($sQuery); + try + { + $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery + self::DumpVariable($oTrash); + } + catch (OQLException $OqlException) + { + if ($bIsCorrectQuery) + { + echo "

    More info on this unexpected failure:
    ".$OqlException->getHtmlDesc()."

    \n"; + throw $OqlException; + return false; + } + else + { + // Everything is fine :-) + echo "

    More info on this expected failure:
    ".$OqlException->getHtmlDesc()."

    \n"; + return true; + } + } + // The query was correctly parsed, was it expected to be correct ? + if ($bIsCorrectQuery) + { + return true; + } + else + { + throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); + return false; + } + } + + protected function TestQuery($sQuery, $bIsCorrectQuery) + { + if (!$this->CheckQuery($sQuery, $bIsCorrectQuery)) + { + return false; + } + return true; + } + + public function DoExecute() + { + $aQueries = array( + 'SELECT toto' => true, + 'SELECT toto WHERE toto.a = 1' => true, + 'SELECT toto WHERE toto.a=1' => true, + 'SELECT toto WHERE toto.a = "1"' => true, + 'SELECT toto WHHHERE toto.a = "1"' => false, + 'SELECT toto WHERE toto.a == "1"' => false, + 'SELECT toto WHERE toto.a % 1' => false, + //'SELECT toto WHERE toto.a LIKE 1' => false, + 'SELECT toto WHERE toto.a like \'arg\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s it"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s "it""' => false, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'That"s it\'' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\'' => true, + 'SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"' => false, + 'SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\'' => true, + + 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\\\\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE ""' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, + "SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = 'lnkContactRealObject' AND UserRightsMatrixClassGrant.action = 'modify' AND UserRightsMatrixClassGrant.login = 'Denis'" => true, + "SELECT A WHERE A.col1 = 'lit1' AND A.col2 = 'lit2' AND A.col3 = 'lit3'" => true, + + 'SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"' => true, + + //'SELECT toto WHERE toto.a > \'asd\'' => false, + 'SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345' => true, + 'SELECT Device JOIN Site ON Device.site = Site.id' => true, + 'SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, + + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = 'aa') OR (A.col3 = 'zzz' AND B.col4 > 100)" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = '' AND B.col4 > 100)" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true, + + 'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true, + + // Several objects in a row... + // + 'SELECT A FROM A' => true, + 'SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A, B,C FROM A JOIN B ON A.myB = B.id' => false, + 'SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => false, + ); + + $iErrors = 0; + + foreach($aQueries as $sQuery => $bIsCorrectQuery) + { + $sIsOk = $bIsCorrectQuery ? 'good' : 'bad'; + echo "

    Testing query: $sQuery ($sIsOk)

    \n"; + $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); + if (!$bRet) $iErrors++; + } + + return ($iErrors == 0); + } +} + + +class TestCSVParser extends TestFunction +{ + static public function GetName() {return 'Check CSV parsing';} + static public function GetDescription() {return 'Loads a set of CSV data';} + + public function DoExecute() + { + $sDataFile = '?field1?;?field2?;?field3? +?a?;?b?;?c? +a;b;c + ? a ? ; ? b ? ; ? c ? + a ; b ; c +??;??;?? +;; +?a"?;?b?;?c? +?a1 +a2?;?b?;?c? +?a1,a2?;?b?;?c? +?a?;?b?;?c1,",c2 +,c3? +?a?;?b?;?ouf !? + Espace sur la fin ; 1234; e@taloc.com '; + + self::DumpVariable($sDataFile); + + $aExpectedResult = array( + //array('field1', 'field2', 'field3'), + array('a', 'b', 'c'), + array('a', 'b', 'c'), + array(' a ', ' b ', ' c '), + array('a', 'b', 'c'), + array('', '', ''), + array('', '', ''), + array('a"', 'b', 'c'), + array("a1\na2", 'b', 'c'), + array('a1,a2', 'b', 'c'), + array('a', 'b', "c1,\",c2\n,c3"), + array('a', 'b', 'ouf !'), + array('Espace sur la fin', '1234', 'e@taloc.com'), + ); + + $oCSVParser = new CSVParser($sDataFile, ';', '?'); + $aData = $oCSVParser->ToArray(1, null, 0); + + $iIssues = 0; + + echo "\n"; + foreach ($aData as $iRow => $aRow) + { + echo "\n"; + foreach ($aRow as $iCol => $sCell) + { + if (empty($sCell)) + { + $sCellValue = ' '; + } + else + { + $sCellValue = htmlentities($sCell); + } + + if (!isset($aExpectedResult[$iRow][$iCol])) + { + $iIssues++; + $sCellValue = "$sCellValue"; + } + elseif ($aExpectedResult[$iRow][$iCol] != $sCell) + { + $iIssues++; + $sCellValue = "$sCellValue, expecting '".$aExpectedResult[$iRow][$iCol]."'"; + } + + echo ""; + } + echo "\n"; + } + echo "
    $sCellValue
    \n"; + return ($iIssues > 0); + } +} + +class TestGenericItoMyModel extends TestBizModelGeneric +{ + static public function GetName() + { + return 'Generic RO test on '.self::GetConfigFile(); + } + + static public function GetConfigFile() {return '../config-test-mymodel.php';} +} + +class TestGenericItopBigModel extends TestBizModelGeneric +{ + static public function GetName() + { + return 'Generic RO test on '.self::GetConfigFile(); + } + + static public function GetConfigFile() {return '../config-test-itopv06.php';} +} + +class TestUserRightsMatrixItop extends TestUserRights +{ + static public function GetName() + { + return 'User rights test on user rights matrix'; + } + + static public 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 +{ + static public function GetName() + { + return 'A series of tests on a weird business model'; + } + + static public function GetDescription() + { + return 'Attempts various operations and build complex queries'; + } + + static public function GetConfigFile() {return '../config-test-mymodel.php';} + + function test_linksinfo() + { + echo "

    Enum links

    "; + self::DumpVariable(MetaModel::EnumReferencedClasses("cmdbTeam")); + self::DumpVariable(MetaModel::EnumReferencingClasses("Organization")); + + self::DumpVariable(MetaModel::EnumLinkingClasses()); + self::DumpVariable(MetaModel::EnumLinkingClasses("cmdbContact")); + self::DumpVariable(MetaModel::EnumLinkingClasses("cmdWorkshop")); + self::DumpVariable(MetaModel::GetLinkLabel("Liens_entre_contacts_et_workshop", "toworkshop")); + } + + function test_list_attributes() + { + echo "

    List attributes

    "; + foreach(MetaModel::ListAttributeDefs("cmdbTeam") as $sAttCode=>$oAttDef) + { + echo $oAttDef->GetLabel()." / ".$oAttDef->GetDescription()." / ".$oAttDef->GetType()."
    \n"; + } + } + + 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 = array(); + 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); + + } + + function test_reload() + { + echo "

    Reload

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

    Set attribute and update

    "; + $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"; + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_setattribute / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + //MetaModel::StartDebugQuery(); + $team->DBUpdateTracked($oMyChange); + //MetaModel::StopDebugQuery(); + + echo "

    Check the modified team

    "; + $oTeam = MetaModel::GetObject("cmdbTeam", "2"); + self::DumpVariable($oTeam); + } + function test_newobject() + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_newobject / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + 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->DBInsertTracked($oMyChange); + echo "Created new team: $iId
    "; + echo "

    Delete team #$iId

    "; + $oTeam = MetaModel::GetObject("cmdbTeam", $iId); + $oTeam->DBDeleteTracked($oMyChange); + echo "Deleted team: $iId
    "; + self::DumpVariable($oTeam); + } + + + 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, array("email" => $sNewEmail)); + + echo "Candidates after:
    "; + $this->search_and_show_list($oMyFilter); + } + + function test_error() + { + trigger_error("Stop requested", E_USER_ERROR); + } + + function test_changetracking() + { + echo "

    Create a change

    "; + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + echo "Created new change: $iChangeId
    "; + 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 + $iId = $oNewTeam->DBInsertTracked($oMyChange); + echo "Created new team: $iId
    "; + echo "

    Delete team #$iId

    "; + $oTeam = MetaModel::GetObject("cmdbTeam", $iId); + $oTeam->DBDeleteTracked($oMyChange); + echo "Deleted team: $iId
    "; + self::DumpVariable($oTeam); + } + + 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::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"; + + } + + 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; + $oMerge->Merge($oSet2); + $oMerge->Merge($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 "Merge - Found ".$oMerge->Count()." items.
    \n"; + //$this->show_list($oObjSet); + } + + 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->GetRelatedObjects("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)"); + + } + + 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", array($oNewLink)); + $oObj->Set("myworkshops", $oSetWorkshops); + $this->show_list($oSetWorkshops); + + echo "
    New workshops
    \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_linkedset / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + $oObj = MetaModel::GetObject("cmdbContact", 18); + + echo "
    After the write
    \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + } + + 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 a complex biz model on the fly +/////////////////////////////////////////////////////////////////////////// + +abstract class MyFarm extends TestBizModel +{ + static public function GetConfigFile() {return '../config-test-farm.php';} + + protected function DoPrepare() + { + parent::DoPrepare(); + $this->ResetDB(); + MetaModel::DBCheckIntegrity(); + } + + protected function InsertMammal($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $sName, $iHeight, $sBirth) + { + $oNew = MetaModel::NewObject('Mammal'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + $oNew->Set('name', $sName); + $oNew->Set('height', $iHeight); + $oNew->Set('birth', $sBirth); + return $this->ObjectToDB($oNew); + } + + protected function InsertBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId) + { + $oNew = MetaModel::NewObject('Bird'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + return $this->ObjectToDB($oNew); + } + + protected function InsertFlyingBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $iFlyingSpeed) + { + $oNew = MetaModel::NewObject('FlyingBird'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + $oNew->Set('flyingspeed', $iFlyingSpeed); + return $this->ObjectToDB($oNew); + } + + private function InsertGroup($sName, $iLeaderId) + { + $oNew = MetaModel::NewObject('Group'); + $oNew->Set('name', $sName); + $oNew->Set('leader', $iLeaderId); + $iId = $oNew->DBInsertNoReload(); + return $iId; + } +} + + +class TestQueriesOnFarm extends MyFarm +{ + static public function GetName() + { + return 'Farm test'; + } + + static public function GetDescription() + { + return 'A series of tests on the farm business model (SQL generation)'; + } + + protected function CheckQuery($sQuery, $bIsCorrectQuery) + { + if ($bIsCorrectQuery) + { + echo "

    $sQuery

    \n"; + } + else + { + echo "

    $sQuery

    \n"; + } + try + { + //$oOql = new OqlInterpreter($sQuery); + //$oTrash = $oOql->ParseObjectQuery(); + //self::DumpVariable($oTrash, true); + $oMyFilter = DBObjectSearch::FromOQL($sQuery); + } + catch (OQLException $oOqlException) + { + if ($bIsCorrectQuery) + { + echo "

    More info on this unexpected failure:
    ".$oOqlException->getHtmlDesc()."

    \n"; + throw $oOqlException; + return false; + } + else + { + // Everything is fine :-) + echo "

    More info on this expected failure:\n"; + echo "

      \n"; + echo "
    • ".get_class($oOqlException)."
    • \n"; + echo "
    • ".$oOqlException->getMessage()."
    • \n"; + echo "
    • ".$oOqlException->getHtmlDesc()."
    • \n"; + echo "
    \n"; + echo "

    \n"; + return true; + } + } + // The query was correctly parsed, was it expected to be correct ? + if (!$bIsCorrectQuery) + { + throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); + return false; + } + echo "

    To OQL: ".$oMyFilter->ToOQL()."

    "; + + $this->search_and_show_list($oMyFilter); + + //echo "

    first pass

    \n"; + //self::DumpVariable($oMyFilter, true); + $sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + //echo "

    second pass

    \n"; + //self::DumpVariable($oMyFilter, true); + //$sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + + $sSerialize = $oMyFilter->serialize(); + echo "

    Serialized:$sSerialize

    \n"; + $oFilter2 = DBObjectSearch::unserialize($sSerialize); + try + { + $sQuery2 = MetaModel::MakeSelectQuery($oFilter2); + } + catch (Exception $e) + { + echo "

    Could not compute the query after unserialize

    \n"; + echo "

    Query 1: $sQuery1

    \n"; + MyHelpers::var_cmp_html($oMyFilter, $oFilter2); + throw $e; + } + //if ($oFilter2 != $oMyFilter) no, they may differ while the resulting query is the same! + if ($sQuery1 != $sQuery2) + { + echo "

    serialize/unserialize mismatch :-(

    \n"; + MyHelpers::var_cmp_html($sQuery1, $sQuery2); + MyHelpers::var_cmp_html($oMyFilter, $oFilter2); + return false; + } + return true; + } + + 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"); + echo "

    Create protagonists...

    "; + + $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); + $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); + $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); + $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); + $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); + + $this->InsertBird('rooster', 'male', 12, 0, 0); + $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); + + // Benchmarking + // + if (false) + { + define ('COUNT_BENCHMARK', 10); + echo "

    Parsing a long query, ".COUNT_BENCHMARK." times

    "; + $sQuery = "SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR) AND Dad.height * 2 <= ROUND(TO_DAYS(Dad.birth) / (3 + 1) * 5 - 3)"; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $oMyFilter = DBObjectSearch::FromOQL($sQuery); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fParsingDuration = $fDuration / COUNT_BENCHMARK; + echo "

    Mean time by op: $fParsingDuration

    "; + } + + echo "

    Test queries...

    "; + + $aQueries = array( + 'SELECT Animal' => true, + 'SELECT Animal WHERE Animal.pkey = 1' => false, + 'SELECT Animal WHERE Animal.id = 1' => true, + 'SELECT Aniiimal' => false, + 'SELECTe Animal' => false, + 'SELECT * FROM Animal' => false, + 'SELECT Animal AS zoo WHERE zoo.species = \'human\'' => true, + 'SELECT Animal AS zoo WHERE species = \'human\'' => true, + 'SELECT Animal AS zoo WHERE espece = \'human\'' => false, + 'SELECT Animal AS zoo WHERE zoo.species IN (\'human\', "pig")' => true, + 'SELECT Animal AS zoo WHERE CONCATENATION(zoo.species, zoo.sex) LIKE "hum%male"' => false, + 'SELECT Animal AS zoo WHERE CONCAT(zoo.species, zoo.sex) LIKE "hum%male"' => true, + 'SELECT Animal AS zoo WHERE zoo.species NOT IN (\'human\', "pig")' => true, + 'SELECT Animal AS zoo WHERE zoo.kind = \'human\'' => false, + 'SELECT Animal WHERE Animal.species = \'human\' AND Animal.sex = \'female\'' => true, + 'SELECT Mammal AS x WHERE (x.species = \'human\' AND x.name LIKE \'ro%\') OR (x.species = \'donkey\' AND x.name LIKE \'po%\')' => true, + 'SELECT Mammal AS x WHERE x.species = \'human\' AND x.name LIKE \'ro%\' OR x.species = \'donkey\' AND x.name LIKE \'po%\'' => true, + 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, + 'SELECT Mammal AS m WHERE DAY(m.birth) = 19' => true, + 'SELECT Mammal AS m WHERE YEAR(m.birth) = 1971' => true, + 'SELECT Mammal AS m WHERE m.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR)' => true, + 'SELECT Mammal AS m WHERE m.birth > DATE_SUB(NOW(), INTERVAL 2000 DAY)' => true, + 'SELECT Mammal AS m WHERE (TO_DAYS(NOW()) - TO_DAYS(m.birth)) > 2000' => true, + 'SELECT Mammal AS m WHERE m.name = IF(FLOOR(ROUND(m.height)) > 2, "pomme", "romain")' => true, + 'SELECT Mammal AS m WHERE (1 + 2' => false, + 'SELECT Mammal AS m WHERE (1 + 2 * 4 / 23) > 0' => true, + 'SELECT Mammal AS m WHERE (4 / 23 * 2 + 1) > 0' => true, + 'SELECT Mammal AS m WHERE 1/0' => true, + 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, + 'SELECT Animal JOIN Group ON Group.leader = Animal.id' => true, + 'SELECT Group JOIN Animal ON Group.leader = Animal.id' => true, + 'SELECT Animal AS A JOIN Group AS G1 ON G1.leader = A.id' => true, + 'SELECT Animal AS A JOIN Group AS G ON FooClass.leader = A.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = FooClass.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.masterchief = A.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON A.id = G.leader' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.qwerty = 123' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.name LIKE "a%"' => true, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.id = 1' => true, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE id = 1' => false, + 'SELECT Animal AS A JOIN Group AS G ON A.member = G.id' => false, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id' => true, + 'SELECT Mammal AS M JOIN Group AS G ON A.member = G.id' => false, + 'SELECT Mammal AS myAlias JOIN Group AS myAlias ON myAlias.member = myAlias.id' => false, + 'SELECT Mammal AS Mammal JOIN Group AS Mammal ON Mammal.member = Mammal.id' => false, + 'SELECT Group AS G WHERE G.leader_name LIKE "%"' => true, + 'SELECT Group AS G WHERE G.leader_speed < 100000' => true, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_speed < 100000' => true, + 'SELECT Mammal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Mammal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.id = 1' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\'' => false, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.speed = 0' => true, + 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, + 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id' => true, + 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id JOIN Mammal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.name=\'chloe\' OR Child.name=\'bizounours\'' => true, + // Specifying multiple objects + 'SELECT Animal FROM Animal' => true, + 'SELECT yelele FROM Animal' => false, + 'SELECT Animal FROM Animal AS A' => false, + 'SELECT A FROM Animal AS A' => true, + ); + //$aQueries = array( + // 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, + //); + foreach($aQueries as $sQuery => $bIsCorrect) + { + $this->CheckQuery($sQuery, $bIsCorrect); + } + return true; + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestBulkChangeOnFarm extends TestBizModel +{ + static public function GetName() + { + return 'Farm test - data load'; + } + + static public function GetDescription() + { + return 'Bulk load'; + } + + static public function GetConfigFile() {return '../config-test-farm.php';} + + protected function DoPrepare() + { + parent::DoPrepare(); + $this->ResetDB(); + MetaModel::DBCheckIntegrity(); + } + + 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"); + + $oParser = new CSVParser("denomination,hauteur,age + suzy,123,2009-01-01 + chita,456, + "); + $aData = $oParser->ToArray(array('_name', '_height', '_birth'), ','); + self::DumpVariable($aData); + + $oBulk = new BulkChange( + 'Mammal', + $aData, + // attributes + array('name' => '_name', 'height' => '_height', 'birth' => '_birth'), + // ext keys + array(), + // reconciliation + array('name') + ); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Testor"); + $iChangeId = $oMyChange->DBInsert(); +// echo "Created new change: $iChangeId
    "; + + echo "

    Planned for loading...

    "; + $aRes = $oBulk->Process(); + self::DumpVariable($aRes); + echo "

    Go for loading...

    "; + $aRes = $oBulk->Process($oMyChange); + self::DumpVariable($aRes); + + return; + + $oRawData = array( + 'Mammal', + array('species', 'sex', 'speed', 'mother', 'father', 'name', 'height', 'birth'), + "human,male,23,0,0,romulus,192,1971 + human,male,23,0,0,remus,154,-50 + human,male,23,0,0,julius,160,-49 + human,female,23,0,0,cleopatra,142,-50 + pig,female,23,0,0,confucius,50,2003" + ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestFullTextSearchOnFarm extends MyFarm +{ + static public function GetName() + { + return 'Farm test - full text search'; + } + + static public function GetDescription() + { + return 'Focus on the full text search feature'; + } + + protected function DoExecute() + { + echo "

    Create protagonists...

    "; + + $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); + $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); + $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); + $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); + $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); + + $this->InsertBird('rooster', 'male', 12, 0, 0); + $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); + + echo "

    Search...

    "; + $oSearch = new DBObjectSearch('Mammal'); + $oSearch->AddCondition_FullText('manof'); + //$oResultSet = new DBObjectSet($oSearch); + $this->search_and_show_list($oSearch); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Benchmark queries +/////////////////////////////////////////////////////////////////////////// + +class TestItopEfficiency extends TestBizModel +{ + static public function GetName() + { + return 'Itop - benchmark'; + } + + static public function GetDescription() + { + return 'Measure time to perform the queries'; + } + + static public function GetConfigFile() {return '../config-itop.php';} + + 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 = MetaModel::MakeSelectQuery($oFilter); + } + $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"; + echo "
    • Parsing: $fParsingDuration
    • \n"; + echo "
    • Build: $fBuildDuration
    • \n"; + echo "
    • Query: $fQueryDuration
    • \n"; + echo "
    • Fetch: $fFetchDuration
    • \n"; + echo "
    • ToOql: $fToOqlDuration
    • \n"; + echo "
    \n"; + + // Everything but the ToOQL (wich is interesting, anyhow) + $fTotal = $fParsingDuration + $fBuildDuration + $fQueryDuration + $fFetchDuration; + + return array( + '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), + ); + } + + protected function DoExecute() + { + define ('COUNT_BENCHMARK', 3); + echo "

    The test will be repeated ".COUNT_BENCHMARK." times

    "; + + $aQueries = array( + '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 Incident JOIN Person ON Incident.agent_id = Person.id WHERE Person.id = 5', + ); + $aStats = array(); + foreach ($aQueries as $sOQL) + { + $aStats[$sOQL] = $this->DoBenchmark($sOQL); + } + + $aData = array(); + foreach ($aStats as $sOQL => $aResults) + { + $aValues = array(); + $aValues['OQL'] = htmlentities($sOQL); + + foreach($aResults as $sDesc => $sInfo) + { + $aValues[$sDesc] = htmlentities($sInfo); + } + $aData[] = $aValues; + } + echo MyHelpers::make_table_from_assoc_array($aData); + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestImportREST extends TestWebServices +{ + static public function GetName() + { + return 'CSV import (REST)'; + } + + static public function GetDescription() + { + return 'Test various options and fonctionality of import.php'; + } + + protected function DoExecSingleLoad($aLoadSpec) + { + $sCsvData = $aLoadSpec['csvdata']; + + echo "
    \n"; + echo "

    {$aLoadSpec['desc']}

    \n"; + + $aPostData = array('csvdata' => $sCsvData); + + $aGetParams = array(); + $aGetParamReport = array(); + 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 = array( + array( + 'desc' => 'Missing class', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + ), + 'csvdata' => "xxx", + ), + array( + 'desc' => 'Wrong class', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'toto', + ), + 'csvdata' => "xxx", + ), + array( + 'desc' => 'Wrong output type', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkDevice', + 'output' => 'onthefly', + ), + 'csvdata' => "xxx", + ), + array( + 'desc' => 'Wrong report level', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkDevice', + 'reportlevel' => 'errors|ouarnings|changed', + ), + 'csvdata' => "xxx", + ), + array( + 'desc' => 'Weird format, working anyhow...', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Server', + 'output' => 'details', + 'separator' => '*', + 'qualifier' => '@', + 'reconciliationkeys' => 'org_id,name', + ), + 'csvdata' => 'name*org_id + server01*2 + @server02@@combodo@* 2 + server45*99', + ), + array( + 'desc' => 'Load an organization', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Organization', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "name;code\nWorldCompany;WCY", + ), + array( + 'desc' => 'Load a location', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "name;org_id;address\nParis;1;Centre de la Franca", + ), + array( + 'desc' => 'Load a person', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Person', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "email;name;first_name;org_id;phone\njohn.foo@starac.com;Foo;John;1;+33(1)23456789", + ), + array( + 'desc' => 'Load a person - wrong email format', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Person', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "email;name;first_name;org_id\nemailPASbon;Foo;John;1", + ), + array( + 'desc' => 'Load a team', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Team', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris", + ), + array( + 'desc' => 'Load server', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Server', + 'output' => 'details', + '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", + ), + array( + 'desc' => 'Load NW if', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkInterface', + 'output' => 'details', + '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 + array( + 'desc' => 'Load NW devices from real life', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkDevice', + 'output' => 'details', + 'reconciliationkeys' => 'org_id->name,name', + ), + 'csvdata' => 'name;management_ip;importance;org_id->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', + ), + array( + 'desc' => 'Load NW ifs', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkInterface', + 'output' => 'details', + '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', + ), + array( + 'desc' => 'The simplest data load', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name\nParis", + ), + array( + 'desc' => 'The simplest data load + org', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org_id\nParis;2", + ), + array( + 'desc' => 'The simplest data load + org (name)', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org_name\nParis;Demo", + ), + array( + 'desc' => 'The simplest data load + org (code)', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org_id->code\nParis;DEMO", + ), + array( + 'desc' => 'Ouput: summary', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'summary', + ), + 'csvdata' => "name;org_id->code\nParis;DEMO", + ), + array( + 'desc' => 'Ouput: retcode', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'retcode', + ), + 'csvdata' => "name;org_id->code\nParis;DEMO", + ), + array( + 'desc' => 'Error in reconciliation list', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + 'reconciliationkeys' => 'org_id', + ), + 'csvdata' => "org_name;name\nDemo;Paris", + ), + array( + 'desc' => 'Error in attribute list that does not allow to compute reconciliation scheme', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "org_name;country\nDemo;France", + ), + array( + 'desc' => 'Error in attribute list - case A', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org\nParis;2", + ), + array( + 'desc' => 'Error in attribute list - case B1 (key->attcode)', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org->code\nParis;DEMO", + ), + array( + 'desc' => 'Error in attribute list - case B2 (key->attcode)', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org_id->duns\nParis;DEMO", + ), + array( + 'desc' => 'Always changing... special comment in change tracking', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + 'comment' => 'automated testing' + ), + 'csvdata' => "org_name;name;address\nDemo;Le pantheon;Addresse bidon:".((string)microtime(true)), + ), + array( + 'desc' => 'Always changing... but "simulate"', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + 'simulate' => '1', + 'comment' => 'SHOULD NEVER APPEAR IN THE HISTORY' + ), + 'csvdata' => "org_name;name;address\nDemo;Le pantheon;restore address?", + ), + ); + + foreach ($aLoads as $aLoadSpec) + { + $this->DoExecSingleLoad($aLoadSpec); + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test massive data load +/////////////////////////////////////////////////////////////////////////// +define('IMPORT_COUNT', 1000); + +class TestImportRESTMassive extends TestImportREST +{ + static public function GetName() + { + return 'CSV import (REST) - HUGE data set ('.IMPORT_COUNT.' PCs)'; + } + + static public function GetDescription() + { + return 'Stress import.php'; + } + + protected function DoExecute() + { + $aLoadSpec = array( + 'desc' => 'Missing class', + 'args' => array( + '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 +/////////////////////////////////////////////////////////////////////////// + +$aWebServices = array( + array( + 'verb' => 'GetVersion', + 'expected result' => WebServices::GetVersion(), + 'explain result' => 'no comment!', + 'args' => array(), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'link attribute unknown + a CI not found', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'desc of ticket', /* sDescription */ + 'initial situation blah blah blah', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'InfrastructureCI', + array(new SOAPSearchCondition('name', 'dbserver1.demo.com')), + array(new SOAPAttributeValue('impacting', 'very critical')) + ), + new SOAPLinkCreationSpec( + 'NetworkDevice', + array(new SOAPSearchCondition('name', 'switch01')), + array(new SOAPAttributeValue('impact', 'who cares')) + ), + new SOAPLinkCreationSpec( + 'Server', + array(new SOAPSearchCondition('name', 'thisone')), + array(new SOAPAttributeValue('impact', 'our lives')) + ), + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'caller not specified', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + new SOAPExternalKeySearch(), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'InfrastructureCI', + array(new SOAPSearchCondition('name', 'dbserver1.demo.com')), + array() + ), /* aImpact */ + ), + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong class on CI to attach', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'logInfra', + array(new SOAPSearchCondition('dummyfiltercode', 2)), + array(new SOAPAttributeValue('impact', 'very critical')) + ), + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong search condition on CI to attach', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'InfrastructureCI', + array(new SOAPSearchCondition('dummyfiltercode', 2)), + array(new SOAPAttributeValue('impact', 'very critical')) + ), + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'no CI to attach (empty array)', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'no CI to attach (null)', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + null, /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'caller unknown', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1000))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong values for impact and urgency', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '6', /* sImpact */ + '7', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong password', + 'args' => array( + 'admin', /* sLogin */ + 'xxxxx', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong login', + 'args' => array( + 'xxxxx', /* sLogin */ + 'yyyyy', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), +); + + +class TestSoap extends TestSoapWebService +{ + static public function GetName() {return 'Test SOAP';} + static public function GetDescription() {return 'Do basic stuff to test the SOAP capability';} + + protected function DoExecute() + { + echo "

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

    \n"; + + global $aSOAPMapping; + + // this file is generated dynamically with location = here + $sWsdlUri = 'http'.(empty($_SERVER['HTTPS']) ? '' : 's').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php'; + + ini_set("soap.wsdl_cache_enabled","0"); + $this->m_SoapClient = new SoapClient + ( + $sWsdlUri, + array( + 'classmap' => $aSOAPMapping, + 'trace' => 1, + ) + ); + + if (false) + { + self::DumpVariable($this->m_SoapClient->__getTypes()); + } + + global $aWebServices; + foreach ($aWebServices as $iPos => $aWebService) + { + echo "

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

    \n"; + echo "

    {$aWebService['explain result']}

    \n"; + + try + { + $oRes = call_user_func_array(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; + } + 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'"); + } + } + } +} + +class TestWebServicesDirect extends TestBizModel +{ + static public function GetName() {return 'Test web services locally';} + static public function GetDescription() {return 'Invoke the service directly (troubleshooting)';} + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function DoExecute() + { + $oWebServices = new WebServices(); + + global $aWebServices; + foreach ($aWebServices as $iPos => $aWebService) + { + echo "

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

    \n"; + echo "

    {$aWebService['explain result']}

    \n"; + $oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']); + self::DumpVariable($oRes); + + if ($oRes instanceof SOAPResult) + { + $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 TestTriggerAndEmail extends TestBizModel +{ + static public function GetName() {return 'Test trigger and email';} + static public function GetDescription() {return 'Create a trigger and an email, then activates the trigger';} + + static public function GetConfigFile() {return '../config-itop.php';} + + 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("bizPerson"); + $oMyPerson->Set("name", "testemail1"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "romain.quetiez@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail2"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "denis.flaven@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail3"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "erwan.taloc@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyServer = MetaModel::NewObject("bizServer"); + $oMyServer->Set("name", "wfr.terminator.com"); + $oMyServer->Set("severity", "low"); + $oMyServer->Set("status", "production"); + $oMyServer->Set("org_id", 2); + $oMyServer->Set("location_id", 2); + $iServerId = $this->ObjectToDB($oMyServer, true); + + $oMyTrigger = MetaModel::NewObject("TriggerOnStateEnter"); + $oMyTrigger->Set("description", "Testor"); + $oMyTrigger->Set("target_class", "bizServer"); + $oMyTrigger->Set("state", "Shipped"); + $iTriggerId = $this->ObjectToDB($oMyTrigger, true); + + // Error in OQL field(s) + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE naime = 'Dali'", + "SELECT bizServer", + 'romain.quetiez@hp.com' + ); + + // Error: no recipient + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "", + "", + 'romain.quetiez@hp.com' + ); + + // Test + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "SELECT bizPerson", + 'romain.quetiez@hp.com' + ); + + // Test failing because of a wrong test recipient address + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "", + 'toto@walibi.bg' + ); + + // Normal behavior + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'enabled', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "", + 'romain.quetiez@hp.com' + ); + + // Does nothing, because it is disabled + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'disabled', + "SELECT bizPerson WHERE name = 'testemail%'", + "", + 'romain.quetiez@hp.com' + ); + + $oMyTrigger->DoActivate($oMyServer->ToArgs('this')); + + return true; + } +} +?>