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->loadXML($sXML); $oExpectedDocument->formatOutput = true; return $oExpectedDocument->saveXML($oExpectedDocument->firstChild); } /** * @param $sExpected * @param $sActual */ protected function AssertEqualiTopXML($sExpected, $sActual) { // 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)); } /** * Assertion ignoring some of the unexpected decoration brought by DOM Elements. */ protected function AssertEqualModels(string $sExpectedXML, ModelFactory $oFactory) { return $this->AssertEqualiTopXML($sExpectedXML, $oFactory->Dump(null, true)); } /** * @dataProvider providerDeltas * @covers ModelFactory::LoadDelta * @covers ModelFactory::ApplyChanges */ public function testAlterationByXMLDelta($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 providerDeltas() { // Basic (structure) $aDeltas['No change at all'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['No change at all - mini delta'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="merge" implicit'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="merge" explicit'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="merge" does not handle data'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << Ghost busters!!! XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="merge" recursively'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; // Define or redefine $aDeltas['_delta="define" without id'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="define" with id'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="define" but existing node'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => null, ]; $aDeltas['_delta="redefine" without id'] = [ 'sInitialXML' => << Initial BB XML, 'sDeltaXML' => << Gainsbourg XML, 'sExpectedXML' => << Gainsbourg XML, ]; $aDeltas['_delta="redefine" with id'] = [ 'sInitialXML' => << Initial BB XML, 'sDeltaXML' => << Gainsbourg XML, 'sExpectedXML' => << Gainsbourg XML, ]; $aDeltas['_delta="redefine" but missing node'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << Gainsbourg XML, 'sExpectedXML' => null, ]; $aDeltas['_delta="force" without id + missing node'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << Hulk XML, 'sExpectedXML' => << Hulk XML, ]; $aDeltas['_delta="force" with id + missing node'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << Hulk XML, 'sExpectedXML' => << Hulk XML, ]; $aDeltas['_delta="force" without id + existing node'] = [ 'sInitialXML' => << Initial BB XML, 'sDeltaXML' => << Gainsbourg XML, 'sExpectedXML' => << Gainsbourg XML, ]; $aDeltas['_delta="force" with id + existing node'] = [ 'sInitialXML' => << Initial BB XML, 'sDeltaXML' => << Gainsbourg XML, 'sExpectedXML' => << Gainsbourg XML, ]; // Rename $aDeltas['rename'] = [ 'sInitialXML' => << Kryptonite XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << Kryptonite XML, ]; $aDeltas['rename but missing node NOT INTUITIVE!!!'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; // Delete $aDeltas['_delta="delete" without id'] = [ 'sInitialXML' => << Initial BB XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="delete" with id'] = [ 'sInitialXML' => << Initial BB XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="delete" but missing node'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => null, ]; $aDeltas['_delta="delete_if_exists" without id + existing node'] = [ 'sInitialXML' => << Initial BB XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="delete_if_exists" with id + existing node'] = [ 'sInitialXML' => << Initial BB XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="delete_if_exists" without id + missing node'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="delete_if_exists" with id + missing node'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; // Conditionals $aDeltas['_delta="must_exist"'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="must_exist on missing node"'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => null, ]; $aDeltas['_delta="if_exists on missing node"'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="if_exists on existing node"'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << XML, 'sExpectedXML' => << XML, ]; $aDeltas['_delta="define_if_not_exists on missing node"'] = [ 'sInitialXML' => << XML, 'sDeltaXML' => << The incredible Hulk XML, 'sExpectedXML' => << The incredible Hulk XML, ]; $aDeltas['_delta="define_if_not_exists on existing node"'] = [ 'sInitialXML' => << Luke Banner XML, 'sDeltaXML' => << The incredible Hulk XML, 'sExpectedXML' => << Luke Banner XML, ]; return $aDeltas; } /** * @dataProvider providerAlterationAPIs * @covers \ModelFactory::GetDelta * @covers \MFElement::AddChildNode * @covers \MFElement::RedefineChildNode * @covers \MFElement::SetChildNode * @covers \MFElement::Delete */ public function testAlterationsByAPIs($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 providerAlterationAPIs() { 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; } /** * @covers \ModelFactory::LoadDelta * @covers \ModelFactory::GetDelta * @covers \ModelFactory::GetDeltaDocument * @dataProvider providerGetDelta */ public function testGetDelta($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 providerGetDelta() { 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 ], '_old_id' => [ 'sInitialXMLInternal' => << XML, 'sExpectedXMLDelta' => << XML ], '_old_id with subtree' => [ 'sInitialXMLInternal' => << etc. XML, 'sExpectedXMLDelta' => << etc. XML ], ]; } }