Merge branch 'develop' into feature/uninstallation

This commit is contained in:
odain
2025-12-19 18:45:34 +01:00
committed by Eric Espie
1460 changed files with 133879 additions and 6193 deletions

View File

@@ -73,7 +73,7 @@ class QueryTest extends ItopDataTestCase
* @param string $sOql query oql phrase
* @param string|null $sFields fields to export
*/
private function CreateQueryOQL(string $sName, string $sDescription, string $sOql, string $sFields = null): QueryOQL
private function CreateQueryOQL(string $sName, string $sDescription, string $sOql, ?string $sFields = null): QueryOQL
{
$oQuery = new QueryOQL();
$oQuery->Set('name', $sName);

View File

@@ -936,4 +936,46 @@ class utilsTest extends ItopTestCase
],
];
}
public function testLoadParamFile()
{
$sTmpFileInsideItop = APPROOT.'data/test/testLoadParamFile.params';
$sDir = dirname($sTmpFileInsideItop);
if (!is_dir($sDir)) {
mkdir($sDir, 0777, true);
}
$sParamName = 'IP1';
$sParamValue = 'IV1';
$sParams = <<<INI
# comment
$sParamName = $sParamValue
INI;
file_put_contents($sTmpFileInsideItop, $sParams);
try {
$this->expectException(\Exception::class);
$this->expectExceptionMessage("File '$sTmpFileInsideItop' should be outside iTop");
self::InvokeNonPublicStaticMethod(utils::class, 'LoadParamFile', [$sTmpFileInsideItop]);
self::assertNotEquals($sParamValue, utils::ReadParam($sParamName, null), "utils::LoadParamFile() should NOT have loaded the file: $sTmpFileInsideItop");
} finally {
if (file_exists($sTmpFileInsideItop)) {
unlink($sTmpFileInsideItop);
}
}
$sParamName = 'OP2';
$sParamValue = 'OV2';
$sTmpFileOutsideItop = tempnam(sys_get_temp_dir(), 'utils-test');
$sParams = <<<INI
# comment
$sParamName = $sParamValue
INI;
file_put_contents($sTmpFileOutsideItop, $sParams);
self::InvokeNonPublicStaticMethod(utils::class, 'LoadParamFile', [$sTmpFileOutsideItop]);
self::assertEquals($sParamValue, utils::ReadParam($sParamName, null), "utils::LoadParamFile() should have loaded the file: $sTmpFileOutsideItop");
unlink($sTmpFileOutsideItop);
}
}

View File

@@ -13,13 +13,6 @@ class AttributeDefinitionTest extends ItopDataTestCase
{
public const CREATE_TEST_ORG = true;
protected function setUp(): void
{
parent::setUp();
require_once(APPROOT.'core/attributedef.class.inc.php');
}
public function testGetImportColumns()
{
$oAttributeDefinition = MetaModel::GetAttributeDef("ApplicationSolution", "status");

View File

@@ -0,0 +1,79 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use AttributeText;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class AttributeTextTest extends ItopDataTestCase
{
protected \Organization $oTestOrganizationForAttributeText;
public function setUp(): void
{
parent::setUp();
$this->oTestOrganizationForAttributeText = $this->CreateOrganization('Test for AttributeTextTest');
}
/**
* @covers AttributeText::RenderWikiHtml
*/
public function testRenderWikiHtml_nonWikiUrlVariants()
{
// String value
$sInput = 'This hyperlink https://combodo.com should be in an anchor tag.';
$sExpected = 'This hyperlink <a href="https://combodo.com">https://combodo.com</a> should be in an anchor tag.';
$this->assertEquals($sExpected, AttributeText::RenderWikiHtml($sInput));
// Empty string value
$this->assertEquals('', AttributeText::RenderWikiHtml(''));
// Null value
$this->assertEquals('', AttributeText::RenderWikiHtml(null));
}
/**
* @covers AttributeText::RenderWikiHtml
*/
public function testRenderWikiHtml_bWikiOnlyAbsentOrFalse_shouldTransformBothRegularAndWikiHyperlinks()
{
$sInput = 'A regular hyperlink https://combodo.com and a wiki hyperlink to an existing object [[Organization:'.$this->oTestOrganizationForAttributeText->GetKey().']]';
// bWikiOnly default value
$sResult = AttributeText::RenderWikiHtml($sInput);
$this->assertStringContainsString('<a href="https://combodo.com">', $sResult);
$this->assertStringContainsString('class="object-ref-link"', $sResult);
// bWikiOnly = false
$sResult = AttributeText::RenderWikiHtml($sInput, false);
$this->assertStringContainsString('<a href="https://combodo.com">', $sResult);
$this->assertStringContainsString('class="object-ref-link"', $sResult);
}
/**
* @covers AttributeText::RenderWikiHtml
*/
public function testRenderWikiHtml_bWikiOnlyToTrue_shouldNotTransformRegularHyperlinkButTransformWikiHyperlink()
{
$sInput = 'A regular hyperlink https://combodo.com and a wiki hyperlink to an existing object [[Organization:'.$this->oTestOrganizationForAttributeText->GetKey().']]';
$sResult = AttributeText::RenderWikiHtml($sInput, true);
$this->assertStringNotContainsString('<a href="https://combodo.com">', $sResult);
$this->assertStringContainsString('class="object-ref-link"', $sResult);
}
/**
* @covers AttributeText::RenderWikiHtml
*/
public function testRenderWikiHtml_shouldTransformWikiHyperlinkForExistingObjectsOnly()
{
$sInput = 'A wiki hyperlink to a non existing object [[Organization:123456789]] and a wiki hyperlink to an existing object [[Organization:'.$this->oTestOrganizationForAttributeText->GetKey().']]';
$sResult = AttributeText::RenderWikiHtml($sInput);
$this->assertStringContainsString('wiki_broken_link', $sResult);
$this->assertStringContainsString('class="object-ref-link"', $sResult);
}
}

View File

@@ -80,7 +80,7 @@ class BulkChangeTest extends ItopDataTestCase
* @param $aExtKeys
* @param $aReconcilKeys
*/
public function testBulkChangeWithoutInitData($aCSVData, $aAttributes, $aExtKeys, $aReconcilKeys, $aResult, array $aResultHTML = null)
public function testBulkChangeWithoutInitData($aCSVData, $aAttributes, $aExtKeys, $aReconcilKeys, $aResult, ?array $aResultHTML = null)
{
$this->debug("aReconcilKeys:".$aReconcilKeys[0]);
$oBulk = new BulkChange(

View File

@@ -158,4 +158,173 @@ EOF;
$this->assertEquals($sExpectedValue, $data);
}
/**
* @dataProvider OrganizationsForExportSanitizeExcelExportProvider
*
* @param $aListOrg
* @param $aExpectedValues
* @return void
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \OQLException
* @throws \ReflectionException
*/
public function testExportWithSanitizeExcelExport(
$aListOrg,
$aExpectedValues,
) {
// Create tests organizations to have enough data
$iFirstOrg = 0;
foreach ($aListOrg as $aOrg) {
$oObj = $this->CreateOrganization($aOrg[0]);
if ($aOrg[1] === false) {
$oObj->Set('status', 'inactive');
$oObj->DBUpdate();
}
if ($iFirstOrg === 0) {
$iFirstOrg = $oObj->GetKey();
}
}
$aStatusInfo = [
"fields" => [
[
"sFieldSpec" => "name",
"sAlias" => "Organization",
"sClass" => "Organization",
"sAttCode" => "name",
"sLabel" => "Name",
"sColLabel" => "Name",
],
],
"text_qualifier" => "\"",
"charset" => "UTF-8",
"separator" => ",",
"date_format" => "Y-m-d H:i:s",
"formatted_text" => false,
"show_obsolete_data" => false,
'ignore_excel_sanitization' => false,
];
$sStatus = [];
$oSearch = DBObjectSearch::FromOQL('SELECT Organization');
$oExporter = BulkExport::FindExporter('csv', $oSearch);
$oExporter->SetStatusInfo($aStatusInfo);
$oExporter->SetObjectList($oSearch);
$oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE);
$data = $oExporter->GetHeader();
$data .= $oExporter->GetNextChunk($sStatus);
// Check that the value is sanitized as expected (with a ' prefix)
foreach ($aExpectedValues as $sExpectedValue) {
$this->assertStringContainsString($sExpectedValue, $data, "The value $sExpectedValue is expected to be found in the export result");
}
}
/**
* @dataProvider OrganizationsForExportSanitizeExcelExportProvider
*
* @param $aListOrg
* @param $aExpectedValues
* @return void
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \OQLException
* @throws \ReflectionException
*/
public function testExportWithoutSanitizeExcelExport(
$aListOrg,
$aExpectedValues,
) {
// Create tests organizations to have enough data
$iFirstOrg = 0;
foreach ($aListOrg as $aOrg) {
$oObj = $this->CreateOrganization($aOrg[0]);
if ($aOrg[1] === false) {
$oObj->Set('status', 'inactive');
$oObj->DBUpdate();
}
if ($iFirstOrg === 0) {
$iFirstOrg = $oObj->GetKey();
}
}
$aStatusInfo = [
"fields" => [
[
"sFieldSpec" => "name",
"sAlias" => "Organization",
"sClass" => "Organization",
"sAttCode" => "name",
"sLabel" => "Name",
"sColLabel" => "Name",
],
],
"text_qualifier" => "\"",
"charset" => "UTF-8",
"separator" => ",",
"date_format" => "Y-m-d H:i:s",
"formatted_text" => false,
"show_obsolete_data" => false,
'ignore_excel_sanitization' => true,
];
$sStatus = [];
$oSearch = DBObjectSearch::FromOQL('SELECT Organization');
$oExporter = BulkExport::FindExporter('csv', $oSearch);
$oExporter->SetStatusInfo($aStatusInfo);
$oExporter->SetObjectList($oSearch);
$oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE);
$data = $oExporter->GetHeader();
$data .= $oExporter->GetNextChunk($sStatus);
// Check that the value is not sanitized
foreach ($aListOrg as $sExpectedValue) {
$this->assertStringContainsString($sExpectedValue[0], $data, "The value $sExpectedValue[0] is expected to be found in the export result");
}
}
public function OrganizationsForExportSanitizeExcelExportProvider()
{
return [
'Page1' => [
'list_org' => [
['=org1', true],
['+org2', true],
['-org3', true],
['@org4', true],
["\t=org5", true],
["\rorg6", true],
["\r\t\r =org7", true],
['org8', true],
['org9', true],
['org10', true],
['org11', true],
['|org12', true],
['%3Dorg13', true],
['%3dorg14', true],
['org15', true],
],
'export_org' => [
"'=org1",
"'+org2",
"'-org3",
"'@org4",
"'\t=org5",
"'\rorg6",
"'\r\t\r =org7",
"'org8",
"'org9",
"'org10",
"'org11",
"'|org12",
"'%3Dorg13",
"'%3dorg14",
"org15",
],
],
];
}
}

