diff --git a/lib/private/Sync/User/UserSyncDBBackend.php b/lib/private/Sync/User/UserSyncDBBackend.php index 5f168f567a5f..9b4c7e1a22bb 100644 --- a/lib/private/Sync/User/UserSyncDBBackend.php +++ b/lib/private/Sync/User/UserSyncDBBackend.php @@ -35,6 +35,20 @@ public function __construct(Database $dbUserBackend) { $this->dbUserBackend = $dbUserBackend; } + /** + * This is intended to be used just for unit tests + */ + public function getPointer(): int { + return $this->pointer; + } + + /** + * This is intended to be used just for unit tests + */ + public function getCachedUserData(): array { + return $this->cachedUserData; + } + /** * @inheritDoc */ @@ -106,7 +120,11 @@ public function getSyncingUser(string $id): ?SyncingUser { * @inheritDoc */ public function userCount(): ?int { - return $this->dbUserBackend->countUsers(); + $nUsers = $this->dbUserBackend->countUsers(); + if ($nUsers === false) { + return null; + } + return $nUsers; } /** diff --git a/lib/private/Sync/User/UserSyncer.php b/lib/private/Sync/User/UserSyncer.php index 4608a4450477..0965fb241a9b 100644 --- a/lib/private/Sync/User/UserSyncer.php +++ b/lib/private/Sync/User/UserSyncer.php @@ -72,10 +72,13 @@ public function registerBackend(IUserSyncBackend $userSyncBackend) { * * Using "missingAction" as option won't do anything here. It will be ignored. * + * This method won't take into account whether the backends (including the + * requested ones) are registered or not. + * * Custom options: * - "backends" => "back1,back2,back3" - * Only those backends will be counted, assuming they're registered. The - * rest of the backends will be ignored. + * Only those backends will be counted. The rest of the backends will + * be ignored. */ public function localItemCount($opts = []): ?int { $backends = $this->extractBackendsFromOpts($opts); @@ -104,8 +107,9 @@ public function localItemCount($opts = []): ?int { * - "missingAction" => "remove" or "disable". * The action to do if the account is missing in the backend * - "backends" => "back1,back2,back3" - * Only those backends will be synced, assuming they're registered. The - * rest of the backends will be ignored. + * Only those backends will be checked. The rest of the backends will + * be ignored. If the backends aren't registered, an error will be + * send through the callback. */ public function check($callback, $opts = []) { $backends = $this->extractBackendsFromOpts($opts); @@ -140,7 +144,7 @@ public function check($callback, $opts = []) { $targetUserSyncBackend = $backendToUserSync[$targetBackend] ?? null; if ($targetUserSyncBackend === null) { - // send and exception to the callback + // backend not registered -> send and exception to the callback $callback(new SyncException("{$a->getUserId()}, backend {$targetBackend} is not found"), ISyncer::CHECK_STATE_ERROR); return; } diff --git a/tests/lib/Sync/SyncManagerTest.php b/tests/lib/Sync/SyncManagerTest.php new file mode 100644 index 000000000000..d4c2687c8a85 --- /dev/null +++ b/tests/lib/Sync/SyncManagerTest.php @@ -0,0 +1,92 @@ + + * + */ + +namespace Tests\Sync; + +use OC\Sync\SyncManager; +use OCP\Sync\ISyncer; +use OCP\Sync\User\IUserSyncer; +use Test\TestCase; + +class SyncManagerTest extends TestCase { + /** @var SyncManager */ + private $syncManager; + + protected function setUp(): void { + $this->syncManager = new SyncManager(); + } + + public function testRegisterSyncer() { + $syncer = $this->createMock(ISyncer::class); + $this->assertTrue($this->syncManager->registerSyncer('mySyncer', $syncer)); + } + + public function testRegisterSyncerTwice() { + $syncer = $this->createMock(ISyncer::class); + $syncer2 = $this->createMock(ISyncer::class); + $this->assertTrue($this->syncManager->registerSyncer('mySyncer', $syncer)); + $this->assertFalse($this->syncManager->registerSyncer('mySyncer', $syncer2)); + } + + public function testOverwriteSyncer() { + $syncer = $this->createMock(ISyncer::class); + $syncer2 = $this->createMock(ISyncer::class); + $this->assertTrue($this->syncManager->registerSyncer('mySyncer', $syncer)); + $this->assertTrue($this->syncManager->overwriteSyncer('mySyncer', $syncer2)); + } + + public function testOverwriteSyncerNotOverwrite() { + $syncer = $this->createMock(ISyncer::class); + $this->assertFalse($this->syncManager->overwriteSyncer('mySyncer', $syncer)); + } + + public function testGetSyncerMissing() { + $this->assertNull($this->syncManager->getSyncer('mySyncer')); + } + + public function testGetSyncerSetAndGet() { + $syncer = $this->createMock(ISyncer::class); + $this->assertTrue($this->syncManager->registerSyncer('mySyncer', $syncer)); + $this->assertSame($syncer, $this->syncManager->getSyncer('mySyncer')); + } + + public function testGetUserSyncerMissing() { + $this->assertNull($this->syncManager->getUserSyncer()); + } + + public function testGetUserSyncerSetAndGet() { + $userSyncer = $this->createMock(IUserSyncer::class); + $this->assertTrue($this->syncManager->registerSyncer('user', $userSyncer)); + $this->assertSame($userSyncer, $this->syncManager->getUserSyncer()); + } + + public function testGetUserSyncerSetAndGetOverwrite() { + $userSyncer = $this->createMock(IUserSyncer::class); + $userSyncer2 = $this->createMock(IUserSyncer::class); + $this->assertTrue($this->syncManager->registerSyncer('user', $userSyncer)); + $this->assertTrue($this->syncManager->overwriteSyncer('user', $userSyncer2)); + $this->assertSame($userSyncer2, $this->syncManager->getUserSyncer()); + } + + public function testGetUserSyncerNotUserSyncer() { + $syncer = $this->createMock(ISyncer::class); + $this->assertTrue($this->syncManager->registerSyncer('user', $syncer)); + $this->assertNull($this->syncManager->getUserSyncer()); + } +} diff --git a/tests/lib/Sync/User/UserSyncDBBackendTest.php b/tests/lib/Sync/User/UserSyncDBBackendTest.php new file mode 100644 index 000000000000..5c9b9fd3c797 --- /dev/null +++ b/tests/lib/Sync/User/UserSyncDBBackendTest.php @@ -0,0 +1,253 @@ + + * + */ + +namespace Tests\Sync\User; + +use OC\Sync\User\UserSyncDBBackend; +use OC\User\Database; +use OCP\UserInterface; +use OCP\Sync\User\IUserSyncBackend; +use OCP\Sync\User\SyncingUser; +use Test\TestCase; + +class UserSyncDBBackendTest extends TestCase { + /** @var UserSyncDBBackend */ + private $userSyncDBBackend; + /** @var Database */ + private $database; + + protected function setUp(): void { + $this->database = $this->createMock(Database::class); + $this->userSyncDBBackend = new UserSyncDBBackend($this->database); + } + + public function testResetPointer() { + $this->userSyncDBBackend->resetPointer(); + $this->assertSame(0, $this->userSyncDBBackend->getPointer()); + $this->assertEquals(['min' => 0, 'max' => 0, 'last' => false], $this->userSyncDBBackend->getCachedUserData()); + } + + public function testGetNextUser() { + $userData = [ + ['uid' => 'user1', 'displayname' => ''], + ['uid' => 'user2', 'displayname' => 'awesome'], + ]; + + $this->database->expects($this->once()) + ->method('getUsersData') + ->willReturn($userData); + $this->database->method('getHome') + ->will($this->returnCallback(function ($uid) { + return "/home/{$uid}"; + })); + + $expectedUser = new SyncingUser('user1'); + $expectedUser->setDisplayName('user1'); + $expectedUser->setHome('/home/user1'); + + $nextUser = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser->getAllData(), $nextUser->getAllData()); + } + + public function testGetNextUser2x() { + $userData = [ + ['uid' => 'user1', 'displayname' => 'display1'], + ['uid' => 'user2', 'displayname' => 'awesome'], + ]; + + $this->database->expects($this->once()) + ->method('getUsersData') + ->willReturn($userData); + $this->database->method('getHome') + ->will($this->returnCallback(function ($uid) { + return "/home/{$uid}"; + })); + + $expectedUser = new SyncingUser('user1'); + $expectedUser->setDisplayName('display1'); + $expectedUser->setHome('/home/user1'); + $expectedUser2 = new SyncingUser('user2'); + $expectedUser2->setDisplayName('awesome'); + $expectedUser2->setHome('/home/user2'); + + $nextUser = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser->getAllData(), $nextUser->getAllData()); + $nextUser2 = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser2->getAllData(), $nextUser2->getAllData()); + } + + public function testGetNextUser3x() { + $userData = [ + ['uid' => 'user1', 'displayname' => 'display1'], + ['uid' => 'user2', 'displayname' => 'awesome'], + ]; + + $this->database->expects($this->exactly(2)) + ->method('getUsersData') + ->will($this->onConsecutiveCalls($userData, [])); + $this->database->method('getHome') + ->will($this->returnCallback(function ($uid) { + return "/home/{$uid}"; + })); + + $expectedUser = new SyncingUser('user1'); + $expectedUser->setDisplayName('display1'); + $expectedUser->setHome('/home/user1'); + $expectedUser2 = new SyncingUser('user2'); + $expectedUser2->setDisplayName('awesome'); + $expectedUser2->setHome('/home/user2'); + + $nextUser = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser->getAllData(), $nextUser->getAllData()); + $nextUser2 = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser2->getAllData(), $nextUser2->getAllData()); + + $this->assertNull($this->userSyncDBBackend->getNextUser()); + } + + public function testGetNextUser3xMoreData() { + $userData = [ + ['uid' => 'user1', 'displayname' => 'display1'], + ['uid' => 'user2', 'displayname' => 'awesome'], + ]; + $userData2 = [ + ['uid' => 'user3', 'displayname' => 'blob'], + ['uid' => 'user4', 'displayname' => 'limeJuice'], + ]; + + $this->database->expects($this->exactly(2)) + ->method('getUsersData') + ->will($this->onConsecutiveCalls($userData, $userData2)); + $this->database->method('getHome') + ->will($this->returnCallback(function ($uid) { + return "/home/{$uid}"; + })); + + $expectedUser = new SyncingUser('user1'); + $expectedUser->setDisplayName('display1'); + $expectedUser->setHome('/home/user1'); + $expectedUser2 = new SyncingUser('user2'); + $expectedUser2->setDisplayName('awesome'); + $expectedUser2->setHome('/home/user2'); + $expectedUser3 = new SyncingUser('user3'); + $expectedUser3->setDisplayName('blob'); + $expectedUser3->setHome('/home/user3'); + + $nextUser = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser->getAllData(), $nextUser->getAllData()); + $nextUser2 = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser2->getAllData(), $nextUser2->getAllData()); + $nextUser3 = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser3->getAllData(), $nextUser3->getAllData()); + } + + public function testGetNextUser4x() { + $userData = [ + ['uid' => 'user1', 'displayname' => 'display1'], + ['uid' => 'user2', 'displayname' => 'awesome'], + ]; + + $this->database->expects($this->exactly(2)) + ->method('getUsersData') + ->will($this->onConsecutiveCalls($userData, [])); + $this->database->method('getHome') + ->will($this->returnCallback(function ($uid) { + return "/home/{$uid}"; + })); + + $expectedUser = new SyncingUser('user1'); + $expectedUser->setDisplayName('display1'); + $expectedUser->setHome('/home/user1'); + $expectedUser2 = new SyncingUser('user2'); + $expectedUser2->setDisplayName('awesome'); + $expectedUser2->setHome('/home/user2'); + + $nextUser = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser->getAllData(), $nextUser->getAllData()); + $nextUser2 = $this->userSyncDBBackend->getNextUser(); + $this->assertEquals($expectedUser2->getAllData(), $nextUser2->getAllData()); + + $this->assertNull($this->userSyncDBBackend->getNextUser()); // would need to fetch data + $this->assertNull($this->userSyncDBBackend->getNextUser()); // no need to fetch data + } + + public function testGetSyncingUser() { + $userData = ['uid' => 'user1', 'displayname' => 'display1']; + + $this->database->method('getUserData') + ->with('user1') + ->willReturn($userData); + $this->database->method('getHome') + ->will($this->returnCallback(function ($uid) { + return "/home/{$uid}"; + })); + + $expectedUser = new SyncingUser('user1'); + $expectedUser->setDisplayName('display1'); + $expectedUser->setHome('/home/user1'); + + $this->assertEquals($expectedUser->getAllData(), $this->userSyncDBBackend->getSyncingUser('user1')->getAllData()); + } + + public function testGetSyncingUserNoDisplayname() { + $userData = ['uid' => 'user1', 'displayname' => '']; + + $this->database->method('getUserData') + ->with('user1') + ->willReturn($userData); + $this->database->method('getHome') + ->will($this->returnCallback(function ($uid) { + return "/home/{$uid}"; + })); + + $expectedUser = new SyncingUser('user1'); + $expectedUser->setDisplayName('user1'); + $expectedUser->setHome('/home/user1'); + + $this->assertEquals($expectedUser->getAllData(), $this->userSyncDBBackend->getSyncingUser('user1')->getAllData()); + } + + public function testGetSyncingUserMissing() { + $this->database->method('getUserData') + ->with('user1') + ->willReturn(false); + $this->database->expects($this->never()) + ->method('getHome'); + + $this->assertNull($this->userSyncDBBackend->getSyncingUser('user1')); + } + + public function testUserCount() { + $this->database->expects($this->once()) + ->method('countUsers') + ->willReturn(77); + $this->assertSame(77, $this->userSyncDBBackend->userCount()); + } + + public function testUserCountFailure() { + $this->database->expects($this->once()) + ->method('countUsers') + ->willReturn(false); + $this->assertNull($this->userSyncDBBackend->userCount()); + } + + public function testGetUserInterface() { + $this->assertSame($this->database, $this->userSyncDBBackend->getUserInterface()); + } +} diff --git a/tests/lib/Sync/User/UserSyncerTest.php b/tests/lib/Sync/User/UserSyncerTest.php new file mode 100644 index 000000000000..031335ae040a --- /dev/null +++ b/tests/lib/Sync/User/UserSyncerTest.php @@ -0,0 +1,936 @@ + + * + */ + +namespace Tests\Sync; + +use OC\Sync\User\UserSyncer; +use OC\User\Account; +use OC\User\AccountMapper; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUserManager; +use OCP\IUser; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\Sync\User\IUserSyncBackend; +use OCP\Sync\User\SyncBackendUserFailedException; +use OCP\Sync\User\SyncBackendBrokenException; +use OCP\Sync\User\SyncingUser; +use OCP\Sync\ISyncer; +use OCP\UserInterface; +use Test\TestCase; + +class UserSyncerTest extends TestCase { + /** @var IUserManager */ + private $userManager; + /** @var AccountMapper */ + private $mapper; + /** @var IConfig */ + private $config; + /** @var ILogger */ + private $logger; + /** @var UserSyncer */ + private $userSyncer; + + protected function setUp(): void { + $this->mapper = $this->createMock(AccountMapper::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->config = $this->createMock(IConfig::class); + $this->logger = $this->createMock(ILogger::class); + + $this->userSyncer = new UserSyncer($this->userManager, $this->mapper, $this->config, $this->logger); + } + + public function testLocalItemCount() { + $this->mapper->method('getUserCount')->willReturn(97); + $this->assertSame(97, $this->userSyncer->localItemCount()); + } + + public function testLocalItemCountWithBackends() { + $userInterface = $this->createMock(UserInterface::class); + $userSyncBackend = $this->createMock(IUserSyncBackend::class); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $perBackendCount = [ + 'back1' => 77, + \get_class($userInterface) => 123, + ]; + + $this->mapper->method('getUserCountPerBackend')->willReturn($perBackendCount); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->assertSame(123, $this->userSyncer->localItemCount(['backends' => \get_class($userInterface)])); + } + + public function testLocalItemCountWithBackendsNotRegistered() { + $userInterface = $this->createMock(UserInterface::class); + $userSyncBackend = $this->createMock(IUserSyncBackend::class); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $perBackendCount = [ + 'back1' => 77, + \get_class($userInterface) => 123, + ]; + + $this->mapper->method('getUserCountPerBackend')->willReturn($perBackendCount); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->assertSame(77, $this->userSyncer->localItemCount(['backends' => 'back1'])); + } + + public function testCheck() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $account2 = Account::fromRow([ + 'id' => 234, + 'email' => 'awe@example.io', + 'user_id' => 'user2', + 'lower_user_id' => 'user2', + 'display_name' => 'awesome', + 'quota' => null, + 'last_login' => 998866, + 'backend' => \get_class($userInterface2), + 'home' => '/home/user2', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '888', + ]); + $syncUser2 = new SyncingUser('user2'); + $syncUser2->setDisplayName('awesome'); + $syncUser2->setEmail('awe@example.io'); + + $userSyncBackend->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user1', $syncUser1] + ])); + $userSyncBackend2->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user2', $syncUser2] + ])); + + $accountList = [$account1, $account2]; + $this->mapper->method('callForUsers') + ->will($this->returnCallback(function ($callback) use ($accountList) { + foreach ($accountList as $acc) { + $callback($acc); + } + })); + + $collectedData = []; + $collectingCallback = function ($kvData, $state) use (&$collectedData) { + $collectedData[] = ['data' => $kvData, 'state' => $state]; + }; + + $this->userSyncer->check($collectingCallback); + + $this->assertSame(2, \count($collectedData)); // 2 results + + $this->assertEquals($syncUser1->getAllData(), $collectedData[0]['data']); + $this->assertSame(ISyncer::CHECK_STATE_NO_CHANGE, $collectedData[0]['state']); + + $this->assertEquals($syncUser2->getAllData(), $collectedData[1]['data']); + $this->assertSame(ISyncer::CHECK_STATE_NO_CHANGE, $collectedData[1]['state']); + } + + public function testCheckFailedUser() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $account2 = Account::fromRow([ + 'id' => 234, + 'email' => 'awe@example.io', + 'user_id' => 'user2', + 'lower_user_id' => 'user2', + 'display_name' => 'awesome', + 'quota' => null, + 'last_login' => 998866, + 'backend' => \get_class($userInterface2), + 'home' => '/home/user2', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '888', + ]); + $syncUser2 = new SyncingUser('user2'); + $syncUser2->setDisplayName('awesome'); + $syncUser2->setEmail('awe@example.io'); + + $userSyncBackend->method('getSyncingUser') + ->will($this->throwException(new SyncBackendUserFailedException('Failed to fetch'))); + $userSyncBackend2->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user2', $syncUser2] + ])); + + $accountList = [$account1, $account2]; + $this->mapper->method('callForUsers') + ->will($this->returnCallback(function ($callback) use ($accountList) { + foreach ($accountList as $acc) { + $callback($acc); + } + })); + + $collectedData = []; + $collectingCallback = function ($kvData, $state) use (&$collectedData) { + $collectedData[] = ['data' => $kvData, 'state' => $state]; + }; + + $this->userSyncer->check($collectingCallback); + + $this->assertSame(2, \count($collectedData)); // 2 results + + $this->assertEquals(new SyncBackendUserFailedException('Failed to fetch'), $collectedData[0]['data']); + $this->assertSame(ISyncer::CHECK_STATE_ERROR, $collectedData[0]['state']); + + $this->assertEquals($syncUser2->getAllData(), $collectedData[1]['data']); + $this->assertSame(ISyncer::CHECK_STATE_NO_CHANGE, $collectedData[1]['state']); + } + + public function testCheckBrokenBackend() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $account1_2 = Account::fromRow([ + 'id' => 123456, + 'email' => 'disp02@example.io', + 'user_id' => 'user1_2', + 'lower_user_id' => 'user1_2', + 'display_name' => 'display1or2', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1_2', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1_2 = new SyncingUser('user1_2'); + $syncUser1_2->setDisplayName('display1or2'); + $syncUser1_2->setEmail('disp02@example.io'); + + $account2 = Account::fromRow([ + 'id' => 234, + 'email' => 'awe@example.io', + 'user_id' => 'user2', + 'lower_user_id' => 'user2', + 'display_name' => 'awesome', + 'quota' => null, + 'last_login' => 998866, + 'backend' => \get_class($userInterface2), + 'home' => '/home/user2', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '888', + ]); + $syncUser2 = new SyncingUser('user2'); + $syncUser2->setDisplayName('awesome'); + $syncUser2->setEmail('awe@example.io'); + + $userSyncBackend->expects($this->once()) + ->method('getSyncingUser') + ->will($this->throwException(new SyncBackendBrokenException('Backend disconnected'))); + $userSyncBackend2->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user2', $syncUser2] + ])); + + $accountList = [$account1, $account1_2, $account2]; + $this->mapper->method('callForUsers') + ->will($this->returnCallback(function ($callback) use ($accountList) { + foreach ($accountList as $acc) { + $callback($acc); + } + })); + + $collectedData = []; + $collectingCallback = function ($kvData, $state) use (&$collectedData) { + $collectedData[] = ['data' => $kvData, 'state' => $state]; + }; + + $this->userSyncer->check($collectingCallback); + + $this->assertSame(2, \count($collectedData)); // 2 results + + $this->assertEquals(new SyncBackendBrokenException('Backend disconnected'), $collectedData[0]['data']); + $this->assertSame(ISyncer::CHECK_STATE_ERROR, $collectedData[0]['state']); + // only one "backend broken" exception is expected for the syncBackend1 + + $this->assertEquals($syncUser2->getAllData(), $collectedData[1]['data']); + $this->assertSame(ISyncer::CHECK_STATE_NO_CHANGE, $collectedData[1]['state']); + } + + public function testCheckOnlyOneBackend() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $account2 = Account::fromRow([ + 'id' => 234, + 'email' => 'awe@example.io', + 'user_id' => 'user2', + 'lower_user_id' => 'user2', + 'display_name' => 'awesome', + 'quota' => null, + 'last_login' => 998866, + 'backend' => \get_class($userInterface2), + 'home' => '/home/user2', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '888', + ]); + $syncUser2 = new SyncingUser('user2'); + $syncUser2->setDisplayName('awesome'); + $syncUser2->setEmail('awe@example.io'); + + $userSyncBackend->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user1', $syncUser1] + ])); + $userSyncBackend2->expects($this->never()) + ->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user2', $syncUser2] + ])); + + $accountList = [$account1, $account2]; + $this->mapper->method('callForUsers') + ->will($this->returnCallback(function ($callback) use ($accountList) { + foreach ($accountList as $acc) { + $callback($acc); + } + })); + + $collectedData = []; + $collectingCallback = function ($kvData, $state) use (&$collectedData) { + $collectedData[] = ['data' => $kvData, 'state' => $state]; + }; + + $this->userSyncer->check($collectingCallback, ['backends' => 'Mock_UserInterface_001']); + + $this->assertSame(1, \count($collectedData)); // 1 results + + $this->assertEquals($syncUser1->getAllData(), $collectedData[0]['data']); + $this->assertSame(ISyncer::CHECK_STATE_NO_CHANGE, $collectedData[0]['state']); + } + + public function testCheckMissingUserDisable() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $account2 = Account::fromRow([ + 'id' => 234, + 'email' => 'awe@example.io', + 'user_id' => 'user2', + 'lower_user_id' => 'user2', + 'display_name' => 'awesome', + 'quota' => null, + 'last_login' => 998866, + 'backend' => \get_class($userInterface2), + 'home' => '/home/user2', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '888', + ]); + $syncUser2 = new SyncingUser('user2'); + $syncUser2->setDisplayName('awesome'); + $syncUser2->setEmail('awe@example.io'); + + $userSyncBackend->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user1', null] + ])); + $userSyncBackend2->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user2', $syncUser2] + ])); + + $userObj = $this->createMock(IUser::class); + $userObj->method('isEnabled')->willReturn(true); + $userObj->expects($this->once()) + ->method('setEnabled') + ->with(false); + $userObj->expects($this->never()) + ->method('delete'); + $this->userManager->method('get') + ->will($this->returnValueMap([ + ['user1', false, $userObj] + ])); + + $accountList = [$account1, $account2]; + $this->mapper->method('callForUsers') + ->will($this->returnCallback(function ($callback) use ($accountList) { + foreach ($accountList as $acc) { + $callback($acc); + } + })); + + $collectedData = []; + $collectingCallback = function ($kvData, $state) use (&$collectedData) { + $collectedData[] = ['data' => $kvData, 'state' => $state]; + }; + + $this->userSyncer->check($collectingCallback); + + $this->assertSame(2, \count($collectedData)); // 2 results + + $this->assertEquals(['uid' => 'user1', 'displayname' => 'display1', 'email' => 'disp@example.io'], $collectedData[0]['data']); + $this->assertSame(ISyncer::CHECK_STATE_DISABLED, $collectedData[0]['state']); + + $this->assertEquals($syncUser2->getAllData(), $collectedData[1]['data']); + $this->assertSame(ISyncer::CHECK_STATE_NO_CHANGE, $collectedData[1]['state']); + } + + public function testCheckMissingUserRemoved() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $account2 = Account::fromRow([ + 'id' => 234, + 'email' => 'awe@example.io', + 'user_id' => 'user2', + 'lower_user_id' => 'user2', + 'display_name' => 'awesome', + 'quota' => null, + 'last_login' => 998866, + 'backend' => \get_class($userInterface2), + 'home' => '/home/user2', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '888', + ]); + $syncUser2 = new SyncingUser('user2'); + $syncUser2->setDisplayName('awesome'); + $syncUser2->setEmail('awe@example.io'); + + $userSyncBackend->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user1', null] + ])); + $userSyncBackend2->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user2', $syncUser2] + ])); + + $userObj = $this->createMock(IUser::class); + $userObj->method('isEnabled')->willReturn(true); + $userObj->expects($this->never()) + ->method('setEnabled'); + $userObj->expects($this->once()) + ->method('delete'); + $this->userManager->method('get') + ->will($this->returnValueMap([ + ['user1', false, $userObj] + ])); + + $accountList = [$account1, $account2]; + $this->mapper->method('callForUsers') + ->will($this->returnCallback(function ($callback) use ($accountList) { + foreach ($accountList as $acc) { + $callback($acc); + } + })); + + $collectedData = []; + $collectingCallback = function ($kvData, $state) use (&$collectedData) { + $collectedData[] = ['data' => $kvData, 'state' => $state]; + }; + + $this->userSyncer->check($collectingCallback, ['missingAction' => 'remove']); + + $this->assertSame(2, \count($collectedData)); // 2 results + + $this->assertEquals(['uid' => 'user1', 'displayname' => 'display1', 'email' => 'disp@example.io'], $collectedData[0]['data']); + $this->assertSame(ISyncer::CHECK_STATE_REMOVED, $collectedData[0]['state']); + + $this->assertEquals($syncUser2->getAllData(), $collectedData[1]['data']); + $this->assertSame(ISyncer::CHECK_STATE_NO_CHANGE, $collectedData[1]['state']); + } + + public function testCheckOne() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $this->mapper->expects($this->once()) + ->method('getByUid') + ->willReturn($account1); + + $userSyncBackend->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user1', $syncUser1] + ])); + $userSyncBackend2->expects($this->never()) + ->method('getSyncingUser'); + + $this->assertSame(ISyncer::CHECK_STATE_NO_CHANGE, $this->userSyncer->checkOne('user1')); + } + + public function testCheckOneDisabled() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $this->mapper->expects($this->once()) + ->method('getByUid') + ->willReturn($account1); + + $userObj = $this->createMock(IUser::class); + $userObj->method('isEnabled')->willReturn(true); + $userObj->expects($this->once()) + ->method('setEnabled'); + $userObj->expects($this->never()) + ->method('delete'); + $this->userManager->method('get') + ->will($this->returnValueMap([ + ['user1', false, $userObj] + ])); + + $userSyncBackend->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user1', null] + ])); + $userSyncBackend2->expects($this->never()) + ->method('getSyncingUser'); + + $this->assertSame(ISyncer::CHECK_STATE_DISABLED, $this->userSyncer->checkOne('user1')); + } + + public function testCheckOneRemoved() { + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $this->mapper->expects($this->once()) + ->method('getByUid') + ->willReturn($account1); + + $userObj = $this->createMock(IUser::class); + $userObj->method('isEnabled')->willReturn(true); + $userObj->expects($this->never()) + ->method('setEnabled'); + $userObj->expects($this->once()) + ->method('delete'); + $this->userManager->method('get') + ->will($this->returnValueMap([ + ['user1', false, $userObj] + ])); + + $userSyncBackend->method('getSyncingUser') + ->will($this->returnValueMap([ + ['user1', null] + ])); + $userSyncBackend2->expects($this->never()) + ->method('getSyncingUser'); + + $this->assertSame(ISyncer::CHECK_STATE_REMOVED, $this->userSyncer->checkOne('user1', ['missingAction' => 'remove'])); + } + + public function testCheckOneAccountNotExists() { + $this->expectException(SyncBackendUserFailedException::class); + $this->expectExceptionMessage('The database returned no accounts for this uid: user1'); + + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $this->mapper->expects($this->once()) + ->method('getByUid') + ->will($this->throwException(new DoesNotExistException('account does not exists'))); + + $this->userSyncer->checkOne('user1'); + } + + public function testCheckOneMultipleAccountExist() { + $this->expectException(SyncBackendUserFailedException::class); + $this->expectExceptionMessage('The database returned multiple accounts for this uid: user1'); + + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $this->mapper->expects($this->once()) + ->method('getByUid') + ->will($this->throwException(new MultipleObjectsReturnedException('account does not exists'))); + + $this->userSyncer->checkOne('user1'); + } + + public function testCheckOneNotInBackend() { + $this->expectException(SyncBackendUserFailedException::class); + $this->expectExceptionMessage('User found not belonging to any of the requested backends'); + + $userInterface = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_001') + ->getMock(); + $userSyncBackend = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_001') + ->getMock(); + $userSyncBackend->method('getUserInterface')->willReturn($userInterface); + + $userInterface2 = $this->getMockBuilder(UserInterface::class) + ->setMockClassName('Mock_UserInterface_002') + ->getMock(); + $userSyncBackend2 = $this->getMockBuilder(IUserSyncBackend::class) + ->setMockClassName('Mock_IUserSyncBackend_002') + ->getMock(); + $userSyncBackend2->method('getUserInterface')->willReturn($userInterface2); + + $this->userSyncer->registerBackend($userSyncBackend); + $this->userSyncer->registerBackend($userSyncBackend2); + + $account1 = Account::fromRow([ + 'id' => 123, + 'email' => 'disp@example.io', + 'user_id' => 'user1', + 'lower_user_id' => 'user1', + 'display_name' => 'display1', + 'quota' => null, + 'last_login' => 998877, + 'backend' => \get_class($userInterface), + 'home' => '/home/user1', + 'state' => Account::STATE_ENABLED, + 'creation_time' => '777', + ]); + $syncUser1 = new SyncingUser('user1'); + $syncUser1->setDisplayName('display1'); + $syncUser1->setEmail('disp@example.io'); + + $this->mapper->expects($this->once()) + ->method('getByUid') + ->willReturn($account1); + + $this->userSyncer->checkOne('user1', ['backends' => 'anotherOne']); + } +}