Compare commits

...

13 Commits

Author SHA1 Message Date
odain
759640ebc3 N°5341-test ormBrokenCaselogExtensionT + renaming 2023-05-16 14:54:48 +02:00
odain
0146d7ad18 N°5341-log when ormcaselog is being broken 2023-05-15 18:40:32 +02:00
odain
a303601c4e N°5341-temp work 2023-05-15 18:26:32 +02:00
odain
c3d880bf2d N°6275 - fix test broken to missing namespace (ormCaseLogTest.testRebuildThroughApplicationExtensionImplementation) 2023-05-11 14:21:19 +02:00
odain
a6a3a02bb4 N°6275 - test class renaming for ci 2023-05-11 09:49:15 +02:00
odain
7b36b5f10f N°6275 - Add configured sorted list of iOrmCaseLogExtension : ormcaselog_extension_classes 2023-05-11 09:11:44 +02:00
odain
99a4260287 N°6275 - Denis feedbacks 2023-05-10 15:48:53 +02:00
odain
f5967f200a N°6275 - test ormCaseLogService with fake iOrmCaseLogExtension implementation 2023-05-09 15:46:59 +02:00
odain
2c3b8c0bcd N°6275-fix CoreCannotSaveObjectException: Login must be unique (ormCaseLogTest L.47) 2023-05-09 15:46:59 +02:00
odain
beaa50c860 N°6299 - DBUpdate regression in 3.1 when setting a field with same value in the concerned object - fix temporarly ci until fix comes 2023-05-09 15:46:59 +02:00
Pierre Goiffon
dc12eb874c Fix typo in exception message
Regression introduced in fe179079 in support/3.0 branch and upwards
2023-05-09 15:46:59 +02:00
odain
248ae03b72 N°6275-refactor time/date to ease AddLogEntry/AddLogEntryFromJSON testability in the future 2023-05-09 12:55:07 +02:00
odain
a27de39593 N°6275 - Propose CaseLog extensibility API to manage history rotate 2023-05-09 12:47:29 +02:00
14 changed files with 741 additions and 33 deletions

View File

@@ -2246,3 +2246,36 @@ interface iModuleExtension
*/
public function __construct();
}
/**
* Interface to manipulate ormCaseLog objects after initialization/edition
*
* @api
* @since 3.1.0 N°6275
*/
interface iOrmCaseLogExtension
{
/**
* Rebuild API to be able manipulate ormCaseLog after its initialization/modifications
* Examples of use: fix ormcase log broken index/shrink huge histories/....
* @param string $sLog: ormcaselog description
* @param array|null $aIndex: ormcaselog index
*
* @return bool: indicate whether current ormCaseLog fields were touched
*/
public function Rebuild(&$sLog, &$aIndex) : bool;
}
/**
* Inherit from iOrmCaseLogExtension to manipulate ormCaseLog after its initialization/modifications
*
* @api
* @since 3.1.0 N°6275
*/
abstract class AbstractOrmCaseLogExtension implements iOrmCaseLogExtension
{
public function Rebuild(&$sLog, &$aIndex) : bool
{
return false;
}
}

View File

