From ba23c098df94a14120fea82554aafeb7d9666434 Mon Sep 17 00:00:00 2001 From: konradoboza Date: Tue, 27 Aug 2024 11:46:44 +0200 Subject: [PATCH 1/6] IBX-8823: Added CLI command to update user --- phpstan-baseline.neon | 2 +- src/bundle/Command/UpdateUserCommand.php | 119 +++++++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/bundle/Command/UpdateUserCommand.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index aa56959..e7981c9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -51,7 +51,7 @@ parameters: path: src/bundle/Controller/PasswordResetController.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\FormInterface\\:\\:getClickedButton\\(\\)\\.$#" + message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\FormInterface\\\\:\\:getClickedButton\\(\\)\\.$#" count: 4 path: src/bundle/Controller/UserRegisterController.php diff --git a/src/bundle/Command/UpdateUserCommand.php b/src/bundle/Command/UpdateUserCommand.php new file mode 100644 index 0000000..db76ce5 --- /dev/null +++ b/src/bundle/Command/UpdateUserCommand.php @@ -0,0 +1,119 @@ +addArgument( + 'user', + InputArgument::REQUIRED, + 'User reference (id or login)', + ); + $this->addOption( + 'password', + null, + InputOption::VALUE_NONE, + 'New plaintext password (type will be in a "hidden" mode)', + ); + $this->addOption( + 'email', + null, + InputOption::VALUE_REQUIRED, + 'New e-mail address', + ); + $this->addOption( + 'enable', + null, + InputOption::VALUE_NONE, + 'Flag enabling the user being updated', + ); + $this->addOption( + 'disable', + null, + InputOption::VALUE_NONE, + 'Flag disabling the user being updated', + ); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $userReference = $input->getArgument('user'); + $password = $input->getOption('password'); + $enable = $input->getOption('enable'); + $disable = $input->getOption('disable'); + $email = $input->getOption('email'); + + if (!$password && !$enable && !$disable && $email === null) { + $io->success('No new user data specified, exiting.'); + + return Command::SUCCESS; + } + + if (is_numeric($userReference)) { + $user = $this->userService->loadUser((int)$userReference); + } else { + $user = $this->userService->loadUserByLogin($userReference); + } + + if ($enable && $disable) { + $io->error('--enable and --disable options cannot be used simultaneously.'); + + return Command::FAILURE; + } + + if ($password) { + $password = $io->askHidden('Password (your type will be hidden)'); + $input->setOption('password', $password); + } + + $userUpdateStruct = new UserUpdateStruct(); + $userUpdateStruct->password = $input->getOption('password'); + $userUpdateStruct->email = $email; + $userUpdateStruct->enabled = $enable === true || !$disable; + + $this->repository->sudo( + function () use ($user, $userUpdateStruct): User { + return $this->userService->updateUser($user, $userUpdateStruct); + } + ); + + $io->success('User was successfully updated.'); + + return Command::SUCCESS; + } +} From fc94767e29f03ef63dfccb9f091bc1f90f82c0db Mon Sep 17 00:00:00 2001 From: konradoboza Date: Tue, 27 Aug 2024 12:53:22 +0200 Subject: [PATCH 2/6] cr remarks --- src/bundle/Command/UpdateUserCommand.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/bundle/Command/UpdateUserCommand.php b/src/bundle/Command/UpdateUserCommand.php index db76ce5..390a629 100644 --- a/src/bundle/Command/UpdateUserCommand.php +++ b/src/bundle/Command/UpdateUserCommand.php @@ -36,13 +36,13 @@ protected function configure(): void $this->addArgument( 'user', InputArgument::REQUIRED, - 'User reference (id or login)', + 'User login', ); $this->addOption( 'password', null, InputOption::VALUE_NONE, - 'New plaintext password (type will be in a "hidden" mode)', + 'New plaintext password (input will be in a "hidden" mode)', ); $this->addOption( 'email', @@ -84,11 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - if (is_numeric($userReference)) { - $user = $this->userService->loadUser((int)$userReference); - } else { - $user = $this->userService->loadUserByLogin($userReference); - } + $user = $this->userService->loadUserByLogin($userReference); if ($enable && $disable) { $io->error('--enable and --disable options cannot be used simultaneously.'); @@ -97,7 +93,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($password) { - $password = $io->askHidden('Password (your type will be hidden)'); + $password = $io->askHidden('Password (your input will be hidden)'); $input->setOption('password', $password); } From e03e29df352d49db17424c2a3efdac1d34b7e39a Mon Sep 17 00:00:00 2001 From: konradoboza Date: Tue, 27 Aug 2024 16:45:05 +0200 Subject: [PATCH 3/6] cr remarks vol1 --- phpstan-baseline.neon | 5 +++ src/bundle/Command/UpdateUserCommand.php | 41 +++++++++++++++++------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e7981c9..3f9b34d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,6 +10,11 @@ parameters: count: 1 path: src/bundle/Command/AuditUserDatabaseCommand.php + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/bundle/Command/UpdateUserCommand.php + - message: "#^Method Ibexa\\\\Bundle\\\\User\\\\Controller\\\\Controller\\:\\:performAccessCheck\\(\\) has no return type specified\\.$#" count: 1 diff --git a/src/bundle/Command/UpdateUserCommand.php b/src/bundle/Command/UpdateUserCommand.php index 390a629..805f92a 100644 --- a/src/bundle/Command/UpdateUserCommand.php +++ b/src/bundle/Command/UpdateUserCommand.php @@ -26,9 +26,8 @@ final class UpdateUserCommand extends Command public function __construct( private readonly UserService $userService, private readonly Repository $repository, - ?string $name = null ) { - parent::__construct($name); + parent::__construct(); } protected function configure(): void @@ -41,8 +40,9 @@ protected function configure(): void $this->addOption( 'password', null, - InputOption::VALUE_NONE, + InputOption::VALUE_OPTIONAL, 'New plaintext password (input will be in a "hidden" mode)', + false ); $this->addOption( 'email', @@ -64,6 +64,19 @@ protected function configure(): void ); } + protected function interact(InputInterface $input, OutputInterface $output): void + { + $io = new SymfonyStyle($input, $output); + $password = $input->getOption('password'); + + if ($password !== null) { + return; + } + + $password = $io->askHidden('Password (your input will be hidden)'); + $input->setOption('password', $password); + } + /** * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException @@ -79,9 +92,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $email = $input->getOption('email'); if (!$password && !$enable && !$disable && $email === null) { - $io->success('No new user data specified, exiting.'); + $io->error('No new user data specified, exiting.'); - return Command::SUCCESS; + return Command::FAILURE; } $user = $this->userService->loadUserByLogin($userReference); @@ -92,15 +105,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } - if ($password) { - $password = $io->askHidden('Password (your input will be hidden)'); - $input->setOption('password', $password); - } - $userUpdateStruct = new UserUpdateStruct(); - $userUpdateStruct->password = $input->getOption('password'); + $userUpdateStruct->password = $password; $userUpdateStruct->email = $email; - $userUpdateStruct->enabled = $enable === true || !$disable; + $userUpdateStruct->enabled = $this->resolveEnabledFlag($enable, $disable); $this->repository->sudo( function () use ($user, $userUpdateStruct): User { @@ -112,4 +120,13 @@ function () use ($user, $userUpdateStruct): User { return Command::SUCCESS; } + + private function resolveEnabledFlag(bool $enable, bool $disable): ?bool + { + if (!$enable && !$disable) { + return null; + } + + return $enable === true || !$disable; + } } From 59503aa32761c7e87a3de174b3e009c605409cf1 Mon Sep 17 00:00:00 2001 From: konradoboza Date: Wed, 28 Aug 2024 08:09:53 +0200 Subject: [PATCH 4/6] cr remarks vol2 --- phpstan-baseline.neon | 5 ----- src/bundle/Command/UpdateUserCommand.php | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3f9b34d..e7981c9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,11 +10,6 @@ parameters: count: 1 path: src/bundle/Command/AuditUserDatabaseCommand.php - - - message: "#^Negated boolean expression is always false\\.$#" - count: 1 - path: src/bundle/Command/UpdateUserCommand.php - - message: "#^Method Ibexa\\\\Bundle\\\\User\\\\Controller\\\\Controller\\:\\:performAccessCheck\\(\\) has no return type specified\\.$#" count: 1 diff --git a/src/bundle/Command/UpdateUserCommand.php b/src/bundle/Command/UpdateUserCommand.php index 805f92a..ce64d99 100644 --- a/src/bundle/Command/UpdateUserCommand.php +++ b/src/bundle/Command/UpdateUserCommand.php @@ -127,6 +127,6 @@ private function resolveEnabledFlag(bool $enable, bool $disable): ?bool return null; } - return $enable === true || !$disable; + return $enable === true; } } From 6932bf954eed51ddd688dc0769fd27c9f99841e7 Mon Sep 17 00:00:00 2001 From: konradoboza Date: Wed, 28 Aug 2024 09:13:10 +0200 Subject: [PATCH 5/6] added test coverage --- .../bundle/Command/UpdateUserCommandTest.php | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/bundle/Command/UpdateUserCommandTest.php diff --git a/tests/bundle/Command/UpdateUserCommandTest.php b/tests/bundle/Command/UpdateUserCommandTest.php new file mode 100644 index 0000000..244852d --- /dev/null +++ b/tests/bundle/Command/UpdateUserCommandTest.php @@ -0,0 +1,87 @@ +setAutoExit(false); + + $command = $application->find('ibexa:user:update-user'); + $this->commandTester = new CommandTester($command); + } + + public function testExecuteWithoutOptionsReturnsFailure(): void + { + $this->commandTester->execute([ + 'user' => 'anonymous', + ]); + + self::assertStringContainsString( + 'No new user data specified, exiting.', + $this->commandTester->getDisplay() + ); + + self::assertSame( + Command::FAILURE, + $this->commandTester->getStatusCode() + ); + } + + public function testExecuteWithEnableAndDisableOptionsReturnsFailure(): void + { + $this->commandTester->execute( + [ + 'user' => 'anonymous', + '--enable' => true, + '--disable' => true, + ], + ); + + self::assertStringContainsString( + '--enable and --disable options cannot be used simultaneously.', + $this->commandTester->getDisplay() + ); + + self::assertSame( + Command::FAILURE, + $this->commandTester->getStatusCode() + ); + } + + public function testExecuteReturnsSuccess(): void + { + $this->commandTester->execute( + [ + 'user' => 'anonymous', + '--password' => true, + '--email' => 'foo@bar.com', + '--enable' => true, + ], + ); + + self::assertStringContainsString( + 'User was successfully updated.', + $this->commandTester->getDisplay() + ); + + $this->commandTester->assertCommandIsSuccessful(); + } +} From 869ecca05b4901eb262d4ebebedef089cacc90d3 Mon Sep 17 00:00:00 2001 From: konradoboza Date: Wed, 28 Aug 2024 09:41:15 +0200 Subject: [PATCH 6/6] cr remark vol3 --- src/bundle/Command/UpdateUserCommand.php | 5 ++++- tests/bundle/Command/UpdateUserCommandTest.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bundle/Command/UpdateUserCommand.php b/src/bundle/Command/UpdateUserCommand.php index ce64d99..533b70a 100644 --- a/src/bundle/Command/UpdateUserCommand.php +++ b/src/bundle/Command/UpdateUserCommand.php @@ -116,7 +116,10 @@ function () use ($user, $userUpdateStruct): User { } ); - $io->success('User was successfully updated.'); + $io->success(sprintf( + 'User "%s" was successfully updated.', + $user->getLogin(), + )); return Command::SUCCESS; } diff --git a/tests/bundle/Command/UpdateUserCommandTest.php b/tests/bundle/Command/UpdateUserCommandTest.php index 244852d..134c7c7 100644 --- a/tests/bundle/Command/UpdateUserCommandTest.php +++ b/tests/bundle/Command/UpdateUserCommandTest.php @@ -78,7 +78,7 @@ public function testExecuteReturnsSuccess(): void ); self::assertStringContainsString( - 'User was successfully updated.', + 'User "anonymous" was successfully updated.', $this->commandTester->getDisplay() );