aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Accounts
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Accounts')
-rw-r--r--tests/lib/Accounts/AccountManagerTest.php1061
-rw-r--r--tests/lib/Accounts/AccountPropertyCollectionTest.php192
-rw-r--r--tests/lib/Accounts/AccountPropertyTest.php131
-rw-r--r--tests/lib/Accounts/AccountTest.php155
-rw-r--r--tests/lib/Accounts/HooksTest.php140
5 files changed, 1679 insertions, 0 deletions
diff --git a/tests/lib/Accounts/AccountManagerTest.php b/tests/lib/Accounts/AccountManagerTest.php
new file mode 100644
index 00000000000..c625644bd96
--- /dev/null
+++ b/tests/lib/Accounts/AccountManagerTest.php
@@ -0,0 +1,1061 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\Accounts;
+
+use OC\Accounts\Account;
+use OC\Accounts\AccountManager;
+use OC\PhoneNumberUtil;
+use OCA\Settings\BackgroundJobs\VerifyUserData;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\UserUpdatedEvent;
+use OCP\BackgroundJob\IJobList;
+use OCP\Defaults;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Http\Client\IClient;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IPhoneNumberUtil;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\L10N\IFactory;
+use OCP\Mail\IMailer;
+use OCP\Security\ICrypto;
+use OCP\Security\VerificationToken\IVerificationToken;
+use OCP\Server;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+/**
+ * Class AccountManagerTest
+ *
+ * @group DB
+ * @package Test\Accounts
+ */
+class AccountManagerTest extends TestCase {
+
+ /** accounts table name */
+ private string $table = 'accounts';
+ private AccountManager $accountManager;
+ private IDBConnection $connection;
+ private IPhoneNumberUtil $phoneNumberUtil;
+
+ protected IVerificationToken&MockObject $verificationToken;
+ protected IMailer&MockObject $mailer;
+ protected ICrypto&MockObject $crypto;
+ protected IURLGenerator&MockObject $urlGenerator;
+ protected Defaults&MockObject $defaults;
+ protected IFactory&MockObject $l10nFactory;
+ protected IConfig&MockObject $config;
+ protected IEventDispatcher&MockObject $eventDispatcher;
+ protected IJobList&MockObject $jobList;
+ private LoggerInterface&MockObject $logger;
+ private IClientService&MockObject $clientService;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->connection = Server::get(IDBConnection::class);
+ $this->phoneNumberUtil = new PhoneNumberUtil();
+
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->jobList = $this->createMock(IJobList::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->verificationToken = $this->createMock(IVerificationToken::class);
+ $this->mailer = $this->createMock(IMailer::class);
+ $this->defaults = $this->createMock(Defaults::class);
+ $this->l10nFactory = $this->createMock(IFactory::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->crypto = $this->createMock(ICrypto::class);
+ $this->clientService = $this->createMock(IClientService::class);
+
+ $this->accountManager = new AccountManager(
+ $this->connection,
+ $this->config,
+ $this->eventDispatcher,
+ $this->jobList,
+ $this->logger,
+ $this->verificationToken,
+ $this->mailer,
+ $this->defaults,
+ $this->l10nFactory,
+ $this->urlGenerator,
+ $this->crypto,
+ $this->phoneNumberUtil,
+ $this->clientService,
+ );
+ }
+
+ protected function tearDown(): void {
+ parent::tearDown();
+ $query = $this->connection->getQueryBuilder();
+ $query->delete($this->table)->executeStatement();
+ }
+
+ protected function makeUser(string $uid, string $name, ?string $email = null): IUser {
+ $user = $this->createMock(IUser::class);
+ $user->expects($this->any())
+ ->method('getUid')
+ ->willReturn($uid);
+ $user->expects($this->any())
+ ->method('getDisplayName')
+ ->willReturn($name);
+ if ($email !== null) {
+ $user->expects($this->any())
+ ->method('getEMailAddress')
+ ->willReturn($email);
+ }
+
+ return $user;
+ }
+
+ protected function populateOrUpdate(): void {
+ $users = [
+ [
+ 'user' => $this->makeUser('j.doe', 'Jane Doe', 'jane.doe@acme.com'),
+ 'data' => [
+ [
+ 'name' => IAccountManager::PROPERTY_DISPLAYNAME,
+ 'value' => 'Jane Doe',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_EMAIL,
+ 'value' => 'jane.doe@acme.com',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_TWITTER,
+ 'value' => '@sometwitter',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_FEDIVERSE,
+ 'value' => '@someMastodon@mastodon.social',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_PHONE,
+ 'value' => '+491601231212',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ADDRESS,
+ 'value' => 'some street',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_WEBSITE,
+ 'value' => 'https://acme.com',
+ 'scope' => IAccountManager::SCOPE_PRIVATE
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ORGANISATION,
+ 'value' => 'Some organisation',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ROLE,
+ 'value' => 'Human',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_HEADLINE,
+ 'value' => 'Hi',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_BIOGRAPHY,
+ 'value' => 'Biography',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ ],
+ ],
+ [
+ 'user' => $this->makeUser('a.allison', 'Alice Allison', 'a.allison@example.org'),
+ 'data' => [
+ [
+ 'name' => IAccountManager::PROPERTY_DISPLAYNAME,
+ 'value' => 'Alice Allison',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_EMAIL,
+ 'value' => 'a.allison@example.org',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_TWITTER,
+ 'value' => '@a_alice',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_FEDIVERSE,
+ 'value' => '@a_alice@cool.social',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_PHONE,
+ 'value' => '+491602312121',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ADDRESS,
+ 'value' => 'Dundee Road 45',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_WEBSITE,
+ 'value' => 'https://example.org',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ORGANISATION,
+ 'value' => 'Another organisation',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ROLE,
+ 'value' => 'Alien',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_HEADLINE,
+ 'value' => 'Hello',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_BIOGRAPHY,
+ 'value' => 'Different biography',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ ],
+ ],
+ [
+ 'user' => $this->makeUser('b32c5a5b-1084-4380-8856-e5223b16de9f', 'Armel Oliseh', 'oliseh@example.com'),
+ 'data' => [
+ [
+ 'name' => IAccountManager::PROPERTY_DISPLAYNAME,
+ 'value' => 'Armel Oliseh',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_EMAIL,
+ 'value' => 'oliseh@example.com',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_TWITTER,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_FEDIVERSE,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_PHONE,
+ 'value' => '+491603121212',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ADDRESS,
+ 'value' => 'Sunflower Blvd. 77',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_WEBSITE,
+ 'value' => 'https://example.com',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ORGANISATION,
+ 'value' => 'Yet another organisation',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ROLE,
+ 'value' => 'Being',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_HEADLINE,
+ 'value' => 'This is a headline',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_BIOGRAPHY,
+ 'value' => 'Some long biography',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ ],
+ ],
+ [
+ 'user' => $this->makeUser('31b5316a-9b57-4b17-970a-315a4cbe73eb', 'K. Cheng', 'cheng@emca.com'),
+ 'data' => [
+ [
+ 'name' => IAccountManager::PROPERTY_DISPLAYNAME,
+ 'value' => 'K. Cheng',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_EMAIL,
+ 'value' => 'cheng@emca.com',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_TWITTER,
+ 'value' => '', '
+ scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_FEDIVERSE,
+ 'value' => '', '
+ scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_PHONE,
+ 'value' => '+71601212123',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ADDRESS,
+ 'value' => 'Pinapple Street 22',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_WEBSITE,
+ 'value' => 'https://emca.com',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ORGANISATION,
+ 'value' => 'Organisation A',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ROLE,
+ 'value' => 'Animal',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_HEADLINE,
+ 'value' => 'My headline',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_BIOGRAPHY,
+ 'value' => 'Short biography',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::COLLECTION_EMAIL,
+ 'value' => 'k.cheng@emca.com',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::COLLECTION_EMAIL,
+ 'value' => 'kai.cheng@emca.com',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ ],
+ ],
+ [
+ 'user' => $this->makeUser('goodpal@elpmaxe.org', 'Goodpal, Kim', 'goodpal@elpmaxe.org'),
+ 'data' => [
+ [
+ 'name' => IAccountManager::PROPERTY_DISPLAYNAME,
+ 'value' => 'Goodpal, Kim',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_EMAIL,
+ 'value' => 'goodpal@elpmaxe.org',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_TWITTER,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_FEDIVERSE,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_PHONE,
+ 'value' => '+71602121231',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ADDRESS,
+ 'value' => 'Octopus Ave 17',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_WEBSITE,
+ 'value' => 'https://elpmaxe.org',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ORGANISATION,
+ 'value' => 'Organisation B',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_ROLE,
+ 'value' => 'Organism',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_HEADLINE,
+ 'value' => 'Best headline',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_BIOGRAPHY,
+ 'value' => 'Autobiography',
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+ ],
+ ],
+ ];
+ $this->config->expects($this->exactly(count($users)))->method('getSystemValue')->with('account_manager.default_property_scope', [])->willReturn([]);
+ foreach ($users as $userInfo) {
+ $this->invokePrivate($this->accountManager, 'updateUser', [$userInfo['user'], $userInfo['data'], null, false]);
+ }
+ }
+
+ /**
+ * get a instance of the accountManager
+ *
+ * @return MockObject | AccountManager
+ */
+ public function getInstance(?array $mockedMethods = null) {
+ return $this->getMockBuilder(AccountManager::class)
+ ->setConstructorArgs([
+ $this->connection,
+ $this->config,
+ $this->eventDispatcher,
+ $this->jobList,
+ $this->logger,
+ $this->verificationToken,
+ $this->mailer,
+ $this->defaults,
+ $this->l10nFactory,
+ $this->urlGenerator,
+ $this->crypto,
+ $this->phoneNumberUtil,
+ $this->clientService,
+ ])
+ ->onlyMethods($mockedMethods)
+ ->getMock();
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTrueFalse')]
+ public function testUpdateUser(array $newData, array $oldData, bool $insertNew, bool $updateExisting): void {
+ $accountManager = $this->getInstance(['getUser', 'insertNewUser', 'updateExistingUser']);
+ /** @var IUser $user */
+ $user = $this->createMock(IUser::class);
+
+ if ($updateExisting) {
+ $accountManager->expects($this->once())->method('updateExistingUser')
+ ->with($user, $newData);
+ $accountManager->expects($this->never())->method('insertNewUser');
+ }
+ if ($insertNew) {
+ $accountManager->expects($this->once())->method('insertNewUser')
+ ->with($user, $newData);
+ $accountManager->expects($this->never())->method('updateExistingUser');
+ }
+
+ if (!$insertNew && !$updateExisting) {
+ $accountManager->expects($this->never())->method('updateExistingUser');
+ $accountManager->expects($this->never())->method('insertNewUser');
+ $this->eventDispatcher->expects($this->never())->method('dispatchTyped');
+ } else {
+ $this->eventDispatcher->expects($this->once())->method('dispatchTyped')
+ ->willReturnCallback(
+ function ($event) use ($user, $newData): void {
+ $this->assertInstanceOf(UserUpdatedEvent::class, $event);
+ $this->assertSame($user, $event->getUser());
+ $this->assertSame($newData, $event->getData());
+ }
+ );
+ }
+
+ $this->invokePrivate($accountManager, 'updateUser', [$user, $newData, $oldData]);
+ }
+
+ public static function dataTrueFalse(): array {
+ return [
+ #$newData | $oldData | $insertNew | $updateExisting
+ [['myProperty' => ['value' => 'newData']], ['myProperty' => ['value' => 'oldData']], false, true],
+ [['myProperty' => ['value' => 'oldData']], ['myProperty' => ['value' => 'oldData']], false, false]
+ ];
+ }
+
+ public function testAddMissingDefaults(): void {
+ $user = $this->createMock(IUser::class);
+
+ $this->config
+ ->expects($this->once())
+ ->method('getAppValue')
+ ->with('settings', 'profile_enabled_by_default', '1')
+ ->willReturn('1');
+
+ $input = [
+ [
+ 'name' => IAccountManager::PROPERTY_DISPLAYNAME,
+ 'value' => 'bob',
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+ [
+ 'name' => IAccountManager::PROPERTY_EMAIL,
+ 'value' => 'bob@bob.bob',
+ ],
+ ];
+
+ $expected = [
+ [
+ 'name' => IAccountManager::PROPERTY_DISPLAYNAME,
+ 'value' => 'bob',
+ 'scope' => IAccountManager::SCOPE_FEDERATED,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_EMAIL,
+ 'value' => 'bob@bob.bob',
+ 'scope' => IAccountManager::SCOPE_FEDERATED,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_ADDRESS,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_WEBSITE,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_AVATAR,
+ 'scope' => IAccountManager::SCOPE_FEDERATED
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_PHONE,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_TWITTER,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_BLUESKY,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_FEDIVERSE,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_ORGANISATION,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_ROLE,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_HEADLINE,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_BIOGRAPHY,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_BIRTHDATE,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_PROFILE_ENABLED,
+ 'value' => '1',
+ ],
+
+ [
+ 'name' => IAccountManager::PROPERTY_PRONOUNS,
+ 'value' => '',
+ 'scope' => IAccountManager::SCOPE_FEDERATED,
+ ],
+ ];
+ $this->config->expects($this->once())->method('getSystemValue')->with('account_manager.default_property_scope', [])->willReturn([]);
+
+ $defaultUserRecord = $this->invokePrivate($this->accountManager, 'buildDefaultUserRecord', [$user]);
+ $result = $this->invokePrivate($this->accountManager, 'addMissingDefaultValues', [$input, $defaultUserRecord]);
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testGetAccount(): void {
+ $accountManager = $this->getInstance(['getUser']);
+ /** @var IUser $user */
+ $user = $this->createMock(IUser::class);
+
+ $data = [
+ [
+ 'value' => '@twitterhandle',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ 'name' => IAccountManager::PROPERTY_TWITTER,
+ ],
+ [
+ 'value' => '@mastohandle@mastodon.social',
+ 'scope' => IAccountManager::SCOPE_LOCAL,
+ 'verified' => IAccountManager::NOT_VERIFIED,
+ 'name' => IAccountManager::PROPERTY_FEDIVERSE,
+ ],
+ [
+ 'value' => 'test@example.com',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED,
+ 'verified' => IAccountManager::VERIFICATION_IN_PROGRESS,
+ 'name' => IAccountManager::PROPERTY_EMAIL,
+ ],
+ [
+ 'value' => 'https://example.com',
+ 'scope' => IAccountManager::SCOPE_FEDERATED,
+ 'verified' => IAccountManager::VERIFIED,
+ 'name' => IAccountManager::PROPERTY_WEBSITE,
+ ],
+ ];
+ $expected = new Account($user);
+ $expected->setProperty(IAccountManager::PROPERTY_TWITTER, '@twitterhandle', IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
+ $expected->setProperty(IAccountManager::PROPERTY_FEDIVERSE, '@mastohandle@mastodon.social', IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
+ $expected->setProperty(IAccountManager::PROPERTY_EMAIL, 'test@example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFICATION_IN_PROGRESS);
+ $expected->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_FEDERATED, IAccountManager::VERIFIED);
+
+ $accountManager->expects($this->once())
+ ->method('getUser')
+ ->willReturn($data);
+ $this->assertEquals($expected, $accountManager->getAccount($user));
+ }
+
+ public static function dataParsePhoneNumber(): array {
+ return [
+ ['0711 / 25 24 28-90', 'DE', '+4971125242890'],
+ ['0711 / 25 24 28-90', '', null],
+ ['+49 711 / 25 24 28-90', '', '+4971125242890'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataParsePhoneNumber')]
+ public function testSanitizePhoneNumberOnUpdateAccount(string $phoneInput, string $defaultRegion, ?string $phoneNumber): void {
+ $this->config->method('getSystemValueString')
+ ->willReturn($defaultRegion);
+
+ $user = $this->createMock(IUser::class);
+ $account = new Account($user);
+ $account->setProperty(IAccountManager::PROPERTY_PHONE, $phoneInput, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
+ $manager = $this->getInstance(['getUser', 'updateUser']);
+ $manager->method('getUser')
+ ->with($user, false)
+ ->willReturn([]);
+ $manager->expects($phoneNumber === null ? self::never() : self::once())
+ ->method('updateUser');
+
+ if ($phoneNumber === null) {
+ $this->expectException(\InvalidArgumentException::class);
+ }
+
+ $manager->updateAccount($account);
+
+ if ($phoneNumber !== null) {
+ self::assertEquals($phoneNumber, $account->getProperty(IAccountManager::PROPERTY_PHONE)->getValue());
+ }
+ }
+
+ public static function dataSanitizeOnUpdate(): array {
+ return [
+ [IAccountManager::PROPERTY_WEBSITE, 'https://nextcloud.com', 'https://nextcloud.com'],
+ [IAccountManager::PROPERTY_WEBSITE, 'http://nextcloud.com', 'http://nextcloud.com'],
+ [IAccountManager::PROPERTY_WEBSITE, 'ftp://nextcloud.com', null],
+ [IAccountManager::PROPERTY_WEBSITE, '//nextcloud.com/', null],
+ [IAccountManager::PROPERTY_WEBSITE, 'https:///?query', null],
+
+ [IAccountManager::PROPERTY_TWITTER, '@nextcloud', 'nextcloud'],
+ [IAccountManager::PROPERTY_TWITTER, '_nextcloud', '_nextcloud'],
+ [IAccountManager::PROPERTY_TWITTER, 'FooB4r', 'FooB4r'],
+ [IAccountManager::PROPERTY_TWITTER, 'X', null],
+ [IAccountManager::PROPERTY_TWITTER, 'next.cloud', null],
+ [IAccountManager::PROPERTY_TWITTER, 'ab/cd.zip', null],
+ [IAccountManager::PROPERTY_TWITTER, 'tooLongForTwitterAndX', null],
+
+ [IAccountManager::PROPERTY_FEDIVERSE, 'nextcloud@mastodon.social', 'nextcloud@mastodon.social'],
+ [IAccountManager::PROPERTY_FEDIVERSE, '@nextcloud@mastodon.xyz', 'nextcloud@mastodon.xyz'],
+ [IAccountManager::PROPERTY_FEDIVERSE, 'l33t.h4x0r@sub.localhost.local', 'l33t.h4x0r@sub.localhost.local'],
+ [IAccountManager::PROPERTY_FEDIVERSE, 'invalid/name@mastodon.social', null],
+ [IAccountManager::PROPERTY_FEDIVERSE, 'name@evil.host/malware.exe', null],
+ [IAccountManager::PROPERTY_FEDIVERSE, '@is-it-a-host-or-name', null],
+ [IAccountManager::PROPERTY_FEDIVERSE, 'only-a-name', null],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataSanitizeOnUpdate')]
+ public function testSanitizingOnUpdateAccount(string $property, string $input, ?string $output): void {
+
+ if ($property === IAccountManager::PROPERTY_FEDIVERSE) {
+ // We do not test the server response here we do this in the `testSanitizingFediverseServer`
+ $this->config
+ ->method('getSystemValueBool')
+ ->with('has_internet_connection', true)
+ ->willReturn(false);
+ }
+
+ $user = $this->createMock(IUser::class);
+
+ $account = new Account($user);
+ $account->setProperty($property, $input, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
+
+ $manager = $this->getInstance(['getUser', 'updateUser']);
+ $manager->method('getUser')
+ ->with($user, false)
+ ->willReturn([]);
+ $manager->expects($output === null ? self::never() : self::once())
+ ->method('updateUser');
+
+ if ($output === null) {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage($property);
+ }
+
+ $manager->updateAccount($account);
+
+ if ($output !== null) {
+ self::assertEquals($output, $account->getProperty($property)->getValue());
+ }
+ }
+
+ public static function dataSanitizeFediverseServer(): array {
+ return [
+ 'no internet' => [
+ '@foo@example.com',
+ 'foo@example.com',
+ false,
+ null,
+ ],
+ 'no internet - no at' => [
+ 'foo@example.com',
+ 'foo@example.com',
+ false,
+ null,
+ ],
+ 'valid response' => [
+ '@foo@example.com',
+ 'foo@example.com',
+ true,
+ json_encode([
+ 'subject' => 'acct:foo@example.com',
+ 'links' => [
+ [
+ 'rel' => 'self',
+ 'type' => 'application/activity+json',
+ 'href' => 'https://example.com/users/foo',
+ ],
+ ],
+ ]),
+ ],
+ 'valid response - no at' => [
+ 'foo@example.com',
+ 'foo@example.com',
+ true,
+ json_encode([
+ 'subject' => 'acct:foo@example.com',
+ 'links' => [
+ [
+ 'rel' => 'self',
+ 'type' => 'application/activity+json',
+ 'href' => 'https://example.com/users/foo',
+ ],
+ ],
+ ]),
+ ],
+ // failures
+ 'invalid response' => [
+ '@foo@example.com',
+ null,
+ true,
+ json_encode([
+ 'subject' => 'acct:foo@example.com',
+ 'links' => [],
+ ]),
+ ],
+ 'no response' => [
+ '@foo@example.com',
+ null,
+ true,
+ null,
+ ],
+ 'wrong user' => [
+ '@foo@example.com',
+ null,
+ true,
+ json_encode([
+ 'links' => [],
+ ]),
+ ],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataSanitizeFediverseServer')]
+ public function testSanitizingFediverseServer(string $input, ?string $output, bool $hasInternet, ?string $serverResponse): void {
+ $this->config->expects(self::once())
+ ->method('getSystemValueBool')
+ ->with('has_internet_connection', true)
+ ->willReturn($hasInternet);
+
+ if ($hasInternet) {
+ $client = $this->createMock(IClient::class);
+ if ($serverResponse !== null) {
+ $response = $this->createMock(IResponse::class);
+ $response->method('getBody')
+ ->willReturn($serverResponse);
+ $client->expects(self::once())
+ ->method('get')
+ ->with('https://example.com/.well-known/webfinger?resource=acct:foo@example.com')
+ ->willReturn($response);
+ } else {
+ $client->expects(self::once())
+ ->method('get')
+ ->with('https://example.com/.well-known/webfinger?resource=acct:foo@example.com')
+ ->willThrowException(new \Exception('404'));
+ }
+
+ $this->clientService
+ ->expects(self::once())
+ ->method('newClient')
+ ->willReturn($client);
+ } else {
+ $this->clientService
+ ->expects(self::never())
+ ->method('newClient');
+ }
+
+ $user = $this->createMock(IUser::class);
+ $account = new Account($user);
+ $account->setProperty(IAccountManager::PROPERTY_FEDIVERSE, $input, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
+
+ $manager = $this->getInstance(['getUser', 'updateUser']);
+ $manager->method('getUser')
+ ->with($user, false)
+ ->willReturn([]);
+ $manager->expects($output === null ? self::never() : self::once())
+ ->method('updateUser');
+
+ if ($output === null) {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage(IAccountManager::PROPERTY_FEDIVERSE);
+ }
+
+ $manager->updateAccount($account);
+
+ if ($output !== null) {
+ self::assertEquals($output, $account->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue());
+ }
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('searchDataProvider')]
+ public function testSearchUsers(string $property, array $values, array $expected): void {
+ $this->populateOrUpdate();
+
+ $matchedUsers = $this->accountManager->searchUsers($property, $values);
+ foreach ($expected as $expectedEntry) {
+ $this->assertContains($expectedEntry, $matchedUsers);
+ }
+ if (empty($expected)) {
+ $this->assertEmpty($matchedUsers);
+ }
+ }
+
+ public static function searchDataProvider(): array {
+ return [
+ [ #0 Search for an existing name
+ IAccountManager::PROPERTY_DISPLAYNAME,
+ ['Jane Doe'],
+ ['Jane Doe' => 'j.doe']
+ ],
+ [ #1 Search for part of a name (no result)
+ IAccountManager::PROPERTY_DISPLAYNAME,
+ ['Jane'],
+ []
+ ],
+ [ #2 Search for part of a name (no result, test wildcard)
+ IAccountManager::PROPERTY_DISPLAYNAME,
+ ['Jane%'],
+ []
+ ],
+ [ #3 Search for phone
+ IAccountManager::PROPERTY_PHONE,
+ ['+491603121212'],
+ ['+491603121212' => 'b32c5a5b-1084-4380-8856-e5223b16de9f'],
+ ],
+ [ #4 Search for twitter handles
+ IAccountManager::PROPERTY_TWITTER,
+ ['@sometwitter', '@a_alice', '@unseen'],
+ ['@sometwitter' => 'j.doe', '@a_alice' => 'a.allison'],
+ ],
+ [ #5 Search for email
+ IAccountManager::PROPERTY_EMAIL,
+ ['cheng@emca.com'],
+ ['cheng@emca.com' => '31b5316a-9b57-4b17-970a-315a4cbe73eb'],
+ ],
+ [ #6 Search for email by additional email
+ IAccountManager::PROPERTY_EMAIL,
+ ['kai.cheng@emca.com'],
+ ['kai.cheng@emca.com' => '31b5316a-9b57-4b17-970a-315a4cbe73eb'],
+ ],
+ [ #7 Search for additional email
+ IAccountManager::COLLECTION_EMAIL,
+ ['kai.cheng@emca.com', 'cheng@emca.com'],
+ ['kai.cheng@emca.com' => '31b5316a-9b57-4b17-970a-315a4cbe73eb'],
+ ],
+ [ #8 Search for email by additional email (two valid search values, but the same user)
+ IAccountManager::PROPERTY_EMAIL,
+ ['kai.cheng@emca.com', 'cheng@emca.com'],
+ [
+ 'kai.cheng@emca.com' => '31b5316a-9b57-4b17-970a-315a4cbe73eb',
+ ],
+ ],
+ ];
+ }
+
+ public static function dataCheckEmailVerification(): array {
+ return [
+ [['steve', 'Steve Smith', 'steve@steve.steve'], null],
+ [['emma', 'Emma Morales', 'emma@emma.com'], 'emma@morales.com'],
+ [['sarah@web.org', 'Sarah Foster', 'sarah@web.org'], null],
+ [['cole@web.org', 'Cole Harrison', 'cole@web.org'], 'cole@example.com'],
+ [['8d29e358-cf69-4849-bbf9-28076c0b908b', 'Alice McPherson', 'alice@example.com'], 'alice@mcpherson.com'],
+ [['11da2744-3f4d-4c17-8c13-4c057a379237', 'James Loranger', 'james@example.com'], ''],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataCheckEmailVerification')]
+ public function testCheckEmailVerification(array $userData, ?string $newEmail): void {
+ $user = $this->makeUser(...$userData);
+ // Once because of getAccount, once because of getUser
+ $this->config->expects($this->exactly(2))->method('getSystemValue')->with('account_manager.default_property_scope', [])->willReturn([]);
+ $account = $this->accountManager->getAccount($user);
+ $emailUpdated = false;
+
+ if (!empty($newEmail)) {
+ $account->getProperty(IAccountManager::PROPERTY_EMAIL)->setValue($newEmail);
+ $emailUpdated = true;
+ }
+
+ if ($emailUpdated) {
+ $this->jobList->expects($this->once())
+ ->method('add')
+ ->with(VerifyUserData::class);
+ } else {
+ $this->jobList->expects($this->never())
+ ->method('add')
+ ->with(VerifyUserData::class);
+ }
+
+ /** @var array $oldData */
+ $oldData = $this->invokePrivate($this->accountManager, 'getUser', [$user, false]);
+ $this->invokePrivate($this->accountManager, 'checkEmailVerification', [$account, $oldData]);
+ }
+
+ public static function dataSetDefaultPropertyScopes(): array {
+ return [
+ [
+ [],
+ [
+ IAccountManager::PROPERTY_DISPLAYNAME => IAccountManager::SCOPE_FEDERATED,
+ IAccountManager::PROPERTY_ADDRESS => IAccountManager::SCOPE_LOCAL,
+ IAccountManager::PROPERTY_EMAIL => IAccountManager::SCOPE_FEDERATED,
+ IAccountManager::PROPERTY_ROLE => IAccountManager::SCOPE_LOCAL,
+ ]
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_DISPLAYNAME => IAccountManager::SCOPE_FEDERATED,
+ IAccountManager::PROPERTY_EMAIL => IAccountManager::SCOPE_LOCAL,
+ IAccountManager::PROPERTY_ROLE => IAccountManager::SCOPE_PRIVATE,
+ ], [
+ IAccountManager::PROPERTY_DISPLAYNAME => IAccountManager::SCOPE_FEDERATED,
+ IAccountManager::PROPERTY_EMAIL => IAccountManager::SCOPE_LOCAL,
+ IAccountManager::PROPERTY_ROLE => IAccountManager::SCOPE_PRIVATE,
+ ]
+ ],
+ [
+ [
+ IAccountManager::PROPERTY_ADDRESS => 'invalid scope',
+ 'invalid property' => IAccountManager::SCOPE_LOCAL,
+ IAccountManager::PROPERTY_ROLE => IAccountManager::SCOPE_PRIVATE,
+ ],
+ [
+ IAccountManager::PROPERTY_ADDRESS => IAccountManager::SCOPE_LOCAL,
+ IAccountManager::PROPERTY_EMAIL => IAccountManager::SCOPE_FEDERATED,
+ IAccountManager::PROPERTY_ROLE => IAccountManager::SCOPE_PRIVATE,
+ ]
+ ],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataSetDefaultPropertyScopes')]
+ public function testSetDefaultPropertyScopes(array $propertyScopes, array $expectedResultScopes): void {
+ $user = $this->makeUser('steve', 'Steve Smith', 'steve@steve.steve');
+ $this->config->expects($this->once())->method('getSystemValue')->with('account_manager.default_property_scope', [])->willReturn($propertyScopes);
+
+ $result = $this->invokePrivate($this->accountManager, 'buildDefaultUserRecord', [$user]);
+ $resultProperties = array_column($result, 'name');
+
+ $this->assertEmpty(array_diff($resultProperties, IAccountManager::ALLOWED_PROPERTIES), 'Building default user record returned non-allowed properties');
+ foreach ($expectedResultScopes as $expectedResultScopeKey => $expectedResultScopeValue) {
+ $resultScope = $result[array_search($expectedResultScopeKey, $resultProperties)]['scope'];
+ $this->assertEquals($expectedResultScopeValue, $resultScope, "The result scope doesn't follow the value set into the config or defaults correctly.");
+ }
+ }
+}
diff --git a/tests/lib/Accounts/AccountPropertyCollectionTest.php b/tests/lib/Accounts/AccountPropertyCollectionTest.php
new file mode 100644
index 00000000000..fa4db10d3d9
--- /dev/null
+++ b/tests/lib/Accounts/AccountPropertyCollectionTest.php
@@ -0,0 +1,192 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace lib\Accounts;
+
+use InvalidArgumentException;
+use OC\Accounts\AccountPropertyCollection;
+use OCP\Accounts\IAccountProperty;
+use OCP\Accounts\IAccountPropertyCollection;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class AccountPropertyCollectionTest extends TestCase {
+ /** @var IAccountPropertyCollection */
+ protected $collection;
+
+ protected const COLLECTION_NAME = 'my_multivalue_property';
+
+ public function setUp(): void {
+ parent::setUp();
+
+ $this->collection = new AccountPropertyCollection(self::COLLECTION_NAME);
+ }
+
+ /**
+ * @return IAccountProperty|MockObject
+ */
+ protected function makePropertyMock(string $propertyName): MockObject {
+ $mock = $this->createMock(IAccountProperty::class);
+ $mock->expects($this->any())
+ ->method('getName')
+ ->willReturn($propertyName);
+
+ return $mock;
+ }
+
+ public function testSetAndGetProperties(): void {
+ $propsBefore = $this->collection->getProperties();
+ $this->assertIsArray($propsBefore);
+ $this->assertEmpty($propsBefore);
+
+ $props = [
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ ];
+
+ $this->collection->setProperties($props);
+ $propsAfter = $this->collection->getProperties();
+ $this->assertIsArray($propsAfter);
+ $this->assertCount(count($props), $propsAfter);
+ }
+
+ public function testSetPropertiesMixedInvalid(): void {
+ $props = [
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock('sneaky_property'),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ ];
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->collection->setProperties($props);
+ }
+
+ public function testAddProperty(): void {
+ $props = [
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ ];
+ $this->collection->setProperties($props);
+
+ $additionalProperty = $this->makePropertyMock(self::COLLECTION_NAME);
+ $this->collection->addProperty($additionalProperty);
+
+ $propsAfter = $this->collection->getProperties();
+ $this->assertCount(count($props) + 1, $propsAfter);
+ $this->assertNotFalse(array_search($additionalProperty, $propsAfter, true));
+ }
+
+ public function testAddPropertyInvalid(): void {
+ $props = [
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ ];
+ $this->collection->setProperties($props);
+
+ $additionalProperty = $this->makePropertyMock('sneaky_property');
+ $exceptionThrown = false;
+ try {
+ $this->collection->addProperty($additionalProperty);
+ } catch (\InvalidArgumentException $e) {
+ $exceptionThrown = true;
+ } finally {
+ $propsAfter = $this->collection->getProperties();
+ $this->assertCount(count($props), $propsAfter);
+ $this->assertFalse(array_search($additionalProperty, $propsAfter, true));
+ $this->assertTrue($exceptionThrown);
+ }
+ }
+
+ public function testRemoveProperty(): void {
+ $additionalProperty = $this->makePropertyMock(self::COLLECTION_NAME);
+ $props = [
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $additionalProperty,
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ ];
+ $this->collection->setProperties($props);
+
+ $propsBefore = $this->collection->getProperties();
+ $this->collection->removeProperty($additionalProperty);
+ $propsAfter = $this->collection->getProperties();
+
+ $this->assertTrue(count($propsBefore) > count($propsAfter));
+ $this->assertCount(count($propsBefore) - 1, $propsAfter);
+ $this->assertFalse(array_search($additionalProperty, $propsAfter, true));
+ }
+
+ public function testRemovePropertyNotFound(): void {
+ $additionalProperty = $this->makePropertyMock(self::COLLECTION_NAME);
+ $props = [
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ ];
+ $this->collection->setProperties($props);
+
+ $propsBefore = $this->collection->getProperties();
+ $this->collection->removeProperty($additionalProperty);
+ $propsAfter = $this->collection->getProperties();
+
+ // no errors, gently
+ $this->assertCount(count($propsBefore), $propsAfter);
+ }
+
+ public function testRemovePropertyByValue(): void {
+ $additionalProperty = $this->makePropertyMock(self::COLLECTION_NAME);
+ $additionalProperty->expects($this->any())
+ ->method('getValue')
+ ->willReturn('Lorem ipsum');
+
+ $additionalPropertyTwo = clone $additionalProperty;
+
+ $props = [
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $additionalProperty,
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $additionalPropertyTwo
+ ];
+ $this->collection->setProperties($props);
+
+ $propsBefore = $this->collection->getProperties();
+ $this->collection->removePropertyByValue('Lorem ipsum');
+ $propsAfter = $this->collection->getProperties();
+
+ $this->assertTrue(count($propsBefore) > count($propsAfter));
+ $this->assertCount(count($propsBefore) - 2, $propsAfter);
+ $this->assertFalse(array_search($additionalProperty, $propsAfter, true));
+ $this->assertFalse(array_search($additionalPropertyTwo, $propsAfter, true));
+ }
+
+ public function testRemovePropertyByValueNotFound(): void {
+ $additionalProperty = $this->makePropertyMock(self::COLLECTION_NAME);
+ $additionalProperty->expects($this->any())
+ ->method('getValue')
+ ->willReturn('Lorem ipsum');
+
+ $props = [
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ $this->makePropertyMock(self::COLLECTION_NAME),
+ ];
+ $this->collection->setProperties($props);
+
+ $propsBefore = $this->collection->getProperties();
+ $this->collection->removePropertyByValue('Lorem ipsum');
+ $propsAfter = $this->collection->getProperties();
+
+ // no errors, gently
+ $this->assertCount(count($propsBefore), $propsAfter);
+ }
+}
diff --git a/tests/lib/Accounts/AccountPropertyTest.php b/tests/lib/Accounts/AccountPropertyTest.php
new file mode 100644
index 00000000000..b92e45176a3
--- /dev/null
+++ b/tests/lib/Accounts/AccountPropertyTest.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Accounts;
+
+use OC\Accounts\AccountProperty;
+use OCP\Accounts\IAccountManager;
+use Test\TestCase;
+
+/**
+ * Class AccountPropertyTest
+ *
+ * @package Test\Accounts
+ */
+class AccountPropertyTest extends TestCase {
+ public function testConstructor(): void {
+ $accountProperty = new AccountProperty(
+ IAccountManager::PROPERTY_WEBSITE,
+ 'https://example.com',
+ IAccountManager::SCOPE_PUBLISHED,
+ IAccountManager::VERIFIED,
+ ''
+ );
+ $this->assertEquals(IAccountManager::PROPERTY_WEBSITE, $accountProperty->getName());
+ $this->assertEquals('https://example.com', $accountProperty->getValue());
+ $this->assertEquals(IAccountManager::SCOPE_PUBLISHED, $accountProperty->getScope());
+ $this->assertEquals(IAccountManager::VERIFIED, $accountProperty->getVerified());
+ }
+
+ public function testSetValue(): void {
+ $accountProperty = new AccountProperty(
+ IAccountManager::PROPERTY_WEBSITE,
+ 'https://example.com',
+ IAccountManager::SCOPE_PUBLISHED,
+ IAccountManager::VERIFIED,
+ ''
+ );
+ $actualReturn = $accountProperty->setValue('https://example.org');
+ $this->assertEquals('https://example.org', $accountProperty->getValue());
+ $this->assertEquals('https://example.org', $actualReturn->getValue());
+ }
+
+ public function testSetScope(): void {
+ $accountProperty = new AccountProperty(
+ IAccountManager::PROPERTY_WEBSITE,
+ 'https://example.com',
+ IAccountManager::SCOPE_PUBLISHED,
+ IAccountManager::VERIFIED,
+ ''
+ );
+ $actualReturn = $accountProperty->setScope(IAccountManager::SCOPE_LOCAL);
+ $this->assertEquals(IAccountManager::SCOPE_LOCAL, $accountProperty->getScope());
+ $this->assertEquals(IAccountManager::SCOPE_LOCAL, $actualReturn->getScope());
+ }
+
+ public static function scopesProvider(): array {
+ return [
+ // current values
+ [IAccountManager::SCOPE_PRIVATE, IAccountManager::SCOPE_PRIVATE],
+ [IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_LOCAL],
+ [IAccountManager::SCOPE_FEDERATED, IAccountManager::SCOPE_FEDERATED],
+ [IAccountManager::SCOPE_PUBLISHED, IAccountManager::SCOPE_PUBLISHED],
+ // invalid values
+ ['unknown', null],
+ ['v2-unknown', null],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('scopesProvider')]
+ public function testSetScopeMapping(string $storedScope, ?string $returnedScope): void {
+ if ($returnedScope === null) {
+ $this->expectException(\InvalidArgumentException::class);
+ }
+ $accountProperty = new AccountProperty(
+ IAccountManager::PROPERTY_WEBSITE,
+ 'https://example.com',
+ $storedScope,
+ IAccountManager::VERIFIED,
+ ''
+ );
+ $this->assertEquals($returnedScope, $accountProperty->getScope());
+ }
+
+ public function testSetVerified(): void {
+ $accountProperty = new AccountProperty(
+ IAccountManager::PROPERTY_WEBSITE,
+ 'https://example.com',
+ IAccountManager::SCOPE_PUBLISHED,
+ IAccountManager::VERIFIED,
+ ''
+ );
+ $actualReturn = $accountProperty->setVerified(IAccountManager::NOT_VERIFIED);
+ $this->assertEquals(IAccountManager::NOT_VERIFIED, $accountProperty->getVerified());
+ $this->assertEquals(IAccountManager::NOT_VERIFIED, $actualReturn->getVerified());
+ }
+
+ public function testSetVerificationData(): void {
+ $accountProperty = new AccountProperty(
+ IAccountManager::PROPERTY_WEBSITE,
+ 'https://example.com',
+ IAccountManager::SCOPE_PUBLISHED,
+ IAccountManager::VERIFIED,
+ ''
+ );
+ $token = uniqid();
+ $actualReturn = $accountProperty->setVerificationData($token);
+ $this->assertEquals($token, $accountProperty->getVerificationData());
+ $this->assertEquals($token, $actualReturn->getVerificationData());
+ }
+
+ public function testJsonSerialize(): void {
+ $accountProperty = new AccountProperty(
+ IAccountManager::PROPERTY_WEBSITE,
+ 'https://example.com',
+ IAccountManager::SCOPE_PUBLISHED,
+ IAccountManager::VERIFIED,
+ '60a7a633b74af',
+ );
+ $this->assertEquals([
+ 'name' => IAccountManager::PROPERTY_WEBSITE,
+ 'value' => 'https://example.com',
+ 'scope' => IAccountManager::SCOPE_PUBLISHED,
+ 'verified' => IAccountManager::VERIFIED,
+ 'verificationData' => '60a7a633b74af'
+ ], $accountProperty->jsonSerialize());
+ }
+}
diff --git a/tests/lib/Accounts/AccountTest.php b/tests/lib/Accounts/AccountTest.php
new file mode 100644
index 00000000000..ddba7c559c0
--- /dev/null
+++ b/tests/lib/Accounts/AccountTest.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Accounts;
+
+use OC\Accounts\Account;
+use OC\Accounts\AccountProperty;
+use OC\Accounts\AccountPropertyCollection;
+use OCP\Accounts\IAccountManager;
+use OCP\IUser;
+use Test\TestCase;
+
+/**
+ * Class AccountTest
+ *
+ * @package Test\Accounts
+ */
+class AccountTest extends TestCase {
+ public function testConstructor(): void {
+ $user = $this->createMock(IUser::class);
+ $account = new Account($user);
+ $this->assertEquals($user, $account->getUser());
+ }
+
+ public function testSetProperty(): void {
+ $user = $this->createMock(IUser::class);
+ $property = new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, '');
+ $account = new Account($user);
+ $account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
+ $this->assertEquals($property, $account->getProperty(IAccountManager::PROPERTY_WEBSITE));
+ }
+
+ public function testGetAndGetAllProperties(): void {
+ $user = $this->createMock(IUser::class);
+ $properties = [
+ IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED, '')
+ ];
+ $account = new Account($user);
+ $account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
+ $account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED);
+
+ $col = new AccountPropertyCollection(IAccountManager::COLLECTION_EMAIL);
+ $additionalProperty = new AccountProperty($col->getName(), 'second@example.org', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, '');
+ $col->addProperty($additionalProperty);
+ $account->setPropertyCollection($col);
+
+ $this->assertEquals($properties, $account->getProperties());
+ $properties[] = $additionalProperty;
+ $this->assertEquals(array_values($properties), \iterator_to_array($account->getAllProperties()));
+ }
+
+ public function testSetAllPropertiesFromJson(): void {
+ $user = $this->createMock(IUser::class);
+ $properties = [
+ IAccountManager::PROPERTY_DISPLAYNAME => new AccountProperty(IAccountManager::PROPERTY_DISPLAYNAME, 'Steve', IAccountManager::SCOPE_FEDERATED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_ADDRESS => new AccountProperty(IAccountManager::PROPERTY_ADDRESS, '123 Acorn Avenue', IAccountManager::SCOPE_FEDERATED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://www.example.org', IAccountManager::SCOPE_FEDERATED, IAccountManager::VERIFIED, ''),
+ IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'steve@earth.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFICATION_IN_PROGRESS, ''),
+ IAccountManager::PROPERTY_AVATAR => new AccountProperty(IAccountManager::PROPERTY_AVATAR, '', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_PHONE => new AccountProperty(IAccountManager::PROPERTY_PHONE, '+358407991028', IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_TWITTER => new AccountProperty(IAccountManager::PROPERTY_TWITTER, 'therealsteve', IAccountManager::SCOPE_PRIVATE, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_BLUESKY => new AccountProperty(IAccountManager::PROPERTY_BLUESKY, 'therealsteve.bsky.social', IAccountManager::SCOPE_PRIVATE, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_ORGANISATION => new AccountProperty(IAccountManager::PROPERTY_ORGANISATION, 'Steve Incorporated', IAccountManager::SCOPE_FEDERATED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_ROLE => new AccountProperty(IAccountManager::PROPERTY_ROLE, 'Founder', IAccountManager::SCOPE_FEDERATED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_HEADLINE => new AccountProperty(IAccountManager::PROPERTY_HEADLINE, 'I am Steve', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_BIOGRAPHY => new AccountProperty(IAccountManager::PROPERTY_BIOGRAPHY, 'Steve is the best', IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_PROFILE_ENABLED => new AccountProperty(IAccountManager::PROPERTY_PROFILE_ENABLED, '1', IAccountManager::SCOPE_FEDERATED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::COLLECTION_EMAIL => [
+ new AccountProperty(IAccountManager::COLLECTION_EMAIL, 'steve@mars.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, ''),
+ new AccountProperty(IAccountManager::COLLECTION_EMAIL, 'steve@neptune.com', IAccountManager::SCOPE_FEDERATED, IAccountManager::NOT_VERIFIED, ''),
+ ],
+ ];
+ $account = new Account($user);
+ $account->setAllPropertiesFromJson(json_decode(json_encode($properties), true));
+ $this->assertEquals($properties, $account->jsonSerialize());
+ }
+
+ public function testGetFilteredProperties(): void {
+ $user = $this->createMock(IUser::class);
+ $properties = [
+ IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED, ''),
+ IAccountManager::PROPERTY_PHONE => new AccountProperty(IAccountManager::PROPERTY_PHONE, '123456', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED, ''),
+ ];
+ $account = new Account($user);
+ $account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
+ $account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED);
+ $account->setProperty(IAccountManager::PROPERTY_PHONE, '123456', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED);
+
+ $col = new AccountPropertyCollection(IAccountManager::COLLECTION_EMAIL);
+ $additionalProperty1 = new AccountProperty($col->getName(), 'second@example.org', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, '');
+ $additionalProperty2 = new AccountProperty($col->getName(), 'third@example.org', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED, '');
+ $col->addProperty($additionalProperty1);
+ $col->addProperty($additionalProperty2);
+ $account->setPropertyCollection($col);
+
+
+ $this->assertEquals(
+ [
+ IAccountManager::PROPERTY_WEBSITE => $properties[IAccountManager::PROPERTY_WEBSITE],
+ IAccountManager::PROPERTY_PHONE => $properties[IAccountManager::PROPERTY_PHONE],
+ IAccountManager::COLLECTION_EMAIL . '#0' => $additionalProperty1,
+ IAccountManager::COLLECTION_EMAIL . '#1' => $additionalProperty2,
+ ],
+ $account->getFilteredProperties(IAccountManager::SCOPE_PUBLISHED)
+ );
+ $this->assertEquals(
+ [
+ IAccountManager::PROPERTY_EMAIL => $properties[IAccountManager::PROPERTY_EMAIL],
+ IAccountManager::PROPERTY_PHONE => $properties[IAccountManager::PROPERTY_PHONE],
+ IAccountManager::COLLECTION_EMAIL . '#0' => $additionalProperty2,
+ ],
+ $account->getFilteredProperties(null, IAccountManager::VERIFIED)
+ );
+ $this->assertEquals(
+ [
+ IAccountManager::PROPERTY_PHONE => $properties[IAccountManager::PROPERTY_PHONE],
+ IAccountManager::COLLECTION_EMAIL . '#0' => $additionalProperty2,
+ ],
+ $account->getFilteredProperties(IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED),
+ );
+ }
+
+ public function testJsonSerialize(): void {
+ $user = $this->createMock(IUser::class);
+ $properties = [
+ IAccountManager::PROPERTY_WEBSITE => new AccountProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED, ''),
+ IAccountManager::PROPERTY_EMAIL => new AccountProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED, ''),
+ IAccountManager::COLLECTION_EMAIL => [
+ new AccountProperty(IAccountManager::COLLECTION_EMAIL, 'apple@orange.com', IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED, ''),
+ new AccountProperty(IAccountManager::COLLECTION_EMAIL, 'banana@orange.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFICATION_IN_PROGRESS, ''),
+ new AccountProperty(IAccountManager::COLLECTION_EMAIL, 'kiwi@watermelon.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED, ''),
+ ],
+ ];
+
+ $account = new Account($user);
+ $account->setProperty(IAccountManager::PROPERTY_WEBSITE, 'https://example.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::NOT_VERIFIED);
+ $account->setProperty(IAccountManager::PROPERTY_EMAIL, 'user@example.com', IAccountManager::SCOPE_LOCAL, IAccountManager::VERIFIED);
+
+ $col = new AccountPropertyCollection(IAccountManager::COLLECTION_EMAIL);
+ $col->setProperties([
+ new AccountProperty($col->getName(), 'apple@orange.com', IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED, ''),
+ new AccountProperty($col->getName(), 'banana@orange.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFICATION_IN_PROGRESS, ''),
+ new AccountProperty($col->getName(), 'kiwi@watermelon.com', IAccountManager::SCOPE_PUBLISHED, IAccountManager::VERIFIED, ''),
+ ]);
+ $account->setPropertyCollection($col);
+
+ $this->assertEquals($properties, $account->jsonSerialize());
+ }
+}
diff --git a/tests/lib/Accounts/HooksTest.php b/tests/lib/Accounts/HooksTest.php
new file mode 100644
index 00000000000..622fb3c7461
--- /dev/null
+++ b/tests/lib/Accounts/HooksTest.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Accounts;
+
+use OC\Accounts\AccountManager;
+use OC\Accounts\Hooks;
+use OCP\Accounts\IAccount;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\IAccountProperty;
+use OCP\IUser;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+/**
+ * Class HooksTest
+ *
+ * @package Test\Accounts
+ * @group DB
+ */
+class HooksTest extends TestCase {
+ /** @var LoggerInterface|MockObject */
+ private $logger;
+
+ /** @var AccountManager|MockObject */
+ private $accountManager;
+
+ /** @var Hooks */
+ private $hooks;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->accountManager = $this->getMockBuilder(AccountManager::class)
+ ->disableOriginalConstructor()->getMock();
+
+ $this->hooks = new Hooks($this->logger, $this->accountManager);
+ }
+
+ /**
+ *
+ * @param $params
+ * @param $data
+ * @param $setEmail
+ * @param $setDisplayName
+ * @param $error
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestChangeUserHook')]
+ public function testChangeUserHook($params, $data, $setEmail, $setDisplayName, $error): void {
+ if ($error) {
+ $this->accountManager->expects($this->never())->method('updateAccount');
+ } else {
+ $account = $this->createMock(IAccount::class);
+ $this->accountManager->expects($this->atLeastOnce())->method('getAccount')->willReturn($account);
+ if ($setEmail) {
+ $property = $this->createMock(IAccountProperty::class);
+ $property->expects($this->atLeastOnce())
+ ->method('getValue')
+ ->willReturn($data[IAccountManager::PROPERTY_EMAIL]['value']);
+ $property->expects($this->atLeastOnce())
+ ->method('setValue')
+ ->with($params['value']);
+
+ $account->expects($this->atLeastOnce())
+ ->method('getProperty')
+ ->with(IAccountManager::PROPERTY_EMAIL)
+ ->willReturn($property);
+
+ $this->accountManager->expects($this->once())
+ ->method('updateAccount')
+ ->with($account);
+ } elseif ($setDisplayName) {
+ $property = $this->createMock(IAccountProperty::class);
+ $property->expects($this->atLeastOnce())
+ ->method('getValue')
+ ->willReturn($data[IAccountManager::PROPERTY_DISPLAYNAME]['value']);
+ $property->expects($this->atLeastOnce())
+ ->method('setValue')
+ ->with($params['value']);
+
+ $account->expects($this->atLeastOnce())
+ ->method('getProperty')
+ ->with(IAccountManager::PROPERTY_DISPLAYNAME)
+ ->willReturn($property);
+
+ $this->accountManager->expects($this->once())
+ ->method('updateAccount')
+ ->with($account);
+ } else {
+ $this->accountManager->expects($this->never())->method('updateAccount');
+ }
+ }
+
+ $params['user'] = $this->createMock(IUser::class);
+ $this->hooks->changeUserHook($params['user'], $params['feature'], $params['value']);
+ }
+
+ public static function dataTestChangeUserHook(): array {
+ return [
+ [
+ ['feature' => '', 'value' => ''],
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => ''],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => '']
+ ],
+ false, false, true
+ ],
+ [
+ ['feature' => 'foo', 'value' => 'bar'],
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
+ ],
+ false, false, false
+ ],
+ [
+ ['feature' => 'eMailAddress', 'value' => 'newMail@example.com'],
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
+ ],
+ true, false, false
+ ],
+ [
+ ['feature' => 'displayName', 'value' => 'newDisplayName'],
+ [
+ IAccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
+ IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
+ ],
+ false, true, false
+ ],
+ ];
+ }
+}