From 8cfdb3acce5a35eeae0098c515b9202145d9f338 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 27 Jun 2020 12:04:41 -0700 Subject: [PATCH] Add test cases for sending a command to a server --- .../Api/Client/Servers/SendCommandRequest.php | 3 +- database/factories/ModelFactory.php | 2 +- .../Api/Client/AccountControllerTest.php | 13 +-- .../Api/Client/ApiKeyControllerTest.php | 4 +- .../Client/ClientApiIntegrationTestCase.php | 53 ++++++++++ .../Api/Client/ClientControllerTest.php | 18 +--- .../Client/Server/CommandControllerTest.php | 100 ++++++++++++++++++ .../Api/Client/TwoFactorControllerTest.php | 13 +-- 8 files changed, 160 insertions(+), 46 deletions(-) create mode 100644 tests/Integration/Api/Client/ClientApiIntegrationTestCase.php create mode 100644 tests/Integration/Api/Client/Server/CommandControllerTest.php diff --git a/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php b/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php index fe0dd1757..3f2f6c196 100644 --- a/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php +++ b/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers; use Pterodactyl\Models\Permission; +use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class SendCommandRequest extends GetServerRequest +class SendCommandRequest extends ClientApiRequest { /** * Determine if the API user has permission to perform this action. diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 553f8a032..6d6208f4f 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -20,7 +20,7 @@ use Pterodactyl\Models\ApiKey; $factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) { return [ - 'uuid' => $faker->unique()->uuid, + 'uuid' => Uuid::uuid4()->toString(), 'uuidShort' => str_random(8), 'name' => $faker->firstName, 'description' => implode(' ', $faker->sentences()), diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php index 9158ef1af..4fbef8749 100644 --- a/tests/Integration/Api/Client/AccountControllerTest.php +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -6,20 +6,9 @@ use Mockery; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Illuminate\Auth\AuthManager; -use Pterodactyl\Tests\Integration\IntegrationTestCase; -class AccountControllerTest extends IntegrationTestCase +class AccountControllerTest extends ClientApiIntegrationTestCase { - /** - * Clean up after running tests. - */ - protected function tearDown(): void - { - User::query()->forceDelete(); - - parent::tearDown(); - } - /** * Test that the user's account details are returned from the account endpoint. */ diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php index f0b35a519..13a0b9c84 100644 --- a/tests/Integration/Api/Client/ApiKeyControllerTest.php +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -5,9 +5,8 @@ namespace Pterodactyl\Tests\Integration\Api\Client; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Pterodactyl\Models\ApiKey; -use Pterodactyl\Tests\Integration\IntegrationTestCase; -class ApiKeyControllerTest extends IntegrationTestCase +class ApiKeyControllerTest extends ClientApiIntegrationTestCase { /** * Cleanup after tests. @@ -15,7 +14,6 @@ class ApiKeyControllerTest extends IntegrationTestCase protected function tearDown(): void { ApiKey::query()->forceDelete(); - User::query()->forceDelete(); parent::tearDown(); } diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php new file mode 100644 index 000000000..ed36e3dd4 --- /dev/null +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -0,0 +1,53 @@ +forceDelete(); + Node::query()->forceDelete(); + Location::query()->forceDelete(); + User::query()->forceDelete(); + + parent::tearDown(); + } + + /** + * Generates a user and a server for that user. If an array of permissions is passed it + * is assumed that the user is actually a subuser of the server. + * + * @param string[] $permissions + * @return array + */ + protected function generateTestAccount(array $permissions = []): array + { + /** @var \Pterodactyl\Models\User $user */ + $user = factory(User::class)->create(); + + if (empty($permissions)) { + return [$user, $this->createServerModel(['user_id' => $user->id])]; + } + + $server = $this->createServerModel(); + + Subuser::query()->create([ + 'user_id' => $user->id, + 'server_id' => $server->id, + 'permissions' => $permissions, + ]); + + return [$user, $server]; + } +} diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php index eb88d122c..1561f59cf 100644 --- a/tests/Integration/Api/Client/ClientControllerTest.php +++ b/tests/Integration/Api/Client/ClientControllerTest.php @@ -2,29 +2,13 @@ namespace Pterodactyl\Tests\Integration\Api\Client; -use Pterodactyl\Models\Node; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Location; use Pterodactyl\Models\Permission; -use Pterodactyl\Tests\Integration\IntegrationTestCase; -class ClientControllerTest extends IntegrationTestCase +class ClientControllerTest extends ClientApiIntegrationTestCase { - /** - * Cleanup after tests are run. - */ - protected function tearDown(): void - { - Server::query()->forceDelete(); - User::query()->forceDelete(); - Node::query()->forceDelete(); - Location::query()->forceDelete(); - - parent::tearDown(); - } - /** * Test that only the servers a logged in user is assigned to are returned by the * API endpoint. Obviously there are cases such as being an administrator or being diff --git a/tests/Integration/Api/Client/Server/CommandControllerTest.php b/tests/Integration/Api/Client/Server/CommandControllerTest.php new file mode 100644 index 000000000..3d7cd090f --- /dev/null +++ b/tests/Integration/Api/Client/Server/CommandControllerTest.php @@ -0,0 +1,100 @@ +repository = Mockery::mock(DaemonCommandRepository::class); + $this->app->instance(DaemonCommandRepository::class, $this->repository); + } + + /** + * Test that a validation error is returned if there is no command present in the + * request. + */ + public function testValidationErrorIsReturnedIfNoCommandIsPresent() + { + [$user, $server] = $this->generateTestAccount(); + + $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + 'command' => '', + ]); + + $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); + $response->assertJsonPath('errors.0.code', 'required'); + } + + /** + * Test that a subuser without the required permission receives an error when trying to + * execute the command. + */ + public function testSubuserWithoutPermissionReceivesError() + { + [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]); + + $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + 'command' => 'say Test', + ]); + + $response->assertStatus(Response::HTTP_FORBIDDEN); + } + + /** + * Test that a command can be sent to the server. + */ + public function testCommandCanSendToServer() + { + [$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_CONSOLE]); + + $this->repository->expects('setServer')->with(Mockery::on(function ($value) use ($server) { + return $value->uuid === $server->uuid; + }))->andReturnSelf(); + $this->repository->expects('send')->with('say Test')->andReturn(new GuzzleResponse); + + $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + 'command' => 'say Test', + ]); + + $response->assertStatus(Response::HTTP_NO_CONTENT); + } + + /** + * Test that an error is returned when the server is offline that is more specific than the + * regular daemon connection error. + */ + public function testErrorIsReturnedWhenServerIsOffline() + { + [$user, $server] = $this->generateTestAccount(); + + $this->repository->expects('setServer->send')->andThrows( + new BadResponseException('', new Request('GET', 'test'), new GuzzleResponse(Response::HTTP_BAD_GATEWAY)) + ); + + $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [ + 'command' => 'say Test', + ]); + + $response->assertStatus(Response::HTTP_BAD_GATEWAY); + $response->assertJsonPath('errors.0.code', 'HttpException'); + $response->assertJsonPath('errors.0.detail', 'Server must be online in order to send commands.'); + } +} diff --git a/tests/Integration/Api/Client/TwoFactorControllerTest.php b/tests/Integration/Api/Client/TwoFactorControllerTest.php index b49c1b57c..8344d2b96 100644 --- a/tests/Integration/Api/Client/TwoFactorControllerTest.php +++ b/tests/Integration/Api/Client/TwoFactorControllerTest.php @@ -6,20 +6,9 @@ use Carbon\Carbon; use Pterodactyl\Models\User; use Illuminate\Http\Response; use PragmaRX\Google2FA\Google2FA; -use Pterodactyl\Tests\Integration\IntegrationTestCase; -class TwoFactorControllerTest extends IntegrationTestCase +class TwoFactorControllerTest extends ClientApiIntegrationTestCase { - /** - * Clean up after tests have run. - */ - protected function tearDown(): void - { - User::query()->forceDelete(); - - parent::tearDown(); - } - /** * Test that image data for enabling 2FA is returned by the endpoint and that the user * record in the database is updated as expected.