From de0e94800a405f255333fbe1b0ea47ccfdadc15a Mon Sep 17 00:00:00 2001 From: odain-cbd <56586767+odain-cbd@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:52:23 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B08806=20-=20Installation=20multiple=20ext?= =?UTF-8?q?ension=20with=20dependence=20via=20ITSM=20Designer=20(#879)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * N°8806 - Installation multiple extension with dependence via ITSM Designer * N°8806 - fix tests * N°8806 - improve GetMFModulesToCompile to handle extensions and installation choices on all setup that use compileFrom (MTP, CoreUpdate, Hub, DryRemoval) * N°8806 - add log when loading MFModule * N°8806 - code cleanup + test cover * N°8806 - typo * N°8806 - log removal --- .../Service/RunTimeEnvironmentCoreUpdater.php | 42 +------ setup/extensionsmap.class.inc.php | 38 +++++-- .../DryRemovalRuntimeEnvironment.php | 39 ------- setup/modelfactory.class.inc.php | 13 ++- .../InstallationChoicesToModuleConverter.php | 25 ++++- setup/runtimeenv.class.inc.php | 59 ++++++++-- .../src/BaseTestCase/ItopTestCase.php | 1 + .../setup/feature_removal/SetupAuditTest.php | 2 +- ...stallationChoicesToModuleConverterTest.php | 106 +++++++++++++++++- 9 files changed, 222 insertions(+), 103 deletions(-) diff --git a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php index 7aeed94bb..b38a0d682 100644 --- a/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php +++ b/datamodels/2.x/itop-core-update/src/Service/RunTimeEnvironmentCoreUpdater.php @@ -11,7 +11,6 @@ require_once(APPROOT."setup/runtimeenv.class.inc.php"); use Config; use Exception; -use ModelFactory; use RunTimeEnvironment; use SetupUtils; @@ -47,11 +46,9 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment */ public function CheckDirectories($sBuildEnv) { - $sBuildDir = APPROOT.'env-'.$sBuildEnv; - $sBuildDir = $sBuildDir.'-build'; - - self::CheckDirectory($sBuildDir); - self::CheckDirectory($sBuildDir); + $sCurrentEnvDir = APPROOT.'env-'.$sBuildEnv; + self::CheckDirectory($sCurrentEnvDir); + self::CheckDirectory($sCurrentEnvDir.'-build'); } /** @@ -115,37 +112,4 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment } throw new Exception('No configuration file available'); } - - protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) - { - $aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir); - - // Add new mandatory modules from datamodel 2.x only - $sSourceDirFull = APPROOT.$sSourceDir; - if (!is_dir($sSourceDirFull)) { - throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)"); - } - $aDirsToCompile = [$sSourceDirFull]; - - $oFactory = new ModelFactory($aDirsToCompile); - $aModules = $oFactory->FindModules(); - $aAvailableModules = []; - /** @var \MFModule $oModule */ - foreach ($aModules as $oModule) { - $aAvailableModules[$oModule->GetName()] = $oModule; - } - // TODO check the auto-selected modules here - foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) { - if ($oExtension->bMarkedAsChosen) { - foreach ($oExtension->aModules as $sModuleName) { - if (!isset($aRet[$sModuleName]) && isset($aAvailableModules[$sModuleName])) { - $aRet[$sModuleName] = $aAvailableModules[$sModuleName]; - } - } - } - } - - return $aRet; - } - } diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php index 51cb8e0ba..8c240f9fc 100644 --- a/setup/extensionsmap.class.inc.php +++ b/setup/extensionsmap.class.inc.php @@ -35,18 +35,32 @@ class iTopExtensionsMap /** @var bool $bHasXmlInstallationFile : false when legacy 1.x package with no installation.xml */ protected $bHasXmlInstallationFile = true; + //extension dirs apart from package + protected array $aExtraDirs = []; + /** * The list of all discovered extensions + * * @param string $sFromEnvironment The environment to scan - * @param bool $bNormailizeOldExtension true to "magically" convert some well-known old extensions (i.e. a set of modules) to the new iTopExtension format + * @param array $aExtraDirs extensions dir to scan + * * @return void */ - public function __construct($sFromEnvironment = ITOP_DEFAULT_ENV, $aExtraDirs = []) + public function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = []) { $this->aExtensions = []; $this->aExtensionsByCode = []; $this->aScannedDirs = []; $this->ScanDisk($sFromEnvironment); + + $this->aExtraDirs = $aExtraDirs; + if (is_dir(APPROOT.'extensions')) { + $this->aExtraDirs [] = APPROOT.'extensions'; + } + if (is_dir(APPROOT.'data/'.$sFromEnvironment.'-modules')) { + $this->aExtraDirs [] = APPROOT.'data/'.$sFromEnvironment.'-modules'; + } + foreach ($aExtraDirs as $sDir) { $this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE); } @@ -64,13 +78,13 @@ class iTopExtensionsMap if (!$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x')) { $this->bHasXmlInstallationFile = false; //no installation xml found in 2.x: let's read all extensions in 2.x first - if (!$this->ReadDir(APPROOT.'/datamodels/2.x', iTopExtension::SOURCE_WIZARD)) { + if (!$this->ReadDir(APPROOT.'datamodels/2.x', iTopExtension::SOURCE_WIZARD)) { //nothing found in 2.x : fallback read in 1.x (flat structure) - $this->ReadDir(APPROOT.'/datamodels/1.x', iTopExtension::SOURCE_WIZARD); + $this->ReadDir(APPROOT.'datamodels/1.x', iTopExtension::SOURCE_WIZARD); } } - $this->ReadDir(APPROOT.'/extensions', iTopExtension::SOURCE_MANUAL); - $this->ReadDir(APPROOT.'/data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE); + $this->ReadDir(APPROOT.'extensions', iTopExtension::SOURCE_MANUAL); + $this->ReadDir(APPROOT.'data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE); } /** @@ -375,9 +389,10 @@ class iTopExtensionsMap } /** - * @param bool $bKeepMissingDependencyExtensions - * @param bool $bRemoteExtensionsShouldBeMandatory - * @return array<\iTopExtension>> + * @param bool $bKeepExtensionsHavingMissingDependencies + * @param bool $bRemoteExtensionsShouldBeMandatory + * + * @return \iTopExtension[] */ public function GetAllExtensionsToDisplayInSetup(bool $bKeepExtensionsHavingMissingDependencies = false, bool $bRemoteExtensionsShouldBeMandatory = true): array { @@ -589,6 +604,11 @@ class iTopExtensionsMap } } + public function GetExtraDirs(): array + { + return $this->aExtraDirs; + } + /** * Tells if the given module name is "chosen" since it is part of a "chosen" extension (in the specified source dir) * @param string $sModuleNameToFind diff --git a/setup/feature_removal/DryRemovalRuntimeEnvironment.php b/setup/feature_removal/DryRemovalRuntimeEnvironment.php index 6dff82d2e..ed86e87e2 100644 --- a/setup/feature_removal/DryRemovalRuntimeEnvironment.php +++ b/setup/feature_removal/DryRemovalRuntimeEnvironment.php @@ -2,15 +2,9 @@ namespace Combodo\iTop\Setup\FeatureRemoval; -use Combodo\iTop\Setup\ModuleDependency\Module; -use Config; -use InstallationChoicesToModuleConverter; use iTopExtensionsMap; -use MetaModel; -use ModuleDiscovery; use RunTimeEnvironment; use SetupUtils; -use utils; class DryRemovalRuntimeEnvironment extends RunTimeEnvironment { @@ -41,18 +35,6 @@ class DryRemovalRuntimeEnvironment extends RunTimeEnvironment SetupUtils::copydir(APPROOT."/conf/$sSourceEnv", APPROOT."/conf/$sBuildEnv"); $this->DeclareExtensionAsRemoved($this->aExtensionsByCode); - - $sSourceDir = MetaModel::GetConfig()->Get('source_dir'); - $aSearchDirs = $this->GetExtraDirsToCompile($sSourceDir); - - $aModulesToLoad = $this->GetModulesToLoad($sSourceEnv, $aSearchDirs); - - try { - ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true, $aModulesToLoad); - } catch (\MissingDependencyException $e) { - \IssueLog::Error("Cannot prepare setup due to dependency issue", null, ['msg' => $e->getMessage(), 'modules_to_load' => $aModulesToLoad]); - throw $e; - } } private function DeclareExtensionAsRemoved(array $aExtensionCodes): void @@ -61,27 +43,6 @@ class DryRemovalRuntimeEnvironment extends RunTimeEnvironment $oExtensionsMap->DeclareExtensionAsRemoved($aExtensionCodes); } - private function GetModulesToLoad(string $sSourceEnv, $aSearchDirs): array - { - $oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv)); - $aChoices = iTopExtensionsMap::GetChoicesFromDatabase($oSourceConfig); - $sSourceDir = $oSourceConfig->Get('source_dir'); - - $sInstallFilePath = APPROOT.$sSourceDir.'/installation.xml'; - if (! is_file($sInstallFilePath)) { - $sInstallFilePath = null; - } - - $aModuleIdsToLoad = InstallationChoicesToModuleConverter::GetInstance()->GetModules($aChoices, $aSearchDirs, $sInstallFilePath); - $aModulesToLoad = []; - foreach ($aModuleIdsToLoad as $sModuleId) { - $oModule = new Module($sModuleId); - $sModuleName = $oModule->GetModuleName(); - $aModulesToLoad[] = $sModuleName; - } - return $aModulesToLoad; - } - public function Cleanup(): void { $sEnv = $this->sBuildEnv; diff --git a/setup/modelfactory.class.inc.php b/setup/modelfactory.class.inc.php index e4d7e1840..f07fe4730 100644 --- a/setup/modelfactory.class.inc.php +++ b/setup/modelfactory.class.inc.php @@ -1073,15 +1073,23 @@ class ModelFactory */ public function LoadModule(MFModule $oModule, $aLanguages = []) { + $sRootDir = null; + $sModuleName = null; + $sModuleVersion = null; + try { - $aDataModels = $oModule->GetDataModelFiles(); + $sRootDir = $oModule->GetRootDir(); + $sModuleVersion = $oModule->GetVersion(); $sModuleName = $oModule->GetName(); + SetupLog::Debug("Loading module", null, [ $sModuleName, $sModuleVersion, $sRootDir]); + $aDataModels = $oModule->GetDataModelFiles(); + self::$aLoadedModules[] = $oModule; // For persistence in the cache $oModuleNode = $this->oDOMDocument->CreateElement('module'); $oModuleNode->setAttribute('id', $oModule->GetId()); - $oModuleNode->appendChild($this->oDOMDocument->CreateElement('root_dir', $oModule->GetRootDir())); + $oModuleNode->appendChild($this->oDOMDocument->CreateElement('root_dir', $sRootDir)); $oModuleNode->appendChild($this->oDOMDocument->CreateElement('label', $oModule->GetLabel())); $oModules = $this->oRoot->getElementsByTagName('loaded_modules')->item(0); @@ -1170,6 +1178,7 @@ class ModelFactory ]); } } catch (Exception $e) { + SetupLog::Exception("Cannot load module", $e, null, [ $sModuleName, $sModuleVersion, $sRootDir]); $aLoadedModuleNames = []; foreach (self::$aLoadedModules as $oLoadedModule) { $aLoadedModuleNames[] = $oLoadedModule->GetName().':'.$oLoadedModule->GetVersion(); diff --git a/setup/moduleinstallation/InstallationChoicesToModuleConverter.php b/setup/moduleinstallation/InstallationChoicesToModuleConverter.php index 9a1ace949..991431e27 100644 --- a/setup/moduleinstallation/InstallationChoicesToModuleConverter.php +++ b/setup/moduleinstallation/InstallationChoicesToModuleConverter.php @@ -6,6 +6,7 @@ */ use Combodo\iTop\Setup\ModuleDependency\DependencyExpression; +use Combodo\iTop\Setup\ModuleDependency\Module; require_once __DIR__.'/ModuleInstallationException.php'; require_once(APPROOT.'/setup/moduledependency/module.class.inc.php'); @@ -36,11 +37,12 @@ class InstallationChoicesToModuleConverter * @param array $aInstallationChoices * @param array $aSearchDirs * @param string|null $sInstallationFilePath + * @param array|null $aExtensionDirs : module/extension dirs to load if they are compliant with choices * * @return array * @throws \ModuleInstallationException */ - public function GetModules(array $aInstallationChoices, array $aSearchDirs, ?string $sInstallationFilePath = null): array + public function GetModules(array $aInstallationChoices, array $aSearchDirs, ?string $sInstallationFilePath = null, ?array $aExtensionDirs = null): array { $aPackageModules = $this->GetAllModules($aSearchDirs); @@ -61,11 +63,28 @@ class InstallationChoicesToModuleConverter foreach (array_keys($aPackageModules) as $sModuleId) { list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId); if (in_array($sModuleName, $aInstalledModuleNames)) { - $aInstalledModules[] = $sModuleId; + $aInstalledModules[$sModuleName] = $sModuleId; } } - return $aInstalledModules; + if (!is_null($aExtensionDirs)) { + foreach (array_keys($this->GetAllModules($aExtensionDirs)) as $sModuleId) { + $oModule = new Module($sModuleId); + + $sPreviousModuleId = $aInstalledModules[$oModule->GetModuleName()] ?? null; + if (is_null($sPreviousModuleId)) { + $aInstalledModules[$oModule->GetModuleName()] = $sModuleId; + continue; + } + + $oPreviousModule = new Module($sPreviousModuleId); + if (version_compare($oModule->GetVersion(), $oPreviousModule->GetVersion(), '>')) { + $aInstalledModules[$oModule->GetModuleName()] = $sModuleId; + } + } + } + + return array_values($aInstalledModules); } /** diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 46f58175e..9622f7c9a 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -26,6 +26,7 @@ use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator; use Combodo\iTop\Setup\FeatureRemoval\SetupAudit; +use Combodo\iTop\Setup\ModuleDependency\Module; use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader; use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException; @@ -145,10 +146,6 @@ class RunTimeEnvironment */ public function InitDataModel($oConfig, $bModelOnly = true, $bUseCache = false): void { - // if (self::$bMetamodelStarted && $bModelOnly) { - // return; - // } - $sConfigFile = $oConfig->GetLoadedFile(); if (strlen($sConfigFile) > 0) { $this->log_info("MetaModel::Startup from $sConfigFile (ModelOnly = $bModelOnly)"); @@ -168,7 +165,6 @@ class RunTimeEnvironment $_SESSION['itop_env'] = $this->sBuildEnv; MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false, $this->sBuildEnv); - self::$bMetamodelStarted = true; if ($this->oExtensionsMap === null) { $this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv); @@ -464,10 +460,7 @@ class RunTimeEnvironment $aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile); $aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs); - // Determine the installed modules and extensions - // $oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE); - $aAvailableModules = $this->AnalyzeInstallation($oSourceConfig, $aDirsToCompile); // Actually read the modules available for the build environment, // but get the selection from the source environment and finally @@ -482,6 +475,9 @@ class RunTimeEnvironment } } + $aModulesToLoad = $this->GetModulesToLoad($this->sFinalEnv, $aDirsToCompile); + $aAvailableModules = $this->AnalyzeInstallation($oSourceConfig, $aDirsToCompile, false, $aModulesToLoad); + // Do load the required modules // $oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries'); @@ -1593,4 +1589,51 @@ class RunTimeEnvironment return substr_compare($sHaystack, $sNeedle, 0, strlen($sNeedle)) === 0; } + + /** + * @param string $sSourceEnv + * @param array $aSearchDirs : module/extension dirs to load if they are included in choices + * + * @return array| null + * @throws \ConfigException + * @throws \CoreException + * @throws \ModuleInstallationException + */ + protected function GetModulesToLoad(string $sSourceEnv, array $aSearchDirs): ?array + { + if (is_null($this->GetExtensionMap())) { + return null; + } + + $oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv)); + + $aChoices = $this->GetExtensionMap()->GetChoicesFromDatabase($oSourceConfig); + if (false === $aChoices) { + return null; + } + $sSourceDir = $oSourceConfig->Get('source_dir'); + + $sInstallFilePath = APPROOT.$sSourceDir.'/installation.xml'; + if (! is_file($sInstallFilePath)) { + $sInstallFilePath = null; + } + + $aExtensionDirs = []; + foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) { + if ($oExtension->bMarkedAsChosen && is_dir($oExtension->sSourceDir)) { + $aExtensionDirs []= $oExtension->sSourceDir; + } + } + + SetupLog::Info(__METHOD__, null, ['ext_dirs' => $aExtensionDirs]); + $aModuleIdsToLoad = InstallationChoicesToModuleConverter::GetInstance()->GetModules($aChoices, $aSearchDirs, $sInstallFilePath, $aExtensionDirs); + $aModulesToLoad = []; + foreach ($aModuleIdsToLoad as $sModuleId) { + $oModule = new Module($sModuleId); + $sModuleName = $oModule->GetModuleName(); + $aModulesToLoad[] = $sModuleName; + } + + return $aModulesToLoad; + } } diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php index 9d97f9362..393d309d1 100644 --- a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php +++ b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php @@ -197,6 +197,7 @@ abstract class ItopTestCase extends KernelTestCase } SetupUtils::tidydir($sPath); + SetupUtils::rrmdir($sPath); } } diff --git a/tests/php-unit-tests/unitary-tests/setup/feature_removal/SetupAuditTest.php b/tests/php-unit-tests/unitary-tests/setup/feature_removal/SetupAuditTest.php index 4404fc1c2..c9efefa4b 100644 --- a/tests/php-unit-tests/unitary-tests/setup/feature_removal/SetupAuditTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/feature_removal/SetupAuditTest.php @@ -54,7 +54,7 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase $oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment($this->GetTestEnvironment(), ['nominal_ext1', 'finalclass_ext2']); $oDryRemovalRuntimeEnvt->CompileFrom($this->GetTestEnvironment()); - $oSetupAudit = new SetupAudit(MetaModel::GetEnvironment()); + $oSetupAudit = new SetupAudit($this->GetTestEnvironment()); $expected = [ "Feature1Module1MyClass", diff --git a/tests/php-unit-tests/unitary-tests/setup/moduleinstallation/InstallationChoicesToModuleConverterTest.php b/tests/php-unit-tests/unitary-tests/setup/moduleinstallation/InstallationChoicesToModuleConverterTest.php index 756d1a176..4aaaacc41 100644 --- a/tests/php-unit-tests/unitary-tests/setup/moduleinstallation/InstallationChoicesToModuleConverterTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/moduleinstallation/InstallationChoicesToModuleConverterTest.php @@ -170,6 +170,109 @@ class InstallationChoicesToModuleConverterTest extends ItopDataTestCase $this->assertEquals($aExpected, $aInstalledModules); } + private function GivenNewExtensionDir($MainDir, $sExtensionCode, $sModuleCode, $sModuleVersion): string + { + $sExtDir = "$MainDir/$sExtensionCode"; + + mkdir($sExtDir, recursive:true); + $sModuleDir = $sExtDir."/$sModuleCode"; + mkdir($sModuleDir); + $sContent = << '$sModuleCode', + 'category' => 'authentication', + 'dependencies' => [], + 'mandatory' => true, + 'visible' => true, + 'datamodel' => [], + 'webservice' => [], + 'data.struct' => [], + 'data.sample' => [], + 'doc.manual_setup' => '', + 'doc.more_information' => '', + ] +); +PHP; + file_put_contents($sModuleDir."/module.$sModuleCode.php", $sContent); + + return $sExtDir; + } + + //integration + public function testGetModules_ComputeAdditionalExtensionDirectories() + { + $aSearchDirs = $this->GivenModuleDiscoveryInit(); + + $MainDir = __DIR__."/ressources/InstallationChoicesToModuleConverterTest"; + $this->aFileToClean [] = $MainDir; + $aExtensionDirs = [ + //should replace same one in the package + $this->GivenNewExtensionDir($MainDir, 'itop-oauth-client', 'itop-oauth-client', '6.6.0'), + //not in the package => should be added after installation + $this->GivenNewExtensionDir($MainDir, 'shadok', 'gabu-zomeu', '1.2.3'), + //same one in the package should remain + $this->GivenNewExtensionDir($MainDir, 'itop-files-information', 'itop-files-information', '3.3.0'), + ]; + + $aInstalledModules = InstallationChoicesToModuleConverter::GetInstance()->GetModules( + $this->GivenNonItilChoices(), + $aSearchDirs, + __DIR__.'/ressources/installation.xml', + $aExtensionDirs, + ); + + $aExpected = [ + 'authent-cas/3.3.0', + 'authent-external/3.3.0', + 'authent-ldap/3.3.0', + 'authent-local/3.3.0', + 'combodo-backoffice-darkmoon-theme/3.3.0', + 'combodo-backoffice-fullmoon-high-contrast-theme/3.3.0', + 'combodo-backoffice-fullmoon-protanopia-deuteranopia-theme/3.3.0', + 'combodo-backoffice-fullmoon-tritanopia-theme/3.3.0', + 'itop-attachments/3.3.0', + 'itop-backup/3.3.0', + 'itop-config/3.3.0', + 'itop-files-information/3.3.0', + 'itop-portal-base/3.3.0', + 'itop-portal/3.3.0', + 'itop-profiles-itil/3.3.0', + 'itop-sla-computation/3.3.0', + 'itop-structure/3.3.0', + 'itop-themes-compat/3.3.0', + 'itop-tickets/3.3.0', + 'itop-welcome-itil/3.3.0', + 'combodo-db-tools/3.3.0', + 'itop-config-mgmt/3.3.0', + 'itop-core-update/3.3.0', + 'itop-datacenter-mgmt/3.3.0', + 'itop-endusers-devices/3.3.0', + 'itop-faq-light/3.3.0', + 'itop-hub-connector/3.3.0', + 'itop-knownerror-mgmt/3.3.0', + 'itop-oauth-client/6.6.0', + 'itop-request-mgmt/3.3.0', + 'itop-service-mgmt/3.3.0', + 'itop-storage-mgmt/3.3.0', + 'itop-virtualization-mgmt/3.3.0', + 'itop-bridge-cmdb-services/3.3.0', + 'itop-bridge-cmdb-ticket/3.3.0', + 'itop-bridge-datacenter-mgmt-services/3.3.0', + 'itop-bridge-endusers-devices-services/3.3.0', + 'itop-bridge-storage-mgmt-services/3.3.0', + 'itop-bridge-virtualization-mgmt-services/3.3.0', + 'itop-bridge-virtualization-storage/3.3.0', + 'itop-change-mgmt/3.3.0', + 'gabu-zomeu/1.2.3', + ]; + $this->assertEquals($aExpected, $aInstalledModules); + } + public function testIsDefaultModule_RootModuleShouldNeverBeDefault() { $sModuleId = ROOT_MODULE; @@ -423,8 +526,7 @@ class InstallationChoicesToModuleConverterTest extends ItopDataTestCase { $aSearchDirs = [APPROOT.'datamodels/2.x']; $this->SetNonPublicStaticProperty(ModuleDiscovery::class, 'm_aSearchDirs', $aSearchDirs); - $aAllModules = $this->GivenAllModules(); - $this->SetNonPublicStaticProperty(ModuleDiscovery::class, 'm_aModules', $aAllModules); + $this->SetNonPublicStaticProperty(ModuleDiscovery::class, 'm_aModules', $this->GivenAllModules()); return $aSearchDirs; } }