diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php index ce8427630..fcb4e512a 100644 --- a/app/Console/Commands/User/DeleteUserCommand.php +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -79,14 +79,14 @@ class DeleteUserCommand extends Command $results = $this->repository->search($search)->all(); if (count($results) < 1) { $this->error(trans('command/messages.user.no_users_found')); - if (! $this->option('no-interaction')) { + if ($this->input->isInteractive()) { return $this->handle(); } return false; } - if (! $this->option('no-interaction')) { + if ($this->input->isInteractive()) { $tableValues = []; foreach ($results as $user) { $tableValues[] = [$user->id, $user->email, $user->name]; @@ -106,7 +106,7 @@ class DeleteUserCommand extends Command $deleteUser = $results->first(); } - if ($this->confirm(trans('command/messages.user.confirm_delete')) || $this->option('no-interaction')) { + if ($this->confirm(trans('command/messages.user.confirm_delete')) || ! $this->input->isInteractive()) { $this->deletionService->handle($deleteUser); $this->info(trans('command/messages.user.deleted')); } diff --git a/tests/Assertions/CommandAssertionsTrait.php b/tests/Assertions/CommandAssertionsTrait.php new file mode 100644 index 000000000..f1912dda0 --- /dev/null +++ b/tests/Assertions/CommandAssertionsTrait.php @@ -0,0 +1,41 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Assertions; + +use PHPUnit\Framework\Assert; + +trait CommandAssertionsTrait +{ + /** + * Assert that an output table contains a value. + * + * @param mixed $string + * @param string $display + */ + public function assertTableContains($string, $display) + { + Assert::assertRegExp('/\|(\s+)' . preg_quote($string) . '(\s+)\|/', $display, 'Assert that a response table contains a value.'); + } +} diff --git a/tests/Assertions/ControllerAssertionsTrait.php b/tests/Assertions/ControllerAssertionsTrait.php index 2211a080a..c8ebc86db 100644 --- a/tests/Assertions/ControllerAssertionsTrait.php +++ b/tests/Assertions/ControllerAssertionsTrait.php @@ -26,7 +26,7 @@ namespace Tests\Assertions; use Illuminate\View\View; use Illuminate\Http\Response; -use PHPUnit_Framework_Assert; +use PHPUnit\Framework\Assert; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; @@ -39,7 +39,7 @@ trait ControllerAssertionsTrait */ public function assertIsViewResponse($response) { - PHPUnit_Framework_Assert::assertInstanceOf(View::class, $response); + Assert::assertInstanceOf(View::class, $response); } /** @@ -49,7 +49,7 @@ trait ControllerAssertionsTrait */ public function assertIsRedirectResponse($response) { - PHPUnit_Framework_Assert::assertInstanceOf(RedirectResponse::class, $response); + Assert::assertInstanceOf(RedirectResponse::class, $response); } /** @@ -59,7 +59,7 @@ trait ControllerAssertionsTrait */ public function assertIsJsonResponse($response) { - PHPUnit_Framework_Assert::assertInstanceOf(JsonResponse::class, $response); + Assert::assertInstanceOf(JsonResponse::class, $response); } /** @@ -69,7 +69,7 @@ trait ControllerAssertionsTrait */ public function assertIsResponse($response) { - PHPUnit_Framework_Assert::assertInstanceOf(Response::class, $response); + Assert::assertInstanceOf(Response::class, $response); } /** @@ -80,7 +80,7 @@ trait ControllerAssertionsTrait */ public function assertViewNameEquals($name, $view) { - PHPUnit_Framework_Assert::assertEquals($name, $view->getName()); + Assert::assertEquals($name, $view->getName()); } /** @@ -91,7 +91,7 @@ trait ControllerAssertionsTrait */ public function assertViewNameNotEquals($name, $view) { - PHPUnit_Framework_Assert::assertNotEquals($name, $view->getName()); + Assert::assertNotEquals($name, $view->getName()); } /** @@ -103,12 +103,12 @@ trait ControllerAssertionsTrait public function assertViewHasKey($attribute, $view) { if (str_contains($attribute, '.')) { - PHPUnit_Framework_Assert::assertNotEquals( + Assert::assertNotEquals( '__TEST__FAIL', array_get($view->getData(), $attribute, '__TEST__FAIL') ); } else { - PHPUnit_Framework_Assert::assertArrayHasKey($attribute, $view->getData()); + Assert::assertArrayHasKey($attribute, $view->getData()); } } @@ -121,12 +121,12 @@ trait ControllerAssertionsTrait public function assertViewNotHasKey($attribute, $view) { if (str_contains($attribute, '.')) { - PHPUnit_Framework_Assert::assertEquals( + Assert::assertEquals( '__TEST__PASS', array_get($view->getData(), $attribute, '__TEST__PASS') ); } else { - PHPUnit_Framework_Assert::assertArrayNotHasKey($attribute, $view->getData()); + Assert::assertArrayNotHasKey($attribute, $view->getData()); } } @@ -139,7 +139,7 @@ trait ControllerAssertionsTrait */ public function assertViewKeyEquals($attribute, $value, $view) { - PHPUnit_Framework_Assert::assertEquals($value, array_get($view->getData(), $attribute, '__TEST__FAIL')); + Assert::assertEquals($value, array_get($view->getData(), $attribute, '__TEST__FAIL')); } /** @@ -151,7 +151,7 @@ trait ControllerAssertionsTrait */ public function assertViewKeyNotEquals($attribute, $value, $view) { - PHPUnit_Framework_Assert::assertNotEquals($value, array_get($view->getData(), $attribute, '__TEST__FAIL')); + Assert::assertNotEquals($value, array_get($view->getData(), $attribute, '__TEST__FAIL')); } /** @@ -163,7 +163,7 @@ trait ControllerAssertionsTrait */ public function assertRedirectRouteEquals($route, $response, array $args = []) { - PHPUnit_Framework_Assert::assertEquals(route($route, $args), $response->getTargetUrl()); + Assert::assertEquals(route($route, $args), $response->getTargetUrl()); } /** @@ -174,7 +174,7 @@ trait ControllerAssertionsTrait */ public function assertRedirectUrlEquals($url, $response) { - PHPUnit_Framework_Assert::assertEquals($url, $response->getTargetUrl()); + Assert::assertEquals($url, $response->getTargetUrl()); } /** @@ -185,7 +185,7 @@ trait ControllerAssertionsTrait */ public function assertResponseCodeEquals($code, $response) { - PHPUnit_Framework_Assert::assertEquals($code, $response->getStatusCode()); + Assert::assertEquals($code, $response->getStatusCode()); } /** @@ -196,7 +196,7 @@ trait ControllerAssertionsTrait */ public function assertResponseCodeNotEquals($code, $response) { - PHPUnit_Framework_Assert::assertNotEquals($code, $response->getStatusCode()); + Assert::assertNotEquals($code, $response->getStatusCode()); } /** @@ -206,7 +206,7 @@ trait ControllerAssertionsTrait */ public function assertResponseHasJsonHeaders($response) { - PHPUnit_Framework_Assert::assertEquals('application/json', $response->headers->get('content-type')); + Assert::assertEquals('application/json', $response->headers->get('content-type')); } /** @@ -217,6 +217,6 @@ trait ControllerAssertionsTrait */ public function assertResponseJsonEquals($json, $response) { - PHPUnit_Framework_Assert::assertEquals(is_array($json) ? json_encode($json) : $json, $response->getContent()); + Assert::assertEquals(is_array($json) ? json_encode($json) : $json, $response->getContent()); } } diff --git a/tests/Unit/Commands/User/DeleteUserCommandTest.php b/tests/Unit/Commands/User/DeleteUserCommandTest.php new file mode 100644 index 000000000..b362dac60 --- /dev/null +++ b/tests/Unit/Commands/User/DeleteUserCommandTest.php @@ -0,0 +1,229 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Commands\User; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\User; +use Tests\Assertions\CommandAssertionsTrait; +use Pterodactyl\Services\Users\UserDeletionService; +use Symfony\Component\Console\Tester\CommandTester; +use Pterodactyl\Console\Commands\User\DeleteUserCommand; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; + +class DeleteUserCommandTest extends TestCase +{ + use CommandAssertionsTrait; + + /** + * @var \Pterodactyl\Console\Commands\User\DeleteUserCommand + */ + protected $command; + + /** + * @var \Pterodactyl\Services\Users\UserDeletionService + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->deletionService = m::mock(UserDeletionService::class); + $this->repository = m::mock(UserRepositoryInterface::class); + + $this->command = new DeleteUserCommand($this->deletionService, $this->repository); + $this->command->setLaravel($this->app); + } + + /** + * Test that a user can be deleted using a normal pathway. + */ + public function testCommandWithNoOptions() + { + $users = collect([ + $user1 = factory(User::class)->make(), + $user2 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('search')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); + + $response = new CommandTester($this->command); + $response->setInputs([$user1->username, $user1->id, 'yes']); + $response->execute([]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertTableContains($user1->id, $display); + $this->assertTableContains($user1->email, $display); + $this->assertTableContains($user1->name, $display); + $this->assertContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test a bad first user search followed by a good second search. + */ + public function testCommandWithInvalidInitialSearch() + { + $users = collect([ + $user1 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('search')->with('noResults')->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn([]); + $this->repository->shouldReceive('search')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); + + $response = new CommandTester($this->command); + $response->setInputs(['noResults', $user1->username, $user1->id, 'yes']); + $response->execute([]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.no_users_found'), $display); + $this->assertTableContains($user1->id, $display); + $this->assertTableContains($user1->email, $display); + $this->assertTableContains($user1->name, $display); + $this->assertContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test the ability to re-do a search for a user account. + */ + public function testReSearchAbility() + { + $users = collect([ + $user1 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('search')->with($user1->username)->twice()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->twice()->andReturn($users); + $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); + + $response = new CommandTester($this->command); + $response->setInputs([$user1->username, 0, $user1->username, $user1->id, 'yes']); + $response->execute([]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.select_search_user'), $display); + $this->assertTableContains($user1->id, $display); + $this->assertTableContains($user1->email, $display); + $this->assertTableContains($user1->name, $display); + $this->assertContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test that answering no works as expected when confirming deletion of account. + */ + public function testAnsweringNoToDeletionConfirmationWillNotDeleteUser() + { + $users = collect([ + $user1 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('search')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldNotReceive('handle'); + + $response = new CommandTester($this->command); + $response->setInputs([$user1->username, $user1->id, 'no']); + $response->execute([]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertNotContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test a single result is deleted if there is no interaction setup. + */ + public function testNoInteractionWithSingleResult() + { + $users = collect([ + $user1 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('search')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldReceive('handle')->with($user1)->once()->andReturnNull(); + + $response = new CommandTester($this->command); + $response->execute(['--user' => $user1->username], ['interactive' => false]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.deleted'), $display); + } + + /** + * Test that an error is returned if there is no interaction but multiple results. + */ + public function testNoInteractionWithMultipleResults() + { + $users = collect([ + $user1 = factory(User::class)->make(), + $user2 = factory(User::class)->make(), + ]); + + $this->repository->shouldReceive('search')->with($user1->username)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); + $this->deletionService->shouldNotReceive('handle'); + + $response = new CommandTester($this->command); + $response->execute(['--user' => $user1->username], ['interactive' => false]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.multiple_found'), $display); + } + + /** + * Test that an error is returned if there is no interaction and no results returned. + */ + public function testNoInteractionWithNoResults() + { + $this->repository->shouldReceive('search')->with(123456)->once()->andReturnSelf() + ->shouldReceive('all')->withNoArgs()->once()->andReturn([]); + + $response = new CommandTester($this->command); + $response->execute(['--user' => 123456], ['interactive' => false]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.no_users_found'), $display); + } +} diff --git a/tests/Unit/Commands/User/MakeUserCommandTest.php b/tests/Unit/Commands/User/MakeUserCommandTest.php index d634aa225..19c08dbe8 100644 --- a/tests/Unit/Commands/User/MakeUserCommandTest.php +++ b/tests/Unit/Commands/User/MakeUserCommandTest.php @@ -29,6 +29,7 @@ use Tests\TestCase; use Pterodactyl\Models\User; use Pterodactyl\Services\Users\UserCreationService; use Symfony\Component\Console\Tester\CommandTester; +use Pterodactyl\Console\Commands\User\MakeUserCommand; class MakeUserCommandTest extends TestCase { @@ -51,7 +52,7 @@ class MakeUserCommandTest extends TestCase $this->creationService = m::mock(UserCreationService::class); - $this->command = m::mock('\Pterodactyl\Console\Commands\User\MakeUserCommand[confirm, ask, secret]', [$this->creationService]); + $this->command = new MakeUserCommand($this->creationService); $this->command->setLaravel($this->app); } @@ -62,13 +63,6 @@ class MakeUserCommandTest extends TestCase { $user = factory(User::class)->make(['root_admin' => true]); - $this->command->shouldReceive('confirm')->with(trans('command/messages.user.ask_admin'))->once()->andReturn($user->root_admin); - $this->command->shouldReceive('ask')->with(trans('command/messages.user.ask_email'))->once()->andReturn($user->email); - $this->command->shouldReceive('ask')->with(trans('command/messages.user.ask_username'))->once()->andReturn($user->username); - $this->command->shouldReceive('ask')->with(trans('command/messages.user.ask_name_first'))->once()->andReturn($user->name_first); - $this->command->shouldReceive('ask')->with(trans('command/messages.user.ask_name_last'))->once()->andReturn($user->name_last); - $this->command->shouldReceive('secret')->with(trans('command/messages.user.ask_password'))->once()->andReturn('Password123'); - $this->creationService->shouldReceive('handle')->with([ 'email' => $user->email, 'username' => $user->username, @@ -79,6 +73,9 @@ class MakeUserCommandTest extends TestCase ])->once()->andReturn($user); $response = new CommandTester($this->command); + $response->setInputs([ + 'yes', $user->email, $user->username, $user->name_first, $user->name_last, 'Password123', + ]); $response->execute([]); $display = $response->getDisplay(); @@ -98,13 +95,6 @@ class MakeUserCommandTest extends TestCase { $user = factory(User::class)->make(['root_admin' => true]); - $this->command->shouldReceive('confirm')->with(trans('command/messages.user.ask_admin'))->once()->andReturn($user->root_admin); - $this->command->shouldReceive('ask')->with(trans('command/messages.user.ask_email'))->once()->andReturn($user->email); - $this->command->shouldReceive('ask')->with(trans('command/messages.user.ask_username'))->once()->andReturn($user->username); - $this->command->shouldReceive('ask')->with(trans('command/messages.user.ask_name_first'))->once()->andReturn($user->name_first); - $this->command->shouldReceive('ask')->with(trans('command/messages.user.ask_name_last'))->once()->andReturn($user->name_last); - $this->command->shouldNotReceive('secret'); - $this->creationService->shouldReceive('handle')->with([ 'email' => $user->email, 'username' => $user->username, @@ -115,6 +105,9 @@ class MakeUserCommandTest extends TestCase ])->once()->andReturn($user); $response = new CommandTester($this->command); + $response->setInputs([ + 'yes', $user->email, $user->username, $user->name_first, $user->name_last, + ]); $response->execute(['--no-password' => true]); $display = $response->getDisplay(); @@ -129,10 +122,6 @@ class MakeUserCommandTest extends TestCase { $user = factory(User::class)->make(['root_admin' => false]); - $this->command->shouldNotReceive('confirm'); - $this->command->shouldNotReceive('ask'); - $this->command->shouldNotReceive('secret'); - $this->creationService->shouldReceive('handle')->with([ 'email' => $user->email, 'username' => $user->username,