Merge pull request #53540 from nextcloud/feature/add-profile-to-occ
Feature/add profile to occpull/53548/head
commit
a1cf92ceed
@ -0,0 +1,234 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OC\Core\Command\User; |
||||
|
||||
use OC\Core\Command\Base; |
||||
use OCP\Accounts\IAccount; |
||||
use OCP\Accounts\IAccountManager; |
||||
use OCP\Accounts\PropertyDoesNotExistException; |
||||
use OCP\IUser; |
||||
use OCP\IUserManager; |
||||
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; |
||||
use Symfony\Component\Console\Input\InputArgument; |
||||
use Symfony\Component\Console\Input\InputInterface; |
||||
use Symfony\Component\Console\Input\InputOption; |
||||
use Symfony\Component\Console\Output\OutputInterface; |
||||
|
||||
class Profile extends Base { |
||||
public function __construct( |
||||
protected IUserManager $userManager, |
||||
protected IAccountManager $accountManager, |
||||
) { |
||||
parent::__construct(); |
||||
} |
||||
|
||||
protected function configure() { |
||||
parent::configure(); |
||||
$this |
||||
->setName('user:profile') |
||||
->setDescription('Read and modify user profile properties') |
||||
->addArgument( |
||||
'uid', |
||||
InputArgument::REQUIRED, |
||||
'Account ID used to login' |
||||
) |
||||
->addArgument( |
||||
'key', |
||||
InputArgument::OPTIONAL, |
||||
'Profile property to set, get or delete', |
||||
'' |
||||
) |
||||
|
||||
// Get |
||||
->addOption( |
||||
'default-value', |
||||
null, |
||||
InputOption::VALUE_REQUIRED, |
||||
'(Only applicable on get) If no default value is set and the property does not exist, the command will exit with 1' |
||||
) |
||||
|
||||
// Set |
||||
->addArgument( |
||||
'value', |
||||
InputArgument::OPTIONAL, |
||||
'The new value of the property', |
||||
null |
||||
) |
||||
->addOption( |
||||
'update-only', |
||||
null, |
||||
InputOption::VALUE_NONE, |
||||
'Only updates the value, if it is not set before, it is not being added' |
||||
) |
||||
|
||||
// Delete |
||||
->addOption( |
||||
'delete', |
||||
null, |
||||
InputOption::VALUE_NONE, |
||||
'Specify this option to delete the property value' |
||||
) |
||||
->addOption( |
||||
'error-if-not-exists', |
||||
null, |
||||
InputOption::VALUE_NONE, |
||||
'Checks whether the property exists before deleting it' |
||||
) |
||||
; |
||||
} |
||||
|
||||
protected function checkInput(InputInterface $input): IUser { |
||||
$uid = $input->getArgument('uid'); |
||||
$user = $this->userManager->get($uid); |
||||
if (!$user) { |
||||
throw new \InvalidArgumentException('The user "' . $uid . '" does not exist.'); |
||||
} |
||||
// normalize uid |
||||
$input->setArgument('uid', $user->getUID()); |
||||
|
||||
$key = $input->getArgument('key'); |
||||
if ($key === '') { |
||||
if ($input->hasParameterOption('--default-value')) { |
||||
throw new \InvalidArgumentException('The "default-value" option can only be used when specifying a key.'); |
||||
} |
||||
if ($input->getArgument('value') !== null) { |
||||
throw new \InvalidArgumentException('The value argument can only be used when specifying a key.'); |
||||
} |
||||
if ($input->getOption('delete')) { |
||||
throw new \InvalidArgumentException('The "delete" option can only be used when specifying a key.'); |
||||
} |
||||
} |
||||
|
||||
if ($input->getArgument('value') !== null && $input->hasParameterOption('--default-value')) { |
||||
throw new \InvalidArgumentException('The value argument can not be used together with "default-value".'); |
||||
} |
||||
if ($input->getOption('update-only') && $input->getArgument('value') === null) { |
||||
throw new \InvalidArgumentException('The "update-only" option can only be used together with "value".'); |
||||
} |
||||
|
||||
if ($input->getOption('delete') && $input->hasParameterOption('--default-value')) { |
||||
throw new \InvalidArgumentException('The "delete" option can not be used together with "default-value".'); |
||||
} |
||||
if ($input->getOption('delete') && $input->getArgument('value') !== null) { |
||||
throw new \InvalidArgumentException('The "delete" option can not be used together with "value".'); |
||||
} |
||||
if ($input->getOption('error-if-not-exists') && !$input->getOption('delete')) { |
||||
throw new \InvalidArgumentException('The "error-if-not-exists" option can only be used together with "delete".'); |
||||
} |
||||
|
||||
return $user; |
||||
} |
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int { |
||||
try { |
||||
$user = $this->checkInput($input); |
||||
} catch (\InvalidArgumentException $e) { |
||||
$output->writeln('<error>' . $e->getMessage() . '</error>'); |
||||
return self::FAILURE; |
||||
} |
||||
|
||||
$uid = $input->getArgument('uid'); |
||||
$key = $input->getArgument('key'); |
||||
$userAccount = $this->accountManager->getAccount($user); |
||||
|
||||
if ($key === '') { |
||||
$settings = $this->getAllProfileProperties($userAccount); |
||||
$this->writeArrayInOutputFormat($input, $output, $settings); |
||||
return self::SUCCESS; |
||||
} |
||||
|
||||
$value = $this->getStoredValue($userAccount, $key); |
||||
$inputValue = $input->getArgument('value'); |
||||
if ($inputValue !== null) { |
||||
if ($input->hasParameterOption('--update-only') && $value === null) { |
||||
$output->writeln('<error>The property does not exist for user "' . $uid . '".</error>'); |
||||
return self::FAILURE; |
||||
} |
||||
|
||||
return $this->editProfileProperty($output, $userAccount, $key, $inputValue); |
||||
} elseif ($input->hasParameterOption('--delete')) { |
||||
if ($input->hasParameterOption('--error-if-not-exists') && $value === null) { |
||||
$output->writeln('<error>The property does not exist for user "' . $uid . '".</error>'); |
||||
return self::FAILURE; |
||||
} |
||||
|
||||
return $this->deleteProfileProperty($output, $userAccount, $key); |
||||
} elseif ($value !== null) { |
||||
$output->writeln($value); |
||||
} elseif ($input->hasParameterOption('--default-value')) { |
||||
$output->writeln($input->getOption('default-value')); |
||||
} else { |
||||
$output->writeln('<error>The property does not exist for user "' . $uid . '".</error>'); |
||||
return self::FAILURE; |
||||
} |
||||
|
||||
return self::SUCCESS; |
||||
} |
||||
|
||||
private function deleteProfileProperty(OutputInterface $output, IAccount $userAccount, string $key): int { |
||||
return $this->editProfileProperty($output, $userAccount, $key, ''); |
||||
} |
||||
|
||||
private function editProfileProperty(OutputInterface $output, IAccount $userAccount, string $key, string $value): int { |
||||
try { |
||||
$userAccount->getProperty($key)->setValue($value); |
||||
} catch (PropertyDoesNotExistException $exception) { |
||||
$output->writeln('<error>' . $exception->getMessage() . '</error>'); |
||||
return self::FAILURE; |
||||
} |
||||
|
||||
$this->accountManager->updateAccount($userAccount); |
||||
return self::SUCCESS; |
||||
} |
||||
|
||||
private function getStoredValue(IAccount $userAccount, string $key): ?string { |
||||
try { |
||||
$property = $userAccount->getProperty($key); |
||||
} catch (PropertyDoesNotExistException) { |
||||
return null; |
||||
} |
||||
return $property->getValue() === '' ? null : $property->getValue(); |
||||
} |
||||
|
||||
private function getAllProfileProperties(IAccount $userAccount): array { |
||||
$properties = []; |
||||
|
||||
foreach ($userAccount->getAllProperties() as $property) { |
||||
if ($property->getValue() !== '') { |
||||
$properties[$property->getName()] = $property->getValue(); |
||||
} |
||||
} |
||||
|
||||
return $properties; |
||||
} |
||||
|
||||
/** |
||||
* @param string $argumentName |
||||
* @param CompletionContext $context |
||||
* @return string[] |
||||
*/ |
||||
public function completeArgumentValues($argumentName, CompletionContext $context): array { |
||||
if ($argumentName === 'uid') { |
||||
return array_map(static fn (IUser $user) => $user->getUID(), $this->userManager->search($context->getCurrentWord())); |
||||
} |
||||
if ($argumentName === 'key') { |
||||
$userId = $context->getWordAtIndex($context->getWordIndex() - 1); |
||||
$user = $this->userManager->get($userId); |
||||
if (!($user instanceof IUser)) { |
||||
return []; |
||||
} |
||||
|
||||
$account = $this->accountManager->getAccount($user); |
||||
|
||||
$properties = $this->getAllProfileProperties($account); |
||||
return array_keys($properties); |
||||
} |
||||
return []; |
||||
} |
||||
} |
||||
@ -0,0 +1,470 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace Core\Command\User; |
||||
|
||||
use OC\Core\Command\User\Profile; |
||||
use OCP\Accounts\IAccount; |
||||
use OCP\Accounts\IAccountManager; |
||||
use OCP\Accounts\IAccountProperty; |
||||
use OCP\IDBConnection; |
||||
use OCP\IUser; |
||||
use OCP\IUserManager; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Symfony\Component\Console\Command\Command; |
||||
use Symfony\Component\Console\Input\InputInterface; |
||||
use Symfony\Component\Console\Output\OutputInterface; |
||||
use Test\TestCase; |
||||
|
||||
class ProfileTest extends TestCase { |
||||
|
||||
protected IAccountManager&MockObject $accountManager; |
||||
protected IUserManager&MockObject $userManager; |
||||
protected IDBConnection&MockObject $connection; |
||||
protected InputInterface&MockObject $consoleInput; |
||||
protected OutputInterface&MockObject $consoleOutput; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$this->accountManager = $this->createMock(IAccountManager::class); |
||||
$this->userManager = $this->createMock(IUserManager::class); |
||||
$this->connection = $this->createMock(IDBConnection::class); |
||||
$this->consoleInput = $this->createMock(InputInterface::class); |
||||
$this->consoleOutput = $this->createMock(OutputInterface::class); |
||||
} |
||||
|
||||
public function getCommand(array $methods = []): Profile|MockObject { |
||||
if (empty($methods)) { |
||||
return new Profile($this->userManager, $this->accountManager); |
||||
} else { |
||||
return $this->getMockBuilder(Profile::class) |
||||
->setConstructorArgs([ |
||||
$this->userManager, |
||||
$this->accountManager, |
||||
]) |
||||
->onlyMethods($methods) |
||||
->getMock(); |
||||
} |
||||
} |
||||
|
||||
public static function dataCheckInput(): array { |
||||
return [ |
||||
'Call with existing user should pass check' => [ |
||||
[['uid', 'username']], |
||||
[], |
||||
[], |
||||
true, |
||||
null, |
||||
], |
||||
'Call with non-existing user should fail check' => [ |
||||
[['uid', 'username']], |
||||
[], |
||||
[], |
||||
false, |
||||
'The user "username" does not exist.', |
||||
], |
||||
|
||||
'Call with uid, key and --default value should pass check' => [ |
||||
[['uid', 'username'], ['key', 'configkey']], |
||||
[], |
||||
[['--default-value', false, true]], |
||||
true, |
||||
null, |
||||
], |
||||
'Call with uid and empty key with default-value option should fail check' => [ |
||||
[['uid', 'username'], ['key', '']], |
||||
[], |
||||
[['--default-value', false, true]], |
||||
true, |
||||
'The "default-value" option can only be used when specifying a key.', |
||||
], |
||||
|
||||
'Call with uid, key, value should pass check' => [ |
||||
[['uid', 'username'], ['key', 'configkey'], ['value', '']], |
||||
[], |
||||
[], |
||||
true, |
||||
null, |
||||
], |
||||
'Call with uid, empty key and empty value should fail check' => [ |
||||
[['uid', 'username'], ['key', ''], ['value', '']], |
||||
[], |
||||
[], |
||||
true, |
||||
'The value argument can only be used when specifying a key.', |
||||
], |
||||
'Call with uid, key, empty value and default-value option should fail check' => [ |
||||
[['uid', 'username'], ['key', 'configkey'], ['value', '']], |
||||
[], |
||||
[['--default-value', false, true]], |
||||
true, |
||||
'The value argument can not be used together with "default-value".', |
||||
], |
||||
'Call with uid, key, empty value and update-only option should pass check' => [ |
||||
[['uid', 'username'], ['key', 'configkey'], ['value', '']], |
||||
[['update-only', true]], |
||||
[], |
||||
true, |
||||
null, |
||||
], |
||||
'Call with uid, key, null value and update-only option should fail check' => [ |
||||
[['uid', 'username'], ['key', 'configkey'], ['value', null]], |
||||
[['update-only', true]], |
||||
[], |
||||
true, |
||||
'The "update-only" option can only be used together with "value".', |
||||
], |
||||
|
||||
'Call with uid, key and delete option should pass check' => [ |
||||
[['uid', 'username'], ['key', 'configkey']], |
||||
[['delete', true]], |
||||
[], |
||||
true, |
||||
null, |
||||
], |
||||
'Call with uid, empty key and delete option should fail check' => [ |
||||
[['uid', 'username'], ['key', '']], |
||||
[['delete', true]], |
||||
[], |
||||
true, |
||||
'The "delete" option can only be used when specifying a key.', |
||||
], |
||||
'Call with uid, key, delete option and default-value should fail check' => [ |
||||
[['uid', 'username'], ['key', 'configkey']], |
||||
[['delete', true]], |
||||
[['--default-value', false, true]], |
||||
true, |
||||
'The "delete" option can not be used together with "default-value".', |
||||
], |
||||
'Call with uid, key, empty value and delete option should fail check' => [ |
||||
[['uid', 'username'], ['key', 'configkey'], ['value', '']], |
||||
[['delete', true]], |
||||
[], |
||||
true, |
||||
'The "delete" option can not be used together with "value".', |
||||
], |
||||
'Call with uid, key, delete and error-if-not-exists should pass check' => [ |
||||
[['uid', 'username'], ['key', 'configkey']], |
||||
[['delete', true], ['error-if-not-exists', true]], |
||||
[], |
||||
true, |
||||
null, |
||||
], |
||||
'Call with uid, key and error-if-not-exists should fail check' => [ |
||||
[['uid', 'username'], ['key', 'configkey']], |
||||
[['delete', false], ['error-if-not-exists', true]], |
||||
[], |
||||
true, |
||||
'The "error-if-not-exists" option can only be used together with "delete".', |
||||
], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataCheckInput |
||||
*/ |
||||
public function testCheckInput(array $arguments, array $options, array $parameterOptions, bool $existingUser, ?string $expectedException): void { |
||||
$this->consoleInput->expects($this->any()) |
||||
->method('getArgument') |
||||
->willReturnMap($arguments); |
||||
$this->consoleInput->expects($this->any()) |
||||
->method('getOption') |
||||
->willReturnMap($options); |
||||
$this->consoleInput->expects($this->any()) |
||||
->method('hasParameterOption') |
||||
->willReturnCallback(function (string|array $values, bool $onlyParams = false) use ($parameterOptions): bool { |
||||
$arguments = func_get_args(); |
||||
foreach ($parameterOptions as $parameterOption) { |
||||
// check the arguments of the function, if they are the same, return the mocked value |
||||
if (array_diff($arguments, $parameterOption) === []) { |
||||
return end($parameterOption); |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
}); |
||||
|
||||
$returnedUser = null; |
||||
if ($existingUser) { |
||||
$mockUser = $this->createMock(IUser::class); |
||||
$mockUser->expects($this->once())->method('getUID')->willReturn('user'); |
||||
$returnedUser = $mockUser; |
||||
} |
||||
$this->userManager->expects($this->once()) |
||||
->method('get') |
||||
->willReturn($returnedUser); |
||||
|
||||
$command = $this->getCommand(); |
||||
try { |
||||
$this->invokePrivate($command, 'checkInput', [$this->consoleInput]); |
||||
$this->assertNull($expectedException); |
||||
} catch (\InvalidArgumentException $e) { |
||||
$this->assertEquals($expectedException, $e->getMessage()); |
||||
} |
||||
} |
||||
|
||||
public function testCheckInputExceptionCatch(): void { |
||||
$command = $this->getCommand(['checkInput']); |
||||
$command->expects($this->once()) |
||||
->method('checkInput') |
||||
->willThrowException(new \InvalidArgumentException('test')); |
||||
|
||||
$this->consoleOutput->expects($this->once()) |
||||
->method('writeln') |
||||
->with('<error>test</error>'); |
||||
|
||||
$this->assertEquals(1, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); |
||||
} |
||||
|
||||
public static function dataExecuteDeleteProfileProperty(): array { |
||||
return [ |
||||
'Deleting existing property should succeed' => ['address', 'Berlin', false, null, Command::SUCCESS], |
||||
'Deleting existing property with error-if-not-exists should succeed' => ['address', 'Berlin', true, null, Command::SUCCESS], |
||||
'Deleting non-existing property should succeed' => ['address', '', false, null, Command::SUCCESS], |
||||
'Deleting non-existing property with error-if-not-exists should fail' => ['address', '', true, '<error>The property does not exist for user "username".</error>', Command::FAILURE], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* Tests the deletion mechanism on profile settings. |
||||
* |
||||
* @dataProvider dataExecuteDeleteProfileProperty |
||||
*/ |
||||
public function testExecuteDeleteProfileProperty(string $configKey, string $value, bool $errorIfNotExists, ?string $expectedLine, int $expectedReturn): void { |
||||
$uid = 'username'; |
||||
$appName = 'profile'; |
||||
$command = $this->getCommand([ |
||||
'writeArrayInOutputFormat', |
||||
'checkInput', |
||||
]); |
||||
|
||||
$this->consoleInput->expects($this->any()) |
||||
->method('getArgument') |
||||
->willReturnMap([ |
||||
['uid', $uid], |
||||
['app', $appName], |
||||
['key', $configKey], |
||||
]); |
||||
|
||||
$mocks = $this->setupProfilePropertiesMock([$configKey => $value]); |
||||
|
||||
$command->expects($this->once()) |
||||
->method('checkInput') |
||||
->willReturn($mocks['userMock']); |
||||
|
||||
$this->consoleInput->expects($this->atLeastOnce()) |
||||
->method('hasParameterOption') |
||||
->willReturnMap([ |
||||
['--delete', false, true], |
||||
['--error-if-not-exists', false, $errorIfNotExists], |
||||
]); |
||||
|
||||
if ($expectedLine === null) { |
||||
$this->consoleOutput->expects($this->never()) |
||||
->method('writeln'); |
||||
$mocks['profilePropertiesMocks'][0]->expects($this->once()) |
||||
->method('setValue') |
||||
->with(''); |
||||
$this->accountManager->expects($this->once()) |
||||
->method('updateAccount') |
||||
->with($mocks['accountMock']); |
||||
} else { |
||||
$this->consoleOutput->expects($this->once()) |
||||
->method('writeln') |
||||
->with($expectedLine); |
||||
$this->accountManager->expects($this->never()) |
||||
->method('updateAccount'); |
||||
} |
||||
|
||||
$this->assertEquals($expectedReturn, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); |
||||
} |
||||
|
||||
public function testExecuteSetProfileProperty(): void { |
||||
$command = $this->getCommand([ |
||||
'writeArrayInOutputFormat', |
||||
'checkInput', |
||||
]); |
||||
|
||||
$uid = 'username'; |
||||
$propertyKey = 'address'; |
||||
$propertyValue = 'Barcelona'; |
||||
|
||||
$this->consoleInput->expects($this->atLeast(3)) |
||||
->method('getArgument') |
||||
->willReturnMap([ |
||||
['uid', $uid], |
||||
['key', $propertyKey], |
||||
['value', $propertyValue], |
||||
]); |
||||
|
||||
$mocks = $this->setupProfilePropertiesMock([$propertyKey => $propertyValue]); |
||||
|
||||
$command->expects($this->once()) |
||||
->method('checkInput') |
||||
->willReturn($mocks['userMock']); |
||||
|
||||
$mocks['profilePropertiesMocks'][0]->expects($this->once()) |
||||
->method('setValue') |
||||
->with($propertyValue); |
||||
$this->accountManager->expects($this->once()) |
||||
->method('updateAccount') |
||||
->with($mocks['accountMock']); |
||||
|
||||
$this->assertEquals(0, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); |
||||
} |
||||
|
||||
public static function dataExecuteGet(): array { |
||||
return [ |
||||
'Get property with set value should pass' => ['configkey', 'value', null, 'value', Command::SUCCESS], |
||||
'Get property with empty value and default-value option should pass' => ['configkey', '', 'default-value', 'default-value', Command::SUCCESS], |
||||
'Get property with empty value should fail' => ['configkey', '', null, '<error>The property does not exist for user "username".</error>', Command::FAILURE], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataExecuteGet |
||||
*/ |
||||
public function testExecuteGet(string $key, string $value, ?string $defaultValue, string $expectedLine, int $expectedReturn): void { |
||||
$command = $this->getCommand([ |
||||
'writeArrayInOutputFormat', |
||||
'checkInput', |
||||
]); |
||||
|
||||
$uid = 'username'; |
||||
|
||||
$this->consoleInput->expects($this->any()) |
||||
->method('getArgument') |
||||
->willReturnMap([ |
||||
['uid', $uid], |
||||
['key', $key], |
||||
]); |
||||
|
||||
$mocks = $this->setupProfilePropertiesMock([$key => $value]); |
||||
|
||||
$command->expects($this->once()) |
||||
->method('checkInput') |
||||
->willReturn($mocks['userMock']); |
||||
|
||||
if ($value === '') { |
||||
if ($defaultValue === null) { |
||||
$this->consoleInput->expects($this->atLeastOnce()) |
||||
->method('hasParameterOption') |
||||
->willReturn(false); |
||||
} else { |
||||
$this->consoleInput->expects($this->atLeastOnce()) |
||||
->method('hasParameterOption') |
||||
->willReturnCallback(fn (string|array $values): bool => $values === '--default-value'); |
||||
$this->consoleInput->expects($this->once()) |
||||
->method('getOption') |
||||
->with('default-value') |
||||
->willReturn($defaultValue); |
||||
} |
||||
} |
||||
|
||||
$this->consoleOutput->expects($this->once()) |
||||
->method('writeln') |
||||
->with($expectedLine); |
||||
|
||||
$this->assertEquals($expectedReturn, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); |
||||
} |
||||
|
||||
public function testExecuteList(): void { |
||||
$uid = 'username'; |
||||
$profileData = [ |
||||
'pronouns' => 'they/them', |
||||
'address' => 'Berlin', |
||||
]; |
||||
|
||||
$command = $this->getCommand([ |
||||
'writeArrayInOutputFormat', |
||||
'checkInput', |
||||
]); |
||||
|
||||
$this->consoleInput->expects($this->any()) |
||||
->method('getArgument') |
||||
->willReturnMap([ |
||||
['uid', $uid], |
||||
['key', ''], |
||||
]); |
||||
|
||||
$mocks = $this->setupProfilePropertiesMock(['address' => $profileData['address'], 'pronouns' => $profileData['pronouns']]); |
||||
|
||||
$command->expects($this->once()) |
||||
->method('checkInput') |
||||
->willReturn($mocks['userMock']); |
||||
|
||||
$command->expects($this->once()) |
||||
->method('writeArrayInOutputFormat') |
||||
->with($this->consoleInput, $this->consoleOutput, $profileData); |
||||
|
||||
|
||||
$this->assertEquals(0, $this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput])); |
||||
} |
||||
|
||||
/** |
||||
* Helper to avoid boilerplate in tests in this file when mocking objects |
||||
* of IAccountProperty type. |
||||
* |
||||
* @param array<string, string> $properties the properties to be set up as key => value |
||||
* @return array{ |
||||
* userMock: IUser&MockObject, |
||||
* accountMock: IAccount&MockObject, |
||||
* profilePropertiesMocks: IAccountProperty&MockObject[] |
||||
* } |
||||
*/ |
||||
private function setupProfilePropertiesMock(array $properties): array { |
||||
$userMock = $this->createMock(IUser::class); |
||||
$accountMock = $this->createMock(IAccount::class); |
||||
$this->accountManager->expects($this->atLeastOnce()) |
||||
->method('getAccount') |
||||
->with($userMock) |
||||
->willReturn($accountMock); |
||||
|
||||
/** @var IAccountProperty&MockObject[] $propertiesMocks */ |
||||
$propertiesMocks = []; |
||||
foreach ($properties as $key => $value) { |
||||
$propertiesMocks[] = $this->getAccountPropertyMock($key, $value); |
||||
} |
||||
|
||||
if (count($properties) === 1) { |
||||
$accountMock->expects($this->atLeastOnce()) |
||||
->method('getProperty') |
||||
->with(array_keys($properties)[0]) |
||||
->willReturn($propertiesMocks[array_key_first($propertiesMocks)]); |
||||
} else { |
||||
$accountMock->expects($this->atLeastOnce()) |
||||
->method('getAllProperties') |
||||
->willReturnCallback(function () use ($propertiesMocks) { |
||||
foreach ($propertiesMocks as $property) { |
||||
yield $property; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
return [ |
||||
'userMock' => $userMock, |
||||
'accountMock' => $accountMock, |
||||
'profilePropertiesMocks' => $propertiesMocks, |
||||
]; |
||||
} |
||||
|
||||
private function getAccountPropertyMock(string $name, string $value): IAccountProperty&MockObject { |
||||
$propertyMock = $this->getMockBuilder(IAccountProperty::class) |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
$propertyMock->expects($this->any()) |
||||
->method('getName') |
||||
->willReturn($name); |
||||
$propertyMock->expects($this->any()) |
||||
->method('getValue') |
||||
->willReturn($value); |
||||
|
||||
return $propertyMock; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue