diff --git a/core/Command/User/SyncBackend.php b/core/Command/User/SyncBackend.php index 0637a4667f89..4ec911279708 100644 --- a/core/Command/User/SyncBackend.php +++ b/core/Command/User/SyncBackend.php @@ -1,5 +1,6 @@ * @author Thomas Müller * * @copyright Copyright (c) 2017, ownCloud GmbH @@ -22,15 +23,17 @@ namespace OC\Core\Command\User; +use OC\Migration\ConsoleOutput; use OC\User\AccountMapper; use OC\User\SyncService; +use OC\User\SyncServiceCallback; use OCP\IConfig; use OCP\ILogger; use OCP\IUser; use OCP\IUserManager; use OCP\UserInterface; +use OCP\Util; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -48,6 +51,13 @@ class SyncBackend extends Command { /** @var ILogger */ private $logger; + private $verbosityLevelMap = array( + OutputInterface::VERBOSITY_QUIET => Util::ERROR, + OutputInterface::VERBOSITY_NORMAL => Util::WARN, + OutputInterface::VERBOSITY_VERBOSE => Util::INFO, + OutputInterface::VERBOSITY_VERY_VERBOSE => Util::DEBUG, + OutputInterface::VERBOSITY_DEBUG => Util::DEBUG, + ); /** * @param AccountMapper $accountMapper * @param IConfig $config @@ -75,6 +85,7 @@ protected function configure() { 'The php class name - e.g. "OCA\User_LDAP\User_Proxy". Please wrap the class name into double quotes. You can use the option --list to list all known backend classes' ) ->addOption('list', 'l', InputOption::VALUE_NONE, 'list all known backend classes') + ->addOption('userid', 'u', InputOption::VALUE_REQUIRED, 'sync only the user with the given id') ->addOption('missing-account-action', 'm', InputOption::VALUE_REQUIRED, 'action to do if the account isn\'t connected to a backend any longer. Options are "disable" and "remove". Use quotes. Note that removing the account will also remove the stored data and files for that account'); } @@ -103,8 +114,8 @@ protected function execute(InputInterface $input, OutputInterface $output) { $validActions = ['disable', 'remove']; - if ($input->getOption('missing-account-action') !== null) { - $missingAccountsAction = $input->getOption('missing-account-action'); + $missingAccountsAction = $input->getOption('missing-account-action'); + if ($missingAccountsAction !== null) { if (!in_array($missingAccountsAction, $validActions, true)) { $output->writeln("Unknown action. Choose between \"disable\" or \"remove\""); return 1; @@ -122,27 +133,108 @@ protected function execute(InputInterface $input, OutputInterface $output) { $syncService = new SyncService($this->accountMapper, $backend, $this->config, $this->logger); - // analyse unknown users - $this->handleUnknownUsers($input, $output, $syncService, $missingAccountsAction, $validActions); - // insert/update known users $output->writeln("Insert new and update existing users ..."); - $p = new ProgressBar($output); - $max = null; - if ($backend->implementsActions(\OC_User_Backend::COUNT_USERS)) { - $max = $backend->countUsers(); + $userid = $input->getOption('userid'); + $consoleOutput = new ConsoleOutput($output); + + if ($userid) { + $this->syncSingleUser($userid, $input, $output, $consoleOutput, $syncService, $backend, $missingAccountsAction, $validActions); + } else { + $this->syncMultipleUsers($input, $output, $consoleOutput, $syncService, $backend, $missingAccountsAction, $validActions); } - $p->start($max); - $syncService->run(function () use ($p) { - $p->advance(); - }); - $p->finish(); + $output->writeln(''); $output->writeln(''); return 0; } + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param ConsoleOutput $consoleOutput + * @param SyncService $syncService + * @param UserInterface $backend + * @param string $missingAccountsAction + * @param array $validActions + */ + private function syncMultipleUsers ( + InputInterface $input, + OutputInterface $output, + ConsoleOutput $consoleOutput, + SyncService $syncService, + UserInterface $backend, + $missingAccountsAction, + array $validActions + ) { + + $output->writeln("Analyzing synced users ..."); + $consoleOutput->startProgress($this->accountMapper->getUserCount(false)); + $unknownUsers = $syncService->getNoLongerExistingUsers(function () use ($consoleOutput) { + $consoleOutput->advance(); + }); + $consoleOutput->finishProgress(); + $output->writeln(''); + $output->writeln(''); + + $this->handleUnknownUsers($unknownUsers, $input, $output, $missingAccountsAction, $validActions); + + // insert/update known users + if ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) { + if ($backend->implementsActions(\OC_User_Backend::COUNT_USERS)) { + $consoleOutput->startProgress($backend->countUsers()); + } else { + $consoleOutput->startProgress(); + } + } + + $syncService->run(new SyncServiceCallback( + $consoleOutput, + $this->verbosityLevelMap[$output->getVerbosity()] + )); + + if ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) { + $consoleOutput->finishProgress(); + } + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param ConsoleOutput $consoleOutput + * @param SyncService $syncService + * @param UserInterface $backend + * @param string $missingAccountsAction + * @param array $validActions + */ + private function syncSingleUser( + $uid, + InputInterface $input, + OutputInterface $output, + ConsoleOutput $consoleOutput, + SyncService $syncService, + UserInterface $backend, + $missingAccountsAction, + array $validActions + ) { + + $output->writeln("Analyzing {$uid} ..."); + if (!$backend->userExists($uid)) { + $this->handleUnknownUsers([$uid], $input, $output, $missingAccountsAction, $validActions); + } else { + // sync + // use at least Verbose output + if ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) { + $logLevel = $this->verbosityLevelMap[OutputInterface::VERBOSITY_VERBOSE]; + } else { + $logLevel = $this->verbosityLevelMap[$output->getVerbosity()]; + } + $syncService->syncUser($uid, new SyncServiceCallback( + $consoleOutput, $logLevel + )); + } + } /** * @param $backend * @return null|UserInterface @@ -177,30 +269,21 @@ private function doActionForAccountUids(array $uids, callable $callbackExists, c } /** + * @param string[] $userIds * @param InputInterface $input * @param OutputInterface $output - * @param $syncService - * @param $missingAccountsAction - * @param $validActions + * @param string $missingAccountsAction + * @param array $validActions */ - private function handleUnknownUsers(InputInterface $input, OutputInterface $output, $syncService, $missingAccountsAction, $validActions) { - $output->writeln("Analyse unknown users ..."); - $p = new ProgressBar($output); - $toBeDeleted = $syncService->getNoLongerExistingUsers(function () use ($p) { - $p->advance(); - }); - $p->finish(); - $output->writeln(''); - $output->writeln(''); - - if (empty($toBeDeleted)) { + private function handleUnknownUsers(array $userIds, InputInterface $input, OutputInterface $output, $missingAccountsAction, $validActions) { + if (empty($userIds)) { $output->writeln("No unknown users have been detected."); } else { $output->writeln("Following users are no longer known with the connected backend."); switch ($missingAccountsAction) { case 'disable': $output->writeln("Proceeding to disable the accounts"); - $this->doActionForAccountUids($toBeDeleted, + $this->doActionForAccountUids($userIds, function ($uid, IUser $ac) use ($output) { $ac->setEnabled(false); $output->writeln($uid); @@ -211,7 +294,7 @@ function ($uid) use ($output) { break; case 'remove': $output->writeln("Proceeding to remove the accounts"); - $this->doActionForAccountUids($toBeDeleted, + $this->doActionForAccountUids($userIds, function ($uid, IUser $ac) use ($output) { $ac->delete(); $output->writeln($uid); @@ -222,7 +305,7 @@ function ($uid) use ($output) { break; case 'ask later': $output->writeln("listing the unknown accounts"); - $this->doActionForAccountUids($toBeDeleted, + $this->doActionForAccountUids($userIds, function ($uid) use ($output) { $output->writeln($uid); }, @@ -241,7 +324,7 @@ function ($uid) use ($output) { // if "nothing" is selected, just ignore and finish case 'disable': $output->writeln("Proceeding to disable the accounts"); - $this->doActionForAccountUids($toBeDeleted, + $this->doActionForAccountUids($userIds, function ($uid, IUser $ac) { $ac->setEnabled(false); }, @@ -251,7 +334,7 @@ function ($uid) use ($output) { break; case 'remove': $output->writeln("Proceeding to remove the accounts"); - $this->doActionForAccountUids($toBeDeleted, + $this->doActionForAccountUids($userIds, function ($uid, IUser $ac) { $ac->delete(); }, diff --git a/core/Migrations/Version20170221114437.php b/core/Migrations/Version20170221114437.php index 1ceebec287de..bce1155af250 100644 --- a/core/Migrations/Version20170221114437.php +++ b/core/Migrations/Version20170221114437.php @@ -1,14 +1,16 @@ info("Insert new users ..."); $out->startProgress($backend->countUsers()); - $syncService->run(function () use ($out) { - $out->advance(); - }); + $syncService->run(new SyncServiceCallback( + $out, + (int)$config->getSystemValue('loglevel', Util::WARN) + )); $out->finishProgress(); } } diff --git a/lib/private/Migration/ConsoleOutput.php b/lib/private/Migration/ConsoleOutput.php index 892a2f43419f..8d6973d921e3 100644 --- a/lib/private/Migration/ConsoleOutput.php +++ b/lib/private/Migration/ConsoleOutput.php @@ -1,5 +1,6 @@ * @author Thomas Müller * * @copyright Copyright (c) 2017, ownCloud GmbH @@ -49,16 +50,18 @@ public function __construct(OutputInterface $output) { /** * @param string $message + * @param bool $newline */ - public function info($message) { - $this->output->writeln("$message"); + public function info($message, $newline = true) { + $this->output->write("$message", $newline); } /** * @param string $message + * @param bool $newline */ - public function warning($message) { - $this->output->writeln("$message"); + public function warning($message, $newline = true) { + $this->output->write("$message", $newline); } /** @@ -77,13 +80,16 @@ public function startProgress($max = 0) { * @param string $description */ public function advance($step = 1, $description = '') { - if (!is_null($this->progressBar)) { + if (is_null($this->progressBar)) { $this->progressBar = new ProgressBar($this->output); $this->progressBar->start(); } $this->progressBar->advance($step); } + /** + * @since 9.1.0 + */ public function finishProgress() { if (is_null($this->progressBar)) { return; diff --git a/lib/private/Migration/SimpleOutput.php b/lib/private/Migration/SimpleOutput.php index b28fcbd76280..58f7850e209c 100644 --- a/lib/private/Migration/SimpleOutput.php +++ b/lib/private/Migration/SimpleOutput.php @@ -1,5 +1,6 @@ * @author Thomas Müller * * @copyright Copyright (c) 2017, ownCloud GmbH @@ -47,17 +48,19 @@ public function __construct(ILogger $logger, $appName) { /** * @param string $message + * @param bool $newline always true because ILogger always uses newlines * @since 9.1.0 */ - public function info($message) { + public function info($message, $newline = true) { $this->logger->info($message, ['app' => $this->appName]); } /** * @param string $message + * @param bool $newline always true because ILogger always uses newlines * @since 9.1.0 */ - public function warning($message) { + public function warning($message, $newline = true) { $this->logger->warning($message, ['app' => $this->appName]); } diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 2e3984687c32..d6a3110d7005 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -3,6 +3,7 @@ * @author Arthur Schiwon * @author Georg Ehrke * @author Joas Schilling + * @author Jörn Friedrich Dreyer * @author Lukas Reschke * @author Morris Jobke * @author Robin Appelman @@ -205,15 +206,20 @@ public function emit($scope, $method, array $arguments = []) { } } - public function info($string) { + /** + * @param string $message + * @param bool $newline ignored for emitting + */ + public function info($message, $newline = true) { // for now just emit as we did in the past - $this->emit('\OC\Repair', 'info', [$string]); + $this->emit('\OC\Repair', 'info', [$message]); } /** * @param string $message + * @param bool $newline ignored for emitting */ - public function warning($message) { + public function warning($message, $newline = true) { // for now just emit as we did in the past $this->emit('\OC\Repair', 'warning', [$message]); } @@ -236,7 +242,7 @@ public function advance($step = 1, $description = '') { } /** - * @param int $max + * @since 9.1.0 */ public function finishProgress() { // for now just emit as we did in the past diff --git a/lib/private/User/AccountMapper.php b/lib/private/User/AccountMapper.php index 3d64f616154b..3426608a1da2 100644 --- a/lib/private/User/AccountMapper.php +++ b/lib/private/User/AccountMapper.php @@ -200,7 +200,11 @@ public function getUserCountPerBackend($hasLoggedIn) { return $return; } - public function getUserCount($hasLoggedIn) { + /** + * @param bool $hasLoggedIn + * @return int + */ + public function getUserCount($hasLoggedIn = false) { $qb = $this->db->getQueryBuilder(); $qb->select([$qb->createFunction('count(*) as `count`')]) ->from($this->getTableName()); diff --git a/lib/private/User/BackendMismatchException.php b/lib/private/User/BackendMismatchException.php new file mode 100644 index 000000000000..baa945c06d5f --- /dev/null +++ b/lib/private/User/BackendMismatchException.php @@ -0,0 +1,37 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\User; + +class BackendMismatchException extends \Exception { + + /** + * BackendMismatchException constructor. + * + * @param Account $account + * @param string $expectedBackendClass + */ + public function __construct(Account $account, $expectedBackendClass) { + $message = "User <{$expectedBackendClass}::{$account->getUserId()}>" + ." already provided by <{$account->getBackend()}>, skipping."; + parent::__construct($message); + } +} diff --git a/lib/private/User/SyncService.php b/lib/private/User/SyncService.php index 28f918d67d3a..02045c0bbc28 100644 --- a/lib/private/User/SyncService.php +++ b/lib/private/User/SyncService.php @@ -1,5 +1,6 @@ * @author Thomas Müller * * @copyright Copyright (c) 2017, ownCloud GmbH @@ -89,42 +90,56 @@ public function getNoLongerExistingUsers(\Closure $callback) { } /** - * @param \Closure $callback is called for every user to progress display + * @param SyncServiceCallback $callback methods are called for every user to progress display */ - public function run(\Closure $callback) { + public function run(SyncServiceCallback $callback) { $limit = 500; $offset = 0; do { $users = $this->backend->getUsers('', $limit, $offset); - // update existing and insert new users foreach ($users as $uid) { try { - $a = $this->mapper->getByUid($uid); - if ($a->getBackend() !== $this->backendClass) { - $this->logger->warning( - "User <$uid> already provided by another backend({$a->getBackend()} != {$this->backendClass}), skipping.", - ['app' => self::class] - ); - continue; - } - $a = $this->setupAccount($a, $uid); - $this->mapper->update($a); - } catch(DoesNotExistException $ex) { - $a = $this->createNewAccount($uid); - $this->setupAccount($a, $uid); - $this->mapper->insert($a); + $this->syncUser($uid, $callback); + } catch (BackendMismatchException $ex) { + $callback->onBackendMismatchException($ex); + continue; } - // clean the user's preferences - $this->cleanPreferences($uid); - - // call the callback - $callback($uid); } + $offset += $limit; } while(count($users) >= $limit); } + /** + * update existing and insert new users + * @param string $uid user ids to sync + * @param SyncServiceCallback $callback methods are called for every user to progress display + * @throws BackendMismatchException if a uid is already used by another backend + */ + public function syncUser($uid, SyncServiceCallback $callback) { + $callback->startSync($uid); + try { + $account = $this->mapper->getByUid($uid); + if ($account->getBackend() !== $this->backendClass) { + throw new BackendMismatchException($account, $this->backendClass); + } + $account = $this->setupAccount($account, $uid); + $this->mapper->update($account); + // clean the user's preferences + $this->cleanPreferences($uid); // TODO always? + $callback->endUpdated($account); + } catch (DoesNotExistException $ex) { + $account = $this->createNewAccount($uid); + $this->setupAccount($account, $uid); + /** @var Account $account */ + $this->mapper->insert($account); // will the id be set in this account or do we need the return value? + // clean the user's preferences + $this->cleanPreferences($uid); // TODO always? + $callback->endCreated($account); + } + } + /** * @param Account $a * @param string $uid @@ -203,9 +218,11 @@ private function readUserConfig($uid, $app, $key) { } /** + * These attributes are now stored in the appconfig table * @param string $uid */ private function cleanPreferences($uid) { + // FIXME use a single query to delete these from the preferences table $this->config->deleteUserValue($uid, 'core', 'enabled'); $this->config->deleteUserValue($uid, 'login', 'lastLogin'); $this->config->deleteUserValue($uid, 'settings', 'email'); diff --git a/lib/private/User/SyncServiceCallback.php b/lib/private/User/SyncServiceCallback.php new file mode 100644 index 000000000000..e48659e6187b --- /dev/null +++ b/lib/private/User/SyncServiceCallback.php @@ -0,0 +1,116 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\User; + +use OCP\Migration\IOutput; +use OCP\Util; + +class SyncServiceCallback { + + /** + * @var IOutput + */ + private $output; + + /** + * @var int + */ + private $logLevel; + + /** + * @var int + */ + private $start; + + /** + * SyncServiceCallback constructor. + * TODO use ITimeFactory to test output when it provides microtime + * + * @param IOutput $output + * @param int $logLevel + */ + public function __construct(IOutput $output, $logLevel) { + $this->output = $output; + $this->logLevel = $logLevel; + } + + /** + * @param string $uid + */ + public function startSync ($uid) { + $this->start = microtime(true); + $this->output->info("Syncing $uid: ", false); + } + + /** + * @param BackendMismatchException $ex + */ + public function onBackendMismatchException(BackendMismatchException $ex) { + $this->output->warning($ex->getMessage()); + $this->output->advance(); + } + + /** + * @param Account $account + */ + public function endCreated(Account $account) { + $this->endSync($account, 'created'); + } + + /** + * @param Account $account + */ + public function endUpdated(Account $account) { + $this->endSync($account, 'updated'); + } + + /** + * @param string $action + * @param Account $account + */ + private function endSync (Account $account, $action) { + $delta = microtime(true)-$this->start; + switch ($this->logLevel) { + case Util::DEBUG: + //$account = $this->mapper->getByUid($account->getUserId()); + $this->output->warning("$action with ".json_encode([ + 'id' => $account->getId(), + 'backend' => $account->getBackend(), + 'userId' => $account->getUserId(), + 'displayName' => $account->getDisplayName(), + 'email' => $account->getEmail(), + 'home' => $account->getHome(), + 'lastLogin' => $account->getLastLogin(), + 'quota' => $account->getQuota(), + 'searchTerms' => $account->getSearchTerms() + ]). " in {$delta}s"); + break; + case Util::INFO: + $this->output->info("$action in {$delta}s"); + break; + default: + // call the callback + $this->output->advance(); + } + } + +} \ No newline at end of file diff --git a/lib/public/Migration/IOutput.php b/lib/public/Migration/IOutput.php index 1cf30f5e264f..6fd0770e6408 100644 --- a/lib/public/Migration/IOutput.php +++ b/lib/public/Migration/IOutput.php @@ -1,5 +1,6 @@ * @author Thomas Müller * @author Vincent Petry * @@ -31,15 +32,17 @@ interface IOutput { /** * @param string $message + * @param bool $newline * @since 9.1.0 */ - public function info($message); + public function info($message, $newline = true); /** * @param string $message + * @param bool $newline * @since 9.1.0 */ - public function warning($message); + public function warning($message, $newline = true); /** * @param int $max @@ -55,7 +58,6 @@ public function startProgress($max = 0); public function advance($step = 1, $description = ''); /** - * @param int $max * @since 9.1.0 */ public function finishProgress(); diff --git a/tests/lib/User/SyncServiceTest.php b/tests/lib/User/SyncServiceTest.php index f8aeb5995b7a..ceaa1d01bbfb 100644 --- a/tests/lib/User/SyncServiceTest.php +++ b/tests/lib/User/SyncServiceTest.php @@ -1,17 +1,31 @@ + * @author Thomas Müller + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * */ - namespace Test\User; - use OC\User\Account; use OC\User\AccountMapper; use OC\User\SyncService; +use OC\User\SyncServiceCallback; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\IConfig; use OCP\ILogger; use OCP\UserInterface; @@ -19,26 +33,141 @@ class SyncServiceTest extends TestCase { + /** + * @var AccountMapper|\PHPUnit_Framework_MockObject_MockObject + */ + protected $mapper; + /** + * @var UserInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $backend; + /** + * @var IConfig|\PHPUnit_Framework_MockObject_MockObject + */ + protected $config; + /** + * @var ILogger|\PHPUnit_Framework_MockObject_MockObject + */ + protected $logger; + + protected function setUp() { + $this->mapper = $this->createMock(AccountMapper::class); + $this->backend = $this->createMock(UserInterface::class); + $this->config = $this->createMock(IConfig::class); + $this->logger = $this->createMock(ILogger::class); + + $this->config->expects($this->any())->method('getUserKeys') + ->will($this->returnCallback(function($uid, $app) { + switch ($app) { + case 'core': + case 'login': + case 'files': + return []; + case 'settings': + return ['email']; + default: + return false; + } + })); + $this->config->expects($this->any()) + ->method('getUserValue') + ->will($this->returnCallback(function($uid, $app, $key) { + switch ($key) { + case 'email': + return "$uid@bar.net"; + default: + return false; + } + })); + parent::setUp(); + } + public function testSetupAccount() { - $mapper = $this->createMock(AccountMapper::class); - $backend = $this->createMock(UserInterface::class); - $config = $this->createMock(IConfig::class); - $logger = $this->createMock(ILogger::class); - - $config->expects($this->any())->method('getUserKeys')->willReturnMap([ - ['user1', 'core', []], - ['user1', 'login', []], - ['user1', 'settings', ['email']], - ['user1', 'files', []], - ]); - $config->expects($this->any())->method('getUserValue')->willReturnMap([ - ['user1', 'settings', 'email', '', 'foo@bar.net'], - ]); - - $s = new SyncService($mapper, $backend, $config, $logger); + + $s = new SyncService($this->mapper, $this->backend, $this->config, $this->logger); $a = new Account(); + $s->setupAccount($a, 'user1'); - $this->assertEquals('foo@bar.net', $a->getEmail()); + $this->assertEquals('user1@bar.net', $a->getEmail()); + } + + public function testSyncUsersExistingIsUpdated() { + + $a1 = new Account(); + $a1->setUserId('user1'); + $a1->setBackend(get_class($this->backend)); + + $this->mapper->expects($this->exactly(1)) + ->method('getByUid') + ->with($this->equalTo('user1')) + ->willReturn($a1); + + $this->mapper->expects($this->exactly(1)) + ->method('update'); + + /** @var SyncServiceCallback|\PHPUnit_Framework_MockObject_MockObject $callback */ + $callback = $this->createMock(SyncServiceCallback::class); + $callback->expects($this->once()) + ->method('startSync'); + $callback->expects($this->once()) + ->method('endUpdated') + ->with($a1); + + $s = new SyncService($this->mapper, $this->backend, $this->config, $this->logger); + $s->syncUser('user1', $callback); + } + + public function testSyncUsersNotExistingIsInserted() { + $this->mapper->expects($this->exactly(1)) + ->method('getByUid') + ->willThrowException( + new DoesNotExistException('User does not exist') + ); + + + $a1 = new Account(); + $a1->setUserId('user1'); + $a1->setBackend('\Some\Mismatching\UserBackend'); + + $this->mapper->expects($this->once()) + ->method('insert') + ->willReturn($a1); + + /** @var SyncServiceCallback|\PHPUnit_Framework_MockObject_MockObject $callback */ + $callback = $this->createMock(SyncServiceCallback::class); + $callback->expects($this->once()) + ->method('startSync'); + $callback->expects($this->once()) + ->method('endCreated'); + + $s = new SyncService($this->mapper, $this->backend, $this->config, $this->logger); + $s->syncUser('user1', $callback); + } + + /** + * @expectedException \OC\User\BackendMismatchException + */ + public function testSyncUserBackendMismatch() { + $a1 = new Account(); + $a1->setUserId('user1'); + $a1->setBackend('\Some\Mismatching\UserBackend'); + + $this->mapper->expects($this->exactly(1)) + ->method('getByUid') + ->with($this->equalTo('user1')) + ->willReturn($a1); + $this->mapper->expects($this->never()) + ->method('insert'); + $this->mapper->expects($this->never()) + ->method('update'); + + /** @var SyncServiceCallback|\PHPUnit_Framework_MockObject_MockObject $callback */ + $callback = $this->createMock(SyncServiceCallback::class); + $callback->expects($this->once()) + ->method('startSync'); + + $s = new SyncService($this->mapper, $this->backend, $this->config, $this->logger); + $s->syncUser('user1', $callback); } } \ No newline at end of file