mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-03 08:04:17 +01:00
Compare commits
87 Commits
3.1.0-desi
...
issue/6716
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bd72409f1 | ||
|
|
3eb06f8ada | ||
|
|
44c189223e | ||
|
|
7fdbb59c30 | ||
|
|
5acf38ac36 | ||
|
|
3e258f32cc | ||
|
|
3c51d6fb98 | ||
|
|
7cfe1389aa | ||
|
|
7292a8540b | ||
|
|
f65c690462 | ||
|
|
ecf8bc42fa | ||
|
|
e76728b2bf | ||
|
|
faba812fc1 | ||
|
|
add433d702 | ||
|
|
9c99cb35e5 | ||
|
|
9d392ad167 | ||
|
|
ea8509db1f | ||
|
|
df25ce76b6 | ||
|
|
e946fc65fc | ||
|
|
d203e075a8 | ||
|
|
dbe2f66539 | ||
|
|
0d8ff7bbac | ||
|
|
48eb022824 | ||
|
|
03c9ffc033 | ||
|
|
61a9a4ac65 | ||
|
|
38962e68ee | ||
|
|
483dbb4a5d | ||
|
|
1f4dcc4f9e | ||
|
|
e86309669e | ||
|
|
6d6f55acf7 | ||
|
|
6ebcd44bb1 | ||
|
|
f8fb51fea0 | ||
|
|
bf768311c2 | ||
|
|
d797436786 | ||
|
|
b508c0d983 | ||
|
|
351893bbdd | ||
|
|
59e4bb028f | ||
|
|
6d895371ec | ||
|
|
ab91631e68 | ||
|
|
3366bae0ab | ||
|
|
03b484c349 | ||
|
|
70081ecf33 | ||
|
|
575ba1cd7b | ||
|
|
d130959692 | ||
|
|
a8c689c6c0 | ||
|
|
1990ccb5d8 | ||
|
|
e107be56e4 | ||
|
|
0f8e87e001 | ||
|
|
d92d2b5e9e | ||
|
|
ebd0136773 | ||
|
|
f6653e1594 | ||
|
|
65bb76b9e3 | ||
|
|
f238593966 | ||
|
|
d951d3b872 | ||
|
|
ccceb870e3 | ||
|
|
ed6df77cbb | ||
|
|
1ad28312ec | ||
|
|
f002aa04cd | ||
|
|
b86d70623e | ||
|
|
fe3467309d | ||
|
|
851ab9c356 | ||
|
|
aef3c2e609 | ||
|
|
5212e15cc4 | ||
|
|
f04fc546b5 | ||
|
|
caf3076b12 | ||
|
|
c4c400d852 | ||
|
|
6cc4cc4fb6 | ||
|
|
d7495af207 | ||
|
|
13ad98b9b3 | ||
|
|
4be54fdd65 | ||
|
|
6d13397ba1 | ||
|
|
48e7e0309a | ||
|
|
2ce9b2afaf | ||
|
|
d64a91d4ce | ||
|
|
c0c8a13864 | ||
|
|
5ffa41bc16 | ||
|
|
d2eef06276 | ||
|
|
77b14c516e | ||
|
|
880a824f2f | ||
|
|
f7f1b5f399 | ||
|
|
85f66f5e0c | ||
|
|
a5c980113b | ||
|
|
18efbfa803 | ||
|
|
7aa478d6ff | ||
|
|
97700dbf15 | ||
|
|
c25c69d746 | ||
|
|
734a788340 |
@@ -66,6 +66,8 @@ gitGraph
|
||||
commit id: "2023-06-19" tag: "3.1.0-beta" type: REVERSE
|
||||
commit id: "2023-07-26" tag: "3.1.0-1" type: HIGHLIGHT
|
||||
branch support/3.1 order: 840
|
||||
checkout support/3.1
|
||||
commit id: "2023-08-09" tag: "3.1.0-2"
|
||||
```
|
||||
|
||||
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
||||
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
||||
|
||||
@@ -34,15 +34,15 @@ class AuditCategory extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "application, grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array('name'),
|
||||
"db_table" => "priv_auditcategory",
|
||||
"db_key_field" => "id",
|
||||
"category" => "application,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array('name'),
|
||||
"db_table" => "priv_auditcategory",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
|
||||
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
@@ -35,7 +35,7 @@ class AuditDomain extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "application, grant_by_profile",
|
||||
"category" => "application,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"complementary_name_attcode" => array('description'),
|
||||
|
||||
@@ -35,7 +35,7 @@ class AuditRule extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "application, grant_by_profile",
|
||||
"category" => "application,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
|
||||
@@ -1246,6 +1246,10 @@ JS
|
||||
} else {
|
||||
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams);
|
||||
}
|
||||
$sClassDescription = MetaModel::GetClassDescription($sClass);
|
||||
if (utils::IsNotNullOrEmptyString($sClassDescription)) {
|
||||
$oBlock->SetClassDescription($sClassDescription);
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ class QueryOQL extends Query
|
||||
}
|
||||
catch
|
||||
(OQLException $e) {
|
||||
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::Format('UI:RunQuery:Error'), $e->getHtmlDesc())
|
||||
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:RunQuery:Error'), $e->getHtmlDesc())
|
||||
->SetIsClosable(false)
|
||||
->SetIsCollapsible(false);
|
||||
$oAlert->AddCSSClass('mb-5');
|
||||
|
||||
@@ -178,17 +178,18 @@ class UILinksWidget
|
||||
|
||||
$oDisplayBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}",
|
||||
array(
|
||||
'menu' => false,
|
||||
[
|
||||
'menu' => false,
|
||||
'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}",
|
||||
'table_id' => "add_{$sLinkedSetId}",
|
||||
'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}",
|
||||
'selection_mode' => true,
|
||||
'json' => $sJson,
|
||||
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
|
||||
'query_params' => $oFilter->GetInternalParams(),
|
||||
'hidden_criteria' => $sAlreadyLinkedExpression,
|
||||
)));
|
||||
'table_id' => "add_{$sLinkedSetId}",
|
||||
'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}",
|
||||
'selection_mode' => true,
|
||||
'json' => $sJson,
|
||||
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
|
||||
'query_params' => $oFilter->GetInternalParams(),
|
||||
'hidden_criteria' => $sAlreadyLinkedExpression,
|
||||
'submit_on_load' => false,
|
||||
]));
|
||||
|
||||
$oBlock->AddForm();
|
||||
}
|
||||
|
||||
@@ -1397,13 +1397,23 @@ class utils
|
||||
return APPROOT . 'env-' . MetaModel::GetEnvironment() . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string A path to the folder into which data can be written
|
||||
* @internal
|
||||
* @since N°6097 2.7.10 3.0.4 3.1.1
|
||||
*/
|
||||
public static function GetDataPath(): string
|
||||
{
|
||||
return APPROOT.'data/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string A path to a folder into which any module can store cache data
|
||||
* The corresponding folder is created or cleaned upon code compilation
|
||||
*/
|
||||
public static function GetCachePath()
|
||||
{
|
||||
return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
|
||||
return static::GetDataPath().'cache-'.MetaModel::GetEnvironment().'/';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,9 +59,16 @@ class DbConnectionWrapper
|
||||
* Use this to register a mock that will handle {@see mysqli::query()}
|
||||
*
|
||||
* @param \mysqli|null $oMysqli
|
||||
* @since 3.0.4 3.1.1 3.2.0 Param $oMysqli becomes nullable
|
||||
*/
|
||||
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli): void
|
||||
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli = null): void
|
||||
{
|
||||
static::$oDbCnxMockableForQuery = $oMysqli;
|
||||
if (is_null($oMysqli)) {
|
||||
// Reset to standard connection
|
||||
static::$oDbCnxMockableForQuery = static::$oDbCnxStandard;
|
||||
}
|
||||
else {
|
||||
static::$oDbCnxMockableForQuery = $oMysqli;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -650,6 +650,9 @@ class ActionEmail extends ActionNotification
|
||||
$aMessageContent['subject'] = 'TEST['.$aMessageContent['subject'].']';
|
||||
$aMessageContent['body'] = $sTestBody;
|
||||
$aMessageContent['to'] = $this->Get('test_recipient');
|
||||
// N°6677 Ensure emails in test are never sent to cc'd and bcc'd addresses
|
||||
$aMessageContent['cc'] = '';
|
||||
$aMessageContent['bcc'] = '';
|
||||
}
|
||||
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
|
||||
$aMessageContent['in_reply_to'] = $aMessageContent['references'];
|
||||
|
||||
@@ -431,6 +431,7 @@ class CMDBSource
|
||||
{
|
||||
self::$m_sDBName = '';
|
||||
}
|
||||
self::_TablesInfoCacheReset(); // reset the table info cache!
|
||||
}
|
||||
|
||||
public static function CreateTable($sQuery)
|
||||
@@ -627,18 +628,24 @@ class CMDBSource
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Exception $e
|
||||
* @param Exception $e
|
||||
* @param bool $bForQuery to get the proper DB connection
|
||||
* @param bool $bCheckMysqliErrno if false won't try to check for mysqli::errno value
|
||||
*
|
||||
* @since 2.7.1
|
||||
* @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6643 new bCheckMysqliErrno parameter as a workaround for mysqli::errno cannot be mocked
|
||||
*/
|
||||
private static function LogDeadLock(Exception $e, $bForQuery = false)
|
||||
private static function LogDeadLock(Exception $e, $bForQuery = false, $bCheckMysqliErrno = true)
|
||||
{
|
||||
// checks MySQL error code
|
||||
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
|
||||
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
|
||||
return;
|
||||
if ($bCheckMysqliErrno) {
|
||||
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
|
||||
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$iMySqlErrorNo = "N/A";
|
||||
}
|
||||
|
||||
// Get error info
|
||||
@@ -665,7 +672,10 @@ class CMDBSource
|
||||
);
|
||||
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
|
||||
|
||||
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
|
||||
IssueLog::Error($sMessage, LogChannels::DEADLOCK, [
|
||||
'exception.class' => get_class($e),
|
||||
'exception.message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -219,6 +219,19 @@
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="AuditDomain" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>application, grant_by_profile</category>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString"/>
|
||||
<field id="description" xsi:type="AttributeString"/>
|
||||
<field id="icon" xsi:type="AttributeImage"/>
|
||||
<field id="categories_list" xsi:type="AttributeLinkedSet"/>
|
||||
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
|
||||
</fields>
|
||||
</class>
|
||||
<class id="Query" _delta="define">
|
||||
<!-- Generated by toolkit/export-class-to-meta.php -->
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventException;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Service\Events\EventServiceLog;
|
||||
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
|
||||
|
||||
/**
|
||||
@@ -203,6 +205,8 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
const MAX_UPDATE_LOOP_COUNT = 10;
|
||||
|
||||
private $aEventListeners = [];
|
||||
|
||||
/**
|
||||
* DBObject constructor.
|
||||
*
|
||||
@@ -255,6 +259,10 @@ abstract class DBObject implements iDisplay
|
||||
$this->RegisterEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RegisterCRUDListener
|
||||
* @see EventService::RegisterListener()
|
||||
*/
|
||||
protected function RegisterEventListeners()
|
||||
{
|
||||
}
|
||||
@@ -6181,6 +6189,51 @@ abstract class DBObject implements iDisplay
|
||||
return OPT_ATT_NORMAL;
|
||||
}
|
||||
|
||||
public final function GetListeners(): array
|
||||
{
|
||||
$aListeners = [];
|
||||
foreach ($this->aEventListeners as $aEventListener) {
|
||||
$aListeners = array_merge($aListeners, $aEventListener);
|
||||
}
|
||||
return $aListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback for a specific event. The method to call will be saved in the object instance itself whereas calling {@see EventService::RegisterListener()} would
|
||||
* save a callable (thus the method name AND the whole DBObject instance)
|
||||
*
|
||||
* @param string $sEvent corresponding event
|
||||
* @param string $callback The callback method to call
|
||||
* @param float $fPriority optional priority for callback order
|
||||
* @param string $sModuleId
|
||||
*
|
||||
* @see EventService::RegisterListener()
|
||||
*
|
||||
* @since 3.1.0-3 3.1.1 3.2.0 N°6716
|
||||
*/
|
||||
final protected function RegisterCRUDListener(string $sEvent, string $callback, float $fPriority = 0.0, string $sModuleId = '')
|
||||
{
|
||||
$aEventCallbacks = $this->aEventListeners[$sEvent] ?? [];
|
||||
|
||||
$aEventCallbacks[] = array(
|
||||
'event' => $sEvent,
|
||||
'callback' => $callback,
|
||||
'priority' => $fPriority,
|
||||
'module' => $sModuleId,
|
||||
);
|
||||
usort($aEventCallbacks, function ($a, $b) {
|
||||
$fPriorityA = $a['priority'];
|
||||
$fPriorityB = $b['priority'];
|
||||
if ($fPriorityA == $fPriorityB) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($fPriorityA < $fPriorityB) ? -1 : 1;
|
||||
});
|
||||
|
||||
$this->aEventListeners[$sEvent] = $aEventCallbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sEvent
|
||||
* @param array $aEventData
|
||||
@@ -6192,15 +6245,53 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function FireEvent(string $sEvent, array $aEventData = array()): void
|
||||
{
|
||||
if (EventService::IsEventRegistered($sEvent)) {
|
||||
$aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
|
||||
$aEventData['object'] = $this;
|
||||
$aEventSources = [$this->m_sObjectUniqId];
|
||||
foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass) {
|
||||
$aEventSources[] = $sClass;
|
||||
$aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
|
||||
$aEventData['object'] = $this;
|
||||
|
||||
// Call local listeners first
|
||||
$aEventCallbacks = $this->aEventListeners[$sEvent] ?? [];
|
||||
$oFirstException = null;
|
||||
$sFirstExceptionMessage = '';
|
||||
foreach ($aEventCallbacks as $aEventCallback) {
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sCallback = $aEventCallback['callback'];
|
||||
if (!method_exists($this, $sCallback)) {
|
||||
EventServiceLog::Error("Callback '".get_class($this).":$sCallback' does not exist");
|
||||
continue;
|
||||
}
|
||||
EventServiceLog::Debug("Fire event '$sEvent' calling '".get_class($this).":$sCallback'");
|
||||
try {
|
||||
call_user_func([$this, $sCallback], new EventData($sEvent, null, $aEventData));
|
||||
}
|
||||
catch (EventException $e) {
|
||||
EventServiceLog::Error("Event '$sEvent' for '$sCallback'} failed with blocking error: ".$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$sMessage = "Event '$sEvent' for '$sCallback'} failed with non-blocking error: ".$e->getMessage();
|
||||
EventServiceLog::Error($sMessage);
|
||||
if (is_null($oFirstException)) {
|
||||
$sFirstExceptionMessage = $sMessage;
|
||||
$oFirstException = $e;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$oKPI->ComputeStats('FireEvent', $sEvent);
|
||||
}
|
||||
EventService::FireEvent(new EventData($sEvent, $aEventSources, $aEventData));
|
||||
}
|
||||
if (!is_null($oFirstException)) {
|
||||
throw new Exception($sFirstExceptionMessage, $oFirstException->getCode(), $oFirstException);
|
||||
}
|
||||
|
||||
// Call global event listeners
|
||||
if (!EventService::IsEventRegistered($sEvent)) {
|
||||
return;
|
||||
}
|
||||
$aEventSources = [];
|
||||
foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass) {
|
||||
$aEventSources[] = $sClass;
|
||||
}
|
||||
EventService::FireEvent(new EventData($sEvent, $aEventSources, $aEventData));
|
||||
}
|
||||
|
||||
//////////////////
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
@@ -56,10 +56,11 @@ class Dict
|
||||
* @param $sLanguageCode
|
||||
*
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @since 3.0.4 3.1.1 3.2.0 Param $sLanguageCode becomes nullable
|
||||
*/
|
||||
public static function SetUserLanguage($sLanguageCode)
|
||||
public static function SetUserLanguage($sLanguageCode = null)
|
||||
{
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
if (!is_null($sLanguageCode) && !array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
{
|
||||
throw new DictExceptionUnknownLanguage($sLanguageCode);
|
||||
}
|
||||
@@ -115,33 +116,50 @@ class Dict
|
||||
* @return string
|
||||
*/
|
||||
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
{
|
||||
$aInfo = self::GetLabelAndLangCode($sStringCode, $sDefault, $bUserLanguageOnly);
|
||||
return $aInfo['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a localised string from the dictonary with its associated lang code
|
||||
*
|
||||
* @param string $sStringCode The code identifying the dictionary entry
|
||||
* @param string $sDefault Default value if there is no match in the dictionary
|
||||
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
|
||||
*
|
||||
* @return array{
|
||||
* lang: string, label: string
|
||||
* } with localized label string and used lang code
|
||||
*/
|
||||
private static function GetLabelAndLangCode($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
{
|
||||
// Attempt to find the string in the user language
|
||||
//
|
||||
$sLangCode = self::GetUserLanguage();
|
||||
self::InitLangIfNeeded($sLangCode);
|
||||
|
||||
if (!array_key_exists($sLangCode, self::$m_aData))
|
||||
if (! array_key_exists($sLangCode, self::$m_aData))
|
||||
{
|
||||
IssueLog::Warning("Cannot find $sLangCode in dictionnaries. default labels displayed");
|
||||
IssueLog::Warning("Cannot find $sLangCode in all registered dictionaries.");
|
||||
// It may happen, when something happens before the dictionaries get loaded
|
||||
return $sStringCode;
|
||||
return [ 'label' => $sStringCode, 'lang' => $sLangCode ];
|
||||
}
|
||||
$aCurrentDictionary = self::$m_aData[$sLangCode];
|
||||
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
|
||||
{
|
||||
return $aCurrentDictionary[$sStringCode];
|
||||
return [ 'label' => $aCurrentDictionary[$sStringCode], 'lang' => $sLangCode ];
|
||||
}
|
||||
if (!$bUserLanguageOnly)
|
||||
{
|
||||
// Attempt to find the string in the default language
|
||||
//
|
||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||
|
||||
|
||||
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
|
||||
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
return $aDefaultDictionary[$sStringCode];
|
||||
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => self::$m_sDefaultLanguage ];
|
||||
}
|
||||
// Attempt to find the string in english
|
||||
//
|
||||
@@ -150,17 +168,17 @@ class Dict
|
||||
$aDefaultDictionary = self::$m_aData['EN US'];
|
||||
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
return $aDefaultDictionary[$sStringCode];
|
||||
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => 'EN US' ];
|
||||
}
|
||||
}
|
||||
// Could not find the string...
|
||||
//
|
||||
if (is_null($sDefault))
|
||||
{
|
||||
return $sStringCode;
|
||||
return [ 'label' => $sStringCode, 'lang' => null ];
|
||||
}
|
||||
|
||||
return $sDefault;
|
||||
return [ 'label' => $sDefault, 'lang' => null ];
|
||||
}
|
||||
|
||||
|
||||
@@ -176,19 +194,25 @@ class Dict
|
||||
*/
|
||||
public static function Format($sFormatCode /*, ... arguments ... */)
|
||||
{
|
||||
$sLocalizedFormat = self::S($sFormatCode);
|
||||
['label' => $sLocalizedFormat, 'lang' => $sLangCode] = self::GetLabelAndLangCode($sFormatCode);
|
||||
|
||||
$aArguments = func_get_args();
|
||||
array_shift($aArguments);
|
||||
|
||||
|
||||
if ($sLocalizedFormat == $sFormatCode)
|
||||
{
|
||||
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
|
||||
return $sFormatCode.' - '.implode(', ', $aArguments);
|
||||
}
|
||||
|
||||
return vsprintf($sLocalizedFormat, $aArguments);
|
||||
try{
|
||||
return vsprintf($sLocalizedFormat, $aArguments);
|
||||
} catch(\Throwable $e){
|
||||
\IssueLog::Error("Cannot format dict key", null, ["sFormatCode" => $sFormatCode, "sLangCode" => $sLangCode, 'exception_msg' => $e->getMessage() ]);
|
||||
return $sFormatCode.' - '.implode(', ', $aArguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize a the entries for a given language (replaces the former Add() method)
|
||||
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
|
||||
@@ -198,7 +222,7 @@ class Dict
|
||||
{
|
||||
self::$m_aData[$sLanguageCode] = $aEntries;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the list of available languages
|
||||
* @param hash $aLanguagesList
|
||||
@@ -259,7 +283,7 @@ class Dict
|
||||
{
|
||||
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
|
||||
require_once($sDictFile);
|
||||
|
||||
|
||||
if (self::GetApcService()->function_exists('apc_store')
|
||||
&& (self::$m_sApplicationPrefix !== null))
|
||||
{
|
||||
@@ -269,7 +293,7 @@ class Dict
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable caching (cached using APC)
|
||||
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
|
||||
@@ -312,14 +336,14 @@ class Dict
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
|
||||
{
|
||||
$aMissing = array(); // Strings missing for the target language
|
||||
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
|
||||
$aNotTranslated = array(); // Strings having the same value in both dictionaries
|
||||
$aOK = array(); // Strings having different values in both dictionaries
|
||||
|
||||
|
||||
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
|
||||
{
|
||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
|
||||
@@ -327,7 +351,7 @@ class Dict
|
||||
$aMissing[$sStringCode] = $sValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
|
||||
{
|
||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
|
||||
@@ -350,7 +374,7 @@ class Dict
|
||||
}
|
||||
return array($aMissing, $aUnexpected, $aNotTranslated, $aOK);
|
||||
}
|
||||
|
||||
|
||||
public static function Dump()
|
||||
{
|
||||
MyHelpers::var_dump_html(self::$m_aData);
|
||||
@@ -373,7 +397,7 @@ class Dict
|
||||
// No need to actually load the strings since it's only used to know the list of languages
|
||||
// at setup time !!
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export all the dictionary entries - of the given language - whose code matches the given prefix
|
||||
* missing entries in the current language will be replaced by entries in the default language
|
||||
@@ -386,7 +410,7 @@ class Dict
|
||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||
$aEntries = array();
|
||||
$iLength = strlen($sStartingWith);
|
||||
|
||||
|
||||
// First prefill the array with entries from the default language
|
||||
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
|
||||
{
|
||||
@@ -395,7 +419,7 @@ class Dict
|
||||
$aEntries[$sCode] = $sEntry;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Now put (overwrite) the entries for the user language
|
||||
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
|
||||
{
|
||||
|
||||
@@ -1138,7 +1138,7 @@ class DeprecatedCallsLog extends LogAPI
|
||||
parent::Enable($sTargetFile);
|
||||
|
||||
if (
|
||||
(false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME))
|
||||
(false === defined('ITOP_PHPUNIT_RUNNING_CONSTANT_NAME'))
|
||||
&& static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)
|
||||
) {
|
||||
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler'], E_DEPRECATED | E_USER_DEPRECATED);
|
||||
|
||||
@@ -6298,6 +6298,13 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production')
|
||||
{
|
||||
// Startup on a new environment is not supported
|
||||
static $bStarted = false;
|
||||
if ($bStarted) {
|
||||
return;
|
||||
}
|
||||
$bStarted = true;
|
||||
|
||||
self::$m_sEnvironment = $sEnvironment;
|
||||
|
||||
try {
|
||||
@@ -6529,6 +6536,19 @@ abstract class MetaModel
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Used for resetting the configuration during automated tests
|
||||
|
||||
* @param \Config $oConfiguration
|
||||
*
|
||||
* @return void
|
||||
* @since 3.0.4 3.1.1 3.2.0
|
||||
*/
|
||||
public static function SetConfig(Config $oConfiguration)
|
||||
{
|
||||
self::$m_oConfig = $oConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
@@ -6810,25 +6830,21 @@ abstract class MetaModel
|
||||
* $bMustBeFound=false)
|
||||
* @throws CoreException if no result found and $bMustBeFound=true
|
||||
* @throws ArchivedObjectException if archive mode disabled and result is archived and $bMustBeFound=true
|
||||
* @throws \Exception
|
||||
*
|
||||
*/
|
||||
public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null)
|
||||
{
|
||||
$oObject = self::GetObjectWithArchive($sClass, $iKey, $bMustBeFound, $bAllowAllData, $aModifierProperties);
|
||||
|
||||
if (empty($oObject))
|
||||
{
|
||||
if (empty($oObject)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!utils::IsArchiveMode() && $oObject->IsArchived())
|
||||
{
|
||||
if (!utils::IsArchiveMode() && $oObject->IsArchived()) {
|
||||
if ($bMustBeFound) {
|
||||
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $oObject;
|
||||
@@ -7611,14 +7627,12 @@ abstract class MetaModel
|
||||
// Build the list of available extensions
|
||||
//
|
||||
$aInterfaces = [
|
||||
'iApplicationUIExtension',
|
||||
'iPreferencesExtension',
|
||||
'iApplicationObjectExtension',
|
||||
'iLoginFSMExtension',
|
||||
'iLoginUIExtension',
|
||||
'iLogoutExtension',
|
||||
'iQueryModifier',
|
||||
'iOnClassInitialization',
|
||||
'iLoginUIExtension',
|
||||
'iPreferencesExtension',
|
||||
'iApplicationUIExtension',
|
||||
'iApplicationObjectExtension',
|
||||
'iPopupMenuExtension',
|
||||
'iPageUIExtension',
|
||||
'iPageUIBlockExtension',
|
||||
@@ -7632,10 +7646,12 @@ abstract class MetaModel
|
||||
'iBackofficeDictEntriesExtension',
|
||||
'iBackofficeDictEntriesPrefixesExtension',
|
||||
'iPortalUIExtension',
|
||||
'ModuleHandlerApiInterface',
|
||||
'iNewsroomProvider',
|
||||
'iQueryModifier',
|
||||
'iOnClassInitialization',
|
||||
'iModuleExtension',
|
||||
'iKPILoggerExtension',
|
||||
'ModuleHandlerApiInterface',
|
||||
'iNewsroomProvider',
|
||||
];
|
||||
foreach ($aInterfaces as $sInterface) {
|
||||
self::$m_aExtensionClassNames[$sInterface] = array();
|
||||
|
||||
@@ -761,14 +761,25 @@ class UserRights
|
||||
protected static $m_aCacheContactPictureAbsUrl = [];
|
||||
/** @var UserRightsAddOnAPI $m_oAddOn */
|
||||
protected static $m_oAddOn;
|
||||
protected static $m_oUser;
|
||||
protected static $m_oRealUser;
|
||||
protected static $m_oUser = null;
|
||||
protected static $m_oRealUser = null;
|
||||
protected static $m_sSelfRegisterAddOn = null;
|
||||
protected static $m_aAdmins = array();
|
||||
protected static $m_aPortalUsers = array();
|
||||
/** @var array array('sName' => $sName, 'bSuccess' => $bSuccess); */
|
||||
private static $m_sLastLoginStatus = null;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @since 3.0.4 3.1.1 3.2.0
|
||||
*/
|
||||
protected static function ResetCurrentUserData()
|
||||
{
|
||||
self::$m_oUser = null;
|
||||
self::$m_oRealUser = null;
|
||||
self::$m_sLastLoginStatus = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sModuleName
|
||||
*
|
||||
@@ -787,8 +798,7 @@ class UserRights
|
||||
}
|
||||
self::$m_oAddOn = new $sModuleName;
|
||||
self::$m_oAddOn->Init();
|
||||
self::$m_oUser = null;
|
||||
self::$m_oRealUser = null;
|
||||
self::ResetCurrentUserData();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -855,6 +865,8 @@ class UserRights
|
||||
*/
|
||||
public static function Login($sLogin, $sAuthentication = 'any')
|
||||
{
|
||||
static::Logoff();
|
||||
|
||||
$oUser = self::FindUser($sLogin, $sAuthentication);
|
||||
if (is_null($oUser))
|
||||
{
|
||||
@@ -872,6 +884,17 @@ class UserRights
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @since 3.0.4 3.1.1 3.2.0
|
||||
*/
|
||||
public static function Logoff()
|
||||
{
|
||||
self::ResetCurrentUserData();
|
||||
Dict::SetUserLanguage(null);
|
||||
self::_ResetSessionCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sLogin Login of the user to check the credentials for
|
||||
* @param string $sPassword
|
||||
|
||||
@@ -20,6 +20,8 @@ $ibo-dashlet-badge--icon--size: 48px !default;
|
||||
|
||||
$ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default;
|
||||
|
||||
$ibo-dashlet-badge--body--tooltip-title--margin-bottom: $ibo-spacing-500 !default;
|
||||
|
||||
/* CSS variables (can be changed directly from the browser) */
|
||||
:root {
|
||||
--ibo-dashlet-badge--min-width: #{$ibo-dashlet-badge--min-width};
|
||||
@@ -74,18 +76,27 @@ $ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default;
|
||||
@extend %ibo-hyperlink-inherited-colors;
|
||||
}
|
||||
}
|
||||
.ibo-dashlet-badge--action-list-count{
|
||||
margin-right: $ibo-dashlet-badge--action-list-count--margin-right;
|
||||
@extend %ibo-font-ral-bol-450;
|
||||
|
||||
.ibo-dashlet-badge--action-list-count {
|
||||
margin-right: $ibo-dashlet-badge--action-list-count--margin-right;
|
||||
@extend %ibo-font-ral-bol-450;
|
||||
}
|
||||
.ibo-dashlet-badge--action-list-label{
|
||||
display: inline-block;
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
|
||||
.ibo-dashlet-badge--action-list-label {
|
||||
display: inline-block;
|
||||
@extend %ibo-text-truncated-with-ellipsis;
|
||||
}
|
||||
.ibo-dashlet-badge--action-create{
|
||||
@extend %ibo-baseline-centered-content;
|
||||
@extend %ibo-font-size-150;
|
||||
|
||||
.ibo-dashlet-badge--action-create {
|
||||
@extend %ibo-baseline-centered-content;
|
||||
@extend %ibo-font-size-150;
|
||||
}
|
||||
.ibo-dashlet-badge--action-create-icon{
|
||||
margin-right: $ibo-dashlet-badge--action-icon--margin-right;
|
||||
|
||||
.ibo-dashlet-badge--action-create-icon {
|
||||
margin-right: $ibo-dashlet-badge--action-icon--margin-right;
|
||||
}
|
||||
|
||||
.ibo-dashlet-badge--body--tooltip-title {
|
||||
@extend %ibo-font-weight-600;
|
||||
margin-bottom: $ibo-dashlet-badge--body--tooltip-title--margin-bottom;
|
||||
}
|
||||
|
||||
@@ -344,6 +344,7 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
|
||||
while ($oAttachment = $oSet->Fetch())
|
||||
{
|
||||
$oTempAttachment = clone $oAttachment;
|
||||
$oTempAttachment->Set('expire', time() + utils::GetConfig()->Get('draft_attachments_lifetime'));
|
||||
$oTempAttachment->Set('item_id', null);
|
||||
$oTempAttachment->Set('temp_id', $sTempId);
|
||||
$oTempAttachment->DBInsert();
|
||||
|
||||
@@ -235,13 +235,16 @@ class DBRestore extends DBBackup
|
||||
if (in_array($oFileInfo->getFilename(), $aStandardFiles)) {
|
||||
continue;
|
||||
}
|
||||
if (strncmp($oFileInfo->getPathname(), $sDataDir.'/production-modules', strlen($sDataDir.'/production-modules')) == 0) {
|
||||
// Normalize filenames to cope with Windows backslashes
|
||||
$sPath = str_replace('\\', '/', $oFileInfo->getPathname());
|
||||
$sRefPath = str_replace('\\', '/', $sDataDir.'/production-modules');
|
||||
if (strncmp($sPath, $sRefPath, strlen($sRefPath)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$aExtraFiles[$oFileInfo->getPathname()] = APPROOT.substr($oFileInfo->getPathname(), strlen($sDataDir));
|
||||
}
|
||||
|
||||
|
||||
return $aExtraFiles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
<group id="Audit" _delta="define">
|
||||
<classes>
|
||||
<!-- This class list is also present in AdminTools group -->
|
||||
<class id="AuditDomain"/>
|
||||
<class id="AuditCategory"/>
|
||||
<class id="AuditRule"/>
|
||||
<class id="ResourceRunQueriesMenu"/>
|
||||
@@ -166,6 +167,7 @@
|
||||
<class id="URP_UserProfile"/>
|
||||
<class id="URP_Profiles"/>
|
||||
<!-- Audit group -->
|
||||
<class id="AuditDomain"/>
|
||||
<class id="AuditCategory"/>
|
||||
<class id="AuditRule"/>
|
||||
<!-- Query group -->
|
||||
|
||||
@@ -99,9 +99,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Class:Contract/Attribute:organization_name' => 'Nom client',
|
||||
'Class:Contract/Attribute:organization_name+' => 'Nom commun',
|
||||
'Class:Contract/Attribute:contacts_list' => 'Contacts',
|
||||
'Class:Contract/Attribute:contacts_list+' => 'Tous les contacts for ce contrat client',
|
||||
'Class:Contract/Attribute:contacts_list+' => 'Tous les contacts pour ce contrat client',
|
||||
'Class:Contract/Attribute:documents_list' => 'Documents',
|
||||
'Class:Contract/Attribute:documents_list+' => 'Tous les documents for ce contrat client',
|
||||
'Class:Contract/Attribute:documents_list+' => 'Tous les documents pour ce contrat client',
|
||||
'Class:Contract/Attribute:description' => 'Description',
|
||||
'Class:Contract/Attribute:description+' => '',
|
||||
'Class:Contract/Attribute:start_date' => 'Date de début',
|
||||
|
||||
@@ -157,6 +157,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
||||
'Class:ProviderContract/Attribute:contracttype_name' => 'Vertragstyp-Name',
|
||||
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
||||
'Class:ProviderContract/Attribute:services_list' => 'Services',
|
||||
'Class:ProviderContract/Attribute:services_list+' => 'Alle für diesen Vertrag erworbenen Services',
|
||||
));
|
||||
|
||||
//
|
||||
|
||||
@@ -169,6 +169,8 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
||||
'Class:ProviderContract/Attribute:contracttype_name' => 'Contract type name',
|
||||
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
||||
'Class:ProviderContract/Attribute:services_list' => 'Services',
|
||||
'Class:ProviderContract/Attribute:services_list+' => 'All the services purchased with this contract',
|
||||
));
|
||||
|
||||
//
|
||||
|
||||
@@ -157,6 +157,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Class:ProviderContract/Attribute:contracttype_id+' => '',
|
||||
'Class:ProviderContract/Attribute:contracttype_name' => 'Nom Type de contrat',
|
||||
'Class:ProviderContract/Attribute:contracttype_name+' => '',
|
||||
'Class:ProviderContract/Attribute:services_list' => 'Services',
|
||||
'Class:ProviderContract/Attribute:services_list+' => 'Tous les services achetés par ce contrat',
|
||||
));
|
||||
|
||||
//
|
||||
|
||||
@@ -837,7 +837,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:RunQuery:DevelopedOQLCount' => 'Developed OQL for count',
|
||||
'UI:RunQuery:ResultSQLCount' => 'Resulting SQL for count',
|
||||
'UI:RunQuery:ResultSQL' => 'Resulting SQL',
|
||||
'UI:RunQuery:Error' => 'An error occured while running the query',
|
||||
'UI:RunQuery:Error' => 'An error occured while running the query: %1$s',
|
||||
'UI:Query:UrlForExcel' => 'URL to use for MS-Excel web queries',
|
||||
'UI:Query:UrlV1' => 'The list of fields has been left unspecified. The page <em>export-V2.php</em> cannot be invoked without this information. Therefore, the URL suggested here below points to the legacy page: <em>export.php</em>. This legacy version of the export has the following limitation: the list of exported fields may vary depending on the output format and the data model of '.ITOP_APPLICATION_SHORT.'. <br/>Should you want to guarantee that the list of exported columns will remain stable on the long run, then you must specify a value for the attribute "Fields" and use the page <em>export-V2.php</em>.',
|
||||
'UI:Schema:Title' => ITOP_APPLICATION_SHORT.' objects schema',
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'Core:DeletedObjectLabel' => '%1s (törölve)',
|
||||
'Core:DeletedObjectLabel' => '%1$s (törölve)',
|
||||
'Core:DeletedObjectTip' => 'A %1$s objektum törölve (%2$s)',
|
||||
'Core:UnknownObjectLabel' => 'Objektum nem található (osztály: %1$s, id: %2$d)',
|
||||
'Core:UnknownObjectTip' => 'Az objektumot nem sikerült megtalálni. Lehet, hogy már törölték egy ideje, és a naplót azóta törölték.',
|
||||
|
||||
@@ -509,7 +509,7 @@ Reméljük, hogy ezt a verziót ugyanúgy kedvelni fogja, mint ahogy mi élvezt
|
||||
'UI:Error:2ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s és %2$s.',
|
||||
'UI:Error:3ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s és %3$s.',
|
||||
'UI:Error:4ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s, %3$s és %4$s.',
|
||||
'UI:Error:IncorrectOQLQuery_Message' => 'Hiba: nem megfelelő OQL lekérdezés: %1$',
|
||||
'UI:Error:IncorrectOQLQuery_Message' => 'Hiba: nem megfelelő OQL lekérdezés: %1$s',
|
||||
'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Hiba történt a lekérdezés futtatása közben: %1$s',
|
||||
'UI:Error:ObjectAlreadyUpdated' => 'Hiba: az objketum már korábban módosításra került.',
|
||||
'UI:Error:ObjectCannotBeUpdated' => 'Hiba: az objektum nem frissíthető.',
|
||||
@@ -715,7 +715,7 @@ Reméljük, hogy ezt a verziót ugyanúgy kedvelni fogja, mint ahogy mi élvezt
|
||||
'UI:CSVReport-Value-Issue-Null' => 'A nulla nem engedélyezett',
|
||||
'UI:CSVReport-Value-Issue-NotFound' => 'Az objektum nincs meg',
|
||||
'UI:CSVReport-Value-Issue-FoundMany' => '%1$d egyezés található',
|
||||
'UI:CSVReport-Value-Issue-Readonly' => 'A \'%1$\'s attribútum csak olvasható (jelenlegi érték: %2$s, várható érték: %3$s)',
|
||||
'UI:CSVReport-Value-Issue-Readonly' => 'A \'%1$s attribútum csak olvasható (jelenlegi érték: %2$s, várható érték: %3$s)',
|
||||
'UI:CSVReport-Value-Issue-Format' => 'A bevitel feldolgozása sikertelen: %1$s',
|
||||
'UI:CSVReport-Value-Issue-NoMatch' => 'A \'%1$s\' attribútum nem várt értéket kapott: nincs egyezés, ellenőrizze a beírást',
|
||||
'UI:CSVReport-Value-Issue-AllowedValues' => 'Allowed \'%1$s\' value(s): %2$s~~',
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* @licence http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'Core:DeletedObjectLabel' => '%1s (削除されました)',
|
||||
'Core:DeletedObjectLabel' => '%1$s (削除されました)',
|
||||
'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)',
|
||||
'Core:UnknownObjectLabel' => 'オブジェクトは見つかりません (クラス: %1$s, id: %2$d)',
|
||||
'Core:UnknownObjectTip' => 'オブジェクトは見つかりません。しばらく前に削除され、その後ログが削除されたかもしれません。',
|
||||
|
||||
@@ -915,7 +915,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '%2$sクラスの%1$dオブジェクトの削除',
|
||||
'UI:Delete:CannotDeleteBecause' => '削除できません: %1$s',
|
||||
'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => '自動的に削除されるべきですが、出来ません。: %1$s',
|
||||
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$',
|
||||
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$s',
|
||||
'UI:Delete:WillBeDeletedAutomatically' => '自動的に削除されます。',
|
||||
'UI:Delete:MustBeDeletedManually' => '手動で削除されるべきです。',
|
||||
'UI:Delete:CannotUpdateBecause_Issue' => '自動的に更新されるべきですが、しかし: %1$s',
|
||||
@@ -1159,8 +1159,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'Enum:Undefined' => '未定義',
|
||||
'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s 日 %2$s 時 %3$s 分 %4$s 秒',
|
||||
'UI:ModifyAllPageTitle' => '全てを修正',
|
||||
'UI:Modify_ObjectsOf_Class' => 'Modifying objects of class %1$s~~',
|
||||
'UI:Modify_N_ObjectsOf_Class' => 'クラス%2$Sの%1$dオブジェクトを修正',
|
||||
'UI:Modify_N_ObjectsOf_Class' => 'クラス%2$sの%1$dオブジェクトを修正',
|
||||
'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'クラス%2$sの%3$d中%1$dを修正',
|
||||
'UI:Menu:ModifyAll' => '修正...',
|
||||
'UI:Menu:ModifyAll_Class' => 'Modify %1$s objects...~~',
|
||||
@@ -1180,7 +1179,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:BulkModify_Count_DistinctValues' => '%1$d 個の個別の値:',
|
||||
'UI:BulkModify:Value_Exists_N_Times' => '%1$s, %2$d 回存在',
|
||||
'UI:BulkModify:N_MoreValues' => '%1$d 個以上の値...',
|
||||
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$にセットしょうとしています。',
|
||||
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$sにセットしょうとしています。',
|
||||
'UI:FailedToApplyStimuli' => 'アクションは失敗しました。',
|
||||
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: クラス%3$sの%2$dオブジェクトを修正',
|
||||
'UI:CaseLogTypeYourTextHere' => 'テキストを入力ください:',
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*
|
||||
*/
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'Core:DeletedObjectLabel' => '%1ы (удален)',
|
||||
'Core:DeletedObjectLabel' => '%1$sы (удален)',
|
||||
'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)',
|
||||
'Core:UnknownObjectLabel' => 'Объект не найден (class: %1$s, id: %2$d)',
|
||||
'Core:UnknownObjectTip' => 'Объект не удается найти. Возможно, он был удален некоторое время назад, и журнал с тех пор был очищен.',
|
||||
|
||||
@@ -497,7 +497,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:Error:MandatoryTemplateParameter_group_by' => 'Parameter group_by je povinný. Skontrolujte definíciu šablóny zobrazenia.',
|
||||
'UI:Error:InvalidGroupByFields' => 'Neplatný zoznam polí pre skupinu podľa: "%1$s".',
|
||||
'UI:Error:UnsupportedStyleOfBlock' => 'Chyba: nepodporovaný štýl bloku: "%1$s".',
|
||||
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Nesprávna definícia spojenia : trieda objektov na manažovanie : %l$s nebol nájdený ako externý kľúč v triede %2$s',
|
||||
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Nesprávna definícia spojenia : trieda objektov na manažovanie : %1$s nebol nájdený ako externý kľúč v triede %2$s',
|
||||
'UI:Error:Object_Class_Id_NotFound' => 'Objekt: %1$s:%2$d nebol nájdený.',
|
||||
'UI:Error:WizardCircularReferenceInDependencies' => 'Chyba: Cyklický odkaz v závislostiach medzi poliami, skontrolujte dátový model.',
|
||||
'UI:Error:UploadedFileTooBig' => 'Nahraný súbor je príliš veľký. (Max povolená veľkosť je %1$s). Ak chcete zmeniť tento limit, obráťte sa na správcu ITOP . (Skontrolujte, PHP konfiguráciu pre upload_max_filesize a post_max_size na serveri).',
|
||||
@@ -1301,7 +1301,7 @@ Keď sú priradené spúštačom, každej akcii je dané číslo "príkazu", šp
|
||||
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Deň v mesiaci pre %1$s',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hodina)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mesiac)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$ (deň v týžni)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (deň v týžni)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (deň v mesiaci)',
|
||||
'UI:DashletGroupBy:MissingGroupBy' => 'Prosím zvoľte pole na ktorom objekty budú zoskupené spolu',
|
||||
'UI:DashletGroupByPie:Label' => 'Koláčový graf',
|
||||
|
||||
@@ -278,7 +278,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s\'nin değeri %2$s olarak atandı (önceki değer: %3$s)',
|
||||
'Change:AttName_SetTo' => '%1$s\'nin değeri %2$s olarak atandı',
|
||||
'Change:Text_AppendedTo_AttName' => '%2$s\'ye %1$s eklendi',
|
||||
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$\'nin değeri deiştirildi, önceki değer: %2$s',
|
||||
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s nin değeri deiştirildi, önceki değer: %2$s',
|
||||
'Change:AttName_Changed' => '%1$s değiştirildi',
|
||||
'Change:AttName_EntryAdded' => '%1$s değiştirilmiş, yeni giriş eklendi.',
|
||||
'Change:State_Changed_NewValue_OldValue' => 'Changed from %2$s to %1$s~~',
|
||||
|
||||
@@ -27,8 +27,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'As correspondências em todos os grupos de menus serão exibidas',
|
||||
'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Nenhum resultado para este filtro de menu',
|
||||
'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Olá %1$s!',
|
||||
'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$',
|
||||
'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$s',
|
||||
'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Abrir menu do usuário',
|
||||
'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filtrar entradas de menu',
|
||||
|
||||
));
|
||||
));
|
||||
|
||||
@@ -117,6 +117,7 @@ $(function()
|
||||
locked_by_someone_else: 'locked_by_someone_else',
|
||||
},
|
||||
},
|
||||
release_lock_promise_resolve: null, // N°4494 - Resolve callback of the Promise used for the action following the log entry send, which must be done only once the lock is released
|
||||
|
||||
// the constructor
|
||||
_create: function () {
|
||||
@@ -500,7 +501,7 @@ $(function()
|
||||
{
|
||||
// Hide all filters' options only if click wasn't on one of them
|
||||
if(($(oEvent.target).closest(this.js_selectors.activity_filter_options_toggler).length === 0)
|
||||
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
|
||||
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
|
||||
this._HideAllFiltersOptions();
|
||||
}
|
||||
},
|
||||
@@ -947,9 +948,9 @@ $(function()
|
||||
|
||||
// Send request to server
|
||||
$.post(
|
||||
GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
|
||||
oParams,
|
||||
'json'
|
||||
GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
|
||||
oParams,
|
||||
'json'
|
||||
)
|
||||
.fail(function (oXHR, sStatus, sErrorThrown) {
|
||||
CombodoModal.OpenErrorModal(sErrorThrown);
|
||||
@@ -972,12 +973,24 @@ $(function()
|
||||
|
||||
// For now, we don't hide the forms as the user may want to add something else
|
||||
me.element.find(me.js_selectors.caselog_entry_form).trigger('clear_entry.caselog_entry_form.itop');
|
||||
|
||||
// Redirect to stimulus
|
||||
// - Convert undefined, null and empty string to null
|
||||
sStimulusCode = ((sStimulusCode ?? '') === '') ? null : sStimulusCode;
|
||||
if (null !== sStimulusCode) {
|
||||
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
|
||||
if (me.options.lock_enabled) {
|
||||
// Use a Promise to ensure that we redirect to the stimulus page ONLY when the lock is released, otherwise we might lock ourselves
|
||||
const oPromise = new Promise(function(resolve) {
|
||||
// Store the resolve callback so we can call it later from outside
|
||||
me.release_lock_promise_resolve = resolve;
|
||||
});
|
||||
oPromise.then(function () {
|
||||
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
|
||||
// Resolve callback is reinitialized in case the redirection fails for any reason and we might need to retry
|
||||
me.release_lock_promise_resolve = null;
|
||||
});
|
||||
} else {
|
||||
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
|
||||
}
|
||||
}
|
||||
})
|
||||
.always(function () {
|
||||
@@ -995,7 +1008,7 @@ $(function()
|
||||
_IncreaseTabTogglerMessagesCounter: function(sCaseLogAttCode){
|
||||
let oTabTogglerCounter = this._GetTabTogglerFromCaseLogAttCode(sCaseLogAttCode).find('[data-role="ibo-activity-panel--tab-title-messages-count"]');
|
||||
let iNewCounterValue = parseInt(oTabTogglerCounter.attr('data-messages-count')) + 1;
|
||||
|
||||
|
||||
oTabTogglerCounter.attr('data-messages-count', iNewCounterValue).text(iNewCounterValue);
|
||||
},
|
||||
/**
|
||||
@@ -1143,11 +1156,10 @@ $(function()
|
||||
else {
|
||||
oParams.operation = 'check_lock_state';
|
||||
}
|
||||
|
||||
$.post(
|
||||
this.options.lock_endpoint,
|
||||
oParams,
|
||||
'json'
|
||||
this.options.lock_endpoint,
|
||||
oParams,
|
||||
'json'
|
||||
)
|
||||
.fail(function (oXHR, sStatus, sErrorThrown) {
|
||||
// In case of HTTP request failure (not lock request), put the details in the JS console
|
||||
@@ -1196,6 +1208,9 @@ $(function()
|
||||
// Tried to release our lock
|
||||
else if ('release_lock' === oParams.operation) {
|
||||
sNewLockStatus = me.enums.lock_status.unknown;
|
||||
if (me.release_lock_promise_resolve !== null) {
|
||||
me.release_lock_promise_resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// Just checked if object was locked
|
||||
@@ -1430,9 +1445,9 @@ $(function()
|
||||
limit_results_length: bLimitResultsLength,
|
||||
};
|
||||
$.post(
|
||||
this.options.load_more_entries_endpoint,
|
||||
oParams,
|
||||
'json'
|
||||
this.options.load_more_entries_endpoint,
|
||||
oParams,
|
||||
'json'
|
||||
)
|
||||
.fail(function (oXHR, sStatus, sErroThrown) {
|
||||
CombodoModal.OpenErrorModal(sErrorThrown);
|
||||
|
||||
@@ -153,16 +153,15 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
|
||||
"dataType": "html"
|
||||
})
|
||||
.done(function (data) {
|
||||
/* N°6152 - Hide during data loading and before open */
|
||||
$('#dlg_'+me.id).hide();
|
||||
$('#dlg_'+me.id).html(data);
|
||||
window[sPromiseId].then(function () {
|
||||
$('#dlg_'+me.id).dialog('open');
|
||||
me.UpdateSizes(null, null);
|
||||
if (me.bDoSearch)
|
||||
{
|
||||
if (me.bDoSearch) {
|
||||
me.SearchObjectsToAdd();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$('#count_'+me.id).change(function () {
|
||||
let c = this.value;
|
||||
me.UpdateButtons(c);
|
||||
|
||||
@@ -105,7 +105,7 @@ if ($oFilter != null)
|
||||
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/UniversalSearch.php';
|
||||
$aExtraParams['table_id'] = '1';
|
||||
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
|
||||
//$aExtraParams['class'] = $sClassName;
|
||||
$aExtraParams['submit_on_load'] = false;
|
||||
$oBlock->Display($oP, 0, $aExtraParams);
|
||||
|
||||
// Search results
|
||||
|
||||
@@ -290,7 +290,6 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
||||
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
|
||||
if (!MetaModel::IsAbstract($sChildClass)) {
|
||||
$oObject = MetaModel::NewObject($sChildClass);
|
||||
$aSources[] = $oObject->GetObjectUniqId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -299,7 +298,6 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
||||
}
|
||||
} else {
|
||||
$oObject = MetaModel::NewObject($sClass);
|
||||
$aSources[] = $oObject->GetObjectUniqId();
|
||||
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sParentClass) {
|
||||
$aSources[] = $sParentClass;
|
||||
}
|
||||
@@ -320,12 +318,19 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
||||
});
|
||||
$aColumns = [
|
||||
'event' => ['label' => Dict::S('UI:Schema:Events:Event')],
|
||||
'listener' => ['label' => Dict::S('UI:Schema:Events:Listener')],
|
||||
'callback' => ['label' => Dict::S('UI:Schema:Events:Listener')],
|
||||
'priority' => ['label' => Dict::S('UI:Schema:Events:Rank')],
|
||||
'module' => ['label' => Dict::S('UI:Schema:Events:Module')],
|
||||
];
|
||||
// Get the object listeners first
|
||||
$aRows = [];
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
if ($oReflectionClass->isInstantiable()) {
|
||||
/** @var DBObject $oClass */
|
||||
$oClass = new $sClass();
|
||||
$aRows = $oClass->GetListeners();
|
||||
}
|
||||
|
||||
foreach ($aListeners as $aListener) {
|
||||
if (is_object($aListener['callback'][0])) {
|
||||
$sListenerClass = $sClass;
|
||||
@@ -343,7 +348,7 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
||||
}
|
||||
$aRows[] = [
|
||||
'event' => $aListener['event'],
|
||||
'listener' => $sListener,
|
||||
'callback' => $sListener,
|
||||
'priority' => $aListener['priority'],
|
||||
'module' => $aListener['module'],
|
||||
];
|
||||
|
||||
@@ -109,6 +109,7 @@ try
|
||||
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/tagadmin.php';
|
||||
$aExtraParams['table_id'] = '1';
|
||||
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
|
||||
$aExtraParams['submit_on_load'] = false;
|
||||
$oBlock->Display($oP, 0, $aExtraParams);
|
||||
|
||||
// Search results
|
||||
|
||||
@@ -1449,17 +1449,12 @@ EOF
|
||||
}
|
||||
$sMethods .= "\n $sCallbackFct\n\n";
|
||||
}
|
||||
if (strpos($sCallback, '::') === false) {
|
||||
$sEventListener = '[$this, \''.$sCallback.'\']';
|
||||
} else {
|
||||
$sEventListener = "'$sCallback'";
|
||||
}
|
||||
|
||||
$sListenerRank = (float)($oListener->GetChildText('rank', '0'));
|
||||
$sEvents .= <<<PHP
|
||||
|
||||
// listenerId = $sListenerId
|
||||
Combodo\iTop\Service\Events\EventService::RegisterListener("$sEventName", $sEventListener, \$this->m_sObjectUniqId, [], null, $sListenerRank, '$sModuleRelativeDir');
|
||||
\$this->RegisterCRUDListener("$sEventName", '$sCallback', $sListenerRank, '$sModuleRelativeDir');
|
||||
PHP;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -892,7 +892,10 @@ class iTopDesignFormat
|
||||
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field/values/value");
|
||||
foreach ($oNodeList as $oNode) {
|
||||
$sCode = $oNode->textContent;
|
||||
$oNode->textContent = '';
|
||||
// N°6562 textContent is readonly, see https://www.php.net/manual/en/class.domnode.php#95545
|
||||
// $oNode->textContent = '';
|
||||
// N°6562 to update text node content we must use the node methods !
|
||||
$oNode->removeChild($oNode->firstChild);
|
||||
$oCodeNode = $oNode->ownerDocument->createElement("code", $sCode);
|
||||
$oNode->appendChild($oCodeNode);
|
||||
}
|
||||
@@ -982,7 +985,14 @@ class iTopDesignFormat
|
||||
if ($oStyleNode) {
|
||||
$this->DeleteNode($oStyleNode);
|
||||
}
|
||||
$oNode->textContent = $sCode;
|
||||
|
||||
// N°6562 textContent is readonly, see https://www.php.net/manual/en/class.domnode.php#95545
|
||||
// $oNode->textContent = $sCode;
|
||||
// N°6562 to update text node content we must use the node methods !
|
||||
// we are using DOMDocument::createTextNode instead of new DOMText because elements created using the constructor are read only
|
||||
// see https://www.php.net/manual/en/domelement.construct.php
|
||||
$oTextContentNode = $this->oDocument->createTextNode($sCode);
|
||||
$oNode->appendChild($oTextContentNode);
|
||||
}
|
||||
}
|
||||
// - Style
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\Dashlet;
|
||||
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\tJSRefreshCallback;
|
||||
use utils;
|
||||
|
||||
class DashletBadge extends DashletContainer
|
||||
{
|
||||
@@ -29,6 +30,11 @@ class DashletBadge extends DashletContainer
|
||||
protected $iCount;
|
||||
/** @var string */
|
||||
protected $sClassLabel;
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.1.1 3.2.0
|
||||
*/
|
||||
protected $sClassDescription;
|
||||
|
||||
/** @var string */
|
||||
protected $sCreateActionUrl;
|
||||
@@ -62,6 +68,7 @@ class DashletBadge extends DashletContainer
|
||||
$this->sCreateActionUrl = $sCreateActionUrl;
|
||||
$this->sCreateActionLabel = $sCreateActionLabel;
|
||||
$this->aRefreshParams = $aRefreshParams;
|
||||
$this->sClassDescription = '';
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +192,37 @@ class DashletBadge extends DashletContainer
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 3.1.1 3.2.0
|
||||
*/
|
||||
public function GetClassDescription(): string
|
||||
{
|
||||
return $this->sClassDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClassDescription
|
||||
*
|
||||
* @return DashletBadge
|
||||
* @since 3.1.1 3.2.0
|
||||
*/
|
||||
public function SetClassDescription(string $sClassDescription)
|
||||
{
|
||||
$this->sClassDescription = $sClassDescription;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public function HasClassDescription(): bool
|
||||
{
|
||||
return utils::IsNotNullOrEmptyString($this->sClassDescription);
|
||||
}
|
||||
|
||||
public function GetJSRefresh(): string
|
||||
{
|
||||
return "$('#".$this->sId."').block();
|
||||
|
||||
@@ -70,6 +70,9 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
||||
/** @var AttributeLinkedSet $oAttDef attribute link set */
|
||||
protected AttributeLinkedSet $oAttDef;
|
||||
|
||||
/** @var bool $bIsAttEditable Is attribute editable */
|
||||
protected bool $bIsAttEditable;
|
||||
|
||||
/** @var string $sTargetClass links target classname */
|
||||
protected string $sTargetClass;
|
||||
|
||||
@@ -119,11 +122,12 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
||||
private function Init()
|
||||
{
|
||||
$this->sTargetClass = $this->GetTargetClass();
|
||||
|
||||
$this->InitIsAttEditable();
|
||||
|
||||
// User rights
|
||||
$this->bIsAllowCreate = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowModify = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowDelete = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowCreate = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowModify = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowDelete = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,6 +200,26 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
||||
$this->AddSubBlock($oBlock->GetRenderContent($oPage, $this->GetExtraParam(), $this->sTableId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
private function InitIsAttEditable(): void
|
||||
{
|
||||
$iFlags = 0;
|
||||
|
||||
if ($this->oDbObject->IsNew())
|
||||
{
|
||||
$iFlags = $this->oDbObject->GetInitialStateAttributeFlags($this->sAttCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
$iFlags = $this->oDbObject->GetAttributeFlags($this->sAttCode);
|
||||
}
|
||||
|
||||
$this->bIsAttEditable = !($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE | OPT_ATT_HIDDEN));
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTableId.
|
||||
*
|
||||
|
||||
@@ -733,6 +733,7 @@ class AjaxRenderController
|
||||
} else {
|
||||
$oFullSetFilter = new DBObjectSearch($sRemoteClass);
|
||||
}
|
||||
$oFullSetFilter->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
$oWidget->DoAddObjects($oPage, $iMaxAddedId, $oFullSetFilter, $oObj);
|
||||
$oKPI->ComputeAndReport('Data write');
|
||||
}
|
||||
|
||||
@@ -320,12 +320,19 @@ EOF
|
||||
if ($this->oField->GetCurrentValue() !== null && $this->oField->GetCurrentValue() !== 0 && $this->oField->GetCurrentValue() !== '')
|
||||
{
|
||||
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
|
||||
$oFieldValue = MetaModel::GetObject($sFieldValueClass, $this->oField->GetCurrentValue(), true, true);
|
||||
$oFieldValue = MetaModel::GetObjectWithArchive($sFieldValueClass, $this->oField->GetCurrentValue(), true, true);
|
||||
$sFieldHtmlValue = $oFieldValue->GetName();
|
||||
$sFieldUrl = ApplicationContext::MakeObjectUrl($sFieldValueClass, $this->oField->GetCurrentValue());
|
||||
if(!empty($sFieldUrl))
|
||||
if($oFieldValue->IsArchived())
|
||||
{
|
||||
$sFieldHtmlValue = '<a href="'.$sFieldUrl.'" data-toggle="itop-portal-modal">'.$sFieldHtmlValue.'</a>';
|
||||
$sFieldHtmlValue = '<span class="text_decoration"><span class="fas fa-archive"></span></span>' . $sFieldHtmlValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sFieldUrl = ApplicationContext::MakeObjectUrl($sFieldValueClass, $this->oField->GetCurrentValue());
|
||||
if (!empty($sFieldUrl))
|
||||
{
|
||||
$sFieldHtmlValue = '<a href="' . $sFieldUrl . '" data-toggle="itop-portal-modal">' . $sFieldHtmlValue . '</a>';
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -10,6 +10,7 @@ use Closure;
|
||||
use Combodo\iTop\Service\Events\Description\EventDescription;
|
||||
use ContextTag;
|
||||
use CoreException;
|
||||
use DBObject;
|
||||
use Exception;
|
||||
use ExecutionKPI;
|
||||
use ReflectionClass;
|
||||
@@ -53,6 +54,12 @@ final class EventService
|
||||
/**
|
||||
* Register a callback for a specific event
|
||||
*
|
||||
* **Warning** : be ultra careful on memory footprint ! each callback will be saved in {@see aEventListeners}, and a callback is
|
||||
* made of the whole object instance and the method name ({@link https://www.php.net/manual/en/language.types.callable.php}).
|
||||
* For example to register on DBObject instances, you should better use {@see DBObject::RegisterCRUDListener()}
|
||||
*
|
||||
* @uses aEventListeners
|
||||
*
|
||||
* @api
|
||||
* @param string $sEvent corresponding event
|
||||
* @param callable $callback The callback to call
|
||||
@@ -61,8 +68,12 @@ final class EventService
|
||||
* @param array|string|null $context context filter
|
||||
* @param float $fPriority optional priority for callback order
|
||||
*
|
||||
* @return string Id of the registration
|
||||
* @return string registration identifier
|
||||
*
|
||||
* @see DBObject::RegisterCRUDListener() to register in DBObject instances instead, to reduce memory footprint (callback saving)
|
||||
*
|
||||
* @since 3.1.0 method creation
|
||||
* @since 3.1.0-3 3.1.1 3.2.0 N°6716 PHPDoc change to warn on memory footprint, and {@see DBObject::RegisterCRUDListener()} alternative
|
||||
*/
|
||||
public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, array $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
|
||||
{
|
||||
|
||||
@@ -138,12 +138,24 @@ class Router
|
||||
{
|
||||
$aRoutes = [];
|
||||
$bUseCache = false === utils::IsDevelopmentEnvironment();
|
||||
$bMustWriteCache = false;
|
||||
$sCacheFilePath = $this->GetCacheFileAbsPath();
|
||||
|
||||
// Try to read from cache
|
||||
if ($bUseCache) {
|
||||
if (is_file($sCacheFilePath)) {
|
||||
$aRoutes = include $sCacheFilePath;
|
||||
$aCachedRoutes = include $sCacheFilePath;
|
||||
|
||||
// N°6618 - Protection against corrupted cache returning `1` instead of an array of routes
|
||||
if (is_array($aCachedRoutes)) {
|
||||
$aRoutes = $aCachedRoutes;
|
||||
} else {
|
||||
// Invalid cache force re-generation
|
||||
// Note that even if it is re-generated corrupted again, this protection should prevent crashes
|
||||
$bMustWriteCache = true;
|
||||
}
|
||||
} else {
|
||||
$bMustWriteCache = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,11 +192,11 @@ class Router
|
||||
}
|
||||
}
|
||||
|
||||
// Save to cache
|
||||
if ($bUseCache) {
|
||||
// Save to cache if it doesn't exist already
|
||||
if ($bMustWriteCache) {
|
||||
$sCacheContent = "<?php\n\nreturn ".var_export($aRoutes, true).";";
|
||||
SetupUtils::builddir(dirname($sCacheFilePath));
|
||||
file_put_contents($sCacheFilePath, $sCacheContent);
|
||||
file_put_contents($sCacheFilePath, $sCacheContent, LOCK_EX);
|
||||
}
|
||||
|
||||
return $aRoutes;
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
{% apply spaceless %}
|
||||
<div class="ibo-dashlet-badge--body{% if oUIBlock.IsHidden() %} ibo-is-hidden{% endif %}" id="{{ oUIBlock.GetId() }}"
|
||||
data-role="ibo-dashlet-badge--body"
|
||||
data-tooltip-content="{{ oUIBlock.GetClassLabel() }}">
|
||||
{% if oUIBlock.HasClassDescription() %}
|
||||
{# Display both class name and description as the name could be truncated if too long #}
|
||||
data-tooltip-content="{{ '<div class="ibo-dashlet-badge--body--tooltip-title">'|escape }}{{ oUIBlock.GetClassLabel() }}{{ '</div><div class="ibo-dashlet-badge--body--tooltip-description">'|escape }}{{ oUIBlock.GetClassDescription() }}{{ '</div>'|escape }}"
|
||||
data-tooltip-html-enabled="true"
|
||||
{% else %}
|
||||
{# Display only class name as it could be truncated if too long #}
|
||||
data-tooltip-content="{{ oUIBlock.GetClassLabel() }}"
|
||||
{% endif %}
|
||||
{# Delay display to avoid having all tooltips appearing when mouse is just passing through the tabs #}
|
||||
data-tooltip-show-delay="300">
|
||||
<div class="ibo-dashlet-badge--icon-container">
|
||||
{# Mind the empty "alt" attribute https://www.w3.org/WAI/tutorials/images/decorative/ #}
|
||||
<img class="ibo-dashlet-badge--icon" src="{{ oUIBlock.GetClassIconUrl() }}" alt="">
|
||||
|
||||
@@ -23,16 +23,16 @@
|
||||
{% if aAction.confirmation is defined %}
|
||||
|
||||
// Prepare confirmation title
|
||||
let sTitle = `{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s|raw }}`;
|
||||
let sTitle = '{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s }}';
|
||||
{% if aAction.confirmation.title is defined %}
|
||||
sTitle = `{{ aAction.confirmation.title|dict_s|raw }}`;
|
||||
sTitle = '{{ aAction.confirmation.title|dict_s }}';
|
||||
{% endif %}
|
||||
sTitle = sTitle.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
|
||||
|
||||
// Prepare confirmation message
|
||||
let sMessage = `{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s|raw }}`;
|
||||
let sMessage = '{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s }}';
|
||||
{% if aAction.confirmation.message is defined %}
|
||||
sMessage = `{{ aAction.confirmation.message|dict_s|raw }}`;
|
||||
sMessage = '{{ aAction.confirmation.message|dict_s }}';
|
||||
{% endif %}
|
||||
sMessage = sMessage.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
[infra]
|
||||
php_version=7.4-apache
|
||||
; N°6629 perf bug on some tests on mariadb for now, so specifying MySQL
|
||||
db_version=5.7
|
||||
|
||||
[itop]
|
||||
itop_setup=tests/setup_params/default-params.xml
|
||||
itop_backup=tests/backups/backup-itop.tar.gz
|
||||
|
||||
@@ -1,7 +1,122 @@
|
||||
# PHP unitary tests
|
||||
|
||||
## Where should I add my test?
|
||||
|
||||
- Covers an iTop PHP class or method?
|
||||
- Most likely in "unitary-tests".
|
||||
- Covers the consistency of some data through the app?
|
||||
- Most likely in "integration-tests".
|
||||
- Most likely in "integration-tests".
|
||||
|
||||
## How do I make sure that my tests are efficient?
|
||||
|
||||
|
||||
### Derive from the relevant test class
|
||||
|
||||
Whenever possible keep it the most simple, hence you should first
|
||||
attempt to derive from `TestCase`.
|
||||
|
||||
Then, you might need to derive from `ItopTestCase`.
|
||||
|
||||
Finally, as a last resort, you will use `ItopDataTestCase`.
|
||||
|
||||
### Determine the most relevant isolation configuration
|
||||
|
||||
Should you have opted for `ItopDataTestCase`, then you will have to follow these steps:
|
||||
|
||||
1) Build you test class until it is successfull, without process isolation.
|
||||
2) Run the whole test suite [unitary-tests](unitary-tests)
|
||||
3) If a false-positive appears, then you will start troubleshooting. One advise: be positive!
|
||||
|
||||
### Leave the place clean
|
||||
|
||||
To check your code against polluting coding patterns, run the test [integration-tests/DetectStaticPollutionTest.php](integration-tests/DetectStaticPollutionTest.php)
|
||||
It will tell you if something is wrong, either in your code, or anywhere else in the tests.
|
||||
Fortunately, it will give you an alternative.
|
||||
|
||||
Detected patterns:
|
||||
* ContextTag::addTag()
|
||||
* EventService::RegisterListener()
|
||||
* Dict::Add()
|
||||
|
||||
|
||||
By the way, some patterns do not pollute, because they are handled by the test framework:
|
||||
* Configuration : automatically reset after test class execution
|
||||
* UserRights : a logoff is performed after each test execution
|
||||
* Dict::SetUserLanguage: the user language is reset after each test execution
|
||||
|
||||
See also `@beforeClass` and `@afterClass` to handle cleanup.
|
||||
|
||||
If you can't, then ok you will have to isolate it!
|
||||
|
||||
## Tips
|
||||
### Memory limit
|
||||
|
||||
As the tests are run in the same process, memory usage
|
||||
may become an issue as soon as tests are all executed at once.
|
||||
|
||||
Fix that in the XML configuration in the PHP section
|
||||
```xml
|
||||
<ini name="memory_limit" value="512M"/>
|
||||
```
|
||||
|
||||
### Understand tests interactions
|
||||
|
||||
With PHPStorm, select two tests, right click to get the context menu, then `run`.
|
||||
|
||||
You will have both tests executed and you will be able to figure out if the first one has an impact on the second one.
|
||||
|
||||
### About process isolation
|
||||
#### Isolation with PHPUnit
|
||||
|
||||
By default, tests are run in a single process launched by PHPUnit.
|
||||
|
||||
If process isolation is configured for some tests, then those tests
|
||||
will be executed in a separate process. The main process will
|
||||
continue executing non isolated tests.
|
||||
|
||||
#### Cost of isolation
|
||||
|
||||
The cost of isolating a very basic `TestCase` is approximately 4 ms.
|
||||
|
||||
The cost of isolating an `ItopDataTestCase` is approximately 800 ms.
|
||||
|
||||
### Isolation within iTop
|
||||
|
||||
#### At the test level (preferred)
|
||||
Add annotation `@runInSeparateProcess`
|
||||
Each and every test case will run in a separate
|
||||
process.
|
||||
|
||||
Note : before N°6658 (3.0.4 / 3.1.1 / 3.2.0) we were also adding the `@backupGlobals disabled`
|
||||
and `@preserveGlobalState disabled` annotations. This is no longer necessary as the first has this default value
|
||||
already, and the second one is now set in iTopTestCase as a PHP class attribute.
|
||||
|
||||
#### At the test class level
|
||||
Add annotation `@runTestsInSeparateProcesses`
|
||||
Each and every test case in the class will run in a separate
|
||||
process.
|
||||
|
||||
#### Globally (never do that)
|
||||
Set it into [phpunit.xml.dist](phpunit.xml.dist)
|
||||
|
||||
### Further enhancements
|
||||
The annotation [`@runClassInSeparateProcess`](https://docs.phpunit.de/en/10.0/attributes.html?highlight=runclassinseparateprocess#runclassinseparateprocess) is supposed to do the perfect job, but it is buggy [(See Issue 5230)](https://github.com/sebastianbergmann/phpunit/issues/5230) and it has
|
||||
the exact same effect as `@runTestsInSeparateProcesses`.
|
||||
|
||||
Note : this option is documented only in the [attributes part of the documentation](https://docs.phpunit.de/en/10.0/attributes.html).
|
||||
|
||||
### Traps
|
||||
#### When it is a matter of stars
|
||||
```php
|
||||
/*
|
||||
* @runTestsInSeparateProcesses
|
||||
```
|
||||
This won't work because the comment MUST start with `/**` (two stars) to be considerer by PHPUnit.
|
||||
|
||||
#### SetupBeforeClass called more often than expected
|
||||
|
||||
`setupBeforeClass` is called once for the class **in a given process**.
|
||||
|
||||
Therefore, if the tests are isolated, then `setupBeforeClass` will be called as often as `setUp`.
|
||||
|
||||
This has been proven with [`runClassInSeparateProcessTest.php`](experiments/runClassInSeparateProcessTest.php)
|
||||
@@ -2,5 +2,12 @@
|
||||
"require-dev": {
|
||||
"phpunit/phpunit" : "^9",
|
||||
"sempro/phpunit-pretty-print": "^1.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Combodo\\iTop\\Test\\UnitTest\\": "src/BaseTestCase/",
|
||||
"Combodo\\iTop\\Test\\UnitTest\\Hook\\": "src/Hook/",
|
||||
"Combodo\\iTop\\Test\\UnitTest\\Service\\": "src/Service/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
tests/php-unit-tests/experiments/README.md
Normal file
1
tests/php-unit-tests/experiments/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This directory aims at providing experimental proof of the mechanics of PHPUnit
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest;
|
||||
|
||||
/**
|
||||
* Shows that
|
||||
* 1) the option runClassInSeparateProcess is equivalent to runTestsInSeparateProcesses
|
||||
* 2) setUpBeforeClass is called within each spawned process (the main one, then in eventuel subprocesses)
|
||||
* 3) setUp behaves as expected, i.e. called one within the same process as the test itself
|
||||
*
|
||||
* @preserveGlobalState disabled
|
||||
* @runClassInSeparateProcess
|
||||
*/
|
||||
class runClassInSeparateProcessTest extends ItopDataTestCase
|
||||
{
|
||||
static public function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass(); // TODO: Change the autogenerated stub
|
||||
|
||||
file_put_contents(
|
||||
dirname(__FILE__).'/pid.txt',
|
||||
getmypid().';'.static::class.';'.__METHOD__."\n",
|
||||
FILE_APPEND);
|
||||
}
|
||||
|
||||
protected function LogPid()
|
||||
{
|
||||
file_put_contents(
|
||||
dirname(__FILE__).'/pid.txt',
|
||||
getmypid().';'.static::class.';'.$this->getName()."\n",
|
||||
FILE_APPEND);
|
||||
}
|
||||
|
||||
function testA()
|
||||
{
|
||||
$this->LogPid();
|
||||
static::assertTrue(true);
|
||||
}
|
||||
|
||||
function testB()
|
||||
{
|
||||
$this->LogPid();
|
||||
static::assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider CProvider
|
||||
*/
|
||||
function testC($i)
|
||||
{
|
||||
$this->LogPid();
|
||||
static::assertTrue(true);
|
||||
}
|
||||
|
||||
function CProvider()
|
||||
{
|
||||
return [
|
||||
[1],
|
||||
[1],
|
||||
[1],
|
||||
[1],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Shows that tearDown is called after a fatal error within a test
|
||||
*/
|
||||
class tearDownAfterFailureTest extends TestCase
|
||||
{
|
||||
static $bIsCorrectlyInitialized = true;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
static::$bIsCorrectlyInitialized = true;
|
||||
}
|
||||
|
||||
function testIsInitializedAndChangeIt()
|
||||
{
|
||||
static::assertTrue(static::$bIsCorrectlyInitialized);
|
||||
|
||||
static::$bIsCorrectlyInitialized = false;
|
||||
|
||||
$this->expectException('Exception');
|
||||
throw new \Exception('hello');
|
||||
}
|
||||
|
||||
function testIsStillInitialized()
|
||||
{
|
||||
static::assertTrue(static::$bIsCorrectlyInitialized);
|
||||
}
|
||||
|
||||
function testFailingDueToUnexpectedException()
|
||||
{
|
||||
static::$bIsCorrectlyInitialized = false;
|
||||
This_Is_Not_A_Function_And_Causes_A_Fatal_Error();
|
||||
}
|
||||
|
||||
function testIsStillInitializedAnyway()
|
||||
{
|
||||
static::assertTrue(static::$bIsCorrectlyInitialized);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2023 Combodo SARL
|
||||
* This file is part of iTop.
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use Dict;
|
||||
use const APPROOT;
|
||||
|
||||
/**
|
||||
* As {@see DictionariesConsistencyTest}, we are testing dict files, but the ones that are compiled, so we cannot be in the beforeSetup group !
|
||||
*/
|
||||
class CompiledDictionariesConsistencyTest extends ItopTestCase
|
||||
{
|
||||
/**
|
||||
* make sure N°5305 dictionary changes (CSV import ergonomy) are still here and UI remains unbroken for any lang
|
||||
*
|
||||
* One of the things checked is the number of parameters in the dict value. This is for now crashing the app (N°5491)
|
||||
* and we have multiple inconsistencies in our existing dict files... So it is complicated to have a generic test for all files !
|
||||
* At least we are protecting those new entries...
|
||||
*/
|
||||
public function testImportCsvMessageStillOk()
|
||||
{
|
||||
$aFailedLabels = [];
|
||||
$aLabelsToTest = [
|
||||
'UI:CSVReport-Value-SetIssue' => [],
|
||||
'UI:CSVReport-Value-ChangeIssue' => ['arg1'],
|
||||
'UI:CSVReport-Value-NoMatch' => ['arg1'],
|
||||
'UI:CSVReport-Value-NoMatch-PossibleValues' => ['arg1', 'arg2'],
|
||||
'UI:CSVReport-Value-NoMatch-NoObject' => ['arg1'],
|
||||
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => ['arg1'],
|
||||
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => ['arg1'],
|
||||
];
|
||||
|
||||
$sCompiledLanguagesFilePath = APPROOT . 'env-' . \utils::GetCurrentEnvironment() . '/dictionaries/languages.php';
|
||||
$this->assertFileExists($sCompiledLanguagesFilePath, 'We must have an existing compiled language.php file in the current env !');
|
||||
require_once($sCompiledLanguagesFilePath);
|
||||
$this->assertNotEmpty(Dict::GetLanguages(), 'the languages.php file exists but didn\'t load any language');
|
||||
|
||||
foreach (glob(APPROOT . 'env-' . \utils::GetCurrentEnvironment() . '/dictionaries/*.dict.php') as $sDictFile) {
|
||||
if (preg_match('/.*\\/(.*).dict.php/', $sDictFile, $aMatches)) {
|
||||
$sLangCode = $aMatches[1];
|
||||
$sLanguageCode = strtoupper(str_replace('-', ' ', $sLangCode));
|
||||
Dict::SetUserLanguage($sLanguageCode);
|
||||
|
||||
foreach ($aLabelsToTest as $sLabelKey => $aLabelArgs) {
|
||||
echo "Testing $sDictFile, label $sLabelKey with " . \var_export($aLabelArgs, true) . "\n";
|
||||
try {
|
||||
$sLabelValue = Dict::Format($sLabelKey, ...$aLabelArgs);
|
||||
//$this->debug($sLabelValue);
|
||||
} catch (\ValueError $e) {
|
||||
$aFailedLabels[] = $sLabelKey;
|
||||
|
||||
$this->debug([
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'label_name' => $sLabelKey,
|
||||
'label_args' => $aLabelArgs,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->assertEquals([], $aFailedLabels, "$sDictFile : test fail for lang $sLangCode and labels (" . implode(", ", $aFailedLabels) . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use GlobIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RecursiveRegexIterator;
|
||||
use RegexIterator;
|
||||
|
||||
/**
|
||||
* Performs code static analysis to detect patterns that will change the values of static data and therefor could affect other tests while running them in a single process
|
||||
*
|
||||
* @runClassInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class detectStaticPollutionTest extends TestCase
|
||||
{
|
||||
protected function FindMatches($sFile, $sFileContents, $sRegexp)
|
||||
{
|
||||
$aRes = [];
|
||||
foreach (explode("\n", $sFileContents) as $iLine => $sLine) {
|
||||
if (preg_match_all($sRegexp, $sLine, $aMatches, PREG_PATTERN_ORDER)) {
|
||||
$sLine = $iLine + 1;
|
||||
$aRes[] = "$sFile:$sLine";
|
||||
}
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider PollutingPatterns
|
||||
* @param $sPattern
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function testDetectPolluters($sPattern, $sFix)
|
||||
{
|
||||
$sScannedDir = dirname(__FILE__).'/../unitary-tests';
|
||||
|
||||
$aPolluters = [];
|
||||
$oDirectory = new RecursiveDirectoryIterator($sScannedDir);
|
||||
$Iterator = new RecursiveIteratorIterator($oDirectory);
|
||||
foreach (new RegexIterator($Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH) as $aMatch) {
|
||||
$sFile = $aMatch[0];
|
||||
if(is_file($sFile)) {
|
||||
$sFileContents = file_get_contents($sFile);
|
||||
if (preg_match_all($sPattern, $sFileContents, $keys, PREG_PATTERN_ORDER)) {
|
||||
$aPolluters = array_merge($aPolluters, $this->FindMatches($sFile, $sFileContents, $sPattern));
|
||||
}
|
||||
}
|
||||
}
|
||||
$iPolluters = count($aPolluters);
|
||||
static::assertTrue($iPolluters === 0, "Found polluter(s) for pattern $sPattern, $sFix:\n".implode("\n", $aPolluters));
|
||||
|
||||
}
|
||||
|
||||
public function PollutingPatterns()
|
||||
{
|
||||
return [
|
||||
'ContextTags' => ['/ContextTag::AddContext/i', 'Use new ContextTag() instead'],
|
||||
'Dict::Add' => ['/Dict::Add/i', 'TODO: implement a facade into ItopDataTestCase'],
|
||||
'EventService::RegisterListener' => ['/EventService::RegisterListener/i', 'Use ItopDataTestCase::EventService_RegisterListener instead'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
class DictionariesConsistencyAfterSetupTest extends ItopTestCase
|
||||
{
|
||||
//used by testDictEntryValues
|
||||
//to filter false positive broken traductions
|
||||
private static $aLabelCodeNotToCheck = [
|
||||
//use of Dict::S not Format
|
||||
"UI:Audit:PercentageOk",
|
||||
|
||||
//unused dead labels
|
||||
"Class:DatacenterDevice/Attribute:redundancy/count",
|
||||
"Class:DatacenterDevice/Attribute:redundancy/disabled",
|
||||
"Class:DatacenterDevice/Attribute:redundancy/percent",
|
||||
"Class:TriggerOnThresholdReached/Attribute:threshold_index+"
|
||||
];
|
||||
|
||||
public function FormatProvider(){
|
||||
return [
|
||||
'key does not exist in dictionnary' => [
|
||||
'sTemplate' => null,
|
||||
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
|
||||
],
|
||||
'traduction that breaks expected nb of arguments' => [
|
||||
'sTemplate' => 'toto %1$s titi %2$s',
|
||||
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
|
||||
],
|
||||
'traduction ok' => [
|
||||
'sTemplate' => 'toto %1$s titi',
|
||||
'sExpectedTraduction' => 'toto 1 titi',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sTemplate : if null it will not create dict entry
|
||||
* @since 2.7.10 N°5491 - Inconsistent dictionary entries regarding arguments to pass to Dict::Format
|
||||
* @dataProvider FormatProvider
|
||||
*/
|
||||
public function testFormatWithOneArgumentAndCustomKey(?string $sTemplate, $sExpectedTranslation){
|
||||
//tricky way to mock GetLabelAndLangCode behavior via connected user language
|
||||
$sLangCode = \Dict::GetUserLanguage();
|
||||
$aDictByLang = $this->GetNonPublicStaticProperty(\Dict::class, 'm_aData');
|
||||
$sDictKey = 'ITOP::DICT:FORMAT:BROKEN:KEY';
|
||||
|
||||
if (! is_null($sTemplate)){
|
||||
$aDictByLang[$sLangCode][$sDictKey] = $sTemplate;
|
||||
}
|
||||
|
||||
$this->SetNonPublicStaticProperty(\Dict::class, 'm_aData', $aDictByLang);
|
||||
|
||||
$this->assertEquals($sExpectedTranslation, \Dict::Format($sDictKey, "1"));
|
||||
}
|
||||
|
||||
//test works after setup (no annotation @beforesetup)
|
||||
//even if it does not extend ItopDataTestCase
|
||||
private function ReadDictKeys($sLangCode) : array {
|
||||
\Dict::InitLangIfNeeded($sLangCode);
|
||||
|
||||
$aDictEntries = $this->GetNonPublicStaticProperty(\Dict::class, 'm_aData');
|
||||
return $aDictEntries[$sLangCode];
|
||||
}
|
||||
|
||||
/**
|
||||
* foreach dictionnary label map (key/value) it counts the number argument that should be passed to use Dict::Format
|
||||
* examples:
|
||||
* for "gabu zomeu" label there are no args
|
||||
* for "shadok %1 %2 %3" there are 3 args
|
||||
*
|
||||
* limitation: there is no validation check for "%3 itop %2 combodo" which seems unconsistent
|
||||
* @param $aDictEntry
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function GetKeyArgCountMap($aDictEntry) {
|
||||
$aKeyArgsCount = [];
|
||||
foreach ($aDictEntry as $sKey => $sValue){
|
||||
$aKeyArgsCount[$sKey] = $this->countArg($sValue);
|
||||
}
|
||||
ksort($aKeyArgsCount);
|
||||
return $aKeyArgsCount;
|
||||
}
|
||||
|
||||
private function countArg($sLabel) {
|
||||
$iMaxIndex = 0;
|
||||
if (preg_match_all("/%(\d+)/", $sLabel, $aMatches)){
|
||||
$aSubMatches = $aMatches[1];
|
||||
if (is_array($aSubMatches)){
|
||||
foreach ($aSubMatches as $aCurrentMatch){
|
||||
$iIndex = $aCurrentMatch;
|
||||
$iMaxIndex = ($iMaxIndex < $iIndex) ? $iIndex : $iMaxIndex;
|
||||
}
|
||||
}
|
||||
} else if ((false !== strpos($sLabel, "%s"))
|
||||
|| (false !== strpos($sLabel, "%d"))
|
||||
){
|
||||
$iMaxIndex = 1;
|
||||
}
|
||||
|
||||
return $iMaxIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning: hardcoded list of languages
|
||||
* It is hard to have it dynamically via Dict::GetLanguages as for each lang Dict::Init should be called first
|
||||
**/
|
||||
public function LangCodeProvider(){
|
||||
return [
|
||||
'cs' => [ 'CS CZ' ],
|
||||
'da' => [ 'DA DA' ],
|
||||
'de' => [ 'DE DE' ],
|
||||
'en' => [ 'EN US' ],
|
||||
'es' => [ 'ES CR' ],
|
||||
'fr' => [ 'FR FR' ],
|
||||
'hu' => [ 'HU HU' ],
|
||||
'it' => [ 'IT IT' ],
|
||||
'ja' => [ 'JA JP' ],
|
||||
'nl' => [ 'NL NL' ],
|
||||
'pt' => [ 'PT BR' ],
|
||||
'ru' => [ 'RU RU' ],
|
||||
'sk' => [ 'SK SK' ],
|
||||
'tr' => [ 'TR TR' ],
|
||||
'zh' => [ 'ZH CN' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* compare en and other dictionaries and check that for all labels there is the same number of arguments
|
||||
* if not Dict::Format could raise an exception for some languages. translation should be done again...
|
||||
* @dataProvider LangCodeProvider
|
||||
*/
|
||||
public function testDictEntryValues($sLanguageCodeToTest)
|
||||
{
|
||||
$sReferenceLangCode = 'EN US';
|
||||
$aReferenceLangDictEntry = $this->ReadDictKeys($sReferenceLangCode);
|
||||
|
||||
$aDictEntry = $this->ReadDictKeys($sLanguageCodeToTest);
|
||||
|
||||
|
||||
$aKeyArgsCountMap = [];
|
||||
$aKeyArgsCountMap[$sReferenceLangCode] = $this->GetKeyArgCountMap($aReferenceLangDictEntry);
|
||||
//$aKeyArgsCountMap[$sCode] = $this->GetKeyArgCountMap($aDictEntry);
|
||||
|
||||
//set user language
|
||||
$this->SetNonPublicStaticProperty(\Dict::class, 'm_sCurrentLanguage', $sLanguageCodeToTest);
|
||||
|
||||
$aMismatchedKeys = [];
|
||||
|
||||
foreach ($aKeyArgsCountMap[$sReferenceLangCode] as $sKey => $iExpectedNbOfArgs){
|
||||
if (in_array($sKey, self::$aLabelCodeNotToCheck)){
|
||||
//false positive: do not test
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists($sKey, $aDictEntry)){
|
||||
$aPlaceHolders = [];
|
||||
for ($i=0; $i<$iExpectedNbOfArgs; $i++){
|
||||
$aPlaceHolders[]=$i;
|
||||
}
|
||||
|
||||
$sLabelTemplate = $aDictEntry[$sKey];
|
||||
try{
|
||||
vsprintf($sLabelTemplate, $aPlaceHolders);
|
||||
} catch(\Throwable $e){
|
||||
$sError = $e->getMessage();
|
||||
if (array_key_exists($sError, $aMismatchedKeys)){
|
||||
$aMismatchedKeys[$sError][$sKey] = $iExpectedNbOfArgs;
|
||||
} else {
|
||||
$aMismatchedKeys[$sError] = [$sKey => $iExpectedNbOfArgs];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$iCount = 0;
|
||||
foreach ($aMismatchedKeys as $sError => $aKeys){
|
||||
var_dump($sError);
|
||||
foreach ($aKeys as $sKey => $iExpectedNbOfArgs) {
|
||||
$iCount++;
|
||||
if ($sReferenceLangCode === $sLanguageCodeToTest) {
|
||||
var_dump([
|
||||
'key label' => $sKey,
|
||||
'expected nb of expected args' => $iExpectedNbOfArgs,
|
||||
"key value in $sLanguageCodeToTest" => $aDictEntry[$sKey],
|
||||
]);
|
||||
} else {
|
||||
var_dump([
|
||||
'key label' => $sKey,
|
||||
'expected nb of expected args' => $iExpectedNbOfArgs,
|
||||
"key value in $sLanguageCodeToTest" => $aDictEntry[$sKey],
|
||||
"key value in $sReferenceLangCode" => $aReferenceLangDictEntry[$sKey],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sErrorMsg = sprintf("%s broken propertie(s) on $sLanguageCodeToTest dictionaries! either change the dict value in $sLanguageCodeToTest or add it in ignored label list (cf aLabelCodeNotToCheck)", $iCount);
|
||||
$this->assertEquals([], $aMismatchedKeys, $sErrorMsg);
|
||||
}
|
||||
|
||||
public function VsprintfProvider(){
|
||||
return [
|
||||
'not enough args' => [
|
||||
"sLabelTemplate" => "$1%s",
|
||||
"aPlaceHolders" => [],
|
||||
],
|
||||
'exact nb of args' => [
|
||||
"sLabelTemplate" => "$1%s",
|
||||
"aPlaceHolders" => ["1"],
|
||||
],
|
||||
'too much args' => [
|
||||
"sLabelTemplate" => "$1%s",
|
||||
"aPlaceHolders" => ["1", "2"],
|
||||
],
|
||||
'\"% ok\" without args' => [
|
||||
"sLabelTemplate" => "% ok",
|
||||
"aPlaceHolders" => [],
|
||||
],
|
||||
'\"% ok $1%s\" without args' => [
|
||||
"sLabelTemplate" => "% ok",
|
||||
"aPlaceHolders" => ['1'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider VsprintfProvider
|
||||
public function testVsprintf($sLabelTemplate, $aPlaceHolders){
|
||||
try{
|
||||
$this->markTestSkipped("usefull to check a specific PHP version behavior");
|
||||
vsprintf($sLabelTemplate, $aPlaceHolders);
|
||||
$this->assertTrue(true);
|
||||
} catch(\Throwable $e) {
|
||||
$this->assertTrue(false, "label \'" . $sLabelTemplate . " failed with " . var_export($aPlaceHolders, true) );
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use Dict;
|
||||
|
||||
/**
|
||||
* For tests on compiled dict files, see {@see CompiledDictionariesConsistencyTest}
|
||||
* @group beforeSetup
|
||||
*/
|
||||
class DictionariesConsistencyTest extends ItopTestCase
|
||||
@@ -98,10 +98,12 @@ class DictionariesConsistencyTest extends ItopTestCase
|
||||
{
|
||||
$this->setUp();
|
||||
|
||||
$sAppRoot = $this->GetAppRoot();
|
||||
|
||||
$aDictFiles = array_merge(
|
||||
glob(APPROOT.'datamodels/2.x/*/*.dict*.php'), // legacy form in modules
|
||||
glob(APPROOT.'datamodels/2.x/*/dictionaries/*.dict*.php'), // modern form in modules
|
||||
glob(APPROOT.'dictionaries/*.dict*.php') // framework
|
||||
glob($sAppRoot.'datamodels/2.x/*/*.dict*.php'), // legacy form in modules
|
||||
glob($sAppRoot.'datamodels/2.x/*/dictionaries/*.dict*.php'), // modern form in modules
|
||||
glob($sAppRoot.'dictionaries/*.dict*.php') // framework
|
||||
);
|
||||
$aTestCases = array();
|
||||
foreach ($aDictFiles as $sDictFile) {
|
||||
@@ -152,67 +154,4 @@ class DictionariesConsistencyTest extends ItopTestCase
|
||||
$sMessage = "File `{$sDictFile}` syntax didn't matched expectations\nparsing results=".var_export($output, true);
|
||||
self::assertEquals($bIsSyntaxValid, $bDictFileSyntaxOk, $sMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ImportCsvMessageStillOkProvider
|
||||
* make sure N°5305 dictionary changes are still here and UI remains unbroken for any lang
|
||||
*/
|
||||
public function testImportCsvMessageStillOk($sLangCode, $sDictFile)
|
||||
{
|
||||
$aFailedLabels = [];
|
||||
$aLabelsToTest = [
|
||||
'UI:CSVReport-Value-SetIssue' => [],
|
||||
'UI:CSVReport-Value-ChangeIssue' => [ 'arg1' ],
|
||||
'UI:CSVReport-Value-NoMatch' => [ 'arg1' ],
|
||||
'UI:CSVReport-Value-NoMatch-PossibleValues' => [ 'arg1', 'arg2' ],
|
||||
'UI:CSVReport-Value-NoMatch-NoObject' => [ 'arg1' ],
|
||||
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => [ 'arg1' ],
|
||||
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => [ 'arg1' ],
|
||||
];
|
||||
|
||||
$sLanguageCode = strtoupper(str_replace('-', ' ', $sLangCode));
|
||||
require_once(APPROOT.'env-'.\utils::GetCurrentEnvironment().'/dictionaries/languages.php');
|
||||
Dict::SetUserLanguage($sLanguageCode);
|
||||
foreach ($aLabelsToTest as $sLabelKey => $aLabelArgs){
|
||||
try{
|
||||
$sLabelValue = Dict::Format($sLabelKey, ...$aLabelArgs);
|
||||
//$this->debug($sLabelValue);
|
||||
} catch (\ValueError $e){
|
||||
$aFailedLabels[] = $sLabelKey;
|
||||
|
||||
$this->debug([
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'label_name' => $sLabelKey,
|
||||
'label_args' =>$aLabelArgs,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->assertEquals([], $aFailedLabels, "test fail for lang $sLangCode and labels (" . implode(", ", $aFailedLabels) . ')');
|
||||
}
|
||||
|
||||
public function ImportCsvMessageStillOkProvider(){
|
||||
return $this->GetDictFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* return a map linked to *.dict.php files that are generated after setup
|
||||
* each entry key is lang code (example 'en')
|
||||
* each value is an array with lang code (again) and dict file path
|
||||
* @return array
|
||||
*/
|
||||
private function GetDictFiles() : array {
|
||||
$aDictFiles = [];
|
||||
|
||||
foreach (glob(APPROOT.'env-'.\utils::GetCurrentEnvironment().'/dictionaries/*.dict.php') as $sDictFile){
|
||||
if (preg_match('/.*\\/(.*).dict.php/', $sDictFile, $aMatches)){
|
||||
$sLangCode = $aMatches[1];
|
||||
$aDictFiles[$sLangCode] = [
|
||||
'lang' => $sLangCode,
|
||||
'file' => $sDictFile
|
||||
];
|
||||
}
|
||||
}
|
||||
return $aDictFiles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use utils;
|
||||
|
||||
/**
|
||||
* @package Combodo\iTop\Test\UnitTest\Setup
|
||||
* @group beforeSetup
|
||||
*/
|
||||
class iTopModulesPhpVersionIntegrationTest extends ItopTestCase {
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@ use iTopDesignFormat;
|
||||
* @covers iTopDesignFormat
|
||||
*
|
||||
* @package Combodo\iTop\Test\UnitTest\Setup
|
||||
* @group beforeSetup
|
||||
*/
|
||||
class iTopModulesXmlVersionIntegrationTest extends ItopTestCase
|
||||
{
|
||||
@@ -71,11 +72,13 @@ class iTopModulesXmlVersionIntegrationTest extends ItopTestCase
|
||||
{
|
||||
static::setUp();
|
||||
|
||||
$sPath = APPROOT.'datamodels/2.x/*/datamodel.*.xml';
|
||||
$sAppRoot = $this->GetAppRoot();
|
||||
|
||||
$sPath = $sAppRoot.'datamodels/2.x/*/datamodel.*.xml';
|
||||
$aXmlFiles = glob($sPath);
|
||||
|
||||
$aXmlFiles[] = APPROOT.'core/datamodel.core.xml';
|
||||
$aXmlFiles[] = APPROOT.'application/datamodel.application.xml';
|
||||
$aXmlFiles[] = $sAppRoot.'core/datamodel.core.xml';
|
||||
$aXmlFiles[] = $sAppRoot.'application/datamodel.application.xml';
|
||||
|
||||
$aTestCases = array();
|
||||
foreach ($aXmlFiles as $sXmlFile) {
|
||||
|
||||
@@ -20,6 +20,7 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
/**
|
||||
* @package Combodo\iTop\Test\UnitTest\Setup
|
||||
* @group beforeSetup
|
||||
*/
|
||||
class iTopXmlVersionIntegrationTest extends ItopTestCase
|
||||
{
|
||||
|
||||
51
tests/php-unit-tests/perf-tests.xml.dist
Normal file
51
tests/php-unit-tests/perf-tests.xml.dist
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
|
||||
bootstrap="unittestautoload.php"
|
||||
backupGlobals="true"
|
||||
colors="true"
|
||||
columns="120"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
stopOnIncomplete="false"
|
||||
stopOnRisky="false"
|
||||
stopOnSkipped="false"
|
||||
verbose="true"
|
||||
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||
>
|
||||
|
||||
<extensions>
|
||||
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||
</extensions>
|
||||
|
||||
<php>
|
||||
<ini name="memory_limit" value="512M"/>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
<ini name="display_errors" value="On"/>
|
||||
<ini name="log_errors" value="On"/>
|
||||
<ini name="html_errors" value="Off"/>
|
||||
<env name="PHPUNIT_PRETTY_PRINT_PROGRESS" value="true"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<!-- Unitary tests -->
|
||||
<testsuite name="Perf">
|
||||
<directory>perf-tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<!-- Code coverage white list -->
|
||||
<filter>
|
||||
<whitelist>
|
||||
<file>../../core/apc-emulation.php</file>
|
||||
<file>../../core/ormlinkset.class.inc.php</file>
|
||||
<file>../../datamodels/2.x/itop-tickets/main.itop-tickets.php</file>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
</phpunit>
|
||||
93
tests/php-unit-tests/perf-tests/BulkDBObjectTest.php
Normal file
93
tests/php-unit-tests/perf-tests/BulkDBObjectTest.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class BulkDBObjectTest extends ItopDataTestCase
|
||||
{
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testInsertAndReadPersonObjects()
|
||||
{
|
||||
echo "Part 1: Insert Persons\n";
|
||||
|
||||
$sOrgId = $this->getTestOrgId();
|
||||
$idx = 1;
|
||||
$fStart = microtime(true);
|
||||
$fMaxExecutionTimeAllowed = 40.0;
|
||||
$iInitialPeak = 0;
|
||||
$sInitialPeak = '';
|
||||
$fStartLoop = $fStart;
|
||||
for ($i = 0; $i < 3000; $i++) {
|
||||
$oPerson = $this->CreateObject(Person::class, ['org_id' => $sOrgId, 'name' => "Person_$i", 'first_name' => 'John']);
|
||||
if (0 == ($idx % 100)) {
|
||||
$fDuration = microtime(true) - $fStartLoop;
|
||||
$iMemoryPeakUsage = memory_get_peak_usage();
|
||||
if ($iInitialPeak === 0) {
|
||||
$iInitialPeak = $iMemoryPeakUsage;
|
||||
$sInitialPeak = \utils::BytesToFriendlyFormat($iInitialPeak, 4);
|
||||
}
|
||||
|
||||
$sCurrPeak = \utils::BytesToFriendlyFormat($iMemoryPeakUsage, 4);
|
||||
echo "$idx ".sprintf('%.1f ms', $fDuration * 1000)." - Peak Memory Usage: $sCurrPeak\n";
|
||||
$this->assertTrue($iMemoryPeakUsage === $iInitialPeak, "Peak memory changed from $sInitialPeak to $sCurrPeak after $idx insert loops"); $fStartLoop = microtime(true);
|
||||
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed ($idx insert loops)");
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||
echo "Total duration: $sTotalDuration\n\n";
|
||||
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed");
|
||||
|
||||
//////////////////////
|
||||
// Part 2 Fetch all the created persons
|
||||
echo "Part 1: Fetch Persons\n";
|
||||
|
||||
$oSearch = DBSearch::FromOQL('SELECT Person WHERE org_id=:org_id');
|
||||
$oSet = new DBObjectSet($oSearch, [], ['org_id' => $sOrgId]);
|
||||
$idx = 1;
|
||||
$iInitialPeak = 0;
|
||||
$sInitialPeak = '';
|
||||
$fMaxExecutionTimeAllowed = 0.5;
|
||||
$fStart = microtime(true);
|
||||
$fStartLoop = $fStart;
|
||||
while ($oContact = $oSet->Fetch()) {
|
||||
if (0 == ($idx % 100)) {
|
||||
$fDuration = microtime(true) - $fStartLoop;
|
||||
$iMemoryPeakUsage = memory_get_peak_usage();
|
||||
if ($iInitialPeak === 0) {
|
||||
$iInitialPeak = $iMemoryPeakUsage;
|
||||
$sInitialPeak = \utils::BytesToFriendlyFormat($iInitialPeak, 4);
|
||||
}
|
||||
|
||||
$sCurrPeak = \utils::BytesToFriendlyFormat($iMemoryPeakUsage, 4);
|
||||
echo "$idx ".sprintf('%.1f ms', $fDuration * 1000)." - Peak Memory Usage: $sCurrPeak\n";
|
||||
$this->assertTrue($iMemoryPeakUsage === $iInitialPeak, "Peak memory changed from $sInitialPeak to $sCurrPeak after $idx fetch loops");
|
||||
$fStartLoop = microtime(true);
|
||||
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed ($idx fetch loops)");
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||
echo "Total duration: $sTotalDuration\n\n";
|
||||
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed");
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,12 @@
|
||||
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||
>
|
||||
|
||||
<extensions>
|
||||
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||
</extensions>
|
||||
|
||||
<php>
|
||||
<ini name="memory_limit" value="512M"/>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
<ini name="display_errors" value="On"/>
|
||||
<ini name="log_errors" value="On"/>
|
||||
@@ -29,6 +34,9 @@
|
||||
|
||||
<testsuites>
|
||||
<!-- Unitary tests -->
|
||||
<testsuite name="Perf">
|
||||
<directory>perf-tests</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Application">
|
||||
<directory>unitary-tests/application</directory>
|
||||
</testsuite>
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||
>
|
||||
|
||||
<extensions>
|
||||
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||
</extensions>
|
||||
|
||||
<php>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
<ini name="display_errors" value="On"/>
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest;
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook;
|
||||
use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment;
|
||||
use Config;
|
||||
use Exception;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
|
||||
|
||||
/**
|
||||
* Class ItopCustomDatamodelTestCase
|
||||
*
|
||||
* Helper class to extend for tests needing a custom DataModel (eg. classes, attributes, etc conditions not available in the standard DM)
|
||||
* Usage:
|
||||
* - Create a test case class extending this one
|
||||
* - Override the {@see ItopCustomDatamodelTestCase::GetDatamodelDeltaAbsPath()} method to define where you XML delta is
|
||||
* - Implement your test case methods as usual
|
||||
*
|
||||
* @since N°6097 2.7.9 3.0.4 3.1.0
|
||||
*/
|
||||
abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
{
|
||||
/**
|
||||
* @var bool[]
|
||||
*/
|
||||
protected static $aReadyCustomEnvironments = [];
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since N°6097 Workaround to make the "runClassInSeparateProcess" directive work
|
||||
*/
|
||||
public function __construct($name = null, array $data = [], $dataName = '')
|
||||
{
|
||||
parent::__construct($name, $data, $dataName);
|
||||
|
||||
// Ensure that a test class derived from this one runs in a dedicated process as it changes the MetaModel / environment on the fly and
|
||||
// for now we have no way of switching environments properly in memory and it will result in other (regular) test classes to fail as they won't be on the expected environment.
|
||||
//
|
||||
// If we don't do this, we would have to add the `@runTestsInSeparateProcesses` on *each* test classes which we want to avoid for obvious possible mistakes.
|
||||
// Note that the `@runClassInSeparateProcess` don't work in PHPUnit yet.
|
||||
$this->setRunClassInSeparateProcess(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Abs path to the XML delta to use for the tests of that class
|
||||
*/
|
||||
abstract public function GetDatamodelDeltaAbsPath(): string;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function LoadRequiredItopFiles(): void
|
||||
{
|
||||
parent::LoadRequiredItopFiles();
|
||||
|
||||
$this->RequireOnceItopFile('setup/setuputils.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/runtimeenv.class.inc.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Environment used as a base (conf. file, modules, DB, ...) to prepare the test environment
|
||||
*/
|
||||
protected function GetSourceEnvironment(): string
|
||||
{
|
||||
return 'production';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @warning This should ONLY be overloaded if your test case XML deltas are NOT compatible with the others, as it will create / compile another environment, increasing the global testing time.
|
||||
*/
|
||||
public function GetTestEnvironment(): string
|
||||
{
|
||||
return 'php-unit-tests';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Absolute path to the {@see \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase::GetTestEnvironment()} folder
|
||||
*/
|
||||
final private function GetTestEnvironmentFolderAbsPath(): string
|
||||
{
|
||||
return APPROOT.'env-'.$this->GetTestEnvironment().'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} as ready (compiled)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
final private function MarkEnvironmentReady(): void
|
||||
{
|
||||
if (false === $this->IsEnvironmentReady()) {
|
||||
touch(static::GetTestEnvironmentFolderAbsPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, but not started)
|
||||
*
|
||||
* @details Having the environment ready means that it has been compiled for this global tests run, not that it is a relic from a previous global tests run
|
||||
*/
|
||||
final private function IsEnvironmentReady(): bool
|
||||
{
|
||||
// As these test cases run in separate processes, the best way we found to let know a process if its environment was already prepared for **this run** was to compare the modification times of:
|
||||
// - its own env-<ENV> folder
|
||||
// - a file generated at the beginning of the global test run {@see \Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook}
|
||||
$sRunStartedFilePath = TestsRunStartHook::GetRunStartedFileAbsPath();
|
||||
$sEnvFolderPath = static::GetTestEnvironmentFolderAbsPath();
|
||||
|
||||
clearstatcache();
|
||||
if (false === file_exists($sRunStartedFilePath) || false === file_exists($sEnvFolderPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$iRunStartedFileModificationTime = filemtime($sRunStartedFilePath);
|
||||
$iEnvFolderModificationTime = filemtime($sEnvFolderPath);
|
||||
|
||||
return $iEnvFolderModificationTime >= $iRunStartedFileModificationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function PrepareEnvironment(): void
|
||||
{
|
||||
$sSourceEnv = $this->GetSourceEnvironment();
|
||||
$sTestEnv = $this->GetTestEnvironment();
|
||||
|
||||
// Check if test env. is already set and only prepare it if it's not up-to-date
|
||||
//
|
||||
// Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once.
|
||||
// This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457
|
||||
if (false === $this->IsEnvironmentReady()) {
|
||||
//----------------------------------------------------
|
||||
// Clear any previous "$sTestEnv" environment
|
||||
//----------------------------------------------------
|
||||
|
||||
// - Configuration file
|
||||
$sConfFile = utils::GetConfigFilePath($sTestEnv);
|
||||
$sConfFolder = dirname($sConfFile);
|
||||
if (is_file($sConfFile)) {
|
||||
chmod($sConfFile, 0777);
|
||||
SetupUtils::tidydir($sConfFolder);
|
||||
}
|
||||
|
||||
// - Datamodel delta files
|
||||
// - Cache folder
|
||||
// - Compiled folder
|
||||
// We don't need to clean them as they are already by the compilation
|
||||
|
||||
// - Drop database
|
||||
// We don't do that now, it will be done before re-creating the DB, once the metamodel is started
|
||||
|
||||
//----------------------------------------------------
|
||||
// Prepare "$sTestEnv" environment
|
||||
//----------------------------------------------------
|
||||
|
||||
// All the following is greatly inspired by the toolkit's sandbox script
|
||||
// - Prepare config file
|
||||
$oSourceConf = new Config(utils::GetConfigFilePath($sSourceEnv));
|
||||
if ($oSourceConf->Get('source_dir') === '') {
|
||||
throw new Exception('Missing entry source_dir from the config file');
|
||||
}
|
||||
|
||||
$oTestConfig = clone($oSourceConf);
|
||||
$oTestConfig->ChangeModulesPath($sSourceEnv, $sTestEnv);
|
||||
// - Switch DB name to a dedicated one so we don't mess with the original one
|
||||
$sTestEnvSanitizedForDBName = preg_replace('/[^\d\w]/', '', $sTestEnv);
|
||||
$oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName);
|
||||
|
||||
// - Compile env. based on the existing 'production' env.
|
||||
$oEnvironment = new UnitTestRunTimeEnvironment($sTestEnv);
|
||||
$oEnvironment->WriteConfigFileSafe($oTestConfig);
|
||||
$oEnvironment->CompileFrom($sSourceEnv, false);
|
||||
|
||||
// - Force re-creating a fresh DB
|
||||
CMDBSource::InitFromConfig($oTestConfig);
|
||||
if (CMDBSource::IsDB($oTestConfig->Get('db_name'))) {
|
||||
CMDBSource::DropDB();
|
||||
}
|
||||
CMDBSource::CreateDB($oTestConfig->Get('db_name'));
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sTestEnv);
|
||||
|
||||
$this->MarkEnvironmentReady();
|
||||
$this->debug('Preparation of custom environment "'.$sTestEnv.'" done.');
|
||||
}
|
||||
|
||||
parent::PrepareEnvironment();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,8 @@
|
||||
<?php
|
||||
// Copyright (c) 2010-2023 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
//
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest;
|
||||
|
||||
@@ -29,7 +16,6 @@ namespace Combodo\iTop\Test\UnitTest;
|
||||
use ArchivedObjectException;
|
||||
use CMDBObject;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Contact;
|
||||
use DBObject;
|
||||
@@ -51,6 +37,7 @@ use Ticket;
|
||||
use URP_UserProfile;
|
||||
use User;
|
||||
use UserRequest;
|
||||
use utils;
|
||||
use VirtualHost;
|
||||
use VirtualMachine;
|
||||
use XMLDataLoader;
|
||||
@@ -61,41 +48,54 @@ define('TAG_CLASS', 'FAQ');
|
||||
define('TAG_ATTCODE', 'domains');
|
||||
|
||||
/**
|
||||
* Class ItopDataTestCase
|
||||
*
|
||||
* Helper class to extend for tests needing access to iTop's metamodel
|
||||
*
|
||||
* **⚠ Warning** Each class extending this one needs to add the following annotations :
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°4624 processIsolation is disabled by default and must be enabled in each test needing it (basically all tests using
|
||||
* iTop datamodel)
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6658 move some setUp/tearDown code to the corresponding methods *BeforeClass to speed up tests process time.
|
||||
*/
|
||||
class ItopDataTestCase extends ItopTestCase
|
||||
abstract class ItopDataTestCase extends ItopTestCase
|
||||
{
|
||||
private $iTestOrgId;
|
||||
|
||||
// For cleanup
|
||||
private $aCreatedObjects = array();
|
||||
|
||||
// Counts
|
||||
public $aReloadCount = [];
|
||||
private $aCreatedObjects = [];
|
||||
private $aEventListeners = [];
|
||||
|
||||
/**
|
||||
* @var string Default environment to use for test cases
|
||||
*/
|
||||
const DEFAULT_TEST_ENVIRONMENT = 'production';
|
||||
const USE_TRANSACTION = true;
|
||||
const CREATE_TEST_ORG = false;
|
||||
|
||||
/**
|
||||
* This method is called before the first test of this test class is run (in the current process).
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run (in the current process).
|
||||
*/
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
\UserRights::FlushPrivileges();
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('application/utils.inc.php');
|
||||
|
||||
$sEnv = 'production';
|
||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
$this->PrepareEnvironment();
|
||||
|
||||
if (static::USE_TRANSACTION)
|
||||
{
|
||||
@@ -105,8 +105,6 @@ class ItopDataTestCase extends ItopTestCase
|
||||
{
|
||||
$this->CreateTestOrganization();
|
||||
}
|
||||
|
||||
EventService::RegisterListener(EVENT_DB_OBJECT_RELOAD, [$this, 'CountObjectReload']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,6 +112,9 @@ class ItopDataTestCase extends ItopTestCase
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
static::SetNonPublicStaticProperty(\cmdbAbstractObject::class, 'aObjectsAwaitingEventDbLinksChanged', []);
|
||||
\cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
|
||||
|
||||
if (static::USE_TRANSACTION) {
|
||||
$this->debug("ROLLBACK !!!");
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -135,10 +136,65 @@ class ItopDataTestCase extends ItopTestCase
|
||||
}
|
||||
}
|
||||
}
|
||||
// As soon as a rollback has been performed, each object memoized should be discarded
|
||||
CMDBObject::SetCurrentChange(null);
|
||||
|
||||
// Leave the place clean
|
||||
\UserRights::Logoff();
|
||||
|
||||
foreach ($this->aEventListeners as $sListenerId) {
|
||||
EventService::UnRegisterListener($sListenerId);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function LoadRequiredItopFiles(): void
|
||||
{
|
||||
parent::LoadRequiredItopFiles();
|
||||
|
||||
$this->RequireOnceItopFile('application/utils.inc.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Environment the test will run in
|
||||
* @since 2.7.9 3.0.4 3.1.0
|
||||
*/
|
||||
protected function GetTestEnvironment(): string
|
||||
{
|
||||
return self::DEFAULT_TEST_ENVIRONMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Absolute path of the configuration file used for the test
|
||||
* @since 2.7.9 3.0.4 3.1.0
|
||||
*/
|
||||
protected function GetConfigFileAbsPath(): string
|
||||
{
|
||||
return utils::GetConfigFilePath($this->GetTestEnvironment());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the iTop environment for test to run
|
||||
*
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \MySQLException
|
||||
* @since 2.7.9 3.0.4 3.1.0
|
||||
*/
|
||||
protected function PrepareEnvironment(): void
|
||||
{
|
||||
$sEnv = $this->GetTestEnvironment();
|
||||
$sConfigFile = $this->GetConfigFileAbsPath();
|
||||
|
||||
// Start MetaModel for the prepared environment
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -147,6 +203,31 @@ class ItopDataTestCase extends ItopTestCase
|
||||
return $this->iTestOrgId;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/// Facades for environment settings
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Facade for EventService::RegisterListener
|
||||
*
|
||||
* @param string $sEvent
|
||||
* @param callable $callback
|
||||
* @param $sEventSource
|
||||
* @param array $aCallbackData
|
||||
* @param $context
|
||||
* @param float $fPriority
|
||||
* @param $sModuleId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function EventService_RegisterListener(string $sEvent, callable $callback, $sEventSource = null, array $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
|
||||
{
|
||||
$ret = EventService::RegisterListener($sEvent, $callback, $sEventSource, $aCallbackData, $context, $fPriority, $sModuleId);
|
||||
if (false !== $ret) {
|
||||
$this->aEventListeners[] = $ret;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/// MetaModel Utilities
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@@ -186,7 +267,9 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$oMyObj->DBInsert();
|
||||
$iKey = $oMyObj->GetKey();
|
||||
$this->debug("Created $sClass::$iKey");
|
||||
$this->aCreatedObjects[] = $oMyObj;
|
||||
if (!static::USE_TRANSACTION) {
|
||||
$this->aCreatedObjects[] = $oMyObj;
|
||||
}
|
||||
|
||||
return $oMyObj;
|
||||
}
|
||||
@@ -841,49 +924,6 @@ class ItopDataTestCase extends ItopTestCase
|
||||
return $oOrg;
|
||||
}
|
||||
|
||||
public function ResetReloadCount()
|
||||
{
|
||||
$this->aReloadCount = [];
|
||||
}
|
||||
|
||||
public function DebugReloadCount($sMsg, $bResetCount = true)
|
||||
{
|
||||
$iTotalCount = 0;
|
||||
$aTotalPerClass = [];
|
||||
foreach ($this->aReloadCount as $sClass => $aCountByKeys) {
|
||||
$iClassCount = 0;
|
||||
foreach ($aCountByKeys as $iCount) {
|
||||
$iClassCount += $iCount;
|
||||
}
|
||||
$iTotalCount += $iClassCount;
|
||||
$aTotalPerClass[$sClass] = $iClassCount;
|
||||
}
|
||||
$this->debug("$sMsg - $iTotalCount reload(s)");
|
||||
foreach ($this->aReloadCount as $sClass => $aCountByKeys) {
|
||||
$this->debug(" $sClass => $aTotalPerClass[$sClass] reload(s)");
|
||||
foreach ($aCountByKeys as $sKey => $iCount) {
|
||||
$this->debug(" $sClass::$sKey => $iCount");
|
||||
}
|
||||
}
|
||||
if ($bResetCount) {
|
||||
$this->ResetReloadCount();
|
||||
}
|
||||
}
|
||||
|
||||
public function CountObjectReload(EventData $oData)
|
||||
{
|
||||
$oObject = $oData->Get('object');
|
||||
$sClass = get_class($oObject);
|
||||
$sKey = $oObject->GetKey();
|
||||
$iCount = $this->GetObjectReloadCount($sClass, $sKey);
|
||||
$this->aReloadCount[$sClass][$sKey] = 1 + $iCount;
|
||||
}
|
||||
|
||||
public function GetObjectReloadCount($sClass, $sKey)
|
||||
{
|
||||
return $this->aReloadCount[$sClass][$sKey] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a series of operations will trigger a given number of MySL queries
|
||||
*
|
||||
@@ -910,6 +950,20 @@ class ItopDataTestCase extends ItopTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6658 method creation
|
||||
*/
|
||||
protected function assertDBChangeOpCount(string $sClass, $iId, int $iExpectedCount)
|
||||
{
|
||||
$oSearch = new \DBObjectSearch('CMDBChangeOp');
|
||||
$oSearch->AddCondition('objclass', $sClass);
|
||||
$oSearch->AddCondition('objkey', $iId);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
$iCount = $oSet->Count();
|
||||
$this->assertEquals($iExpectedCount, $iCount, "Found $iCount changes for object $sClass::$iId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a set of XML files describing a consistent set of iTop objects
|
||||
* @param string[] $aFiles
|
||||
@@ -1,66 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2023 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest;
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Eric
|
||||
* Date: 20/11/2017
|
||||
* Time: 11:21
|
||||
*/
|
||||
|
||||
use CMDBSource;
|
||||
use MySQLTransactionNotClosedException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SetupUtils;
|
||||
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
|
||||
class ItopTestCase extends TestCase
|
||||
/**
|
||||
* Helper class to extend for tests that DO NOT need to access the DataModel or the Database
|
||||
*
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6658 move some setUp/tearDown code to the corresponding methods *BeforeClass to speed up tests process time.
|
||||
*/
|
||||
abstract class ItopTestCase extends TestCase
|
||||
{
|
||||
const TEST_LOG_DIR = 'test';
|
||||
static $DEBUG_UNIT_TEST = false;
|
||||
public const TEST_LOG_DIR = 'test';
|
||||
public static $DEBUG_UNIT_TEST = false;
|
||||
|
||||
/** @noinspection UsingInclusionOnceReturnValueInspection avoid errors for approot includes */
|
||||
protected function setUp(): void {
|
||||
$sAppRootRelPath = 'approot.inc.php';
|
||||
$sDepthSeparator = '../';
|
||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||
if (file_exists($sAppRootRelPath)) {
|
||||
require_once $sAppRootRelPath;
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* @link https://docs.phpunit.de/en/9.6/annotations.html#preserveglobalstate PHPUnit `preserveGlobalState` annotation documentation
|
||||
*
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6658 Override default value creation so that we don't need to add the annotation on each test classes that have runInSeparateProcess.
|
||||
* This parameter isn't used when test is run in the same process so ok to change it globally !
|
||||
*/
|
||||
protected $preserveGlobalState = false;
|
||||
|
||||
$sAppRootRelPath = $sDepthSeparator.$sAppRootRelPath;
|
||||
}
|
||||
/**
|
||||
* This method is called before the first test of this test class is run (in the current process).
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
static::$DEBUG_UNIT_TEST = getenv('DEBUG_UNIT_TEST');
|
||||
|
||||
$this->debug("\n----------\n---------- ".$this->getName()."\n----------\n");
|
||||
require_once static::GetAppRoot() . 'approot.inc.php';
|
||||
|
||||
if (false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME)) {
|
||||
if (false === defined('ITOP_PHPUNIT_RUNNING_CONSTANT_NAME')) {
|
||||
// setUp might be called multiple times, so protecting the define() call !
|
||||
define(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME, true);
|
||||
define('ITOP_PHPUNIT_RUNNING_CONSTANT_NAME', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run (in the current process).
|
||||
*/
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
parent::tearDownAfterClass();
|
||||
|
||||
if (method_exists('utils', 'GetConfig')) {
|
||||
// Reset the config by forcing the load from disk
|
||||
$oConfig = \utils::GetConfig(true);
|
||||
if (method_exists('MetaModel', 'SetConfig')) {
|
||||
\MetaModel::SetConfig($oConfig);
|
||||
}
|
||||
}
|
||||
if (method_exists('Dict', 'SetUserLanguage')) {
|
||||
\Dict::SetUserLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->debug("\n----------\n---------- ".$this->getName()."\n----------\n");
|
||||
|
||||
$this->LoadRequiredItopFiles();
|
||||
$this->LoadRequiredTestFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \MySQLTransactionNotClosedException see N°5538
|
||||
*
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°5538
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6658 if transaction not closed, we are now doing a rollback
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
@@ -68,10 +87,52 @@ class ItopTestCase extends TestCase
|
||||
|
||||
if (CMDBSource::IsInsideTransaction()) {
|
||||
// Nested transactions were opened but not finished !
|
||||
// Rollback to avoid side effects on next tests
|
||||
while (CMDBSource::IsInsideTransaction()) {
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
}
|
||||
throw new MySQLTransactionNotClosedException('Some DB transactions were opened but not closed ! Fix the code by adding ROLLBACK or COMMIT statements !', []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper than can be called in the context of a data provider
|
||||
*
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6658 method creation
|
||||
*/
|
||||
public static function GetAppRoot()
|
||||
{
|
||||
if (defined('APPROOT')) {
|
||||
return APPROOT;
|
||||
}
|
||||
|
||||
$sAppRootPath = static::GetFirstDirUpContainingFile(__DIR__, 'approot.inc.php');
|
||||
|
||||
return $sAppRootPath . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()}
|
||||
*
|
||||
* @return void
|
||||
* @since 2.7.9 3.0.4 3.1.0
|
||||
*/
|
||||
protected function LoadRequiredItopFiles(): void
|
||||
{
|
||||
// Empty until we actually need to require some files in the class
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()}
|
||||
*
|
||||
* @return void
|
||||
* @since 2.7.10 3.0.4 3.1.0
|
||||
*/
|
||||
protected function LoadRequiredTestFiles(): void
|
||||
{
|
||||
// Empty until we actually need to require some files in the class
|
||||
}
|
||||
|
||||
/**
|
||||
* Require once an iTop file (core or extension) from its relative path to the iTop root dir.
|
||||
* This ensure to always use the right absolute path, especially in {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()}
|
||||
@@ -83,7 +144,24 @@ class ItopTestCase extends TestCase
|
||||
*/
|
||||
protected function RequireOnceItopFile(string $sFileRelPath): void
|
||||
{
|
||||
require_once APPROOT . $sFileRelPath;
|
||||
require_once $this->GetAppRoot() . $sFileRelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to load a module file. The caller test must be in that module !
|
||||
* Will browse dir up to find a module.*.php
|
||||
*
|
||||
* @param string $sFileRelPath for example 'portal/src/Helper/ApplicationHelper.php'
|
||||
* @since 2.7.10 3.1.1 3.2.0 N°6709 method creation
|
||||
*/
|
||||
protected function RequireOnceCurrentModuleFile(string $sFileRelPath): void
|
||||
{
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
|
||||
$sCallerFileFullPath = $aStack[0]['file'];
|
||||
$sCallerDir = dirname($sCallerFileFullPath);
|
||||
|
||||
$sModuleRootPath = static::GetFirstDirUpContainingFile($sCallerDir, 'module.*.php');
|
||||
require_once $sModuleRootPath . $sFileRelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,6 +181,26 @@ class ItopTestCase extends TestCase
|
||||
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
|
||||
}
|
||||
|
||||
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
|
||||
{
|
||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
|
||||
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
|
||||
return $sSearchPath . '/';
|
||||
}
|
||||
$iOffsetSep = strrpos($sSearchPath, '/');
|
||||
if ($iOffsetSep === false) {
|
||||
$iOffsetSep = strrpos($sSearchPath, '\\');
|
||||
if ($iOffsetSep === false) {
|
||||
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
|
||||
return 'Could not find the approot file in ' . $sSearchPath;
|
||||
}
|
||||
}
|
||||
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function debug($sMsg)
|
||||
{
|
||||
if (static::$DEBUG_UNIT_TEST) {
|
||||
@@ -151,7 +249,7 @@ class ItopTestCase extends TestCase
|
||||
/**
|
||||
* @since 2.7.4 3.0.0
|
||||
*/
|
||||
public function InvokeNonPublicStaticMethod($sObjectClass, $sMethodName, $aArgs)
|
||||
public function InvokeNonPublicStaticMethod($sObjectClass, $sMethodName, $aArgs = [])
|
||||
{
|
||||
return $this->InvokeNonPublicMethod($sObjectClass, $sMethodName, null, $aArgs);
|
||||
}
|
||||
@@ -168,7 +266,7 @@ class ItopTestCase extends TestCase
|
||||
*
|
||||
* @since 2.7.4 3.0.0
|
||||
*/
|
||||
public function InvokeNonPublicMethod($sObjectClass, $sMethodName, $oObject, $aArgs)
|
||||
public function InvokeNonPublicMethod($sObjectClass, $sMethodName, $oObject, $aArgs = [])
|
||||
{
|
||||
$class = new \ReflectionClass($sObjectClass);
|
||||
$method = $class->getMethod($sMethodName);
|
||||
@@ -177,9 +275,8 @@ class ItopTestCase extends TestCase
|
||||
return $method->invokeArgs($oObject, $aArgs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 3.1.0
|
||||
* @since 2.7.10 3.1.0
|
||||
*/
|
||||
public function GetNonPublicStaticProperty(string $sClass, string $sProperty)
|
||||
{
|
||||
@@ -206,7 +303,7 @@ class ItopTestCase extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0
|
||||
* @since 2.7.10 3.1.0
|
||||
*/
|
||||
private function GetProperty(string $sClass, string $sProperty): \ReflectionProperty
|
||||
{
|
||||
@@ -232,7 +329,7 @@ class ItopTestCase extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0
|
||||
* @since 2.7.10 3.1.0
|
||||
*/
|
||||
public function SetNonPublicStaticProperty(string $sClass, string $sProperty, $value)
|
||||
{
|
||||
@@ -312,4 +409,4 @@ class ItopTestCase extends TestCase
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
tests/php-unit-tests/src/Hook/TestsRunStartHook.php
Normal file
63
tests/php-unit-tests/src/Hook/TestsRunStartHook.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Hook;
|
||||
|
||||
require_once __DIR__ . '/../../../../approot.inc.php';
|
||||
|
||||
use PHPUnit\Runner\AfterLastTestHook;
|
||||
use PHPUnit\Runner\BeforeFirstTestHook;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* Class TestsRunStartHook
|
||||
*
|
||||
* IMPORTANT: This will no longer work in PHPUnit 10.0 and there is no alternative for now, so we will have to migrate it when the time comes
|
||||
* @link https://localheinz.com/articles/2023/02/14/extending-phpunit-with-its-new-event-system/#content-hooks-event-system
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
* @package Combodo\iTop\Test\UnitTest\Hook
|
||||
* @since N°6097 2.7.10 3.0.4 3.1.1
|
||||
*/
|
||||
class TestsRunStartHook implements BeforeFirstTestHook, AfterLastTestHook
|
||||
{
|
||||
/**
|
||||
* Use the modification time on this file to check whereas it is newer than the requirements in a test case
|
||||
*
|
||||
* @return string Abs. path to a file generated when the global tests run starts.
|
||||
*/
|
||||
public static function GetRunStartedFileAbsPath(): string
|
||||
{
|
||||
// Note: This can't be put in the cache-<ENV> folder as we have multiple <ENV> running across the test cases
|
||||
// We also don't want to put it in the unit tests folder as it is not supposed to be writable
|
||||
return APPROOT.'data/.php-unit-tests-run-started';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function executeBeforeFirstTest(): void
|
||||
{
|
||||
// Create / change modification timestamp of file marking the beginning of the tests run
|
||||
touch(static::GetRunStartedFileAbsPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function executeAfterLastTest(): void
|
||||
{
|
||||
// Cleanup of file marking the beginning of the tests run
|
||||
if (file_exists(static::GetRunStartedFileAbsPath())) {
|
||||
unlink(static::GetRunStartedFileAbsPath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Service;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
use IssueLog;
|
||||
use MFCoreModule;
|
||||
use ReflectionClass;
|
||||
use RunTimeEnvironment;
|
||||
|
||||
|
||||
/**
|
||||
* Class UnitTestRunTimeEnvironment
|
||||
*
|
||||
* Runtime env. dedicated to creating a temp. environment for a group of unit tests with XML deltas.
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
* @since N°6097 2.7.10 3.0.4 3.1.1
|
||||
*/
|
||||
class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
{
|
||||
$aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir);
|
||||
|
||||
/** @var string[] $aDeltaFiles Referential of loaded deltas. Mostly to avoid duplicates. */
|
||||
$aDeltaFiles = [];
|
||||
foreach (get_declared_classes() as $sClass) {
|
||||
// Filter on classes derived from this \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCaseItopCustomDatamodelTestCase
|
||||
if (false === is_a($sClass, ItopCustomDatamodelTestCase::class, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$oReflectionMethod = $oReflectionClass->getMethod('GetDatamodelDeltaAbsPath');
|
||||
|
||||
// Filter on classes with an actual XML delta (eg. not \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase and maybe some other deriving from a class with a delta)
|
||||
if ($oReflectionMethod->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */
|
||||
$oTestClassInstance = new $sClass();
|
||||
|
||||
// Check test class is for desired environment
|
||||
if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check XML delta actually exists
|
||||
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
|
||||
if (false === is_file($sDeltaFile)) {
|
||||
$this->fail("Could not prepare '$this->sFinalEnv' as the XML delta file '$sDeltaFile' (used in $sClass) does not seem to exist");
|
||||
}
|
||||
|
||||
// Avoid duplicates
|
||||
if (in_array($sDeltaFile, $aDeltaFiles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prepare fake module name for delta
|
||||
$sDeltaName = preg_replace('/[^\d\w]/', '', $sDeltaFile);
|
||||
// Note: We can't use \MFDeltaModule as we can't specify the ID which leads to only 1 delta being applied... In the future we might introduce a new MFXXXModule, but in the meantime it feels alright (GLA / RQU)
|
||||
$oDelta = new MFCoreModule($sDeltaName, $sDeltaName, $sDeltaFile);
|
||||
|
||||
IssueLog::Debug('XML delta found for unit tests', static::class, [
|
||||
'Unit test class' => $sClass,
|
||||
'Delta file path' => $sDeltaFile,
|
||||
]);
|
||||
|
||||
$aDeltaFiles[] = $sDeltaFile;
|
||||
$aRet[$sDeltaName] = $oDelta;
|
||||
}
|
||||
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
}
|
||||
64
tests/php-unit-tests/tools/run_class_by_class.php
Normal file
64
tests/php-unit-tests/tools/run_class_by_class.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* Usage: php run_class_by_class.php
|
||||
*
|
||||
* Execute the whole test suite (as declared in phpunit.xml.dist) one class at a time.
|
||||
* This is to ensure that test class are still independant from each other, after a rework of ItopTestCase, for instance.
|
||||
*/
|
||||
const PHP_EXE = 'php';
|
||||
const ITOP_ROOT = __DIR__.'/../../..';
|
||||
|
||||
const ITOP_PHPUNIT = ITOP_ROOT.'/tests/php-unit-tests';
|
||||
const PHPUNIT_COMMAND = PHP_EXE.' '.ITOP_PHPUNIT.'/vendor/phpunit/phpunit/phpunit';
|
||||
|
||||
function ListTests($sUnitaryTestsDir = '')
|
||||
{
|
||||
$sConfigFile = ITOP_PHPUNIT."/phpunit.xml.dist";
|
||||
$sCommand = PHPUNIT_COMMAND." --configuration $sConfigFile --list-tests $sUnitaryTestsDir";
|
||||
exec($sCommand, $aOutput, $iResultCode);
|
||||
//passthru($sCommand, $iResultCode);
|
||||
if ($iResultCode != 0) { // or 1 in case of a failing test
|
||||
echo "Failed executing command: $sCommand\n";
|
||||
return [];
|
||||
}
|
||||
$aClasses = [];
|
||||
foreach ($aOutput as $iLine => $sLine) {
|
||||
// Example of formats to be filtered
|
||||
//- DatamodelsXmlFilesTest::testAllItopXmlFilesCovered
|
||||
//- Combodo\iTop\Test\UnitTest\Application\DashboardLayoutTest::testGetDashletCoordinates"OneColLayout-Cell0"
|
||||
//if (preg_match('@^- ([a-z]+\\\\)*([a-z]+::[a-z0-9]+)@i', $sLine, $aMatches)) {
|
||||
if (preg_match('@([a-z0-9]+)::test@i', $sLine, $aMatches)) {
|
||||
$sTestClass = $aMatches[1];
|
||||
$aClasses[$sTestClass] = $sTestClass;
|
||||
}
|
||||
}
|
||||
return array_keys($aClasses);
|
||||
}
|
||||
|
||||
function RunTests($sFilterRegExp, $sUnitaryTestsDir = '', $bPassthru = false)
|
||||
{
|
||||
$sRegExpShellArg = '"'.str_replace('"', '\\"', $sFilterRegExp).'"';
|
||||
$sConfigFile = ITOP_PHPUNIT."/phpunit.xml.dist";
|
||||
$sCommand = PHPUNIT_COMMAND." --configuration $sConfigFile --filter $sRegExpShellArg $sUnitaryTestsDir";
|
||||
///echo "executing <<<$sCommand>>>\n";
|
||||
if ($bPassthru) {
|
||||
passthru($sCommand, $iResultCode);
|
||||
}
|
||||
else {
|
||||
exec($sCommand, $aTrashedOutput, $iResultCode);
|
||||
}
|
||||
$bTestSuccess = ($iResultCode == 0); // or 1 in case of a failing test
|
||||
return $bTestSuccess;
|
||||
}
|
||||
|
||||
$sUnitaryTestsDir = '';
|
||||
|
||||
$aTestClasses = ListTests($sUnitaryTestsDir);
|
||||
echo "Found ".count($aTestClasses)." to execute: ".implode(", ", $aTestClasses)."\n";
|
||||
echo "Testing...\n";
|
||||
foreach ($aTestClasses as $sTestClass) {
|
||||
$fStarted = microtime(true);
|
||||
$bSuccess = RunTests($sTestClass);
|
||||
$sDuration = round(microtime(true) - $fStarted, 3);
|
||||
echo "$sTestClass: ".($bSuccess ? 'Ok' : "FAILURE")." [$sDuration s]\n";
|
||||
}
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class ApplicationObjectExtensionTest extends \Combodo\iTop\Test\UnitTest\ItopDataTestCase
|
||||
{
|
||||
|
||||
@@ -7,8 +7,6 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class SessionTest extends ItopTestCase
|
||||
{
|
||||
|
||||
@@ -7,9 +7,6 @@ use FindStylesheetObject;
|
||||
use ThemeHandler;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
* @covers ThemeHandler
|
||||
*/
|
||||
class ThemeHandlerTest extends ItopTestCase
|
||||
|
||||
@@ -9,9 +9,6 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
* Class NavigationMenuTest
|
||||
*
|
||||
* @package UI\Base\Layout
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Application;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* @covers \iBackofficeLinkedScriptsExtension
|
||||
*/
|
||||
class ApplicationExtensionTest extends ItopCustomDatamodelTestCase
|
||||
{
|
||||
protected const ENUM_API_CALL_METHOD_ENUMPLUGINS = 'MetaModel::EnumPlugins';
|
||||
protected const ENUM_API_CALL_METHOD_GETCLASSESFORINTERFACE = 'utils::GetClassesForInterface';
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
return __DIR__ . '/Delta/application-extension-usages-in-snippets.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* This test ensures that APIs are discovered / registered / called.
|
||||
*
|
||||
* It was introduced after {@since N°6436} when some APIs registration in \MetaModel::InitClasses() were lost during a merge {@link https://github.com/Combodo/iTop/commit/6432678de9f635990e22a6512e5b30713c22204a#diff-c94890a26989b5a5ce638f82e8cc7d4c7aa24e6fbb9c2ca89850e8fa4e0e9adaL3004} preventing them from being called when requested. This was hard to detect as it needed an extension to use the lost API to witness that it was no longer called.
|
||||
*
|
||||
* For each new API, a new test case should be added here to ensure that we don't lose it later.
|
||||
* To do so:
|
||||
* - Add the API to the provider
|
||||
* - Add a class extending / implementing the API in ./Delta/application-extension-usages-in-snippets.xml
|
||||
*
|
||||
* @param string $sAPIFQCN
|
||||
* @param string $sCallMethod
|
||||
*
|
||||
* @return void
|
||||
* @dataProvider ExtensionAPIRegisteredAndCalledProvider
|
||||
*/
|
||||
public function testExtensionAPIRegisteredAndCalled(string $sAPIFQCN, string $sCallMethod)
|
||||
{
|
||||
if ($sCallMethod === static::ENUM_API_CALL_METHOD_ENUMPLUGINS) {
|
||||
$iExtendingClassesCount = count(MetaModel::EnumPlugins($sAPIFQCN));
|
||||
} else {
|
||||
$iExtendingClassesCount = count(utils::GetClassesForInterface($sAPIFQCN, '', ['[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]']));
|
||||
}
|
||||
$this->assertGreaterThan(0, $iExtendingClassesCount, "Found no class extending the $sAPIFQCN API");
|
||||
}
|
||||
|
||||
public function ExtensionAPIRegisteredAndCalledProvider(): array
|
||||
{
|
||||
// APIs not concerned by this test:
|
||||
// * \iRestServiceProvider as it is discovered by iterating over declared classes directly
|
||||
// * \iLoginUIExtension as it is not iterated directly, only its derived interfaces
|
||||
|
||||
return [
|
||||
\iLoginFSMExtension::class => [
|
||||
\iLoginFSMExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iLogoutExtension::class => [
|
||||
\iLogoutExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iLoginUIExtension::class => [
|
||||
\iLoginUIExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iPreferencesExtension::class => [
|
||||
\iPreferencesExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iApplicationUIExtension::class => [
|
||||
\iApplicationUIExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iApplicationObjectExtension::class => [
|
||||
\iApplicationObjectExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iPopupMenuExtension::class => [
|
||||
\iPopupMenuExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iPageUIExtension::class => [
|
||||
\iPageUIExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iPageUIBlockExtension::class => [
|
||||
\iPageUIBlockExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeLinkedScriptsExtension::class => [
|
||||
\iBackofficeLinkedScriptsExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeEarlyScriptExtension::class => [
|
||||
\iBackofficeEarlyScriptExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeScriptExtension::class => [
|
||||
\iBackofficeScriptExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeInitScriptExtension::class => [
|
||||
\iBackofficeInitScriptExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeReadyScriptExtension::class => [
|
||||
\iBackofficeReadyScriptExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeLinkedStylesheetsExtension::class => [
|
||||
\iBackofficeLinkedStylesheetsExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeStyleExtension::class => [
|
||||
\iBackofficeStyleExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeDictEntriesExtension::class => [
|
||||
\iBackofficeDictEntriesExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iBackofficeDictEntriesPrefixesExtension::class => [
|
||||
\iBackofficeDictEntriesPrefixesExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iPortalUIExtension::class => [
|
||||
\iPortalUIExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iQueryModifier::class => [
|
||||
\iQueryModifier::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iOnClassInitialization::class => [
|
||||
\iOnClassInitialization::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iFieldRendererMappingsExtension::class => [
|
||||
\iFieldRendererMappingsExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_GETCLASSESFORINTERFACE,
|
||||
],
|
||||
\iModuleExtension::class => [
|
||||
\iModuleExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iKPILoggerExtension::class => [
|
||||
\iKPILoggerExtension::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\ModuleHandlerApiInterface::class => [
|
||||
\ModuleHandlerApiInterface::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
\iNewsroomProvider::class => [
|
||||
\iNewsroomProvider::class,
|
||||
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
|
||||
<snippets>
|
||||
<!-- These snippets just implements application/applicationextension.inc.php APIs for the ApplicationExtensionTest unit test -->
|
||||
<snippet id="ExampleFor_iLoginFSMExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iLoginFSMExtension extends \AbstractLoginFSMExtension
|
||||
{
|
||||
public function ListSupportedLoginModes()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iLogoutExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iLogoutExtension implements \iLogoutExtension
|
||||
{
|
||||
public function LogoutAction()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
|
||||
public function ListSupportedLoginModes()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iLoginUIExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iLoginUIExtension implements \iLoginUIExtension
|
||||
{
|
||||
public function GetTwigContext()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
|
||||
public function ListSupportedLoginModes()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iPreferencesExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iPreferencesExtension extends \AbstractPreferencesExtension
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iApplicationUIExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iApplicationUIExtension extends \AbstractApplicationUIExtension
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iApplicationObjectExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iApplicationObjectExtension extends \AbstractApplicationObjectExtension
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iPopupMenuExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iPopupMenuExtension implements \iPopupMenuExtension
|
||||
{
|
||||
public static function EnumItems($iMenuId, $param)
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_ApplicationPopupMenuItem" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_ApplicationPopupMenuItem extends \ApplicationPopupMenuItem
|
||||
{
|
||||
public function GetMenuItem()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iPageUIExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iPageUIExtension extends \AbstractPageUIExtension
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExamplePageUIBlockExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iPageUIBlockExtension extends \AbstractPageUIBlockExtension
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeLinkedScriptsExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeLinkedScriptsExtension implements \iBackofficeLinkedScriptsExtension
|
||||
{
|
||||
public function GetLinkedScriptsAbsUrls(): array
|
||||
{
|
||||
return [
|
||||
'https://foo.bar/first.js',
|
||||
'https://foo.bar/second.js',
|
||||
];
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeEarlyScriptExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeEarlyScriptExtension implements \iBackofficeEarlyScriptExtension
|
||||
{
|
||||
public function GetEarlyScript(): string
|
||||
{
|
||||
return <<<JS
|
||||
console.log('This is a PHP unit test');
|
||||
JS;
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeScriptExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeScriptExtension implements \iBackofficeScriptExtension
|
||||
{
|
||||
public function GetScript(): string
|
||||
{
|
||||
return <<<JS
|
||||
console.log('This is a PHP unit test');
|
||||
JS;
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeInitScriptExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeInitScriptExtension implements \iBackofficeInitScriptExtension
|
||||
{
|
||||
public function GetInitScript(): string
|
||||
{
|
||||
return <<<JS
|
||||
console.log('This is a PHP unit test');
|
||||
JS;
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeReadyScriptExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeReadyScriptExtension implements \iBackofficeReadyScriptExtension
|
||||
{
|
||||
public function GetReadyScript(): string
|
||||
{
|
||||
return <<<JS
|
||||
console.log('This is a PHP unit test');
|
||||
JS;
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeLinkedStylesheetsExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeLinkedStylesheetsExtension implements \iBackofficeLinkedStylesheetsExtension
|
||||
{
|
||||
public function GetLinkedStylesheetsAbsUrls(): array
|
||||
{
|
||||
return [
|
||||
'https://foo.bar/first.css',
|
||||
'https://foo.bar/second.css',
|
||||
];
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeStyleExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeStyleExtension implements \iBackofficeStyleExtension
|
||||
{
|
||||
public function GetStyle(): string
|
||||
{
|
||||
return <<<CSS
|
||||
.foo {
|
||||
color: inherit;
|
||||
}
|
||||
CSS;
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeDictEntriesExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeDictEntriesExtension implements \iBackofficeDictEntriesExtension
|
||||
{
|
||||
public function GetDictEntries(): array
|
||||
{
|
||||
return [
|
||||
'Foo:First' => 'Foo is first',
|
||||
'Foo:Second' => 'Foo is second',
|
||||
];
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iBackofficeDictEntriesPrefixesExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iBackofficeDictEntriesPrefixesExtension implements \iBackofficeDictEntriesPrefixesExtension
|
||||
{
|
||||
public function GetDictEntriesPrefixes(): array
|
||||
{
|
||||
return [
|
||||
'Foo:',
|
||||
'Bar:',
|
||||
];
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iPortalUIExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iPortalUIExtension extends \AbstractPortalUIExtension
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iQueryModifier" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iQueryModifier implements \iQueryModifier
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
|
||||
public function GetFieldExpression(QueryBuilderContext &$oBuild, $sClass, $sAttCode, $sColId, Expression $oFieldSQLExp, SQLQuery &$oSelect)
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iOnClassInitialization" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iOnClassInitialization implements \iOnClassInitialization
|
||||
{
|
||||
public function OnAfterClassInitialization($sClass)
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iFieldRendererMappingsExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iFieldRendererMappingsExtension implements \iFieldRendererMappingsExtension
|
||||
{
|
||||
public static function RegisterSupportedFields(): array
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iModuleExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iModuleExtension implements \iModuleExtension
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<snippet id="ExampleFor_iKPILoggerExtension" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iKPILoggerExtension implements \iKPILoggerExtension
|
||||
{
|
||||
public function InitStats()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
|
||||
public function LogOperation($oKpiLogData)
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<!-- These snippets just implements core/modulehandler.class.inc.php APIs for the ApplicationExtensionTest unit test -->
|
||||
<snippet id="ExampleFor_ModuleHandlerApiInterface" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_ModuleHandlerApiInterface extends \ModuleHandlerAPI
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
<!-- These snippets just implements application/newsroomprovider.class.inc.php APIs for the ApplicationExtensionTest unit test -->
|
||||
<snippet id="ExampleFor_iNewsroomProvider" _delta="define">
|
||||
<placement>core</placement>
|
||||
<rank>0</rank>
|
||||
<content><![CDATA[
|
||||
class ExampleFor_iNewsroomProvider extends \NewsroomProviderBase
|
||||
{
|
||||
public function GetLabel()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
|
||||
public function GetFetchURL()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
|
||||
public function GetMarkAllAsReadURL()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
|
||||
public function GetViewAllURL()
|
||||
{
|
||||
// Do nothing, we just need the class to exists for the unit test
|
||||
}
|
||||
}
|
||||
]]></content>
|
||||
</snippet>
|
||||
</snippets>
|
||||
</itop_design>
|
||||
@@ -4,11 +4,6 @@ use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
const USE_TRANSACTION = true;
|
||||
const CREATE_TEST_ORG = true;
|
||||
@@ -18,6 +13,8 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::$aEventCalls = [];
|
||||
static::$iEventCalls = 0;
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
@@ -80,6 +77,9 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
self::assertSame($aExpectedLinkStack, $aLinkModificationsStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testProcessClassIdDeferredUpdate()
|
||||
{
|
||||
// Create the team
|
||||
@@ -157,6 +157,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are not sent to the current updated/created object (Team)
|
||||
* the events are sent to the other side (Person)
|
||||
*
|
||||
@@ -188,7 +189,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
|
||||
$oEventReceiver = new LinksEventReceiver();
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'persons_list' => $oLinkSet, 'org_id' => $this->getTestOrgId()]);
|
||||
@@ -200,6 +201,8 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when creating a new lnk object
|
||||
*
|
||||
* @return void
|
||||
@@ -223,7 +226,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver();
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link creation will signal both the Person an the Team
|
||||
@@ -235,6 +238,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when updating an existing lnk object
|
||||
*
|
||||
* @return void
|
||||
@@ -262,7 +266,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver();
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link update will signal both the Person, the Team and the ContactType
|
||||
@@ -277,6 +281,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* Check that when a link changes from an object to another, then both objects are notified
|
||||
*
|
||||
* @return void
|
||||
@@ -307,7 +312,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver();
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link update will signal both the Persons and the Team
|
||||
@@ -320,6 +325,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when deleting an existing lnk object
|
||||
*
|
||||
* @return void
|
||||
@@ -347,7 +353,7 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver();
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link delete will signal both the Person an the Team
|
||||
@@ -383,10 +389,16 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
*/
|
||||
class LinksEventReceiver
|
||||
{
|
||||
private $oTestCase;
|
||||
private $aCallbacks = [];
|
||||
|
||||
public static $bIsObjectInCrudStack;
|
||||
|
||||
public function __construct(ItopDataTestCase $oTestCase)
|
||||
{
|
||||
$this->oTestCase = $oTestCase;
|
||||
}
|
||||
|
||||
public function AddCallback(string $sEvent, string $sClass, string $sFct, int $iCount = 1): void
|
||||
{
|
||||
$this->aCallbacks[$sEvent][$sClass] = [
|
||||
@@ -423,53 +435,10 @@ class LinksEventReceiver
|
||||
{
|
||||
$this->Debug('Registering Test event listeners');
|
||||
if (is_null($sEvent)) {
|
||||
EventService::RegisterListener(EVENT_DB_LINKS_CHANGED, [$this, 'OnEvent']);
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_LINKS_CHANGED, [$this, 'OnEvent']);
|
||||
return;
|
||||
}
|
||||
EventService::RegisterListener($sEvent, [$this, 'OnEvent'], $mEventSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oObject
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
private function AddRoleToLink($oObject): void
|
||||
{
|
||||
$this->Debug(__METHOD__);
|
||||
$oContactType = MetaModel::NewObject(ContactType::class, ['name' => 'test_'.$oObject->GetKey()]);
|
||||
$oContactType->DBInsert();
|
||||
$oObject->Set('role_id', $oContactType->GetKey());
|
||||
}
|
||||
|
||||
private function SetPersonFunction($oObject): void
|
||||
{
|
||||
$this->Debug(__METHOD__);
|
||||
$oObject->Set('function', 'CRUD_function_'.rand());
|
||||
}
|
||||
|
||||
private function SetPersonFirstName($oObject): void
|
||||
{
|
||||
$this->Debug(__METHOD__);
|
||||
$oObject->Set('first_name', 'CRUD_first_name_'.rand());
|
||||
}
|
||||
|
||||
private function CheckCrudStack(DBObject $oObject): void
|
||||
{
|
||||
self::$bIsObjectInCrudStack = DBObject::IsObjectCurrentlyInCrud(get_class($oObject), $oObject->GetKey());
|
||||
}
|
||||
|
||||
private function CheckUpdateInLnk(lnkPersonToTeam $oLnkPersonToTeam)
|
||||
{
|
||||
$iTeamId = $oLnkPersonToTeam->Get('team_id');
|
||||
self::$bIsObjectInCrudStack = DBObject::IsObjectCurrentlyInCrud(Team::class, $iTeamId);
|
||||
$this->oTestCase->EventService_RegisterListener($sEvent, [$this, 'OnEvent'], $mEventSource);
|
||||
}
|
||||
|
||||
private function Debug($msg)
|
||||
|
||||
@@ -25,10 +25,6 @@ use privUITransactionFile;
|
||||
use UserRights;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*
|
||||
* @covers utils
|
||||
* @group sampleDataNeeded
|
||||
* @group defaultProfiles
|
||||
@@ -180,7 +176,7 @@ class privUITransactionFileTest extends ItopDataTestCase
|
||||
$this->assertTrue($bResult, 'Token created by support user must be removed in the support user context');
|
||||
|
||||
// test when no user logged (combodo-unauthenticated-form module for example)
|
||||
UserRights::_ResetSessionCache();
|
||||
UserRights::Logoff();
|
||||
$sTransactionIdUnauthenticatedUser = privUITransactionFile::GetNewTransactionId();
|
||||
$bResult = privUITransactionFile::IsTransactionValid($sTransactionIdUnauthenticatedUser, false);
|
||||
$this->assertTrue($bResult, 'Token created by unauthenticated user must be valid when no user logged');
|
||||
|
||||
@@ -33,9 +33,6 @@ use QueryOQL;
|
||||
* All objects created in this test will be deleted by the test.
|
||||
*
|
||||
* @group iTopQuery
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class QueryTest extends ItopDataTestCase
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* @runClassInSeparateProcess
|
||||
* @covers utils
|
||||
*/
|
||||
class utilsTest extends ItopTestCase
|
||||
@@ -68,36 +69,36 @@ class utilsTest extends ItopTestCase
|
||||
|
||||
public function realPathDataProvider()
|
||||
{
|
||||
parent::setUp(); // if not called, APPROOT won't be defined :(
|
||||
$sAppRoot = static::GetAppRoot();
|
||||
|
||||
$sSep = DIRECTORY_SEPARATOR;
|
||||
$sItopRootRealPath = realpath(APPROOT).$sSep;
|
||||
$sItopRootRealPath = realpath($sAppRoot).$sSep;
|
||||
$sLicenseFileName = 'license.txt';
|
||||
if (!is_file(APPROOT.$sLicenseFileName))
|
||||
if (!is_file($sAppRoot.$sLicenseFileName))
|
||||
{
|
||||
$sLicenseFileName = 'LICENSE';
|
||||
}
|
||||
|
||||
return [
|
||||
$sLicenseFileName => [APPROOT.$sLicenseFileName, APPROOT, $sItopRootRealPath.$sLicenseFileName],
|
||||
'unexisting file' => [APPROOT.'license_DOES_NOT_EXIST.txt', APPROOT, false],
|
||||
'/'.$sLicenseFileName => [APPROOT.$sSep.$sLicenseFileName, APPROOT, $sItopRootRealPath.$sLicenseFileName],
|
||||
'%2f'.$sLicenseFileName => [APPROOT.'%2f'. $sLicenseFileName, APPROOT, false],
|
||||
'../'.$sLicenseFileName => [APPROOT.'..'.$sSep.$sLicenseFileName, APPROOT, false],
|
||||
'%2e%2e%2f'.$sLicenseFileName => [APPROOT.'%2e%2e%2f'.$sLicenseFileName, APPROOT, false],
|
||||
$sLicenseFileName => [$sAppRoot.$sLicenseFileName, $sAppRoot, $sItopRootRealPath.$sLicenseFileName],
|
||||
'unexisting file' => [$sAppRoot.'license_DOES_NOT_EXIST.txt', $sAppRoot, false],
|
||||
'/'.$sLicenseFileName => [$sAppRoot.$sSep.$sLicenseFileName, $sAppRoot, $sItopRootRealPath.$sLicenseFileName],
|
||||
'%2f'.$sLicenseFileName => [$sAppRoot.'%2f'. $sLicenseFileName, $sAppRoot, false],
|
||||
'../'.$sLicenseFileName => [$sAppRoot.'..'.$sSep.$sLicenseFileName, $sAppRoot, false],
|
||||
'%2e%2e%2f'.$sLicenseFileName => [$sAppRoot.'%2e%2e%2f'.$sLicenseFileName, $sAppRoot, false],
|
||||
'application/utils.inc.php with basepath=APPROOT' => [
|
||||
APPROOT.'application/utils.inc.php',
|
||||
APPROOT,
|
||||
$sAppRoot.'application/utils.inc.php',
|
||||
$sAppRoot,
|
||||
$sItopRootRealPath.'application'.$sSep.'utils.inc.php',
|
||||
],
|
||||
'application/utils.inc.php with basepath=APPROOT/application' => [
|
||||
APPROOT.'application/utils.inc.php',
|
||||
APPROOT.'application',
|
||||
$sAppRoot.'application/utils.inc.php',
|
||||
$sAppRoot.'application',
|
||||
$sItopRootRealPath.'application'.$sSep.'utils.inc.php',
|
||||
],
|
||||
'basepath containing / and \\' => [
|
||||
APPROOT.'sources/Form/Form.php',
|
||||
APPROOT.'sources/Form\\Form.php',
|
||||
$sAppRoot.'sources/Form/Form.php',
|
||||
$sAppRoot.'sources/Form\\Form.php',
|
||||
$sItopRootRealPath.'sources'.$sSep.'Form'.$sSep.'Form.php',
|
||||
],
|
||||
];
|
||||
@@ -117,13 +118,14 @@ class utilsTest extends ItopTestCase
|
||||
|
||||
public function LocalPathProvider()
|
||||
{
|
||||
$sAppRoot = static::GetAppRoot();
|
||||
return array(
|
||||
'index.php' => array(
|
||||
'sAbsolutePath' => APPROOT.'index.php',
|
||||
'sAbsolutePath' => $sAppRoot.'index.php',
|
||||
'expected' => 'index.php',
|
||||
),
|
||||
'non existing' => array(
|
||||
'sAbsolutePath' => APPROOT.'nonexisting/nonexisting',
|
||||
'sAbsolutePath' => $sAppRoot.'nonexisting/nonexisting',
|
||||
'expected' => false,
|
||||
),
|
||||
'outside' => array(
|
||||
@@ -131,15 +133,15 @@ class utilsTest extends ItopTestCase
|
||||
'expected' => false,
|
||||
),
|
||||
'application/cmdbabstract.class.inc.php' => array(
|
||||
'sAbsolutePath' => APPROOT.'application/cmdbabstract.class.inc.php',
|
||||
'sAbsolutePath' => $sAppRoot.'application/cmdbabstract.class.inc.php',
|
||||
'expected' => 'application/cmdbabstract.class.inc.php',
|
||||
),
|
||||
'dir' => array(
|
||||
'sAbsolutePath' => APPROOT.'application/.',
|
||||
'sAbsolutePath' => $sAppRoot.'application/.',
|
||||
'expected' => 'application',
|
||||
),
|
||||
'root' => array(
|
||||
'sAbsolutePath' => APPROOT.'.',
|
||||
'sAbsolutePath' => $sAppRoot.'.',
|
||||
'expected' => '',
|
||||
),
|
||||
);
|
||||
@@ -288,7 +290,7 @@ class utilsTest extends ItopTestCase
|
||||
|
||||
public function GetDefaultUrlAppRootProvider()
|
||||
{
|
||||
$this->setUp();
|
||||
$sAppRoot = static::GetAppRoot();
|
||||
|
||||
$baseServerVar = [
|
||||
'REMOTE_ADDR' => '127.0.0.1', //is not set, disable IsProxyTrusted
|
||||
@@ -298,7 +300,7 @@ class utilsTest extends ItopTestCase
|
||||
'HTTP_X_FORWARDED_PORT' => null,
|
||||
'REQUEST_URI' => '/index.php?baz=1',
|
||||
'SCRIPT_NAME' => '/index.php',
|
||||
'SCRIPT_FILENAME' => APPROOT.'index.php',
|
||||
'SCRIPT_FILENAME' => $sAppRoot.'index.php',
|
||||
'QUERY_STRING' => 'baz=1',
|
||||
'HTTP_X_FORWARDED_PROTO' => null,
|
||||
'HTTP_X_FORWARDED_PROTOCOL' => null,
|
||||
@@ -629,23 +631,21 @@ class utilsTest extends ItopTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider GetMentionedObjectsFromTextProvider
|
||||
* @covers utils::GetMentionedObjectsFromText
|
||||
*
|
||||
* @param string $sInput
|
||||
* @param string $sFormat
|
||||
* @param array $aExceptedMentionedObjects
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetMentionedObjectsFromText(string $sInput, string $sFormat, array $aExceptedMentionedObjects)
|
||||
public function testGetMentionedObjectsFromText()
|
||||
{
|
||||
$aTestedMentionedObjects = utils::GetMentionedObjectsFromText($sInput, $sFormat);
|
||||
// Emulate the "Case provider mechanism" (reason: the data provider requires utils constants not available before the application startup)
|
||||
foreach ($this->GetMentionedObjectsFromTextProvider() as $sCase => list($sInput, $sFormat, $aExceptedMentionedObjects)) {
|
||||
$aTestedMentionedObjects = utils::GetMentionedObjectsFromText($sInput, $sFormat);
|
||||
|
||||
$sExpectedAsString = print_r($aExceptedMentionedObjects, true);
|
||||
$sTestedAsString = print_r($aTestedMentionedObjects, true);
|
||||
$sExpectedAsString = print_r($aExceptedMentionedObjects, true);
|
||||
$sTestedAsString = print_r($aTestedMentionedObjects, true);
|
||||
|
||||
$this->assertEquals($sTestedAsString, $sExpectedAsString, "Found mentioned objects don't match. Got: $sTestedAsString, expected $sExpectedAsString");
|
||||
$this->assertEquals($sTestedAsString, $sExpectedAsString, "Case '$sCase': Found mentioned objects don't match. Got: $sTestedAsString, expected $sExpectedAsString");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -779,17 +779,13 @@ class utilsTest extends ItopTestCase
|
||||
/**
|
||||
* Test sanitizer.
|
||||
*
|
||||
* @param $type string type of sanitizer
|
||||
* @param $valueToSanitize ? value to sanitize
|
||||
* @param $expectedResult ? expected result
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @dataProvider sanitizerDataProvider
|
||||
*/
|
||||
public function testSanitizer($type, $valueToSanitize, $expectedResult)
|
||||
public function testSanitizer()
|
||||
{
|
||||
$this->assertEquals($expectedResult, utils::Sanitize($valueToSanitize, null, $type), 'url sanitize failed');
|
||||
// Emulate the "Case provider mechanism" (reason: the data provider requires utils constants not available before the application startup)
|
||||
foreach ($this->sanitizerDataProvider() as $sCase => list($type, $valueToSanitize, $expectedResult)) {
|
||||
$this->assertEquals($expectedResult, utils::Sanitize($valueToSanitize, null, $type), "Case '$sCase': url sanitize failed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,9 +10,6 @@ use utils;
|
||||
use Dict;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
* @covers \ActionEmail
|
||||
*/
|
||||
class ActionEmailTest extends ItopDataTestCase
|
||||
|
||||
@@ -5,11 +5,6 @@ namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class AttributeDefinitionTest extends ItopDataTestCase {
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
|
||||
@@ -5,11 +5,6 @@ namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
use AttributeURLDefaultPattern;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class AttributeURLTest extends ItopTestCase {
|
||||
public function setUp(): void
|
||||
{
|
||||
|
||||
@@ -7,9 +7,7 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
* @runClassInSeparateProcess
|
||||
*/
|
||||
class BulkChangeTest extends ItopDataTestCase {
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
@@ -15,11 +15,6 @@ use MetaModel;
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class CMDBObjectTest extends ItopDataTestCase
|
||||
{
|
||||
private $sAdminLogin;
|
||||
@@ -99,6 +94,7 @@ class CMDBObjectTest extends ItopDataTestCase
|
||||
* @covers CMDBObject::SetCurrentChange
|
||||
* @since 3.0.1 N°5135 - Impersonate: history of changes versus log entries
|
||||
*
|
||||
* @runInSeparateProcess
|
||||
* @dataProvider CurrentChangeUnderImpersonationProvider
|
||||
*/
|
||||
public function testCurrentChangeUnderImpersonation($sTrackInfo=null, $sExpectedChangeLogWhenImpersonation=null) {
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Core\DbConnectionWrapper;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use Exception;
|
||||
use IssueLog;
|
||||
use LogChannels;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
@@ -14,11 +17,6 @@ use utils;
|
||||
* @package Combodo\iTop\Test\UnitTest\Core
|
||||
*/
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class CMDBSourceTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
@@ -123,6 +121,8 @@ class CMDBSourceTest extends ItopTestCase
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
* @since 3.0.0 N°4215
|
||||
*
|
||||
* @runInSeparateProcess Resetting DB connection, thus making other tests to fail!
|
||||
*/
|
||||
public function testIsOpenedDbConnectionUsingTls()
|
||||
{
|
||||
@@ -137,4 +137,49 @@ class CMDBSourceTest extends ItopTestCase
|
||||
$bIsTlsCnx = $this->InvokeNonPublicStaticMethod(CMDBSource::class, 'IsOpenedDbConnectionUsingTls', [$oMysqli]);
|
||||
$this->assertFalse($bIsTlsCnx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6643 Checks writing in IssueLog is really done
|
||||
*/
|
||||
public function testLogDeadLock(): void
|
||||
{
|
||||
$sExceptionMessage = 'Test exception for deadlock';
|
||||
$oDeadlockException = new Exception($sExceptionMessage);
|
||||
|
||||
// \CMDBSource::LogDeadLock uses mysqli::errno and mysqli::query()
|
||||
// I didn't achieve mocking the errno property by either of the following means :
|
||||
// - PHPUnit mock => property is read only error
|
||||
// - DbConnectionWrapper::SetDbConnectionMockForQuery with a custom mysqli children
|
||||
// - override of errno property with an assignment (public $errno = ...;) => property is read only error
|
||||
// - override of __get() for the errno property => no error but no change
|
||||
// Solution for errno was to add a new parameter to disable errno read :/
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
$oMockMysqli = $this->getMockBuilder('mysqli')
|
||||
->setMethods(['query'])
|
||||
->getMock();
|
||||
$oMockMysqli->expects($this->any())
|
||||
->method('query')
|
||||
->willReturnCallback(function () {
|
||||
return false;
|
||||
});
|
||||
DbConnectionWrapper::SetDbConnectionMockForQuery($oMockMysqli);
|
||||
|
||||
$sTestErrorLogPath = APPROOT . 'log/error.phpunit.log';
|
||||
IssueLog::Enable($sTestErrorLogPath);
|
||||
try {
|
||||
$this->InvokeNonPublicStaticMethod(CMDBSource::class, 'LogDeadLock', [$oDeadlockException, true, false]);
|
||||
$sLastErrorLogLine = $this->GetErrorLogLastLines($sTestErrorLogPath, 10); // we are getting multiple lines as the context log introduced multiple lines per log
|
||||
$this->assertStringContainsString(LogChannels::DEADLOCK, $sLastErrorLogLine);
|
||||
$this->assertStringContainsString($sExceptionMessage, $sLastErrorLogLine);
|
||||
} finally {
|
||||
if (file_exists($sTestErrorLogPath)) {
|
||||
unlink($sTestErrorLogPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function GetErrorLogLastLines(string $sErrorLogPath, int $iLineNumbers = 1): string
|
||||
{
|
||||
return trim(implode("", array_slice(file($sErrorLogPath), -$iLineNumbers)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ use MetaModel;
|
||||
use MySQLTransactionNotClosedException;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*
|
||||
* @group itopRequestMgmt
|
||||
* @group specificOrgInSampleData
|
||||
* Class TransactionsTest
|
||||
@@ -108,6 +104,15 @@ class TransactionsTest extends ItopTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test case was originaly in DBInsertProvider
|
||||
* @runInSeparateProcess Failing when run in the same process as other...
|
||||
*/
|
||||
public function testDBInsertCaseHistory38()
|
||||
{
|
||||
$this->testDBInsert(40, false);
|
||||
}
|
||||
|
||||
public function DBInsertProvider()
|
||||
{
|
||||
return [
|
||||
@@ -151,7 +156,6 @@ class TransactionsTest extends ItopTestCase
|
||||
"History 35" => ['iFailAt' => 37, 'bIsInDB' => false],
|
||||
"History 36" => ['iFailAt' => 38, 'bIsInDB' => false],
|
||||
"History 37" => ['iFailAt' => 39, 'bIsInDB' => false],
|
||||
"History 38" => ['iFailAt' => 40, 'bIsInDB' => false],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -276,6 +280,7 @@ class TransactionsTest extends ItopTestCase
|
||||
protected function tearDown(): void
|
||||
{
|
||||
try {
|
||||
DbConnectionWrapper::SetDbConnectionMockForQuery();
|
||||
parent::tearDown();
|
||||
}
|
||||
catch (MySQLTransactionNotClosedException $e) {
|
||||
|
||||
@@ -12,6 +12,7 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ContactType;
|
||||
use CoreException;
|
||||
use DBObject;
|
||||
use DBObject\MockDBObjectWithCRUDEventListener;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
use lnkPersonToTeam;
|
||||
@@ -21,11 +22,6 @@ use Person;
|
||||
use Team;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class CRUDEventTest extends ItopDataTestCase
|
||||
{
|
||||
const USE_TRANSACTION = true;
|
||||
@@ -37,6 +33,8 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::$aEventCalls = [];
|
||||
static::$iEventCalls = 0;
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
@@ -54,7 +52,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
*/
|
||||
public function testDBInsert()
|
||||
{
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
$oOrg = $this->CreateOrganization('Organization1');
|
||||
@@ -77,7 +75,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oOrg = $this->CreateOrganization('Organization1');
|
||||
$this->assertIsObject($oOrg);
|
||||
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
$oOrg->Set('name', 'test');
|
||||
@@ -101,7 +99,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oOrg = $this->CreateOrganization('Organization1');
|
||||
$this->assertIsObject($oOrg);
|
||||
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
$oOrg->DBUpdate();
|
||||
@@ -122,7 +120,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
*/
|
||||
public function testComputeValuesOnInsert()
|
||||
{
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Set the person's first name during Compute Values
|
||||
$oEventReceiver->AddCallback(EVENT_DB_COMPUTE_VALUES, Person::class, 'SetPersonFirstName');
|
||||
$oEventReceiver->RegisterCRUDListeners(EVENT_DB_COMPUTE_VALUES);
|
||||
@@ -155,7 +153,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Set the person's first name during Compute Values
|
||||
$oEventReceiver->AddCallback(EVENT_DB_COMPUTE_VALUES, Person::class, 'SetPersonFirstName');
|
||||
$oEventReceiver->RegisterCRUDListeners(EVENT_DB_COMPUTE_VALUES);
|
||||
@@ -180,7 +178,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
*/
|
||||
public function testCheckToWriteProtectedOnInsert()
|
||||
{
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Modify the person's function
|
||||
$oEventReceiver->AddCallback(EVENT_DB_CHECK_TO_WRITE, Person::class, 'SetPersonFunction');
|
||||
$oEventReceiver->RegisterCRUDListeners(EVENT_DB_CHECK_TO_WRITE);
|
||||
@@ -201,7 +199,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
// Modify the person's function
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
$oEventReceiver->AddCallback(EVENT_DB_CHECK_TO_WRITE, Person::class, 'SetPersonFunction');
|
||||
$oEventReceiver->RegisterCRUDListeners(EVENT_DB_CHECK_TO_WRITE);
|
||||
|
||||
@@ -221,7 +219,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
*/
|
||||
public function testModificationsDuringCreateDone()
|
||||
{
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Set the person's first name during Compute Values
|
||||
$oEventReceiver->AddCallback(EVENT_DB_AFTER_WRITE, Person::class, 'SetPersonFirstName');
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
@@ -254,7 +252,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Set the person's first name during Compute Values
|
||||
$oEventReceiver->AddCallback(EVENT_DB_AFTER_WRITE, Person::class, 'SetPersonFirstName');
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
@@ -287,7 +285,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Set the person's first name during Compute Values
|
||||
$oEventReceiver->AddCallback(EVENT_DB_AFTER_WRITE, Person::class, 'SetPersonFirstName', 100);
|
||||
$oEventReceiver->RegisterCRUDListeners(EVENT_DB_AFTER_WRITE);
|
||||
@@ -329,7 +327,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'persons_list' => $oLinkSet, 'org_id' => $this->getTestOrgId()]);
|
||||
@@ -375,7 +373,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oLinkSet->AddItem($oLink);
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Create a new role and add it to the newly created lnkPersonToTeam
|
||||
$oEventReceiver->AddCallback(EVENT_DB_AFTER_WRITE, lnkPersonToTeam::class, 'AddRoleToLink');
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
@@ -411,13 +409,13 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
*/
|
||||
public function testPostponedUpdates()
|
||||
{
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Set the person's function after the creation
|
||||
$oEventReceiver->AddCallback(EVENT_DB_AFTER_WRITE, Person::class, 'SetPersonFunction');
|
||||
$oEventReceiver->RegisterCRUDListeners(EVENT_DB_AFTER_WRITE);
|
||||
|
||||
// Intentionally register twice so 2 modifications will be done
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
$oEventReceiver->AddCallback(EVENT_DB_AFTER_WRITE, Person::class, 'SetPersonFirstName');
|
||||
$oEventReceiver->RegisterCRUDListeners(EVENT_DB_AFTER_WRITE);
|
||||
|
||||
@@ -444,7 +442,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
|
||||
public function testCrudStack()
|
||||
{
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
// Modify the person's function
|
||||
$oEventReceiver->AddCallback(EVENT_DB_COMPUTE_VALUES, Person::class, 'CheckCrudStack');
|
||||
$oEventReceiver->RegisterCRUDListeners(EVENT_DB_COMPUTE_VALUES);
|
||||
@@ -490,7 +488,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oTeam->DBInsert();
|
||||
|
||||
// Start receiving events
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// Create a link between Person and Team => generate 2 EVENT_DB_LINKS_CHANGED
|
||||
@@ -514,13 +512,31 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oLnk->DBInsert();
|
||||
|
||||
// Start receiving events
|
||||
$oEventReceiver = new CRUDEventReceiver();
|
||||
$oEventReceiver = new CRUDEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
$oLnk->DBDelete();
|
||||
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
// Tests with MockDBObject
|
||||
public function testFireCRUDEvent()
|
||||
{
|
||||
$this->RequireOnceUnitTestFile('DBObject/MockDBObjectWithCRUDEventListener.php');
|
||||
|
||||
// For Metamodel list of classes
|
||||
MockDBObjectWithCRUDEventListener::Init();
|
||||
$oDBObject = new MockDBObjectWithCRUDEventListener();
|
||||
$oDBObject2 = new MockDBObjectWithCRUDEventListener();
|
||||
|
||||
$oDBObject->FireEvent(MockDBObjectWithCRUDEventListener::TEST_EVENT);
|
||||
|
||||
$this->assertNotNull($oDBObject->oEventDataReceived);
|
||||
$this->assertNull($oDBObject2->oEventDataReceived);
|
||||
|
||||
//echo($oDBObject->oEventDataReceived->Get('debug_info'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -553,10 +569,16 @@ class ClassesWithDebug
|
||||
*/
|
||||
class CRUDEventReceiver extends ClassesWithDebug
|
||||
{
|
||||
private $oTestCase;
|
||||
private $aCallbacks = [];
|
||||
|
||||
public static $bIsObjectInCrudStack;
|
||||
|
||||
public function __construct(ItopDataTestCase $oTestCase)
|
||||
{
|
||||
$this->oTestCase = $oTestCase;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/**
|
||||
@@ -613,30 +635,21 @@ class CRUDEventReceiver extends ClassesWithDebug
|
||||
{
|
||||
$this->Debug('Registering Test event listeners');
|
||||
if (is_null($sEvent)) {
|
||||
EventService::RegisterListener(EVENT_DB_COMPUTE_VALUES, [$this, 'OnEvent']);
|
||||
EventService::RegisterListener(EVENT_DB_CHECK_TO_WRITE, [$this, 'OnEvent']);
|
||||
EventService::RegisterListener(EVENT_DB_CHECK_TO_DELETE, [$this, 'OnEvent']);
|
||||
EventService::RegisterListener(EVENT_DB_BEFORE_WRITE, [$this, 'OnEvent']);
|
||||
EventService::RegisterListener(EVENT_DB_AFTER_WRITE, [$this, 'OnEvent']);
|
||||
EventService::RegisterListener(EVENT_DB_AFTER_DELETE, [$this, 'OnEvent']);
|
||||
EventService::RegisterListener(EVENT_DB_LINKS_CHANGED, [$this, 'OnEvent']);
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_COMPUTE_VALUES, [$this, 'OnEvent']);
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_CHECK_TO_WRITE, [$this, 'OnEvent']);
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_CHECK_TO_DELETE, [$this, 'OnEvent']);
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_BEFORE_WRITE, [$this, 'OnEvent']);
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_AFTER_WRITE, [$this, 'OnEvent']);
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_AFTER_DELETE, [$this, 'OnEvent']);
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_LINKS_CHANGED, [$this, 'OnEvent']);
|
||||
|
||||
return;
|
||||
}
|
||||
EventService::RegisterListener($sEvent, [$this, 'OnEvent'], $mEventSource);
|
||||
$this->oTestCase->EventService_RegisterListener($sEvent, [$this, 'OnEvent'], $mEventSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oObject
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
* @noinspection PhpUnusedPrivateMethodInspection Used as a callback
|
||||
*/
|
||||
private function AddRoleToLink($oObject): void
|
||||
{
|
||||
@@ -646,26 +659,30 @@ class CRUDEventReceiver extends ClassesWithDebug
|
||||
$oObject->Set('role_id', $oContactType->GetKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnusedPrivateMethodInspection Used as a callback
|
||||
*/
|
||||
private function SetPersonFunction($oObject): void
|
||||
{
|
||||
$this->Debug(__METHOD__);
|
||||
$oObject->Set('function', 'CRUD_function_'.rand());
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnusedPrivateMethodInspection Used as a callback
|
||||
*/
|
||||
private function SetPersonFirstName($oObject): void
|
||||
{
|
||||
$this->Debug(__METHOD__);
|
||||
$oObject->Set('first_name', 'CRUD_first_name_'.rand());
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnusedPrivateMethodInspection Used as a callback
|
||||
*/
|
||||
private function CheckCrudStack(DBObject $oObject): void
|
||||
{
|
||||
self::$bIsObjectInCrudStack = DBObject::IsObjectCurrentlyInCrud(get_class($oObject), $oObject->GetKey());
|
||||
}
|
||||
|
||||
private function CheckUpdateInLnk(lnkPersonToTeam $oLnkPersonToTeam)
|
||||
{
|
||||
$iTeamId = $oLnkPersonToTeam->Get('team_id');
|
||||
self::$bIsObjectInCrudStack = DBObject::IsObjectCurrentlyInCrud(Team::class, $iTeamId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace DBObject;
|
||||
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use MetaModel;
|
||||
|
||||
class MockDBObjectWithCRUDEventListener extends \DBObject
|
||||
{
|
||||
const TEST_EVENT = 'test_event';
|
||||
public $oEventDataReceived = null;
|
||||
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
'category' => 'bizmodel, searchable',
|
||||
'key_type' => 'autoincrement',
|
||||
'name_attcode' => '',
|
||||
'state_attcode' => '',
|
||||
'reconc_keys' => [],
|
||||
'db_table' => 'priv_unit_tests_mock',
|
||||
'db_key_field' => 'id',
|
||||
'db_finalclass_field' => '',
|
||||
'display_template' => '',
|
||||
'indexes' => [],
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
}
|
||||
|
||||
protected function RegisterEventListeners()
|
||||
{
|
||||
$this->RegisterCRUDListener(self::TEST_EVENT, 'TestEventCallback', 0, 'unit-test');
|
||||
}
|
||||
|
||||
public function TestEventCallback(EventData $oEventData)
|
||||
{
|
||||
$this->oEventDataReceived = $oEventData;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use CoreException;
|
||||
use DBObject;
|
||||
@@ -36,19 +37,20 @@ use MetaModel;
|
||||
|
||||
/**
|
||||
* @group specificOrgInSampleData
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBObjectTest extends ItopDataTestCase
|
||||
{
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
// Counts
|
||||
public $aReloadCount = [];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('core/dbobject.class.php');
|
||||
|
||||
$this->EventService_RegisterListener(EVENT_DB_OBJECT_RELOAD, [$this, 'CountObjectReload']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -897,4 +899,81 @@ class DBObjectTest extends ItopDataTestCase
|
||||
return $oPerson;
|
||||
}
|
||||
|
||||
public function ResetReloadCount()
|
||||
{
|
||||
$this->aReloadCount = [];
|
||||
}
|
||||
|
||||
public function DebugReloadCount($sMsg, $bResetCount = true)
|
||||
{
|
||||
$iTotalCount = 0;
|
||||
$aTotalPerClass = [];
|
||||
foreach ($this->aReloadCount as $sClass => $aCountByKeys) {
|
||||
$iClassCount = 0;
|
||||
foreach ($aCountByKeys as $iCount) {
|
||||
$iClassCount += $iCount;
|
||||
}
|
||||
$iTotalCount += $iClassCount;
|
||||
$aTotalPerClass[$sClass] = $iClassCount;
|
||||
}
|
||||
$this->debug("$sMsg - $iTotalCount reload(s)");
|
||||
foreach ($this->aReloadCount as $sClass => $aCountByKeys) {
|
||||
$this->debug(" $sClass => $aTotalPerClass[$sClass] reload(s)");
|
||||
foreach ($aCountByKeys as $sKey => $iCount) {
|
||||
$this->debug(" $sClass::$sKey => $iCount");
|
||||
}
|
||||
}
|
||||
if ($bResetCount) {
|
||||
$this->ResetReloadCount();
|
||||
}
|
||||
}
|
||||
|
||||
public function CountObjectReload(EventData $oData)
|
||||
{
|
||||
$oObject = $oData->Get('object');
|
||||
$sClass = get_class($oObject);
|
||||
$sKey = $oObject->GetKey();
|
||||
$iCount = $this->GetObjectReloadCount($sClass, $sKey);
|
||||
$this->aReloadCount[$sClass][$sKey] = 1 + $iCount;
|
||||
}
|
||||
|
||||
public function GetObjectReloadCount($sClass, $sKey)
|
||||
{
|
||||
return $this->aReloadCount[$sClass][$sKey] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0-3 3.1.1 3.2.0 N°6716 test creation
|
||||
*/
|
||||
public function testConstructorMemoryFootprint():void
|
||||
{
|
||||
$idx = 1;
|
||||
$fStart = microtime(true);
|
||||
$fStartLoop = $fStart;
|
||||
$iInitialPeak = 0;
|
||||
|
||||
for ($i = 0; $i < 5000; $i++) {
|
||||
/** @noinspection PhpUnusedLocalVariableInspection We intentionally use a reference that will disappear on each loop */
|
||||
$oPerson = MetaModel::NewObject(\Person::class);
|
||||
if (0 == ($idx % 100)) {
|
||||
$fDuration = microtime(true) - $fStartLoop;
|
||||
$iMemoryPeakUsage = memory_get_peak_usage();
|
||||
if ($iInitialPeak === 0) {
|
||||
$iInitialPeak = $iMemoryPeakUsage;
|
||||
$sInitialPeak = \utils::BytesToFriendlyFormat($iInitialPeak, 4);
|
||||
}
|
||||
|
||||
$sCurrPeak = \utils::BytesToFriendlyFormat($iMemoryPeakUsage, 4);
|
||||
echo "$idx ".sprintf('%.1f ms', $fDuration * 1000)." - Peak Memory Usage: $sCurrPeak\n";
|
||||
|
||||
$this->assertTrue($iMemoryPeakUsage === $iInitialPeak, "Peak memory changed from $sInitialPeak to $sCurrPeak after $i loops");
|
||||
|
||||
$fStartLoop = microtime(true);
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
echo 'Total duration: '.sprintf('%.3f s', $fTotalDuration)."\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,6 @@ use DBSearch;
|
||||
* <ul>
|
||||
* <li>MakeGroupByQuery</li>
|
||||
* </ul>
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBSearchCommitTest extends ItopDataTestCase
|
||||
{
|
||||
|
||||
@@ -11,10 +11,6 @@ use DBSearch;
|
||||
* Class DBSearchIntersectTest
|
||||
*
|
||||
* @package Combodo\iTop\Test\UnitTest\Core
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBSearchIntersectTest extends ItopTestCase
|
||||
{
|
||||
@@ -71,16 +67,16 @@ class DBSearchIntersectTest extends ItopTestCase
|
||||
'result' => "SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE (`P`.`org_id` = 3)");
|
||||
|
||||
$aTests['Multiple selected classes inverted 1'] = array(
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE 1",
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN Server AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE 1",
|
||||
'right' => "SELECT Location WHERE org_id = 3",
|
||||
'alias' => "L",
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE (`L`.`org_id` = 3)");
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN Server AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE (`L`.`org_id` = 3)");
|
||||
|
||||
$aTests['Multiple selected classes inverted 2'] = array(
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE (`L`.`org_id` = 3)",
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN Server AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE (`L`.`org_id` = 3)",
|
||||
'right' => "SELECT Person WHERE org_id = 3",
|
||||
'alias' => "P",
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE ((`L`.`org_id` = 3) AND (`P`.`org_id` = 3))");
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN Server AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE ((`L`.`org_id` = 3) AND (`P`.`org_id` = 3))");
|
||||
|
||||
$aTests['Same class'] = array(
|
||||
'left' => "SELECT Contact WHERE name = 'Christie'",
|
||||
@@ -191,9 +187,9 @@ class DBSearchIntersectTest extends ItopTestCase
|
||||
'result' => "SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE (`P`.`org_id` = 3)");
|
||||
|
||||
$aTests['Multiple selected classes inverted 2'] = array(
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE (`L`.`org_id` = 3)",
|
||||
'left' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN Server AS D ON D.location_id = L.id JOIN Person AS P2 ON P.manager_id = P2.id WHERE (`L`.`org_id` = 3)",
|
||||
'right' => "SELECT Person WHERE org_id = 3",
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN PC AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE ((`L`.`org_id` = 3) AND (`P`.`org_id` = 3))");
|
||||
'result' => "SELECT `L`, `P`, `D` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id JOIN Server AS `D` ON `D`.location_id = `L`.id JOIN Person AS `P2` ON `P`.manager_id = `P2`.id WHERE ((`L`.`org_id` = 3) AND (`P`.`org_id` = 3))");
|
||||
|
||||
$aTests['Same class'] = array(
|
||||
'left' => "SELECT Contact WHERE name = 'Christie'",
|
||||
|
||||
@@ -11,10 +11,6 @@ use DBSearch;
|
||||
* Class DBSearchIntersectTest
|
||||
*
|
||||
* @package Combodo\iTop\Test\UnitTest\Core
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBSearchJoinTest extends ItopDataTestCase {
|
||||
|
||||
|
||||
@@ -43,10 +43,6 @@ use FunctionExpression;
|
||||
* <ul>
|
||||
* <li>MakeGroupByQuery</li>
|
||||
* </ul>
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBSearchTest extends ItopDataTestCase
|
||||
{
|
||||
|
||||
@@ -10,10 +10,6 @@ use DBObjectSearch;
|
||||
* Class DBSearchUpdateRealiasingMapTest
|
||||
*
|
||||
* @package Combodo\iTop\Test\UnitTest\Core
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBSearchUpdateRealiasingMapTest extends ItopDataTestCase
|
||||
{
|
||||
|
||||
@@ -7,11 +7,6 @@
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBUnionSearchTest extends ItopDataTestCase
|
||||
{
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\iTopDataTestCase;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Expression;
|
||||
@@ -13,12 +13,7 @@ use FunctionExpression;
|
||||
use MetaModel;
|
||||
use ScalarExpression;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
class ExpressionEvaluateTest extends ItopDataTestCase
|
||||
{
|
||||
const USE_TRANSACTION = false;
|
||||
|
||||
@@ -38,7 +33,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
$aParameters = $oExpression->GetParameters($sParentFilter);
|
||||
sort($aExpectedParameters);
|
||||
sort($aParameters);
|
||||
static::assertEquals($aExpectedParameters, $aParameters);
|
||||
$this->assertEquals($aExpectedParameters, $aParameters);
|
||||
}
|
||||
|
||||
public function GetParametersProvider()
|
||||
@@ -86,7 +81,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
{
|
||||
$oExpression = Expression::FromOQL($sExpression);
|
||||
$value = $oExpression->Evaluate(array());
|
||||
static::assertEquals($expectedValue, $value);
|
||||
$this->assertEquals($expectedValue, $value);
|
||||
}
|
||||
|
||||
public function VariousExpressionsProvider()
|
||||
@@ -225,7 +220,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
$sNewExpression = "return $sExpression;";
|
||||
$oExpression = eval($sNewExpression);
|
||||
$res = $oExpression->Evaluate(array());
|
||||
static::assertEquals($expectedValue, $res);
|
||||
$this->assertEquals($expectedValue, $res);
|
||||
}
|
||||
|
||||
public function NotYetParsableExpressionsProvider()
|
||||
@@ -287,7 +282,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
$value = $aResults[0]["test_$i"];
|
||||
$expectedValue = $aTest[1];
|
||||
$this->debug("Test #$i: {$aTests[$i][0]} => ".var_export($value, true));
|
||||
static::assertEquals($expectedValue, $value);
|
||||
$this->assertEquals($expectedValue, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +305,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
|
||||
$res = $oObject->EvaluateExpression($oExpression);
|
||||
|
||||
static::assertEquals($expected, $res);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
public function ExpressionsWithObjectFieldsProvider()
|
||||
@@ -340,12 +335,18 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
{
|
||||
$oExpression = Expression::FromOQL($sExpression);
|
||||
$res = $oExpression->Evaluate($aParameters);
|
||||
static::assertEquals($expected, $res);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
public function ExpressionWithParametersProvider()
|
||||
{
|
||||
return array(
|
||||
['`DBVariables["analyze_sample_percentage"]` > 10', ['DBVariables["analyze_sample_percentage"]' => 20], true],
|
||||
['`DataBase["DBDataSize"]`', ['DataBase["DBDataSize"]' => 4096], 4096],
|
||||
['`FileSystem["ItopInstallationIntegrity"]`', ['FileSystem["ItopInstallationIntegrity"]' => 'not_conform'], 'not_conform'],
|
||||
['`DBTablesInfo["attachment"].DataSize` > 100', ['DBTablesInfo["attachment"].DataSize' => 200], true],
|
||||
['`DBTablesInfo[].DataSize` > 100', ['DBTablesInfo[].DataSize' => 50], false],
|
||||
['(`DBTablesInfo[].DataSize` > 100) AND (`DBTablesInfo[].DataFree` * 100 / (`DBTablesInfo[].DataSize` + `DBTablesInfo[].IndexSize` + `DBTablesInfo[].DataFree`) > 10)', ['DBTablesInfo[].DataSize' => 200, 'DBTablesInfo[].DataFree' => 100, 'DBTablesInfo[].IndexSize' => 10], true],
|
||||
array('CONCAT(SUBSTR(name, 4), " cause")', array('name' => 'noble'), 'le cause'),
|
||||
);
|
||||
}
|
||||
@@ -369,11 +370,11 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
$res = $oExpression->IsTrue();
|
||||
if ($bExpectTrue)
|
||||
{
|
||||
static::assertTrue($res, 'arg: '.$sExpression);
|
||||
$this->assertTrue($res, 'arg: '.$sExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
static::assertFalse($res, 'arg: '.$sExpression);
|
||||
$this->assertFalse($res, 'arg: '.$sExpression);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,10 +415,10 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
if ($bProcessed)
|
||||
{
|
||||
$sqlValue = CMDBSource::QueryToScalar("SELECT DATE_FORMAT('$sDate', '%$sFormat')");
|
||||
static::assertEquals($sqlValue, $sValueOrException, 'Check test against MySQL');
|
||||
$this->assertEquals($sqlValue, $sValueOrException, 'Check test against MySQL');
|
||||
|
||||
$res = $oExpression->Evaluate(array());
|
||||
static::assertEquals($sValueOrException, $res, 'Check evaluation');
|
||||
$this->assertEquals($sValueOrException, $res, 'Check evaluation');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -510,7 +511,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
{
|
||||
$oExpression = new FunctionExpression('DATE_FORMAT', array(new ScalarExpression($sDate), new ScalarExpression("%$sFormat")));
|
||||
$itopExpressionResult = $oExpression->Evaluate(array());
|
||||
static::assertSame($aMysqlDateFormatRsultsForAllFormats[$sFormat], $itopExpressionResult, "Format %$sFormat not matching MySQL for '$sDate'");
|
||||
$this->assertSame($aMysqlDateFormatRsultsForAllFormats[$sFormat], $itopExpressionResult, "Format %$sFormat not matching MySQL for '$sDate'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -546,7 +547,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
$oDate = new DateTime($sStartDate);
|
||||
for ($i = 0 ; $i < $iRepeat ; $i++)
|
||||
{
|
||||
$sDate = date_format($oDate, 'Y-m-d, H:i:s');
|
||||
$sDate = date_format($oDate, 'Y-m-d H:i:s');
|
||||
$this->debug("Checking '$sDate'");
|
||||
$this->testEveryTimeFormat($sDate);
|
||||
$oDate->add(new DateInterval($sInterval));
|
||||
|
||||
@@ -5,11 +5,6 @@ namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Expression;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class ExpressionTest extends ItopDataTestCase
|
||||
{
|
||||
const USE_TRANSACTION = false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user