mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-19 23:32:17 +02:00
360 lines
11 KiB
PHP
360 lines
11 KiB
PHP
<?php
|
|
|
|
/*
|
|
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
|
* @license http://opensource.org/licenses/AGPL-3.0
|
|
*/
|
|
|
|
namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval\Service;
|
|
|
|
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
|
use Combodo\iTop\DataFeatureRemoval\Service\DataCleanupService;
|
|
use Combodo\iTop\Service\Limits\ExecutionLimits;
|
|
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
|
|
/**
|
|
* Unit tests for the CleanupService cf the Combodo Data Feature Removal module.
|
|
*
|
|
* These tests cover:
|
|
* - GetCleanupSummary method: handling null and empty input, and verifying summary output for various delete/update scenarios.
|
|
* - ExecuteCleanup method: confirming that an exception is thrown when issues are detected in the deletion plan.
|
|
*
|
|
* Key aspects tested:
|
|
* - Consistent singleton instance management.
|
|
* - Accurate summary generation for deletion and update operations, including mode and issue reporting per class.
|
|
* - Edge cases such as null, empty, and multiple classes.
|
|
* - Proper exception handling when the deletion plan contains issues.
|
|
*
|
|
* The tests use PHPUnit, mocks for Cleanup and CleanupService, and data providers to cover multiple scenarios.
|
|
*
|
|
* @see DataCleanupService
|
|
* @see DataCleanupSummaryEntity
|
|
* @see ItopDataTestCase
|
|
*/
|
|
class DataCleanupServiceTest extends ItopCustomDatamodelTestCase
|
|
{
|
|
private ExecutionLimits&MockObject $oExecutionLimits;
|
|
|
|
public function GetDatamodelDeltaAbsPath(): string
|
|
{
|
|
return __DIR__.'/data_cleanup_delta.xml';
|
|
}
|
|
|
|
//--- GetCleanupSummary tests ---
|
|
|
|
/**
|
|
* Tests that GetCleanupSummary returns an empty array when passed null as input.
|
|
*/
|
|
public function testGetCleanupSummaryReturnsEmptyArrayWhenNull(): void
|
|
{
|
|
$oService = new DataCleanupService();
|
|
$aResult = $oService->GetCleanupSummary(null);
|
|
|
|
$this->assertIsArray($aResult, 'Expected result to be an array when input is null.');
|
|
$this->assertEmpty($aResult, 'Expected result to be empty array when input is null.');
|
|
}
|
|
|
|
//--- ExecuteCleanup tests ---
|
|
|
|
public function testExecuteCleanup_DeleteOneObjPerClassWithoutLimit()
|
|
{
|
|
$this->GivenDFRTreeInDB(<<<EOF
|
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
|
EOF);
|
|
|
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
|
$oService = new DataCleanupService();
|
|
$aRes = $oService->ExecuteCleanup($aClasses);
|
|
$aExpected = [
|
|
['DFRToUpdate', 1, 0 ],
|
|
['DFRToRemoveLeaf', 0, 1 ],
|
|
['DFRRemovedCollateral', 0, 1 ],
|
|
['DFRRemovedCollateralCascade', 0, 1 ],
|
|
];
|
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
|
}
|
|
|
|
public function testExecuteCleanup_DeleteManyObjPerClassWithoutLimit()
|
|
{
|
|
$this->GivenDFRTreeInDB(<<<EOF
|
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
|
|
|
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
|
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
|
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
|
|
|
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
|
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
|
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
|
EOF);
|
|
|
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
|
$oService = new DataCleanupService();
|
|
$aRes = $oService->ExecuteCleanup($aClasses);
|
|
$aExpected = [
|
|
['DFRToUpdate', 3, 0 ],
|
|
['DFRToRemoveLeaf', 0, 3 ],
|
|
['DFRRemovedCollateral', 0, 3 ],
|
|
['DFRRemovedCollateralCascade', 0, 3 ],
|
|
];
|
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
|
}
|
|
|
|
public function testExecuteCleanup_CheckSummaryIsEnrichedAfterEachPass()
|
|
{
|
|
$aExecutionSummary = [];
|
|
|
|
$this->GivenDFRTreeInDB(
|
|
<<<EOF
|
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
|
|
|
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
|
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
|
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
|
|
|
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
|
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
|
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
|
EOF
|
|
);
|
|
|
|
$aClasses = ['DFRToRemoveLeaf'];
|
|
$oService = new DataCleanupService();
|
|
$aExecutionSummary = $oService->ExecuteCleanup($aClasses);
|
|
|
|
$aExpected = [
|
|
['DFRToUpdate', 3, 0 ],
|
|
['DFRToRemoveLeaf', 0, 3 ],
|
|
['DFRRemovedCollateral', 0, 3 ],
|
|
['DFRRemovedCollateralCascade', 0, 3 ],
|
|
];
|
|
$this->AssertSummaryEquals($aExpected, $aExecutionSummary);
|
|
|
|
$this->GivenDFRTreeInDB(
|
|
<<<EOF
|
|
DFRToRemoveLeaf_4 <- DFRToUpdate_4
|
|
DFRToRemoveLeaf_4 <- DFRRemovedCollateral_4
|
|
DFRRemovedCollateral_4 <- DFRRemovedCollateralCascade_4
|
|
EOF
|
|
);
|
|
|
|
$aClasses = ['DFRToRemoveLeaf'];
|
|
$oService = new DataCleanupService();
|
|
$aExecutionSummary = $oService->ExecuteCleanup($aClasses);
|
|
|
|
$aExpected = [
|
|
['DFRToUpdate', 1, 0, 0, 4 ],
|
|
['DFRToRemoveLeaf', 0, 1, 0, 0, 4 ],
|
|
['DFRRemovedCollateral', 0, 1, 0, 0, 4 ],
|
|
['DFRRemovedCollateralCascade', 0, 1, 0, 0, 4 ],
|
|
];
|
|
$this->AssertSummaryEquals($aExpected, $aExecutionSummary);
|
|
}
|
|
|
|
public function testGetCleanupSummary_DeleteManyObjPerClassWithoutLimit()
|
|
{
|
|
$this->GivenDFRTreeInDB(<<<EOF
|
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
|
|
|
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
|
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
|
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
|
|
|
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
|
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
|
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
|
EOF);
|
|
|
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
|
$oService = new DataCleanupService();
|
|
$aRes = $oService->GetCleanupSummary($aClasses);
|
|
$aExpected = [
|
|
['DFRToUpdate', 3, 0 ],
|
|
['DFRToRemoveLeaf', 0, 3 ],
|
|
['DFRRemovedCollateral', 0, 3 ],
|
|
['DFRRemovedCollateralCascade', 0, 3 ],
|
|
];
|
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
|
}
|
|
|
|
public function testExecuteCleanup_ManualDeleteShouldFail()
|
|
{
|
|
$this->GivenDFRTreeInDB(<<<EOF
|
|
DFRToRemoveLeaf_1 <- DFRManual_1
|
|
EOF);
|
|
|
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
|
$this->expectException(DataFeatureRemovalException::class);
|
|
$this->expectExceptionMessage('Deletion Plan cannot be executed due to issues');
|
|
$oService = new DataCleanupService();
|
|
$oService->ExecuteCleanup($aClasses);
|
|
}
|
|
|
|
public function testGetCleanupSummary_ManualDeleteShouldFail()
|
|
{
|
|
$this->GivenDFRTreeInDB(<<<EOF
|
|
DFRToRemoveLeaf_1 <- DFRManual_1
|
|
DFRToRemoveLeaf_1 <- DFRManual_2
|
|
DFRToRemoveLeaf_2 <- DFRManual_3
|
|
EOF);
|
|
|
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
|
$oService = new DataCleanupService();
|
|
$aRes = $oService->GetCleanupSummary($aClasses);
|
|
$aExpected = [
|
|
['DFRManual', 0, 0, 3 ],
|
|
['DFRToRemoveLeaf', 0, 2],
|
|
];
|
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
|
}
|
|
|
|
private function AssertSummaryEquals(array $expected, $actual, $sMessage = '')
|
|
{
|
|
$aExpected = [];
|
|
foreach ($expected as $line) {
|
|
$sClass = $line[0];
|
|
$iUpdate = $line[1];
|
|
$iDelete = $line[2];
|
|
$iIssue = $line[3] ?? 0;
|
|
$iTotalUpdate = $line[4] ?? $iUpdate;
|
|
$iTotalDelete = $line[5] ?? $iDelete;
|
|
|
|
$oCleanupSummaryEntity = new DataCleanupSummaryEntity($sClass);
|
|
$oCleanupSummaryEntity->iUpdateCount = $iUpdate;
|
|
$oCleanupSummaryEntity->iDeleteCount = $iDelete;
|
|
$oCleanupSummaryEntity->iIssueCount = $iIssue;
|
|
$oCleanupSummaryEntity->iTotalUpdateCount = $iTotalUpdate;
|
|
$oCleanupSummaryEntity->iTotalDeleteCount = $iTotalDelete;
|
|
|
|
$aExpected[$sClass] = $oCleanupSummaryEntity;
|
|
}
|
|
$this->assertEquals($aExpected, $actual, $sMessage);
|
|
}
|
|
|
|
public static function ExecuteCleanup_StopInProcessKeepDatabaseOk(): array
|
|
{
|
|
return [
|
|
'Stop after 1' => [
|
|
1,
|
|
[
|
|
['DFRToUpdate', 1, 0],
|
|
],
|
|
],
|
|
'Stop after 2' => [
|
|
2,
|
|
[
|
|
['DFRToUpdate', 1, 0],
|
|
['DFRRemovedCollateralCascade', 0, 1],
|
|
],
|
|
],
|
|
'Stop after 3' => [
|
|
3,
|
|
[
|
|
['DFRToUpdate', 1, 0],
|
|
['DFRRemovedCollateralCascade', 0, 1],
|
|
['DFRRemovedCollateral', 0, 1],
|
|
],
|
|
],
|
|
'Stop after 4' => [
|
|
4,
|
|
[
|
|
['DFRToUpdate', 1, 0],
|
|
['DFRRemovedCollateralCascade', 0, 1],
|
|
['DFRRemovedCollateral', 0, 1],
|
|
['DFRToRemoveLeaf', 0, 1],
|
|
],
|
|
],
|
|
'Stop after 5' => [
|
|
5,
|
|
[
|
|
['DFRToUpdate', 2, 0],
|
|
['DFRRemovedCollateralCascade', 0, 1],
|
|
['DFRRemovedCollateral', 0, 1],
|
|
['DFRToRemoveLeaf', 0, 1],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ExecuteCleanup_StopInProcessKeepDatabaseOk
|
|
*/
|
|
public function testExecuteCleanup_StopInProcessKeepDatabaseOk(int $iExecutionCount, array $aExpected): void
|
|
{
|
|
$this->GivenDFRTreeInDB(<<<EOF
|
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
|
|
|
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
|
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
|
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
|
|
|
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
|
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
|
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
|
EOF);
|
|
|
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
|
$oDeletionPlaService = new DataCleanupService();
|
|
$this->GivenExecutionLimits($iExecutionCount);
|
|
$this->SetNonPublicProperty($oDeletionPlaService, 'oExecutionLimits', $this->oExecutionLimits);
|
|
$aRes = $oDeletionPlaService->ExecuteCleanup($aClasses);
|
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
|
}
|
|
|
|
private function GivenDFRTreeInDB(string $sTree)
|
|
{
|
|
$aTree = explode("\n", $sTree);
|
|
foreach ($aTree as $sLine) {
|
|
if (trim($sLine) === "") {
|
|
continue;
|
|
}
|
|
$this->GivenDFRTreeLineInDB($sLine);
|
|
}
|
|
}
|
|
|
|
private array $aIdByObjectName = [];
|
|
private function GivenDFRTreeLineInDB(string $sLine)
|
|
{
|
|
[$sLeft, $sRight] = explode('<-', $sLine);
|
|
$sLeft = trim($sLeft);
|
|
|
|
$iLeftId = $this->aIdByObjectName[$sLeft] ?? 0;
|
|
if ($iLeftId === 0) {
|
|
[$sChildClass, ] = explode('_', $sLeft, 2);
|
|
$iLeftId = $this->GivenObjectInDB($sChildClass, ['name' => $sLeft]);
|
|
$this->aIdByObjectName[$sLeft] = $iLeftId;
|
|
}
|
|
|
|
$sRight = trim($sRight);
|
|
[$sChildClass, ] = explode('_', $sRight, 2);
|
|
$iRightId = $this->GivenObjectInDB($sChildClass, ['name' => $sRight, 'extkey_id' => $iLeftId]);
|
|
$this->aIdByObjectName[$sRight] = $iRightId;
|
|
}
|
|
|
|
private function GivenExecutionLimits(int $iStopAfterCallNumberReached): void
|
|
{
|
|
$matcher = $this->any();
|
|
|
|
$this->oExecutionLimits = $this->createMock(ExecutionLimits::class);
|
|
$this->oExecutionLimits->expects($matcher)
|
|
->method('ShouldStopExecution')->willReturnCallback(function () use ($matcher, $iStopAfterCallNumberReached) {
|
|
$invocationCount = $matcher->getInvocationCount();
|
|
|
|
return ($invocationCount >= $iStopAfterCallNumberReached);
|
|
});
|
|
}
|
|
}
|