mirror of
https://github.com/Combodo/iTop.git
synced 2026-07-23 15:16:40 +02:00
Compare commits
7 Commits
feature/98
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a808e89c30 | ||
|
|
c6e50a0ed7 | ||
|
|
2cc071b07a | ||
|
|
76a410e00d | ||
|
|
c68887c2bc | ||
|
|
837b433622 | ||
|
|
52fca750b0 |
@@ -134,10 +134,6 @@ class DataCleanupService
|
||||
/** @var DBObject $oDependentObj */
|
||||
while ($oDependentObj = $oSet->Fetch()) {
|
||||
$iDeletePropagationOption = $oExtKeyAttDef->GetDeletionPropagationOption();
|
||||
if ($iDeletePropagationOption == DEL_MANUAL) {
|
||||
$this->oObjectService->SetIssue(get_class($oDependentObj));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($oExtKeyAttDef->IsNullAllowed()) {
|
||||
// Optional external key, list to reset
|
||||
@@ -152,6 +148,12 @@ class DataCleanupService
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Mandatory external key
|
||||
if ($iDeletePropagationOption == DEL_MANUAL) {
|
||||
// Cannot be deleted automatically, must be handled manually
|
||||
$this->oObjectService->SetIssue(get_class($oDependentObj));
|
||||
continue;
|
||||
}
|
||||
// Propagate deletion only if not visited
|
||||
if ($this->IsVisited($oDependentObj)) {
|
||||
continue;
|
||||
|
||||
@@ -59,9 +59,11 @@ class StaticDeletionPlan
|
||||
{
|
||||
foreach ($aClasses as $sClass) {
|
||||
$oDeletionPlanItem = $this->GetInitialClassDeletionPlan($sClass);
|
||||
$oDeletionPlanEntity = new DeletionPlanEntity();
|
||||
$oDeletionPlanEntity->oDelete->Merge($oDeletionPlanItem);
|
||||
$this->aDeletionPlan[$sClass] = $oDeletionPlanEntity;
|
||||
// N°9831 Do not overwrite existing entity as it may already exist for this class if a previously processed class references it (issues/updates already accumulated must be kept)
|
||||
if (false === array_key_exists($sClass, $this->aDeletionPlan)) {
|
||||
$this->aDeletionPlan[$sClass] = new DeletionPlanEntity();
|
||||
}
|
||||
$this->aDeletionPlan[$sClass]->oDelete->Merge($oDeletionPlanItem);
|
||||
|
||||
$this->DeletionPlanForReferencingClasses($sClass);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use DBObjectSet;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
use SetupLog;
|
||||
use TagSetFieldData;
|
||||
|
||||
require_once APPROOT.'setup/feature_removal/ModelReflectionSerializer.php';
|
||||
|
||||
@@ -68,6 +69,10 @@ abstract class AbstractSetupAudit
|
||||
continue;
|
||||
}
|
||||
|
||||
if (MetaModel::IsSameFamily($sClass, TagSetFieldData::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MetaModel::IsStandaloneClass($sClass)) {
|
||||
$iCount = $this->Count($sClass);
|
||||
$this->aFinalClassesToCleanup[$sClass] = $iCount;
|
||||
|
||||
@@ -163,6 +163,58 @@ class DataCleanupServiceTest extends \AbstractCleanup
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
/**
|
||||
* N°9831
|
||||
*
|
||||
* A nullable external key configured as DEL_MANUAL must NOT be treated as an issue: it is simply
|
||||
* reset (set to null), exactly like core DBObject::MakeDeletionPlan does. DEL_MANUAL only blocks
|
||||
* for mandatory keys. This mirrors the real "Hypervisor.farm_id -> Farm" case (nullable + DEL_MANUAL).
|
||||
*
|
||||
* Regression guard for the ordering of the IsNullAllowed()/DEL_MANUAL checks in RecursiveDeletion:
|
||||
* reverting that change would make ExecuteCleanup throw here instead of resetting the object.
|
||||
*/
|
||||
public function testExecuteCleanup_NullableManualShouldResetAndNotThrow()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRManualNullable_1
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$oService = new DataCleanupService();
|
||||
// Must NOT throw: the nullable external key is reset, not reported as an issue
|
||||
$aRes = $oService->ExecuteCleanup($aClasses);
|
||||
|
||||
$aExpected = [
|
||||
['DFRManualNullable', 1, 0 ],
|
||||
['DFRToRemoveLeaf', 0, 1 ],
|
||||
];
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
/**
|
||||
* N°9831
|
||||
*
|
||||
* Summary counterpart of the test above: a nullable DEL_MANUAL external key is reported as an
|
||||
* update (reset), with an issue count of 0. Reverting the RecursiveDeletion change would report
|
||||
* it as an issue instead.
|
||||
*/
|
||||
public function testGetCleanupSummary_NullableManualShouldBeUpdateNotIssue()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRManualNullable_1
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$oService = new DataCleanupService();
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
|
||||
$aExpected = [
|
||||
['DFRManualNullable', 1, 0, 0 ],
|
||||
['DFRToRemoveLeaf', 0, 1, 0 ],
|
||||
];
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
private function AssertSummaryEquals(array $expected, $actual, $sMessage = '')
|
||||
{
|
||||
$aExpected = [];
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval;
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan;
|
||||
use MetaModel;
|
||||
|
||||
require_once __DIR__."/AbstractCleanup.php";
|
||||
class StaticDeletionPlanTest extends \AbstractCleanup
|
||||
@@ -150,6 +149,66 @@ EOF);
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
/**
|
||||
* N°9831
|
||||
*
|
||||
* A DEL_MANUAL issue must still be reported when the referencing class (DFRManual) is itself part
|
||||
* of the classes to remove AND is processed AFTER the class it points to (DFRToRemoveLeaf).
|
||||
*
|
||||
* This mirrors the real "VirtualMachine -> VirtualHost" case: the setup audit sorts the removed
|
||||
* classes alphabetically and drops abstract classes, so a referencing class such as VirtualMachine
|
||||
* ends up processed after its concrete targets (Farm/Hypervisor/Cloud). Here DFRToRemove is abstract
|
||||
* and DFRToRemoveLeaf is its concrete child, while DFRManual points to DFRToRemove with a mandatory
|
||||
* DEL_MANUAL external key.
|
||||
*
|
||||
* Regression: the plan entity accumulating the issue used to be overwritten when the referencing
|
||||
* class was processed as a top-level class, silently dropping the issue (no blocking message on the
|
||||
* summary page, then a crash at execution time).
|
||||
*/
|
||||
public function testGetCleanupSummary_IssueKeptWhenReferencingClassListedAfterTarget(): void
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRToRemoveLeaf (name = DFRToRemoveLeaf_1)
|
||||
create DFRManual (name = DFRManual_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
EOF);
|
||||
|
||||
// Target listed BEFORE the referencing class, as produced by the alphabetically sorted audit
|
||||
$aClasses = ['DFRToRemoveLeaf', 'DFRManual'];
|
||||
$oService = new StaticDeletionPlan();
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
|
||||
$aExpected = [
|
||||
'DFRToRemoveLeaf' => ['iDeleteCount' => 1],
|
||||
'DFRManual' => ['iDeleteCount' => 1, 'iIssueCount' => 1],
|
||||
];
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
/**
|
||||
* N°9831
|
||||
*
|
||||
* Control case: the same issue must also be reported when the referencing class is listed BEFORE
|
||||
* its target (this order already worked, e.g. "Enclosure -> Rack"). Together with the test above,
|
||||
* this locks in that issue detection is independent of the processing order.
|
||||
*/
|
||||
public function testGetCleanupSummary_IssueKeptWhenReferencingClassListedBeforeTarget(): void
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRToRemoveLeaf (name = DFRToRemoveLeaf_1)
|
||||
create DFRManual (name = DFRManual_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = ['DFRManual', 'DFRToRemoveLeaf'];
|
||||
$oService = new StaticDeletionPlan();
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
|
||||
$aExpected = [
|
||||
'DFRToRemoveLeaf' => ['iDeleteCount' => 1],
|
||||
'DFRManual' => ['iDeleteCount' => 1, 'iIssueCount' => 1],
|
||||
];
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testCircularRefsShouldNotRunInfinitely()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
|
||||
@@ -319,6 +319,58 @@
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRManualNullable" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrmanualnullable</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extkey_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<target_class>DFRToRemove</target_class>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extkey_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRLeafNotToRemove" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
@@ -718,6 +770,14 @@
|
||||
<entry id="Class:DFRManual/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManual/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
|
||||
<entry id="Class:DFRManual/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManualNullable/Name" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManualNullable/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManualNullable" _delta="define"><![CDATA[DFRManualNullable]]></entry>
|
||||
<entry id="Class:DFRManualNullable+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManualNullable/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||
<entry id="Class:DFRManualNullable/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||
<entry id="Class:DFRManualNullable/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
|
||||
<entry id="Class:DFRManualNullable/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||
</entries>
|
||||
</dictionary>
|
||||
</dictionaries>
|
||||
|
||||
@@ -79,6 +79,7 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase
|
||||
"Feature1Module1MyClass",
|
||||
"FinalClassFeature2Module1MyClass",
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation",
|
||||
"TagSetFieldDataFor_FinalClassFeature2Module1MyFinalClassFromLocation__domains",
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->GetRemovedClasses());
|
||||
|
||||
|
||||
@@ -64,6 +64,12 @@
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
</field>
|
||||
<field id="domains" xsi:type="AttributeTagSet">
|
||||
<sql>domains</sql>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
<max_items>12</max_items>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
<field id="domains" xsi:type="AttributeTagSet">
|
||||
<sql>domains</sql>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
<max_items>12</max_items>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
|
||||
Reference in New Issue
Block a user