Merge remote-tracking branch 'origin/support/2.7' into develop

# Conflicts:
#	core/dbobject.class.php
#	core/log.class.inc.php
#	setup/compiler.class.inc.php
This commit is contained in:
Pierre Goiffon
2021-06-28 08:58:34 +02:00
15 changed files with 509 additions and 118 deletions

View File

@@ -68,6 +68,7 @@ class CMDBSource
/** @var mysqli $m_oMysqli */
protected static $m_oMysqli;
protected static $oMySQLiForQuery;
/**
* @var int number of level for nested transactions : 0 if no transaction was ever opened, +1 for each 'START TRANSACTION' sent
@@ -134,6 +135,7 @@ class CMDBSource
self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA;
self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
self::SetMySQLiForQuery(self::$m_oMysqli);
}
/**
@@ -151,8 +153,6 @@ class CMDBSource
public static function GetMysqliInstance(
$sDbHost, $sUser, $sPwd, $sSource = '', $bTlsEnabled = false, $sTlsCa = null, $bCheckTlsAfterConnection = false
) {
$oMysqli = null;
$sServer = null;
$iPort = null;
self::InitServerAndPort($sDbHost, $sServer, $iPort);
@@ -450,6 +450,24 @@ class CMDBSource
return self::$m_oMysqli;
}
/**
* @return
*/
private static function GetMySQLiForQuery()
{
return self::$oMySQLiForQuery;
}
/**
* Used for test purpose (mysqli mock)
* @param $oMySQLi
*/
private static function SetMySQLiForQuery($oMySQLi)
{
self::$oMySQLiForQuery = $oMySQLi;
}
public static function GetErrNo()
{
if (self::$m_oMysqli->errno != 0)
@@ -585,7 +603,7 @@ class CMDBSource
*/
private static function DBQuery($sSql)
{
$sShortSQL = str_replace("\n", " ", substr($sSql, 0, 120));
$sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSql, 0, 180)), 0, 150);
if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) {
IssueLog::Trace("$sShortSQL", 'cmdbsource');
}
@@ -593,7 +611,7 @@ class CMDBSource
$oKPI = new ExecutionKPI();
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch (mysqli_sql_exception $e)
{
@@ -657,7 +675,7 @@ class CMDBSource
);
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
IssueLog::Error($sMessage, 'DeadLock', $e->getMessage());
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
}
/**
@@ -855,7 +873,7 @@ class CMDBSource
$oKPI = new ExecutionKPI();
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -895,7 +913,7 @@ class CMDBSource
$oKPI = new ExecutionKPI();
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -977,7 +995,7 @@ class CMDBSource
{
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -1465,7 +1483,7 @@ class CMDBSource
$sSql = "SELECT * FROM `$sTable`";
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch(mysqli_sql_exception $e)
{

View File

@@ -3271,13 +3271,6 @@ abstract class DBObject implements iDisplay
$this->DBWriteLinks();
$this->WriteExternalAttributes();
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
// new values are already in the object (call {@see DBObject::Get()} to get them)
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
$this->m_bDirty = false;
$this->m_aTouchedAtt = array();
$this->m_aModifiedAtt = array();
if (count($aChanges) != 0)
{
$this->RecordAttChanges($aChanges, $aOriginalValues);

View File

@@ -199,14 +199,13 @@ class InlineImage extends DBObject
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
$aInlineImagesId = array();
while($oInlineImage = $oSet->Fetch())
{
while ($oInlineImage = $oSet->Fetch()) {
$aInlineImagesId[] = $oInlineImage->GetKey();
$oInlineImage->SetItem($oObject);
$oInlineImage->Set('temp_id', '');
$oInlineImage->DBUpdate();
}
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', 'InlineImage', array(
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', LogChannels::INLINE_IMAGE, array(
'$sObjectClass' => get_class($oObject),
'$sTransactionId' => $iTransactionId,
'$sTempId' => $sTempId,
@@ -215,9 +214,8 @@ class InlineImage extends DBObject
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
}
else
{
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', 'InlineImage', array(
else {
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', LogChannels::INLINE_IMAGE, array(
'$sObjectClass' => get_class($oObject),
'$sTransactionId' => $iTransactionId,
'$sUser' => UserRights::GetUser(),
@@ -253,7 +251,7 @@ class InlineImage extends DBObject
$aInlineImagesId[] = $oInlineImage->GetKey();
$oInlineImage->DBDelete();
}
IssueLog::Trace('OnFormCancel', 'InlineImage', array(
IssueLog::Trace('OnFormCancel', LogChannels::INLINE_IMAGE, array(
'$sTempId' => $sTempId,
'$aInlineImagesId' => $aInlineImagesId,
'$sUser' => UserRights::GetUser(),
@@ -608,7 +606,7 @@ JS
*/
protected function AfterInsert()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),
@@ -629,7 +627,7 @@ JS
*/
protected function AfterUpdate()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),
@@ -650,7 +648,7 @@ JS
*/
protected function AfterDelete()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),