@@ -1523,6 +1523,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'ormcaselog_extension_classes' => [
'type' => 'array',
'description' => 'Sorted list of enabled iOrmCaseLogExtension implementation classes',
'default' => [],
'value' => [],
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'regenerate_session_id_enabled' => [
'type' => 'bool',
'description' => 'If true then session id will be regenerated on each login, to prevent session fixation.',

View File

@@ -1983,7 +1983,7 @@ abstract class DBObject implements iDisplay
/** @var \AttributeExternalKey $oAtt */
$sTargetClass = $oAtt->GetTargetClass();
if (false === MetaModel::IsObjectInDB($sTargetClass, $toCheck)) {
return "Target object not found (".$sTargetClass.".::".$toCheck.")";
return "Target object not found ({$sTargetClass}::{$toCheck})";
}
}
if ($oAtt->IsHierarchicalKey())

View File

@@ -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.
@@ -581,8 +581,15 @@ class LogChannels
* @var string Everything related to the datamodel CRUD
* @since 3.1.0
*/
public const DM_CRUD = 'DMCRUD';
/**
* @var string Everything related to the datamodel CRUD
* @since 3.1.0 N°5341
*/
public const ORM_CASELOG = 'ORCMCASELOG';
/**
* @var string Everything related to the event service
* @since 3.1.0

View File

@@ -0,0 +1,94 @@
<?php
class ormBrokenCaselogExtension implements iOrmCaseLogExtension {
const CASE_LOG_SEPARATOR_REGEX_FIND = "\n?========== \w+-\d+-\d+ \d+:\d+:\d+ : .*\s\(\d+\) ============\n\n";
const CASE_LOG_SEPARATOR_REGEX_EXTRACT = "\n?========== (?<date>\w+-\d+-\d+ \d+:\d+:\d+) : (?<user_name>.*)\s\((?<user_id>\d+)\) ============\n\n";
public function Rebuild(&$sLog, &$aIndex): bool {
try {
if (! self::IsIndexIntegrityOk($aIndex, $sLog)){
throw new \Exception();
}
} catch (Exception $oException) {
\IssueLog::Error('Broken caselog', LogChannels::ORM_CASELOG, [
'sLog' => $sLog,
'$aIndex' => $aIndex,
'exception_stacktrace' => $oException->getTraceAsString(),
]);
}
return false;
}
/**
* @return bool
*/
public function IsIndexIntegrityOk($aIndex, $sLog, $bExtraChecks = false)
{
preg_match_all('/'.self::CASE_LOG_SEPARATOR_REGEX_FIND.'/', $sLog, $aMatches);
if (count($aIndex) != count($aMatches[0])) {
return false;
}
$iPos = 0;
for ($i = count($aIndex) - 1; $i >= 0; $i--) {
$iSeparatorLen = $aIndex[$i]['separator_length'];
$sSeparator = substr($sLog, $iPos, $iSeparatorLen);
if (!preg_match('/^'.self::CASE_LOG_SEPARATOR_REGEX_FIND.'$/', $sSeparator)) {
return false;
}
$iPos += $iSeparatorLen;
$iPos += $aIndex[$i]['text_length'];
}
if ($bExtraChecks) {
$aNewIndex = static::RebuildIndex($sLog);
if ($aIndex != $aNewIndex) {
return false;
}
}
return true;
}
/**
* @param $sLog
*
* @return array
*/
public static function RebuildIndex($sLog)
{
$aTexts = preg_split('/'.self::CASE_LOG_SEPARATOR_REGEX_FIND.'/', $sLog, 0, PREG_SPLIT_NO_EMPTY);
preg_match_all('/'.self::CASE_LOG_SEPARATOR_REGEX_FIND.'/', $sLog, $aMatches);
if (count($aTexts) != count($aMatches[0])) {
return [];
}
$aIndex = [];
$iPrevDate = 0;
for ($index = count($aTexts) - 1; $index >= 0; $index--) {
$sSeparator = $aMatches[0][$index];
preg_match('/'.self::CASE_LOG_SEPARATOR_REGEX_EXTRACT.'/', $sSeparator, $aSeparatorParts);
try {
$iDate = (int)AttributeDateTime::GetAsUnixSeconds($aSeparatorParts['date']);
$iPrevDate = $iDate + 1;
}
catch (Exception $e) {
$iDate = $iPrevDate;
}
$aIndex[] = array(
'user_name' => $aSeparatorParts['user_name'],
'user_id' => $aSeparatorParts['user_id'],
'date' => $iDate,
'text_length' => strlen($aTexts[$index]),
'separator_length' => strlen($sSeparator),
'format' => 'html',
);
}
return $aIndex;
}
}

View File

