Skip to content

Commit cbdb8eb

Browse files
authored
IBX-8823: Added CLI command to update user (#86)
* IBX-8823: Added CLI command to update user * cr remarks * cr remarks vol1 * cr remarks vol2 * added test coverage * cr remark vol3
1 parent b41ec1e commit cbdb8eb

File tree

3 files changed

+223
-1
lines changed

3 files changed

+223
-1
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ parameters:
5151
path: src/bundle/Controller/PasswordResetController.php
5252

5353
-
54-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\FormInterface\\:\\:getClickedButton\\(\\)\\.$#"
54+
message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\FormInterface\\<mixed\\>\\:\\:getClickedButton\\(\\)\\.$#"
5555
count: 4
5656
path: src/bundle/Controller/UserRegisterController.php
5757

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Bundle\User\Command;
10+
11+
use Ibexa\Contracts\Core\Repository\Repository;
12+
use Ibexa\Contracts\Core\Repository\UserService;
13+
use Ibexa\Contracts\Core\Repository\Values\User\User;
14+
use Ibexa\Contracts\Core\Repository\Values\User\UserUpdateStruct;
15+
use Symfony\Component\Console\Attribute\AsCommand;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\Input\InputArgument;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Input\InputOption;
20+
use Symfony\Component\Console\Output\OutputInterface;
21+
use Symfony\Component\Console\Style\SymfonyStyle;
22+
23+
#[AsCommand(name: 'ibexa:user:update-user', description: 'Updates basic user data.')]
24+
final class UpdateUserCommand extends Command
25+
{
26+
public function __construct(
27+
private readonly UserService $userService,
28+
private readonly Repository $repository,
29+
) {
30+
parent::__construct();
31+
}
32+
33+
protected function configure(): void
34+
{
35+
$this->addArgument(
36+
'user',
37+
InputArgument::REQUIRED,
38+
'User login',
39+
);
40+
$this->addOption(
41+
'password',
42+
null,
43+
InputOption::VALUE_OPTIONAL,
44+
'New plaintext password (input will be in a "hidden" mode)',
45+
false
46+
);
47+
$this->addOption(
48+
'email',
49+
null,
50+
InputOption::VALUE_REQUIRED,
51+
'New e-mail address',
52+
);
53+
$this->addOption(
54+
'enable',
55+
null,
56+
InputOption::VALUE_NONE,
57+
'Flag enabling the user being updated',
58+
);
59+
$this->addOption(
60+
'disable',
61+
null,
62+
InputOption::VALUE_NONE,
63+
'Flag disabling the user being updated',
64+
);
65+
}
66+
67+
protected function interact(InputInterface $input, OutputInterface $output): void
68+
{
69+
$io = new SymfonyStyle($input, $output);
70+
$password = $input->getOption('password');
71+
72+
if ($password !== null) {
73+
return;
74+
}
75+
76+
$password = $io->askHidden('Password (your input will be hidden)');
77+
$input->setOption('password', $password);
78+
}
79+
80+
/**
81+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
82+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException
83+
*/
84+
protected function execute(InputInterface $input, OutputInterface $output): int
85+
{
86+
$io = new SymfonyStyle($input, $output);
87+
88+
$userReference = $input->getArgument('user');
89+
$password = $input->getOption('password');
90+
$enable = $input->getOption('enable');
91+
$disable = $input->getOption('disable');
92+
$email = $input->getOption('email');
93+
94+
if (!$password && !$enable && !$disable && $email === null) {
95+
$io->error('No new user data specified, exiting.');
96+
97+
return Command::FAILURE;
98+
}
99+
100+
$user = $this->userService->loadUserByLogin($userReference);
101+
102+
if ($enable && $disable) {
103+
$io->error('--enable and --disable options cannot be used simultaneously.');
104+
105+
return Command::FAILURE;
106+
}
107+
108+
$userUpdateStruct = new UserUpdateStruct();
109+
$userUpdateStruct->password = $password;
110+
$userUpdateStruct->email = $email;
111+
$userUpdateStruct->enabled = $this->resolveEnabledFlag($enable, $disable);
112+
113+
$this->repository->sudo(
114+
function () use ($user, $userUpdateStruct): User {
115+
return $this->userService->updateUser($user, $userUpdateStruct);
116+
}
117+
);
118+
119+
$io->success(sprintf(
120+
'User "%s" was successfully updated.',
121+
$user->getLogin(),
122+
));
123+
124+
return Command::SUCCESS;
125+
}
126+
127+
private function resolveEnabledFlag(bool $enable, bool $disable): ?bool
128+
{
129+
if (!$enable && !$disable) {
130+
return null;
131+
}
132+
133+
return $enable === true;
134+
}
135+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\Bundle\User\Command;
10+
11+
use Symfony\Bundle\FrameworkBundle\Console\Application;
12+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
13+
use Symfony\Component\Console\Command\Command;
14+
use Symfony\Component\Console\Tester\CommandTester;
15+
16+
final class UpdateUserCommandTest extends KernelTestCase
17+
{
18+
private readonly CommandTester $commandTester;
19+
20+
protected function setUp(): void
21+
{
22+
self::bootKernel();
23+
24+
$application = new Application(self::$kernel);
25+
$application->setAutoExit(false);
26+
27+
$command = $application->find('ibexa:user:update-user');
28+
$this->commandTester = new CommandTester($command);
29+
}
30+
31+
public function testExecuteWithoutOptionsReturnsFailure(): void
32+
{
33+
$this->commandTester->execute([
34+
'user' => 'anonymous',
35+
]);
36+
37+
self::assertStringContainsString(
38+
'No new user data specified, exiting.',
39+
$this->commandTester->getDisplay()
40+
);
41+
42+
self::assertSame(
43+
Command::FAILURE,
44+
$this->commandTester->getStatusCode()
45+
);
46+
}
47+
48+
public function testExecuteWithEnableAndDisableOptionsReturnsFailure(): void
49+
{
50+
$this->commandTester->execute(
51+
[
52+
'user' => 'anonymous',
53+
'--enable' => true,
54+
'--disable' => true,
55+
],
56+
);
57+
58+
self::assertStringContainsString(
59+
'--enable and --disable options cannot be used simultaneously.',
60+
$this->commandTester->getDisplay()
61+
);
62+
63+
self::assertSame(
64+
Command::FAILURE,
65+
$this->commandTester->getStatusCode()
66+
);
67+
}
68+
69+
public function testExecuteReturnsSuccess(): void
70+
{
71+
$this->commandTester->execute(
72+
[
73+
'user' => 'anonymous',
74+
'--password' => true,
75+
'--email' => '[email protected]',
76+
'--enable' => true,
77+
],
78+
);
79+
80+
self::assertStringContainsString(
81+
'User "anonymous" was successfully updated.',
82+
$this->commandTester->getDisplay()
83+
);
84+
85+
$this->commandTester->assertCommandIsSuccessful();
86+
}
87+
}

0 commit comments

Comments
 (0)