[TASK] Stabilize / Solidify public API of adminPanel 14/57714/2
authorSusanne Moog <susanne.moog@typo3.org>
Sun, 29 Jul 2018 11:27:48 +0000 (13:27 +0200)
committerWouter Wolters <typo3@wouterwolters.nl>
Sun, 29 Jul 2018 12:49:25 +0000 (14:49 +0200)
* public service classes for extension authors have gotten tests
* exceptions in public API have been made more specific
* non-API has been defined as internal
* API has been cleaned up

Resolves: #85675
Releases: master
Change-Id: Icc9bab027eaca07fccd9368763350487959148a7
Reviewed-on: https://review.typo3.org/57714
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
13 files changed:
typo3/sysext/adminpanel/Classes/Controller/AjaxController.php
typo3/sysext/adminpanel/Classes/Exceptions/InvalidConfigurationException.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/Hooks/RenderHook.php
typo3/sysext/adminpanel/Classes/Middleware/AdminPanelInitiator.php
typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
typo3/sysext/adminpanel/Classes/Repositories/FrontendGroupsRepository.php
typo3/sysext/adminpanel/Classes/Service/ConfigurationService.php
typo3/sysext/adminpanel/Classes/Service/ModuleLoader.php
typo3/sysext/adminpanel/Tests/Unit/Fixtures/DisabledMainModuleFixture.php [new file with mode: 0644]
typo3/sysext/adminpanel/Tests/Unit/Fixtures/MainModuleFixture.php [new file with mode: 0644]
typo3/sysext/adminpanel/Tests/Unit/Fixtures/SubModuleFixture.php [new file with mode: 0644]
typo3/sysext/adminpanel/Tests/Unit/Service/ConfigurationServiceTest.php [new file with mode: 0644]
typo3/sysext/adminpanel/Tests/Unit/Service/ModuleLoaderTest.php [new file with mode: 0644]

