<?php

namespace Combodo\iTop\Test\UnitTest\Setup;

use Combodo\iTop\DesignDocument;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use DOMDocument;
use MFDocument;
use MFElement;
use ModelFactory;
use PHPUnit\Framework\ExpectationFailedException;


/**
 * Class ModelFactoryTest
 *
 * Test XML assembly, and in particular the following verbs
 *
 *                      ┌─────────────────┐
 *                      │                 │
 *     LoadDelta ──────►│  ModelFactory   │
 *                      │                 ├──►GetDelta
 *   ApplyChanges ─────►│   ┌──────────┐  │
 *                      ├───┤MFDocument├──┤
 *      Delete ────────►│   └──────────┘  │
 *    AddChildNode ────►│                 │
 * RedefineChildNode ──►│    MFElement    │
 *      Rename ────────►│                 │
 *    SetChildNode ────►│                 │
 *                      └─────────────────┘
 *
 * @covers ModelFactory
 * @covers MFElement
 *
 */
class ModelFactoryTest extends ItopTestCase
{
	protected function setUp(): void
	{
		parent::setUp();

		static::$DEBUG_UNIT_TEST = true;

		$this->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'        => '<itop_design>
	<classes>
		<class id="A" _delta="define"/>
	</classes>
</itop_design>',
			'sClassName'       => 'A',
			'sExpectedComment' => null,
		];

		$aData['No Comment other Class'] = [
			'sDeltaXML'        => '<itop_design>
	<classes>
		<!-- Test comment -->
		<class id="B" _delta="define"/>
		<class id="A" _delta="define"/>
	</classes>
</itop_design>',
			'sClassName'       => 'A',
			'sExpectedComment' => null,
		];

		$aData['Comment first class'] = [
			'sDeltaXML'        => '<itop_design>
	<classes>
		<!-- Test comment -->
		<class id="A" _delta="define"/>
	</classes>
</itop_design>',
			'sClassName'       => 'A',
			'sExpectedComment' => ' Test comment ',
		];

		$aData['Comment other Class'] = [
			'sDeltaXML'        => '<itop_design>
	<classes>
		<class id="B" _delta="define"/>
		<!-- Test comment -->
		<class id="A" _delta="define"/>
	</classes>
</itop_design>',
			'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'    => '
<itop_design>
</itop_design>',
				'sExpectedXML' => '<itop_design>
</itop_design>',
			],

			'Flat delete' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1_2" _delta="delete"/>
		<class id="C_1_1" _delta="define"/>
		<class id="C_1" _delta="delete"/>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
	<classes>
		<class id="C_1_2" _delta="delete"/>
		<class id="C_1_1" _delta="define"/>
		<class id="C_1" _delta="delete"/>
	</classes>
</itop_design>',
			],

			'flat define root' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="define">
			<parent>cmdbAbstractObject</parent>
		</class>
		<class id="C_2" _delta="define">
			<parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1" _delta="define">
		<parent>cmdbAbstractObject</parent>
	</class>
    <class id="C_2" _delta="define">
		<parent>cmdbAbstractObject</parent>
	</class>
  </classes>
</itop_design>',
			],

			'flat force root' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="force">
			<parent>cmdbAbstractObject</parent>
		</class>
		<class id="C_2" _delta="force">
			<parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1" _delta="force">
		<parent>cmdbAbstractObject</parent>
	</class>
    <class id="C_2" _delta="force">
		<parent>cmdbAbstractObject</parent>
	</class>
  </classes>
</itop_design>',
			],

			'flat redefine root' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="redefine">
			<parent>cmdbAbstractObject</parent>
		</class>
		<class id="C_2" _delta="redefine">
			<parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1" _delta="redefine">
		<parent>cmdbAbstractObject</parent>
	</class>
    <class id="C_2" _delta="redefine">
		<parent>cmdbAbstractObject</parent>
	</class>
  </classes>
</itop_design>',
			],

			'Simple hierarchy define root' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="define">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1">
				<parent>C_1</parent>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1" _delta="define">
		<parent>cmdbAbstractObject</parent>
	</class>
	<!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1" _delta="force">
      <parent>C_1</parent>
    </class>
  </classes>
</itop_design>',
			],

			'Complex hierarchy delete' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1">
				<parent>C_1</parent>
				<class id="C_1_1_1">
					<parent>C_1_1</parent>
					<class id="C_1_1_1_1" _delta="delete"/>
				</class>
			</class>
			<class id="C_1_2">
				<parent>C_1</parent>
				<class id="C_1_2_1" _delta="delete"/>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1">
		<parent>cmdbAbstractObject</parent>
	</class>
	<!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1">
      <parent>C_1</parent>
    </class>
    <!-- Automatically moved from class/C_1_1 to classes -->
    <class id="C_1_1_1">
      <parent>C_1_1</parent>
    </class>
    <!-- Automatically moved from class/C_1_1_1 to classes -->
    <class id="C_1_1_1_1" _delta="delete"/>
	<!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_2">
      <parent>C_1</parent>
    </class>
	<!-- Automatically moved from class/C_1_2 to classes -->
    <class id="C_1_2_1" _delta="delete"/>
  </classes>
