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