RequireOnceItopFile('setup/modelfactory.class.inc.php');
}
/**
* @param $sInitialXML
*
* @return \ModelFactory
* @throws \Exception
*/
protected function MakeVanillaModelFactory($sInitialXML): ModelFactory
{
/* @var MFDocument $oFactoryRoot */
$oFactory = new ModelFactory([]);
$oInitialDocument = new MFDocument();
$oInitialDocument->preserveWhiteSpace = false;
$oInitialDocument->loadXML($sInitialXML);
$this->SetNonPublicProperty($oFactory, 'oDOMDocument', $oInitialDocument);
$this->SetNonPublicProperty($oFactory, 'oRoot', $oInitialDocument->firstChild);
return $oFactory;
}
/**
* @param $sXML
*
* @return false|string
*/
protected function CanonicalizeXML($sXML)
{
// Canonicalize the expected XML (to cope with indentation)
$oExpectedDocument = new DOMDocument();
$oExpectedDocument->preserveWhiteSpace = false;
$oExpectedDocument->formatOutput = true;
$oExpectedDocument->loadXML($sXML);
$sSavedXML = $oExpectedDocument->SaveXML();
return str_replace(' encoding="UTF-8"', '', $sSavedXML);
}
/**
* @param $sExpected
* @param $sActual
* @param string $sMessage
*/
protected function AssertEqualiTopXML($sExpected, $sActual, string $sMessage = '')
{
// Note: assertEquals reports the differences in a diff which is easier to interpret (in PHPStorm)
// as compared to the report given by assertEqualXMLStructure
static::assertEquals($this->CanonicalizeXML($sExpected), $this->CanonicalizeXML($sActual), $sMessage);
}
/**
* Assertion ignoring some of the unexpected decoration brought by DOM Elements.
*/
protected function AssertEqualModels(string $sExpectedXML, ModelFactory $oFactory, $sMessage = '')
{
// constants aren't accessible in the data provider :(
$sExpectedXML = str_replace('##ITOP_DESIGN_LATEST_VERSION##', ITOP_DESIGN_LATEST_VERSION, $sExpectedXML);
$this->AssertEqualiTopXML($sExpectedXML, $oFactory->Dump(null, true), $sMessage);
}
/**
* @test
* @dataProvider GetPreviousCommentProvider
* @covers ModelFactory::GetPreviousComment
*
* @param $sDeltaXML
* @param $sClassName
* @param $sExpectedComment
*
* @return void
* @throws \Exception
*/
public function GetPreviousCommentTest($sDeltaXML, $sClassName, $sExpectedComment)
{
$oDocument = new MFDocument();
$oDocument->loadXML($sDeltaXML);
$oXPath = new \DOMXPath($oDocument);
$sClassName = DesignDocument::XPathQuote($sClassName);
/** @var MFElement $oClassNode */
$oClassNode = $oXPath->query("/itop_design/classes/class[@id=$sClassName]")->item(0);
/** @var \DOMComment|null $oCommentNode */
$oCommentNode = ModelFactory::GetPreviousComment($oClassNode);
if (is_null($sExpectedComment)) {
$this->assertNull($oCommentNode);
} else {
$this->assertEquals($sExpectedComment, $oCommentNode->textContent);
}
}
public function GetPreviousCommentProvider()
{
$aData = [];
$aData['No Comment first Class'] = [
'sDeltaXML' => '
',
'sClassName' => 'A',
'sExpectedComment' => null,
];
$aData['No Comment other Class'] = [
'sDeltaXML' => '
',
'sClassName' => 'A',
'sExpectedComment' => null,
];
$aData['Comment first class'] = [
'sDeltaXML' => '
',
'sClassName' => 'A',
'sExpectedComment' => ' Test comment ',
];
$aData['Comment other Class'] = [
'sDeltaXML' => '
',
'sClassName' => 'A',
'sExpectedComment' => ' Test comment ',
];
return $aData;
}
/**
* @test
* @dataProvider FlattenDeltaProvider
* @covers ModelFactory::FlattenClassesInDelta
*
* @param $sDeltaXML
* @param $sExpectedXML
*
* @return void
* @throws \ReflectionException
*/
public function FlattenDeltaTest($sDeltaXML, $sExpectedXML)
{
$oFactory = new ModelFactory([]);
$oDocument = new MFDocument();
$oDocument->loadXML($sDeltaXML);
/* @var MFElement $oDeltaRoot */
$oDeltaRoot = $oDocument->firstChild;
/** @var MFElement $oFlattenDeltaRoot */
if (is_null($sExpectedXML)) {
$this->expectException(\MFException::class);
}
$oFlattenDeltaRoot = $this->InvokeNonPublicMethod(ModelFactory::class, 'FlattenClassesInDelta', $oFactory, [$oDeltaRoot]);
if (!is_null($sExpectedXML)) {
$this->AssertEqualiTopXML($sExpectedXML, $oFlattenDeltaRoot->ownerDocument->saveXML());
}
}
public function FlattenDeltaProvider()
{
return [
'Empty delta' => [
'sDeltaXML' => '
',
'sExpectedXML' => '
',
],
'Flat delete' => [
'sDeltaXML' => '
',
'sExpectedXML' => '
',
],
'flat define root' => [
'sDeltaXML' => '
cmdbAbstractObject
cmdbAbstractObject
',
'sExpectedXML' => '
cmdbAbstractObject
cmdbAbstractObject
',
],
'flat force root' => [
'sDeltaXML' => '
cmdbAbstractObject
cmdbAbstractObject
',
'sExpectedXML' => '
cmdbAbstractObject
cmdbAbstractObject
',
],
'flat redefine root' => [
'sDeltaXML' => '
cmdbAbstractObject
cmdbAbstractObject
',
'sExpectedXML' => '
cmdbAbstractObject
cmdbAbstractObject
',
],
'Simple hierarchy define root' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
',
'sExpectedXML' => '
cmdbAbstractObject
C_1
',
],
'Complex hierarchy delete' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
C_1_1
C_1
',
'sExpectedXML' => '
cmdbAbstractObject
C_1
C_1_1
C_1
',
],
'Complex hierarchy define root' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
C_1_1
C_1_1_1
C_1
C_1_2
',
'sExpectedXML' => '
cmdbAbstractObject
C_1
C_1_1
C_1_1_1
C_1
C_1_2
',
],
'Complex hierarchy define' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
C_1_1
C_1_1_1
C_1
C_1_2
',
'sExpectedXML' => '
cmdbAbstractObject
C_1
C_1_1
C_1_1_1
C_1
C_1_2
',
],
'Complex hierarchy force' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
C_1_1
',
'sExpectedXML' => '
cmdbAbstractObject
C_1
C_1_1
',
],
'Complex hierarchy force root' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
C_1_1
',
'sExpectedXML' => '
cmdbAbstractObject
C_1
C_1_1
',
],
'Complex hierarchy redefine' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
C_1_1
',
'sExpectedXML' => '
cmdbAbstractObject
C_1
C_1_1
',
],
'Complex hierarchy redefine root' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
',
'sExpectedXML' => '
cmdbAbstractObject
C_1
',
],
'Complex hierarchy define_if_not_exists flattening generates an error' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
C_1_1
',
'sExpectedXML' => null,
],
'Complex hierarchy if_exists flattening generates an error' => [
'sDeltaXML' => '
cmdbAbstractObject
C_1
C_1_1
',
'sExpectedXML' => null,
],
];
}
/**
* @test
* @dataProvider LoadDeltaProvider
* @covers ModelFactory::LoadDelta
*
* @param $sInitialXML
* @param $sDeltaXML
* @param $sExpectedXML
*
* @return void
* @throws \Exception
*/
public function LoadDeltaTest($sInitialXML, $sDeltaXML, $sExpectedXMLOrErrorMessage)
{
$oFactory = $this->MakeVanillaModelFactory($sInitialXML);
$oFactoryDocument = $this->GetNonPublicProperty($oFactory, 'oDOMDocument');
$sExpectedXML = null;
if (\utils::StartsWith(trim($sExpectedXMLOrErrorMessage), '<')) {
$sExpectedXML = $sExpectedXMLOrErrorMessage;
}
// Load the delta
$oDocument = new MFDocument();
$oDocument->loadXML($sDeltaXML);
/* @var MFElement $oDeltaRoot */
$oDeltaRoot = $oDocument->firstChild;
try {
$oFactory->LoadDelta($oDeltaRoot, $oFactoryDocument);
}
catch (\Exception $e) {
$this->assertNull($sExpectedXML, 'LoadDelta() should not have failed with exception: '.$e->getMessage());
$this->assertEquals($sExpectedXMLOrErrorMessage, $e->getMessage());
return;
}
$this->AssertEqualModels($sExpectedXML, $oFactory, 'LoadDelta() did not produce the expected result');
}
public function LoadDeltaProvider()
{
return [
'empty delta' => [
'sInitialXML' => '
',
'sDeltaXML' => '',
'sExpectedXMLOrErrorMessage' => '
',
],
'merge delta lax mode' => [
'sInitialXML' => '
',
'sDeltaXML' => '
cmdbAbstractObject
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
',
],
'Add a class' => [
'sInitialXML' => '
',
'sDeltaXML' => '
cmdbAbstractObject
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
',
],
'Add a class if not exists (N°6660)' => [
'sInitialXML' => '
',
'sDeltaXML' => '
cmdbAbstractObject
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
',
],
'Conditionally add a class but it already exists' => [
'sInitialXML' => '
cmdbAbstractObject
',
'sDeltaXML' => '
toto
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
',
],
'Add a class and subclass in hierarchy' => [
'sInitialXML' => '
',
'sDeltaXML' => '
cmdbAbstractObject
C_1
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
C_1
',
],
'Delete a class' => [
'sInitialXML' => '
cmdbAbstractObject
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Redefine a recently added subclass' => [
'sInitialXML' => '
cmdbAbstractObject
C_1
',
'sDeltaXML' => '
C_1
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
C_1
',
],
'Delete a recently added class' => [
'sInitialXML' => '
cmdbAbstractObject
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Delete hierarchically a class' => [
'sInitialXML' => '
cmdbAbstractObject
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Delete hierarchically a class and subclass' => [
'sInitialXML' => '
cmdbAbstractObject
C_1
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Delete hierarchically a class and subclass already deleted' => [
'sInitialXML' => '
cmdbAbstractObject
C_1
C_1
C_1_2
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Delete if exist hierarchically an existing class' => [
'sInitialXML' => '
cmdbAbstractObject
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Delete if exist hierarchically an non existing class' => [
'sInitialXML' => '
cmdbAbstractObject
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
',
],
'Delete if exist hierarchically a removed class' => [
'sInitialXML' => '
cmdbAbstractObject
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Delete if exist hierarchically an existing class and subclass' => [
'sInitialXML' => '
cmdbAbstractObject
C_1
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Delete if exist hierarchically a non existing subclass' => [
'sInitialXML' => '
cmdbAbstractObject
C_1
',
'sDeltaXML' => '
',
'sExpectedXMLOrErrorMessage' => '
',
],
'Class comment should be preserved' => [
'sInitialXML' => '
',
'sDeltaXML' => '
cmdbAbstractObject
cmdbAbstractObject
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
cmdbAbstractObject
',
],
'Delete hierarchically a class and add it again' => [
'sInitialXML' => '
cmdbAbstractObject
C_1
C_1_1
',
'sDeltaXML' => '
cmdbAbstractObject
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
',
],
'merge delta strict' => [
'sInitialXML' => '
',
'sDeltaXML' => '
cmdbAbstractObject
',
'sExpectedXMLOrErrorMessage' => '/itop_design/classes/class[C_1] at line 3: could not be found or marked as removed (strict mode)',
],
'Redefine classes and changing parent' => [
'sInitialXML' => '
cmdbAbstractObject
cmdbAbstractObject
Licence
',
'sDeltaXML' => '
FunctionalCI
ConfigElement
Key
Key
',
'sExpectedXMLOrErrorMessage' => '
cmdbAbstractObject
FunctionalCI
ConfigElement
Key
Key
',
],
'Class with wrong parent should generate an error' => [
'sInitialXML' => '
cmdbAbstractObject
',
'sDeltaXML' => '
toto
',
'sExpectedXMLOrErrorMessage' => "/itop_design/classes/class[C_1]/parent at line 4: invalid parent class 'toto'",
],
];
}
/**
* @test
* @dataProvider AlterationByXMLDeltaProvider
* @covers ModelFactory::LoadDelta
* @covers ModelFactory::ApplyChanges
*/
public function AlterationByXMLDeltaTest($sInitialXML, $sDeltaXML, $sExpectedXML)
{
$oFactory = $this->MakeVanillaModelFactory($sInitialXML);
$oFactoryRoot = $this->GetNonPublicProperty($oFactory, 'oDOMDocument');
$oDocument = new MFDocument();
$oDocument->loadXML($sDeltaXML);
/* @var MFElement $oDeltaRoot */
$oDeltaRoot = $oDocument->firstChild;
if ($sExpectedXML === null) {
$this->expectException('Exception');
}
$oFactory->LoadDelta($oDeltaRoot, $oFactoryRoot);
$oFactory->ApplyChanges();
$this->AssertEqualModels($sExpectedXML, $oFactory);
}
/**
* @return array
*/
public function AlterationByXMLDeltaProvider()
{
// Basic (structure)
return [
'No change at all' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'No change at all - mini delta' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="merge" implicit' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="merge" explicit (lax)' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="merge" does preserve text in lax mode' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
Maintained Text
XML
,
'sExpectedXML' => <<
Maintained Text
XML
,
],
'_delta="merge" recursively' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
// Define or redefine
'_delta="define" without id' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="define" with id' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="define" but existing node' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => null,
],
'_delta="redefine" without id' => [
'sInitialXML' => <<
Initial BB
XML
,
'sDeltaXML' => <<
Gainsbourg
XML
,
'sExpectedXML' => <<
Gainsbourg
XML
,
],
'_delta="redefine" with id' => [
'sInitialXML' => <<
- Initial BB
XML
,
'sDeltaXML' => <<
- Gainsbourg
XML
,
'sExpectedXML' => <<
- Gainsbourg
XML
,
],
'_delta="redefine" but missing node' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
- Gainsbourg
XML
,
'sExpectedXML' => null,
],
'_delta="force" without id + missing node' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
Hulk
XML
,
'sExpectedXML' => <<
Hulk
XML
,
],
'_delta="force" with id + missing node' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
- Hulk
XML
,
'sExpectedXML' => <<
- Hulk
XML
,
],
'_delta="force" without id + existing node' => [
'sInitialXML' => <<
Initial BB
XML
,
'sDeltaXML' => <<
Gainsbourg
XML
,
'sExpectedXML' => <<
Gainsbourg
XML
,
],
'_delta="force" with id + existing node' => [
'sInitialXML' => <<
- Initial BB
XML
,
'sDeltaXML' => <<
- Gainsbourg
XML
,
'sExpectedXML' => <<
- Gainsbourg
XML
,
],
// Rename
'rename' => [
'sInitialXML' => <<
- Kryptonite
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
- Kryptonite
XML
,
],
'rename but missing node NOT INTUITIVE!!!' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
// Delete
'_delta="delete" without id' => [
'sInitialXML' => <<
Initial BB
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="delete" with id' => [
'sInitialXML' => <<
- Initial BB
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="delete" but missing node' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => null,
],
'_delta="delete_if_exists" without id + existing node' => [
'sInitialXML' => <<
Initial BB
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => '',
],
'_delta="delete_if_exists" with id + existing node' => [
'sInitialXML' => <<
- Initial BB
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => '',
],
'_delta="delete_if_exists" without id + missing node' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="delete_if_exists" with id + missing node' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
// Conditionals
'_delta="must_exist"' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="must_exist on missing node"' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => null,
],
'_delta="if_exists on missing node (lax)' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="if_exists on missing node (strict)' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="if_exists on existing node"' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="define_if_not_exists on missing node"' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
The incredible Hulk
XML
,
'sExpectedXML' => <<
The incredible Hulk
XML
,
],
'_delta="define_if_not_exists on existing node"' => [
'sInitialXML' => <<
Luke Banner
XML
,
'sDeltaXML' => <<
The incredible Hulk
XML
,
'sExpectedXML' => <<
Luke Banner
XML
,
],
'_delta="define_and_must_exits"' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'_delta="define_then_must_exist"' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'nested _delta should be cleaned' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
'Class comments are stripped when class is deleted' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXML' => '
',
],
'Class comments are preserved' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXML' => '
',
],
'if_exist on removed node does nothing' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXML' => '',
],
'if_exist on missing node does nothing' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXML' => '',
],
'if_exist on existing node merges' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXML' => '
',
],
'must_exist on removed node does error' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXML' => null,
],
'must_exist on missing node does error' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXML' => null,
],
'must_exist on existing node merges' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXML' => '
',
],
'N°8440 Quotes are allowed in ids' => [
'sInitialXML' => <<
XML
,
'sDeltaXML' => <<
XML
,
'sExpectedXML' => <<
XML
,
],
];
}
/**
* @test
* @dataProvider AlterationAPIsProvider
* @covers \ModelFactory::GetDelta
* @covers \MFElement::AddChildNode
* @covers \MFElement::RedefineChildNode
* @covers \MFElement::SetChildNode
* @covers \MFElement::Delete
* @throws \MFException
*/
public function AlterationsByAPIsTest($sInitialXML, $sOperation, $sExpectedXML)
{
$oFactory = $this->MakeVanillaModelFactory($sInitialXML);
if ($sExpectedXML === null) {
$this->expectException('Exception');
}
switch ($sOperation) {
case 'Delete':
/* @var MFElement $oTargetNode */
$oTargetNode = $oFactory->GetNodes('//target_tag', null, false)->item(0);
$oTargetNode->Delete();
break;
case 'AddChildNodeToContainer':
$oContainerNode = $oFactory->GetNodes('//container_tag', null, false)->item(0);
$oFactoryRoot = $this->GetNonPublicProperty($oFactory, 'oDOMDocument');
$oChild = $oFactoryRoot->CreateElement('target_tag', 'Hello, I\'m a newly added node');
/* @var MFElement $oContainerNode */
$oContainerNode->AddChildNode($oChild);
break;
case 'RedefineChildNodeToContainer':
$oContainerNode = $oFactory->GetNodes('//container_tag', null, false)->item(0);
$oFactoryRoot = $this->GetNonPublicProperty($oFactory, 'oDOMDocument');
$oChild = $oFactoryRoot->CreateElement('target_tag', 'Hello, I\'m replacing the previous node');
/* @var MFElement $oContainerNode */
$oContainerNode->RedefineChildNode($oChild);
break;
case 'SetChildNodeToContainer':
$oContainerNode = $oFactory->GetNodes('//container_tag', null, false)->item(0);
$oFactoryRoot = $this->GetNonPublicProperty($oFactory, 'oDOMDocument');
$oChild = $oFactoryRoot->CreateElement('target_tag', 'Hello, I\'m replacing the previous node');
/* @var MFElement $oContainerNode */
$oContainerNode->SetChildNode($oChild);
break;
default:
static::fail("Unknown operation '$sOperation'");
}
if ($sExpectedXML !== null) {
$this->AssertEqualModels($sExpectedXML, $oFactory);
}
}
/**
* @return array[]
*/
public function AlterationAPIsProvider()
{
define('CASE_NO_FLAG', <<
XML
);
define('CASE_ABOVE_A_FLAG', <<
Blah
XML
);
define('CASE_IN_A_DEFINITION', <<
Blah
XML
);
define('CASE_FLAG_ON_TARGET_define', <<
XML
);
define('CASE_FLAG_ON_TARGET_redefine', <<
XML
);
define('CASE_FLAG_ON_TARGET_needed', <<
XML
);
define('CASE_FLAG_ON_TARGET_forced', <<
XML
);
define('CASE_FLAG_ON_TARGET_removed', <<
XML
);
define('CASE_FLAG_ON_TARGET_old_id', <<
XML
);
define('CASE_MISSING_TARGET', <<
XML
);
$aData = [
'CASE_NO_FLAG Delete' => [
CASE_NO_FLAG,
'Delete',
<<
XML
,
],
'CASE_ABOVE_A_FLAG Delete' => [
CASE_ABOVE_A_FLAG,
'Delete',
<<
XML
,
],
'CASE_IN_A_DEFINITION Delete' => [
CASE_IN_A_DEFINITION,
'Delete',
<<
XML
,
],
'CASE_FLAG_ON_TARGET_define Delete' => [
CASE_FLAG_ON_TARGET_define,
'Delete',
<<
XML
,
],
'CASE_FLAG_ON_TARGET_redefine Delete' => [
CASE_FLAG_ON_TARGET_redefine,
'Delete',
<<
XML
,
],
'CASE_FLAG_ON_TARGET_needed Delete' => [
CASE_FLAG_ON_TARGET_needed,
'Delete',
<<
XML
,
],
'CASE_FLAG_ON_TARGET_forced Delete' => [
CASE_FLAG_ON_TARGET_forced,
'Delete',
<<
XML
,
],
'CASE_FLAG_ON_TARGET_removed Delete' => [
CASE_FLAG_ON_TARGET_removed,
'Delete',
null,
],
'CASE_FLAG_ON_TARGET_old_id Delete' => [
CASE_FLAG_ON_TARGET_old_id,
'Delete',
<<
XML
,
],
'CASE_NO_FLAG AddChildNode' => [
CASE_NO_FLAG,
'AddChildNodeToContainer',
null,
],
'CASE_ABOVE_A_FLAG AddChildNode' => [
CASE_ABOVE_A_FLAG,
'AddChildNodeToContainer',
null,
],
'CASE_IN_A_DEFINITION AddChildNode' => [
CASE_IN_A_DEFINITION,
'AddChildNodeToContainer',
null,
],
'CASE_FLAG_ON_TARGET_define AddChildNode' => [
CASE_FLAG_ON_TARGET_define,
'AddChildNodeToContainer',
null,
],
'CASE_FLAG_ON_TARGET_redefine AddChildNode' => [
CASE_FLAG_ON_TARGET_redefine,
'AddChildNodeToContainer',
null,
],
'CASE_FLAG_ON_TARGET_needed AddChildNode' => [
CASE_FLAG_ON_TARGET_needed,
'AddChildNodeToContainer',
null,
],
'CASE_FLAG_ON_TARGET_forced AddChildNode' => [
CASE_FLAG_ON_TARGET_forced,
'AddChildNodeToContainer',
null,
],
'CASE_FLAG_ON_TARGET_removed AddChildNode' => [
CASE_FLAG_ON_TARGET_removed,
'AddChildNodeToContainer',
<<
Hello, I'm a newly added node
XML
,
],
'CASE_FLAG_ON_TARGET_old_id AddChildNode' => [
CASE_FLAG_ON_TARGET_old_id,
'AddChildNodeToContainer',
null,
],
'CASE_MISSING_TARGET AddChildNode' => [
CASE_MISSING_TARGET,
'AddChildNodeToContainer',
<<
Hello, I'm a newly added node
XML
,
],
'CASE_NO_FLAG RedefineChildNode' => [
CASE_NO_FLAG,
'RedefineChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_ABOVE_A_FLAG RedefineChildNode' => [
CASE_ABOVE_A_FLAG,
'RedefineChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_IN_A_DEFINITION RedefineChildNode' => [
CASE_IN_A_DEFINITION,
'RedefineChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_define RedefineChildNode' => [
CASE_FLAG_ON_TARGET_define,
'RedefineChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_redefine RedefineChildNode' => [
CASE_FLAG_ON_TARGET_redefine,
'RedefineChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
// Note: buggy case ?
'CASE_FLAG_ON_TARGET_needed RedefineChildNode' => [
CASE_FLAG_ON_TARGET_needed,
'RedefineChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_forced RedefineChildNode' => [
CASE_FLAG_ON_TARGET_forced,
'RedefineChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_removed RedefineChildNode' => [
CASE_FLAG_ON_TARGET_removed,
'RedefineChildNodeToContainer',
null,
],
'CASE_FLAG_ON_TARGET_old_id RedefineChildNode' => [
CASE_FLAG_ON_TARGET_old_id,
'RedefineChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_MISSING_TARGET RedefineChildNode' => [
CASE_MISSING_TARGET,
'RedefineChildNodeToContainer',
null,
],
'CASE_NO_FLAG SetChildNode' => [
CASE_NO_FLAG,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_ABOVE_A_FLAG SetChildNode' => [
CASE_ABOVE_A_FLAG,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_IN_A_DEFINITION SetChildNode' => [
CASE_IN_A_DEFINITION,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_define SetChildNode' => [
CASE_FLAG_ON_TARGET_define,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_redefine SetChildNode' => [
CASE_FLAG_ON_TARGET_redefine,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
// Note: buggy case ?
'CASE_FLAG_ON_TARGET_needed SetChildNode' => [
CASE_FLAG_ON_TARGET_needed,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_forced SetChildNode' => [
CASE_FLAG_ON_TARGET_forced,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_removed SetChildNode' => [
CASE_FLAG_ON_TARGET_removed,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_FLAG_ON_TARGET_old_id SetChildNode' => [
CASE_FLAG_ON_TARGET_old_id,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
'CASE_MISSING_TARGET SetChildNode' => [
CASE_MISSING_TARGET,
'SetChildNodeToContainer',
<<
Hello, I'm replacing the previous node
XML
,
],
];
return $aData;
}
/**
* @test
* @covers \ModelFactory::LoadDelta
* @covers \ModelFactory::GetDelta
* @covers \ModelFactory::GetDeltaDocument
* @dataProvider GetDeltaProvider
*/
public function GetDeltaTest($sInitialXMLInternal, $sExpectedXMLDelta)
{
// constants aren't accessible in the data provider :(
$sExpectedXMLDelta = str_replace('##ITOP_DESIGN_LATEST_VERSION##', ITOP_DESIGN_LATEST_VERSION, $sExpectedXMLDelta);
$oFactory = $this->MakeVanillaModelFactory($sInitialXMLInternal);
// Get the delta back
$sNewDeltaXML = $oFactory->GetDelta();
static::AssertEqualiTopXML($sExpectedXMLDelta, $sNewDeltaXML);
}
/**
* @return array[]
*/
public function GetDeltaProvider()
{
return [
'no alteration' => [
'sInitialXMLInternal' => <<
Roger Moore
XML
,
// Weird, but seems ok as of now
'sExpectedXMLDelta' => <<
XML
,
],
'_alteration="added" singleton' => [
'sInitialXMLInternal' => <<
XML
,
'sExpectedXMLDelta' => <<
XML
,
],
'_alteration="added" with value' => [
'sInitialXMLInternal' => <<
Roger Moore
XML
,
'sExpectedXMLDelta' => <<
Roger Moore
XML
,
],
'_alteration="added" with subtree' => [
'sInitialXMLInternal' => <<
Moore
Roger
XML
,
'sExpectedXMLDelta' => <<
Moore
Roger
XML
,
],
'_alteration="forced" singleton' => [
'sInitialXMLInternal' => <<
XML
,
'sExpectedXMLDelta' => <<
XML
,
],
'_alteration="forced" with value' => [
'sInitialXMLInternal' => <<
Roger Moore
XML
,
'sExpectedXMLDelta' => <<
Roger Moore
XML
,
],
'_alteration="forced" with subtree' => [
'sInitialXMLInternal' => <<
Moore
Roger
XML
,
'sExpectedXMLDelta' => <<
Moore
Roger
XML
,
],
'_alteration="needed" singleton' => [
'sInitialXMLInternal' => <<
XML
,
'sExpectedXMLDelta' => <<
XML
,
],
'_alteration="needed" with value' => [
'sInitialXMLInternal' => <<
Roger Moore
XML
,
'sExpectedXMLDelta' => <<
Roger Moore
XML
,
],
'_alteration="needed" with subtree' => [
'sInitialXMLInternal' => <<
Moore
Roger
XML
,
'sExpectedXMLDelta' => <<
Moore
Roger
XML
,
],
'_alteration="replaced" with value' => [
'sInitialXMLInternal' => <<
Sean Connery
XML
,
'sExpectedXMLDelta' => <<
Sean Connery
XML
,
],
'_alteration="replaced" with subtree' => [
'sInitialXMLInternal' => <<
Sean
Connery
XML
,
'sExpectedXMLDelta' => <<
Sean
Connery
XML
,
],
'_alteration="removed"' => [
'sInitialXMLInternal' => <<
XML
,
'sExpectedXMLDelta' => <<
XML
,
],
'_alteration="remove_needed"' => [
'sInitialXMLInternal' => <<
XML
,
'sExpectedXMLDelta' => <<
XML
,
],
'_old_id' => [
'sInitialXMLInternal' => <<
XML
,
'sExpectedXMLDelta' => <<
XML
,
],
'_old_id with subtree' => [
'sInitialXMLInternal' => <<
etc.
XML
,
'sExpectedXMLDelta' => <<
etc.
XML
,
],
'Class Comments are not kept for created classes' => [
'sInitialXMLInternal' => <<
cmdbAbstractObject
XML
,
'sExpectedXMLDelta' => <<
cmdbAbstractObject
XML
,
],
'Class Comments are not preserved' => [
'sInitialXMLInternal' => <<
cmdbAbstractObject
cmdbAbstractObject
XML
,
'sExpectedXMLDelta' => <<
cmdbAbstractObject
cmdbAbstractObject
XML
,
],
'Conditionally deleted class' => [
'sInitialXMLInternal' => '
',
'sExpectedXMLDelta' => '
',
],
];
}
/**
* @param $aClasses
* @param \ModelFactory $oFactory
*
* @return void
* @throws \Exception
*/
private function CreateClasses($aClasses, ModelFactory $oFactory): void
{
foreach ($aClasses as $aClass) {
$sClassName = $aClass['name'];
$sModuleName = $aClass['module'];
$sParent = $aClass['parent'];
$oNode = $oFactory->CreateElement('class');
$oNode->setAttribute('id', $sClassName);
$oNode->setAttribute('_created_in', $sModuleName);
$oDoc = $oNode->ownerDocument;
foreach (array('properties', 'fields', 'methods', 'presentation') as $sElementName) {
$oElement = $oDoc->createElement($sElementName);
$oNode->appendChild($oElement);
}
$oParent = $oDoc->createElement('parent', $sParent);
$oNode->appendChild($oParent);
$oFactory->AddClass($oNode, $sModuleName);
}
}
/**
* @test
*
* @dataProvider AddClassProvider
* @return void
* @throws \Exception
*/
public function AddClassTest($aClasses, $sExpectedXML)
{
$oFactory = new ModelFactory([]);
$this->CreateClasses($aClasses, $oFactory);
$this->AssertEqualModels($sExpectedXML, $oFactory, 'The classes are added without hierarchy (not under an existing class)');
}
/**
* @test
* @dataProvider AddClassProvider
* @return void
* @throws \Exception
*/
public function ListRootClassesTest($aClasses, $sExpectedXML, $aExpectedRootClasses)
{
$oFactory = new ModelFactory([]);
$this->CreateClasses($aClasses, $oFactory);
$oRootClasses = $oFactory->ListRootClasses();
$aRootClasses = [];
/** @var MFElement $oRootClass */
foreach ($oRootClasses as $oRootClass) {
$aRootClasses[] = $oRootClass->getAttribute('id');
}
sort($aRootClasses);
$aDiff = array_diff($aExpectedRootClasses, $aRootClasses);
$this->assertCount(0, $aDiff);
}
/**
* @test
* @dataProvider AddClassProvider
* @return void
* @throws \Exception
*/
public function ClassNameExistsTest($aClasses, $sExpectedXML, $aExpectedRootClasses, $aExpectedClasses, $aExpectedClassNotExist)
{
$oFactory = new ModelFactory([]);
$this->CreateClasses($aClasses, $oFactory);
foreach ($aExpectedClasses as $sExpectedClassExist) {
$this->assertTrue($this->InvokeNonPublicMethod(ModelFactory::class, 'ClassNameExists', $oFactory, [$sExpectedClassExist]));
}
foreach ($aExpectedClassNotExist as $sExpectedClassNotExist) {
$this->assertFalse($this->InvokeNonPublicMethod(ModelFactory::class, 'ClassNameExists', $oFactory, [$sExpectedClassNotExist]));
}
}
/**
* @test
* @dataProvider AddClassProvider
* @return void
* @throws \Exception
*/
public function ListClassesTest($aClasses, $sExpectedXML, $aExpectedRootClasses, $aExpectedClasses, $aExpectedClassNotExist, $aExpectedClassesByModule)
{
$oFactory = new ModelFactory([]);
$this->CreateClasses($aClasses, $oFactory);
foreach ($aExpectedClassesByModule as $sModule => $aExpectedClasses) {
$oClasses = $oFactory->ListClasses($sModule);
$aFoundClasses = [];
/** @var MFElement $oClass */
foreach ($oClasses as $oClass) {
$aFoundClasses[] = $oClass->getAttribute('id');
}
sort($aFoundClasses);
$aDiff = array_diff($aExpectedClasses, $aFoundClasses);
$this->assertCount(0, $aDiff);
}
}
/**
* @test
* @dataProvider AddClassProvider
* @return void
* @throws \Exception
*/
public function ListAllClassesTest($aClasses, $sExpectedXML, $aExpectedRootClasses, $aExpectedClasses)
{
$oFactory = new ModelFactory([]);
$this->CreateClasses($aClasses, $oFactory);
$oClasses = $oFactory->ListAllClasses();
$aFoundClasses = [];
/** @var MFElement $oClass */
foreach ($oClasses as $oClass) {
$aFoundClasses[] = $oClass->getAttribute('id');
}
sort($aFoundClasses);
$aDiff = array_diff($aExpectedClasses, $aFoundClasses);
$this->assertCount(0, $aDiff);
}
/**
* @test
* @dataProvider AddClassProvider
* @return void
* @throws \Exception
*/
public function GetClassTest($aClasses, $sExpectedXML, $aExpectedRootClasses, $aExpectedClasses)
{
$oFactory = new ModelFactory([]);
$this->CreateClasses($aClasses, $oFactory);
foreach ($aExpectedClasses as $sClassName) {
$oClass = $oFactory->GetClass($sClassName);
$this->assertInstanceOf(\DOMNode::class, $oClass);
}
}
/**
* @test
* @dataProvider AddClassProvider
* @return void
* @throws \Exception
*/
public function GetChildClassesTest($aClasses, $sExpectedXML, $aExpectedRootClasses, $aExpectedClasses, $aExpectedClassNotExist, $aExpectedClassesByModule, $aExpectedChildClasses)
{
$oFactory = new ModelFactory([]);
$this->CreateClasses($aClasses, $oFactory);
foreach ($aExpectedChildClasses as $sClassName => $aChildClasses) {
$oClassNode = $oFactory->GetClass($sClassName);
$oClasses = $oFactory->GetChildClasses($oClassNode);
$aFoundClasses = [];
/** @var MFElement $oClass */
foreach ($oClasses as $oClass) {
$aFoundClasses[] = $oClass->getAttribute('id');
}
$aDiff = array_diff($aChildClasses, $aFoundClasses);
$sMessage = "Children of $sClassName awaited [".implode(', ', $aChildClasses)."] got [".implode(', ', $aFoundClasses)."]";
$this->assertCount(0, $aDiff, $sMessage);
}
$this->assertTrue(true);
}
/**
* @return array
*/
public function AddClassProvider()
{
$aClasses = [
"1 root class" => [
'aClasses' => [
['name' => 'A', 'module' => 'M', 'parent' => 'cmdbAbstractObject'],
],
'sExpectedXML' => '
cmdbAbstractObject
',
'aExpectedRootClasses' => [],
'aExpectedClasses' => ['A'],
'aExpectedClassNotExist' => ['B'],
'aExpectedClassesByModule' => ['M' => ['A']],
'aExpectedChildClasses' => ['A' => []],
],
'2 root classes' => [
'aClasses' => [
['name' => 'A', 'module' => 'M', 'parent' => 'cmdbAbstractObject'],
['name' => 'B', 'module' => 'M2', 'parent' => 'cmdbAbstractObject'],
],
'sExpectedXML' => '
cmdbAbstractObject
cmdbAbstractObject
',
'aExpectedRootClasses' => [],
'aExpectedClasses' => ['A', 'B'],
'aExpectedClassNotExist' => ['C'],
'aExpectedClassesByModule' => ['M' => ['A'], 'M2' => ['B']],
'aExpectedChildClasses' => ['A' => [], 'B' => []],
],
'2 hierarchical classes' => [
'aClasses' => [
['name' => 'A', 'module' => 'M', 'parent' => 'cmdbAbstractObject'],
['name' => 'B', 'module' => 'M2', 'parent' => 'A'],
],
'sExpectedXML' => '
cmdbAbstractObject
A
',
'aExpectedRootClasses' => ['A'],
'aExpectedClasses' => ['A', 'B'],
'aExpectedClassNotExist' => ['C'],
'aExpectedClassesByModule' => ['M' => ['A'], 'M2' => ['B']],
'aExpectedChildClasses' => ['A' => ['B'], 'B' => []],
],
'4 mixed classes' => [
'aClasses' => [
['name' => 'A', 'module' => 'M', 'parent' => 'cmdbAbstractObject'],
['name' => 'B', 'module' => 'M2', 'parent' => 'A'],
['name' => 'C', 'module' => 'M3', 'parent' => 'cmdbAbstractObject'],
['name' => 'D', 'module' => 'M3', 'parent' => 'B'],
],
'sExpectedXML' => '
cmdbAbstractObject
A
cmdbAbstractObject
B
',
'aExpectedRootClasses' => ['A'],
'aExpectedClasses' => ['A', 'B', 'C', 'D'],
'aExpectedClassNotExist' => ['E'],
'aExpectedClassesByModule' => ['M' => ['A'], 'M2' => ['B'], 'M3' => ['C', 'D']],
'aExpectedChildClasses' => ['A' => ['B'], 'B' => ['D'], 'C' => [], 'D' => []],
],
];
return $aClasses;
}
/**
* @test
* @dataProvider LoadDeltaModeProvider
*
* @param $sInitialXML
* @param $sDeltaXML
* @param $sMode
* @param $sExpectedXML
*
* @return void
* @throws \Exception
*/
public function LoadDeltaModeTest($sInitialXML, $sDeltaXML, $sExpectedXMLInLaxMode, $sExpectedXMLInStrictMode)
{
// Load in Lax mode
$oFactory = $this->MakeVanillaModelFactory($sInitialXML);
$oFactoryDocument = $this->GetNonPublicProperty($oFactory, 'oDOMDocument');
$oDocument = new MFDocument();
$oDocument->loadXML($sDeltaXML);
/* @var MFElement $oDeltaRoot */
$oDeltaRoot = $oDocument->firstChild;
$sExpectedXML = null;
if (\utils::StartsWith(trim($sExpectedXMLInLaxMode), '<')) {
$sExpectedXML = $sExpectedXMLInLaxMode;
}
try {
$oFactory->LoadDelta($oDeltaRoot, $oFactoryDocument, ModelFactory::LOAD_DELTA_MODE_LAX);
$this->assertNotNull($sExpectedXML, "LoadDelta(lax) should have failed with exception: $sExpectedXMLInLaxMode");
$this->AssertEqualModels($sExpectedXML, $oFactory, 'LoadDelta(lax) did not produce the expected result');
}
catch (ExpectationFailedException $e) {
throw $e;
}
catch (\Exception $e) {
$this->assertNull($sExpectedXML, 'LoadDelta(lax) should not have failed with exception: '.$e->getMessage());
$this->assertEquals($sExpectedXMLInLaxMode, $e->getMessage());
}
// Load in Strict mode
$oFactory = $this->MakeVanillaModelFactory($sInitialXML);
$oFactoryDocument = $this->GetNonPublicProperty($oFactory, 'oDOMDocument');
$oDocument = new MFDocument();
$oDocument->loadXML($sDeltaXML);
/* @var MFElement $oDeltaRoot */
$oDeltaRoot = $oDocument->firstChild;
$sExpectedXML = null;
if (\utils::StartsWith(trim($sExpectedXMLInStrictMode), '<')) {
$sExpectedXML = $sExpectedXMLInStrictMode;
}
try {
$oFactory->LoadDelta($oDeltaRoot, $oFactoryDocument, ModelFactory::LOAD_DELTA_MODE_STRICT);
$this->assertNotNull($sExpectedXML, "LoadDelta(lax) should have failed with exception: $sExpectedXMLInStrictMode");
$this->AssertEqualModels($sExpectedXML, $oFactory, 'LoadDelta(strict) did not produce the expected result');
}
catch (ExpectationFailedException $e) {
throw $e;
}
catch (\Exception $e) {
$this->assertNull($sExpectedXML, 'LoadDelta(strict) should not have failed with exception: '.$e->getMessage());
$this->assertEquals($sExpectedXMLInStrictMode, $e->getMessage());
}
}
public function LoadDeltaModeProvider()
{
return [
'default no _delta have different behavior depending on the mode' => [
'sInitialXML' => '
',
'sDeltaXML' => '
cmdbAbstractObject
',
'sExpectedXMLInLaxMode' => '
cmdbAbstractObject
',
'sExpectedXMLInStrictMode' => '/itop_design/nodeA/nodeB[C_1] at line 4: could not be found or marked as removed (strict mode)',
],
'mode specified in delta takes precedence' => [
'sInitialXML' => '
',
'sDeltaXML' => '
cmdbAbstractObject
',
'sExpectedXMLInLaxMode' => '/itop_design/nodeA/nodeB[C_1] at line 4: could not be found or marked as removed (strict mode)',
'sExpectedXMLInStrictMode' => '/itop_design/nodeA/nodeB[C_1] at line 4: could not be found or marked as removed (strict mode)',
],
'default no _delta leaf nodes have different behavior depending on the mode' => [
'sInitialXML' => '
Test
',
'sDeltaXML' => '
Taste
',
'sExpectedXMLInLaxMode' => '
Taste
',
'sExpectedXMLInStrictMode' => '/itop_design/nodeA at line 3: cannot be modified without _delta flag (strict mode)',
],
'default no _delta on existing leaf nodes without text have same behavior' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXMLInLaxMode' => '
',
'sExpectedXMLInStrictMode' => '
',
],
'default no _delta on non-existing leaf nodes without text have different behavior' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXMLInLaxMode' => '
',
'sExpectedXMLInStrictMode' => '/itop_design/nodeA at line 3: could not be found or marked as removed (strict mode)',
],
'default no _delta on non-existing nodes with sub-nodes defined' => [
'sInitialXML' => '
',
'sDeltaXML' => '
',
'sExpectedXMLInLaxMode' => '
',
'sExpectedXMLInStrictMode' => '
',
],
'merge _delta on non-existing node must create node' => [
'sInitialXML' => '
',
'sDeltaXML' => '
- Test
',
'sExpectedXMLInLaxMode' => '
- Test
',
'sExpectedXMLInStrictMode' => '
- Test
',
],
'merge _delta on existing tree must merge...' => [
'sInitialXML' => '
',
'sDeltaXML' => '
- Test
',
'sExpectedXMLInLaxMode' => '
- Test
',
'sExpectedXMLInStrictMode' => '
- Test
',
],
];
}
public function testDictEntryIsIntegratedIntoMF() {
$oFactory = new ModelFactory([]);
$sLanguageCode = 'RU URSS';
$oFactory->IntegrateDictEntriesIntoXML($sLanguageCode,
[
'english_description' => 'Russian',
'localized_description' => 'URSS',
'entries' => [
'key1' => 'Label 1',
],
]
);
$this->assertDictEntryUniqueAndEquals($oFactory, $sLanguageCode, 'key1', 'Label 1', 'New entry in new language should be present in XML');
$oFactory->IntegrateDictEntriesIntoXML($sLanguageCode,
[
'english_description' => 'Russian',
'localized_description' => 'URSS',
'entries' => [
'key1' => 'Label new',
'key2' => 'Label 😍',
],
]
);
$this->assertDictEntryUniqueAndEquals($oFactory, $sLanguageCode, 'key1', 'Label new', 'Existing entry should be overwritten by latest loaded dictionary');
$this->assertDictEntryUniqueAndEquals($oFactory, $sLanguageCode, 'key2', 'Label 😍', 'New entry in existing dictionary should be present in XML');
}
private function assertDictEntryUniqueAndEquals(ModelFactory $oFactory, string $sLanguageCode, string $sKey, string $sValue, string $sIndicationMessage)
{
$oNodes = $oFactory->GetNodes("/itop_design/dictionaries/dictionary[@id='$sLanguageCode']/entries/entry[@id='$sKey']");
$this->assertCount(1, $oNodes, "The dictionary entry $sKey should be found and unique");
$this->assertEquals($sValue, $oNodes[0]->textContent, $sIndicationMessage);
}
}