</itop_design>',
			],

			'Complex hierarchy define root' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="define">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1">
				<parent>C_1</parent>
				<class id="C_1_1_1">
					<parent>C_1_1</parent>
					<class id="C_1_1_1_1">
						<parent>C_1_1_1</parent>
					</class>
				</class>
			</class>
			<class id="C_1_2">
				<parent>C_1</parent>
				<class id="C_1_2_1">
					<parent>C_1_2</parent>
				</class>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1" _delta="define">
		<parent>cmdbAbstractObject</parent>
	</class>
	<!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1" _delta="force">
      <parent>C_1</parent>
    </class>
	<!-- Automatically moved from class/C_1_1 to classes -->
    <class id="C_1_1_1" _delta="force">
      <parent>C_1_1</parent>
    </class>
	<!-- Automatically moved from class/C_1_1_1 to classes -->
    <class id="C_1_1_1_1" _delta="force">
      <parent>C_1_1_1</parent>
    </class>
	<!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_2" _delta="force">
      <parent>C_1</parent>
    </class>
	<!-- Automatically moved from class/C_1_2 to classes -->
    <class id="C_1_2_1" _delta="force">
      <parent>C_1_2</parent>
    </class>
  </classes>
</itop_design>',
			],

			'Complex hierarchy define' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1" _delta="define">
				<parent>C_1</parent>
				<class id="C_1_1_1">
					<parent>C_1_1</parent>
					<class id="C_1_1_1_1">
						<parent>C_1_1_1</parent>
					</class>
				</class>
			</class>
			<class id="C_1_2">
				<parent>C_1</parent>
				<class id="C_1_2_1" _delta="define">
					<parent>C_1_2</parent>
				</class>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1">
		<parent>cmdbAbstractObject</parent>
	</class>
	<!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1" _delta="define">
      <parent>C_1</parent>
    </class>
	<!-- Automatically moved from class/C_1_1 to classes -->
    <class id="C_1_1_1" _delta="force">
      <parent>C_1_1</parent>
    </class>
	<!-- Automatically moved from class/C_1_1_1 to classes -->
    <class id="C_1_1_1_1" _delta="force">
      <parent>C_1_1_1</parent>
    </class>
	<!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_2">
      <parent>C_1</parent>
    </class>
	<!-- Automatically moved from class/C_1_2 to classes -->
    <class id="C_1_2_1" _delta="define">
      <parent>C_1_2</parent>
    </class>
  </classes>
</itop_design>',
			],

			'Complex hierarchy force' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1" _delta="force">
				<parent>C_1</parent>
				<class id="C_1_1_1">
					<parent>C_1_1</parent>
				</class>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
    <!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1" _delta="force">
      <parent>C_1</parent>
    </class>
    <!-- Automatically moved from class/C_1_1 to classes -->
    <class id="C_1_1_1" _delta="force">
      <parent>C_1_1</parent>
    </class>
  </classes>
</itop_design>',
			],

			'Complex hierarchy force root' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="force">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1">
				<parent>C_1</parent>
				<class id="C_1_1_1">
					<parent>C_1_1</parent>
				</class>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1" _delta="force">
      <parent>cmdbAbstractObject</parent>
    </class>
    <!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1" _delta="force">
      <parent>C_1</parent>
    </class>
    <!-- Automatically moved from class/C_1_1 to classes -->
    <class id="C_1_1_1" _delta="force">
      <parent>C_1_1</parent>
    </class>
  </classes>
</itop_design>',
			],

			'Complex hierarchy redefine' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1" _delta="redefine">
				<parent>C_1</parent>
				<class id="C_1_1_1">
					<parent>C_1_1</parent>
				</class>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
    <!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1" _delta="redefine">
      <parent>C_1</parent>
    </class>
    <!-- Automatically moved from class/C_1_1 to classes -->
    <class id="C_1_1_1" _delta="force">
      <parent>C_1_1</parent>
    </class>
  </classes>
</itop_design>',
			],

			'Complex hierarchy redefine root' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="redefine">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1">
				<parent>C_1</parent>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
  <classes>
    <class id="C_1" _delta="redefine">
      <parent>cmdbAbstractObject</parent>
    </class>
    <!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1" _delta="force">
      <parent>C_1</parent>
    </class>
  </classes>
</itop_design>',
			],

			'Complex hierarchy define_if_not_exists flattening generates an error' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1" _delta="define_if_not_exists">
				<parent>C_1</parent>
				<class id="C_1_1_1">
					<parent>C_1_1</parent>
				</class>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXML' => null,
			],

			'Complex hierarchy if_exists flattening generates an error' => [
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1">
			<parent>cmdbAbstractObject</parent>
			<class id="C_1_1" _delta="if_exists">
				<parent>C_1</parent>
				<class id="C_1_1_1" _delta="define">
					<parent>C_1_1</parent>
				</class>
			</class>
		</class>
	</classes>
</itop_design>',
				'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'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
				'sDeltaXML'    => '<itop_design></itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
			],
			'merge delta lax mode' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
				'sDeltaXML'    => '<itop_design>
	<classes>
		<class id="C_1">
            <parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="added">
        <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
			],
			'Add a class'          => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="define">
            <parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="added">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
			],
			'Add a class if not exists (N°6660)' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="define_if_not_exists">
            <parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="needed">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
			],
			'Conditionally add a class but it already exists' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="define_if_not_exists">
            <parent>toto</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
			],

			'Add a class and subclass in hierarchy' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="define">
            <parent>cmdbAbstractObject</parent>
            <class id="C_1_1">
              <parent>C_1</parent>
			</class>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="added">
      <parent>cmdbAbstractObject</parent>
    </class>
    <!-- Automatically moved from class/C_1 to classes -->
    <class id="C_1_1" _alteration="forced">
	  <parent>C_1</parent>
	</class>
  </classes>
</itop_design>',
			],

			'Delete a class' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="delete">
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="removed"/>
  </classes>