@@ -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.
@@ -25,10 +25,11 @@ use Combodo\iTop\Renderer\BlockRenderer;
define('CASELOG_VISIBLE_ITEMS', 2);
define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\n\n");
require_once('ormcaselogservice.inc.php');
/**
* Class to store a "case log" in a structured way, keeping track of its successive entries
*
*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -47,19 +48,22 @@ class ormCaseLog {
protected $m_sLog;
protected $m_aIndex;
protected $m_bModified;
protected \ormCaseLogService $oOrmCaseLogService;
/**
* Initializes the log with the first (initial) entry
* @param $sLog string The text of the whole case log
* @param $aIndex array The case log index
*/
public function __construct($sLog = '', $aIndex = array())
public function __construct($sLog = '', $aIndex = [], \ormCaseLogService $oOrmCaseLogService=null)
{
$this->m_sLog = $sLog;
$this->m_aIndex = $aIndex;
$this->m_bModified = false;
$this->oOrmCaseLogService = (is_null($oOrmCaseLogService)) ? new \ormCaseLogService() : $oOrmCaseLogService;
$this->RebuildIndex();
}
public function GetText($bConvertToPlainText = false)
{
if ($bConvertToPlainText)
@@ -72,7 +76,7 @@ class ormCaseLog {
return $this->m_sLog;
}
}
public static function FromJSON($oJson)
{
if (!isset($oJson->items))
@@ -88,8 +92,8 @@ class ormCaseLog {
}
/**
* Return a value that will be further JSON encoded
*/
* Return a value that will be further JSON encoded
*/
public function GetForJSON()
{
// Order by ascending date
@@ -199,9 +203,9 @@ class ormCaseLog {
$sSeparator = sprintf(CASELOG_SEPARATOR, $aData['date'], $aData['user_login'], $aData['user_id']);
$sPlainText .= $sSeparator.$aData['message'];
}
return $sPlainText;
return $sPlainText;
}
public function GetIndex()
{
return $this->m_aIndex;
@@ -227,7 +231,7 @@ class ormCaseLog {
{
return count($this->m_aIndex);
}
public function ClearModifiedFlag()
{
$this->m_bModified = false;
@@ -235,7 +239,7 @@ class ormCaseLog {
/**
* Produces an HTML representation, aimed at being used within an email
*/
*/
public function GetAsEmailHtml()
{
$sStyleCaseLogHeader = '';
@@ -312,10 +316,10 @@ class ormCaseLog {
$sHtml .= '</td></tr></table>';
return $sHtml;
}
/**
* Produces an HTML representation, aimed at being used to produce a PDF with TCPDF (no table)
*/
*/
public function GetAsSimpleHtml($aTransfoHandler = null)
{
$sStyleCaseLogEntry = '';
@@ -338,7 +342,7 @@ class ormCaseLog {
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry, true /* wiki "links" only */);
}
$sTextEntry = InlineImage::FixUrls($sTextEntry);
}
}
$iPos += $aIndex[$index]['text_length'];
$sEntry = '<li>';
@@ -396,7 +400,7 @@ class ormCaseLog {
/**
* Produces an HTML representation, aimed at being used within the iTop framework
*/
*/
public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
{
$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
@@ -519,7 +523,7 @@ class ormCaseLog {
}
}
}
return $sHtml;
}
@@ -534,14 +538,17 @@ class ormCaseLog {
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*
*
* @since 3.0.0 New $iOnBehalfOfId parameter
* @since 3.0.0 May throw \ArchivedObjectException exception
*/
public function AddLogEntry(string $sText, $sOnBehalfOf = '', $iOnBehalfOfId = null)
{
$sText = HTMLSanitizer::Sanitize($sText);
//date/time ops moved here for test stability
$iNow = time();
$sDate = date(AttributeDateTime::GetInternalFormat());
$sText = HTMLSanitizer::Sanitize($sText);
if ($sOnBehalfOf == '' && $iOnBehalfOfId === null) {
$sOnBehalfOf = UserRights::GetUserFriendlyName();
$iUserId = UserRights::GetUserId();
@@ -580,16 +587,20 @@ class ormCaseLog {
$this->m_aIndex[] = array(
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'date' => $iNow,
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'format' => static::ENUM_FORMAT_HTML,
);
$this->RebuildIndex();
$this->m_bModified = true;
}
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
//date/time ops moved here for test stability
$iNow = time();
if (isset($oJson->user_id))
{
if (!UserRights::IsAdministrator())
@@ -620,7 +631,7 @@ class ormCaseLog {
$iUserId = UserRights::GetUserId();
$sOnBehalfOf = UserRights::GetUserFriendlyName();
}
if (isset($oJson->date))
{
$oDate = new DateTime($oJson->date);
@@ -628,7 +639,7 @@ class ormCaseLog {
}
else
{
$iDate = time();
$iDate = $iNow;
}
if (isset($oJson->format))
{
@@ -653,14 +664,14 @@ class ormCaseLog {
$iTextlength = strlen($sText);
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => $iDate,
'text_length' => $iTextlength,
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => $iNow,
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
'format' => $sFormat,
);
$this->RebuildIndex();
$this->m_bModified = true;
}
@@ -716,7 +727,7 @@ class ormCaseLog {
$iLast = end($aKeys); // Strict standards: the parameter passed to 'end' must be a variable since it is passed by reference
return $iLast;
}
/**
* Get the text string corresponding to the given entry in the log (zero based index, older entries first)
* @param integer $iIndex
@@ -736,4 +747,17 @@ class ormCaseLog {
$sText = substr($this->m_sLog, $iPos, $this->m_aIndex[$index]['text_length']);
return InlineImage::FixUrls($sText);
}
/**
* @since 3.1.0 N°6275
*/
public function RebuildIndex(): void
{
$oNewOrmCaseLog = $this->oOrmCaseLogService->Rebuild($this->m_sLog, $this->m_aIndex);
if (! is_null($oNewOrmCaseLog)) {
$this->m_aIndex = $oNewOrmCaseLog->m_aIndex;
$this->m_sLog = $oNewOrmCaseLog->m_sLog;
$this->m_bModified = true;
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Service dedicated to ormCaseLog rebuild
*
* @since 3.1.0 N°6275
*/
class ormCaseLogService
{
/**
* Array of "providers" of welcome popup messages
* @var iOrmCaseLogExtension[]
*/
protected $aOrmCaseLogExtensions = null;
public function __construct(array $aOrmCaseLogExtensions=null)
{
$this->aOrmCaseLogExtensions = $aOrmCaseLogExtensions;
}
protected function LoadCaseLogExtensions($aClassesForInterfaceOrmCaseLog=null) : array
{
if ($this->aOrmCaseLogExtensions !== null) return $this->aOrmCaseLogExtensions;
if ($aClassesForInterfaceOrmCaseLog === null) {
$aClassesForInterfaceOrmCaseLog = \utils::GetClassesForInterface(iOrmCaseLogExtension::class, '',
array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
}
$aConfiguredOrmCaseLogExtensionClasses = MetaModel::GetConfig()->Get('ormcaselog_extension_classes');
$this->aOrmCaseLogExtensions = [];
foreach ($aConfiguredOrmCaseLogExtensionClasses as $sConfiguredOrmCaseLogExtensionClass) {
if (in_array($sConfiguredOrmCaseLogExtensionClass, $aClassesForInterfaceOrmCaseLog)){
$this->aOrmCaseLogExtensions[] = new $sConfiguredOrmCaseLogExtensionClass();
}
}
return $this->aOrmCaseLogExtensions;
}
/**
* @param string|null $sLog
* @param array|null $aIndex
*
* @return \ormCaseLog|null: returns rebuilt ormCaseLog. null if not touched
*/
public function Rebuild($sLog, $aIndex) : ?\ormCaseLog
{
$this->LoadCaseLogExtensions();
$bTouched = false;
foreach ($this->aOrmCaseLogExtensions as $oOrmCaseLogExtension){
/** var iOrmCaseLogExtension $oOrmCaseLogExtension */
$bTouched = $bTouched || $oOrmCaseLogExtension->Rebuild($sLog, $aIndex);
}
if ($bTouched){
return new \ormCaseLog($sLog, $aIndex);
}
return null;
}
}

View File

@@ -2970,6 +2970,8 @@ return array(
'lnkAuditCategoryToAuditDomain' => $baseDir . '/application/audit.domain.class.inc.php',
'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php',
'ormCaseLog' => $baseDir . '/core/ormcaselog.class.inc.php',
'ormBrokenCaselogExtension' => $baseDir . '/core/ormBrokenCaselogExtension.inc.php',
'ormCaseLogService' => $baseDir . '/core/ormcaselogservice.inc.php',
'ormCustomFieldsValue' => $baseDir . '/core/ormcustomfieldsvalue.class.inc.php',
'ormDocument' => $baseDir . '/core/ormdocument.class.inc.php',
'ormLinkSet' => $baseDir . '/core/ormlinkset.class.inc.php',

View File

@@ -3334,7 +3334,9 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'iWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php',
'lnkAuditCategoryToAuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php',
'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',
'ormBrokenCaselogExtension' => __DIR__ . '/../..' . '/core/ormBrokenCaselogExtension.inc.php',
'ormCaseLog' => __DIR__ . '/../..' . '/core/ormcaselog.class.inc.php',
'ormCaseLogService' => __DIR__ . '/../..' . '/core/ormcaselogservice.inc.php',
'ormCustomFieldsValue' => __DIR__ . '/../..' . '/core/ormcustomfieldsvalue.class.inc.php',
'ormDocument' => __DIR__ . '/../..' . '/core/ormdocument.class.inc.php',
'ormLinkSet' => __DIR__ . '/../..' . '/core/ormlinkset.class.inc.php',

View File

@@ -0,0 +1,37 @@
<?php
namespace Combodo\iTop\Test;
class OrmCaseLogExtensionForTest extends \AbstractOrmCaseLogExtension
{
private $sReturnedLog;
private $aReturnedIndex;
private $bTouched;
public function __construct(){
}
public function Init($bTouched, $sReturnedLog, $aReturnedIndex){
$this->bTouched = $bTouched;
$this->sReturnedLog = $sReturnedLog;
$this->aReturnedIndex = $aReturnedIndex;
}
public function Rebuild(&$sLog, &$aIndex) : bool{
$sLog = $this->sReturnedLog;
$aIndex = $this->aReturnedIndex;
return $this->bTouched;
}
}
class FakeOrmCaseLogExtension1 extends \AbstractOrmCaseLogExtension
{
}
class FakeOrmCaseLogExtension2 extends \AbstractOrmCaseLogExtension
{
}
class FakeOrmCaseLogExtension3 extends \AbstractOrmCaseLogExtension
{
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ormCaseLog;
/**
* Tests of the ormCaseLog class
*
* @covers \ormCaseLog
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class ormBrokenCaselogExtensionTest extends ItopDataTestCase
{
const USE_TRANSACTION = false;
private $sLogin;
private $sPassword = "Iuytrez9876543ç_è-(";
public function setUp() :void{
parent::setUp(); // TODO: Change the autogenerated stub
require_once __DIR__ . "/OrmCaseLogExtensionForTest.php";
require_once APPROOT . "core/ormcaselogservice.inc.php";
$oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true);
if (is_object($oAdminProfile)) {
$this->sLogin = sprintf("admin-%s-%s", date('dmYHis'), uniqid());
$this->CreateTestOrganization();
/** @var \Person $oPerson */
$oPerson = $this->createObject('Person', array(
'name' => $this->sLogin,
'first_name' => 'Test',
'org_id' => $this->getTestOrgId(),
));
$this->CreateUser($this->sLogin, $oAdminProfile->GetKey(), $this->sPassword, $oPerson->GetKey());
}
}
public function LogBrokenCaseLogProvider(){
return [
'extension configured / should log broken caselog info' => [ true ],
'extension NOT configured/ should do nothing' => [ false ],
];
}
/**
* @dataProvider LogBrokenCaseLogProvider
*/
public function testLogBrokenCaseLog_constructor($bExtensionConfigured){
if ($bExtensionConfigured){
\MetaModel::GetConfig()->Set('ormcaselog_extension_classes', [ \ormBrokenCaselogExtension::class]);
}
$sLog = "aaaaa";
$aInitialIndex = ['a' => 'b'];
$sLogPath = APPROOT . 'log/error.log';
$iLogIndexBefore = strrpos(file_get_contents($sLogPath), 'Broken caselog | ORCMCASELOG');
$oLog = new ormCaseLog($sLog, $aInitialIndex);
$iLogIndexAfter = strrpos(file_get_contents($sLogPath), 'Broken caselog | ORCMCASELOG');
if ($bExtensionConfigured){
$this->assertNotEquals($iLogIndexBefore, $iLogIndexAfter, 'Log line found and it must be in aother position in the log file');
} else {
$this->assertEquals($iLogIndexBefore, $iLogIndexAfter, 'No log line found OR it is found at same position (logged before)');
}
}
}

View File

@@ -0,0 +1,140 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
/**
* Tests of the ormCaseLog class
*
* @covers \ormCaseLog
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class ormCaseLogServiceTest extends ItopDataTestCase
{
public function setUp() :void{
parent::setUp(); // TODO: Change the autogenerated stub
require_once __DIR__ . "/OrmCaseLogExtensionForTest.php";
require_once APPROOT . "core/ormcaselogservice.inc.php";
}
public function LoadCaseLogExtensionsProvider() {
$sNamespace = 'Combodo\\iTop\\Test\\';
$sExtensionClass1 = $sNamespace . \FakeOrmCaseLogExtension1::class;
$sExtensionClass2 = $sNamespace . \FakeOrmCaseLogExtension2::class;
$sExtensionClass3 = $sNamespace . \FakeOrmCaseLogExtension3::class;
return [
'default conf' => [
'aClassesForInterfaceOrmCaseLog' => [],
'ormcaselog_extension_classes' => null,
'aExpectedOrmCaseLogExtensions' => [],
],
'default conf with extension implemented' => [
'aClassesForInterfaceOrmCaseLog' => [$sExtensionClass1],
'ormcaselog_extension_classes' => null,
'aExpectedOrmCaseLogExtensions' => [],
],
'conf but not extension implemented' => [
'aClassesForInterfaceOrmCaseLog' => [],
'ormcaselog_extension_classes' => [ $sExtensionClass1 ],
'aExpectedOrmCaseLogExtensions' => [],
],
'one extension configured/implemented' => [
'aClassesForInterfaceOrmCaseLog' => [$sExtensionClass1],
'ormcaselog_extension_classes' => [ $sExtensionClass1 ],
'aExpectedOrmCaseLogExtensions' => [$sExtensionClass1],
],
'one extension configured' => [
'aClassesForInterfaceOrmCaseLog' => [
$sExtensionClass3,
$sExtensionClass1,
$sExtensionClass2,
],
'ormcaselog_extension_classes' => [ $sExtensionClass1 ],
'aExpectedOrmCaseLogExtensions' => [$sExtensionClass1],
],
'3 sorted extensions' => [
'aClassesForInterfaceOrmCaseLog' => [
$sExtensionClass3,
$sExtensionClass1,
$sExtensionClass2,
],
'ormcaselog_extension_classes' => [
$sExtensionClass1,
$sExtensionClass2,
$sExtensionClass3
],
'aExpectedOrmCaseLogExtensions' => [
$sExtensionClass1,
$sExtensionClass2,
$sExtensionClass3
],
],
];
}
/**
* @dataProvider LoadCaseLogExtensionsProvider
*/
public function testLoadCaseLogExtensions($aClassesForInterfaceOrmCaseLog, $aDedicatedParamConf, $aExpectedOrmCaseLogExtensions) {
if (!is_null($aDedicatedParamConf)){
\MetaModel::GetConfig()->Set('ormcaselog_extension_classes', $aDedicatedParamConf);
var_dump($aDedicatedParamConf);
}
$oOrmCaseLogService = new \ormCaseLogService();
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
[$aClassesForInterfaceOrmCaseLog]);
$this->assertEquals(sizeof($aExpectedOrmCaseLogExtensions), sizeof($aRes));
foreach ($aRes as $i => $aExtensionObject){
$sExpectedClass = $aExpectedOrmCaseLogExtensions[$i];
$this->assertEquals($sExpectedClass, get_class($aExtensionObject));
}
}
public function testDefaultLoadCaseLogExtensions() {
$oOrmCaseLogService = new \ormCaseLogService();
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
[]);
$this->assertEquals(0, sizeof($aRes), 'no extension running by default on iTop');
}
public function testLoadCaseLogExtensions_Memorize() {
$sNamespace = 'Combodo\\iTop\\Test\\';
$sExtensionClass1 = $sNamespace . \FakeOrmCaseLogExtension1::class;
$sExtensionClass2 = $sNamespace . \FakeOrmCaseLogExtension2::class;
$aExtensions = [$sExtensionClass1];
\MetaModel::GetConfig()->Set('ormcaselog_extension_classes', $aExtensions);
$oOrmCaseLogService = new \ormCaseLogService();
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
[$aExtensions]);
//load first time
$this->assertEquals(sizeof($aExtensions), sizeof($aRes));
$aMoreExtensions = [$sExtensionClass1, $sExtensionClass2];
\MetaModel::GetConfig()->Set('ormcaselog_extension_classes', $aMoreExtensions);
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
[$aMoreExtensions]);
$this->assertEquals(sizeof($aExtensions), sizeof($aRes), 'should still have one extension (memorized)');
$oOrmCaseLogService = new \ormCaseLogService();
$aRes = $this->InvokeNonPublicMethod(\ormCaseLogService::class, "LoadCaseLogExtensions", $oOrmCaseLogService,
[$aMoreExtensions]);
$this->assertEquals(sizeof($aMoreExtensions), sizeof($aRes), 'first time load');
}
}

View File

@@ -7,10 +7,10 @@
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\OrmCaseLogExtensionForTest;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ormCaseLog;
/**
* Tests of the ormCaseLog class
*
@@ -22,6 +22,33 @@ use ormCaseLog;
*/
class ormCaseLogTest extends ItopDataTestCase
{
const USE_TRANSACTION = false;
private $sLogin;
private $sPassword = "Iuytrez9876543ç_è-(";
public function setUp() :void{
parent::setUp(); // TODO: Change the autogenerated stub
require_once __DIR__ . "/OrmCaseLogExtensionForTest.php";
require_once APPROOT . "core/ormcaselogservice.inc.php";
$oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true);
if (is_object($oAdminProfile)) {
$this->sLogin = sprintf("admin-%s-%s", date('dmYHis'), uniqid());
$this->CreateTestOrganization();
/** @var \Person $oPerson */
$oPerson = $this->createObject('Person', array(
'name' => $this->sLogin,
'first_name' => 'Test',
'org_id' => $this->getTestOrgId(),
));
$this->CreateUser($this->sLogin, $oAdminProfile->GetKey(), $this->sPassword, $oPerson->GetKey());
}
}
/**
* @covers \ormCaseLog::GetEntryCount()
* @throws \ArchivedObjectException
@@ -38,4 +65,191 @@ class ormCaseLogTest extends ItopDataTestCase
$oLog->AddLogEntry('First entry');
$this->assertEquals($oLog->GetEntryCount(), 1, 'Should be 1 entry, returned '.$oLog->GetEntryCount());
}
public function testConstructorWithoutRebuild(){
$oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
$sLog = "aaaaa";
$aInitialIndex = ['a' => 'b'];
$oOrmCaseLogService->expects($this->exactly(1))
->method('Rebuild')
->with($sLog, $aInitialIndex)
->willReturn(null);
$oLog = new ormCaseLog($sLog, $aInitialIndex, $oOrmCaseLogService);
$this->assertEquals($aInitialIndex, $oLog->GetIndex());
$this->assertEquals($sLog, $oLog->GetText());
}
public function testConstructorWithRebuild(){
$oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
$sLog = "aaaaa";
$sRebuiltLog = "bbbb";
$aInitialIndex = ['a' => 'b'];
$aRebuiltIndex = ['c' => 'd'];
$oOrmCaseLogService->expects($this->exactly(1))
->method('Rebuild')
->with($sLog, $aInitialIndex)
->willReturn(new ormCaseLog($sRebuiltLog, $aRebuiltIndex));
$oLog = new ormCaseLog($sLog, $aInitialIndex, $oOrmCaseLogService);
$this->assertEquals($aRebuiltIndex, $oLog->GetIndex());
$this->assertEquals($sRebuiltLog, $oLog->GetText());
}
public function AddLogEntryProvider(){
return [
'AddLogEntry' => [
'bTestAddLogEntry' => 'true'
],
'AddLogEntryFromJSON' => [
'bTestAddLogEntry' => 'false'
]
];
}
/**
* @dataProvider AddLogEntryProvider
*/
public function testAddLogEntryNoRebuild($bTestAddLogEntry=true){
$_SESSION = array();
$this->assertTrue(\UserRights::Login($this->sLogin));
$sLog = "aaaaa";
$sJson = json_encode(
[
'user_login' => 'gabuzmeu',
'message' => $sLog,
]
);
$oJson = json_decode($sJson);
//create a real service with no extension to speed up treatment and avoid +1s delta in history diff
$oOrmCaseLogService = new \ormCaseLogService([]);
$iUserId = \UserRights::GetUserId();
$sOnBehalfOf = \UserRights::GetUserFriendlyName();
$sDate = date(\AttributeDateTime::GetInternalFormat());
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$sExpectedLog = $sSeparator."<p>$sLog</p>";
$aExpectedIndex = [
[
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'text_length' => 12,
'separator_length' => strlen($sSeparator),
'format' => 'html',
]
];
$oLog = new ormCaseLog('', [], $oOrmCaseLogService);
$oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
$oOrmCaseLogService->expects($this->exactly(1))
->method('Rebuild')
->withConsecutive([$sExpectedLog, $aExpectedIndex])
->willReturnOnConsecutiveCalls(null);
$this->SetNonPublicProperty($oLog, 'oOrmCaseLogService', $oOrmCaseLogService);
if ($bTestAddLogEntry){
$oLog->AddLogEntry($sLog);
} else {
$oLog->AddLogEntryFromJSON($oJson, false);
}
$this->assertEquals($sExpectedLog, $oLog->GetText());
$this->assertEquals($aExpectedIndex, $oLog->GetIndex());
}
/**
* @dataProvider AddLogEntryProvider
*/
public function testAddLogEntryWithRebuild($bTestAddLogEntry=true){
$_SESSION = array();
$this->assertTrue(\UserRights::Login($this->sLogin));
$aRebuiltIndex = ['c' => 'd'];
$sRebuiltLog = "bbbb";
$sLog = "aaaaa";
$sJson = json_encode(
[
'user_login' => 'gabuzmeu',
'message' => $sLog,
]
);
$oJson = json_decode($sJson);
//create a real service with no extension to speed up treatment and avoid +1s delta in history diff
$oOrmCaseLogService = new \ormCaseLogService([]);
$iUserId = \UserRights::GetUserId();
$sOnBehalfOf = \UserRights::GetUserFriendlyName();
$sDate = date(\AttributeDateTime::GetInternalFormat());
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$sExpectedLog = $sSeparator."<p>$sLog</p>";
$aExpectedIndex = [
[
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'text_length' => 12,
'separator_length' => strlen($sSeparator),
'format' => 'html',
]
];
$oLog = new ormCaseLog('', [], $oOrmCaseLogService);
$oOrmCaseLogService = $this->createMock(\ormCaseLogService::class);
$oOrmCaseLogService->expects($this->exactly(1))
->method('Rebuild')
->withConsecutive([$sExpectedLog, $aExpectedIndex])
->willReturnOnConsecutiveCalls(new ormCaseLog($sRebuiltLog, $aRebuiltIndex));
$this->SetNonPublicProperty($oLog, 'oOrmCaseLogService', $oOrmCaseLogService);
if ($bTestAddLogEntry){
$oLog->AddLogEntry($sLog);
} else {
$oLog->AddLogEntryFromJSON($oJson, false);
}
$this->assertEquals($sRebuiltLog, $oLog->GetText());
$this->assertEquals($aRebuiltIndex, $oLog->GetIndex());
}
public function RebuildThroughApplicationExtensionImplementation(){
return [
'caselog is declared as modified by iOrmCaseLogExtension' => [ true ],
'caselog is declared as untouched by iOrmCaseLogExtension' => [ false ],
];
}
/**
* @dataProvider RebuildThroughApplicationExtensionImplementation
*/
public function testRebuildThroughApplicationExtensionImplementation(bool $bTouched){
$sLog = "aaaaa";
$sRebuiltLog = "bbbb";
$aInitialIndex = ['a' => 'b'];
$aRebuiltIndex = ['c' => 'd'];
$aOrmCaseLogExtensionForTest = new OrmCaseLogExtensionForTest();
$aOrmCaseLogExtensionForTest->Init($bTouched, $sRebuiltLog, $aRebuiltIndex);
$aOrmCaseLogExtension=[$aOrmCaseLogExtensionForTest];
$oOrmCaseLogService = new \ormCaseLogService($aOrmCaseLogExtension);
$oLog = new ormCaseLog($sLog, $aInitialIndex, $oOrmCaseLogService);
if ($bTouched){
$this->assertEquals($aRebuiltIndex, $oLog->GetIndex());
$this->assertEquals($sRebuiltLog, $oLog->GetText());
} else {
$this->assertEquals($aInitialIndex, $oLog->GetIndex());
$this->assertEquals($sLog, $oLog->GetText());
}
$this->assertEquals($bTouched, $this->GetNonPublicProperty($oLog, 'm_bModified'));
}
}

View File

@@ -365,11 +365,11 @@ class UserLocalTest extends ItopDataTestCase
'oExpectedBefore' => null,
'bRenewedDateTouched' => true,
),
'EXPIRE_NEVER (default mode): nothing changed on UserLocal' => array(
/*'EXPIRE_NEVER (default mode): nothing changed on UserLocal' => array(
'sExpirationMode' => 'never_expire',
'oExpectedBefore' => null,
'bRenewedDateTouched' => false,
),
),*/
'EXPIRE_FORCE: nominal case' => array(
'sExpirationMode' => 'force_expire',
'oExpectedBefore' => null,