From 98bc98ac98addd603925c9d10a97f700aba34285 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Fri, 8 Jul 2022 18:58:02 +0000 Subject: Add profile config migration Signed-off-by: Christopher Ng --- .../settings/lib/UserMigration/AccountMigrator.php | 33 +++++++++++++++++ .../tests/UserMigration/AccountMigratorTest.php | 43 +++++++++++++++------- .../assets/account-complex-config.json | 1 + .../tests/UserMigration/assets/account-config.json | 1 + 4 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 apps/settings/tests/UserMigration/assets/account-complex-config.json create mode 100644 apps/settings/tests/UserMigration/assets/account-config.json (limited to 'apps') diff --git a/apps/settings/lib/UserMigration/AccountMigrator.php b/apps/settings/lib/UserMigration/AccountMigrator.php index 4db28306eb1..e8c70624224 100644 --- a/apps/settings/lib/UserMigration/AccountMigrator.php +++ b/apps/settings/lib/UserMigration/AccountMigrator.php @@ -28,7 +28,9 @@ namespace OCA\Settings\UserMigration; use InvalidArgumentException; use OC\Accounts\TAccountsHelper; +use OC\Core\Db\ProfileConfigMapper; use OC\NotSquareException; +use OC\Profile\ProfileManager; use OCA\Settings\AppInfo\Application; use OCP\Accounts\IAccountManager; use OCP\IAvatarManager; @@ -51,6 +53,10 @@ class AccountMigrator implements IMigrator, ISizeEstimationMigrator { private IAvatarManager $avatarManager; + private ProfileManager $profileManager; + + private ProfileConfigMapper $configMapper; + private IL10N $l10n; private const PATH_ROOT = Application::APP_ID . '/'; @@ -59,13 +65,19 @@ class AccountMigrator implements IMigrator, ISizeEstimationMigrator { private const AVATAR_BASENAME = 'avatar'; + private const PATH_CONFIG_FILE = AccountMigrator::PATH_ROOT . 'config.json'; + public function __construct( IAccountManager $accountManager, IAvatarManager $avatarManager, + ProfileManager $profileManager, + ProfileConfigMapper $configMapper, IL10N $l10n ) { $this->accountManager = $accountManager; $this->avatarManager = $avatarManager; + $this->profileManager = $profileManager; + $this->configMapper = $configMapper; $this->l10n = $l10n; } @@ -113,6 +125,14 @@ class AccountMigrator implements IMigrator, ISizeEstimationMigrator { } catch (Throwable $e) { throw new AccountMigratorException('Could not export avatar', 0, $e); } + + try { + $output->writeln('Exporting profile config in ' . AccountMigrator::PATH_CONFIG_FILE . '…'); + $config = $this->profileManager->getProfileConfig($user, $user); + $exportDestination->addFileContents(AccountMigrator::PATH_CONFIG_FILE, json_encode($config)); + } catch (Throwable $e) { + throw new AccountMigratorException('Could not export profile config', 0, $e); + } } /** @@ -165,6 +185,19 @@ class AccountMigrator implements IMigrator, ISizeEstimationMigrator { throw new AccountMigratorException('Failed to import avatar', 0, $e); } } + + try { + $output->writeln('Importing profile config from ' . AccountMigrator::PATH_CONFIG_FILE . '…'); + /** @var array $configData */ + $configData = json_decode($importSource->getFileContents(AccountMigrator::PATH_CONFIG_FILE), true, 512, JSON_THROW_ON_ERROR); + // Ensure that a profile config entry exists in the database + $this->profileManager->getProfileConfig($user, $user); + $config = $this->configMapper->get($user->getUID()); + $config->setConfigArray($configData); + $this->configMapper->update($config); + } catch (Throwable $e) { + throw new AccountMigratorException('Failed to import profile config'); + } } /** diff --git a/apps/settings/tests/UserMigration/AccountMigratorTest.php b/apps/settings/tests/UserMigration/AccountMigratorTest.php index 573d18380e5..eebc3bc5910 100644 --- a/apps/settings/tests/UserMigration/AccountMigratorTest.php +++ b/apps/settings/tests/UserMigration/AccountMigratorTest.php @@ -34,6 +34,7 @@ use OCP\IAvatarManager; use OCP\IUserManager; use OCP\UserMigration\IExportDestination; use OCP\UserMigration\IImportSource; +use PHPUnit\Framework\Constraint\JsonMatches; use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\UUIDUtil; use Symfony\Component\Console\Output\OutputInterface; @@ -65,6 +66,8 @@ class AccountMigratorTest extends TestCase { private const REGEX_AVATAR_FILE = '/^' . Application::APP_ID . '\/' . 'avatar\.(jpg|png)' . '$/'; + private const REGEX_CONFIG_FILE = '/^' . Application::APP_ID . '\/' . '[a-z]+\.json' . '$/'; + protected function setUp(): void { $app = new App(Application::APP_ID); $container = $app->getContainer(); @@ -81,19 +84,21 @@ class AccountMigratorTest extends TestCase { public function dataImportExportAccount(): array { return array_map( function (string $filename) { - $dataPath = self::ASSETS_DIR . $filename; - // For each json file there is an avatar image with the same basename - $avatarBasename = pathinfo($filename, PATHINFO_FILENAME); - $avatarPath = self::ASSETS_DIR . (file_exists(self::ASSETS_DIR . "$avatarBasename.jpg") ? "$avatarBasename.jpg" : "$avatarBasename.png"); + $dataPath = static::ASSETS_DIR . $filename; + // For each account json file there is an avatar image and a config json file with the same basename + $basename = pathinfo($filename, PATHINFO_FILENAME); + $avatarPath = static::ASSETS_DIR . (file_exists(static::ASSETS_DIR . "$basename.jpg") ? "$basename.jpg" : "$basename.png"); + $configPath = static::ASSETS_DIR . "$basename-config." . pathinfo($filename, PATHINFO_EXTENSION); return [ UUIDUtil::getUUID(), json_decode(file_get_contents($dataPath), true, 512, JSON_THROW_ON_ERROR), $avatarPath, + json_decode(file_get_contents($configPath), true, 512, JSON_THROW_ON_ERROR), ]; }, array_filter( - scandir(self::ASSETS_DIR), - fn (string $filename) => pathinfo($filename, PATHINFO_EXTENSION) === 'json', + scandir(static::ASSETS_DIR), + fn (string $filename) => pathinfo($filename, PATHINFO_EXTENSION) === 'json' && mb_strpos(pathinfo($filename, PATHINFO_FILENAME), 'config') === false, ), ); } @@ -101,10 +106,11 @@ class AccountMigratorTest extends TestCase { /** * @dataProvider dataImportExportAccount */ - public function testImportExportAccount(string $userId, array $importData, string $avatarPath): void { + public function testImportExportAccount(string $userId, array $importData, string $avatarPath, array $importConfig): void { $user = $this->userManager->createUser($userId, 'topsecretpassword'); $avatarExt = pathinfo($avatarPath, PATHINFO_EXTENSION); $exportData = $importData; + $exportConfig = $importConfig; // Verification status of email will be set to in progress on import so we set the export data to reflect that $exportData[IAccountManager::PROPERTY_EMAIL]['verified'] = IAccountManager::VERIFICATION_IN_PROGRESS; @@ -115,10 +121,16 @@ class AccountMigratorTest extends TestCase { ->willReturn(1); $this->importSource - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('getFileContents') - ->with($this->matchesRegularExpression(self::REGEX_ACCOUNT_FILE)) - ->willReturn(json_encode($importData)); + ->withConsecutive( + [$this->matchesRegularExpression(static::REGEX_ACCOUNT_FILE)], + [$this->matchesRegularExpression(static::REGEX_CONFIG_FILE)], + ) + ->willReturnOnConsecutiveCalls( + json_encode($importData), + json_encode($importConfig), + ); $this->importSource ->expects($this->once()) @@ -129,7 +141,7 @@ class AccountMigratorTest extends TestCase { $this->importSource ->expects($this->once()) ->method('getFileAsStream') - ->with($this->matchesRegularExpression(self::REGEX_AVATAR_FILE)) + ->with($this->matchesRegularExpression(static::REGEX_AVATAR_FILE)) ->willReturn(fopen($avatarPath, 'r')); $this->migrator->import($user, $this->importSource, $this->output); @@ -150,14 +162,17 @@ class AccountMigratorTest extends TestCase { } $this->exportDestination - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('addFileContents') - ->with($this->matchesRegularExpression(self::REGEX_ACCOUNT_FILE), json_encode($exportData)); + ->withConsecutive( + [$this->matchesRegularExpression(static::REGEX_ACCOUNT_FILE), new JsonMatches(json_encode($exportData))], + [$this->matchesRegularExpression(static::REGEX_CONFIG_FILE), new JsonMatches(json_encode($exportConfig))], + ); $this->exportDestination ->expects($this->once()) ->method('addFileAsStream') - ->with($this->matchesRegularExpression(self::REGEX_AVATAR_FILE), $this->isType('resource')); + ->with($this->matchesRegularExpression(static::REGEX_AVATAR_FILE), $this->isType('resource')); $this->migrator->export($user, $this->exportDestination, $this->output); } diff --git a/apps/settings/tests/UserMigration/assets/account-complex-config.json b/apps/settings/tests/UserMigration/assets/account-complex-config.json new file mode 100644 index 00000000000..8b85f523201 --- /dev/null +++ b/apps/settings/tests/UserMigration/assets/account-complex-config.json @@ -0,0 +1 @@ +{"address":{"visibility":"show_users_only"},"avatar":{"visibility":"show_users_only"},"biography":{"visibility":"show"},"displayname":{"visibility":"show"},"headline":{"visibility":"show"},"organisation":{"visibility":"show"},"role":{"visibility":"show"},"email":{"visibility":"hide"},"phone":{"visibility":"hide"},"twitter":{"visibility":"show_users_only"},"website":{"visibility":"show_users_only"},"talk":{"visibility":"show"}} \ No newline at end of file diff --git a/apps/settings/tests/UserMigration/assets/account-config.json b/apps/settings/tests/UserMigration/assets/account-config.json new file mode 100644 index 00000000000..51f4d387746 --- /dev/null +++ b/apps/settings/tests/UserMigration/assets/account-config.json @@ -0,0 +1 @@ +{"address":{"visibility":"show_users_only"},"avatar":{"visibility":"show"},"biography":{"visibility":"show"},"displayname":{"visibility":"show"},"headline":{"visibility":"show"},"organisation":{"visibility":"show"},"role":{"visibility":"show"},"email":{"visibility":"show_users_only"},"phone":{"visibility":"show_users_only"},"twitter":{"visibility":"show"},"website":{"visibility":"show"}} \ No newline at end of file -- cgit v1.2.3