View File

@@ -77,7 +77,7 @@ class CRUDEventReceiver
}
}
public function RegisterCRUDEventListeners(string $sEvent = null, $mEventSource = null)
public function RegisterCRUDEventListeners(?string $sEvent = null, $mEventSource = null)
{
$this->Debug('Registering Test event listeners');
if (is_null($sEvent)) {

View File

@@ -183,4 +183,14 @@ class DBUnionSearchTest extends ItopDataTestCase
],
];
}
public function testAllowAllDataOnUnions()
{
$oSearch = \DBObjectSearch::FromOQL('SELECT Server UNION SELECT VirtualMachine');
$oSearch->AllowAllData(false);
self::assertFalse($oSearch->IsAllDataAllowed(), 'DBUnionSearch AllowData value');
$oSearch->AllowAllData(true);
self::assertTrue($oSearch->IsAllDataAllowed(), 'DBUnionSearch AllowData value');
}
}

View File

@@ -9,6 +9,7 @@ namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use InlineImage;
use ormDocument;
class InlineImageTest extends ItopDataTestCase
{
@@ -59,4 +60,75 @@ class InlineImageTest extends ItopDataTestCase
],
];
}
/**
* @covers InlineImage::FixUrls
*/
public function testFixUrls_shouldReturnAnEmptyStringIfNullOrEmptyStringPassed()
{
$sResult = InlineImage::FixUrls(null);
$this->assertEquals('', $sResult);
$sResult = InlineImage::FixUrls('');
$this->assertEquals('', $sResult);
}
/**
* @covers InlineImage::FixUrls
*/
public function testFixUrls_shouldReturnUnchangedValueIfValueContainsNoImage()
{
$sHtml = '<div><p>Texte sans image</p></div>';
$sResult = InlineImage::FixUrls($sHtml);
$this->assertEquals($sHtml, $sResult);
}
/**
* @covers InlineImage::FixUrls
*/
public function testFixUrls_shouldReplaceImagesSrcWithCurrentAppRootUrlAndSecret()
{
$sHtml = <<<HTML
<div>
<img src="/images/test1.png" data-img-id="123" data-img-secret="abc" />
<img src="/images/test2.png" data-img-id="456" data-img-secret="def" />
</div>
HTML;
$sResult = InlineImage::FixUrls($sHtml);
$this->assertStringContainsString('<img', $sResult);
$this->assertStringContainsString(\utils::EscapeHtml(\utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.'123&s=abc'), $sResult);
$this->assertStringContainsString(\utils::EscapeHtml(\utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.'456&s=def'), $sResult);
}
/**
* @covers InlineImage::ReplaceInlineImagesWithBase64Representation
*/
public function testReplaceInlineImagesWithBase64Representation()
{
// create an inline image in the database
$oInlineImage = $this->createObject(InlineImage::class, [
'expire' => (new \DateTime('+1 day'))->format('Y-m-d H:i:s'),
'item_class' => 'UserRequest',
'item_id' => 999,
'item_org_id' => 1,
'contents' => new ormDocument('0x89504E470D0A1A0A0000000D494844520000000E0000000E08060000001F482DD1000000017352474200AECE1CE90000000467414D410000B18F0BFC6105000000097048597300000EC300000EC301C76FA8640000001E49444154384F63782BA3F29F1CCC802E402C1ED588078F6AC483E9AF11008B8BA9C08A7A3F290000000049454E44AE426082', 'image/png', 'square_red.png'),
'secret' => 'a94bff3ea6a872bdbc359a1704cdddb3',
]);
$sInlineImageId = $oInlineImage->GetKey();
$sInlineImageSecret = $oInlineImage->Get('secret');
// HTML with inline image
$sHtml = <<<HTML
<img src="http://host/iTop/pages/ajax.document.php?operation=download_inlineimage&amp;id=$sInlineImageId&amp;s=$sInlineImageSecret" data-img-id="$sInlineImageId" data-img-secret="$sInlineImageSecret" />
HTML;
// expected HTML with base64 representation of the image
$sExpected = <<<HTML
<img src="data:image/png;base64,MHg4OTUwNEU0NzBEMEExQTBBMDAwMDAwMEQ0OTQ4NDQ1MjAwMDAwMDBFMDAwMDAwMEUwODA2MDAwMDAwMUY0ODJERDEwMDAwMDAwMTczNTI0NzQyMDBBRUNFMUNFOTAwMDAwMDA0Njc0MTRENDEwMDAwQjE4RjBCRkM2MTA1MDAwMDAwMDk3MDQ4NTk3MzAwMDAwRUMzMDAwMDBFQzMwMUM3NkZBODY0MDAwMDAwMUU0OTQ0NDE1NDM4NEY2Mzc4MkJBM0YyOUYxQ0NDODAyRTQwMkMxRUQ1ODgwNzhGNkFDNDgzRTlBRjExMDA4QjhCQTlDMDhBN0EzRjI5MDAwMDAwMDA0OTQ1NEU0NEFFNDI2MDgy" />
HTML;
// test the method
$sResult = InlineImage::ReplaceInlineImagesWithBase64Representation($sHtml);
$this->assertEquals($sExpected, $sResult);
}
}

View File

@@ -220,7 +220,7 @@ class UserLocalTest extends ItopDataTestCase
],
'expectedCheckStatus' => false,
'expectedCheckIssues' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
'expectedCheckIssues' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.',
'userLanguage' => 'EN US',
],
'notValidPattern custom message string not array' => [

View File

@@ -64,36 +64,6 @@ class ModelFactoryTest extends ItopTestCase
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.
*/

View File

@@ -0,0 +1,72 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\sources\Forms;
use Combodo\iTop\Forms\Block\AbstractFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\Forms\IO\FormBlockIOException;
use Combodo\iTop\Forms\IO\FormInput;
use Combodo\iTop\Forms\IO\FormOutput;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ReflectionClass;
/**
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
abstract class AbstractFormsTest extends ItopDataTestCase
{
/**
* @throws FormBlockIOException
*/
public function GivenInput(string $sName, string $sType = StringIOFormat::class): FormInput
{
$oBlock = $this->GivenFormBlock($sName);
$oInput = new FormInput($sName, $sType);
$oInput->SetOwnerBlock($oBlock);
return $oInput;
}
/**
* @throws FormBlockIOException
*/
public function GivenOutput(string $sName, string $sType = StringIOFormat::class): FormOutput
{
$oBlock = $this->GivenFormBlock($sName);
$oOutput = new FormOutput($sName, $sType);
$oOutput->SetOwnerBlock($oBlock);
return $oOutput;
}
public function GivenFormBlock(string $sName): FormBlock
{
return new FormBlock($sName, []);
}
public function GivenSubFormBlock(FormBlock $oParent, string $sName, string $ssBlockClass = FormBlock::class): AbstractFormBlock
{
$oParent->Add($sName, $ssBlockClass, []);
return $oParent->Get($sName);
}
public function GivenIORegister(AbstractFormBlock $oFormBlock): IORegister
{
$reflection = new ReflectionClass(AbstractFormBlock::class);
$reflection_property = $reflection->getProperty('oIORegister');
$reflection_property->setAccessible(true);
return $reflection_property->getValue($oFormBlock);
}
}