index 23db002..0bb0362 100644 (file)
@@ -25,6 +25,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Admin Panel Ajax Controller - Route endpoint for ajax actions
+ *
+ * @internal
  */
 class AjaxController
 {
diff --git a/typo3/sysext/adminpanel/Classes/Exceptions/InvalidConfigurationException.php b/typo3/sysext/adminpanel/Classes/Exceptions/InvalidConfigurationException.php
new file mode 100644 (file)
index 0000000..7b9ca6c
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Exceptions;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Exception class for invalid admin panel configuration
+ */
+class InvalidConfigurationException extends \RuntimeException
+{
+}
index 09c45ee..7ed79b0 100644 (file)
@@ -23,6 +23,8 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
  * Hook to render the admin panel
+ *
+ * @internal
  */
 class RenderHook
 {
index 19f0f73..1db10f3 100644 (file)
@@ -27,6 +27,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * PSR-15 Middleware to initialize the admin panel
+ *
+ * @internal
  */
 class AdminPanelInitiator implements MiddlewareInterface
 {
index 30e75b3..300e986 100644 (file)
@@ -128,8 +128,9 @@ class PreviewModule extends AbstractModule
         $activeConfiguration = (int)$this->getConfigOptionForModule('showFluidDebug');
         if (isset($input['preview_showFluidDebug']) && (int)$input['preview_showFluidDebug'] !== $activeConfiguration) {
             $pageId = (int)$request->getParsedBody()['TSFE_ADMIN_PANEL']['preview_clearCacheId'];
-            $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_pages');
-            $cache->flushByTag('pageId_' . $pageId);
+            $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
+            $cacheManager->getCache('cache_pages')->flushByTag('pageId_' . $pageId);
+            $cacheManager->getCache('fluid_template')->flush();
         }
     }
 
index 6f18401..695cc7c 100644 (file)
@@ -24,6 +24,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Admin Panel Frontend Groups Repository
+ *
+ * @internal
  */
 class FrontendGroupsRepository
 {
index add0623..df4f443 100644 (file)
@@ -20,10 +20,13 @@ use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\SingletonInterface;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 
+/**
+ * Admin Panel Service Class for Configuration Handling
+ *
+ * Scope: User TSConfig + Backend User UC
+ */
 class ConfigurationService implements SingletonInterface
 {
     /**
@@ -56,12 +59,14 @@ class ConfigurationService implements SingletonInterface
      */
     public function getConfigurationOption(string $identifier, string $option): string
     {
-        $beUser = $this->getBackendUser();
+        if ($identifier === '' || $option === '') {
+            throw new \InvalidArgumentException('Identifier and option may not be empty', 1532861423);
+        }
 
-        if ($option && isset($this->mainConfiguration['override.'][$identifier . '.'][$option])) {
+        if (isset($this->mainConfiguration['override.'][$identifier . '.'][$option])) {
             $returnValue = $this->mainConfiguration['override.'][$identifier . '.'][$option];
         } else {
-            $returnValue = $beUser->uc['TSFE_adminConfig'][$identifier . '_' . $option] ?? '';
+            $returnValue = $this->getBackendUser()->uc['TSFE_adminConfig'][$identifier . '_' . $option] ?? '';
         }
 
         return (string)$returnValue;
@@ -98,10 +103,6 @@ class ConfigurationService implements SingletonInterface
         unset($beUser->uc['TSFE_adminConfig']['action']);
         // Saving
         $beUser->writeUC();
-        // Flush fluid template cache
-        $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
-        $cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
-        $cacheManager->getCache('fluid_template')->flush();
     }
 
     /**
index 5571452..07f2897 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Adminpanel\Service;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Adminpanel\Exceptions\InvalidConfigurationException;
 use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
 use TYPO3\CMS\Adminpanel\Modules\AdminPanelSubModuleInterface;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
@@ -44,7 +45,7 @@ class ModuleLoader
         }
         foreach ($modules as $identifier => $configuration) {
             if (empty($configuration) || !is_array($configuration)) {
-                throw new \RuntimeException(
+                throw new InvalidConfigurationException(
                     'Missing configuration for module "' . $identifier . '".',
                     1519490105
                 );
@@ -54,10 +55,11 @@ class ModuleLoader
                 !class_exists($configuration['module']) ||
                 !is_subclass_of(
                     $configuration['module'],
-                    ($type === 'main' ? AdminPanelModuleInterface::class : AdminPanelSubModuleInterface::class)
+                    ($type === 'main' ? AdminPanelModuleInterface::class : AdminPanelSubModuleInterface::class),
+                    true
                 )
             ) {
-                throw new \RuntimeException(
+                throw new InvalidConfigurationException(
                     'The module "' .
                     $identifier .
                     '" defines an invalid module class. Ensure the class exists and implements the "' .
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Fixtures/DisabledMainModuleFixture.php b/typo3/sysext/adminpanel/Tests/Unit/Fixtures/DisabledMainModuleFixture.php
new file mode 100644 (file)
index 0000000..3969eec
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+class DisabledMainModuleFixture extends MainModuleFixture
+{
+    public function isEnabled(): bool
+    {
+        return false;
+    }
+}
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Fixtures/MainModuleFixture.php b/typo3/sysext/adminpanel/Tests/Unit/Fixtures/MainModuleFixture.php
new file mode 100644 (file)
index 0000000..3633f78
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
+use TYPO3\CMS\Adminpanel\Modules\AdminPanelSubModuleInterface;
+
+class MainModuleFixture implements AdminPanelModuleInterface
+{
+
+    /**
+     * Identifier for this module,
+     * for example "preview" or "cache"
+     *
+     * @return string
+     */
+    public function getIdentifier(): string
+    {
+        return 'example';
+    }
+
+    /**
+     * Module label
+     *
+     * @return string
+     */
+    public function getLabel(): string
+    {
+        return 'Example Label';
+    }
+
+    /**
+     * Module Icon identifier - needs to be registered in iconRegistry
+     *
+     * @return string
+     */
+    public function getIconIdentifier(): string
+    {
+        return 'actions-document-info';
+    }
+
+    /**
+     * Displayed directly in the bar if module has content
+     *
+     * @return string
+     */
+    public function getShortInfo(): string
+    {
+        return 'short info';
+    }
+
+    /**
+     * @return string
+     */
+    public function getSettings(): string
+    {
+        return 'example settings';
+    }
+
+    /**
+     * Initialize the module - runs early in a TYPO3 request
+     *
+     * @param ServerRequestInterface $request
+     */
+    public function initializeModule(ServerRequestInterface $request): void
+    {
+    }
+
+    /**
+     * Module is enabled
+     * -> should be initialized
+     * A module may be enabled but not shown
+     * -> only the initializeModule() method
+     * will be called
+     *
+     * @return bool
+     */
+    public function isEnabled(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Executed on saving / submit of the configuration form
+     * Can be used to react to changed settings
+     * (for example: clearing a specific cache)
+     *
+     * @param array $configurationToSave
+     * @param ServerRequestInterface $request
+     */
+    public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void
+    {
+    }
+
+    /**
+     * Returns a string array with javascript files that will be rendered after the module
+     *
+     * @return array
+     */
+    public function getJavaScriptFiles(): array
+    {
+        return [];
+    }
+
+    /**
+     * Returns a string array with css files that will be rendered after the module
+     *
+     * @return array
+     */
+    public function getCssFiles(): array
+    {
+        return [];
+    }
+
+    /**
+     * Set SubModules for current module
+     *
+     * @param AdminPanelSubModuleInterface[] $subModules
+     */
+    public function setSubModules(array $subModules): void
+    {
+    }
+
+    /**
+     * Get SubModules for current module
+     *
+     * @return AdminPanelSubModuleInterface[]
+     */
+    public function getSubModules(): array
+    {
+        return [];
+    }
+
+    /**
+     * Returns true if submodule has own settings
+     *
+     * @return bool
+     */
+    public function getHasSubmoduleSettings(): bool
+    {
+        return false;
+    }
+}
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Fixtures/SubModuleFixture.php b/typo3/sysext/adminpanel/Tests/Unit/Fixtures/SubModuleFixture.php
new file mode 100644 (file)
index 0000000..880fde4
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\Modules\AdminPanelSubModuleInterface;
+
+class SubModuleFixture implements AdminPanelSubModuleInterface
+{
+
+    /**
+     * Initialize the module - runs early in a TYPO3 request
+     *
+     * @param ServerRequestInterface $request
+     */
+    public function initializeModule(ServerRequestInterface $request): void
+    {
+    }
+
+    /**
+     * Identifier for this Sub-module,
+     * for example "preview" or "cache"
+     *
+     * @return string
+     */
+    public function getIdentifier(): string
+    {
+        return 'example-submodule';
+    }
+
+    /**
+     * Sub-Module label
+     *
+     * @return string
+     */
+    public function getLabel(): string
+    {
+        return 'Example SubModule';
+    }
+
+    /**
+     * Sub-Module content as rendered HTML
+     *
+     * @return string
+     */
+    public function getContent(): string
+    {
+        return 'content';
+    }
+
+    /**
+     * Settings as HTML form elements (without wrapping form tag or save button)
+     *
+     * @return string
+     */
+    public function getSettings(): string
+    {
+        return 'settings';
+    }
+
+    /**
+     * Executed on saving / submit of the configuration form
+     * Can be used to react to changed settings
+     * (for example: clearing a specific cache)
+     *
+     * @param array $configurationToSave
+     * @param ServerRequestInterface $request
+     */
+    public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void
+    {
+    }
+}
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Service/ConfigurationServiceTest.php b/typo3/sysext/adminpanel/Tests/Unit/Service/ConfigurationServiceTest.php
new file mode 100644 (file)
index 0000000..c344b75
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Tests\Unit\Service;
+
+use Prophecy\Argument;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
+use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\MainModuleFixture;
+use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\SubModuleFixture;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class ConfigurationServiceTest extends UnitTestCase
+{
+    /**
+     * @var BackendUserAuthentication|\Prophecy\Prophecy\ObjectProphecy
+     */
+    protected $beUserProphecy;
+
+    public function setUp()
+    {
+        parent::setUp();
+        $this->beUserProphecy = $this->prophesize(BackendUserAuthentication::class);
+        $GLOBALS['BE_USER'] = $this->beUserProphecy->reveal();
+    }
+
+    /**
+     * @test
+     */
+    public function getMainConfigurationReturnsTsConfigFromUser(): void
+    {
+        $userTsAdmPanelConfig = [
+            'enable.' => [
+                'all' => '1',
+            ],
+        ];
+        $this->setUpUserTsConfigForAdmPanel($userTsAdmPanelConfig);
+
+        $configurationService = new ConfigurationService();
+        $result = $configurationService->getMainConfiguration();
+
+        self::assertSame($userTsAdmPanelConfig, $result);
+    }
+
+    /**
+     * @test
+     */
+    public function getConfigurationOptionReturnsEmptyStringIfNoConfigurationFound(): void
+    {
+        $configurationService = new ConfigurationService();
+        $result = $configurationService->getConfigurationOption('foo', 'bar');
+        self::assertSame('', $result);
+    }
+
+    /**
+     * @test
+     */
+    public function getConfigurationOptionReturnsOverrideOptionIfSet(): void
+    {
+        $this->setUpUserTsConfigForAdmPanel(
+            [
+                'override.' => [
+                    'preview.' => [
+                        'showHiddenPages' => '1',
+                    ],
+                ],
+            ]
+        );
+
+        $configurationService = new ConfigurationService();
+        $result = $configurationService->getConfigurationOption('preview', 'showHiddenPages');
+
+        self::assertSame('1', $result);
+    }
+
+    /**
+     * @test
+     */
+    public function getConfigurationOptionCastsResultToString(): void
+    {
+        $this->setUpUserTsConfigForAdmPanel(
+            [
+                'override.' => [
+                    'preview.' => [
+                        'showHiddenPages' => 1,
+                    ],
+                ],
+            ]
+        );
+
+        $configurationService = new ConfigurationService();
+        $result = $configurationService->getConfigurationOption('preview', 'showHiddenPages');
+
+        self::assertSame('1', $result);
+    }
+
+    public function getConfigurationOptionEmptyArgumentDataProvider(): array
+    {
+        return [
+            'empty identifier' => [
+                '',
+                'foo',
+            ],
+            'empty option' => [
+                'foo',
+                '',
+            ],
+            'both empty' => [
+                '',
+                '',
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getConfigurationOptionEmptyArgumentDataProvider
+     * @param $identifier
+     * @param $option
+     */
+    public function getConfigurationOptionThrowsExceptionOnEmptyArgument($identifier, $option): void
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1532861423);
+
+        $configurationService = new ConfigurationService();
+        $configurationService->getConfigurationOption($identifier, $option);
+    }
+
+    /**
+     * @test
+     */
+    public function getConfigurationOptionReturnsSettingFromUcIfNoOverrideGiven(): void
+    {
+        $this->setUpUserTsConfigForAdmPanel([]);
+        $this->beUserProphecy->uc = [
+            'TSFE_adminConfig' => [
+                'preview_showHiddenPages' => '1',
+            ],
+        ];
+
+        $configurationService = new ConfigurationService();
+        $result = $configurationService->getConfigurationOption('preview', 'showHiddenPages');
+
+        self::assertSame('1', $result);
+    }
+
+    /**
+     * @test
+     */
+    public function saveConfigurationTriggersOnSubmitOnEnabledModules(): void
+    {
+        $subModuleFixture = $this->prophesize(SubModuleFixture::class);
+        $mainModuleFixture = $this->prophesize(MainModuleFixture::class);
+        $mainModuleFixture->isEnabled()->willReturn(true);
+        $mainModuleFixture->onSubmit(Argument::cetera())->shouldBeCalled()->hasReturnVoid();
+        $mainModuleFixture->getSubModules()->willReturn(
+            [$subModuleFixture->reveal()]
+        );
+        $modules = [
+            $mainModuleFixture->reveal(),
+        ];
+
+        $requestProphecy = $this->prophesize(ServerRequestInterface::class);
+
+        $configurationService = new ConfigurationService();
+        $configurationService->saveConfiguration($modules, $requestProphecy->reveal());
+
+        $mainModuleFixture->onSubmit([], $requestProphecy->reveal())->shouldHaveBeenCalled();
+        $subModuleFixture->onSubmit([], $requestProphecy->reveal())->shouldHaveBeenCalled();
+    }
+
+    /**
+     * @test
+     */
+    public function saveConfigurationSavesMergedExistingAndNewConfiguration(): void
+    {
+        // existing configuration from UC
+        $this->beUserProphecy->uc = [
+            'TSFE_adminConfig' => [
+                'foo' => 'bar',
+            ],
+        ];
+
+        // new configuration to save
+        $requestProphecy = $this->prophesize(ServerRequestInterface::class);
+        $requestProphecy->getParsedBody()->willReturn(
+            [
+                'TSFE_ADMIN_PANEL' => [
+                    'baz' => 'bam',
+                ],
+            ]
+        );
+
+        $configurationService = new ConfigurationService();
+        $configurationService->saveConfiguration([], $requestProphecy->reveal());
+
+        $expected = [
+            'TSFE_adminConfig' => [
+                'foo' => 'bar',
+                'baz' => 'bam',
+            ],
+        ];
+        self::assertSame($expected, $this->beUserProphecy->uc);
+        $this->beUserProphecy->writeUC()->shouldHaveBeenCalled();
+    }
+
+    /**
+     * @param $userTsAdmPanelConfig
+     */
+    private function setUpUserTsConfigForAdmPanel($userTsAdmPanelConfig): void
+    {
+        $this->beUserProphecy->getTSConfig('admPanel')->willReturn(
+            ['properties' => $userTsAdmPanelConfig]
+        );
+    }
+}
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Service/ModuleLoaderTest.php b/typo3/sysext/adminpanel/Tests/Unit/Service/ModuleLoaderTest.php
new file mode 100644 (file)
index 0000000..f951c65
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Tests\Unit\Service;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Adminpanel\Exceptions\InvalidConfigurationException;
+use TYPO3\CMS\Adminpanel\Service\ModuleLoader;
+use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\DisabledMainModuleFixture;
+use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\MainModuleFixture;
+use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\SubModuleFixture;
+use TYPO3\CMS\Core\Service\DependencyOrderingService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class ModuleLoaderTest extends UnitTestCase
+{
+
+    /**
+     * @test
+     */
+    public function validateSortAndInitializeModulesReturnsEmptyArrayIfNoModulesAreConfigured()
+    {
+        $moduleLoader = new ModuleLoader();
+        $result = $moduleLoader->validateSortAndInitializeModules([]);
+
+        self::assertSame([], $result);
+    }
+
+    public function missingConfigurationDataProvider(): array
+    {
+        return [
+            'empty' => [['modulename' => []]],
+            'no array' => [['modulename' => '']],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider missingConfigurationDataProvider
+     */
+    public function validateSortAndInitializeModulesThrowsExceptionIfModuleHasMissingConfiguration($configuration): void
+    {
+        $this->expectException(InvalidConfigurationException::class);
+        $this->expectExceptionCode(1519490105);
+
+        $moduleLoader = new ModuleLoader();
+        $moduleLoader->validateSortAndInitializeModules($configuration);
+    }
+
+    public function invalidConfigurationDataProvider(): array
+    {
+        return [
+            'module class name is no string' => [
+                [
+                    'modulename' => ['module' => []],
+                ],
+            ],
+            'module class name is empty' => [
+                [
+                    'modulename' => ['module' => ''],
+                ],
+            ],
+            'module class name is no valid class' => [
+                [
+                    'modulename' => ['module' => 'nonExistingClassName'],
+                ],
+            ],
+            'module class name does not implement AdminPanelModuleInterface' => [
+                [
+                    'modulename' => ['module' => \stdClass::class],
+                ],
+            ],
+            'submodule class name given when main modules requested' => [
+                [
+                    'modulename' => ['module' => SubModuleFixture::class]
+                ]
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider  invalidConfigurationDataProvider
+     */
+    public function validateSortAndInitializeModulesThrowsExceptionIfModuleHasInvalidConfiguration($configuration): void
+    {
+        $this->expectException(InvalidConfigurationException::class);
+        $this->expectExceptionCode(1519490112);
+
+        $moduleLoader = new ModuleLoader();
+        $moduleLoader->validateSortAndInitializeModules($configuration);
+    }
+
+    /**
+     * @test
+     * @dataProvider  invalidConfigurationDataProvider
+     */
+    public function validateSortAndInitializeModulesThrowsExceptionIfSubModuleRequestedButMainModuleGiven($configuration): void
+    {
+        $config = [
+            'module1' => [
+                'module' => MainModuleFixture::class
+            ]
+        ];
+
+        $this->expectException(InvalidConfigurationException::class);
+        $this->expectExceptionCode(1519490112);
+
+        $moduleLoader = new ModuleLoader();
+        $moduleLoader->validateSortAndInitializeModules($config, 'sub');
+    }
+
+    /**
+     * @test
+     */
+    public function validateSortAndInitializeModulesOrdersModulesWithDependencyOrderingService(): void
+    {
+        $config = [
+            'module1' => [
+                'module' => MainModuleFixture::class
+            ]
+        ];
+
+        $dependencyOrderingServiceProphecy = $this->prophesize(DependencyOrderingService::class);
+        GeneralUtility::addInstance(DependencyOrderingService::class, $dependencyOrderingServiceProphecy->reveal());
+        $dependencyOrderingServiceProphecy->orderByDependencies($config)->willReturn($config);
+
+        $moduleLoader = new ModuleLoader();
+        $moduleLoader->validateSortAndInitializeModules($config);
+
+        $dependencyOrderingServiceProphecy->orderByDependencies($config)->shouldHaveBeenCalled();
+    }
+
+    /**
+     * @test
+     */
+    public function validateSortAndInitializeModulesInstantiatesMainModulesOnlyIfEnabled(): void
+    {
+        $config = [
+            'module1' => [
+                'module' => MainModuleFixture::class
+            ],
+            'module2' => [
+                'module' => DisabledMainModuleFixture::class
+            ]
+        ];
+
+        $dependencyOrderingServiceProphecy = $this->prophesize(DependencyOrderingService::class);
+        GeneralUtility::addInstance(DependencyOrderingService::class, $dependencyOrderingServiceProphecy->reveal());
+        $dependencyOrderingServiceProphecy->orderByDependencies($config)->willReturn($config);
+
+        $moduleLoader = new ModuleLoader();
+        $result = $moduleLoader->validateSortAndInitializeModules($config);
+
+        self::assertCount(1, $result);
+        self::assertInstanceOf(MainModuleFixture::class, $result[0]);
+        self::assertNotInstanceOf(DisabledMainModuleFixture::class, $result[0]);
+    }
+
+    /**
+     * @test
+     */
+    public function validateSortAndInitializeSubModulesInstantiatesSubModules(): void
+    {
+        $config = [
+            'module1' => [
+                'module' => SubModuleFixture::class
+            ],
+            'module2' => [
+                'module' => SubModuleFixture::class
+            ]
+        ];
+
+        $dependencyOrderingServiceProphecy = $this->prophesize(DependencyOrderingService::class);
+        GeneralUtility::addInstance(DependencyOrderingService::class, $dependencyOrderingServiceProphecy->reveal());
+        $dependencyOrderingServiceProphecy->orderByDependencies($config)->willReturn($config);
+
+        $moduleLoader = new ModuleLoader();
+        $result = $moduleLoader->validateSortAndInitializeSubModules($config);
+
+        self::assertCount(2, $result);
+        self::assertInstanceOf(SubModuleFixture::class, $result[0]);
+        self::assertInstanceOf(SubModuleFixture::class, $result[1]);
+    }
+}