</itop_design>',
			],

			'Redefine a recently added subclass' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="added">
      <parent>cmdbAbstractObject</parent>
    </class>
	<class id="C_1_1" _alteration="added">
	  <parent>C_1</parent>
	</class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="merge"/>
		<class id="C_1_1" _delta="redefine">
			<parent>C_1</parent>
			<properties/>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="added">
      <parent>cmdbAbstractObject</parent>
    </class>
	<class id="C_1_1" _alteration="added">
		<parent>C_1</parent>
		<properties/>
	</class>        
  </classes>
</itop_design>',
				],

			'Delete a recently added class' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="added">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<!-- Comment -->
		<class id="C_1" _delta="delete"/>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
			],
			'Delete hierarchically a class' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="delete">
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="removed"/>
  </classes>
</itop_design>',
			],

			'Delete hierarchically a class and subclass' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
	<class id="C_1_1">
	  <parent>C_1</parent>
	</class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="delete"/>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="removed"/>
  </classes>
</itop_design>',
			],

			'Delete hierarchically a class and subclass already deleted' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
	<class id="C_1_1">
	  <parent>C_1</parent>
	</class>
	<class id="C_1_2">
	  <parent>C_1</parent>
	</class>
	<class id="C_1_2_1">
	  <parent>C_1_2</parent>
	</class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1_2" _delta="delete"/>
		<class id="C_1" _delta="delete"/>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1_2" _alteration="removed"/>
    <class id="C_1" _alteration="removed"/>
  </classes>
</itop_design>',
			],

			'Delete if exist hierarchically an existing class' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="delete_if_exists">
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="remove_needed"/>
  </classes>
</itop_design>',
			],

			'Delete if exist hierarchically an non existing class' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_2">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="delete_if_exists"/>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_2">
      <parent>cmdbAbstractObject</parent>
    </class>
    <class id="C_1" _alteration="remove_needed"/>
  </classes>
</itop_design>',
			],

			'Delete if exist hierarchically a removed class' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="delete"/>
		<class id="C_1" _delta="delete_if_exists"/>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="removed"/>
  </classes>
</itop_design>',
			],

			'Delete if exist hierarchically an existing class and subclass' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
	<class id="C_1_1">
	  <parent>C_1</parent>
	</class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="delete_if_exists"/>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="remove_needed"/>
  </classes>
</itop_design>',
			],

			'Delete if exist hierarchically a non existing subclass' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
	<class id="C_1_1">
	  <parent>C_1</parent>
	</class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1_1" _delta="delete"/>
		<class id="C_1" _delta="delete_if_exists"/>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1_1" _alteration="removed"/>
    <class id="C_1" _alteration="remove_needed"/>
  </classes>
</itop_design>',
			],

			'Class comment should be preserved'              => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<!-- Test Comment on class C_1 -->
		<class id="C_1" _delta="define">
            <parent>cmdbAbstractObject</parent>
		</class>
		<!-- Test Comment on merged class -->
		<class id="C_2">
            <parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <!-- Test Comment on class C_1 -->
    <class id="C_1" _alteration="added">
      <parent>cmdbAbstractObject</parent>
    </class>
	<!-- Test Comment on merged class -->
	<class id="C_2" _alteration="added">
        <parent>cmdbAbstractObject</parent>
	</class>
  </classes>
</itop_design>',
			],
			'Delete hierarchically a class and add it again' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
      <parent>cmdbAbstractObject</parent>
    </class>
	<class id="C_1_1">
	  <parent>C_1</parent>
	</class>
	<class id="C_1_1_1">
	  <parent>C_1_1</parent>
	</class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<classes>
		<class id="C_1" _delta="delete"/>
		<class id="C_1" _delta="define">
			<parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1" _alteration="replaced">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>',
			],
			'merge delta strict'                             => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
  </classes>
</itop_design>',
				'sDeltaXML'    => '<itop_design load="strict">
	<classes>
		<class id="C_1">
            <parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>',
				'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'  => '<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="FunctionalCI">
        <parent>cmdbAbstractObject</parent>
    </class>
	<class id="Licence">
		<parent>cmdbAbstractObject</parent>
	</class>
	<class id="Certificat">
		<parent>Licence</parent>
	</class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '<itop_design>
	<classes>
		<class id="ConfigElement" _delta="define">
			<parent>FunctionalCI</parent>
			<class id="Key">
				<parent>ConfigElement</parent>
				<class id="Certificat">
					<parent>Key</parent>
				</class>
			</class>
		</class>
		<class id="Licence" _delta="redefine">
			<parent>Key</parent>
		</class>
	</classes>
</itop_design>',
				'sExpectedXMLOrErrorMessage' => '<itop_design>
	<classes>
		<class id="cmdbAbstractObject"/>
		<class id="FunctionalCI">
			<parent>cmdbAbstractObject</parent>
		</class>
		<class id="ConfigElement" _alteration="added">
			<parent>FunctionalCI</parent>
		</class>
		<!-- Automatically moved from class/ConfigElement to classes -->
		<class id="Key" _alteration="forced">
			<parent>ConfigElement</parent>
		</class>
		<class id="Licence" _alteration="replaced">
			<parent>Key</parent>
		</class>
		<!-- Automatically moved from class/Key to classes -->
		<class id="Certificat" _alteration="forced">
			<parent>Key</parent>
		</class>
	</classes>
</itop_design>',
			],
			'Class with wrong parent should generate an error' => [
				'sInitialXML'  => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1">
        <parent>cmdbAbstractObject</parent>
	</class>
  </classes>
</itop_design>',
				'sDeltaXML'    => '<itop_design>
	<classes>
		<class id="C_1" _delta="redefine">
            <parent>toto</parent>
		</class>
	</classes>
</itop_design>',
				'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
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
			],
			'No change at all - mini delta'                        => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA/>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
			],
			'_delta="merge" implicit'                              => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
			],
			'_delta="merge" explicit (lax)'                        => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="merge"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
			],
			'_delta="merge" does preserve text in lax mode'        => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB>Maintained Text</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>Maintained Text</nodeB>
