diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-01-30 01:25:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-30 01:25:20 +0100 |
commit | fce58d82fe13c507ecba2b1d447179a8b130a18a (patch) | |
tree | 291602e66bd66db164116812fbddb01c83b07f8b | |
parent | 4e9821f5c391705570166a42cfb3df2f2df17abd (diff) | |
parent | bf7dc2f2e7a4ef82db38d10deebfdfaebdac4b13 (diff) | |
download | nextcloud-server-fce58d82fe13c507ecba2b1d447179a8b130a18a.tar.gz nextcloud-server-fce58d82fe13c507ecba2b1d447179a8b130a18a.zip |
Merge pull request #50503 from nextcloud/fix/theming-migration
fix(theming): Do not throw in background color migration
-rw-r--r-- | apps/theming/lib/Migration/Version2006Date20240905111627.php | 40 | ||||
-rw-r--r-- | apps/theming/tests/Migration/Version2006Date20240905111627Test.php | 181 |
2 files changed, 220 insertions, 1 deletions
diff --git a/apps/theming/lib/Migration/Version2006Date20240905111627.php b/apps/theming/lib/Migration/Version2006Date20240905111627.php index 92b3c0e4cfa..8f4130cba46 100644 --- a/apps/theming/lib/Migration/Version2006Date20240905111627.php +++ b/apps/theming/lib/Migration/Version2006Date20240905111627.php @@ -83,7 +83,45 @@ class Version2006Date20240905111627 implements IMigrationStep { ->set('configkey', $qb->createNamedParameter('primary_color')) ->where($qb->expr()->eq('appid', $qb->createNamedParameter(Application::APP_ID))) ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('background_color'))); - $qb->executeStatement(); + + try { + $qb->executeStatement(); + } catch (\Exception) { + $output->debug('Some users already configured the background color'); + $this->restoreUserColorsFallback($output); + } + $output->info('Primary color of users restored'); } + + /** + * Similar to restoreUserColors but also works if some users already setup a new value. + * This is only called if the first approach fails as this takes much longer on the DB. + */ + private function restoreUserColorsFallback(IOutput $output): void { + $qb = $this->connection->getQueryBuilder(); + $qb2 = $this->connection->getQueryBuilder(); + + $qb2->select('userid') + ->from('preferences') + ->where($qb->expr()->eq('appid', $qb->createNamedParameter(Application::APP_ID))) + ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('primary_color'))); + + // MySQL does not update on select of the same table, so this is a workaround: + if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_MYSQL) { + $subquery = 'SELECT * from ( ' . $qb2->getSQL() . ' ) preferences_alias'; + } else { + $subquery = $qb2->getSQL(); + } + + $qb->update('preferences') + ->set('configkey', $qb->createNamedParameter('primary_color')) + ->where($qb->expr()->eq('appid', $qb->createNamedParameter(Application::APP_ID))) + ->andWhere( + $qb->expr()->eq('configkey', $qb->createNamedParameter('background_color')), + $qb->expr()->notIn('userid', $qb->createFunction($subquery)), + ); + + $qb->executeStatement(); + } } diff --git a/apps/theming/tests/Migration/Version2006Date20240905111627Test.php b/apps/theming/tests/Migration/Version2006Date20240905111627Test.php new file mode 100644 index 00000000000..bdd27d08e7f --- /dev/null +++ b/apps/theming/tests/Migration/Version2006Date20240905111627Test.php @@ -0,0 +1,181 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Theming\Tests\Migration; + +use NCU\Config\IUserConfig; +use OCA\Theming\Migration\Version2006Date20240905111627; +use OCP\BackgroundJob\IJobList; +use OCP\IAppConfig; +use OCP\IDBConnection; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +/** + * @group DB + */ +class Version2006Date20240905111627Test extends TestCase { + + private IAppConfig&MockObject $appConfig; + private IDBConnection&MockObject $connection; + private IJobList&MockObject $jobList; + private Version2006Date20240905111627 $migration; + + protected function setUp(): void { + parent::setUp(); + + $this->appConfig = $this->createMock(IAppConfig::class); + $this->connection = $this->createMock(IDBConnection::class); + $this->jobList = $this->createMock(IJobList::class); + $this->migration = new Version2006Date20240905111627( + $this->jobList, + $this->appConfig, + $this->connection, + ); + } + + public function testRestoreSystemColors(): void { + $this->appConfig->expects(self::once()) + ->method('getValueString') + ->with('theming', 'color', '') + ->willReturn('ffab00'); + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn(true); + + // expect the color value to be deleted + $this->appConfig->expects(self::once()) + ->method('deleteKey') + ->with('theming', 'color'); + // expect the correct calls to setValueString (setting the new values) + $setValueCalls = []; + $this->appConfig->expects(self::exactly(2)) + ->method('setValueString') + ->willReturnCallback(function () use (&$setValueCalls) { + $setValueCalls[] = func_get_args(); + return true; + }); + + $output = $this->createMock(IOutput::class); + $this->migration->changeSchema($output, fn () => null, []); + + $this->assertEquals([ + ['theming', 'background_color', 'ffab00', false, false], + ['theming', 'primary_color', 'ffab00', false, false], + ], $setValueCalls); + } + + /** + * @group DB + */ + public function testRestoreUserColors(): void { + $this->appConfig->expects(self::once()) + ->method('getValueString') + ->with('theming', 'color', '') + ->willReturn(''); + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn(false); + + // Create a user + $manager = \OCP\Server::get(IUserManager::class); + $user = $manager->createUser('theming_legacy', 'theming_legacy'); + self::assertNotFalse($user); + // Set the users theming value to legacy key + $config = \OCP\Server::get(IUserConfig::class); + $config->setValueString('theming_legacy', 'theming', 'background_color', 'ffab00'); + + // expect some output + $output = $this->createMock(IOutput::class); + $output->expects(self::exactly(3)) + ->method('info') + ->willReturnCallback(fn ($txt) => match($txt) { + 'No custom system color configured - skipping' => true, + 'Restoring user primary color' => true, + 'Primary color of users restored' => true, + default => self::fail('output.info called with unexpected argument: ' . $txt) + }); + // Create the migration class + $migration = new Version2006Date20240905111627( + $this->jobList, + $this->appConfig, + \OCP\Server::get(IDBConnection::class), + ); + // Run the migration + $migration->changeSchema($output, fn () => null, []); + + // See new value + $config->clearCache('theming_legacy'); + $newValue = $config->getValueString('theming_legacy', 'theming', 'primary_color'); + self::assertEquals('ffab00', $newValue); + + // cleanup + $user->delete(); + } + + /** + * Ensure only users with background color but no primary color are migrated + * @group DB + */ + public function testRestoreUserColorsWithConflicts(): void { + $this->appConfig->expects(self::once()) + ->method('getValueString') + ->with('theming', 'color', '') + ->willReturn(''); + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('theming', 'disable-user-theming') + ->willReturn(false); + + // Create a user + $manager = \OCP\Server::get(IUserManager::class); + $legacyUser = $manager->createUser('theming_legacy', 'theming_legacy'); + self::assertNotFalse($legacyUser); + $user = $manager->createUser('theming_no_legacy', 'theming_no_legacy'); + self::assertNotFalse($user); + // Set the users theming value to legacy key + $config = \OCP\Server::get(IUserConfig::class); + $config->setValueString($user->getUID(), 'theming', 'primary_color', '999999'); + $config->setValueString($user->getUID(), 'theming', 'background_color', '111111'); + $config->setValueString($legacyUser->getUID(), 'theming', 'background_color', 'ffab00'); + + // expect some output + $output = $this->createMock(IOutput::class); + $output->expects(self::exactly(3)) + ->method('info') + ->willReturnCallback(fn ($txt) => match($txt) { + 'No custom system color configured - skipping' => true, + 'Restoring user primary color' => true, + 'Primary color of users restored' => true, + default => self::fail('output.info called with unexpected argument: ' . $txt) + }); + // Create the migration class + $migration = new Version2006Date20240905111627( + $this->jobList, + $this->appConfig, + \OCP\Server::get(IDBConnection::class), + ); + // Run the migration + $migration->changeSchema($output, fn () => null, []); + + // See new value of only the legacy user + $config->clearCacheAll(); + self::assertEquals('111111', $config->getValueString($user->getUID(), 'theming', 'background_color')); + self::assertEquals('999999', $config->getValueString($user->getUID(), 'theming', 'primary_color')); + self::assertEquals('ffab00', $config->getValueString($legacyUser->getUID(), 'theming', 'primary_color')); + + // cleanup + $legacyUser->delete(); + $user->delete(); + } +} |