From 65c5961f671ca47acc9ab0a6a971437d45a1744b Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Wed, 10 Jun 2026 10:41:58 +0200 Subject: [PATCH 01/30] chore(deprecated): remove imagedestroy as it's noop since PHP 8.0 Function imagedestroy() is deprecated since 8.5, as it has no effect since PHP 8.0 Signed-off-by: Benjamin Gaussorgues --- apps/theming/lib/ImageManager.php | 5 ----- build/integration/features/bootstrap/Avatar.php | 4 ---- lib/private/Image.php | 4 ---- 3 files changed, 13 deletions(-) diff --git a/apps/theming/lib/ImageManager.php b/apps/theming/lib/ImageManager.php index 3b99402d57738..ad1c1055806fc 100644 --- a/apps/theming/lib/ImageManager.php +++ b/apps/theming/lib/ImageManager.php @@ -265,12 +265,7 @@ public function updateImage(string $key, string $tmpFile): string { } } $tmpFile = $newTmpFile; - imagedestroy($outputImage); } catch (\Exception $e) { - if (isset($outputImage) && is_resource($outputImage) || $outputImage instanceof \GdImage) { - imagedestroy($outputImage); - } - $this->logger->debug($e->getMessage()); } } diff --git a/build/integration/features/bootstrap/Avatar.php b/build/integration/features/bootstrap/Avatar.php index 59d9ed920a110..e3ebde513a7d0 100644 --- a/build/integration/features/bootstrap/Avatar.php +++ b/build/integration/features/bootstrap/Avatar.php @@ -183,15 +183,11 @@ private function getColorFromLastAvatar() { // on solid color images the resizing can cause some small // artifacts that slightly modify the color of certain pixels. if (!$this->isSameColor($firstPixelColor, $currentPixelColor)) { - imagedestroy($image); - return null; } } } - imagedestroy($image); - return $firstPixelColor; } diff --git a/lib/private/Image.php b/lib/private/Image.php index 2ab199e69c62f..d1a2f164ed52e 100644 --- a/lib/private/Image.php +++ b/lib/private/Image.php @@ -508,7 +508,6 @@ public function fixOrientation(): bool { if ($res) { if (imagealphablending($res, true)) { if (imagesavealpha($res, true)) { - imagedestroy($this->resource); $this->resource = $res; return true; } else { @@ -926,7 +925,6 @@ public function preciseResizeNew(int $width, int $height): \GdImage|false { $res = imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); if ($res === false) { $this->logger->debug(__METHOD__ . '(): Error re-sampling process image', ['app' => 'core']); - imagedestroy($process); return false; } return $process; @@ -988,7 +986,6 @@ public function centerCrop(int $size = 0): bool { $this->logger->debug('Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, ['app' => 'core']); return false; } - imagedestroy($this->resource); $this->resource = $process; return true; } @@ -1009,7 +1006,6 @@ public function crop(int $x, int $y, int $w, int $h): bool { return false; } $result = $this->cropNew($x, $y, $w, $h); - imagedestroy($this->resource); $this->resource = $result; return $this->valid(); } From 8d57774269f6d35e94d2d46dec0e7609913448b3 Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Wed, 10 Jun 2026 10:50:40 +0200 Subject: [PATCH 02/30] chore(deprecated): remove Reflection*::setAccessible as it's noop since PHP 8.1 Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1 Signed-off-by: Benjamin Gaussorgues --- core/Command/Background/Job.php | 1 - core/Command/Background/JobBase.php | 1 - .../lib/Files/Mount/RootMountProviderTest.php | 1 - tests/lib/Files/Stream/EncryptionTest.php | 18 ------------------ tests/lib/Template/ResourceLocatorTest.php | 1 - 5 files changed, 22 deletions(-) diff --git a/core/Command/Background/Job.php b/core/Command/Background/Job.php index 4ab7c930b790c..f8e3df9972afa 100644 --- a/core/Command/Background/Job.php +++ b/core/Command/Background/Job.php @@ -124,7 +124,6 @@ protected function printJobInfo(string $jobId, IJob $job, OutputInterface $outpu if ($isTimedJob) { $reflection = new \ReflectionClass($job); $intervalProperty = $reflection->getProperty('interval'); - $intervalProperty->setAccessible(true); $interval = $intervalProperty->getValue($job); $nextRun = new \DateTime(); diff --git a/core/Command/Background/JobBase.php b/core/Command/Background/JobBase.php index 558ccccf29dc8..484e2556d14d3 100644 --- a/core/Command/Background/JobBase.php +++ b/core/Command/Background/JobBase.php @@ -65,7 +65,6 @@ protected function printJobInfo(string $jobId, IJob $job, OutputInterface $outpu if ($isTimedJob) { $reflection = new \ReflectionClass($job); $intervalProperty = $reflection->getProperty('interval'); - $intervalProperty->setAccessible(true); $interval = $intervalProperty->getValue($job); $nextRun = new \DateTime(); diff --git a/tests/lib/Files/Mount/RootMountProviderTest.php b/tests/lib/Files/Mount/RootMountProviderTest.php index 8a61dc81034f5..22491285fb008 100644 --- a/tests/lib/Files/Mount/RootMountProviderTest.php +++ b/tests/lib/Files/Mount/RootMountProviderTest.php @@ -85,7 +85,6 @@ public function testObjectStore(): void { $class = new \ReflectionClass($storage); $prop = $class->getProperty('objectStore'); - $prop->setAccessible(true); /** @var S3 $objectStore */ $objectStore = $prop->getValue($storage); $this->assertEquals('nextcloud', $objectStore->getBucket()); diff --git a/tests/lib/Files/Stream/EncryptionTest.php b/tests/lib/Files/Stream/EncryptionTest.php index 13f14bbc3248b..b397019df930e 100644 --- a/tests/lib/Files/Stream/EncryptionTest.php +++ b/tests/lib/Files/Stream/EncryptionTest.php @@ -149,29 +149,17 @@ public function testStreamOpen( // set internal properties of the stream wrapper $stream = new \ReflectionClass(Encryption::class); $encryptionModule = $stream->getProperty('encryptionModule'); - $encryptionModule->setAccessible(true); $encryptionModule->setValue($streamWrapper, $encryptionModuleMock); - $encryptionModule->setAccessible(false); $storage = $stream->getProperty('storage'); - $storage->setAccessible(true); $storage->setValue($streamWrapper, $storageMock); - $storage->setAccessible(false); $file = $stream->getProperty('file'); - $file->setAccessible(true); $file->setValue($streamWrapper, $fileMock); - $file->setAccessible(false); $util = $stream->getProperty('util'); - $util->setAccessible(true); $util->setValue($streamWrapper, $utilMock); - $util->setAccessible(false); $fullPathP = $stream->getProperty('fullPath'); - $fullPathP->setAccessible(true); $fullPathP->setValue($streamWrapper, $fullPath); - $fullPathP->setAccessible(false); $header = $stream->getProperty('header'); - $header->setAccessible(true); $header->setValue($streamWrapper, []); - $header->setAccessible(false); $this->invokePrivate($streamWrapper, 'signed', [true]); $this->invokePrivate($streamWrapper, 'internalPath', [$fullPath]); $this->invokePrivate($streamWrapper, 'uid', ['test']); @@ -182,19 +170,13 @@ public function testStreamOpen( // check internal properties $size = $stream->getProperty('size'); - $size->setAccessible(true); $this->assertSame($expectedSize, $size->getValue($streamWrapper)); - $size->setAccessible(false); $unencryptedSize = $stream->getProperty('unencryptedSize'); - $unencryptedSize->setAccessible(true); $this->assertSame($expectedUnencryptedSize, $unencryptedSize->getValue($streamWrapper)); - $unencryptedSize->setAccessible(false); $readOnly = $stream->getProperty('readOnly'); - $readOnly->setAccessible(true); $this->assertSame($expectedReadOnly, $readOnly->getValue($streamWrapper)); - $readOnly->setAccessible(false); } public static function dataProviderStreamOpen(): array { diff --git a/tests/lib/Template/ResourceLocatorTest.php b/tests/lib/Template/ResourceLocatorTest.php index ab5c780ec61fd..3244250f2fd5a 100644 --- a/tests/lib/Template/ResourceLocatorTest.php +++ b/tests/lib/Template/ResourceLocatorTest.php @@ -73,7 +73,6 @@ public function testFindNotFound(): void { public function testAppendIfExist(): void { $locator = $this->getResourceLocator('theme'); $method = new \ReflectionMethod($locator, 'appendIfExist'); - $method->setAccessible(true); $method->invoke($locator, __DIR__, basename(__FILE__), 'webroot'); $resource1 = [__DIR__, 'webroot', basename(__FILE__)]; From 816f90a634c557102c933379044b8c4ce3159bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 10:52:24 +0200 Subject: [PATCH 03/30] chore(tests): Avoid deprecation in PublicKeyTokenProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also cleaned up the test a bit. Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- .../Token/PublicKeyTokenProvider.php | 16 ++++---- .../Token/PublicKeyTokenProviderTest.php | 40 ++++++++----------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index a079c5e38f1ab..9f0d4ccbe9662 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -531,16 +531,18 @@ public function updatePasswords(string $uid, string $password) { $hashNeedsUpdate = []; foreach ($tokens as $t) { - if (!isset($hashNeedsUpdate[$t->getPasswordHash()])) { - if ($t->getPasswordHash() === null) { - $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = true; - } elseif (!$this->hasher->verify(sha1($password) . $password, $t->getPasswordHash())) { - $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = true; + $passwordHash = $t->getPasswordHash(); + if ($passwordHash === null) { + $hashNeedsUpdate[''] = true; + $needsUpdating = true; + } elseif (!isset($hashNeedsUpdate[$passwordHash])) { + if (!$this->hasher->verify(sha1($password) . $password, $passwordHash)) { + $hashNeedsUpdate[$passwordHash] = true; } else { - $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = false; + $hashNeedsUpdate[$passwordHash] = false; } + $needsUpdating = $hashNeedsUpdate[$passwordHash] ?? true; } - $needsUpdating = $hashNeedsUpdate[$t->getPasswordHash() ?: ''] ?? true; if ($needsUpdating) { if ($newPasswordHash === null) { diff --git a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php index 1c4bfba202d29..f08a7be279d01 100644 --- a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php +++ b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php @@ -30,36 +30,25 @@ use Test\TestCase; class PublicKeyTokenProviderTest extends TestCase { - /** @var PublicKeyTokenProvider|\PHPUnit\Framework\MockObject\MockObject */ - private $tokenProvider; - /** @var PublicKeyTokenMapper|\PHPUnit\Framework\MockObject\MockObject */ - private $mapper; - /** @var IHasher|\PHPUnit\Framework\MockObject\MockObject */ - private $hasher; - /** @var ICrypto */ - private $crypto; - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ - private $config; - /** @var IDBConnection|MockObject */ - private IDBConnection $db; - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $logger; - /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; - /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $cacheFactory; - /** @var int */ - private $time; - /** @var IEventDispatcher */ - private $eventDispatcher; + private PublicKeyTokenProvider $tokenProvider; + + private PublicKeyTokenMapper&MockObject $mapper; + private IConfig&MockObject $config; + private IDBConnection&MockObject $db; + private LoggerInterface&MockObject $logger; + private ITimeFactory&MockObject $timeFactory; + private ICacheFactory&MockObject $cacheFactory; + + private int $time; + private IHasher $hasher; + private ICrypto $crypto; + private IEventDispatcher $eventDispatcher; #[\Override] protected function setUp(): void { parent::setUp(); $this->mapper = $this->createMock(PublicKeyTokenMapper::class); - $this->hasher = Server::get(IHasher::class); - $this->crypto = Server::get(ICrypto::class); $this->config = $this->createMock(IConfig::class); $this->config->method('getSystemValue') ->willReturnMap([ @@ -76,6 +65,9 @@ protected function setUp(): void { $this->timeFactory->method('getTime') ->willReturn($this->time); $this->cacheFactory = $this->createMock(ICacheFactory::class); + + $this->hasher = Server::get(IHasher::class); + $this->crypto = Server::get(ICrypto::class); $this->eventDispatcher = Server::get(IEventDispatcher::class); $this->tokenProvider = new PublicKeyTokenProvider( From 5fc9f76a818cdd8aa70d0b092359162ab1ee5118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 11:03:40 +0200 Subject: [PATCH 04/30] chore(tests): Fix User UID mocking in TwoFactorAuth/ManagerTest.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- .../TwoFactorAuth/ManagerTest.php | 91 ++++++------------- 1 file changed, 28 insertions(+), 63 deletions(-) diff --git a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php index e919d0991322c..9c751d487c130 100644 --- a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php +++ b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php @@ -31,53 +31,30 @@ use function reset; class ManagerTest extends TestCase { - /** @var IUser|MockObject */ - private $user; - - /** @var ProviderLoader|MockObject */ - private $providerLoader; - - /** @var IRegistry|MockObject */ - private $providerRegistry; - - /** @var MandatoryTwoFactor|MockObject */ - private $mandatoryTwoFactor; - - /** @var ISession|MockObject */ - private $session; - - /** @var Manager */ - private $manager; - - /** @var IConfig|MockObject */ - private $config; - - /** @var IManager|MockObject */ - private $activityManager; - - /** @var LoggerInterface|MockObject */ - private $logger; - - /** @var IProvider|MockObject */ - private $fakeProvider; - - /** @var IProvider|MockObject */ - private $backupProvider; - - /** @var TokenProvider|MockObject */ - private $tokenProvider; - - /** @var ITimeFactory|MockObject */ - private $timeFactory; - - /** @var IEventDispatcher|MockObject */ - private $dispatcher; + private IUser&MockObject $user; + private ProviderLoader&MockObject $providerLoader; + private IRegistry&MockObject $providerRegistry; + private MandatoryTwoFactor&MockObject $mandatoryTwoFactor; + private ISession&MockObject $session; + private IConfig&MockObject $config; + private IManager&MockObject $activityManager; + private LoggerInterface&MockObject $logger; + private IProvider&MockObject $fakeProvider; + private IProvider&MockObject $backupProvider; + private TokenProvider&MockObject $tokenProvider; + private ITimeFactory&MockObject $timeFactory; + private IEventDispatcher&MockObject $dispatcher; + + private Manager $manager; #[\Override] protected function setUp(): void { parent::setUp(); $this->user = $this->createMock(IUser::class); + $this->user->expects($this->any()) + ->method('getUID') + ->willReturn('user-uid'); $this->providerLoader = $this->createMock(ProviderLoader::class); $this->providerRegistry = $this->createMock(IRegistry::class); $this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class); @@ -371,15 +348,12 @@ public function testVerifyChallenge(): void { $this->session->expects($this->once()) ->method('set') - ->with(Manager::SESSION_UID_DONE, 'jos'); + ->with(Manager::SESSION_UID_DONE, $this->user->getUID()); $this->session->method('getId') ->willReturn('mysessionid'); $this->activityManager->expects($this->once()) ->method('generateEvent') ->willReturn($event); - $this->user->expects($this->any()) - ->method('getUID') - ->willReturn('jos'); $event->expects($this->once()) ->method('setApp') ->with($this->equalTo('core')) @@ -390,11 +364,11 @@ public function testVerifyChallenge(): void { ->willReturnSelf(); $event->expects($this->once()) ->method('setAuthor') - ->with($this->equalTo('jos')) + ->with($this->equalTo($this->user->getUID())) ->willReturnSelf(); $event->expects($this->once()) ->method('setAffectedUser') - ->with($this->equalTo('jos')) + ->with($this->equalTo($this->user->getUID())) ->willReturnSelf(); $this->fakeProvider ->method('getDisplayName') @@ -413,7 +387,7 @@ public function testVerifyChallenge(): void { ->willReturn(42); $this->config->expects($this->once()) ->method('deleteUserValue') - ->with('jos', 'login_token_2fa', '42'); + ->with($this->user->getUID(), 'login_token_2fa', '42'); $result = $this->manager->verifyChallenge('email', $this->user, $challenge); @@ -447,9 +421,6 @@ public function testVerifyInvalidChallenge(): void { $this->activityManager->expects($this->once()) ->method('generateEvent') ->willReturn($event); - $this->user->expects($this->any()) - ->method('getUID') - ->willReturn('jos'); $event->expects($this->once()) ->method('setApp') ->with($this->equalTo('core')) @@ -460,11 +431,11 @@ public function testVerifyInvalidChallenge(): void { ->willReturnSelf(); $event->expects($this->once()) ->method('setAuthor') - ->with($this->equalTo('jos')) + ->with($this->equalTo($this->user->getUID())) ->willReturnSelf(); $event->expects($this->once()) ->method('setAffectedUser') - ->with($this->equalTo('jos')) + ->with($this->equalTo($this->user->getUID())) ->willReturnSelf(); $this->fakeProvider ->method('getDisplayName') @@ -559,11 +530,8 @@ public function testNeedsSecondFactorWithNoProviderAvailableAnymore(): void { } public function testPrepareTwoFactorLogin(): void { - $this->user->method('getUID') - ->willReturn('ferdinand'); - $calls = [ - ['two_factor_auth_uid', 'ferdinand'], + ['two_factor_auth_uid', $this->user->getUID()], ['two_factor_remember_login', true], ]; $this->session->expects($this->exactly(2)) @@ -586,17 +554,14 @@ public function testPrepareTwoFactorLogin(): void { ->willReturn(1337); $this->config->method('setUserValue') - ->with('ferdinand', 'login_token_2fa', '42', '1337'); + ->with($this->user->getUID(), 'login_token_2fa', '42', '1337'); $this->manager->prepareTwoFactorLogin($this->user, true); } public function testPrepareTwoFactorLoginDontRemember(): void { - $this->user->method('getUID') - ->willReturn('ferdinand'); - $calls = [ - ['two_factor_auth_uid', 'ferdinand'], + ['two_factor_auth_uid', $this->user->getUID()], ['two_factor_remember_login', false], ]; $this->session->expects($this->exactly(2)) @@ -619,7 +584,7 @@ public function testPrepareTwoFactorLoginDontRemember(): void { ->willReturn(1337); $this->config->method('setUserValue') - ->with('ferdinand', 'login_token_2fa', '42', '1337'); + ->with($this->user->getUID(), 'login_token_2fa', '42', '1337'); $this->manager->prepareTwoFactorLogin($this->user, false); } From 7ce4f8b163f976f66e57efa4cce316329ad6da85 Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Wed, 10 Jun 2026 11:03:55 +0200 Subject: [PATCH 05/30] chore(L10N): add strict types in L10N::localeExists Signed-off-by: Benjamin Gaussorgues --- lib/private/L10N/Factory.php | 8 ++------ lib/public/L10N/IFactory.php | 5 ++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php index dbf18a05c36a0..accdf47efe61c 100644 --- a/lib/private/L10N/Factory.php +++ b/lib/private/L10N/Factory.php @@ -287,7 +287,7 @@ public function findLocale($lang = null) { // Default : use system default locale $defaultLocale = $this->config->getSystemValue('default_locale', false); - if ($defaultLocale !== false && $this->localeExists($defaultLocale)) { + if (is_string($defaultLocale) && $this->localeExists($defaultLocale)) { return $defaultLocale; } @@ -463,12 +463,8 @@ public function getUserLanguage(?IUser $user = null): string { return $this->cleanLanguage($this->request->getParam('forceLanguage')) ?? $this->config->getSystemValueString('default_language', 'en'); } - /** - * @param string $locale - * @return bool - */ #[\Override] - public function localeExists($locale) { + public function localeExists(string $locale): bool { if ($locale === 'en') { //english is always available return true; } diff --git a/lib/public/L10N/IFactory.php b/lib/public/L10N/IFactory.php index 4217ff5e4cbff..b7ad1327ccf59 100644 --- a/lib/public/L10N/IFactory.php +++ b/lib/public/L10N/IFactory.php @@ -98,11 +98,10 @@ public function findAvailableLocales(); public function languageExists($app, $lang); /** - * @param string $locale - * @return bool * @since 14.0.0 + * @since 35.0.0 Use typed parameter and return */ - public function localeExists($locale); + public function localeExists(string $locale): bool; /** * Return the language direction From f46b68791bad2e530d40d98c5c48788ca3a42d3e Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Wed, 10 Jun 2026 11:17:21 +0200 Subject: [PATCH 06/30] chore: add strict types in IAddressBook::getKey Signed-off-by: Benjamin Gaussorgues --- apps/dav/lib/CardDAV/AddressBookImpl.php | 3 ++- lib/public/IAddressBook.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php index 0668d8fdb7367..2413b2c9cc34f 100644 --- a/apps/dav/lib/CardDAV/AddressBookImpl.php +++ b/apps/dav/lib/CardDAV/AddressBookImpl.php @@ -41,9 +41,10 @@ public function __construct( /** * @return string defining the technical unique key * @since 5.0.0 + * @since 35.0.0 Typed return type */ #[\Override] - public function getKey() { + public function getKey(): string { return (string)$this->addressBookInfo['id']; } diff --git a/lib/public/IAddressBook.php b/lib/public/IAddressBook.php index 5a5cc487cee42..cd7a13e27d4d8 100644 --- a/lib/public/IAddressBook.php +++ b/lib/public/IAddressBook.php @@ -18,8 +18,9 @@ interface IAddressBook { /** * @return string defining the technical unique key * @since 5.0.0 + * @since 35.0.0 Typed return type */ - public function getKey(); + public function getKey(): string; /** * @return string defining the unique uri From fd8db531058642b0d078ce372aa759fede9f068f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 11:14:00 +0200 Subject: [PATCH 07/30] chore: List skipped tests in PHPUnit output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 03881a15fc071..7b2bb8d43ff26 100644 --- a/composer.json +++ b/composer.json @@ -84,7 +84,7 @@ ], "test": [ "Composer\\Config::disableProcessTimeout", - "phpunit --fail-on-warning --fail-on-risky --display-warnings --display-deprecations --display-phpunit-deprecations --colors=always --configuration tests/phpunit-autotest.xml" + "phpunit --fail-on-warning --fail-on-risky --display-warnings --display-deprecations --display-phpunit-deprecations --display-skipped --colors=always --configuration tests/phpunit-autotest.xml" ], "test:db": "@composer run test -- --group DB --group SLOWDB", "test:files_external": "phpunit --fail-on-warning --fail-on-risky --display-warnings --display-deprecations --display-phpunit-deprecations --colors=always --configuration tests/phpunit-autotest-external.xml", From 85e68d5bfa72fb7e82a90c49d7fa2a7037678985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 11:38:28 +0200 Subject: [PATCH 08/30] fix(cache): Improve typing of ICache and CappedMemoryCache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For CappedMemoryCache we allow string|int to be consistent with PHP array keys, for ICache we strictly apply the previously stated string type for keys. Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- lib/public/Cache/CappedMemoryCache.php | 23 +++++++++-------------- lib/public/ICache.php | 17 +++++++---------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/lib/public/Cache/CappedMemoryCache.php b/lib/public/Cache/CappedMemoryCache.php index a071d7a9cb93d..40a4d26cbbe56 100644 --- a/lib/public/Cache/CappedMemoryCache.php +++ b/lib/public/Cache/CappedMemoryCache.php @@ -1,5 +1,7 @@ + * @template-implements \ArrayAccess */ class CappedMemoryCache implements ICache, \ArrayAccess { private int $capacity; @@ -37,7 +39,7 @@ public function __construct(int $capacity = 512) { * @since 25.0.0 */ #[\Override] - public function hasKey($key): bool { + public function hasKey(string|int $key): bool { return isset($this->cache[$key]); } @@ -46,25 +48,18 @@ public function hasKey($key): bool { * @since 25.0.0 */ #[\Override] - public function get($key) { + public function get(string|int $key) { return $this->cache[$key] ?? null; } /** * @inheritdoc - * @param string $key * @param T $value - * @param int $ttl * @since 25.0.0 - * @return bool */ #[\Override] - public function set($key, $value, $ttl = 0): bool { - if (is_null($key)) { - $this->cache[] = $value; - } else { - $this->cache[$key] = $value; - } + public function set(string|int $key, $value, int $ttl = 0): bool { + $this->cache[$key] = $value; $this->garbageCollect(); return true; } @@ -73,7 +68,7 @@ public function set($key, $value, $ttl = 0): bool { * @since 25.0.0 */ #[\Override] - public function remove($key): bool { + public function remove(string|int $key): bool { unset($this->cache[$key]); return true; } @@ -83,7 +78,7 @@ public function remove($key): bool { * @since 25.0.0 */ #[\Override] - public function clear($prefix = ''): bool { + public function clear(string $prefix = ''): bool { $this->cache = []; return true; } diff --git a/lib/public/ICache.php b/lib/public/ICache.php index 5e755d81e1448..8fd9fea7e60b6 100644 --- a/lib/public/ICache.php +++ b/lib/public/ICache.php @@ -1,5 +1,7 @@ Date: Wed, 10 Jun 2026 11:49:42 +0200 Subject: [PATCH 09/30] chore: Exclude skipped groups from nodb test suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- .github/workflows/phpunit-nodb.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpunit-nodb.yml b/.github/workflows/phpunit-nodb.yml index 3c7af086a4bd1..f0ee71de2c987 100644 --- a/.github/workflows/phpunit-nodb.yml +++ b/.github/workflows/phpunit-nodb.yml @@ -106,7 +106,7 @@ jobs: php -f tests/enable_all.php - name: PHPUnit nodb testsuite - run: composer run test -- --exclude-group DB --exclude-group SLOWDB --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.nodb.xml' || '' }} + run: composer run test -- --exclude-group DB --exclude-group SLOWDB --exclude-group Memcached --exclude-group PRIMARY-swift --exclude-group PRIMARY-s3 --exclude-group PRIMARY-azure --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.nodb.xml' || '' }} - name: Upload nodb code coverage if: ${{ !cancelled() && matrix.coverage }} From 63315cd5dfba8d9a119b74a651e5d05bd2b15bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 11:50:29 +0200 Subject: [PATCH 10/30] chore: Re-enable tests failing on drone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We do not use drone anymore, let’s see if we can fix these tests or if they should be removed. Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- apps/files_sharing/tests/ShareTest.php | 2 -- tests/lib/Avatar/GuestAvatarTest.php | 1 - tests/lib/TempManagerTest.php | 4 ---- tests/lib/UtilCheckServerTest.php | 2 -- 4 files changed, 9 deletions(-) diff --git a/apps/files_sharing/tests/ShareTest.php b/apps/files_sharing/tests/ShareTest.php index 1ed6bc9b3fe01..eedd3aba726ae 100644 --- a/apps/files_sharing/tests/ShareTest.php +++ b/apps/files_sharing/tests/ShareTest.php @@ -148,8 +148,6 @@ public function testShareWithDifferentShareFolder(): void { } public function testShareWithGroupUniqueName(): void { - $this->markTestSkipped('TODO: Disable because fails on drone'); - $this->loginHelper(self::TEST_FILES_SHARING_API_USER1); Filesystem::file_put_contents('test.txt', 'test'); diff --git a/tests/lib/Avatar/GuestAvatarTest.php b/tests/lib/Avatar/GuestAvatarTest.php index aa4c59d9ba76d..c50b4b2066ba7 100644 --- a/tests/lib/Avatar/GuestAvatarTest.php +++ b/tests/lib/Avatar/GuestAvatarTest.php @@ -45,7 +45,6 @@ public function setupGuestAvatar(): void { * the generated image is compared with an expected one. */ public function testGet(): void { - $this->markTestSkipped('TODO: Disable because fails on drone'); $avatar = $this->guestAvatar->getFile(32); self::assertInstanceOf(InMemoryFile::class, $avatar); $expectedFile = file_get_contents( diff --git a/tests/lib/TempManagerTest.php b/tests/lib/TempManagerTest.php index e63ad7d3cd90e..d9261e2385a00 100644 --- a/tests/lib/TempManagerTest.php +++ b/tests/lib/TempManagerTest.php @@ -135,8 +135,6 @@ public function testCleanOld(): void { } public function testLogCantCreateFile(): void { - $this->markTestSkipped('TODO: Disable because fails on drone'); - $logger = $this->createMock(LoggerInterface::class); $manager = $this->getManager($logger); chmod($this->baseDir, 0500); @@ -147,8 +145,6 @@ public function testLogCantCreateFile(): void { } public function testLogCantCreateFolder(): void { - $this->markTestSkipped('TODO: Disable because fails on drone'); - $logger = $this->createMock(LoggerInterface::class); $manager = $this->getManager($logger); chmod($this->baseDir, 0500); diff --git a/tests/lib/UtilCheckServerTest.php b/tests/lib/UtilCheckServerTest.php index fade2f6d341f5..55e97accc1dc2 100644 --- a/tests/lib/UtilCheckServerTest.php +++ b/tests/lib/UtilCheckServerTest.php @@ -151,8 +151,6 @@ public function testDataDirWritable(): void { * Tests an error is given when the datadir is not writable */ public function testDataDirNotWritable(): void { - $this->markTestSkipped('TODO: Disable because fails on drone'); - chmod($this->datadir, 0300); $result = \OC_Util::checkServer($this->getConfig([ 'installed' => true, From 1db5a21bddd9f5a547824da43d82362d95a581d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 11:58:37 +0200 Subject: [PATCH 11/30] chore: Exclude S3 groups from test:db composer command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These groups need a specific setup and are not supported by our main DB testing workflows. Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7b2bb8d43ff26..56ab4e65aabed 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,7 @@ "Composer\\Config::disableProcessTimeout", "phpunit --fail-on-warning --fail-on-risky --display-warnings --display-deprecations --display-phpunit-deprecations --display-skipped --colors=always --configuration tests/phpunit-autotest.xml" ], - "test:db": "@composer run test -- --group DB --group SLOWDB", + "test:db": "@composer run test -- --group DB --group SLOWDB --exclude-group S3 --exclude-group PRIMARY-swift --exclude-group PRIMARY-s3 --exclude-group PRIMARY-azure", "test:files_external": "phpunit --fail-on-warning --fail-on-risky --display-warnings --display-deprecations --display-phpunit-deprecations --colors=always --configuration tests/phpunit-autotest-external.xml", "rector": "rector --config=build/rector.php && composer cs:fix", "rector:strict": "rector --config=build/rector-strict.php && composer cs:fix", From 185f2069eebc7b50d7e0909d4939db251a77c853 Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Wed, 10 Jun 2026 12:46:38 +0200 Subject: [PATCH 12/30] chore(workflows): add APCu extension on no db tests Signed-off-by: Benjamin Gaussorgues --- .github/workflows/phpunit-nodb.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpunit-nodb.yml b/.github/workflows/phpunit-nodb.yml index f0ee71de2c987..d796c8eb2ab22 100644 --- a/.github/workflows/phpunit-nodb.yml +++ b/.github/workflows/phpunit-nodb.yml @@ -86,7 +86,7 @@ jobs: with: php-version: ${{ matrix.php-versions }} # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, imagick, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite + extensions: apcu, bz2, ctype, curl, dom, fileinfo, gd, iconv, imagick, intl, json, libxml, mbstring, openssl, pcntl, pdo_sqlite, posix, redis, session, simplexml, sqlite, xmlreader, xmlwriter, zip, zlib coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} ini-file: development # Required for tests that use pcntl From 467792e58e97fa486e962d90ed610af20dc81366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 12:46:43 +0200 Subject: [PATCH 13/30] chore(tests): Set a token string to avoid getToken returning null from tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- .../lib/Authentication/Token/PublicKeyTokenProviderTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php index f08a7be279d01..1e65b53f8f960 100644 --- a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php +++ b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php @@ -178,6 +178,7 @@ public function testUpdateToken(): void { ->method('updateActivity') ->with($tk, $this->time); $tk->setLastActivity($this->time - 200); + $tk->setToken('token'); $this->config->method('getSystemValueBool') ->willReturnMap([ ['auth.storeCryptedPassword', true, true], @@ -440,6 +441,7 @@ public function testRenewSessionTokenWithPassword(): void { public function testGetToken(): void { $token = new PublicKeyToken(); + $token->setToken('token'); $this->config->method('getSystemValue') ->with('secret') @@ -587,6 +589,9 @@ public function testMarkPasswordInvalidInvalidToken(): void { public function testMarkPasswordInvalid(): void { $token = $this->createMock(PublicKeyToken::class); + $token->expects($this->once()) + ->method('getToken') + ->willReturn('token'); $token->expects($this->once()) ->method('setPasswordInvalid') From 25a3222b67d7d155c47337456af1c6e3cdc3c351 Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Wed, 10 Jun 2026 14:36:02 +0200 Subject: [PATCH 14/30] chore(users): add stricter return types to IUser Signed-off-by: Benjamin Gaussorgues --- lib/private/User/LazyUser.php | 32 +++++++++++----------- lib/private/User/User.php | 10 +------ lib/public/IUser.php | 50 +++++++++++------------------------ 3 files changed, 34 insertions(+), 58 deletions(-) diff --git a/lib/private/User/LazyUser.php b/lib/private/User/LazyUser.php index 3ae6a557a2dca..25ea950bf859c 100644 --- a/lib/private/User/LazyUser.php +++ b/lib/private/User/LazyUser.php @@ -1,6 +1,7 @@ displayName) { return $this->displayName; } @@ -56,7 +58,7 @@ public function getDisplayName() { } #[\Override] - public function setDisplayName($displayName) { + public function setDisplayName($displayName): bool { return $this->getUser()->setDisplayName($displayName); } @@ -76,12 +78,12 @@ public function updateLastLoginTimestamp(): bool { } #[\Override] - public function delete() { + public function delete(): bool { return $this->getUser()->delete(); } #[\Override] - public function setPassword($password, $recoveryPassword = null) { + public function setPassword($password, $recoveryPassword = null): bool { return $this->getUser()->setPassword($password, $recoveryPassword); } @@ -96,12 +98,12 @@ public function setPasswordHash(string $passwordHash): bool { } #[\Override] - public function getHome() { + public function getHome(): string { return $this->getUser()->getHome(); } #[\Override] - public function getBackendClassName() { + public function getBackendClassName(): string { return $this->getUser()->getBackendClassName(); } @@ -136,17 +138,17 @@ public function canEditProperty(string $property): bool { } #[\Override] - public function isEnabled() { + public function isEnabled(): bool { return $this->getUser()->isEnabled(); } #[\Override] - public function setEnabled(bool $enabled = true) { - return $this->getUser()->setEnabled($enabled); + public function setEnabled(bool $enabled = true): void { + $this->getUser()->setEnabled($enabled); } #[\Override] - public function getEMailAddress() { + public function getEMailAddress(): string { return $this->getUser()->getEMailAddress(); } @@ -161,17 +163,17 @@ public function getPrimaryEMailAddress(): ?string { } #[\Override] - public function getAvatarImage($size) { + public function getAvatarImage($size): ?IImage { return $this->getUser()->getAvatarImage($size); } #[\Override] - public function getCloudId() { + public function getCloudId(): string { return $this->getUser()->getCloudId(); } #[\Override] - public function setEMailAddress($mailAddress) { + public function setEMailAddress($mailAddress): void { $this->getUser()->setEMailAddress($mailAddress); } @@ -186,7 +188,7 @@ public function setPrimaryEMailAddress(string $mailAddress): void { } #[\Override] - public function getQuota() { + public function getQuota(): string { return $this->getUser()->getQuota(); } @@ -196,7 +198,7 @@ public function getQuotaBytes(): int|float { } #[\Override] - public function setQuota($quota) { + public function setQuota($quota): void { $this->getUser()->setQuota($quota); } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index d22b8ae89a478..e54ac397b3efe 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -481,11 +481,9 @@ public function isEnabled(): bool { /** * set the enabled status for the user - * - * @return void */ #[\Override] - public function setEnabled(bool $enabled = true) { + public function setEnabled(bool $enabled = true): void { $oldStatus = $this->isEnabled(); $setDatabaseValue = function (bool $enabled): void { $this->config->setUserValue($this->uid, 'core', 'enabled', $enabled ? 'true' : 'false'); @@ -524,18 +522,12 @@ public function getEMailAddress(): ?string { return $this->getPrimaryEMailAddress() ?? $this->getSystemEMailAddress(); } - /** - * @inheritDoc - */ #[\Override] public function getSystemEMailAddress(): ?string { $email = $this->config->getUserValue($this->uid, 'settings', 'email', null); return $email ? mb_strtolower(trim($email)) : null; } - /** - * @inheritDoc - */ #[\Override] public function getPrimaryEMailAddress(): ?string { $email = $this->config->getUserValue($this->uid, 'settings', 'primary_email', null); diff --git a/lib/public/IUser.php b/lib/public/IUser.php index 5a06577fde7d1..afbcfd2df087b 100644 --- a/lib/public/IUser.php +++ b/lib/public/IUser.php @@ -27,36 +27,31 @@ interface IUser { /** * get the user id * - * @return string * @since 8.0.0 */ - public function getUID(); + public function getUID(): string; /** * get the display name for the user, if no specific display name is set it will fallback to the user id * - * @return string * @since 8.0.0 */ - public function getDisplayName(); + public function getDisplayName(): string; /** * set the display name for the user * - * @param string $displayName - * @return bool * @since 8.0.0 * * @since 25.0.0 Throw InvalidArgumentException * @throws \InvalidArgumentException */ - public function setDisplayName($displayName); + public function setDisplayName(string $displayName): bool; /** * returns the timestamp of the user's last login or 0 if the user did never * login * - * @return int * @since 8.0.0 */ public function getLastLogin(): int; @@ -79,20 +74,18 @@ public function updateLastLoginTimestamp(): bool; /** * Delete the user * - * @return bool * @since 8.0.0 */ - public function delete(); + public function delete(): bool; /** * Set the password of the user * * @param string $password * @param string $recoveryPassword for the encryption app to reset encryption keys - * @return bool * @since 8.0.0 */ - public function setPassword($password, $recoveryPassword = null); + public function setPassword($password, $recoveryPassword = null): bool; /** * Get the password hash of the user @@ -114,25 +107,23 @@ public function setPasswordHash(string $passwordHash): bool; /** * get the users home folder to mount * - * @return string * @since 8.0.0 */ - public function getHome(); + public function getHome(): string; /** * Get the name of the backend class the user is connected with * - * @return string * @since 8.0.0 */ - public function getBackendClassName(); + public function getBackendClassName(): string; /** * Get the backend for the current user object - * @return ?UserInterface + * * @since 15.0.0 */ - public function getBackend(); + public function getBackend(): ?UserInterface; /** * check if the backend allows the user to change their avatar on Personal page @@ -171,26 +162,23 @@ public function canEditProperty(string $property): bool; /** * check if the user is enabled * - * @return bool * @since 8.0.0 */ - public function isEnabled(); + public function isEnabled(): bool; /** * set the enabled status for the user * - * @param bool $enabled * @since 8.0.0 */ - public function setEnabled(bool $enabled = true); + public function setEnabled(bool $enabled = true): void; /** * get the user's email address * - * @return string|null * @since 9.0.0 */ - public function getEMailAddress(); + public function getEMailAddress(): ?string; /** * get the user's system email address @@ -201,7 +189,6 @@ public function getEMailAddress(); * Use this getter only when the system address is needed. For picking the * proper address to e.g. send a mail to, use getEMailAddress(). * - * @return string|null * @since 23.0.0 */ public function getSystemEMailAddress(): ?string; @@ -216,7 +203,6 @@ public function getSystemEMailAddress(): ?string; * Use this getter only when the primary address is needed. For picking the * proper address to e.g. send a mail to, use getEMailAddress(). * - * @return string|null * @since 23.0.0 */ public function getPrimaryEMailAddress(): ?string; @@ -225,10 +211,9 @@ public function getPrimaryEMailAddress(): ?string; * get the avatar image if it exists * * @param int $size - * @return IImage|null * @since 9.0.0 */ - public function getAvatarImage($size); + public function getAvatarImage($size): ?IImage; /** * get the federation cloud id @@ -244,11 +229,10 @@ public function getCloudId(); * It is an alias to setSystemEMailAddress() * * @param string|null $mailAddress - * @return void * @since 9.0.0 * @deprecated 23.0.0 use setSystemEMailAddress() or setPrimaryEMailAddress() */ - public function setEMailAddress($mailAddress); + public function setEMailAddress($mailAddress): void; /** * Set the system email address of the user @@ -282,10 +266,9 @@ public function setPrimaryEMailAddress(string $mailAddress): void; * set for the user, the default value is returned. If a default setting * was not set otherwise, it is return as 'none', i.e. quota is not limited. * - * @return string * @since 9.0.0 */ - public function getQuota(); + public function getQuota(): string; /** * Get the users' quota in machine readable form. If a specific quota is set @@ -300,10 +283,9 @@ public function getQuotaBytes(): int|float; * set the users' quota * * @param string $quota - * @return void * @since 9.0.0 */ - public function setQuota($quota); + public function setQuota($quota): void; /** * Get the user's manager UIDs From bc44af502bb3b3d153043b18bd20d98ca6bf3623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 13:49:18 +0200 Subject: [PATCH 15/30] fix: Add explicit getToken method for PublicKeyToken entity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows mocking the method in tests, and makes sure typing is respected Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- lib/private/Authentication/Token/PublicKeyToken.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/private/Authentication/Token/PublicKeyToken.php b/lib/private/Authentication/Token/PublicKeyToken.php index e3d5e7001ff5b..42c186f5e3373 100644 --- a/lib/private/Authentication/Token/PublicKeyToken.php +++ b/lib/private/Authentication/Token/PublicKeyToken.php @@ -205,6 +205,10 @@ public function getRemember(): int { return parent::getRemember(); } + public function getToken(): string { + return parent::getToken(); + } + #[\Override] public function setToken(string $token): void { parent::setToken($token); From d75b90386d4df81e63917fdc6e25da1f5f2980de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 13:55:05 +0200 Subject: [PATCH 16/30] fix: Silence PHP warnings from fopen and mkdir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We are testing the result and logging our own error anyway. Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- lib/private/TempManager.php | 6 ++++-- tests/lib/TempManagerTest.php | 7 +------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/private/TempManager.php b/lib/private/TempManager.php index f5801e5ab9896..fa7f891ca9272 100644 --- a/lib/private/TempManager.php +++ b/lib/private/TempManager.php @@ -50,13 +50,14 @@ public function getTemporaryFile($postFix = ''): string|false { $path = $this->generateTemporaryPath($postFix); $old_umask = umask(0077); - $fp = fopen($path, 'x'); + $fp = @fopen($path, 'x'); umask($old_umask); if ($fp === false) { $this->log->warning( 'Can not create a temporary file in directory {dir}. Check it exists and has correct permissions', [ 'dir' => $this->tmpBaseDir, + 'error' => error_get_last(), ] ); return false; @@ -71,11 +72,12 @@ public function getTemporaryFile($postFix = ''): string|false { public function getTemporaryFolder($postFix = ''): string|false { $path = $this->generateTemporaryPath($postFix) . '/'; - if (mkdir($path, 0700) === false) { + if (@mkdir($path, 0700) === false) { $this->log->warning( 'Can not create a temporary folder in directory {dir}. Check it exists and has correct permissions', [ 'dir' => $this->tmpBaseDir, + 'error' => error_get_last(), ] ); return false; diff --git a/tests/lib/TempManagerTest.php b/tests/lib/TempManagerTest.php index d9261e2385a00..cae7e9f8dd544 100644 --- a/tests/lib/TempManagerTest.php +++ b/tests/lib/TempManagerTest.php @@ -36,12 +36,7 @@ protected function tearDown(): void { parent::tearDown(); } - /** - * @param ?LoggerInterface $logger - * @param ?IConfig $config - * @return TempManager - */ - protected function getManager($logger = null, $config = null) { + protected function getManager(?LoggerInterface $logger = null, ?IConfig $config = null): TempManager { if (!$logger) { $logger = $this->createMock(LoggerInterface::class); } From aec037746d9cecbecc1db9952307308045d23e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 14:16:20 +0200 Subject: [PATCH 17/30] chore: Update guest avatar test image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- tests/data/guest_avatar_einstein_32.png | Bin 270 -> 272 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/data/guest_avatar_einstein_32.png b/tests/data/guest_avatar_einstein_32.png index d280dadcc8deb847e42a9c581e7a166551cde375..d03f0509bac3b028f15f29292a57ec8447c68966 100644 GIT binary patch delta 223 zcmV<503iR40+0fbIe)@QL_t(YiDP{E`8xwG0TWG4AGKiA0;&KOBGrBU_2>ELA83Li z+-w3IY}B#f#N)Sh`>vu1W|}F-=*v^b0tjez(UBM6hI55E2{@l*3uO4X6a~2nt3v{V zCXylG_rHI?|Net>S(ypDk2DKXHk?7`E)TVk6X2qz1%+0sQdqp4aIP2+J3%d^SzsU` zs3Ocml9rKER6xLqCvP8q_zLGL3-c%l5Xcs!Sup+7U1V;inKD5O7)LApQ42;b7`1>f Z008F9C+|8(z&-!~002ovPDHLkV1mGcUrhi2 delta 221 zcmV<303!d80*(TZIe)-OL_t(YiDP{8>jHF;*UKI9G^^lb{yTEYJ`W zkrNakNz2G7Dj?v}^Ow&)eTH-81o)*0Wed_QSb52)1 From c64c2361aeef006c0a79a22c377d7453be6bb3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 14:29:12 +0200 Subject: [PATCH 18/30] chore: Fix test in user_ldap passing wrong type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- apps/user_ldap/tests/AccessTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user_ldap/tests/AccessTest.php b/apps/user_ldap/tests/AccessTest.php index 8450e787a873d..3551daf261ba5 100644 --- a/apps/user_ldap/tests/AccessTest.php +++ b/apps/user_ldap/tests/AccessTest.php @@ -712,7 +712,7 @@ public function testUserStateUpdate(): void { ->method('__get') ->willReturnMap([ [ 'ldapUserDisplayName', 'displayName' ], - [ 'ldapUserDisplayName2', null], + [ 'ldapUserDisplayName2', ''], ]); $offlineUserMock = $this->createMock(OfflineUser::class); From be9625094843dcd86ab53db58c6718e3310f3a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 14:29:43 +0200 Subject: [PATCH 19/30] chore: Pass correct type for alias in QueryBuilderTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- .../lib/DB/QueryBuilder/QueryBuilderTest.php | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/tests/lib/DB/QueryBuilder/QueryBuilderTest.php b/tests/lib/DB/QueryBuilder/QueryBuilderTest.php index 1b54a8c946f13..e39ba0b78c62c 100644 --- a/tests/lib/DB/QueryBuilder/QueryBuilderTest.php +++ b/tests/lib/DB/QueryBuilder/QueryBuilderTest.php @@ -531,8 +531,8 @@ public function testFrom(string $table1Name, ?string $table1Alias, ?string $tabl public static function dataJoin(): array { return [ [ - 'd1', 'data2', null, null, - ['`d1`' => [['joinType' => 'inner', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => null, 'joinCondition' => null]]], + 'd1', 'data2', '', null, + ['`d1`' => [['joinType' => 'inner', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => '', 'joinCondition' => null]]], '`*PREFIX*data1` `d1` INNER JOIN `*PREFIX*data2` ' ], [ @@ -611,8 +611,8 @@ public function testInnerJoin($fromAlias, $tableName, $tableAlias, $condition, $ public static function dataLeftJoin(): array { return [ [ - 'd1', 'data2', null, null, - ['`d1`' => [['joinType' => 'left', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => null, 'joinCondition' => null]]], + 'd1', 'data2', '', null, + ['`d1`' => [['joinType' => 'left', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => '', 'joinCondition' => null]]], '`*PREFIX*data1` `d1` LEFT JOIN `*PREFIX*data2` ' ], [ @@ -661,8 +661,8 @@ public function testLeftJoin($fromAlias, $tableName, $tableAlias, $condition, $e public static function dataRightJoin(): array { return [ [ - 'd1', 'data2', null, null, - ['`d1`' => [['joinType' => 'right', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => null, 'joinCondition' => null]]], + 'd1', 'data2', '', null, + ['`d1`' => [['joinType' => 'right', 'joinTable' => '`*PREFIX*data2`', 'joinAlias' => '', 'joinCondition' => null]]], '`*PREFIX*data1` `d1` RIGHT JOIN `*PREFIX*data2` ' ], [ @@ -678,17 +678,15 @@ public static function dataRightJoin(): array { ]; } - /** - * - * @param string $fromAlias - * @param string $tableName - * @param string $tableAlias - * @param string $condition - * @param array $expectedQueryPart - * @param string $expectedQuery - */ #[DataProvider('dataRightJoin')] - public function testRightJoin($fromAlias, $tableName, $tableAlias, $condition, $expectedQueryPart, $expectedQuery): void { + public function testRightJoin( + string $fromAlias, + string $tableName, + string $tableAlias, + ?string $condition, + array $expectedQueryPart, + string $expectedQuery, + ): void { $this->queryBuilder->from('data1', 'd1'); $this->queryBuilder->rightJoin( $fromAlias, From 52d38d0d28d1ad464b144e19ffcc447b42980b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 14:38:01 +0200 Subject: [PATCH 20/30] chore(tests): Exclude files_external tests from default testsuite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- tests/phpunit-autotest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/phpunit-autotest.xml b/tests/phpunit-autotest.xml index 11dd78dc8095b..94f8e0a978770 100644 --- a/tests/phpunit-autotest.xml +++ b/tests/phpunit-autotest.xml @@ -18,6 +18,8 @@ Core/ ../apps/ ../apps/user_ldap/tests/Integration + + ../apps/files_external @@ -27,7 +29,7 @@ ../3rdparty ../apps/*/composer ../apps/*/tests - ../apps/files_external/3rdparty + ../apps/files_external ../build ../lib/composer ../tests From c25d442e11195ba87fc43446b9d213725ee78287 Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Wed, 10 Jun 2026 15:23:23 +0200 Subject: [PATCH 21/30] chore(tests): enable setup-php failure on deps Signed-off-by: Benjamin Gaussorgues --- .github/workflows/phpunit-mariadb.yml | 1 + .github/workflows/phpunit-memcached.yml | 1 + .github/workflows/phpunit-mysql-sharding.yml | 5 +++-- .github/workflows/phpunit-mysql.yml | 1 + .github/workflows/phpunit-nodb.yml | 5 +++-- .github/workflows/phpunit-object-store-primary.yml | 8 ++++---- .github/workflows/phpunit-oci.yml | 1 + .github/workflows/phpunit-pgsql.yml | 1 + .github/workflows/phpunit-sqlite.yml | 1 + 9 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/phpunit-mariadb.yml b/.github/workflows/phpunit-mariadb.yml index 9a9b646792fb5..b8b70029dc31c 100644 --- a/.github/workflows/phpunit-mariadb.yml +++ b/.github/workflows/phpunit-mariadb.yml @@ -104,6 +104,7 @@ jobs: ini-file: development ini-values: disable_functions="" env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up dependencies diff --git a/.github/workflows/phpunit-memcached.yml b/.github/workflows/phpunit-memcached.yml index 96729c1e8dd58..7aa42765fe9a8 100644 --- a/.github/workflows/phpunit-memcached.yml +++ b/.github/workflows/phpunit-memcached.yml @@ -88,6 +88,7 @@ jobs: ini-file: development ini-values: disable_functions="" env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up dependencies diff --git a/.github/workflows/phpunit-mysql-sharding.yml b/.github/workflows/phpunit-mysql-sharding.yml index f8c458b8e961a..204b151b92e18 100644 --- a/.github/workflows/phpunit-mysql-sharding.yml +++ b/.github/workflows/phpunit-mysql-sharding.yml @@ -56,8 +56,8 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.2'] - mysql-versions: ['8.4'] + php-versions: ["8.2"] + mysql-versions: ["8.4"] name: Sharding - MySQL ${{ matrix.mysql-versions }} (PHP ${{ matrix.php-versions }}) - database tests @@ -137,6 +137,7 @@ jobs: ini-file: development ini-values: disable_functions="" env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up dependencies diff --git a/.github/workflows/phpunit-mysql.yml b/.github/workflows/phpunit-mysql.yml index ffc1f9d90a3c0..a0f4a3f3136ed 100644 --- a/.github/workflows/phpunit-mysql.yml +++ b/.github/workflows/phpunit-mysql.yml @@ -104,6 +104,7 @@ jobs: ini-file: development ini-values: disable_functions="" env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up dependencies diff --git a/.github/workflows/phpunit-nodb.yml b/.github/workflows/phpunit-nodb.yml index d796c8eb2ab22..3504d3568754a 100644 --- a/.github/workflows/phpunit-nodb.yml +++ b/.github/workflows/phpunit-nodb.yml @@ -59,9 +59,9 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.3', '8.4', '8.5'] + php-versions: ["8.3", "8.4", "8.5"] include: - - php-versions: '8.2' + - php-versions: "8.2" coverage: ${{ github.event_name != 'pull_request' }} name: No DB unit tests (PHP ${{ matrix.php-versions }}) @@ -92,6 +92,7 @@ jobs: # Required for tests that use pcntl ini-values: disable_functions="" env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up dependencies diff --git a/.github/workflows/phpunit-object-store-primary.yml b/.github/workflows/phpunit-object-store-primary.yml index b7187d349a0a0..0b1e0106b71c7 100644 --- a/.github/workflows/phpunit-object-store-primary.yml +++ b/.github/workflows/phpunit-object-store-primary.yml @@ -49,8 +49,8 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.2'] - key: ['s3', 's3-multibucket'] + php-versions: ["8.2"] + key: ["s3", "s3-multibucket"] name: php${{ matrix.php-versions }}-${{ matrix.key }}-minio @@ -84,6 +84,7 @@ jobs: php-version: ${{ matrix.php-versions }} extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up Nextcloud @@ -113,10 +114,9 @@ jobs: docker ps -a docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - object-store-primary-summary: runs-on: ubuntu-latest-low - needs: [changes,object-store-primary-tests-minio] + needs: [changes, object-store-primary-tests-minio] if: always() diff --git a/.github/workflows/phpunit-oci.yml b/.github/workflows/phpunit-oci.yml index c53687707d943..2e524df428f90 100644 --- a/.github/workflows/phpunit-oci.yml +++ b/.github/workflows/phpunit-oci.yml @@ -111,6 +111,7 @@ jobs: ini-file: development ini-values: disable_functions="" env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up dependencies diff --git a/.github/workflows/phpunit-pgsql.yml b/.github/workflows/phpunit-pgsql.yml index a7c3814315604..789ac9ff0d54c 100644 --- a/.github/workflows/phpunit-pgsql.yml +++ b/.github/workflows/phpunit-pgsql.yml @@ -107,6 +107,7 @@ jobs: ini-file: development ini-values: disable_functions="" env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up dependencies diff --git a/.github/workflows/phpunit-sqlite.yml b/.github/workflows/phpunit-sqlite.yml index 22236155fec3d..375cce658c02b 100644 --- a/.github/workflows/phpunit-sqlite.yml +++ b/.github/workflows/phpunit-sqlite.yml @@ -90,6 +90,7 @@ jobs: ini-file: development ini-values: disable_functions="" env: + fail-fast: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up dependencies From aae83dbfb5802e74f0bb4565e522698831118180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 16:05:27 +0200 Subject: [PATCH 22/30] chore(tests): Adapt tests to IUser strict typing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- .../Connector/Sabre/AddExtraHeadersPluginTest.php | 10 +++++++--- .../dav/tests/unit/Connector/Sabre/PrincipalTest.php | 12 ++++++++++++ .../tests/Controller/UsersControllerTest.php | 8 ++++---- .../ContactsMenu/Providers/LocalTimeProviderTest.php | 4 ---- tests/lib/TaskProcessing/TaskProcessingTest.php | 2 +- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/dav/tests/unit/Connector/Sabre/AddExtraHeadersPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/AddExtraHeadersPluginTest.php index 2906afea7d8d3..f913010773b79 100644 --- a/apps/dav/tests/unit/Connector/Sabre/AddExtraHeadersPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/AddExtraHeadersPluginTest.php @@ -81,9 +81,13 @@ function ($method, $callback) use (&$afterPut): void { $this->tree->expects($this->once())->method('getNodeForPath') ->willReturn($node); - $user = $this->createMock(IUser::class); - $node->expects($this->once())->method('getOwner')->willReturn($user); - $user->expects($this->once())->method('getUID')->willReturn($ownerId); + if ($ownerId !== null) { + $user = $this->createMock(IUser::class); + $node->expects($this->once())->method('getOwner')->willReturn($user); + $user->expects($this->once())->method('getUID')->willReturn($ownerId); + } else { + $node->expects($this->once())->method('getOwner')->willReturn(null); + } $node->expects($this->once())->method('getDavPermissions')->willReturn($permissions); $matcher = $this->exactly($expectedInvocations); diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php index 8699903e1ec10..8cd19b1143cac 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php @@ -101,6 +101,10 @@ public function testGetPrincipalsByPrefixWithUsers(): void { ->expects($this->once()) ->method('getSystemEMailAddress') ->willReturn('bar@nextcloud.com'); + $barUser + ->expects($this->once()) + ->method('getDisplayName') + ->willReturn('bar'); $this->userManager ->expects($this->once()) ->method('search') @@ -189,6 +193,10 @@ public function testGetPrincipalsByPathWithoutMail(): void { ->expects($this->once()) ->method('getUID') ->willReturn('foo'); + $fooUser + ->expects($this->once()) + ->method('getDisplayName') + ->willReturn('foo'); $this->userManager ->expects($this->once()) ->method('get') @@ -221,6 +229,10 @@ public function testGetPrincipalsByPathWithMail(): void { ->expects($this->once()) ->method('getUID') ->willReturn('foo'); + $fooUser + ->expects($this->once()) + ->method('getDisplayName') + ->willReturn('foo'); $this->userManager ->expects($this->once()) ->method('get') diff --git a/apps/settings/tests/Controller/UsersControllerTest.php b/apps/settings/tests/Controller/UsersControllerTest.php index 39a4dbc42c0e6..1a47fa5b74f00 100644 --- a/apps/settings/tests/Controller/UsersControllerTest.php +++ b/apps/settings/tests/Controller/UsersControllerTest.php @@ -675,7 +675,7 @@ public static function dataTestSetUserSettingsSubset(): array { } #[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'dataTestSaveUserSettings')] - public function testSaveUserSettings(array $data, ?string $oldEmailAddress, ?string $oldDisplayName): void { + public function testSaveUserSettings(array $data, ?string $oldEmailAddress, string $oldDisplayName): void { $controller = $this->getController(); $user = $this->createMock(IUser::class); @@ -690,7 +690,7 @@ public function testSaveUserSettings(array $data, ?string $oldEmailAddress, ?str ->with($data[IAccountManager::PROPERTY_EMAIL]['value']); } - if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName ?? '') { + if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName) { $user->expects($this->never())->method('setDisplayName'); } else { $user->expects($this->once())->method('setDisplayName') @@ -775,7 +775,7 @@ public static function dataTestSaveUserSettings(): array { IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'], ], 'john@example.com', - null + '' ], [ [ @@ -783,7 +783,7 @@ public static function dataTestSaveUserSettings(): array { IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'], ], 'JOHN@example.com', - null + '' ], ]; } diff --git a/tests/lib/Contacts/ContactsMenu/Providers/LocalTimeProviderTest.php b/tests/lib/Contacts/ContactsMenu/Providers/LocalTimeProviderTest.php index a36416ca9ead6..e595af8e03555 100644 --- a/tests/lib/Contacts/ContactsMenu/Providers/LocalTimeProviderTest.php +++ b/tests/lib/Contacts/ContactsMenu/Providers/LocalTimeProviderTest.php @@ -186,10 +186,6 @@ public function testProcessNoUser(): void { ->with('UID') ->willReturn('user1'); - $user = $this->createMock(IUser::class); - $user->method('getUID') - ->willReturn(null); - $entry->expects($this->never()) ->method('addAction'); diff --git a/tests/lib/TaskProcessing/TaskProcessingTest.php b/tests/lib/TaskProcessing/TaskProcessingTest.php index 3bfc586240c9e..bb47bd735b96c 100644 --- a/tests/lib/TaskProcessing/TaskProcessingTest.php +++ b/tests/lib/TaskProcessing/TaskProcessingTest.php @@ -912,7 +912,7 @@ public function testProviderShouldBeRegisteredAndTaskWithFilesFailValidation(): new ServiceRegistration('test', AsyncProvider::class) ]); $user = $this->createMock(IUser::class); - $user->expects($this->any())->method('getUID')->willReturn(null); + $user->expects($this->any())->method('getUID')->willReturn('uid'); $mount = $this->createMock(ICachedMountInfo::class); $mount->expects($this->any())->method('getUser')->willReturn($user); $this->userMountCache->expects($this->any())->method('getMountsForFileId')->willReturn([$mount]); From 1e3d541b8e2677b8e5c2d85f23a2931f3730c719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 16:06:03 +0200 Subject: [PATCH 23/30] fix(dav): Remove useless null check on displayName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is already a fallback to uid inside User.php Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- apps/dav/lib/Connector/Sabre/Principal.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php index 6d406b1739a5b..a29961c2a2ae6 100644 --- a/apps/dav/lib/Connector/Sabre/Principal.php +++ b/apps/dav/lib/Connector/Sabre/Principal.php @@ -496,10 +496,9 @@ protected function userToPrincipal($user, ?array $propertyFilter = null) { }; $userId = $user->getUID(); - $displayName = $user->getDisplayName(); $principal = [ 'uri' => $this->principalPrefix . '/' . $userId, - '{DAV:}displayname' => is_null($displayName) ? $userId : $displayName, + '{DAV:}displayname' => $user->getDisplayName(), '{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL', ]; From 094007a47f87bd027eb5b2d157a5b77fdb743681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 16:07:21 +0200 Subject: [PATCH 24/30] chore(tests): Fix typo resulting in tests issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- tests/Core/Controller/ContactsMenuControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Core/Controller/ContactsMenuControllerTest.php b/tests/Core/Controller/ContactsMenuControllerTest.php index ea790ea81581f..c06086bce24c4 100644 --- a/tests/Core/Controller/ContactsMenuControllerTest.php +++ b/tests/Core/Controller/ContactsMenuControllerTest.php @@ -72,7 +72,7 @@ public function testIndex_withTeam(): void { $entries[0]->method('getProperty') ->with('UID') ->willReturn('member1'); - $entries[0]->method('getProperty') + $entries[1]->method('getProperty') ->with('UID') ->willReturn('member2'); From 6a82921172d352d286fe0f93b0bcd461c6ed7978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 16:41:39 +0200 Subject: [PATCH 25/30] chore: Enable sqlite CI testing again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was restricted to a single test by mistake in 4b015568fcbaaaea6e0766404887de7aefff56fb Signed-off-by: Côme Chilliet Signed-off-by: Benjamin Gaussorgues --- .github/workflows/phpunit-sqlite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpunit-sqlite.yml b/.github/workflows/phpunit-sqlite.yml index 375cce658c02b..0ff8a56e5a14b 100644 --- a/.github/workflows/phpunit-sqlite.yml +++ b/.github/workflows/phpunit-sqlite.yml @@ -111,7 +111,7 @@ jobs: run: ./occ app:list && echo "======= System config =======" && ./occ config:list system - name: PHPUnit database tests - run: composer run test:db -- --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.db.xml' || '' }} tests/lib/Preview/PostscriptTest.php + run: composer run test:db -- --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.db.xml' || '' }} - name: Upload db code coverage if: ${{ !cancelled() && matrix.coverage }} From 947dcbb8a5e19e43c7e50f369f0043f1ef785c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 17:21:59 +0200 Subject: [PATCH 26/30] chore(tests): Speed up TARTest by compressing a smaller folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- tests/lib/Archive/TestBase.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/lib/Archive/TestBase.php b/tests/lib/Archive/TestBase.php index 24b518104d53a..fc5ad22c8f313 100644 --- a/tests/lib/Archive/TestBase.php +++ b/tests/lib/Archive/TestBase.php @@ -139,11 +139,9 @@ public function testMoveRemove(): void { $this->assertFalse($this->instance->fileExists('target.txt')); } public function testRecursive(): void { - $dir = \OC::$SERVERROOT . '/tests/data'; + $dir = \OC::$SERVERROOT . '/tests/data/themes'; $this->instance = $this->getNew(); $this->instance->addRecursive('/dir', $dir); - $this->assertTrue($this->instance->fileExists('/dir/lorem.txt')); - $this->assertTrue($this->instance->fileExists('/dir/data.zip')); - $this->assertTrue($this->instance->fileExists('/dir/data.tar.gz')); + $this->assertTrue($this->instance->fileExists('/dir/abc/apps/files/l10n/zz.json')); } } From f1976d41cdb3b43d5abcd0417b113fc93eddfac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 17:53:50 +0200 Subject: [PATCH 27/30] fix: Fix data directory permission check and its test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/private/legacy/OC_Util.php | 9 ++++++++- tests/lib/UtilCheckServerTest.php | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index da8b9ded24c7a..60914b70dad77 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -314,7 +314,14 @@ public static function checkServer(SystemConfig $config) { [$urlGenerator->linkToDocs('admin-dir_permissions')]) ]; } - } elseif (!is_writable($CONFIG_DATADIRECTORY) || !is_readable($CONFIG_DATADIRECTORY)) { + } elseif (!is_readable($CONFIG_DATADIRECTORY)) { + $permissionsHint = $l->t('Permissions can usually be fixed by giving the web server write access to the root directory. See %s.', + [$urlGenerator->linkToDocs('admin-dir_permissions')]); + $errors[] = [ + 'error' => $l->t('Your data directory is not readable.'), + 'hint' => $permissionsHint + ]; + } elseif (!is_writable($CONFIG_DATADIRECTORY)) { // is_writable doesn't work for NFS mounts, so try to write a file and check if it exists. $testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_')); $handle = fopen($testFile, 'w'); diff --git a/tests/lib/UtilCheckServerTest.php b/tests/lib/UtilCheckServerTest.php index 55e97accc1dc2..712235f559996 100644 --- a/tests/lib/UtilCheckServerTest.php +++ b/tests/lib/UtilCheckServerTest.php @@ -148,9 +148,9 @@ public function testDataDirWritable(): void { } /** - * Tests an error is given when the datadir is not writable + * Tests an error is given when the datadir is not readable */ - public function testDataDirNotWritable(): void { + public function testDataDirNotReadable(): void { chmod($this->datadir, 0300); $result = \OC_Util::checkServer($this->getConfig([ 'installed' => true, @@ -163,7 +163,7 @@ public function testDataDirNotWritable(): void { * Tests no error is given when the datadir is not writable during setup */ public function testDataDirNotWritableSetup(): void { - chmod($this->datadir, 0300); + chmod($this->datadir, 0500); $result = \OC_Util::checkServer($this->getConfig([ 'installed' => false, 'version' => implode('.', Util::getVersion()) From 35800f399f215941345b1602c48115502dd09c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 22:35:40 +0200 Subject: [PATCH 28/30] chore: Fix psalm issues following strong typing of IUser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- apps/files_trashbin/lib/Trashbin.php | 2 +- apps/files_versions/lib/Storage.php | 2 +- apps/provisioning_api/lib/Controller/GroupsController.php | 2 +- apps/user_ldap/lib/Connection.php | 2 +- build/psalm-baseline.xml | 1 + lib/private/Group/Manager.php | 2 +- lib/private/User/LazyUser.php | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index b5edf37bf2f2c..b32b2ccb7c687 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -850,7 +850,7 @@ private static function calculateFreeSpace(Folder $userFolder, int|float $trashb $softQuota = true; $quota = $user->getQuota(); - if ($quota === null || $quota === 'none') { + if ($quota === 'none') { $quota = Filesystem::free_space('/'); $softQuota = false; // inf or unknown free space diff --git a/apps/files_versions/lib/Storage.php b/apps/files_versions/lib/Storage.php index 47f14d96b61c6..cf49e5a1d4d26 100644 --- a/apps/files_versions/lib/Storage.php +++ b/apps/files_versions/lib/Storage.php @@ -879,7 +879,7 @@ public static function expire($filename, $uid) { $softQuota = true; $quota = $user->getQuota(); - if ($quota === null || $quota === 'none') { + if ($quota === 'none') { $quota = Filesystem::free_space('/'); $softQuota = false; } else { diff --git a/apps/provisioning_api/lib/Controller/GroupsController.php b/apps/provisioning_api/lib/Controller/GroupsController.php index b251610e67f96..3cda3ae74ba2a 100644 --- a/apps/provisioning_api/lib/Controller/GroupsController.php +++ b/apps/provisioning_api/lib/Controller/GroupsController.php @@ -216,7 +216,7 @@ public function getGroupUsersDetails(string $groupId, string $search = '', ?int foreach ($users as $user) { try { /** @var IUser $user */ - $userId = (string)$user->getUID(); + $userId = $user->getUID(); $userData = $this->getUserData($userId); // Do not insert empty entry if ($userData !== null) { diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php index 3bc0580be366a..3cc86922dada4 100644 --- a/apps/user_ldap/lib/Connection.php +++ b/apps/user_ldap/lib/Connection.php @@ -313,7 +313,7 @@ public function writeToCache($key, $value, ?int $ttlOverride = null): void { $key = $this->getCacheKey($key); $value = base64_encode(json_encode($value)); $ttl = $ttlOverride ?? $this->configuration->ldapCacheTTL; - $this->cache->set($key, $value, $ttl); + $this->cache->set($key, $value, (int)$ttl); } public function clearCache() { diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index fe478aab69d41..ca0d4b6785c53 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -3417,6 +3417,7 @@ + diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php index 415a38ef41830..4dddfb3ed3779 100644 --- a/lib/private/Group/Manager.php +++ b/lib/private/Group/Manager.php @@ -452,7 +452,7 @@ public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0 $matchingUsers = []; foreach ($groupUsers as $groupUser) { - $matchingUsers[(string)$groupUser->getUID()] = $groupUser->getDisplayName(); + $matchingUsers[$groupUser->getUID()] = $groupUser->getDisplayName(); } return $matchingUsers; } diff --git a/lib/private/User/LazyUser.php b/lib/private/User/LazyUser.php index 25ea950bf859c..8a010782a03e2 100644 --- a/lib/private/User/LazyUser.php +++ b/lib/private/User/LazyUser.php @@ -148,7 +148,7 @@ public function setEnabled(bool $enabled = true): void { } #[\Override] - public function getEMailAddress(): string { + public function getEMailAddress(): ?string { return $this->getUser()->getEMailAddress(); } From c0080e0a3dcb28a1808e200b28a0cec8b55a9636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 22:57:19 +0200 Subject: [PATCH 29/30] chore(tests): Use /dev/shm as a tempdirectory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Should fix tests in phpunit-32bits Signed-off-by: Côme Chilliet --- tests/lib/TempManagerTest.php | 2 +- tests/preseed-config.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lib/TempManagerTest.php b/tests/lib/TempManagerTest.php index cae7e9f8dd544..06a43930e919f 100644 --- a/tests/lib/TempManagerTest.php +++ b/tests/lib/TempManagerTest.php @@ -44,7 +44,7 @@ protected function getManager(?LoggerInterface $logger = null, ?IConfig $config $config = $this->createMock(IConfig::class); $config->method('getSystemValue') ->with('tempdirectory', null) - ->willReturn('/tmp'); + ->willReturn('/dev/shm'); } $iniGetWrapper = $this->createMock(IniGetWrapper::class); $manager = new TempManager($logger, $config, $iniGetWrapper); diff --git a/tests/preseed-config.php b/tests/preseed-config.php index 1fc98a4226ac0..0853b220a653e 100644 --- a/tests/preseed-config.php +++ b/tests/preseed-config.php @@ -16,6 +16,7 @@ 'writable' => true, ], ], + 'tempdirectory' => '/dev/shm', ]; if (is_dir(OC::$SERVERROOT . '/apps2')) { From bb544c4fdfeb2dcec197feda9ac261eac54561df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 10 Jun 2026 23:25:19 +0200 Subject: [PATCH 30/30] tmp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- tests/lib/TempManagerTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/lib/TempManagerTest.php b/tests/lib/TempManagerTest.php index 06a43930e919f..19bde09c4d781 100644 --- a/tests/lib/TempManagerTest.php +++ b/tests/lib/TempManagerTest.php @@ -132,7 +132,14 @@ public function testCleanOld(): void { public function testLogCantCreateFile(): void { $logger = $this->createMock(LoggerInterface::class); $manager = $this->getManager($logger); + var_dump( + posix_getpwuid(posix_getuid()), + ); + printf('%o', fileperms($this->baseDir)); + echo PHP_EOL; chmod($this->baseDir, 0500); + printf('%o', fileperms($this->baseDir)); + echo PHP_EOL; $logger->expects($this->once()) ->method('warning') ->with($this->stringContains('Can not create a temporary file in directory'));