</nodeA>
XML
				,
			],
			'_delta="merge" recursively'                           => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB>
		<nodeC>
			<nodeD/>
		</nodeC>
	</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>
		<nodeC>
			<nodeD/>
		</nodeC>
	</nodeB>
</nodeA>
XML
				,
			],
			// Define or redefine
			'_delta="define" without id'                           => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="define"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
			],
			'_delta="define" with id'                              => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="define"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<item id="toto"/>
</nodeA>
XML
				,
			],
			'_delta="define" but existing node'                    => [
				'sInitialXML'  => <<<XML
<nodeA>
	<item id="toto"/>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="define"/>
</nodeA>
XML
				,
				'sExpectedXML' => null,
			],
			'_delta="redefine" without id'                         => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB>Initial BB</nodeB>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="redefine">Gainsbourg</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>Gainsbourg</nodeB>
</nodeA>
XML
				,
			],
			'_delta="redefine" with id'                            => [
				'sInitialXML'  => <<<XML
<nodeA>
	<item id="toto">Initial BB</item>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="redefine">Gainsbourg</item>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<item id="toto">Gainsbourg</item>
</nodeA>
XML
				,
			],
			'_delta="redefine" but missing node'                   => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="redefine">Gainsbourg</item>
</nodeA>
XML
				,
				'sExpectedXML' => null,
			],
			'_delta="force" without id + missing node'             => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="force">Hulk</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>Hulk</nodeB>
</nodeA>
XML
				,
			],
			'_delta="force" with id + missing node'                => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="force">Hulk</item>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<item id="toto">Hulk</item>
</nodeA>
XML
				,
			],
			'_delta="force" without id + existing node'            => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB>Initial BB</nodeB>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="force">Gainsbourg</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>Gainsbourg</nodeB>
</nodeA>
XML
				,
			],
			'_delta="force" with id + existing node'               => [
				'sInitialXML'  => <<<XML
<nodeA>
	<item id="toto">Initial BB</item>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="force">Gainsbourg</item>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<item id="toto">Gainsbourg</item>
</nodeA>
XML
				,
			],
			// Rename
			'rename'                                               => [
				'sInitialXML'  => <<<XML
<nodeA>
	<item id="Kent">Kryptonite</item>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="Superman" _rename_from="Kent"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<item id="Superman">Kryptonite</item>
</nodeA>
XML
				,
			],
			'rename but missing node NOT INTUITIVE!!!'             => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="Superman" _rename_from="Kent"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<item id="Superman"/>
</nodeA>
XML
				,
			],
			// Delete
			'_delta="delete" without id'                           => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB>Initial BB</nodeB>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="delete"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA/>
XML
				,
			],
			'_delta="delete" with id'                              => [
				'sInitialXML'  => <<<XML
<nodeA>
	<item id="toto">Initial BB</item>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="delete"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA/>
XML
				,
			],
			'_delta="delete" but missing node'                     => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="delete"/>
</nodeA>
XML
				,
				'sExpectedXML' => null,
			],
			'_delta="delete_if_exists" without id + existing node' => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB>Initial BB</nodeB>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="delete_if_exists"/>
</nodeA>
XML
				,
				'sExpectedXML' => '<nodeA/>',
			],
			'_delta="delete_if_exists" with id + existing node'    => [
				'sInitialXML'  => <<<XML
<nodeA>
	<item id="toto">Initial BB</item>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="delete_if_exists"/>
</nodeA>
XML
				,
				'sExpectedXML' => '<nodeA/>',
			],
			'_delta="delete_if_exists" without id + missing node'  => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="delete_if_exists"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA/>
XML
				,
			],
			'_delta="delete_if_exists" with id + missing node'     => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<item id="toto" _delta="delete_if_exists"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA/>
XML
				,
			],
			// Conditionals
			'_delta="must_exist"'                                  => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="must_exist">
		<nodeC _delta="define"/>
	</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>
		<nodeC/>
	</nodeB>
</nodeA>
XML
				,
			],
			'_delta="must_exist on missing node"'                  => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="must_exist">
		<nodeC _delta="define"/>
	</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => null,
			],
			'_delta="if_exists on missing node (lax)'                   => [
				'sInitialXML'  => <<<XML
<nodeA>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="if_exists">
		<nodeC _delta="define"/>
	</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
</nodeA>
XML
				,
			],
			'_delta="if_exists on missing node (strict)'                   => [
				'sInitialXML'  => <<<XML
<itop_design>
</itop_design>
XML
				,
				'sDeltaXML'    => <<<XML
<itop_design load="strict">
	<nodeB _delta="if_exists">
		<nodeC _delta="define"/>
	</nodeB>
</itop_design>
XML
				,
				'sExpectedXML' => <<<XML
<itop_design>
</itop_design>
XML
				,
			],
			'_delta="if_exists on existing node"'                  => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB/>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="if_exists">
		<nodeC _delta="define"/>
	</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>
		<nodeC/>
	</nodeB>
</nodeA>
XML
				,
			],
			'_delta="define_if_not_exists on missing node"'        => [
				'sInitialXML'  => <<<XML
<nodeA/>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="define_if_not_exists">The incredible Hulk</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>The incredible Hulk</nodeB>
</nodeA>
XML
				,
			],
			'_delta="define_if_not_exists on existing node"'       => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB>Luke Banner</nodeB>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB _delta="define_if_not_exists">The incredible Hulk</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
	<nodeB>Luke Banner</nodeB>