View File

@@ -518,12 +518,9 @@ class FileLog
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
if (empty($aContext))
{
if (empty($aContext)) {
fwrite($hLogFile, "$sDate | $sText\n");
}
else
{
} else {
$sContext = var_export($aContext, true);
fwrite($hLogFile, "$sDate | $sText\n$sContext\n");
}
@@ -534,6 +531,21 @@ class FileLog
}
}
/**
* Simple enum like class to factorize channels values as constants
* Channels are used especially as parameters in {@see \LogAPI} methods
*
* @since 2.7.5 3.0.0 N°4012
*/
class LogChannels
{
const DEADLOCK = 'DeadLock';
const INLINE_IMAGE = 'InlineImage';
const PORTAL = 'portal';
}
abstract class LogAPI
{
public const CHANNEL_DEFAULT = '';
@@ -546,6 +558,7 @@ abstract class LogAPI
public const LEVEL_TRACE = 'Trace';
/**
* @var string default log level
* @see GetMinLogLevel
* @used-by GetLevelDefault
* @since 2.7.1 N°2977
*/

View File

@@ -31,6 +31,7 @@ use DBObjectSet;
use DBSearch;
use FieldExpression;
use IssueLog;
use LogChannels;
use MetaModel;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -441,9 +442,9 @@ class BrowseBrickController extends BrickController
}
}
IssueLog::Debug('Portal BrowseBrick query', 'portal', array(
'portalId' => $sPortalId,
'brickId' => $sBrickId,
IssueLog::Debug('Portal BrowseBrick query', LogChannels::PORTAL, array(
'sPortalId' => $sPortalId,
'sBrickId' => $sBrickId,
'oql' => $oSet->GetFilter()->ToOQL(),
));

View File

@@ -44,6 +44,7 @@ use FieldExpression;
use iPopupMenuExtension;
use IssueLog;
use JSButtonItem;
use LogChannels;
use MetaModel;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -816,10 +817,10 @@ class ManageBrickController extends BrickController
);
}
IssueLog::Debug('Portal ManageBrick query', 'portal', array(
'portalId' => $sPortalId,
'brickId' => $sBrickId,
'groupingTab' => $sGroupingTab,
IssueLog::Debug('Portal ManageBrick query', LogChannels::PORTAL, array(
'sPortalId' => $sPortalId,
'sBrickId' => $sBrickId,
'sGroupingTab' => $sGroupingTab,
'oql' => $oSet->GetFilter()->ToOQL(),
'aGroupingTabs' => $aGroupingTabs,
));

View File

@@ -78,6 +78,12 @@ class ObjectFormManager extends FormManager
protected $aFormProperties;
/** @var array $aCallbackUrls */
protected $aCallbackUrls = array();
/**
* List of hidden fields, used for form update (eg. remove them from the form regarding they dependencies)
* @var array $aHiddenFieldsId
* @since 2.7.5
*/
protected $aHiddenFieldsId = array();
/**
* Creates an instance of \Combodo\iTop\Portal\Form\ObjectFormManager from JSON data that must contain at least :
@@ -954,6 +960,8 @@ class ObjectFormManager extends FormManager
if($oField->GetHidden() === false)
{
$oForm->AddField($oField);
} else {
$this->aHiddenFieldsId[]=$oField->GetId();
}
}
@@ -1485,4 +1493,22 @@ class ObjectFormManager extends FormManager
}
return $oChangeOp;
}
/**
* @return array
* @since 2.7.5
*/
public function GetHiddenFieldsId()
{
return $this->aHiddenFieldsId;
}
/**
* @param array $aHiddenFieldsId
* @since 2.7.5
*/
public function SetHiddenFieldsId($aHiddenFieldsId)
{
$this->aHiddenFieldsId = $aHiddenFieldsId;
}
}

View File

@@ -280,7 +280,7 @@ class ObjectFormHandlerHelper
->SetFormProperties($aFormProperties);
$oFormManager->Build();
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
// Check the number of editable fields
$aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
}
@@ -388,6 +388,7 @@ class ObjectFormHandlerHelper
$aFormData['object_state'] = $oFormManager->GetObject()->GetState();
$aFormData['fieldset'] = $aFieldSetData;
$aFormData['display_mode'] = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['display_mode'] : ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE;
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
// - Set a text to be copied on title if the object is not in creation
if($sMode !== static::ENUM_MODE_CREATE && !empty($sObjectId))
{

View File

@@ -151,9 +151,16 @@ $(function()
// Intended for overloading in derived classes
_onUpdateSuccess: function(oData, sFormPath)
{
var me = this;
if(oData.form.hidden_fields !== undefined)
{
$.each(oData.form.hidden_fields, function( index, value ) {
me.element.find('[data-form-path="' + sFormPath + '"][data-field-id="'+value+'"][data-attribute-flag-hidden="false"]').children().remove();
});
}
if(oData.form.updated_fields !== undefined)
{
this.element.find('[data-form-path="' + sFormPath + '"]').trigger('update_form', {updated_fields: oData.form.updated_fields});
me.element.find('[data-form-path="' + sFormPath + '"]').trigger('update_form', {updated_fields: oData.form.updated_fields});
}
},
// Intended for overloading in derived classes

View File

@@ -2491,7 +2491,7 @@ EOF
$aResult['height'] = $aDimensions['height'];
}
IssueLog::Trace('InlineImage created', 'InlineImage', array(
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
'$operation' => $operation,
'$aResult' => $aResult,
'secret' => $oAttachment->Get('secret'),
@@ -2542,7 +2542,7 @@ EOF
$oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess
$iAttId = $oAttachment->DBInsert();
IssueLog::Trace('InlineImage created', 'InlineImage', array(
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
'$operation' => $operation,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,

View File

@@ -64,6 +64,14 @@ class MFCompiler
/** @var \ThemeHandlerService */
protected static $oThemeHandlerService;
/**
* Path to the "calculate hKeys" file
* If this file is present, then we don't recalculate hkeys
*
* @var string
*/
public const REBUILD_HKEYS_NEVER= APPROOT.'data/.setup-rebuild-hkeys-never';
/** @var \ModelFactory */
protected $oFactory;
@@ -167,6 +175,20 @@ class MFCompiler
unlink(static::USE_SYMBOLIC_LINKS_FILE_PATH);
}
/**
* @return bool possible return values :
* * if flag is present true, false otherwise
*
* @uses \file_exists()
* @uses REBUILD_HKEYS_NEVER
*
* @since 2.7.5
*/
public static function SkipRebuildHKeys(): bool
{
return (file_exists(static::REBUILD_HKEYS_NEVER));
}
/**
* Compile the data model into PHP files and data structures
*

View File

@@ -598,10 +598,15 @@ class RunTimeEnvironment
$this->log_ok("Database structure successfully updated.");
// Check (and update only if it seems needed) the hierarchical keys
if (MFCompiler::SkipRebuildHKeys()) {
$this->log_ok("Hierchical keys are NOT rebuilt due to the presence of the \"data/.setup-rebuild-hkeys-never\" file");
} else {
ob_start();
$this->log_ok("Start of rebuilt of hierchical keys. If you have problems with this step, you can skip it by creating a \".setup-rebuild-hkeys-never\" file in data");
MetaModel::CheckHKeys(false /* bDiagnosticsOnly */, true /* bVerbose*/, true /* bForceUpdate */); // Since in 1.2-beta the detection was buggy, let's force the rebuilding of HKeys
$sFeedback = ob_get_clean();
$this->log_ok("Hierchical keys rebuilt: $sFeedback");
}
// Check (and fix) data sync configuration
ob_start();

View File

@@ -113,11 +113,6 @@ class CMDBSourceTest extends ItopTestCase
"ENUM('value 1 (with parenthesis)','value 2') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
"enum('value 1 (with parenthesis)','value 3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
),
'ENUM with different values, containing parenthesis' => array(
false,
"ENUM('value 1 ) with parenthesis)','value 2') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
"enum('value 1 (with parenthesis)','value 3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
),
);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use CMDBSource;
use Exception;
use IssueLog;
use MySQLException;
use utils;
class DeadLockInjection
{
private $bMustFail = false;
private $iRequestCount = 0;
private $iFailAt = 0;
private $bShowRequest = true;
/**
*
* @param int $iFailAt
*/
public function SetFailAt($iFailAt)
{
$this->bMustFail = true;
$this->iRequestCount = 0;
$this->iFailAt = $iFailAt;
$this->bShowRequest = true;
}
/**
* @param bool $bShowRequest
*/
public function SetShowRequest($bShowRequest)
{
$this->bShowRequest = $bShowRequest;
}
public function query($sSQL)
{
if (utils::StartsWith($sSQL, "SELECT")) {
return;
}
if ($this->bShowRequest) {
$sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSQL, 0, 180)), 0, 150);
echo "$sShortSQL\n";
}
if (CMDBSource::IsInsideTransaction() && $this->bMustFail) {
$this->iRequestCount++;
if ($this->iRequestCount == $this->iFailAt) {
echo "Generating a FAKE DEADLOCK\n";
IssueLog::Trace("Generating a FAKE DEADLOCK", 'cmdbsource');
throw new MySQLException("FAKE DEADLOCK", [], new Exception("FAKE DEADLOCK", 1213));
}
}
}
}

