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; } /** * Assertion ignoring some of the unexpected decoration brought by DOM Elements. */ protected function AssertEqualModels(string $sExpectedXML, ModelFactory $oFactory, $sMessage = '') { // constants aren't accessible in the data provider :( $sExpectedXML = str_replace('##ITOP_DESIGN_LATEST_VERSION##', ITOP_DESIGN_LATEST_VERSION, $sExpectedXML); $this->AssertEqualiTopXML($sExpectedXML, $oFactory->Dump(null, true), $sMessage); } /** * @test * @dataProvider GetPreviousCommentProvider * @covers ModelFactory::GetPreviousComment * * @param $sDeltaXML * @param $sClassName * @param $sExpectedComment * * @return void * @throws \Exception */ public function GetPreviousCommentTest($sDeltaXML, $sClassName, $sExpectedComment) { $oDocument = new MFDocument(); $oDocument->loadXML($sDeltaXML); $oXPath = new \DOMXPath($oDocument); $sClassName = DesignDocument::XPathQuote($sClassName); /** @var MFElement $oClassNode */ $oClassNode = $oXPath->query("/itop_design/classes/class[@id=$sClassName]")->item(0); /** @var \DOMComment|null $oCommentNode */ $oCommentNode = ModelFactory::GetPreviousComment($oClassNode); if (is_null($sExpectedComment)) { $this->assertNull($oCommentNode); } else { $this->assertEquals($sExpectedComment, $oCommentNode->textContent); } } public function GetPreviousCommentProvider() { $aData = []; $aData['No Comment first Class'] = [ 'sDeltaXML' => ' ', 'sClassName' => 'A', 'sExpectedComment' => null, ]; $aData['No Comment other Class'] = [ 'sDeltaXML' => ' ', 'sClassName' => 'A', 'sExpectedComment' => null, ]; $aData['Comment first class'] = [ 'sDeltaXML' => ' ', 'sClassName' => 'A', 'sExpectedComment' => ' Test comment ', ]; $aData['Comment other Class'] = [ 'sDeltaXML' => ' ', 'sClassName' => 'A', 'sExpectedComment' => ' Test comment ', ]; return $aData; } /** * @test * @dataProvider FlattenDeltaProvider * @covers ModelFactory::FlattenClassesInDelta * * @param $sDeltaXML * @param $sExpectedXML * * @return void * @throws \ReflectionException */ public function FlattenDeltaTest($sDeltaXML, $sExpectedXML) { $oFactory = new ModelFactory([]); $oDocument = new MFDocument(); $oDocument->loadXML($sDeltaXML); /* @var MFElement $oDeltaRoot */ $oDeltaRoot = $oDocument->firstChild; /** @var MFElement $oFlattenDeltaRoot */ if (is_null($sExpectedXML)) { $this->expectException(\MFException::class); } $oFlattenDeltaRoot = $this->InvokeNonPublicMethod(ModelFactory::class, 'FlattenClassesInDelta', $oFactory, [$oDeltaRoot]); if (!is_null($sExpectedXML)) { $this->AssertEqualiTopXML($sExpectedXML, $oFlattenDeltaRoot->ownerDocument->saveXML()); } } public function FlattenDeltaProvider() { return [ 'Empty delta' => [ 'sDeltaXML' => ' ', 'sExpectedXML' => ' ', ], 'Flat delete' => [ 'sDeltaXML' => ' ', 'sExpectedXML' => ' ', ], 'flat define root' => [ 'sDeltaXML' => ' cmdbAbstractObject cmdbAbstractObject ', 'sExpectedXML' => ' cmdbAbstractObject cmdbAbstractObject ', ], 'flat force root' => [ 'sDeltaXML' => ' cmdbAbstractObject cmdbAbstractObject ', 'sExpectedXML' => ' cmdbAbstractObject cmdbAbstractObject ', ], 'flat redefine root' => [ 'sDeltaXML' => ' cmdbAbstractObject cmdbAbstractObject ', 'sExpectedXML' => ' cmdbAbstractObject cmdbAbstractObject ', ], 'Simple hierarchy define root' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 ', 'sExpectedXML' => ' cmdbAbstractObject C_1 ', ], 'Complex hierarchy delete' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 C_1_1 C_1 ', 'sExpectedXML' => ' cmdbAbstractObject C_1 C_1_1 C_1 ', ], 'Complex hierarchy define root' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 C_1_1 C_1_1_1 C_1 C_1_2 ', 'sExpectedXML' => ' cmdbAbstractObject C_1 C_1_1 C_1_1_1 C_1 C_1_2 ', ], 'Complex hierarchy define' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 C_1_1 C_1_1_1 C_1 C_1_2 ', 'sExpectedXML' => ' cmdbAbstractObject C_1 C_1_1 C_1_1_1 C_1 C_1_2 ', ], 'Complex hierarchy force' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 C_1_1 ', 'sExpectedXML' => ' cmdbAbstractObject C_1 C_1_1 ', ], 'Complex hierarchy force root' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 C_1_1 ', 'sExpectedXML' => ' cmdbAbstractObject C_1 C_1_1 ', ], 'Complex hierarchy redefine' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 C_1_1 ', 'sExpectedXML' => ' cmdbAbstractObject C_1 C_1_1 ', ], 'Complex hierarchy redefine root' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 ', 'sExpectedXML' => ' cmdbAbstractObject C_1 ', ], 'Complex hierarchy define_if_not_exists flattening generates an error' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 C_1_1 ', 'sExpectedXML' => null, ], 'Complex hierarchy if_exists flattening generates an error' => [ 'sDeltaXML' => ' cmdbAbstractObject C_1 C_1_1 ', 'sExpectedXML' => null, ], ]; } /** * @test * @dataProvider LoadDeltaProvider * @covers ModelFactory::LoadDelta * * @param $sInitialXML * @param $sDeltaXML * @param $sExpectedXML * * @return void * @throws \Exception */ public function LoadDeltaTest($sInitialXML, $sDeltaXML, $sExpectedXMLOrErrorMessage) { $oFactory = $this->MakeVanillaModelFactory($sInitialXML); $oFactoryDocument = $this->GetNonPublicProperty($oFactory, 'oDOMDocument'); $sExpectedXML = null; if (\utils::StartsWith(trim($sExpectedXMLOrErrorMessage), '<')) { $sExpectedXML = $sExpectedXMLOrErrorMessage; } // Load the delta $oDocument = new MFDocument(); $oDocument->loadXML($sDeltaXML); /* @var MFElement $oDeltaRoot */ $oDeltaRoot = $oDocument->firstChild; try { $oFactory->LoadDelta($oDeltaRoot, $oFactoryDocument); } catch (\Exception $e) { $this->assertNull($sExpectedXML, 'LoadDelta() should not have failed with exception: '.$e->getMessage()); $this->assertEquals($sExpectedXMLOrErrorMessage, $e->getMessage()); return; } $this->AssertEqualModels($sExpectedXML, $oFactory, 'LoadDelta() did not produce the expected result'); } public function LoadDeltaProvider() { return [ 'empty delta' => [ 'sInitialXML' => ' ', 'sDeltaXML' => '', 'sExpectedXMLOrErrorMessage' => ' ', ], 'merge delta lax mode' => [ 'sInitialXML' => ' ', 'sDeltaXML' => ' cmdbAbstractObject ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject ', ], 'Add a class' => [ 'sInitialXML' => ' ', 'sDeltaXML' => ' cmdbAbstractObject ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject ', ], 'Add a class if not exists (N°6660)' => [ 'sInitialXML' => ' ', 'sDeltaXML' => ' cmdbAbstractObject ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject ', ], 'Conditionally add a class but it already exists' => [ 'sInitialXML' => ' cmdbAbstractObject ', 'sDeltaXML' => ' toto ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject ', ], 'Add a class and subclass in hierarchy' => [ 'sInitialXML' => ' ', 'sDeltaXML' => ' cmdbAbstractObject C_1 ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject C_1 ', ], 'Delete a class' => [ 'sInitialXML' => ' cmdbAbstractObject ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Redefine a recently added subclass' => [ 'sInitialXML' => ' cmdbAbstractObject C_1 ', 'sDeltaXML' => ' C_1 ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject C_1 ', ], 'Delete a recently added class' => [ 'sInitialXML' => ' cmdbAbstractObject ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Delete hierarchically a class' => [ 'sInitialXML' => ' cmdbAbstractObject ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Delete hierarchically a class and subclass' => [ 'sInitialXML' => ' cmdbAbstractObject C_1 ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Delete hierarchically a class and subclass already deleted' => [ 'sInitialXML' => ' cmdbAbstractObject C_1 C_1 C_1_2 ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Delete if exist hierarchically an existing class' => [ 'sInitialXML' => ' cmdbAbstractObject ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Delete if exist hierarchically an non existing class' => [ 'sInitialXML' => ' cmdbAbstractObject ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject ', ], 'Delete if exist hierarchically a removed class' => [ 'sInitialXML' => ' cmdbAbstractObject ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Delete if exist hierarchically an existing class and subclass' => [ 'sInitialXML' => ' cmdbAbstractObject C_1 ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Delete if exist hierarchically a non existing subclass' => [ 'sInitialXML' => ' cmdbAbstractObject C_1 ', 'sDeltaXML' => ' ', 'sExpectedXMLOrErrorMessage' => ' ', ], 'Class comment should be preserved' => [ 'sInitialXML' => ' ', 'sDeltaXML' => ' cmdbAbstractObject cmdbAbstractObject ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject cmdbAbstractObject ', ], 'Delete hierarchically a class and add it again' => [ 'sInitialXML' => ' cmdbAbstractObject C_1 C_1_1 ', 'sDeltaXML' => ' cmdbAbstractObject ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject ', ], 'merge delta strict' => [ 'sInitialXML' => ' ', 'sDeltaXML' => ' cmdbAbstractObject ', 'sExpectedXMLOrErrorMessage' => '/itop_design/classes/class[C_1] at line 3: could not be found or marked as removed (strict mode)', ], 'Redefine classes and changing parent' => [ 'sInitialXML' => ' cmdbAbstractObject cmdbAbstractObject Licence ', 'sDeltaXML' => ' FunctionalCI ConfigElement Key Key ', 'sExpectedXMLOrErrorMessage' => ' cmdbAbstractObject FunctionalCI ConfigElement Key Key ', ], 'Class with wrong parent should generate an error' => [ 'sInitialXML' => ' cmdbAbstractObject ', 'sDeltaXML' => ' toto ', 'sExpectedXMLOrErrorMessage' => "/itop_design/classes/class[C_1]/parent at line 4: invalid parent class 'toto'", ], ]; } /** * @test * @dataProvider AlterationByXMLDeltaProvider * @covers ModelFactory::LoadDelta * @covers ModelFactory::ApplyChanges */ public function AlterationByXMLDeltaTest($sInitialXML, $sDeltaXML, $sExpectedXML) { $oFactory = $this->MakeVanillaModelFactory($sInitialXML); $oFactoryRoot = $this->GetNonPublicProperty($oFactory, 'oDOMDocument'); $oDocument = new MFDocument(); $oDocument->loadXML($sDeltaXML); /* @var MFElement $oDeltaRoot */ $oDeltaRoot = $oDocument->firstChild; if ($sExpectedXML === null) { $this->expectException('Exception'); } $oFactory->LoadDelta($oDeltaRoot, $oFactoryRoot); $oFactory->ApplyChanges(); $this->AssertEqualModels($sExpectedXML, $oFactory); } /** * @return array */ public function AlterationByXMLDeltaProvider() { // Basic (structure) return [ 'No change at all' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], 'No change at all - mini delta' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="merge" implicit' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="merge" explicit (lax)' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="merge" does preserve text in lax mode' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << Maintained Text XML , 'sExpectedXML' => << Maintained Text XML , ], '_delta="merge" recursively' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], // Define or redefine '_delta="define" without id' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="define" with id' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="define" but existing node' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => null, ], '_delta="redefine" without id' => [ 'sInitialXML' => << Initial BB XML , 'sDeltaXML' => << Gainsbourg XML , 'sExpectedXML' => << Gainsbourg XML , ], '_delta="redefine" with id' => [ 'sInitialXML' => << Initial BB XML , 'sDeltaXML' => << Gainsbourg XML , 'sExpectedXML' => << Gainsbourg XML , ], '_delta="redefine" but missing node' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << Gainsbourg XML , 'sExpectedXML' => null, ], '_delta="force" without id + missing node' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << Hulk XML , 'sExpectedXML' => << Hulk XML , ], '_delta="force" with id + missing node' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << Hulk XML , 'sExpectedXML' => << Hulk XML , ], '_delta="force" without id + existing node' => [ 'sInitialXML' => << Initial BB XML , 'sDeltaXML' => << Gainsbourg XML , 'sExpectedXML' => << Gainsbourg XML , ], '_delta="force" with id + existing node' => [ 'sInitialXML' => << Initial BB XML , 'sDeltaXML' => << Gainsbourg XML , 'sExpectedXML' => << Gainsbourg XML , ], // Rename 'rename' => [ 'sInitialXML' => << Kryptonite XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << Kryptonite XML , ], 'rename but missing node NOT INTUITIVE!!!' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], // Delete '_delta="delete" without id' => [ 'sInitialXML' => << Initial BB XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="delete" with id' => [ 'sInitialXML' => << Initial BB XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="delete" but missing node' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => null, ], '_delta="delete_if_exists" without id + existing node' => [ 'sInitialXML' => << Initial BB XML , 'sDeltaXML' => << XML , 'sExpectedXML' => '', ], '_delta="delete_if_exists" with id + existing node' => [ 'sInitialXML' => << Initial BB XML , 'sDeltaXML' => << XML , 'sExpectedXML' => '', ], '_delta="delete_if_exists" without id + missing node' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="delete_if_exists" with id + missing node' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], // Conditionals '_delta="must_exist"' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="must_exist on missing node"' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => null, ], '_delta="if_exists on missing node (lax)' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="if_exists on missing node (strict)' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="if_exists on existing node"' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << XML , 'sExpectedXML' => << XML , ], '_delta="define_if_not_exists on missing node"' => [ 'sInitialXML' => << XML , 'sDeltaXML' => << The incredible Hulk XML , 'sExpectedXML' => << The incredible Hulk XML , ], '_delta="define_if_not_exists on existing node"' => [ 'sInitialXML' => << Luke Banner XML , 'sDeltaXML' => << The incredible Hulk XML , 'sExpectedXML' => << Luke Banner XML , ], '_delta="define_and_must_exits"' => [ 'sInitialXML' => << XML , 'sDeltaXML' => <<