View File

@@ -0,0 +1,127 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Sources\Forms\Block;
use Combodo\iTop\Forms\Block\AbstractTypeFormBlock;
use Combodo\iTop\Forms\Block\Base\CheckboxFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\Base\TextFormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\Block\IFormBlock;
use Combodo\iTop\Forms\Forms;
use Combodo\iTop\ItopSdkFormDemonstrator\Form\Block\Dashboard\GenericDashlet;
use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
use OutOfBoundsException;
use ReflectionException;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
/**
* Test forms block.
*
*/
class BlockTest extends AbstractFormsTest
{
/**
* Block get form type must return a class derived from Symfony form AbstractType.
*
* @throws ReflectionException
*/
public function testGetFormTypeReturnSymfonyType(): void
{
$aFormBlocks = InterfaceDiscovery::GetInstance()->FindItopClasses(iFormBlock::class);
foreach ($aFormBlocks as $sFormBlock) {
$oChoiceBlock = new ($sFormBlock)($sFormBlock);
if ($oChoiceBlock instanceof AbstractTypeFormBlock) {
if (!$oChoiceBlock instanceof GenericDashlet) {
$oClass = new \ReflectionClass($oChoiceBlock->GetFormType());
$this->assertTrue($oClass->isSubclassOf(AbstractType::class));
}
}
}
}
/**
* Pass a Symfony type instead of a FormBlock type will raise an exception
*
* @throws ReflectionException
*/
public function testAddChildBlockExpectFormBlockClass(): void
{
$oFormBlock = new FormBlock('formBlock');
$this->expectException(FormBlockException::class);
$oFormBlock->Add('wrong', TextType::class, []);
}
/**
* All block must contain a reference to themselves in their options
*/
public function testBlockOptionsContainsBlockReference(): void
{
$aFormBlocks = InterfaceDiscovery::GetInstance()->FindItopClasses(iFormBlock::class);
foreach ($aFormBlocks as $sFormBlock) {
$oChoiceBlock = new ($sFormBlock)($sFormBlock);
$this->assertTrue($oChoiceBlock->GetOption('form_block') === $oChoiceBlock);
}
}
/**
* Check that a block with dependencies return true for HasDependenciesBlocks.
*
* @return void
* @throws FormBlockException
* @throws ReflectionException
*/
public function testCheckDependencyState(): void
{
$oFormBlock = new FormBlock('formBlock');
$oFormBlock->Add('allow_age', CheckboxFormBlock::class, []);
$oBirthdateBlock = $oFormBlock->Add('birthdate', TextFormBlock::class, [])
->InputDependsOn(AbstractTypeFormBlock::INPUT_VISIBLE, 'allow_age', CheckboxFormBlock::OUTPUT_CHECKED);
$this->assertTrue($oBirthdateBlock->HasDependenciesBlocks());
}
/**
* Dependent fields are not added to the form directly.
*
* @return void
* @throws FormBlockException
* @throws ReflectionException
*/
public function testFormBlockNotContainsDependentFields(): void
{
// form with a dependent field
$oFormBlock = new FormBlock('formBlock');
$oFormBlock->Add('firstname', TextFormBlock::class, []);
$oFormBlock->Add('lastname', TextFormBlock::class, []);
$oFormBlock->Add('allow_age', CheckboxFormBlock::class, []);
$oFormBlock->Add('birthdate', TextFormBlock::class, [])
->InputDependsOn(AbstractTypeFormBlock::INPUT_VISIBLE, 'allow_age', CheckboxFormBlock::OUTPUT_CHECKED);
// form builder
$oFormFactoryBuilder = Forms::createFormFactoryBuilder();
$oForm = $oFormFactoryBuilder->getFormFactory()->createNamedBuilder($oFormBlock->GetName(), $oFormBlock->GetFormType(), [], $oFormBlock->GetOptions())->getForm();
// try to get the dependent field
$this->expectException(OutOfBoundsException::class);
$oForm->get('birthdate');
}
public function testIsRootBlock(): void
{
/** @var FormBlock $oFormBlock */
$oFormBlock = $this->GivenFormBlock('OneBlock');
$oFormBlock->Add('subform', FormBlock::class);
$this->assertTrue($oFormBlock->IsRootBlock());
$this->assertFalse($oFormBlock->Get('subform')->IsRootBlock());
}
}

View File

@@ -0,0 +1,108 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\sources\Forms\IO;
use Combodo\iTop\Forms\IO\FormBlockIOException;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
use Symfony\Component\Form\FormEvents;
class AbstractFormIOTest extends AbstractFormsTest
{
public function testFormIoHasNoDataAtCreation()
{
$oInput = $this->GivenInput('test');
$this->assertFalse($oInput->IsDataReady(), 'Created Input must no have data ready at creation');
$this->assertFalse($oInput->HasValue(), 'Created Input must no have value at creation');
$this->assertFalse($oInput->HasBindingOut());
$oOutput = $this->GivenOutput('test');
$this->assertFalse($oOutput->IsDataReady(), 'Created output must no have data ready at creation');
$this->assertFalse($oOutput->HasValue(), 'Created output must no have value at creation');
$this->assertFalse($oOutput->HasBindingOut());
}
public function testFormIoHasDataAfterSetValue()
{
$oInput = $this->GivenInput('test');
$oInput->SetValue(FormEvents::POST_SET_DATA, 'test');
$this->assertTrue($oInput->IsDataReady(), 'Input must have data ready when set');
$this->assertTrue($oInput->HasValue(), 'Input must have value when set');
$oOutput = $this->GivenOutput('test');
$oOutput->SetValue(FormEvents::POST_SET_DATA, 'test');
$this->assertTrue($oOutput->IsDataReady(), 'Output must have data ready when set');
$this->assertTrue($oOutput->HasValue(), 'Output must have value when set');
}
public function testIOValueReflectsTheValuePostedOrTheValueSet()
{
$oInput = $this->GivenInput('test');
// When
$oInput->SetValue(FormEvents::POST_SET_DATA, 'The value set');
// Then
$this->assertEquals('The value set', $oInput->GetValue());
// When
$oInput->SetValue(FormEvents::POST_SUBMIT, 'The value posted');
// Then
$this->assertEquals('The value posted', $oInput->GetValue());
}
/**
* @dataProvider NameFormatSupportsOnlyLettersUnderscoreAndNumbersProvider
* @return void
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
*/
public function testNameFormatSupportsOnlyLettersUnderscoreAndNumbersAndDot(string $sName, bool $bGenerateException = true)
{
if ($bGenerateException) {
$this->expectException(FormBlockIOException::class);
}
$oInput = $this->GivenInput($sName);
if (!$bGenerateException) {
$this->assertEquals($sName, $oInput->GetName());
}
}
public function NameFormatSupportsOnlyLettersUnderscoreAndNumbersProvider()
{
return [
// Incorrects
'Spaces not supported' => ['The test name'],
'Minus not supported' => ['The-test-name'],
'Percent not supported' => ['name%'],
'Accent not supported' => ['namé'],
'emoji not supported' => ['🎄🎄🎄🎄🎄'],
'.name not supported' => ['.name'],
'name. not supported' => ['name.'],
// Corrects
'Numbers OK' => ['name123', false],
'Starting with number OK' => ['123name123', false],
'Underscore OK' => ['The_test_name', false],
'Camel OK' => ['TheTestName', false],
'name.subname OK' => ['name.subname', false],
];
}
public function testCreatingIOWithUnknownFormatThrowsException()
{
$this->expectException(FormBlockIOException::class);
$oInput = $this->GivenInput('test', 'test_toto');
}
}

View File

