diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php
index 0213896a1..2b3169672 100644
--- a/application/cmdbabstract.class.inc.php
+++ b/application/cmdbabstract.class.inc.php
@@ -3439,8 +3439,18 @@ EOF
}
$sInputType = '';
$sInputId = 'att_'.$iFieldIndex;
+ $value = $this->Get($sAttCode);
+ $sDisplayValue = $this->GetEditValue($sAttCode);
+ if ($oAttDef instanceof AttributeDateTime && !$oAttDef->IsNullAllowed() && $value === $oAttDef->GetNullValue()) {
+ $value = $oAttDef->GetDefaultValue($this);
+ if ($value !== $oAttDef->GetNullValue()) {
+ // Set default date
+ $this->Set($sAttCode, $value);
+ $sDisplayValue = $this->GetEditValue($sAttCode);
+ }
+ }
$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef,
- $this->Get($sAttCode), $this->GetEditValue($sAttCode), $sInputId, '', $iExpectCode,
+ $value, $sDisplayValue, $sInputId, '', $iExpectCode,
$aArgs, true, $sInputType);
$aAttrib = array(
'label' => ''.$oAttDef->GetLabel().'',
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 048363d5c..c5d86d2a5 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -6372,7 +6372,11 @@ class AttributeDateTime extends AttributeDBField
$oFormField = parent::MakeFormField($oObject, $oFormField);
// After call to the parent as it sets the current value
- $oFormField->SetCurrentValue($this->GetFormat()->Format($oObject->Get($this->GetCode())));
+ $oValue = $oObject->Get($this->GetCode());
+ if ($oValue === $this->GetNullValue()) {
+ $oValue = $this->GetDefaultValue($oObject);
+ }
+ $oFormField->SetCurrentValue($this->GetFormat()->Format($oValue));
return $oFormField;
}
@@ -6458,8 +6462,20 @@ class AttributeDateTime extends AttributeDBField
public function GetDefaultValue(DBObject $oHostObject = null)
{
- if (!$this->IsNullAllowed()) {
- return date($this->GetInternalFormat());
+ $sDefaultValue = $this->Get('default_value');
+ if (!$this->IsNullAllowed() && utils::IsNotNullOrEmptyString($sDefaultValue)) {
+ try {
+ $oDate = new DateTimeImmutable($sDefaultValue);
+ }
+ catch (Exception $e) {
+ IssueLog::Error($e->getMessage(), null, [
+ 'class' => get_class($this),
+ 'name' => $this->GetCode(),
+ 'stack' => $e->getTraceAsString()]);
+ return $this->GetNullValue();
+ }
+
+ return $oDate->format($this->GetInternalFormat());
}
return $this->GetNullValue();
}
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index bf51353eb..48b3364ed 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -2867,6 +2867,14 @@ abstract class DBObject implements iDisplay
protected function ListChangedValues(array $aProposal)
{
$aDelta = array();
+ $sClass = get_class($this);
+ if (MetaModel::HasLifecycle($sClass) && utils::IsNotNullOrEmptyString($this->sStimulusBeingApplied)) {
+ $sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
+ if (!in_array($sStateAttCode, $aProposal)) {
+ // Same state but the transition was asked, act as if the state was changed
+ $aDelta[$sStateAttCode] = $this->m_aCurrValues[$sStateAttCode];
+ }
+ }
foreach ($aProposal as $sAtt => $proposedValue)
{
if (!array_key_exists($sAtt, $this->m_aOrigValues))
diff --git a/core/event.class.inc.php b/core/event.class.inc.php
index 41c626a7e..7cf5c4d44 100644
--- a/core/event.class.inc.php
+++ b/core/event.class.inc.php
@@ -39,7 +39,7 @@ class Event extends DBObject implements iDisplay
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
- MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+ MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"now", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php
index 88cc64b2e..ea797a2b5 100644
--- a/core/oql/expression.class.inc.php
+++ b/core/oql/expression.class.inc.php
@@ -575,6 +575,9 @@ class BinaryExpression extends Expression
case 'LIKE':
$sType = 'like';
break;
+ case 'IN':
+ $sType = 'in';
+ break;
default:
throw new Exception("Operator '$sOperator' not yet supported");
}
@@ -641,6 +644,9 @@ class BinaryExpression extends Expression
$sEscaped = str_replace(array('%', '_', '\\\\.*', '\\\\.'), array('.*', '.', '%', '_'), $sEscaped);
$result = (int) preg_match("/$sEscaped/i", $mLeft);
break;
+ case 'in':
+ $result = in_array($mLeft, $mRight);
+ break;
}
return $result;
}
@@ -2250,7 +2256,12 @@ class ListExpression extends Expression
*/
public function Evaluate(array $aArgs)
{
- throw new Exception('list expression not yet supported');
+ //throw new Exception('list expression not yet supported');
+ $aResult = [];
+ foreach ($this->m_aExpressions as $oExpressions) {
+ $aResult[] = $oExpressions->Evaluate($aArgs);
+ }
+ return $aResult;
}
/**
diff --git a/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig
index 9d018912f..2f01faf12 100644
--- a/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig
@@ -23,25 +23,23 @@
{% set iTableCount = 0 %}
{% if aGroupingAreasData|length > 0 %}
{% for aAreaData in aGroupingAreasData %}
- {% if aAreaData.iItemsCount > 0 %}
- {% set iTableCount = iTableCount + 1 %}
-
-
-
{{ aAreaData.sTitle }}
- {% if bCanExport %}
-
-
-
- {% endif %}
-
-
+ {% set iTableCount = iTableCount + 1 %}
+
+
+
{{ aAreaData.sTitle }}
+ {% if bCanExport %}
+
+
+
+ {% endif %}
- {% endif %}
+
+
{% endfor %}
{% endif %}
diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
index c48f5d695..8da300791 100644
--- a/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
+++ b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
@@ -440,6 +440,14 @@ abstract class ItopDataTestCase extends ItopTestCase
$this->RemoveObjects($sTagClass, "SELECT $sTagClass WHERE code = '$sTagCode'");
}
+ public function RemoveAllObjects($sClassName)
+ {
+ $oSet = new \DBObjectSet(new \DBObjectSearch($sClassName));
+ while ($oObject = $oSet->Fetch()) {
+ $oObject->DBDelete();
+ }
+ }
+
private function RemoveObjects($sClass, $sOQL)
{
$oFilter = DBSearch::FromOQL($sOQL);
@@ -930,11 +938,12 @@ abstract class ItopDataTestCase extends ItopTestCase
*
* @param $iExpectedCount Number of MySQL queries that should be executed
* @param callable $oFunction Operations to perform
+ * @param string $sMessage Message to display in case of failure
*
* @throws \MySQLException
* @throws \MySQLQueryHasNoResultException
*/
- protected function assertDBQueryCount($iExpectedCount, callable $oFunction)
+ protected function assertDBQueryCount($iExpectedCount, callable $oFunction, $sMessage = '')
{
$iInitialCount = (int) CMDBSource::QueryToScalar("SHOW SESSION STATUS LIKE 'Queries'", 1);
$oFunction();
@@ -942,7 +951,13 @@ abstract class ItopDataTestCase extends ItopTestCase
$iCount = $iFinalCount - 1 - $iInitialCount;
if ($iCount != $iExpectedCount)
{
- $this->fail("Expected $iExpectedCount queries. $iCount have been executed.");
+ if ($sMessage === '') {
+ $sMessage = "Expected $iExpectedCount queries. $iCount have been executed.";
+ }
+ else {
+ $sMessage .= " - Expected $iExpectedCount queries. $iCount have been executed.";
+ }
+ $this->fail($sMessage);
}
else
{
@@ -965,6 +980,18 @@ abstract class ItopDataTestCase extends ItopTestCase
$this->assertEquals($iExpectedCount, $iCount, "Found $iCount changes for object $sClass::$iId");
}
+ /**
+ * @since 3.2.1
+ */
+ protected static function assertIsDBObject(string $sExpectedClass, ?int $iExpectedKey, $oObject, ?string $sMessage = '')
+ {
+ self::assertNotNull($oObject, $sMessage);
+ self::assertInstanceOf($sExpectedClass, $oObject, $sMessage);
+ if ($iExpectedKey !== null) {
+ self::assertEquals($iExpectedKey, $oObject->GetKey(), $sMessage);
+ }
+ }
+
/**
* Import a set of XML files describing a consistent set of iTop objects
* @param string[] $aFiles
@@ -1464,4 +1491,28 @@ abstract class ItopDataTestCase extends ItopTestCase
$oSet = new DBObjectSet($oSearch);
$this->assertEquals(1, $oSet->Count(), $sMessage);
}
+
+ static protected function StartStopwatchInThePast(DBObject $oObject, string $sStopwatchAttCode, int $iDelayInSecond)
+ {
+ $iStartDate = time() - $iDelayInSecond;
+ /** @var \ormStopWatch $oStopwatch */
+ $oStopwatch = $oObject->Get($sStopwatchAttCode);
+ $oAttDef = MetaModel::GetAttributeDef(get_class($oObject), $sStopwatchAttCode);
+ $oStopwatch->Start($oObject, $oAttDef, $iStartDate);
+ $oStopwatch->ComputeDeadlines($oObject, $oAttDef);
+ $oObject->Set($sStopwatchAttCode, $oStopwatch);
+ }
+
+
+ static protected function StopStopwatchInTheFuture(DBObject $oObject, string $sStopwatchAttCode, int $iDelayInSecond)
+ {
+ $iEndDate = time() + $iDelayInSecond;
+ /** @var \ormStopWatch $oStopwatch */
+ $oStopwatch = $oObject->Get($sStopwatchAttCode);
+ $oAttDef = MetaModel::GetAttributeDef(get_class($oObject), $sStopwatchAttCode);
+ $oStopwatch->Stop($oObject, $oAttDef, $iEndDate);
+ $oStopwatch->ComputeDeadlines($oObject, $oAttDef);
+ $oObject->Set($sStopwatchAttCode, $oStopwatch);
+ }
+
}
diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
index 463d1c44b..52cbe3436 100644
--- a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
+++ b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
@@ -9,9 +9,9 @@ namespace Combodo\iTop\Test\UnitTest;
use CMDBSource;
use DeprecatedCallsLog;
use MySQLTransactionNotClosedException;
+use PHPUnit\Framework\TestCase;
use ReflectionMethod;
use SetupUtils;
-use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpKernel\KernelInterface;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
@@ -23,7 +23,7 @@ use const DEBUG_BACKTRACE_IGNORE_ARGS;
*
* @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 KernelTestCase
+abstract class ItopTestCase extends TestCase
{
public const TEST_LOG_DIR = 'test';
diff --git a/tests/php-unit-tests/unitary-tests/application/MenuNodeTest.php b/tests/php-unit-tests/unitary-tests/application/MenuNodeTest.php
index 4c9961ece..053fef232 100644
--- a/tests/php-unit-tests/unitary-tests/application/MenuNodeTest.php
+++ b/tests/php-unit-tests/unitary-tests/application/MenuNodeTest.php
@@ -20,16 +20,6 @@ class MenuNodeTest extends ItopDataTestCase {
$this->oUR = $this->CreateUserRequest(666, $aUserRequestCustomParams);
}
- private function StartStopwatchInThePast(\UserRequest $oTicket, string $sAttCode, int $iSecDelay)
- {
- $iStartDate = time() - $iSecDelay;
- /** @var \ormStopWatch $oStopwatch */
- $oStopwatch = $oTicket->Get($sAttCode);
- $oAttDef = \MetaModel::GetAttributeDef(get_class($oTicket), $sAttCode);
- $oStopwatch->Start($oTicket, $oAttDef, $iStartDate);
- $oStopwatch->ComputeDeadlines($oTicket, $oAttDef);
- $oTicket->Set($sAttCode, $oStopwatch);
- }
public function RenderOQLSearchProvider()
{
@@ -70,7 +60,7 @@ OQL;
*/
public function testRenderOQLSearchOqlWithDateFormatOnDeadline()
{
- $this->StartStopwatchInThePast($this->oUR, 'ttr', 10);
+ static::StartStopwatchInThePast($this->oUR, 'ttr', 10);
$sOql = <<
assertEquals($bComputationExpected, $oAttDef->HasPHPComputation(), "Standard DataModel should be configured with property 'has_php_computation'=$sComputationExpected for $sClass:$sAttCode");
}
- public function WithConstraintParameterProvider()
+ public static function WithConstraintParameterProvider()
{
return [
['User', 'profile_list', true, true],
@@ -238,4 +240,109 @@ PHP
['Ticket', 'functionalcis_list', false, true],
];
}
+
+ public function testDateTimeEmptyDefaultReturnsNullAsDefaultValue()
+ {
+ // Given
+ $oDateAttribute = new AttributeDateTime('start_date', ['sql' => 'start_date', 'is_null_allowed' => false, 'default_value' => '', 'allowed_values' => null, 'depends_on' => [], 'always_load_in_tables' => false]);
+ $oDateAttribute->SetHostClass('WorkOrder');
+ $oWorkOrder = MetaModel::NewObject('WorkOrder');
+
+ //When
+ $defaultValue = $oDateAttribute->GetDefaultValue();
+ $oField = $oDateAttribute->MakeFormField($oWorkOrder);
+
+ // Then
+ self::assertNull($defaultValue, 'Empty default value for DateTime attribute should give null default value');
+ self::assertEmpty($oField->GetCurrentValue(), 'Empty default value for DateTime attribute should give empty form field');
+ }
+
+ public function testDateEmptyDefaultReturnsNullAsDefaultValue()
+ {
+ // Given
+ $oDateAttribute = new AttributeDate('start_date', ['sql' => 'start_date', 'is_null_allowed' => false, 'default_value' => '', 'allowed_values' => null, 'depends_on' => [], 'always_load_in_tables' => false]);
+ $oDateAttribute->SetHostClass('WorkOrder');
+ $oWorkOrder = MetaModel::NewObject('WorkOrder');
+
+ //When
+ $defaultValue = $oDateAttribute->GetDefaultValue();
+ $oField = $oDateAttribute->MakeFormField($oWorkOrder);
+
+ // Then
+ self::assertNull($defaultValue, 'Empty default value for Date attribute should give null default value');
+ self::assertEmpty($oField->GetCurrentValue(), 'Empty default value for DateTime attribute should give empty form field');
+ }
+
+
+ public function testDateTimeNowAsDefaultGivesCurrentDateAsDefaultValue()
+ {
+ // Given
+ $oDateAttribute = new AttributeDateTime('start_date', ['sql' => 'start_date', 'is_null_allowed' => false, 'default_value' => 'now', 'allowed_values' => null, 'depends_on' => [], 'always_load_in_tables' => false]);
+ $oDateAttribute->SetHostClass('WorkOrder');
+ $oWorkOrder = MetaModel::NewObject('WorkOrder');
+
+ //When
+ $defaultValue = $oDateAttribute->GetDefaultValue();
+ $oField = $oDateAttribute->MakeFormField($oWorkOrder);
+
+ // Then
+ $sNow = date($oDateAttribute->GetInternalFormat());
+ self::assertEquals($sNow, $defaultValue, 'Now as default value for DateTime attribute should give current date as default value');
+ self::assertEquals($sNow, $oField->GetCurrentValue(), 'Now as default value for DateTime attribute should give current date as form field');
+ }
+
+
+ public function testDateNowAsDefaultGivesCurrentDateAsDefaultValue()
+ {
+ // Given
+ $oDateAttribute = new AttributeDate('start_date', ['sql' => 'start_date', 'is_null_allowed' => false, 'default_value' => 'now', 'allowed_values' => null, 'depends_on' => [], 'always_load_in_tables' => false]);
+ $oDateAttribute->SetHostClass('WorkOrder');
+ $oWorkOrder = MetaModel::NewObject('WorkOrder');
+
+ //When
+ $defaultValue = $oDateAttribute->GetDefaultValue();
+ $oField = $oDateAttribute->MakeFormField($oWorkOrder);
+
+ // Then
+ $sNow = date($oDateAttribute->GetInternalFormat());
+ self::assertEquals($sNow, $defaultValue, 'Now as default value for Date attribute should give current date as default value');
+ self::assertEquals($sNow, $oField->GetCurrentValue(), 'Now as default value for Date attribute should give current date as form field');
+ }
+
+ public function testDateTimeIntervalAsDefaultGivesCorrectDateAsDefaultValue()
+ {
+ // Given
+ $oDateAttribute = new AttributeDateTime('start_date', ['sql' => 'start_date', 'is_null_allowed' => false, 'default_value' => '+1day', 'allowed_values' => null, 'depends_on' => [], 'always_load_in_tables' => false]);
+ $oDateAttribute->SetHostClass('WorkOrder');
+ $oWorkOrder = MetaModel::NewObject('WorkOrder');
+
+ //When
+ $defaultValue = $oDateAttribute->GetDefaultValue();
+ $oField = $oDateAttribute->MakeFormField($oWorkOrder);
+
+ // Then
+ $oDate = new \DateTimeImmutable('+1day');
+ $sExpected = $oDate->format($oDateAttribute->GetInternalFormat());
+ self::assertEquals($sExpected, $defaultValue, 'Interval as default value for DateTime attribute should give correct date as default value');
+ self::assertEquals($sExpected, $oField->GetCurrentValue(), 'Interval as default value for DateTime attribute should give correct date as form field');
+ }
+
+ public function testDateIntervalAsDefaultGivesCorrectDateAsDefaultValue()
+ {
+ // Given
+ $oDateAttribute = new AttributeDate('start_date', ['sql' => 'start_date', 'is_null_allowed' => false, 'default_value' => '+1day', 'allowed_values' => null, 'depends_on' => [], 'always_load_in_tables' => false]);
+ $oDateAttribute->SetHostClass('WorkOrder');
+ $oWorkOrder = MetaModel::NewObject('WorkOrder');
+
+ //When
+ $defaultValue = $oDateAttribute->GetDefaultValue();
+ $oField = $oDateAttribute->MakeFormField($oWorkOrder);
+
+ // Then
+ $oDate = new \DateTimeImmutable('+1day');
+ $sExpected = $oDate->format($oDateAttribute->GetInternalFormat());
+ self::assertEquals($sExpected, $defaultValue, 'Interval as default value for Date attribute should give correct date as default value');
+ self::assertEquals($sExpected, $oField->GetCurrentValue(), 'Interval as default value for Date attribute should give correct date as form field');
+ }
+
}
\ No newline at end of file
diff --git a/tests/php-unit-tests/unitary-tests/core/EventIssueTest.php b/tests/php-unit-tests/unitary-tests/core/EventIssueTest.php
index 62faa35a4..400a37180 100644
--- a/tests/php-unit-tests/unitary-tests/core/EventIssueTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/EventIssueTest.php
@@ -62,7 +62,7 @@ class EventIssueTest extends ItopDataTestCase
$oEventIssue->DBInsert();
}
catch (CoreException $e) {
- $this->fail('we should be able to persist the object though it contains long values in its attributes');
+ $this->fail('we should be able to persist the object though it contains long values in its attributes: '.$e->getMessage());
}
$this->assertTrue(true);
}
diff --git a/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php b/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php
index 1d6cb5307..6b9847fe0 100644
--- a/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php
@@ -90,7 +90,7 @@ class ExpressionEvaluateTest extends ItopDataTestCase
{
$aExpressions = array(
// Test case to isolate for troubleshooting purposes
- array('1+1', 2),
+ array("'a' IN ('a', 'b')", true),
);
}
else
@@ -141,6 +141,9 @@ class ExpressionEvaluateTest extends ItopDataTestCase
array('"2020-06-12 17:35:13" < "2020-06-12"', 0),
array('"2020-06-12 00:00:00" = "2020-06-12"', 0),
+ // IN operator
+ array("'a' IN ('a', 'b')", true),
+
// Logical operators
array('0 AND 0', 0),
array('1 AND 0', 0),
diff --git a/tests/php-unit-tests/unitary-tests/core/TriggerOnStateEnterTest.php b/tests/php-unit-tests/unitary-tests/core/TriggerOnStateEnterTest.php
new file mode 100644
index 000000000..f7e878354
--- /dev/null
+++ b/tests/php-unit-tests/unitary-tests/core/TriggerOnStateEnterTest.php
@@ -0,0 +1,89 @@
+RemoveAllObjects(\Trigger::class);
+ $this->RemoveAllObjects(\EventNotificationEmail::class);
+ }
+
+ public function testIsTriggeredOnTransition()
+ {
+ $iTrigger = $this->GivenTriggerWithAction('TriggerOnStateEnter', 'assigned');
+ $oUserRequest = $this->GivenUserRequest('new');
+
+ $oUserRequest->ApplyStimulus('ev_assign');
+ $this->AssertTriggerExecuted($iTrigger, 1, 'The trigger should have been executed');
+
+ $oUserRequest->ApplyStimulus('ev_assign');
+ $this->AssertTriggerExecuted($iTrigger, 1, 'The trigger should not be executed when stimulus not expected in current state');
+ }
+
+ public function testIsTriggeredOnTransitionStayingInSameState()
+ {
+ $iTrigger = $this->GivenTriggerWithAction('TriggerOnStateEnter', 'assigned');
+ $oUserRequest = $this->GivenUserRequest('new');
+ $oUserRequest->ApplyStimulus('ev_assign');
+
+ $bTransitioned = $oUserRequest->ApplyStimulus('ev_reassign');
+ $this->assertTrue($bTransitioned, 'The stimulus should have been accepted');
+
+ $this->AssertTriggerExecuted($iTrigger, 2, 'The trigger should have been executed twice');
+ }
+ public function testIsTriggeredOnNewObject()
+ {
+ $iTrigger = $this->GivenTriggerWithAction('TriggerOnStateEnter', 'new');
+ $oUserRequest = $this->GivenUserRequest('new');
+ $this->AssertTriggerExecuted($iTrigger, 0, 'The trigger TriggerOnStateEnter should not be executed on created object');
+ }
+
+ private function GivenTriggerWithAction(string $sTriggerClass, string $sState)
+ {
+ $iTrigger = $this->GivenObjectInDB($sTriggerClass, [
+ 'description' => 'Description',
+ 'target_class' => 'UserRequest',
+ 'state' => $sState,
+ ]);
+ $this->GivenObjectInDB('ActionEmail', [
+ 'from' => 'test@combodo.com',
+ 'subject' => 'Subject',
+ 'body' => 'Body',
+ 'description' => 'Description',
+ 'test_recipient' => 'test@combodo.com',
+ 'name' => 'UserRequest',
+ 'asynchronous' => 'yes',
+ 'trigger_list' => [
+ "trigger_id:$iTrigger",
+ ],
+ ]);
+ return $iTrigger;
+ }
+
+ private function AssertTriggerExecuted(int $iTrigger, $iCount, $sMessage = '')
+ {
+ $oSearch = new \DBObjectSearch('EventNotificationEmail');
+ $oSearch->AddCondition('trigger_id', $iTrigger);
+ $oSet = new \DBObjectSet($oSearch);
+ $this->assertEquals($iCount, $oSet->Count(), $sMessage);
+ }
+
+ public function GivenUserRequest(string $sStatus): ?\DBObject
+ {
+ $iUserRequest = $this->GivenObjectInDB('UserRequest', [
+ 'title' => 'Title',
+ 'description' => 'Description',
+ 'status' => $sStatus,
+ ]);
+ return MetaModel::GetObject('UserRequest', $iUserRequest);
+ }
+}
\ No newline at end of file
diff --git a/tests/php-unit-tests/unitary-tests/core/UserRightsTest.php b/tests/php-unit-tests/unitary-tests/core/UserRightsTest.php
index 6a3d3d7a2..5a743a0e8 100644
--- a/tests/php-unit-tests/unitary-tests/core/UserRightsTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/UserRightsTest.php
@@ -491,52 +491,65 @@ class UserRightsTest extends ItopDataTestCase
public function testFindUser_ExistingInternalUser()
{
- $sLogin = 'UserRightsFindUser'.uniqid();
- $iKey = $this->CreateUser($sLogin, self::$aURP_Profiles['Administrator'])->GetKey();
- $oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
+ $sLogin = 'AnInternalUser'.uniqid();
+ $iKey = $this->GivenObjectInDB(\UserLocal::class, ['login' => $sLogin]);
- $this->assertNotNull($oUser);
- $this->assertEquals($iKey, $oUser->GetKey());
- $this->assertEquals(\UserLocal::class, get_class($oUser));
+ $this->assertDBQueryCount(
+ 1,
+ fn() => $this->FindUserAndAssertItHasBeenFound($sLogin, $iKey),
+ 'A query should be performed the first time FindUser is called'
+ );
- $this->assertDBQueryCount(0, function() use ($sLogin, $iKey){
- $oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
- static::assertEquals($iKey, $oUser->GetKey());
- static::assertEquals(\UserLocal::class, get_class($oUser));
- });
+ $this->assertDBQueryCount(
+ 0,
+ fn() => $this->FindUserAndAssertItHasBeenFound($sLogin, $iKey),
+ 'The cache should prevent additional queries on subsequent calls'
+ );
}
public function testFindUser_ExistingExternalUser()
{
- $sLogin = 'UserRightsFindUser'.uniqid();
+ $sLogin = 'AnExternalUser'.uniqid();
+ $iKey = $this->GivenObjectInDB(\UserExternal::class, ['login' => $sLogin]);
- $iKey = $this->GivenObjectInDB(\UserExternal::class, [
- 'login' => $sLogin,
- 'language' => 'EN US',
- ]);
+ $this->assertDBQueryCount(
+ 2,
+ fn() => $this->FindUserAndAssertItHasBeenFound($sLogin, $iKey),
+ 'Some queries should be performed the first time FindUser is called'
+ );
- $oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
-
- $this->assertNotNull($oUser);
- $this->assertEquals($iKey, $oUser->GetKey());
- $this->assertEquals(\UserExternal::class, get_class($oUser));
-
- $this->assertDBQueryCount(0, function() use ($sLogin, $iKey){
- $oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
- static::assertEquals($iKey, $oUser->GetKey());
- static::assertEquals(\UserExternal::class, get_class($oUser));
- });
+ $this->assertDBQueryCount(
+ 0,
+ fn() => $this->FindUserAndAssertItHasBeenFound($sLogin, $iKey),
+ 'The cache should prevent additional queries on subsequent calls'
+ );
}
- public function testFindUser_UnknownLogin_AvoidSameSqlQueryTwice()
+ public function testFindUser_UnknownLogin()
{
- $sLogin = 'UserRightsFindUser'.uniqid();
- $oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
- $this->assertNull($oUser);
+ $sLogin = 'NobodyLogin';
- $this->assertDBQueryCount(0, function() use ($sLogin){
- $oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
- $this->assertNull($oUser);
- });
+ $this->assertDBQueryCount(
+ 2,
+ fn() => $this->FindUserAndAssertItWasNotFound($sLogin),
+ 'Some queries should be performed the first time FindUser is called'
+ );
+
+ $this->assertDBQueryCount(
+ 0,
+ fn() => $this->FindUserAndAssertItWasNotFound($sLogin),
+ 'The cache should prevent additional queries on subsequent calls'
+ );
+ }
+
+ public function FindUserAndAssertItHasBeenFound($sLogin, $iExpectedKey)
+ {
+ $oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
+ static::assertIsDBObject(\User::class, $iExpectedKey, $oUser, 'FindUser should return the User object corresponding to the login');
+ }
+ public function FindUserAndAssertItWasNotFound($sLogin)
+ {
+ $oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
+ static::assertNull($oUser, 'FindUser should return null when the login is unknown');
}
}