-
Notifications
You must be signed in to change notification settings - Fork 317
feat(UserMigration): Overwork migration to include all settings (tags) #12785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/user-migration-text-blocks
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,156 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declare(strict_types=1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace OCA\Mail\UserMigration\Service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use JsonException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use OCA\Mail\Db\Tag; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use OCA\Mail\Db\TagMapper; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use OCA\Mail\UserMigration\MailAccountMigrator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use OCP\IL10N; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use OCP\IUser; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use OCP\UserMigration\IExportDestination; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use OCP\UserMigration\IImportSource; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use OCP\UserMigration\UserMigrationException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Symfony\Component\Console\Output\OutputInterface; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class TagsMigrationService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public const TAGS_FILE = MailAccountMigrator::EXPORT_ROOT . '/tags.json'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function __construct( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly TagMapper $tagMapper, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly IL10N $l10n, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Export all tags the user currently uses. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @throws UserMigrationException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function exportTags(IUser $user, IExportDestination $exportDestination, OutputInterface $output): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $output->writeln( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->l10n->t('Exporting tags for user %s', [$user->getUID()]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OutputInterface::VERBOSITY_VERBOSE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $tags = $this->tagMapper->getAllTagsForUser($user->getUID()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $exportDestination->addFileContents(self::TAGS_FILE, json_encode($tags, JSON_THROW_ON_ERROR)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (JsonException|UserMigrationException $exception) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new UserMigrationException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Failed to export tags for user {$user->getUID()}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| previous: $exception | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Import all tags the user used on export. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @throws \OCP\UserMigration\UserMigrationException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @throws \OCP\DB\Exception | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function importTags(IUser $user, IImportSource $importSource, OutputInterface $output): array { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $output->writeln( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->l10n->t('Importing tags for user %s', [$user->getUID()]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OutputInterface::VERBOSITY_VERBOSE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $tagsFileContent = $importSource->getFileContents(self::TAGS_FILE); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (UserMigrationException) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $output->writeln( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->l10n->t('Tags for user %s not found. Continue...', [$user->getUID()]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OutputInterface::VERBOSITY_VERBOSE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $tags = json_decode($tagsFileContent, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
DerDreschner marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->validateTags($tags); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $newTags = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($tags as $tag) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $newTag = new Tag(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $newTag->setUserId($user->getUID()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $newTag->setDisplayName($tag['displayName']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $newTag->setImapLabel($tag['imapLabel']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $newTag->setColor($tag['color']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $newTag->setIsDefaultTag($tag['isDefaultTag']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $newTags[$tag['id']] = $this->tagMapper->insert($newTag)->getId(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
DerDreschner marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $newTags; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @throws \OCP\DB\Exception | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function deleteAllTags(IUser $user, OutputInterface $output): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $output->writeln( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->l10n->t('Delete existing tags for user %s', [$user->getUID()]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OutputInterface::VERBOSITY_VERBOSE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->tagMapper->deleteAll($user->getUID()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Validate the parsed tags to ensure they | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * have the expected structure and types. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @throws UserMigrationException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private function validateTags(mixed $tags): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $tagsArrayIsValid = is_array($tags) && array_is_list($tags); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!$tagsArrayIsValid) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new UserMigrationException('Invalid tags export structure'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We automatically import tags from imap once first seen, hence we should also be forgiving here. Something isn't right with the tags export, go ahead, dont abort the import.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, will change it as the comment in my other PR. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($tags as $tag) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $tagArrayIsValid = is_array($tag); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $idIsValid = $tagArrayIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && array_key_exists('id', $tag) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && is_int($tag['id']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $displayNameIsValid = $tagArrayIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && array_key_exists('displayName', $tag) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && is_string($tag['displayName']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $imapLabelIsValid = $tagArrayIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && array_key_exists('imapLabel', $tag) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && is_string($tag['imapLabel']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $colorIsValid = $tagArrayIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && array_key_exists('color', $tag) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && is_string($tag['color']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $isDefaultTagIsValid = $tagArrayIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && array_key_exists('isDefaultTag', $tag) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| && is_bool($tag['isDefaultTag']); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| !$idIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || !$displayNameIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || !$imapLabelIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || !$colorIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || !$isDefaultTagIsValid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new UserMigrationException('Invalid tag entry'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+119
to
+152
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new UserMigrationException('Invalid tags export structure'); | |
| } | |
| foreach ($tags as $tag) { | |
| $tagArrayIsValid = is_array($tag); | |
| $displayNameIsValid = $tagArrayIsValid | |
| && array_key_exists('displayName', $tag) | |
| && is_string($tag['displayName']); | |
| $imapLabelIsValid = $tagArrayIsValid | |
| && array_key_exists('imapLabel', $tag) | |
| && is_string($tag['imapLabel']); | |
| $colorIsValid = $tagArrayIsValid | |
| && array_key_exists('color', $tag) | |
| && is_string($tag['color']); | |
| $isDefaultTagIsValid = $tagArrayIsValid | |
| && array_key_exists('isDefaultTag', $tag) | |
| && is_bool($tag['isDefaultTag']); | |
| if (!$displayNameIsValid || !$imapLabelIsValid || !$colorIsValid || !$isDefaultTagIsValid) { | |
| throw new UserMigrationException('Invalid tag entry'); | |
| throw new UserMigrationException('Invalid tags export structure: expected a JSON list of tag entries'); | |
| } | |
| foreach ($tags as $index => $tag) { | |
| if (!is_array($tag)) { | |
| throw new UserMigrationException(sprintf('Invalid tag entry at index %d: expected an object/associative array', $index)); | |
| } | |
| if (!array_key_exists('id', $tag) || (!is_int($tag['id']) && !is_string($tag['id']))) { | |
| throw new UserMigrationException(sprintf('Invalid tag entry at index %d: missing or invalid "id"', $index)); | |
| } | |
| if (!array_key_exists('displayName', $tag) || !is_string($tag['displayName'])) { | |
| throw new UserMigrationException(sprintf('Invalid tag entry at index %d: missing or invalid "displayName"', $index)); | |
| } | |
| if (!array_key_exists('imapLabel', $tag) || !is_string($tag['imapLabel'])) { | |
| throw new UserMigrationException(sprintf('Invalid tag entry at index %d: missing or invalid "imapLabel"', $index)); | |
| } | |
| if (!array_key_exists('color', $tag) || !is_string($tag['color'])) { | |
| throw new UserMigrationException(sprintf('Invalid tag entry at index %d: missing or invalid "color"', $index)); | |
| } | |
| if (!array_key_exists('isDefaultTag', $tag) || !is_bool($tag['isDefaultTag'])) { | |
| throw new UserMigrationException(sprintf('Invalid tag entry at index %d: missing or invalid "isDefaultTag"', $index)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhm, that's a bit too much in my opinion
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace Unit\UserMigration\Service; | ||
|
|
||
| use ChristophWurst\Nextcloud\Testing\ServiceMockObject; | ||
| use ChristophWurst\Nextcloud\Testing\TestCase; | ||
| use OCA\Mail\Db\Tag; | ||
| use OCA\Mail\UserMigration\Service\TagsMigrationService; | ||
| use OCP\IUser; | ||
| use OCP\UserMigration\IExportDestination; | ||
| use OCP\UserMigration\IImportSource; | ||
| use OCP\UserMigration\UserMigrationException; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
|
|
||
| class TagsMigrationServiceTest extends TestCase { | ||
| private const USER_ID = '123'; | ||
| private OutputInterface $output; | ||
| private IUser $user; | ||
| private IExportDestination $exportDestination; | ||
| private IImportSource $importSource; | ||
| private ServiceMockObject $serviceMock; | ||
| private TagsMigrationService $migrationService; | ||
|
|
||
| protected function setUp(): void { | ||
| parent::setUp(); | ||
|
|
||
| $this->output = $this->createMock(OutputInterface::class); | ||
| $this->exportDestination = $this->createMock(IExportDestination::class); | ||
| $this->importSource = $this->createMock(IImportSource::class); | ||
|
|
||
| $this->user = $this->createMock(IUser::class); | ||
| $this->user->method('getUID')->willReturn(self::USER_ID); | ||
|
|
||
| $this->serviceMock = $this->createServiceMock(TagsMigrationService::class); | ||
| $this->migrationService = $this->serviceMock->getService(); | ||
| } | ||
|
|
||
| public function testExportsMultipleTags(): void { | ||
| $tagsList = [$this->getTestingTag(), $this->getSuccessfulTag()]; | ||
| $this->exportDestination->expects(self::once())->method('addFileContents')->with(TagsMigrationService::TAGS_FILE, json_encode($tagsList)); | ||
|
|
||
| $this->serviceMock->getParameter('tagMapper')->method('getAllTagsForUser')->with(self::USER_ID)->willReturn($tagsList); | ||
| $this->migrationService->exportTags($this->user, $this->exportDestination, $this->output); | ||
| } | ||
|
DerDreschner marked this conversation as resolved.
|
||
|
|
||
| public function testExportsNoTags(): void { | ||
| $tagsList = []; | ||
|
|
||
| $this->serviceMock->getParameter('tagMapper')->method('getAllTagsForUser')->with(self::USER_ID)->willReturn($tagsList); | ||
| $this->exportDestination->expects(self::once())->method('addFileContents')->with(TagsMigrationService::TAGS_FILE, json_encode($tagsList)); | ||
|
|
||
| $this->migrationService->exportTags($this->user, $this->exportDestination, $this->output); | ||
| } | ||
|
|
||
| public function testImportMultipleTags(): void { | ||
| $testingTag = $this->getTestingTag(); | ||
| $successfulTag = $this->getSuccessfulTag(); | ||
| $tagsList = [$testingTag, $successfulTag]; | ||
| $this->importSource->expects(self::once())->method('getFileContents')->with(TagsMigrationService::TAGS_FILE)->willReturn(json_encode($tagsList)); | ||
|
|
||
| $this->serviceMock->getParameter('tagMapper')->expects(self::exactly(2))->method('insert')->with(self::callback(function (Tag $writtenTag) use ($testingTag, $successfulTag): bool { | ||
| if ($this->userIdMatches($writtenTag) | ||
| && $this->displayNameMatches($writtenTag) | ||
| && $this->imapLabelMatches($writtenTag) | ||
| && $this->colorMatches($writtenTag) | ||
| && $this->isDefaultTagMatches($writtenTag) | ||
| ) { | ||
| return true; | ||
| } else { | ||
| return false; | ||
| } | ||
| }))->willReturnCallback(function ($test) { | ||
| $test->setId(random_int(10, 999)); | ||
| return $test; | ||
| }); | ||
|
|
||
| $mappedTags = $this->migrationService->importTags($this->user, $this->importSource, $this->output); | ||
|
|
||
| $this->assertCount(2, $mappedTags); | ||
| $this->assertArrayHasKey($testingTag->getId(), $mappedTags); | ||
| $this->assertIsInt($mappedTags[$testingTag->getId()]); | ||
| $this->assertArrayHasKey($successfulTag->getId(), $mappedTags); | ||
| $this->assertIsInt($mappedTags[$successfulTag->getId()]); | ||
| } | ||
|
|
||
| public function testImportNoTags(): void { | ||
| $tagsList = []; | ||
| $this->importSource->expects(self::once())->method('getFileContents')->with(TagsMigrationService::TAGS_FILE)->willReturn(json_encode($tagsList)); | ||
| $this->serviceMock->getParameter('tagMapper')->expects(self::never())->method('insert'); | ||
| $mappedTags = $this->migrationService->importTags($this->user, $this->importSource, $this->output); | ||
| $this->assertCount(0, $mappedTags); | ||
| } | ||
|
|
||
| public function testImportNoFileIsBeingIgnored(): void { | ||
| $this->importSource->expects(self::once())->method('getFileContents')->with(TagsMigrationService::TAGS_FILE)->willThrowException(new UserMigrationException()); | ||
| $this->serviceMock->getParameter('tagMapper')->expects(self::never())->method('insert'); | ||
| $mappedTags = $this->migrationService->importTags($this->user, $this->importSource, $this->output); | ||
| $this->assertCount(0, $mappedTags); | ||
| } | ||
|
|
||
| private function getTestingTag(): Tag { | ||
| $testingTag = new Tag(); | ||
|
|
||
| $testingTag->setId(1); | ||
| $testingTag->setUserId(self::USER_ID); | ||
| $testingTag->setImapLabel('testing'); | ||
| $testingTag->setDisplayName('Testing'); | ||
| $testingTag->setColor('#fff'); | ||
| $testingTag->setIsDefaultTag(false); | ||
|
|
||
| return $testingTag; | ||
| } | ||
|
|
||
| private function getSuccessfulTag(): Tag { | ||
| $successfulTag = new Tag(); | ||
|
|
||
| $successfulTag->setId(2); | ||
| $successfulTag->setUserId(self::USER_ID); | ||
| $successfulTag->setImapLabel('successful'); | ||
| $successfulTag->setDisplayName('Successful'); | ||
| $successfulTag->setColor('#fff'); | ||
| $successfulTag->setIsDefaultTag(false); | ||
|
|
||
| return $successfulTag; | ||
| } | ||
|
|
||
| private function userIdMatches(Tag $tag): bool { | ||
| return $tag->getUserId() === self::USER_ID; | ||
| } | ||
|
|
||
| private function displayNameMatches(Tag $tag): bool { | ||
| $testing = $this->getTestingTag(); | ||
| $successful = $this->getSuccessfulTag(); | ||
|
|
||
| return $tag->getDisplayName() === $testing->getDisplayName() || $tag->getDisplayName() === $successful->getDisplayName(); | ||
| } | ||
|
|
||
| private function imapLabelMatches(Tag $tag): bool { | ||
| $testing = $this->getTestingTag(); | ||
| $successful = $this->getSuccessfulTag(); | ||
|
|
||
| return $tag->getImapLabel() === $testing->getImapLabel() || $tag->getImapLabel() === $successful->getImapLabel(); | ||
| } | ||
|
|
||
| private function colorMatches(Tag $tag): bool { | ||
| $testing = $this->getTestingTag(); | ||
| $successful = $this->getSuccessfulTag(); | ||
|
|
||
| return $tag->getColor() === $testing->getColor() || $tag->getColor() === $successful->getColor(); | ||
| } | ||
|
|
||
| private function isDefaultTagMatches(Tag $tag): bool { | ||
| $testing = $this->getTestingTag(); | ||
| $successful = $this->getSuccessfulTag(); | ||
|
|
||
| return $tag->getIsDefaultTag() === $testing->getIsDefaultTag() || $tag->getIsDefaultTag() === $successful->getIsDefaultTag(); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.