</nodeA>
XML
				,
			],
			'_delta="define_and_must_exits"'                       => [
				'sInitialXML'  => <<<XML
<nodeA>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB id="Banner" _delta="define"/>
	<nodeB id="Banner" _delta="must_exist">
		<nodeC _delta="define"/>
	</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
  <nodeB id="Banner">
    <nodeC/>
  </nodeB>
</nodeA>
XML
				,
			],
			'_delta="define_then_must_exist"'                      => [
				'sInitialXML'  => <<<XML
<nodeA>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB id="Banner" _delta="define">
		<nodeE/>
	</nodeB>
	<nodeB id="Banner" _delta="must_exist">
		<nodeC _delta="define_if_not_exists">
			<nodeD id="Bruce" _delta="define"/>
		</nodeC>
	</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
  <nodeB id="Banner">
    <nodeE/>
    <nodeC>
      <nodeD id="Bruce"/>
    </nodeC>
  </nodeB>
</nodeA>
XML
				,
			],
			'nested _delta should be cleaned'                      => [
				'sInitialXML'  => <<<XML
<nodeA>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB id="Banner" _delta="define">
		<nodeC>
			<nodeD id="Bruce" _delta="define"/>
		</nodeC>
	</nodeB>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA>
  <nodeB id="Banner">
    <nodeC>
      <nodeD id="Bruce"/>
    </nodeC>
  </nodeB>
</nodeA>
XML
				,
			],
			'Class comments are stripped when class is deleted'    => [
				'sInitialXML'  => '
<itop_design>
<classes>
<class id="cmdbAbstractObject"/>
<!-- Test Comment on class C_1 -->
<class id="C_1"/>
</classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
<classes>
<class id="C_1" _delta="delete"/>
</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
<classes>
<class id="cmdbAbstractObject"/>
</classes>
</itop_design>',
			],
			'Class comments are preserved'                         => [
				'sInitialXML'  => '
<itop_design>
<classes>
<class id="cmdbAbstractObject"/>
<!-- Test Comment on class C_1 -->
<class id="C_1"/>
</classes>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
<classes>
</classes>
</itop_design>',
				'sExpectedXML' => '<itop_design>
<classes>
<class id="cmdbAbstractObject"/>
<!-- Test Comment on class C_1 -->
<class id="C_1"/>
</classes>
</itop_design>',
			],
			'if_exist on removed node does nothing'                => [
				'sInitialXML'  => '
<root>
<nodeA _alteration="removed"/>
</root>',
				'sDeltaXML'    => '
<root>
<nodeA _delta="if_exists">
<nodeB _delta="define"/>
</nodeA>
</root>',
				'sExpectedXML' => '<root/>',
			],
			'if_exist on missing node does nothing'                => [
				'sInitialXML'  => '
<root/>',
				'sDeltaXML'    => '
<root>
<nodeA _delta="if_exists">
<nodeB _delta="define"/>
</nodeA>
</root>',
				'sExpectedXML' => '<root/>',
			],
			'if_exist on existing node merges'                     => [
				'sInitialXML'  => '
<root>
<nodeA/>
</root>',
				'sDeltaXML'    => '
<root>
<nodeA _delta="if_exists">
<nodeB _delta="define"/>
</nodeA>
</root>',
				'sExpectedXML' => '<root>
<nodeA>
<nodeB/>
</nodeA>
</root>',
			],
			'must_exist on removed node does error'                => [
				'sInitialXML'  => '
<root>
<nodeA _alteration="removed"/>
</root>',
				'sDeltaXML'    => '
<root>
<nodeA _delta="must_exist">
<nodeB _delta="define"/>
</nodeA>
</root>',
				'sExpectedXML' => null,
			],
			'must_exist on missing node does error'                => [
				'sInitialXML'  => '
<root/>',
				'sDeltaXML'    => '
<root>
<nodeA _delta="must_exist">
<nodeB _delta="define"/>
</nodeA>
</root>',
				'sExpectedXML' => null,
			],
			'must_exist on existing node merges'                   => [
				'sInitialXML'  => '
<root>
<nodeA/>
</root>',
				'sDeltaXML'    => '
<root>
<nodeA _delta="must_exist">
<nodeB _delta="define"/>
</nodeA>
</root>',
				'sExpectedXML' => '<root>
<nodeA>
<nodeB/>
</nodeA>
</root>',
			],
			'N°8440 Quotes are allowed in ids'                     => [
				'sInitialXML'  => <<<XML
<nodeA>
	<nodeB id="I'm happy"/>
</nodeA>
XML
				,
				'sDeltaXML'    => <<<XML
<nodeA>
	<nodeB id="I'm happy" _delta="redefine"/>
	<nodeB id="I'm happy" _delta="delete"/>
</nodeA>
XML
				,
				'sExpectedXML' => <<<XML
<nodeA/>
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
<root_tag>
	<container_tag>
		<target_tag/>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_ABOVE_A_FLAG', <<<XML
<root_tag>
	<container_tag>
		<target_tag>
			<child_tag _alteration="added">Blah</child_tag>
		</target_tag>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_IN_A_DEFINITION', <<<XML
<root_tag>
	<container_tag _alteration="added">
		<target_tag>
			<child_tag>Blah</child_tag>
		</target_tag>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_FLAG_ON_TARGET_define', <<<XML
<root_tag>
	<container_tag>
		<target_tag _alteration="added"/>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_FLAG_ON_TARGET_redefine', <<<XML
<root_tag>
	<container_tag>
		<target_tag _alteration="replaced"/>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_FLAG_ON_TARGET_needed', <<<XML
<root_tag>
	<container_tag>
		<target_tag _alteration="needed"/>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_FLAG_ON_TARGET_forced', <<<XML
<root_tag>
	<container_tag>
		<target_tag _alteration="forced"/>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_FLAG_ON_TARGET_removed', <<<XML
<root_tag>
	<container_tag>
		<target_tag _alteration="removed"/>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_FLAG_ON_TARGET_old_id', <<<XML
<root_tag>
	<container_tag>
		<target_tag id="fraise" _old_id="tagada"/>
	</container_tag>
</root_tag>
XML
		);
		define('CASE_MISSING_TARGET', <<<XML
<root_tag>
	<container_tag/>
</root_tag>
XML
		);
		$aData = [
			'CASE_NO_FLAG Delete'                            => [
				CASE_NO_FLAG,
				'Delete',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="removed"/>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_ABOVE_A_FLAG Delete'                       => [
				CASE_ABOVE_A_FLAG,
				'Delete',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="removed"/>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_IN_A_DEFINITION Delete'                    => [
				CASE_IN_A_DEFINITION,
				'Delete',
				<<<XML
<root_tag>
	<container_tag _alteration="added"/>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_define Delete'              => [
				CASE_FLAG_ON_TARGET_define,
				'Delete',
				<<<XML
<root_tag>
	<container_tag/>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_redefine Delete'            => [
				CASE_FLAG_ON_TARGET_redefine,
				'Delete',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="removed"/>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_needed Delete'              => [
				CASE_FLAG_ON_TARGET_needed,
				'Delete',
				<<<XML
<root_tag>
  <container_tag/>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_forced Delete'              => [
				CASE_FLAG_ON_TARGET_forced,
				'Delete',
				<<<XML
<root_tag>
  <container_tag/>
</root_tag>
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
<root_tag>
  <container_tag>
      <target_tag id="fraise" _old_id="tagada" _alteration="removed"/>
	</container_tag>
</root_tag>
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',
				<<<XML
<root_tag>
  <container_tag>
      <target_tag _alteration="replaced">Hello, I'm a newly added node</target_tag>
	</container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_old_id AddChildNode'        => [
				CASE_FLAG_ON_TARGET_old_id,
				'AddChildNodeToContainer',
				null,
			],
			'CASE_MISSING_TARGET AddChildNode'               => [
				CASE_MISSING_TARGET,
				'AddChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
      <target_tag _alteration="added">Hello, I'm a newly added node</target_tag>
	</container_tag>
</root_tag>
XML
				,
			],
			'CASE_NO_FLAG RedefineChildNode'                 => [
				CASE_NO_FLAG,
				'RedefineChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="replaced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_ABOVE_A_FLAG RedefineChildNode'            => [
				CASE_ABOVE_A_FLAG,
				'RedefineChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="replaced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_IN_A_DEFINITION RedefineChildNode'         => [
				CASE_IN_A_DEFINITION,
				'RedefineChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag _alteration="added">
    <target_tag>Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_define RedefineChildNode'   => [
				CASE_FLAG_ON_TARGET_define,
				'RedefineChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="added">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_redefine RedefineChildNode' => [
				CASE_FLAG_ON_TARGET_redefine,
				'RedefineChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="replaced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			// Note: buggy case ?
			'CASE_FLAG_ON_TARGET_needed RedefineChildNode'   => [
				CASE_FLAG_ON_TARGET_needed,
				'RedefineChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="needed">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_forced RedefineChildNode'   => [
				CASE_FLAG_ON_TARGET_forced,
				'RedefineChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="forced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
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',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="replaced" _old_id="tagada">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_MISSING_TARGET RedefineChildNode'          => [
				CASE_MISSING_TARGET,
				'RedefineChildNodeToContainer',
				null,
			],
			'CASE_NO_FLAG SetChildNode'                      => [
				CASE_NO_FLAG,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="replaced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_ABOVE_A_FLAG SetChildNode'                 => [
				CASE_ABOVE_A_FLAG,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="replaced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_IN_A_DEFINITION SetChildNode'              => [
				CASE_IN_A_DEFINITION,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag _alteration="added">
    <target_tag>Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_define SetChildNode'        => [
				CASE_FLAG_ON_TARGET_define,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="added">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_redefine SetChildNode'      => [
				CASE_FLAG_ON_TARGET_redefine,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="replaced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			// Note: buggy case ?
			'CASE_FLAG_ON_TARGET_needed SetChildNode'        => [
				CASE_FLAG_ON_TARGET_needed,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="needed">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_forced SetChildNode'        => [
				CASE_FLAG_ON_TARGET_forced,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="forced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_removed SetChildNode'       => [
				CASE_FLAG_ON_TARGET_removed,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="replaced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_FLAG_ON_TARGET_old_id SetChildNode'        => [
				CASE_FLAG_ON_TARGET_old_id,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _old_id="tagada" _alteration="replaced">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
XML
				,
			],
			'CASE_MISSING_TARGET SetChildNode'               => [
				CASE_MISSING_TARGET,
				'SetChildNodeToContainer',
				<<<XML
<root_tag>
  <container_tag>
    <target_tag _alteration="added">Hello, I'm replacing the previous node</target_tag>
  </container_tag>
</root_tag>
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' => <<<XML
<root_node>
	<james_bond>Roger Moore</james_bond>
	<stairway_to_heaven/>
	<robot id="r2d2"/>
</root_node>
XML
				,
				// Weird, but seems ok as of now
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="##ITOP_DESIGN_LATEST_VERSION##"/>
XML
				,
			],
			'_alteration="added" singleton'       => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="added"/>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="define"/>
</root_node>

XML
				,
			],
			'_alteration="added" with value'      => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="added">Roger Moore</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="define">Roger Moore</james_bond>
</root_node>
XML
				,
			],
			'_alteration="added" with subtree'    => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="added">
		<name>Moore</name>
		<last_name>Roger</last_name>
	</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="define">
    <name>Moore</name>
    <last_name>Roger</last_name>
  </james_bond>
</root_node>
XML
				,
			],
			'_alteration="forced" singleton'      => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="forced"/>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="force"/>
</root_node>
XML
				,
			],
			'_alteration="forced" with value'     => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="forced">Roger Moore</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="force">Roger Moore</james_bond>
</root_node>
XML
				,
			],
			'_alteration="forced" with subtree'   => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="forced">
		<name>Moore</name>
		<last_name>Roger</last_name>
	</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="force">
    <name>Moore</name>
    <last_name>Roger</last_name>
  </james_bond>
</root_node>
XML
				,
			],
			'_alteration="needed" singleton'      => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="needed"/>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="define_if_not_exists"/>
</root_node>
XML
				,
			],
			'_alteration="needed" with value'     => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="needed">Roger Moore</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="define_if_not_exists">Roger Moore</james_bond>
</root_node>
XML
				,
			],
			'_alteration="needed" with subtree'   => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="needed">
		<name>Moore</name>
		<last_name>Roger</last_name>
	</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="define_if_not_exists">
    <name>Moore</name>
    <last_name>Roger</last_name>
  </james_bond>
