From d40ae8e45fd29287dabdf0c34cf7bb1e7f0b7eac Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 2 Aug 2026 10:50:40 -0400 Subject: [PATCH 1/4] test(Setup): chore - tidy constructor formatting (pure refactor) Signed-off-by: Josh --- tests/lib/SetupTest.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/lib/SetupTest.php b/tests/lib/SetupTest.php index 3def0ff69e1b3..2d0fac142a404 100644 --- a/tests/lib/SetupTest.php +++ b/tests/lib/SetupTest.php @@ -37,18 +37,30 @@ protected function setUp(): void { $this->config = $this->createMock(SystemConfig::class); $this->iniWrapper = $this->createMock(IniGetWrapper::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->random = $this->createMock(ISecureRandom::class); + $this->l10n = $this->createMock(IL10N::class); $this->l10nFactory = $this->createMock(IL10NFactory::class); $this->l10nFactory->method('get') ->willReturn($this->l10n); + $this->defaults = $this->createMock(Defaults::class); - $this->logger = $this->createMock(LoggerInterface::class); - $this->random = $this->createMock(ISecureRandom::class); $this->installer = $this->createMock(Installer::class); $this->eventDispatcher = $this->createMock(IEventDispatcher::class); + $this->setupClass = $this->getMockBuilder(Setup::class) ->onlyMethods(['class_exists', 'is_callable', 'getAvailableDbDriversForPdo']) - ->setConstructorArgs([$this->config, $this->iniWrapper, $this->l10nFactory, $this->defaults, $this->logger, $this->random, $this->installer, $this->eventDispatcher]) + ->setConstructorArgs([ + $this->config, + $this->iniWrapper, + $this->l10nFactory, + $this->defaults, + $this->logger, + $this->random, + $this->installer, + $this->eventDispatcher, + ]) ->getMock(); } From a5a796b3c64c808c441490c821cc4b37c15ad53a Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 2 Aug 2026 11:09:52 -0400 Subject: [PATCH 2/4] test(Setup): verify important contract gaps in existing tests - `getSupportedDatabases(true)` bypasses system configuration and evaluates all known DB types. - unknown configured values are silently ignored, while later valid values are retained. - PDO versus function availability probes are dispatched the expected number of times and with the expected callable. - non-CLI `findWebRoot()` must not access `overwrite.cli.url`; it returns `\OC::$WEBROOT`, defaulting to '/' and also handling sub-directory installs. Signed-off-by: Josh --- tests/lib/SetupTest.php | 108 +++++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 17 deletions(-) diff --git a/tests/lib/SetupTest.php b/tests/lib/SetupTest.php index 2d0fac142a404..eab20fd826d8a 100644 --- a/tests/lib/SetupTest.php +++ b/tests/lib/SetupTest.php @@ -68,21 +68,19 @@ public function testGetSupportedDatabasesWithOneWorking(): void { $this->config ->expects($this->once()) ->method('getValue') - ->willReturn( - ['sqlite', 'mysql', 'oci'] - ); + ->willReturn(['sqlite', 'mysql', 'oci']); $this->setupClass ->expects($this->once()) ->method('is_callable') + ->with('oci_connect') ->willReturn(false); $this->setupClass - ->expects($this->any()) + ->expects($this->exactly(2)) ->method('getAvailableDbDriversForPdo') ->willReturn(['sqlite']); + $result = $this->setupClass->getSupportedDatabases(); - $expectedResult = [ - 'sqlite' => 'SQLite' - ]; + $expectedResult = ['sqlite' => 'SQLite']; $this->assertSame($expectedResult, $result); } @@ -91,17 +89,17 @@ public function testGetSupportedDatabasesWithNoWorking(): void { $this->config ->expects($this->once()) ->method('getValue') - ->willReturn( - ['sqlite', 'mysql', 'oci', 'pgsql'] - ); + ->willReturn(['sqlite', 'mysql', 'oci', 'pgsql']); $this->setupClass - ->expects($this->any()) + ->expects($this->once()) ->method('is_callable') + ->with('oci_connect') ->willReturn(false); $this->setupClass - ->expects($this->any()) + ->expects($this->exactly(3)) ->method('getAvailableDbDriversForPdo') ->willReturn([]); + $result = $this->setupClass->getSupportedDatabases(); $this->assertSame([], $result); @@ -111,17 +109,17 @@ public function testGetSupportedDatabasesWithAllWorking(): void { $this->config ->expects($this->once()) ->method('getValue') - ->willReturn( - ['sqlite', 'mysql', 'pgsql', 'oci'] - ); + ->willReturn(['sqlite', 'mysql', 'pgsql', 'oci']); $this->setupClass - ->expects($this->any()) + ->expects($this->once()) ->method('is_callable') + ->with('oci_connect') ->willReturn(true); $this->setupClass - ->expects($this->any()) + ->expects($this->exactly(3)) ->method('getAvailableDbDriversForPdo') ->willReturn(['sqlite', 'mysql', 'pgsql']); + $result = $this->setupClass->getSupportedDatabases(); $expectedResult = [ 'sqlite' => 'SQLite', @@ -129,9 +127,53 @@ public function testGetSupportedDatabasesWithAllWorking(): void { 'pgsql' => 'PostgreSQL', 'oci' => 'Oracle' ]; + $this->assertSame($expectedResult, $result); } + public function testGetSupportedDatabasesAllowsAllKnownDatabasesWithoutConfiguration(): void { + $this->config + ->expects($this->never()) + ->method('getValue'); + $this->setupClass + ->expects($this->once()) + ->method('is_callable') + ->with('oci_connect') + ->willReturn(true); + $this->setupClass + ->expects($this->exactly(3)) + ->method('getAvailableDbDriversForPdo') + ->willReturn(['sqlite', 'mysql', 'pgsql']); + + $result = $this->setupClass->getSupportedDatabases(true); + + $this->assertSame([ + 'sqlite' => 'SQLite', + 'mysql' => 'MySQL/MariaDB', + 'pgsql' => 'PostgreSQL', + 'oci' => 'Oracle', + ], $result); + } + + public function testGetSupportedDatabasesIgnoresUnknownConfiguredDatabases(): void { + $this->config + ->expects($this->once()) + ->method('getValue') + ->with('supportedDatabases', ['sqlite', 'mysql', 'pgsql']) + ->willReturn(['unknown', 'sqlite']); + $this->setupClass + ->expects($this->never()) + ->method('is_callable'); + $this->setupClass + ->expects($this->once()) + ->method('getAvailableDbDriversForPdo') + ->willReturn(['sqlite']); + + $result = $this->setupClass->getSupportedDatabases(); + + $this->assertSame(['sqlite' => 'SQLite'], $result); + } + public function testGetSupportedDatabaseException(): void { $this->expectException(\Exception::class); $this->expectExceptionMessage('Supported databases are not properly configured.'); @@ -140,6 +182,7 @@ public function testGetSupportedDatabaseException(): void { ->expects($this->once()) ->method('getValue') ->willReturn('NotAnArray'); + $this->setupClass->getSupportedDatabases(); } @@ -185,4 +228,35 @@ public static function findWebRootProvider(): array { 'empty' => ['', false], ]; } + + /** + * @param string $configuredWebRoot + */ + #[\PHPUnit\Framework\Attributes\DataProvider('findWebRootWebProvider')] + public function testFindWebRootWeb(string $configuredWebRoot, string $expected): void { + $cliState = \OC::$CLI; + $webRootState = \OC::$WEBROOT; + $this->config + ->expects($this->never()) + ->method('getValue'); + + try { + \OC::$CLI = false; + \OC::$WEBROOT = $configuredWebRoot; + + $webRoot = self::invokePrivate($this->setupClass, 'findWebRoot', [$this->config]); + } finally { + \OC::$CLI = $cliState; + \OC::$WEBROOT = $webRootState; + } + + $this->assertSame($expected, $webRoot); + } + + public static function findWebRootWebProvider(): array { + return [ + 'configured web root' => ['/nextcloud', '/nextcloud'], + 'empty web root defaults to slash' => ['', '/'], + ]; + } } From 6d42ad0d27c829ca094febc6a5a0d67535dc1e18 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 2 Aug 2026 16:32:43 -0400 Subject: [PATCH 3/4] test(setup): Add a test for the unavailable-PDO branch The existing test fixture mocks `getAvailableDbDriversForPdo()` itself. This adds a separate partial mock that mocks only `class_exists()`, which is testable after fixing Setup to use the $this->class_exists() wrapper it already has in it... Signed-off-by: Josh --- tests/lib/SetupTest.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/lib/SetupTest.php b/tests/lib/SetupTest.php index eab20fd826d8a..92e57d75856ec 100644 --- a/tests/lib/SetupTest.php +++ b/tests/lib/SetupTest.php @@ -186,6 +186,33 @@ public function testGetSupportedDatabaseException(): void { $this->setupClass->getSupportedDatabases(); } + public function testGetAvailableDbDriversForPdoWhenPdoIsUnavailable(): void { + $setup = $this->getMockBuilder(Setup::class) + ->onlyMethods(['class_exists']) + ->setConstructorArgs([ + $this->config, + $this->iniWrapper, + $this->l10nFactory, + $this->defaults, + $this->logger, + $this->random, + $this->installer, + $this->eventDispatcher, + ]) + ->getMock(); + + $setup + ->expects($this->once()) + ->method('class_exists') + ->with(\PDO::class) + ->willReturn(false); + + $this->assertSame( + [], + self::invokePrivate($setup, 'getAvailableDbDriversForPdo'), + ); + } + /** * @param $url * @param $expected From 52f12e0036a82e557f1c73b53227475090fa2a26 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 2 Aug 2026 16:35:24 -0400 Subject: [PATCH 4/4] fix(setup): use the existing wrapper to facilitate testing The wrapper was already there; just wasn't being used. Signed-off-by: Josh --- lib/private/Setup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 7f598f7abc843..29d9a270c4fe1 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -101,7 +101,7 @@ protected function is_callable(string $name): bool { * Wrapper around \PDO::getAvailableDrivers */ protected function getAvailableDbDriversForPdo(): array { - if (class_exists(\PDO::class)) { + if ($this->class_exists(\PDO::class)) { return \PDO::getAvailableDrivers(); } return [];