@@ -0,0 +1,237 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Sources\Forms\IO;
use Combodo\iTop\Forms\IO\Format\AttributeIOFormat;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\IO\Format\NumberIOFormat;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\Forms\IO\FormBinding;
use Combodo\iTop\Forms\IO\FormBlockIOException;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
use Symfony\Component\Form\FormEvents;
/**
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class FormBindingTest extends AbstractFormsTest
{
public function testCreatingABinding()
{
$oInputIO = $this->GivenInput('test');
$oOutputIO = $this->GivenOutput('test');
// When Linking output to input
new FormBinding($oOutputIO, $oInputIO);
// Then
$this->assertTrue($oInputIO->IsBound(), 'DestinationIO must be Bound when creating a new binding');
}
public function testBindingTwiceToTheSameInputIsNotPossible()
{
$oInputIO = $this->GivenInput('test');
$oOutputIO1 = $this->GivenOutput('test1');
$oOutputIO2 = $this->GivenOutput('test2');
// When
new FormBinding($oOutputIO1, $oInputIO);
// Then
$this->expectException(FormBlockIOException::class);
new FormBinding($oOutputIO2, $oInputIO);
}
public function testBindingTwiceToTheSameOutputIsNotPossible()
{
$oOutputIO1 = $this->GivenOutput('test1');
$oOutputIO2 = $this->GivenOutput('test2');
$oOutputIO3 = $this->GivenOutput('test3');
// When
new FormBinding($oOutputIO1, $oOutputIO3);
// Then
$this->expectException(FormBlockIOException::class);
new FormBinding($oOutputIO2, $oOutputIO3);
}
public function testOutputCanBeBoundToInputAndInputIsBoundAfterThat()
{
$oInputIO = $this->GivenInput('test');
$oOutputIO = $this->GivenOutput('test1');
$this->assertFalse($oInputIO->IsBound(), 'Input must not be Bound by default');
// When
$oOutputIO->BindToInput($oInputIO);
// Then
$this->assertTrue($oInputIO->IsBound(), 'Input must be Bound when binding from an output');
}
public function testInputCanBeBoundToAnotherInputAndItIsBoundAfterThat()
{
$oInputIO1 = $this->GivenInput('test1');
$oInputIO2 = $this->GivenInput('test2');
// When
$oInputIO1->BindToInput($oInputIO2);
// Then
$this->assertTrue($oInputIO2->IsBound(), 'Input must be Bound when binding from an input');
}
public function testOutputCanBeBoundToAnotherOutputAndItIsBoundAfterThat()
{
$oOutputIO1 = $this->GivenOutput('test1');
$oOutputIO2 = $this->GivenOutput('test2');
$this->assertFalse($oOutputIO2->IsBound(), 'Output must not be bound by default');
// When
$oOutputIO1->BindToOutput($oOutputIO2);
// Then
$this->assertTrue($oOutputIO2->IsBound(), 'Output must be Bound when binding from an output');
}
public function testOutBindingsAreStoredWhenBindToInput()
{
$oInputIO1 = $this->GivenInput('test1');
$oInputIO2 = $this->GivenInput('test2');
$oOutputIO1 = $this->GivenOutput('test1');
// When
$oBindingO2ToI1 = $oOutputIO1->BindToInput($oInputIO1);
// Then
$this->assertTrue($oOutputIO1->HasBindingOut(), 'Must have bindings after BindToInput');
$this->assertEquals([$oBindingO2ToI1], $oOutputIO1->GetBindingsToInputs(), 'Must have bindings after BindToInput');
// When
$oBindingO1ToI2 = $oOutputIO1->BindToInput($oInputIO2);
// Then
$this->assertEquals([$oBindingO2ToI1, $oBindingO1ToI2], $oOutputIO1->GetBindingsToInputs(), 'Must have bindings after BindToInput');
}
public function testOutBindingsAreStoredWhenBindToOutput()
{
$oOutputIO1 = $this->GivenOutput('test1');
$oOutputIO2 = $this->GivenOutput('test2');
$oOutputIO3 = $this->GivenOutput('test3');
// When
$oBindingO1ToO2 = $oOutputIO1->BindToOutput($oOutputIO2);
// Then
$this->assertTrue($oOutputIO1->HasBindingOut(), 'Must have bindings after BindToInput');
$this->assertEquals([$oBindingO1ToO2], $oOutputIO1->GetBindingsToOutputs(), 'Must have bindings after BindToOutput');
// When
$oBindingO1ToO3 = $oOutputIO1->BindToOutput($oOutputIO3);
// Then
$this->assertEquals([$oBindingO1ToO2, $oBindingO1ToO3], $oOutputIO1->GetBindingsToOutputs(), 'Must have bindings after BindToOutput');
}
public function testSourceValueIsPropagatedToDestIO()
{
$oOutputIO1 = $this->GivenOutput('test1');
$oInputIO1 = $this->GivenInput('test1');
$oBinding = $oOutputIO1->BindToInput($oInputIO1);
$oOutputIO1->SetValue(FormEvents::PRE_SET_DATA, 'The Value');
// When
$oBinding->PropagateValues();
// Then
$this->assertEquals('The Value', $oOutputIO1->GetValue(FormEvents::PRE_SET_DATA));
}
/**
* @dataProvider BindingIncompatibleFormatsProvider
*
* @param string $sSourceFormat
* @param string $sDestinationFormat
*
* @return void
*/
public function testBindingIncompatibleFormatsThrowsException(string $sSourceFormat, string $sDestinationFormat)
{
$oOutputIO = $this->GivenOutput('test', $sSourceFormat);
$oInputIO = $this->GivenInput('test', $sDestinationFormat);
$this->expectException(FormBlockIOException::class);
$oOutputIO->BindToInput($oInputIO);
}
public function BindingIncompatibleFormatsProvider(): array
{
return [
'Attribute -> Boolean' => [AttributeIOFormat::class, BooleanIOFormat::class],
'Attribute -> Class' => [AttributeIOFormat::class, ClassIOFormat::class],
'Attribute -> Number' => [AttributeIOFormat::class, NumberIOFormat::class],
'Boolean => Attribute' => [BooleanIOFormat::class, AttributeIOFormat::class],
'Boolean => Class' => [BooleanIOFormat::class, ClassIOFormat::class],
'Boolean => Number' => [BooleanIOFormat::class, NumberIOFormat::class],
'Boolean -> String' => [BooleanIOFormat::class, StringIOFormat::class],
'Class => Attribute' => [ClassIOFormat::class, AttributeIOFormat::class],
'Class => Boolean' => [ClassIOFormat::class, BooleanIOFormat::class],
'Class => Number' => [ClassIOFormat::class, NumberIOFormat::class],
'Number => Attribute' => [NumberIOFormat::class, AttributeIOFormat::class],
'Number => Class' => [NumberIOFormat::class, ClassIOFormat::class],
'Number => Boolean' => [NumberIOFormat::class, BooleanIOFormat::class],
'Number -> String' => [NumberIOFormat::class, StringIOFormat::class],
'String => Attribute' => [StringIOFormat::class, AttributeIOFormat::class],
'String => Class' => [StringIOFormat::class, ClassIOFormat::class],
'String => Boolean' => [StringIOFormat::class, BooleanIOFormat::class],
'String -> Number' => [StringIOFormat::class, NumberIOFormat::class],
];
}
/**
* @dataProvider BindingCompatibleFormatsProvider
*
* @param string $sSourceFormat
* @param string $sDestinationFormat
*
* @return void
*/
public function testBindingCompatibleFormatsWorks(string $sSourceFormat, string $sDestinationFormat)
{
$oOutputIO = $this->GivenOutput('test', $sSourceFormat);
$oInputIO = $this->GivenInput('test', $sDestinationFormat);
$oBinding = $oOutputIO->BindToInput($oInputIO);
$this->assertTrue(is_a($oBinding, FormBinding::class));
}
public function BindingCompatibleFormatsProvider(): array
{
return [
'Attribute -> Attribute' => [AttributeIOFormat::class, AttributeIOFormat::class],
'Attribute -> String' => [AttributeIOFormat::class, StringIOFormat::class],
'Boolean => Boolean' => [BooleanIOFormat::class, BooleanIOFormat::class],
'Class => Class' => [ClassIOFormat::class, ClassIOFormat::class],
'Class -> String' => [ClassIOFormat::class, StringIOFormat::class],
'Number => Number' => [NumberIOFormat::class, NumberIOFormat::class],
'String => String' => [StringIOFormat::class, StringIOFormat::class],
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\sources\Forms\IO\Format;
use Combodo\iTop\Forms\IO\Format\AttributeIOFormat;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
use Symfony\Component\Form\FormEvents;
class TestAttributeIOFormat extends AbstractFormsTest
{
public function testAttributeIOIsAString()
{
$oInputIO = $this->GivenInput('test', AttributeIOFormat::class);
$oInputIO->SetValue(FormEvents::POST_SUBMIT, 'name');
$this->assertEquals('name', $oInputIO->GetValue());
}
}

View File

@@ -0,0 +1,24 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\sources\Forms\IO\Format;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
use Symfony\Component\Form\FormEvents;
class TestBooleanIOFormat extends AbstractFormsTest
{
public function testBooleanIOFormatIsABoolean()
{
$oInputIO = $this->GivenInput('test', BooleanIOFormat::class);
$oInputIO->SetValue(FormEvents::POST_SUBMIT, 'true');
$this->assertEquals(true, $oInputIO->GetValue());
}
}

View File

@@ -0,0 +1,14 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\sources\Forms\IO\Format;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
class TestClassIOFormat extends AbstractFormsTest
{
}

View File

@@ -0,0 +1,14 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\sources\Forms\IO\Format;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
class TestNumberIOFormat extends AbstractFormsTest
{
}

View File

@@ -0,0 +1,193 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Sources\Forms\Register;
use Combodo\iTop\Forms\Block\Base\CheckboxFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\Base\TextFormBlock;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\Forms\Register\IORegister;
use Combodo\iTop\Forms\Register\RegisterException;
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
class IORegisterTest extends AbstractFormsTest
{
private FormBlock $oFormBlock;
private IORegister $oIORegister;
protected function setUp(): void
{
parent::setUp();
$this->oFormBlock = $this->GivenFormBlock('OneBlock');
$this->oIORegister = $this->GivenIORegister($this->oFormBlock);
}
public function testAddInput(): void
{
$this->oIORegister->AddInput('input', StringIOFormat::class);
$this->assertTrue($this->oIORegister->HasInput('input'));
$this->assertNotNull($this->oIORegister->GetInput('input'));
}
public function testGetInputs(): void
{
$iOriginInputCount = count($this->oIORegister->GetInputs());
$this->oIORegister->AddInput('input_1', StringIOFormat::class);
$this->oIORegister->AddInput('input_2', BooleanIOFormat::class);
$this->oIORegister->AddInput('input_3', StringIOFormat::class);
$this->assertCount(3 + $iOriginInputCount, $this->oIORegister->GetInputs());
$this->assertArrayHasKey('input_1', $this->oIORegister->GetInputs());
}
public function testGetOutputs(): void
{
$this->oIORegister->AddOutput('output_1', StringIOFormat::class);
$this->oIORegister->AddOutput('output_2', BooleanIOFormat::class);
$this->assertCount(2, $this->oIORegister->GetOutputs());
$this->assertArrayHasKey('output_1', $this->oIORegister->GetOutputs());
}
public function testMissingInput(): void
{
$this->expectException(RegisterException::class);
$this->oIORegister->GetInput('missing_input');
}
public function testMissingOutput(): void
{
$this->expectException(RegisterException::class);
$this->oIORegister->GetOutput('missing_output');
}
public function testGetBoundInputs(): void
{
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormB', CheckboxFormBlock::class);
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormC', TextFormBlock::class);
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
$oSubForm->AddInput('input_from_B', BooleanIOFormat::class);
$oSubForm->AddInput('input_from_C', StringIOFormat::class);
$oSubForm->AddInput('unbound_input', StringIOFormat::class);
$this->GivenIORegister($oSubForm)->InputDependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
$this->GivenIORegister($oSubForm)->InputDependsOn('input_from_B', 'SubFormB', CheckboxFormBlock::OUTPUT_CHECKED);
$this->GivenIORegister($oSubForm)->InputDependsOn('input_from_C', 'SubFormC', TextFormBlock::OUTPUT_TEXT);
$aBoundInputs = $this->GivenIORegister($oSubForm)->GetBoundInputs();
$this->assertCount(3, $aBoundInputs);
}
public function testGetBoundOutputs(): void
{
$this->oFormBlock->AddOutput('output', StringIOFormat::class);
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oIORegisterA = $this->GivenIORegister($oSubFormA);
$oIORegisterA->OutputImpactParent(TextFormBlock::OUTPUT_TEXT, 'output');
$this->assertCount(1, $this->oIORegister->GetBoundOutputs());
}
public function testAddInputDependsOn(): void
{
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oSubFormB = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormB', TextFormBlock::class);
$oIORegisterB = $this->GivenIORegister($oSubFormB);
$oIORegisterB->AddInputDependsOn('input', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
$this->assertNotNull($oIORegisterB->GetInput('input'));
}
public function testImpactParent(): void
{
$this->oFormBlock->AddOutput('output', StringIOFormat::class);
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oIORegisterA = $this->GivenIORegister($oSubFormA);
$oIORegisterA->OutputImpactParent(TextFormBlock::OUTPUT_TEXT, 'output');
$this->assertTrue($this->oFormBlock->GetOutput('output')->IsBound());
}
public function testAddingTwiceTheSameInputThrowsException(): void
{
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
}
public function testAddingTwiceTheSameOutputThrowsException(): void
{
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
}
public function testDependingOnNonExistingInputThrowsException(): void
{
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->InputDependsOn('non_existing_input', 'OtherBlock', 'test_output');
}
public function testDependingOnNonExistingOutputThrowsException(): void
{
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->InputDependsOn('test_input', 'OtherBlock', 'non_existing_output');
}
public function testDependingOnNonExistingBlockThrowsException(): void
{
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
$this->expectException(RegisterException::class);
$this->oIORegister->InputDependsOn('test_input', 'UnknownBlock', 'test');
}
public function testHasDependenciesBlocks(): void
{
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
$this->GivenIORegister($oSubForm)->InputDependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
$this->assertTrue($this->GivenIORegister($oSubForm)->HasDependenciesBlocks());
$this->assertFalse($this->oIORegister->HasDependenciesBlocks());
}
public function testImpactBlocks(): void
{
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
$this->GivenIORegister($oSubForm)->InputDependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
$this->assertFalse($this->GivenIORegister($oSubForm)->IsImpactingBlocks());
$this->assertTrue($this->GivenIORegister($oSubFormA)->IsImpactingBlocks());
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Sources\Forms\Register;
use Combodo\iTop\Forms\Register\OptionsRegister;
use Combodo\iTop\Forms\Register\RegisterException;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class OptionsRegisterTest extends ItopDataTestCase
{
private OptionsRegister $oOptionsRegister;
protected function setUp(): void
{
parent::setUp();
$this->oOptionsRegister = new OptionsRegister();
}
public function testSetOptionWithInvalidName(): void
{
$this->oOptionsRegister->SetOption('valid_option_name', 'value');
$this->expectException(RegisterException::class);
$this->oOptionsRegister->SetOption('not valid option name', 'value');
}
public function testSetOptionTwice(): void
{
$this->oOptionsRegister->SetOption('valid_option_name', 'value');
$this->oOptionsRegister->SetOption('valid_option_name', 'value2');
$this->assertEquals('value2', $this->oOptionsRegister->GetOption('valid_option_name'));
}
public function testSetNonTypeOption(): void
{
$this->oOptionsRegister->SetOption('not_a_type_option', 'value', false);
$this->assertArrayNotHasKey('not_a_type_option', $this->oOptionsRegister->GetOptions());
}
public function testSetOptionArrayValue(): void
{
$this->oOptionsRegister->SetOptionArrayValue('att', 'class', 'ibo-class');
$this->assertEquals('ibo-class', $this->oOptionsRegister->GetOption('att')['class']);
}
public function testHasOption(): void
{
$this->oOptionsRegister->SetOption('option', true);
$this->assertTrue($this->oOptionsRegister->HasOption('option'));
}
}

View File

@@ -0,0 +1,852 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\PropertyType\Compiler\PropertyTypeCompiler;
use Combodo\iTop\Service\DependencyInjection\ServiceLocator;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class FormsCompilerTest extends ItopDataTestCase
{
/**
* @dataProvider CompileFormFromXMLProvider
*
* @param string $sXMLContent
* @param string $sExpectedPHP
*
* @return void
* @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function testCompileFormFromXML(string $sXMLContent, string $sExpectedPHP)
{
ServiceLocator::GetInstance()->RegisterService('ModelReflection', new ModelReflectionRuntime());
$sProducedPHP = PropertyTypeCompiler::GetInstance()->CompileFormFromXML($sXMLContent);
$this->AssertPHPCodeIsValid($sProducedPHP);
$sMessage = $this->dataName();
$this->assertEquals($sExpectedPHP, $sProducedPHP, $sMessage);
}
public function CompileFormFromXMLProvider()
{
return [
'Basic scalar properties should generate PHP' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="basic_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__basic_test extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('title_property', 'Combodo\iTop\Forms\Block\DataModel\LabelFormBlock', [
'label' => 'UI:BasicTest:Prop-Title',
]);
\$this->Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [
'label' => 'UI:BasicTest:Prop-Class',
'choices' => [
],
]);
}
}
PHP,
],
'Empty property tree should generate minimal PHP' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="EmptyTest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__EmptyTest extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{ }
}
PHP,
],
'Empty property tree lower case should generate lower case class name' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="empty_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__empty_test extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{ }
}
PHP,
],
'Properties with all value-types' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="AllValueTypesTest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="aggregate_function_property" xsi:type="Combodo-ValueType-AggregateFunction">
<label>UI:AggregateFunction</label>
</node>
<node id="choice_property" xsi:type="Combodo-ValueType-Choice">
<label>UI:Choice</label>
<values>
<value id="value_a">
<label>Label A</label>
</value>
<value id="value_b">
<label>Label B</label>
</value>
</values>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:Class</label>
<categories-csv>test</categories-csv>
</node>
<node id="class_attribute_property" xsi:type="Combodo-ValueType-ClassAttribute">
<label>UI:ClassAttribute</label>
</node>
<node id="class_attribute_group_by_property" xsi:type="Combodo-ValueType-ClassAttributeGroupBy">
<label>UI:ClassAttributeGroupBy</label>
</node>
<node id="class_attribute_value_property" xsi:type="Combodo-ValueType-ClassAttributeValue">
<label>UI:ClassAttributeValue</label>
</node>
<node id="integer_property" xsi:type="Combodo-ValueType-Integer">
<label>UI:Integer</label>
</node>
<node id="label_property" xsi:type="Combodo-ValueType-Label">
<label>UI:Label</label>
</node>
<node id="oql_property" xsi:type="Combodo-ValueType-OQL">
<label>UI:OQL</label>
</node>
<node id="string_property" xsi:type="Combodo-ValueType-String">
<label>UI:String</label>
</node>
<node id="choice_from_input" xsi:type="Combodo-ValueType-ChoiceFromInput">
<label>UI:ChoiceFromInput</label>
<values>
<value id="value_a">
<label>{{class_attribute_property.label}}</label>
</value>
<value id="value_b">
<label>{{class_attribute_group_by_property.label}}</label>
</value>
</values>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__AllValueTypesTest extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('aggregate_function_property', 'Combodo\iTop\Forms\Block\DataModel\Dashlet\AggregateFunctionFormBlock', [
'label' => 'UI:AggregateFunction',
]);
\$this->Add('choice_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [
'label' => 'UI:Choice',
'choices' => [
\Dict::S('Label A') => 'value_a',
\Dict::S('Label B') => 'value_b',
],
]);
\$this->Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [
'label' => 'UI:Class',
'choices' => [
],
]);
\$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [
'label' => 'UI:ClassAttribute',
]);
\$this->Add('class_attribute_group_by_property', 'Combodo\iTop\Forms\Block\DataModel\Dashlet\ClassAttributeGroupByFormBlock', [
'label' => 'UI:ClassAttributeGroupBy',
]);
\$this->Add('class_attribute_value_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeValueChoiceFormBlock', [
'label' => 'UI:ClassAttributeValue',
]);
\$this->Add('integer_property', 'Combodo\iTop\Forms\Block\Base\IntegerFormBlock', [
'label' => 'UI:Integer',
]);
\$this->Add('label_property', 'Combodo\iTop\Forms\Block\DataModel\LabelFormBlock', [
'label' => 'UI:Label',
]);
\$this->Add('oql_property', 'Combodo\iTop\Forms\Block\DataModel\OqlFormBlock', [
'label' => 'UI:OQL',
]);
\$this->Add('string_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
'label' => 'UI:String',
]);
\$this->Add('choice_from_input', 'Combodo\iTop\Forms\Block\Base\ChoiceFromInputsBlock', [
'label' => 'UI:ChoiceFromInput',
])
->AddInputDependsOn('value_a', 'class_attribute_property', 'label')
->AddInputDependsOn('value_b', 'class_attribute_group_by_property', 'label');
}
}
PHP,
],
'Collection of trees' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="collection_of_trees_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="sub_tree_collection" xsi:type="Combodo-ValueType-Collection">
<label>UI:SubTree</label>
<xml-format xsi:type="Combodo-XMLFormat-FlatArray">
<count-tag>item_count</count-tag>
<tag-format>item_\$rank\$_\$id\$</tag-format>
</xml-format>
<prototype>
<node id="string_property" xsi:type="Combodo-ValueType-String">
<label>UI:String</label>
</node>
<node id="integer_property" xsi:type="Combodo-ValueType-Integer">
<label>UI:Integer</label>
<relevance-condition>{{string_property.text != 'no-display'}}</relevance-condition>
</node>
</prototype>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class SubFormFor__collection_of_trees_test__sub_tree_collection extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('string_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
'label' => 'UI:String',
]);
\$this->Add('integer_property_visible_expression', 'Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock', [
'expression' => 'string_property.text != \'no-display\'',
])
->AddInputDependsOn('string_property.text', 'string_property', 'text');
\$this->Add('integer_property', 'Combodo\iTop\Forms\Block\Base\IntegerFormBlock', [
'label' => 'UI:Integer',
])
->InputDependsOn('visible', 'integer_property_visible_expression', 'result');
}
}
class FormFor__collection_of_trees_test extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('sub_tree_collection', 'Combodo\iTop\Forms\Block\Base\CollectionBlock', [
'label' => 'UI:SubTree',
'button_label' => 'UI:AddSubTree',
'block_entry_type' => 'SubFormFor__collection_of_trees_test__sub_tree_collection',
]);
}
}
PHP,
],
'Static inputs should be bound and invalid input should be ignored' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="input_static_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="class_attribute_property" xsi:type="Combodo-ValueType-ClassAttribute">
<label>UI:ClassAttribute</label>
<class>Contact</class>
<invalid-input>Test</invalid-input>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__input_static_test extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [
'label' => 'UI:ClassAttribute',
])
->SetInputValue('class', 'Contact');
}
}
PHP,
],
'Quotes should be handled gracefully' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="input_quotes_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="class_attribute_property" xsi:type="Combodo-ValueType-ClassAttribute">
<label>'Class' and "Attribute"</label>
<class>{{CONCAT("'", '"')}}</class>
<category>'Class' and "Attribute"</category>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__input_quotes_test extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('class_attribute_property_class_expression', 'Combodo\iTop\Forms\Block\Expression\StringExpressionFormBlock', [
'expression' => 'CONCAT("\'", \'"\')',
]);
\$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [
'label' => '\'Class\' and "Attribute"',
])
->InputDependsOn('class', 'class_attribute_property_class_expression', 'result')
->SetInputValue('category', '\'Class\' and "Attribute"');
}
}
PHP,
],
'Dynamic input should be bound' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="input_binding_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:Class</label>
<categories-csv>test</categories-csv>
</node>
<node id="class_attribute_property" xsi:type="Combodo-ValueType-ClassAttribute">
<label>UI:ClassAttribute</label>
<class>{{class_property.text}}</class>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__input_binding_test extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [
'label' => 'UI:Class',
'choices' => [
],
]);
\$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [
'label' => 'UI:ClassAttribute',
])
->InputDependsOn('class', 'class_property', 'text');
}
}
PHP,
],
'Dynamic input can be an expression' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="input_binding_expression" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:Class</label>
<categories-csv>test</categories-csv>
</node>
<node id="class_attribute_property" xsi:type="Combodo-ValueType-ClassAttribute">
<label>UI:ClassAttribute</label>
<class>{{IF(class_property.value = '', 'Person', class_property.value)}}</class>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__input_binding_expression extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('class_property', 'Combodo\iTop\Forms\Block\Base\ChoiceFormBlock', [
'label' => 'UI:Class',
'choices' => [
],
]);
\$this->Add('class_attribute_property_class_expression', 'Combodo\iTop\Forms\Block\Expression\StringExpressionFormBlock', [
'expression' => 'IF(class_property.value = \'\', \'Person\', class_property.value)',
])
->AddInputDependsOn('class_property.value', 'class_property', 'value');
\$this->Add('class_attribute_property', 'Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock', [
'label' => 'UI:ClassAttribute',
])
->InputDependsOn('class', 'class_attribute_property_class_expression', 'result');
}
}
PHP,
],
'Relevance condition should generate a boolean block expression' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="source_property" xsi:type="Combodo-ValueType-String">
<label>UI:Source</label>
</node>
<node id="dependant_property" xsi:type="Combodo-ValueType-String">
<label>UI:Dependant</label>
<relevance-condition>{{source_property.text != 'count'}}</relevance-condition>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__RelevanceCondition extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('source_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
'label' => 'UI:Source',
]);
\$this->Add('dependant_property_visible_expression', 'Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock', [
'expression' => 'source_property.text != \'count\'',
])
->AddInputDependsOn('source_property.text', 'source_property', 'text');
\$this->Add('dependant_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
'label' => 'UI:Dependant',
])
->InputDependsOn('visible', 'dependant_property_visible_expression', 'result');
}
}
PHP,
],
'Complex Relevance condition should generate a boolean block expression' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="ComplexRelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="source_a_property" xsi:type="Combodo-ValueType-String">
<label>UI:Source</label>
</node>
<node id="source_b_property" xsi:type="Combodo-ValueType-String">
<label>UI:Source</label>
</node>
<node id="dependant_property" xsi:type="Combodo-ValueType-String">
<label>UI:Dependant</label>
<relevance-condition>{{IF(source_a_property.text != '', source_a_property.text, source_b_property.text)}}</relevance-condition>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__ComplexRelevanceCondition extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('source_a_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
'label' => 'UI:Source',
]);
\$this->Add('source_b_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
'label' => 'UI:Source',
]);
\$this->Add('dependant_property_visible_expression', 'Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock', [
'expression' => 'IF(source_a_property.text != \'\', source_a_property.text, source_b_property.text)',
])
->AddInputDependsOn('source_a_property.text', 'source_a_property', 'text')
->AddInputDependsOn('source_b_property.text', 'source_b_property', 'text');
\$this->Add('dependant_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
'label' => 'UI:Dependant',
])
->InputDependsOn('visible', 'dependant_property_visible_expression', 'result');
}
}
PHP,
],
'Sub form generate a sub-form' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_typeproperty_type id="SubFormTest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="sub_form_property" xsi:type="Combodo-ValueType-PropertyTree">
<label>UI:SubForm:Title</label>
<nodes>
<node id="string_property" xsi:type="Combodo-ValueType-String">
<label>UI:String</label>
</node>
</nodes>
</node>
</nodes>
</definition>
</property_typeproperty_type>
XML,
'sExpectedPHP' => <<<PHP
class SubFormFor__SubFormTest__sub_form_property extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('string_property', 'Combodo\iTop\Forms\Block\Base\TextFormBlock', [
'label' => 'UI:String',
]);
}
}
class FormFor__SubFormTest extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('sub_form_property', 'SubFormFor__SubFormTest__sub_form_property', [
'label' => 'UI:SubForm:Title',
]);
}
}
PHP,
],
'Collection of values' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_typeproperty_type id="CollectionOfValuesTest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="coll" xsi:type="Combodo-ValueType-CollectionOfValues">
<label>UI:ClassAttributeValue</label>
<xml-format xsi:type="Combodo-XMLFormat-CSV"/>
<value-type xsi:type="Combodo-ValueType-ClassAttributeValue">
<class>Contact</class>
<attribute>status</attribute>
</value-type>
</node>
</nodes>
</definition>
</property_typeproperty_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__CollectionOfValuesTest extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
\$this->Add('coll', 'Combodo\iTop\Forms\Block\DataModel\AttributeValueChoiceFormBlock', [
'label' => 'UI:ClassAttributeValue',
'multiple' => true,
]);
}
}
PHP,
],
'Single value' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_typeproperty_type id="SingleValueTest" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-Label">
<label>Single Test</label>
</definition>
</property_typeproperty_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__SingleValueTest extends Combodo\iTop\Forms\Block\DataModel\LabelFormBlock
{
}
PHP,
],
'test' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_typeproperty_type id="Test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
</nodes>
</definition>
</property_typeproperty_type>
XML,
'sExpectedPHP' => <<<PHP
class FormFor__Test extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{ }
}
PHP,
],
];
}
/**
* @dataProvider CompileFormFromInvalidXMLProvider
*
* @param string $sXMLContent
* @param string $sExpectedClass
* @param string $sExpectedMessage
*
* @return void
* @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function testCompileFormFromInvalidXML(string $sXMLContent, string $sExpectedClass, string $sExpectedMessage)
{
$this->expectException($sExpectedClass);
$this->expectExceptionMessage($sExpectedMessage);
PropertyTypeCompiler::GetInstance()->CompileFormFromXML($sXMLContent);
}
public function CompileFormFromInvalidXMLProvider()
{
return [
'Invalid OQL expression in condition' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="dependant_property" xsi:type="Combodo-ValueType-String">
<label>UI:Dependant</label>
<relevance-condition>{{source_property.text == 'count'}}</relevance-condition>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\PropertyTypeException',
'sExpectedMessage' => 'Node: dependant_property, invalid syntax in condition: Unexpected token EQ - found \'=\' at 22 in \'source_property.text == \'count\'\'',
],
'Unknown source in relevance condition' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="dependant_property" xsi:type="Combodo-ValueType-String">
<label>UI:Dependant</label>
<relevance-condition>{{source_property.text = 'count'}}</relevance-condition>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\PropertyTypeException',
'sExpectedMessage' => 'Node: dependant_property, invalid source in condition: source_property',
],
'Unknown output in relevance condition' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="source_property" xsi:type="Combodo-ValueType-String">
<label>UI:Source</label>
</node>
<node id="dependant_property" xsi:type="Combodo-ValueType-String">
<label>UI:Dependant</label>
<relevance-condition>{{source_property.text_output != 'count'}}</relevance-condition>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\PropertyTypeException',
'sExpectedMessage' => 'Node: dependant_property, invalid output in condition: source_property.text_output',
],
'Missing output or source in relevance condition' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="RelevanceCondition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="source_property" xsi:type="Combodo-ValueType-String">
<label>UI:Source</label>
</node>
<node id="dependant_property" xsi:type="Combodo-ValueType-String">
<label>UI:Dependant</label>
<relevance-condition>{{source_property != 'count'}}</relevance-condition>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\PropertyTypeException',
'sExpectedMessage' => 'Node: dependant_property, missing output or source in condition: source_property',
],
'Missing value-type in node specification' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="WrongDefinition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="source_property">
<label>UI:Source</label>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\PropertyTypeException',
'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node[source_property]: Missing value-type in node specification',
],
'Wrong class for value-type in node specification' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="WrongDefinition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="source_property" xsi:type="Test-Combodo">
<label>UI:Source</label>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\PropertyTypeException',
'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node[source_property]: Unknown value-type node class: "Test-Combodo"',
],
'Wrong class for xml-format in node specification' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="WrongDefinition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="coll" xsi:type="Combodo-ValueType-CollectionOfValues">
<label>UI:ClassAttributeValue</label>
<xml-format xsi:type="Combodo-test"/>
<value-type xsi:type="Combodo-ValueType-ClassAttributeValue">
<class>Contact</class>
<attribute>status</attribute>
</value-type>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\Serializer\SerializerException',
'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node[coll]/xml-format: Unknown type node class: "Combodo-test"',
],
'Missing class for xml-format in node specification' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="WrongDefinition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="coll" xsi:type="Combodo-ValueType-CollectionOfValues">
<label>UI:ClassAttributeValue</label>
<xml-format/>
<value-type xsi:type="Combodo-ValueType-ClassAttributeValue">
<class>Contact</class>
<attribute>status</attribute>
</value-type>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\Serializer\SerializerException',
'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node[coll]/xml-format: Missing xsi:type in node specification',
],
'Missing tag in xml-format ' => [
'sXMLContent' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="WrongDefinition" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node xsi:type="Combodo-ValueType-CollectionOfValues">
<xml-format xsi:type="Combodo-XMLFormat-ValueAsId">
</xml-format>
<value-type xsi:type="Combodo-ValueType-Class">
</value-type>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedClass' => 'Combodo\iTop\PropertyType\Serializer\SerializerException',
'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node/xml-format: Missing <tag-name> element',
],
];
}
}

View File

@@ -0,0 +1,331 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\PropertyType\Compiler\PropertyTypeCompiler;
use Combodo\iTop\PropertyType\PropertyTypeDesign;
use Combodo\iTop\Service\DependencyInjection\ServiceLocator;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class XMLSerializerTest extends ItopDataTestCase
{
/**
* @dataProvider XMLSerializerProvider
*
* @param $inputContent
* @param string $sPropertyTypeXML
* @param string $sExpectedXMLContent
*
* @return void
* @throws \DOMException
*/
public function testSerializeXML($inputContent, string $sPropertyTypeXML, string $sExpectedXMLContent)
{
ServiceLocator::GetInstance()->RegisterService('ModelReflection', new ModelReflectionRuntime());
$oDOMDocument = new PropertyTypeDesign();
$oDOMDocument->preserveWhiteSpace = false;
$oDOMDocument->formatOutput = true;
/** @var \Combodo\iTop\DesignElement $oRootNode */
$oRootNode = $oDOMDocument->createElement('root');
$oDOMDocument->appendChild($oRootNode);
Combodo\iTop\PropertyType\Serializer\XMLSerializer::GetInstance()->SerializeForPropertyType($inputContent, $oRootNode, $sPropertyTypeXML);
$sActualXML = $oDOMDocument->saveXML();
$this->AssertEqualiTopXML($sExpectedXMLContent, $sActualXML);
}
public function XMLSerializerProvider()
{
return [
'Basic test should serialize to XML' => [
'inputContent' => 'text',
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="basic_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-Label">
</definition>
</property_type>
XML,
'sExpectedXMLContent' => <<<XML
<?xml version="1.0"?>
<root>text</root>
XML,
],
'Collection of values as CSV' => [
'inputContent' => ['Contact', 'Organization'],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="basic_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-CollectionOfValues">
<xml-format xsi:type="Combodo-XMLFormat-CSV"/>
<value-type xsi:type="Combodo-ValueType-Class">
</value-type>
</definition>
</property_type>
XML,
'sExpectedXMLContent' => <<<XML
<?xml version="1.0"?>
<root>Contact,Organization</root>
XML,
],
'Collection of values as id attribute' => [
'inputContent' => ['Contact', 'Organization'],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="class_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-CollectionOfValues">
<xml-format xsi:type="Combodo-XMLFormat-ValueAsId">
<tag-name>item</tag-name>
</xml-format>
<value-type xsi:type="Combodo-ValueType-Class">
</value-type>
</definition>
</property_type>
XML,
'sExpectedXMLContent' => <<<XML
<?xml version="1.0"?>
<root>
<item id="Contact"/>
<item id="Organization"/>
</root>
XML,
],
'Collection of tree as flat array' => [
'inputContent' => [
[
'title_property' => 'title_a',
'class_property' => 'class_a',
],
[
'title_property' => 'title_b',
'class_property' => 'class_b',
],
],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="collection_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-Collection">
<xml-format xsi:type="Combodo-XMLFormat-FlatArray">
<count-tag>item_count</count-tag>
<tag-format>item_\$rank\$_\$id\$</tag-format>
</xml-format>
<prototype>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</prototype>
</definition>
</property_type>
XML,
'sExpectedXMLContent' => <<<XML
<?xml version="1.0"?>
<root>
<item_count>2</item_count>
<item_0_title_property>title_a</item_0_title_property>
<item_0_class_property>class_a</item_0_class_property>
<item_1_title_property>title_b</item_1_title_property>
<item_1_class_property>class_b</item_1_class_property>
</root>
XML,
],
'Property tree' => [
'inputContent' => ['title_property' => 'title', 'class_property' => 'class'],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="property_tree_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</nodes>
</definition>
</property_type>
XML,
'sExpectedXMLContent' => <<<XML
<?xml version="1.0"?>
<root>
<title_property>title</title_property>
<class_property>class</class_property>
</root>
XML,
],
];
}
/**
* @dataProvider XMLUnserializerProvider
*
* @param $sInputXMLContent
* @param string $sPropertyTypeXML
* @param $expectedValue
*
* @return void
*/
public function testUnserializeXML($sInputXMLContent, string $sPropertyTypeXML, $expectedValue)
{
ServiceLocator::GetInstance()->RegisterService('ModelReflection', new ModelReflectionRuntime());
$oDoc = new PropertyTypeDesign();
$oDoc->loadXML($sInputXMLContent);
/** @var \Combodo\iTop\DesignElement $oRoot */
$oRoot = $oDoc->firstChild;
$aActualValue = Combodo\iTop\PropertyType\Serializer\XMLSerializer::GetInstance()->UnserializeForPropertyType($oRoot, $sPropertyTypeXML);
$this->assertEquals($expectedValue, $aActualValue);
}
public function XMLUnserializerProvider()
{
return [
'Basic test should unserialize from XML' => [
'sInputXMLContent' => <<<XML
<?xml version="1.0"?>
<root>text</root>
XML,
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="basic_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-Label">
</definition>
</property_type>
XML,
'expectedValue' => 'text',
],
'Collection of values as CSV' => [
'sInputXMLContent' => <<<XML
<?xml version="1.0"?>
<root>Contact,Organization</root>
XML,
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="basic_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-CollectionOfValues">
<xml-format xsi:type="Combodo-XMLFormat-CSV"/>
<value-type xsi:type="Combodo-ValueType-Class">
</value-type>
</definition>
</property_type>
XML,
'expectedValue' => ['Contact', 'Organization'],
],
'Collection of values as id attribute' => [
'sInputXMLContent' => <<<XML
<?xml version="1.0"?>
<root>
<item id="Contact"/>
<item id="Organization"/>
</root>
XML,
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="class_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-CollectionOfValues">
<xml-format xsi:type="Combodo-XMLFormat-ValueAsId">
<tag-name>item</tag-name>
</xml-format>
<value-type xsi:type="Combodo-ValueType-Class">
</value-type>
</definition>
</property_type>
XML,
'expectedValue' => ['Contact', 'Organization'],
],
'Collection of tree as flat array' => [
'sInputXMLContent' => <<<XML
<?xml version="1.0"?>
<root>
<item_count>2</item_count>
<item_0_title_property>title_a</item_0_title_property>
<item_0_class_property>class_a</item_0_class_property>
<item_1_title_property>title_b</item_1_title_property>
<item_1_class_property>class_b</item_1_class_property>
</root>
XML,
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="collection_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-Collection">
<xml-format xsi:type="Combodo-XMLFormat-FlatArray">
<count-tag>item_count</count-tag>
<tag-format>item_\$rank\$_\$id\$</tag-format>
</xml-format>
<prototype>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</prototype>
</definition>
</property_type>
XML,
'expectedValue' => [
[
'title_property' => 'title_a',
'class_property' => 'class_a',
],
[
'title_property' => 'title_b',
'class_property' => 'class_b',
],
],
],
'Property tree' => [
'sInputXMLContent' => <<<XML
<?xml version="1.0"?>
<root>
<title_property>title</title_property>
<class_property>class</class_property>
</root>
XML,
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="property_tree_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</nodes>
</definition>
</property_type>
XML,
'expectedValue' => ['title_property' => 'title', 'class_property' => 'class'],
],
];
}
}

View File

@@ -156,10 +156,26 @@ class DataModelDependantCacheTest extends ItopTestCase
$this->assertEquals($iRefTime, $this->oCacheService->GetEntryModificationTime('pool-A', 'key'), 'GetEntryModificationTime should return the modification time of the cache file');
$this->assertEquals(null, $this->oCacheService->GetEntryModificationTime('pool-A', 'non-existing-key'), 'GetEntryModificationTime should return null for an invalid key');
}
public function testKeyUndesiredCharactersShouldBeTransformedToUnderscore()
{
$sUglyKey = 'key with ugly characters:\{&"#@ç^²/,;[(|🤔';
$sUglyKey = 'key with ugly characters:\{&"#@ç^²,;[(|🤔';
$sFilePath = $this->InvokeNonPublicMethod(DataModelDependantCache::class, 'MakeCacheFileName', $this->oCacheService, ['pool-A', $sUglyKey]);
$this->assertEquals('key_with_ugly_characters______________________.php', basename($sFilePath));
$this->assertEquals('key_with_ugly_characters_____________________.php', basename($sFilePath));
}
public function testKeyCanBeADirectoryTree()
{
$sBaseKey = 'test';
$sBaseFilePath = $this->InvokeNonPublicMethod(DataModelDependantCache::class, 'MakeCacheFileName', $this->oCacheService, ['pool-A', $sBaseKey]);
$sKey = 'Path/To/KeyCanBePath';
$sFilePath = $this->InvokeNonPublicMethod(DataModelDependantCache::class, 'MakeCacheFileName', $this->oCacheService, ['pool-A', $sKey]);
$this->assertEquals(dirname($sBaseFilePath).'/Path/To', dirname($sFilePath));
$this->oCacheService->Store('pool-A', $sKey, 'some data...');
$this->assertTrue($this->oCacheService->HasEntry('pool-A', $sKey), 'The data should have been stored');
$this->assertFileExists($sFilePath, 'A file should have been created in the corresponding directory');
}
}

View File

@@ -94,7 +94,6 @@ class RestTest extends ItopDataTestCase
public function testPostJSONDataAsCurlFile()
{
$sCallbackName = 'fooCallback';
$sJsonData = '{"operation": "list_operations"}';
// Test regular JSON result
@@ -269,7 +268,7 @@ JSON;
*
* @return bool|string
*/
private function CallRestApi_HTTP(string $sJsonDataContent = null, string $sCallbackName = null, bool $bJSONDataAsFile = false)
private function CallRestApi_HTTP(?string $sJsonDataContent = null, ?string $sCallbackName = null, bool $bJSONDataAsFile = false)
{
$ch = curl_init();
$aPostFields = [