</root_node>
XML
				,
			],
			'_alteration="replaced" with value'   => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="replaced">Sean Connery</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="redefine">Sean Connery</james_bond>
</root_node>
XML
				,
			],
			'_alteration="replaced" with subtree' => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="added">
		<name>Sean</name>
		<last_name>Connery</last_name>
	</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
  <james_bond _delta="define">
    <name>Sean</name>
    <last_name>Connery</last_name>
  </james_bond>
</root_node>
XML
				,
			],
			'_alteration="removed"'               => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="removed"/>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
	<james_bond _delta="delete"/>
</root_node>
XML
				,
			],
			'_alteration="remove_needed"'               => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond _alteration="remove_needed"/>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
	<james_bond _delta="delete_if_exists"/>
</root_node>
XML
				,
			],
			'_old_id'                             => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond id="Sean" _old_id="Roger"/>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
	<james_bond  id="Sean" _rename_from="Roger"/>
</root_node>
XML
				,
			],
			'_old_id with subtree'                => [
				'sInitialXMLInternal' => <<<XML
<root_node>
	<james_bond id="Sean" _old_id="Roger">
		<subtree _alteration="added">etc.</subtree>
	</james_bond>
</root_node>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<root_node>
	<james_bond  id="Sean" _rename_from="Roger">
    <subtree _delta="define">etc.</subtree>	
</james_bond>
</root_node>
XML
				,
			],

			'Class Comments are not kept for created classes' => [
				'sInitialXMLInternal' => <<<XML
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <!-- Test Comment on class C_1 -->
    <class id="C_1" _alteration="added">
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
</itop_design>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<itop_design>
	<classes>
		<class id="C_1" _delta="define">
            <parent>cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>
XML
				,
			],
			'Class Comments are not preserved'          => [
				'sInitialXMLInternal' => <<<XML
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <!-- Test Comment on class C_1 -->
    <class id="C_1" _alteration="added">
      <parent>cmdbAbstractObject</parent>
    </class>
	<!-- Test Comment on merged class -->
	<class id="C_2">
        <parent _alteration="added">cmdbAbstractObject</parent>
	</class>
  </classes>
</itop_design>
XML
				,
				'sExpectedXMLDelta'   => <<<XML
<itop_design>
	<classes>
		<class id="C_1" _delta="define">
            <parent>cmdbAbstractObject</parent>
		</class>
		<class id="C_2">
            <parent _delta="define">cmdbAbstractObject</parent>
		</class>
	</classes>
</itop_design>
XML
				,
			],
			'Conditionally deleted class' => [
				'sInitialXMLInternal' => '
<itop_design>
  <classes>
    <class id="cmdbAbstractObject"/>
    <class id="C_1_1" _alteration="removed"/>
    <class id="C_1" _alteration="remove_needed"/>
  </classes>
</itop_design>',
				'sExpectedXMLDelta'   => '
<itop_design>
  <classes>
    <class id="C_1_1" _delta="delete"/>
    <class id="C_1" _delta="delete_if_exists"/>
  </classes>
</itop_design>
				',
				],
		];
	}

	/**
	 * @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'             => '<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="##ITOP_DESIGN_LATEST_VERSION##">
  <loaded_modules/>
  <classes>
    <class id="DBObject"/>
    <class id="CMDBObject"/>
    <class id="cmdbAbstractObject"/>
    <class id="A" _created_in="M" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
  <dictionaries/>
  <menus/>
  <meta/>
  <events/>
</itop_design>',
				'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'             => '<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="##ITOP_DESIGN_LATEST_VERSION##">
  <loaded_modules/>
  <classes>
    <class id="DBObject"/>
    <class id="CMDBObject"/>
    <class id="cmdbAbstractObject"/>
    <class id="A" _created_in="M" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>cmdbAbstractObject</parent>
    </class>
    <class id="B" _created_in="M2" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>cmdbAbstractObject</parent>
    </class>
  </classes>
  <dictionaries/>
  <menus/>
  <meta/>
  <events/>
</itop_design>',
				'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'             => '<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="##ITOP_DESIGN_LATEST_VERSION##">
  <loaded_modules/>
  <classes>
    <class id="DBObject"/>
    <class id="CMDBObject"/>
    <class id="cmdbAbstractObject"/>
    <class id="A" _created_in="M" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>cmdbAbstractObject</parent>
    </class>
    <class id="B" _created_in="M2" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>A</parent>
    </class>
  </classes>
  <dictionaries/>
  <menus/>
  <meta/>
  <events/>
</itop_design>',
				'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'             => '<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="##ITOP_DESIGN_LATEST_VERSION##">
  <loaded_modules/>
  <classes>
    <class id="DBObject"/>
    <class id="CMDBObject"/>
    <class id="cmdbAbstractObject"/>
    <class id="A" _created_in="M" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>cmdbAbstractObject</parent>
    </class>
    <class id="B" _created_in="M2" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>A</parent>
    </class>
    <class id="C" _created_in="M3" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>cmdbAbstractObject</parent>
    </class>
    <class id="D" _created_in="M3" _alteration="added">
      <properties/>
      <fields/>
      <methods/>
      <presentation/>
      <parent>B</parent>
    </class>
  </classes>
  <dictionaries/>
  <menus/>
  <meta/>
  <events/>
</itop_design>',
				'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'  => '
<itop_design>
  <nodeA/>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<nodeA>
		<nodeB id="C_1">
			<parent>cmdbAbstractObject</parent>
		</nodeB>
	</nodeA>
</itop_design>',
				'sExpectedXMLInLaxMode' => '
<itop_design>
  <nodeA>
    <nodeB id="C_1" _alteration="added">
        <parent>cmdbAbstractObject</parent>
    </nodeB>
  </nodeA>
</itop_design>',
				'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'  => '
<itop_design>
	<nodeA/>
</itop_design>',
				'sDeltaXML'    => '
<itop_design load="strict">
	<nodeA>
		<nodeB id="C_1">
			<parent>cmdbAbstractObject</parent>
		</nodeB>
	</nodeA>
</itop_design>',
				'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'  => '
<itop_design>
	<nodeA>Test</nodeA>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<nodeA>Taste</nodeA>
</itop_design>',
				'sExpectedXMLInLaxMode' => '
<itop_design>
    <nodeA _alteration="replaced">Taste</nodeA>
</itop_design>',
				'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'  => '
<itop_design>
	<nodeA/>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<nodeA/>
</itop_design>',
				'sExpectedXMLInLaxMode' => '
<itop_design>
	<nodeA/>
</itop_design>',
				'sExpectedXMLInStrictMode' => '
<itop_design>
	<nodeA/>
</itop_design>',
			],

			'default no _delta on non-existing leaf nodes without text have different behavior' => [
				'sInitialXML'  => '
<itop_design>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<nodeA/>
</itop_design>',
				'sExpectedXMLInLaxMode' => '
<itop_design>
	<nodeA/>
</itop_design>',
				'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'  => '
<itop_design>
</itop_design>',
				'sDeltaXML'    => '
<itop_design>
	<nodeA>
		<nodeB _delta="define"/>
	</nodeA>
</itop_design>',
				'sExpectedXMLInLaxMode' => '
<itop_design>
	<nodeA>
		<nodeB _alteration="added"/>
	</nodeA>
</itop_design>',
				'sExpectedXMLInStrictMode' => '
<itop_design>
	<nodeA>
		<nodeB _alteration="added"/>
	</nodeA>
</itop_design>',
			],

			'merge _delta on non-existing node must create node' => [
				'sInitialXML'  => '
<itop_design>
	<nodeA>
		<nodeB/>
	</nodeA>
</itop_design>',
				'sDeltaXML'    => '
<itop_design load="strict">
	<nodeA>
		<nodeB>
			<nodeC id="C_1" _delta="merge">
				<items>
					<item id="I_1" _delta="define">Test</item>
				</items>
			</nodeC>
		</nodeB>
	</nodeA>
</itop_design>',
				'sExpectedXMLInLaxMode' => '
<itop_design>
  <nodeA>
	<nodeB>
		<nodeC id="C_1" _alteration="added">
			<items>
				<item id="I_1">Test</item>
			</items>
		</nodeC>
	</nodeB>
  </nodeA>
</itop_design>',
				'sExpectedXMLInStrictMode' => '
<itop_design>
  <nodeA>
	<nodeB>
		<nodeC id="C_1" _alteration="added">
			<items>
				<item id="I_1">Test</item>
			</items>
		</nodeC>
	</nodeB>
  </nodeA>
</itop_design>',
			],

			'merge _delta on existing tree must merge...' => [
				'sInitialXML'  => '
<itop_design>
	<nodeA>
		<nodeB>
			<nodeC id="C_1">
				<items/>	
			</nodeC>
		</nodeB>
	</nodeA>
</itop_design>',
				'sDeltaXML'    => '
<itop_design load="strict">
	<nodeA>
		<nodeB>
			<nodeC id="C_1" _delta="merge">
				<items>
					<item id="I_1" _delta="define">Test</item>
				</items>
			</nodeC>
		</nodeB>
	</nodeA>
</itop_design>',
				'sExpectedXMLInLaxMode' => '
<itop_design>
  <nodeA>
	<nodeB>
		<nodeC id="C_1">
			<items>
				<item id="I_1" _alteration="added">Test</item>
			</items>
		</nodeC>
	</nodeB>
  </nodeA>
</itop_design>',
				'sExpectedXMLInStrictMode' => '
<itop_design>
  <nodeA>
	<nodeB>
		<nodeC id="C_1">
			<items>
				<item id="I_1" _alteration="added">Test</item>
			</items>
		</nodeC>
	</nodeB>
  </nodeA>
</itop_design>',
			],
		];
	}

	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);
	}
}
