From 30c0f3d79da72954c718a6974ec932a37eb78cff Mon Sep 17 00:00:00 2001 From: Stefan Froemken Date: Sun, 12 Jan 2025 02:50:34 +0100 Subject: [PATCH 1/3] Add func tests for ExtConf and ExtConfFactory --- Build/FunctionalTests.xml | 28 -- Build/UnitTests.xml | 29 -- Build/phpunit/FunctionalTests.xml | 26 ++ .../FunctionalTestsBootstrap.php | 4 +- Build/phpunit/UnitTests.xml | 31 ++ Build/{ => phpunit}/UnitTestsBootstrap.php | 19 +- Classes/Configuration/ExtConf.php | 25 +- Classes/Configuration/ExtConfFactory.php | 66 ++++ Configuration/Icons.php | 2 +- Configuration/Services.yaml | 3 + .../Configuration/ExtConfFactoryTest.php | 290 ++++++++++++++++++ .../Functional/Configuration/ExtConfTest.php | 72 ++++- composer.json | 13 +- 13 files changed, 511 insertions(+), 97 deletions(-) delete mode 100644 Build/FunctionalTests.xml delete mode 100644 Build/UnitTests.xml create mode 100644 Build/phpunit/FunctionalTests.xml rename Build/{ => phpunit}/FunctionalTestsBootstrap.php (93%) create mode 100644 Build/phpunit/UnitTests.xml rename Build/{ => phpunit}/UnitTestsBootstrap.php (83%) create mode 100644 Classes/Configuration/ExtConfFactory.php create mode 100644 Tests/Functional/Configuration/ExtConfFactoryTest.php diff --git a/Build/FunctionalTests.xml b/Build/FunctionalTests.xml deleted file mode 100644 index 59a41b6..0000000 --- a/Build/FunctionalTests.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - ../Tests/Functional/ - - - - - - - diff --git a/Build/UnitTests.xml b/Build/UnitTests.xml deleted file mode 100644 index a90da4c..0000000 --- a/Build/UnitTests.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - ../Tests/Unit/ - - - - - - - diff --git a/Build/phpunit/FunctionalTests.xml b/Build/phpunit/FunctionalTests.xml new file mode 100644 index 0000000..7c503be --- /dev/null +++ b/Build/phpunit/FunctionalTests.xml @@ -0,0 +1,26 @@ + + + + + + + ../../Tests/Functional/ + + + + + + + diff --git a/Build/FunctionalTestsBootstrap.php b/Build/phpunit/FunctionalTestsBootstrap.php similarity index 93% rename from Build/FunctionalTestsBootstrap.php rename to Build/phpunit/FunctionalTestsBootstrap.php index 8bad2c5..079170b 100644 --- a/Build/FunctionalTestsBootstrap.php +++ b/Build/phpunit/FunctionalTestsBootstrap.php @@ -9,9 +9,9 @@ use TYPO3\TestingFramework\Core\Testbase; -call_user_func(function () { +(static function () { $testbase = new Testbase(); $testbase->defineOriginalRootPath(); $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests'); $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient'); -}); +})(); diff --git a/Build/phpunit/UnitTests.xml b/Build/phpunit/UnitTests.xml new file mode 100644 index 0000000..aad070d --- /dev/null +++ b/Build/phpunit/UnitTests.xml @@ -0,0 +1,31 @@ + + + + + + + ../../Tests/Unit/ + + + + + + + diff --git a/Build/UnitTestsBootstrap.php b/Build/phpunit/UnitTestsBootstrap.php similarity index 83% rename from Build/UnitTestsBootstrap.php rename to Build/phpunit/UnitTestsBootstrap.php index 8a8c307..9cfd3dc 100644 --- a/Build/UnitTestsBootstrap.php +++ b/Build/phpunit/UnitTestsBootstrap.php @@ -7,6 +7,21 @@ * LICENSE file that was distributed with this source code. */ +/** + * Boilerplate for a unit test phpunit boostrap file. + * + * This file is loosely maintained within TYPO3 testing-framework, extensions + * are encouraged to not use it directly, but to copy it to an own place, + * usually in parallel to a UnitTests.xml file. + * + * This file is defined in UnitTests.xml and called by phpunit + * before instantiating the test suites. + * + * The recommended way to execute the suite is "runTests.sh". See the + * according script within TYPO3 core's Build/Scripts directory and + * adapt to extensions needs. + */ + use TYPO3\CMS\Core\Cache\Backend\NullBackend; use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; use TYPO3\CMS\Core\Configuration\ConfigurationManager; @@ -59,11 +74,11 @@ $cache = new PhpFrontend( 'core', - new NullBackend('production', []) + new NullBackend('production', []), ); $packageManager = Bootstrap::createPackageManager( UnitTestPackageManager::class, - Bootstrap::createPackageCache($cache) + Bootstrap::createPackageCache($cache), ); GeneralUtility::setSingletonInstance(PackageManager::class, $packageManager); diff --git a/Classes/Configuration/ExtConf.php b/Classes/Configuration/ExtConf.php index 4b6e7a6..ff11329 100644 --- a/Classes/Configuration/ExtConf.php +++ b/Classes/Configuration/ExtConf.php @@ -11,15 +11,10 @@ namespace StefanFroemken\PleskWidget\Configuration; -use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException; -use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException; -use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; -use TYPO3\CMS\Core\SingletonInterface; - /* * This class streamlines all settings from extension settings */ -readonly class ExtConf implements SingletonInterface +readonly class ExtConf { private string $host; @@ -33,22 +28,10 @@ private string $domain; - public function __construct(ExtensionConfiguration $extensionConfiguration) + public function __construct(array $extensionSettings) { - try { - $extConf = (array)$extensionConfiguration->get('plesk_widget'); - - $this->host = trim((string)($extConf['host'] ?? '')); - $this->port = (int)($extConf['port'] ?? 8443); - $this->username = trim((string)($extConf['username'] ?? '')); - $this->password = trim((string)($extConf['password'] ?? '')); - - $this->diskUsageType = trim((string)($extConf['diskUsageType'] ?? '%')); - $this->domain = trim((string)($extConf['domain'] ?? '')); - } catch (ExtensionConfigurationExtensionNotConfiguredException $extensionConfigurationExtensionNotConfiguredException) { - // Do nothing. The values will still be empty. We catch that as Exception just before the first API call - } catch (ExtensionConfigurationPathDoesNotExistException $extensionConfigurationPathDoesNotExistException) { - // Can never be called, as $path is not set + foreach ($extensionSettings as $property => $value) { + $this->{$property} = $value; } } diff --git a/Classes/Configuration/ExtConfFactory.php b/Classes/Configuration/ExtConfFactory.php new file mode 100644 index 0000000..c29c595 --- /dev/null +++ b/Classes/Configuration/ExtConfFactory.php @@ -0,0 +1,66 @@ + '', + 'port' => 8443, + 'username' => '', + 'password' => '', + 'diskUsageType' => '%', + 'domain' => '', + ]; + + public function __construct(private ExtensionConfiguration $extensionConfiguration) {} + + public function create(): ExtConf + { + return new ExtConf($this->getExtensionSettings()); + } + + private function getExtensionSettings(): array + { + $extensionSettings = self::DEFAULT_SETTINGS; + + try { + $extensionSettings = (array)$this->extensionConfiguration->get('plesk_widget'); + + // Remove whitespaces + $extensionSettings = array_map('trim', $extensionSettings); + + // remove empty values + $extensionSettings = array_filter($extensionSettings); + + $extensionSettings = array_merge(self::DEFAULT_SETTINGS, $extensionSettings); + + // Special handling for integer value "port" + if (MathUtility::canBeInterpretedAsInteger($extensionSettings['port'])) { + $extensionSettings['port'] = (int)$extensionSettings['port']; + } else { + $extensionSettings['port'] = self::DEFAULT_SETTINGS['port']; + } + } catch (ExtensionConfigurationExtensionNotConfiguredException) { + // Do nothing. Keep the default values + } catch (ExtensionConfigurationPathDoesNotExistException) { + // Will never be thrown, as $path is not given + } + + return $extensionSettings; + } +} diff --git a/Configuration/Icons.php b/Configuration/Icons.php index 3954951..59076a4 100644 --- a/Configuration/Icons.php +++ b/Configuration/Icons.php @@ -1,7 +1,7 @@ extensionConfigurationMock = $this->createMock(ExtensionConfiguration::class); + + $this->subject = new ExtConfFactory( + $this->extensionConfigurationMock + ); + } + + protected function tearDown(): void + { + unset( + $this->extensionConfigurationMock, + $this->subject, + ); + parent::tearDown(); + } + + #[Test] + public function getHostWillInitiallyReturnEmptyValue(): void + { + self::assertSame( + '', + $this->subject->create()->getHost() + ); + } + + #[Test] + public function getHostWillReturnHost(): void + { + $this->extensionConfigurationMock + ->expects(self::once()) + ->method('get') + ->with(self::identicalTo('plesk_widget')) + ->willReturn([ + 'host' => 'plesk.example.com', + ]); + + self::assertSame( + 'plesk.example.com', + $this->subject->create()->getHost() + ); + } + + #[Test] + public function getPortWillInitiallyReturnDefaultPort(): void + { + self::assertSame( + 8443, + $this->subject->create()->getPort() + ); + } + + public static function portDataProvider(): array + { + return [ + 'Test port with string value' => ['1234', 1234], + 'Test port with integer value' => [4321, 4321], + 'Test port with invalid value' => ['Murks', 8443], + ]; + } + + #[Test] + #[DataProvider('portDataProvider')] + public function getPortWillReturnPort(mixed $port, int $expectedPort): void + { + $this->extensionConfigurationMock + ->expects(self::once()) + ->method('get') + ->with(self::identicalTo('plesk_widget')) + ->willReturn([ + 'port' => $port, + ]); + + self::assertSame( + $expectedPort, + $this->subject->create()->getPort() + ); + } + + #[Test] + public function getUsernameWillInitiallyReturnEmptyValue(): void + { + self::assertSame( + '', + $this->subject->create()->getUsername() + ); + } + + #[Test] + public function getUsernameWillReturnUsername(): void + { + $this->extensionConfigurationMock + ->expects(self::once()) + ->method('get') + ->with(self::identicalTo('plesk_widget')) + ->willReturn([ + 'username' => 'mustermann', + ]); + + self::assertSame( + 'mustermann', + $this->subject->create()->getUsername() + ); + } + + #[Test] + public function getPasswordWillInitiallyReturnEmptyValue(): void + { + self::assertSame( + '', + $this->subject->create()->getPassword() + ); + } + + #[Test] + public function getPasswordWillReturnPassword(): void + { + $this->extensionConfigurationMock + ->expects(self::once()) + ->method('get') + ->with(self::identicalTo('plesk_widget')) + ->willReturn([ + 'password' => 'very-cryptic', + ]); + + self::assertSame( + 'very-cryptic', + $this->subject->create()->getPassword() + ); + } + + #[Test] + public function getDiskUsageTypeWillInitiallyReturnDefaultValue(): void + { + self::assertSame( + '%', + $this->subject->create()->getDiskUsageType() + ); + } + + #[Test] + public function getDiskUsageTypeWillReturnDiskUsageType(): void + { + $this->extensionConfigurationMock + ->expects(self::once()) + ->method('get') + ->with(self::identicalTo('plesk_widget')) + ->willReturn([ + 'diskUsageType' => 'MB', + ]); + + self::assertSame( + 'MB', + $this->subject->create()->getDiskUsageType() + ); + } + + #[Test] + public function getDomainWillInitiallyReturnEmptyValue(): void + { + self::assertSame( + '', + $this->subject->create()->getDomain() + ); + } + + #[Test] + public function getDomainWillReturnDomain(): void + { + $this->extensionConfigurationMock + ->expects(self::once()) + ->method('get') + ->with(self::identicalTo('plesk_widget')) + ->willReturn([ + 'domain' => '134.example.com', + ]); + + self::assertSame( + '134.example.com', + $this->subject->create()->getDomain() + ); + } + + #[Test] + public function trimValuesBeforeAssigningThem(): void + { + $this->extensionConfigurationMock + ->expects(self::once()) + ->method('get') + ->with(self::identicalTo('plesk_widget')) + ->willReturn([ + 'host' => ' plesk.example.com', + 'port' => ' 1234', + 'username' => 'mustermann ', + 'password' => ' very-cryptic', + 'diskUsageType' => 'MB', + 'domain' => ' 134.example.com ', + ]); + + $extConf = $this->subject->create(); + + self::assertSame( + [ + 'host' => 'plesk.example.com', + 'port' => 1234, + 'username' => 'mustermann', + 'password' => 'very-cryptic', + 'diskUsageType' => 'MB', + 'domain' => '134.example.com', + ], + [ + 'host' => $extConf->getHost(), + 'port' => $extConf->getPort(), + 'username' => $extConf->getUsername(), + 'password' => $extConf->getPassword(), + 'diskUsageType' => $extConf->getDiskUsageType(), + 'domain' => $extConf->getDomain(), + ] + ); + } + + #[Test] + public function emptyValuesWillBeFilledWithDefaultValues(): void + { + $this->extensionConfigurationMock + ->expects(self::once()) + ->method('get') + ->with(self::identicalTo('plesk_widget')) + ->willReturn([ + 'port' => '', + 'diskUsageType' => '', + ]); + + $extConf = $this->subject->create(); + + self::assertSame( + [ + 'host' => '', + 'port' => 8443, + 'username' => '', + 'password' => '', + 'diskUsageType' => '%', + 'domain' => '', + ], + [ + 'host' => $extConf->getHost(), + 'port' => $extConf->getPort(), + 'username' => $extConf->getUsername(), + 'password' => $extConf->getPassword(), + 'diskUsageType' => $extConf->getDiskUsageType(), + 'domain' => $extConf->getDomain(), + ] + ); + } +} diff --git a/Tests/Functional/Configuration/ExtConfTest.php b/Tests/Functional/Configuration/ExtConfTest.php index 3df7c77..8907055 100644 --- a/Tests/Functional/Configuration/ExtConfTest.php +++ b/Tests/Functional/Configuration/ExtConfTest.php @@ -7,10 +7,10 @@ * LICENSE file that was distributed with this source code. */ -namespace JWeiland\Glossary2\Tests\Functional\Configuration; +namespace StefanFroemken\PleskWidget\Tests\Functional\Configuration; +use PHPUnit\Framework\Attributes\Test; use StefanFroemken\PleskWidget\Configuration\ExtConf; -use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; /** @@ -18,11 +18,10 @@ */ class ExtConfTest extends FunctionalTestCase { - protected ExtensionConfiguration|MockObject $extensionConfigurationMock; - protected ExtConf $subject; protected array $testExtensionsToLoad = [ + 'typo3/cms-dashboard', 'stefanfroemken/plesk-widget', ]; @@ -30,27 +29,78 @@ protected function setUp(): void { parent::setUp(); - $this->extensionConfigurationMock = $this->createMock(ExtensionConfiguration::class); + $extensionSetting = [ + 'host' => 'plesk.example.com', + 'port' => 1234, + 'username' => 'mustermann', + 'password' => 'very-cryptic', + 'diskUsageType' => 'MB', + 'domain' => '134.example.com', + ]; - $this->subject = new ExtConf( - $this->extensionConfigurationMock - ); + $this->subject = new ExtConf($extensionSetting); } protected function tearDown(): void { unset( + $this->extensionConfigurationMock, $this->subject, ); parent::tearDown(); } #[Test] - public function getHostWillReturnEmptyHost(): void + public function getHostWillReturnHost(): void + { + self::assertSame( + 'plesk.example.com', + $this->subject->getHost() + ); + } + + #[Test] + public function getPortWillReturnPortAsInt(): void + { + self::assertSame( + 1234, + $this->subject->getPort() + ); + } + + #[Test] + public function getUsernameWillReturnUsername(): void + { + self::assertSame( + 'mustermann', + $this->subject->getUsername() + ); + } + + #[Test] + public function getPasswordWillReturnPassword(): void + { + self::assertSame( + 'very-cryptic', + $this->subject->getPassword() + ); + } + + #[Test] + public function getDiskUsageTypeWillReturnDiskUsageType(): void + { + self::assertSame( + 'MB', + $this->subject->getDiskUsageType() + ); + } + + #[Test] + public function getDomainWillReturnDomain(): void { self::assertSame( - '0-9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z', - $this->subject->getPossibleLetters(), + '134.example.com', + $this->subject->getDomain() ); } } diff --git a/composer.json b/composer.json index c76f04d..53456a6 100644 --- a/composer.json +++ b/composer.json @@ -23,11 +23,13 @@ "plesk/api-php-lib": "1.1.2" }, "require-dev": { + "ergebnis/composer-normalize": "^2.44", "friendsofphp/php-cs-fixer": "^3.14", - "phpunit/phpunit": "^9.6", + "phpstan/phpdoc-parser": "^1.33", + "phpunit/phpunit": "^11.2.5", "roave/security-advisories": "dev-latest", - "typo3/coding-standards": "^0.6", - "typo3/testing-framework": "^7.0.2" + "typo3/coding-standards": "^0.8", + "typo3/testing-framework": "^9.0.1" }, "replace": { "typo3-ter/plesk-widget": "self.version" @@ -38,6 +40,11 @@ "PleskX\\": "Resources/Private/Php/plesk/api-php-lib/src" } }, + "autoload-dev": { + "psr-4": { + "StefanFroemken\\PleskWidget\\Tests\\": "Tests" + } + }, "config": { "sort-packages": true, "vendor-dir": ".Build/vendor", From 555aa3a5d462fdfa3e8e94fc26063a9b4b418e70 Mon Sep 17 00:00:00 2001 From: Stefan Froemken Date: Sun, 12 Jan 2025 02:52:49 +0100 Subject: [PATCH 2/3] Start func tests with GitHub actions --- .github/workflows/ci.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b821c6..e4c81eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,14 +9,17 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout + - name: 'Checkout' uses: actions/checkout@v4 - - name: Composer + - name: 'Composer' run: Build/Scripts/runTests.sh -p 8.1 -s composerUpdate - - name: Lint PHP + - name: 'Lint PHP' run: Build/Scripts/runTests.sh -p 8.1 -s lint - - name: Validate code against CGL + - name: 'Validate code against CGL' run: Build/Scripts/runTests.sh -p 8.1 -s cgl -n + + - name: 'Execute functional tests' + run: Build/Scripts/runTests.sh -p 8.1 -d sqlite -s functional From 184303da1cf0dd30da0de91183032993fbb5019b Mon Sep 17 00:00:00 2001 From: Stefan Froemken Date: Sun, 12 Jan 2025 02:54:53 +0100 Subject: [PATCH 3/3] Test against PHP 8.2 --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4c81eb..fdf1585 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,13 +13,13 @@ jobs: uses: actions/checkout@v4 - name: 'Composer' - run: Build/Scripts/runTests.sh -p 8.1 -s composerUpdate + run: Build/Scripts/runTests.sh -p 8.2 -s composerUpdate - name: 'Lint PHP' - run: Build/Scripts/runTests.sh -p 8.1 -s lint + run: Build/Scripts/runTests.sh -p 8.2 -s lint - name: 'Validate code against CGL' - run: Build/Scripts/runTests.sh -p 8.1 -s cgl -n + run: Build/Scripts/runTests.sh -p 8.2 -s cgl -n - name: 'Execute functional tests' - run: Build/Scripts/runTests.sh -p 8.1 -d sqlite -s functional + run: Build/Scripts/runTests.sh -p 8.2 -d sqlite -s functional