View File

@@ -0,0 +1,248 @@
<?php
/**
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use CMDBSource;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use Exception;
use MetaModel;
/**
*
* @group itopRequestMgmt
* Class TransactionsTest
*
* @package Combodo\iTop\Test\UnitTest\Core
*/
class TransactionsTest extends ItopTestCase
{
/** @var DeadLockInjection */
private $oMySQLiMock;
protected function setUp()
{
parent::setUp();
require_once ('DeadLockInjection.php');
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
$sEnv = 'production';
$sConfigFile = APPCONF.$sEnv.'/config-itop.php';
MetaModel::Startup($sConfigFile, false, true, false, $sEnv);
$oInitialMysqli = CMDBSource::GetMysqli();
$this->oMySQLiMock = new DeadLockInjection();
$oMockMysqli = $this->getMockBuilder('mysqli')
->setMethods(['query'])
->getMock();
$oMockMysqli->expects($this->any())
->method('query')
->will($this->returnCallback(
function ($sSql) use ($oInitialMysqli) {
$this->oMySQLiMock->query($sSql);
return $oInitialMysqli->query($sSql);
}
));
$this->InvokeNonPublicStaticMethod('CMDBSource', 'SetMySQLiForQuery', [$oMockMysqli]);
}
/**
* Test DBInsertNoReload database transaction by provoking deadlock exceptions
*
* @dataProvider DBInsertProvider
*
* @param int $iFailAt Specify the request occurrence that fails
* @param bool $bIsInDB Indicates if the object must have been created or not
*
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function testDBInsert($iFailAt, $bIsInDB)
{
// Create a UserRequest with 2 contacts
$oTicket = MetaModel::NewObject('UserRequest', [
'ref' => 'Test Ticket',
'title' => 'Create OK',
'description' => 'Create OK',
'caller_id' => 15,
'org_id' => 3,
]);
$oLinkSet = $oTicket->Get('contacts_list');
$oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 6]));
$oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 7]));
$this->oMySQLiMock->SetFailAt($iFailAt);
$this->debug("---> DBInsert()");
try {
$oTicket->DBWrite();
}
catch (Exception $e) {
// If an exception occurs must be a deadlock
$this->assertTrue(CMDBSource::IsDeadlockException($e), $e->getMessage());
}
// Verify if the ticket is considered as saved in the database
$this->assertEquals($bIsInDB, !$oTicket->IsNew());
if (!$oTicket->IsNew()) {
$this->oMySQLiMock->SetShowRequest(false);
// Delete created objects
$oTicket->DBDelete();
}
}
public function DBInsertProvider()
{
return [
"Normal case" => ['iFailAt' => -1, 'bIsInDB' => true],
"ticket" => ['iFailAt' => 1, 'bIsInDB' => false],
"ticket_request" => ['iFailAt' => 2, 'bIsInDB' => false],
"priv_change" => ['iFailAt' => 3, 'bIsInDB' => false],
"priv_changeop" => ['iFailAt' => 4, 'bIsInDB' => false],
"priv_changeop_create" => ['iFailAt' => 5, 'bIsInDB' => false],
"History 4" => ['iFailAt' => 6, 'bIsInDB' => false],
"History 5" => ['iFailAt' => 7, 'bIsInDB' => false],
"History 6" => ['iFailAt' => 8, 'bIsInDB' => false],
"History 7" => ['iFailAt' => 9, 'bIsInDB' => false],
"History 8" => ['iFailAt' => 10, 'bIsInDB' => false],
"History 9" => ['iFailAt' => 11, 'bIsInDB' => false],
"History 10" => ['iFailAt' => 12, 'bIsInDB' => false],
"History 11" => ['iFailAt' => 13, 'bIsInDB' => false],
"History 12" => ['iFailAt' => 14, 'bIsInDB' => false],
"History 13" => ['iFailAt' => 15, 'bIsInDB' => false],
"History 14" => ['iFailAt' => 16, 'bIsInDB' => false],
"History 15" => ['iFailAt' => 17, 'bIsInDB' => false],
// For 3.0 when CRUD sequence is ok
// "History 16" => ['iFailAt' => 18, 'bIsInDB' => false],
// "History 17" => ['iFailAt' => 19, 'bIsInDB' => false],
// "History 18" => ['iFailAt' => 20, 'bIsInDB' => false],
// "History 19" => ['iFailAt' => 21, 'bIsInDB' => false],
// "History 20" => ['iFailAt' => 22, 'bIsInDB' => false],
// "History 21" => ['iFailAt' => 23, 'bIsInDB' => false],
// "History 22" => ['iFailAt' => 24, 'bIsInDB' => false],
// "History 23" => ['iFailAt' => 25, 'bIsInDB' => false],
// "History 24" => ['iFailAt' => 26, 'bIsInDB' => false],
// "History 25" => ['iFailAt' => 27, 'bIsInDB' => false],
// "History 26" => ['iFailAt' => 28, 'bIsInDB' => false],
// "History 27" => ['iFailAt' => 29, 'bIsInDB' => false],
// "History 28" => ['iFailAt' => 30, 'bIsInDB' => false],
// "History 29" => ['iFailAt' => 31, 'bIsInDB' => false],
// "History 30" => ['iFailAt' => 32, 'bIsInDB' => false],
// "History 31" => ['iFailAt' => 33, 'bIsInDB' => false],
// "History 32" => ['iFailAt' => 34, 'bIsInDB' => false],
// "History 33" => ['iFailAt' => 35, 'bIsInDB' => false],
// "History 34" => ['iFailAt' => 36, 'bIsInDB' => false],
// "History 35" => ['iFailAt' => 37, 'bIsInDB' => false],
// "History 36" => ['iFailAt' => 38, 'bIsInDB' => false],
// "History 37" => ['iFailAt' => 39, 'bIsInDB' => false],
// "History 38" => ['iFailAt' => 40, 'bIsInDB' => false],
];
}
/**
* Test DBUpdate database transaction by provoking deadlock exceptions
*
* @dataProvider DBUpdateProvider
* @param $iFailAt
*
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function testDBUpdate($iFailAt, $bIsModified)
{
// Create a UserRequest into the database with 2 contacts
$oTicket = MetaModel::NewObject('UserRequest', [
'ref' => 'Test Ticket',
'title' => 'Create OK',
'description' => 'Create OK',
'solution' => 'Create OK',
'caller_id' => 15,
'org_id' => 3,
]);
$oLinkSet = $oTicket->Get('contacts_list');
$oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 6]));
$oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 7]));
//$oTicket->Set('contacts_list', $oLinkSet);
$this->oMySQLiMock->SetShowRequest(false);
$oTicket->DBWrite();
// Verify that the object is considered as saved in the database
$this->assertEquals(true, !$oTicket->IsNew());
// Reload from db
$oTicket = MetaModel::GetObject('UserRequest', $oTicket->GetKey());
$oTicket->Set('description', 'Update OK');
$oTicket->Set('solution', 'Test OK');
$oLinkSet = $oTicket->Get('contacts_list');
$oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 8]));
$oTicket->Set('contacts_list', $oLinkSet);
// Provoke an error during the update
$this->oMySQLiMock->SetFailAt($iFailAt);
$this->debug("---> DBUpdate()");
try {
$oTicket->DBWrite();
}
catch (Exception $e) {
// If an exception occurs must be a deadlock
$this->assertTrue(CMDBSource::IsDeadlockException($e));
}
// Verify if the ticket is considered as saved in the database
$this->assertEquals($bIsModified, $oTicket->IsModified());
// Reload from db after the update to check the value present in the database
$oTicket = MetaModel::GetObject('UserRequest', $oTicket->GetKey());
if ($bIsModified) {
$this->assertEquals('Create OK', $oTicket->Get('solution'));
} else {
$this->assertEquals('Test OK', $oTicket->Get('solution'));
}
if (!$oTicket->IsNew()) {
$this->oMySQLiMock->SetShowRequest(false);
// Delete created objects
$oTicket->DBDelete();
}
}
public function DBUpdateProvider()
{
return [
"Normal case" => ['iFailAt' => -1, 'bIsModified' => false],
"ticket_request" => ['iFailAt' => 1, 'bIsModified' => true],
"lnkcontacttoticket" => ['iFailAt' => 2, 'bIsModified' => true],
"History 1" => ['iFailAt' => 3, 'bIsModified' => true],
"History 2" => ['iFailAt' => 4, 'bIsModified' => true],
"History 3" => ['iFailAt' => 5, 'bIsModified' => true],
"History 4" => ['iFailAt' => 6, 'bIsModified' => true],
"History 5" => ['iFailAt' => 7, 'bIsModified' => true],
"History 6" => ['iFailAt' => 8, 'bIsModified' => true],
"History 7" => ['iFailAt' => 9, 'bIsModified' => true],
"History 8" => ['iFailAt' => 10, 'bIsModified' => true],
"History 9" => ['iFailAt' => 11, 'bIsModified' => true],
"History 10" => ['iFailAt' => 12, 'bIsModified' => true],
"History 11" => ['iFailAt' => 13, 'bIsModified' => true],
"History 12" => ['iFailAt' => 14, 'bIsModified' => true],
"History 13" => ['iFailAt' => 15, 'bIsModified' => true],
];
}
}