diff --git a/app/Models/Model.php b/app/Models/Model.php index 095fe7adc..f6b94a3ab 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -7,6 +7,7 @@ use Illuminate\Validation\Rule; use Illuminate\Container\Container; use Illuminate\Contracts\Validation\Factory; use Illuminate\Database\Eloquent\Model as IlluminateModel; +use Pterodactyl\Exceptions\Model\DataValidationException; abstract class Model extends IlluminateModel { @@ -55,7 +56,11 @@ abstract class Model extends IlluminateModel static::$validatorFactory = Container::getInstance()->make(Factory::class); static::saving(function (Model $model) { - return $model->validate(); + if (! $model->validate()) { + throw new DataValidationException($model->getValidator()); + } + + return true; }); } @@ -147,9 +152,9 @@ abstract class Model extends IlluminateModel } return $this->getValidator()->setData( - // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist - // for that model. Doing this will return all of the attributes in a format that can - // properly be validated. + // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist + // for that model. Doing this will return all of the attributes in a format that can + // properly be validated. $this->addCastAttributesToArray( $this->getAttributes(), $this->getMutatedAttributes() ) diff --git a/app/Models/Server.php b/app/Models/Server.php index 13fcb931a..aa4a39f06 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -15,7 +15,7 @@ use Znck\Eloquent\Traits\BelongsToThrough; * @property string $name * @property string $description * @property bool $skip_scripts - * @property int $suspended + * @property bool $suspended * @property int $owner_id * @property int $memory * @property int $swap @@ -133,7 +133,7 @@ class Server extends Model protected $casts = [ 'node_id' => 'integer', 'skip_scripts' => 'boolean', - 'suspended' => 'integer', + 'suspended' => 'boolean', 'owner_id' => 'integer', 'memory' => 'integer', 'swap' => 'integer', diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index b69a5343d..a2fdec52a 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -55,7 +55,7 @@ class ServerConfigurationStructureService { return [ 'uuid' => $server->uuid, - 'suspended' => (bool) $server->suspended, + 'suspended' => $server->suspended, 'environment' => $this->environment->handle($server), 'invocation' => $server->startup, 'skip_egg_scripts' => $server->skip_scripts, diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index 7bec8fac6..2d8dc3a14 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -2,13 +2,13 @@ namespace Pterodactyl\Services\Servers; +use Illuminate\Support\Arr; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; +use Pterodactyl\Models\ServerVariable; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Traits\Services\HasUserLevels; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; class StartupModificationService { @@ -19,63 +19,21 @@ class StartupModificationService */ private $connection; - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $eggRepository; - - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environmentService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface - */ - private $serverVariableRepository; - /** * @var \Pterodactyl\Services\Servers\VariableValidatorService */ private $validatorService; - /** - * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService - */ - private $structureService; - /** * StartupModificationService constructor. * * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $eggRepository - * @param \Pterodactyl\Services\Servers\EnvironmentService $environmentService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $structureService - * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService */ - public function __construct( - ConnectionInterface $connection, - EggRepositoryInterface $eggRepository, - EnvironmentService $environmentService, - ServerRepositoryInterface $repository, - ServerConfigurationStructureService $structureService, - ServerVariableRepositoryInterface $serverVariableRepository, - VariableValidatorService $validatorService - ) { + public function __construct(ConnectionInterface $connection, VariableValidatorService $validatorService) + { $this->connection = $connection; - $this->eggRepository = $eggRepository; - $this->environmentService = $environmentService; - $this->repository = $repository; - $this->serverVariableRepository = $serverVariableRepository; $this->validatorService = $validatorService; - $this->structureService = $structureService; } /** @@ -85,34 +43,42 @@ class StartupModificationService * @param array $data * @return \Pterodactyl\Models\Server * - * @throws \Illuminate\Validation\ValidationException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ public function handle(Server $server, array $data): Server { - $this->connection->beginTransaction(); - if (! is_null(array_get($data, 'environment'))) { - $this->validatorService->setUserLevel($this->getUserLevel()); - $results = $this->validatorService->handle(array_get($data, 'egg_id', $server->egg_id), array_get($data, 'environment', [])); + return $this->connection->transaction(function () use ($server, $data) { + if (! empty($data['environment'])) { + $egg = $this->isUserLevel(User::USER_LEVEL_ADMIN) ? ($data['egg_id'] ?? $server->egg_id) : $server->egg_id; - $results->each(function ($result) use ($server) { - $this->serverVariableRepository->withoutFreshModel()->updateOrCreate([ - 'server_id' => $server->id, - 'variable_id' => $result->id, - ], [ - 'variable_value' => $result->value ?? '', - ]); - }); - } + $results = $this->validatorService + ->setUserLevel($this->getUserLevel()) + ->handle($egg, $data['environment']); - if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { - $this->updateAdministrativeSettings($data, $server); - } + foreach ($results as $result) { + ServerVariable::query()->updateOrCreate( + [ + 'server_id' => $server->id, + 'variable_id' => $result->id, + ], + ['variable_value' => $result->value ?? ''] + ); + } + } - $this->connection->commit(); + if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { + $this->updateAdministrativeSettings($data, $server); + } - return $server; + // Calling ->refresh() rather than ->fresh() here causes it to return the + // variables as triplicates for some reason? Not entirely sure, should dig + // in more to figure it out, but luckily we have a test case covering this + // specific call so we can be assured we're not breaking it _here_ at least. + // + // TODO(dane): this seems like a red-flag for the code powering the relationship + // that should be looked into more. + return $server->fresh(); + }); } /** @@ -120,28 +86,27 @@ class StartupModificationService * * @param array $data * @param \Pterodactyl\Models\Server $server - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - private function updateAdministrativeSettings(array $data, Server &$server) + protected function updateAdministrativeSettings(array $data, Server &$server) { if ( - is_digit(array_get($data, 'egg_id')) + is_digit(Arr::get($data, 'egg_id')) && $data['egg_id'] != $server->egg_id - && is_null(array_get($data, 'nest_id')) + && is_null(Arr::get($data, 'nest_id')) ) { - $egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find($data['egg_id']); + /** @var \Pterodactyl\Models\Egg $egg */ + $egg = Egg::query()->select(['nest_id'])->findOrFail($data['egg_id']); + $data['nest_id'] = $egg->nest_id; } - $server = $this->repository->update($server->id, [ + $server->forceFill([ 'installed' => 0, - 'startup' => array_get($data, 'startup', $server->startup), - 'nest_id' => array_get($data, 'nest_id', $server->nest_id), - 'egg_id' => array_get($data, 'egg_id', $server->egg_id), - 'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']), - 'image' => array_get($data, 'docker_image', $server->image), - ]); + 'startup' => $data['startup'] ?? $server->startup, + 'nest_id' => $data['nest_id'] ?? $server->nest_id, + 'egg_id' => $data['egg_id'] ?? $server->egg_id, + 'skip_scripts' => $data['skip_scripts'] ?? isset($data['skip_scripts']), + 'image' => $data['docker_image'] ?? $server->image, + ])->save(); } } diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index 9fb95645d..6497d73ed 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -2,12 +2,10 @@ namespace Pterodactyl\Services\Servers; -use Psr\Log\LoggerInterface; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class SuspensionService { @@ -19,16 +17,6 @@ class SuspensionService */ private $connection; - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - - /** - * @var \Psr\Log\LoggerInterface - */ - private $writer; - /** * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository */ @@ -39,25 +27,19 @@ class SuspensionService * * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Psr\Log\LoggerInterface $writer */ public function __construct( ConnectionInterface $connection, - DaemonServerRepository $daemonServerRepository, - ServerRepositoryInterface $repository, - LoggerInterface $writer + DaemonServerRepository $daemonServerRepository ) { $this->connection = $connection; - $this->repository = $repository; - $this->writer = $writer; $this->daemonServerRepository = $daemonServerRepository; } /** * Suspends a server on the system. * - * @param int|\Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Server $server * @param string $action * * @throws \Throwable @@ -66,15 +48,16 @@ class SuspensionService { Assert::oneOf($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND]); - if ( - $action === self::ACTION_SUSPEND && $server->suspended || - $action === self::ACTION_UNSUSPEND && ! $server->suspended - ) { + $isSuspending = $action === self::ACTION_SUSPEND; + // Nothing needs to happen if we're suspending the server and it is already + // suspended in the database. Additionally, nothing needs to happen if the server + // is not suspended and we try to un-suspend the instance. + if ($isSuspending === $server->suspended) { return; } $this->connection->transaction(function () use ($action, $server) { - $this->repository->withoutFreshModel()->update($server->id, [ + $server->update([ 'suspended' => $action === self::ACTION_SUSPEND, ]); diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index d40bfd3f6..92b9ea0bf 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -67,7 +67,7 @@ class ServerTransformer extends BaseClientTransformer 'allocations' => $server->allocation_limit, 'backups' => $server->backup_limit, ], - 'is_suspended' => $server->suspended !== 0, + 'is_suspended' => $server->suspended, 'is_installing' => $server->installed !== 1, ]; } diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index e893c17b9..b77959058 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -9,7 +9,6 @@ use Pterodactyl\Models\Node; use Pterodactyl\Models\Task; use Pterodactyl\Models\User; use Webmozart\Assert\Assert; -use Pterodactyl\Models\Model; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Location; diff --git a/tests/Integration/Services/Servers/StartupModificationServiceTest.php b/tests/Integration/Services/Servers/StartupModificationServiceTest.php new file mode 100644 index 000000000..4900f910f --- /dev/null +++ b/tests/Integration/Services/Servers/StartupModificationServiceTest.php @@ -0,0 +1,65 @@ +createServerModel(['egg_id' => 1]); + + try { + $this->app->make(StartupModificationService::class)->handle($server, [ + 'egg_id' => $server->egg_id + 1, + 'environment' => [ + 'BUNGEE_VERSION' => '$$', + 'SERVER_JARFILE' => 'server.jar', + ], + ]); + + $this->assertTrue(false, 'This assertion should not be called.'); + } catch (Exception $exception) { + $this->assertInstanceOf(ValidationException::class, $exception); + + /** @var \Illuminate\Validation\ValidationException $exception */ + $errors = $exception->validator->errors()->toArray(); + + $this->assertCount(1, $errors); + $this->assertArrayHasKey('environment.BUNGEE_VERSION', $errors); + $this->assertCount(1, $errors['environment.BUNGEE_VERSION']); + $this->assertSame('The Bungeecord Version variable may only contain letters and numbers.', $errors['environment.BUNGEE_VERSION'][0]); + } + + ServerVariable::query()->where('variable_id', $server->variables[1]->id)->delete(); + + /** @var \Pterodactyl\Models\Server $result */ + $result = $this->app->make(StartupModificationService::class)->handle($server, [ + 'egg_id' => $server->egg_id + 1, + 'startup' => 'random gibberish', + 'environment' => [ + 'BUNGEE_VERSION' => '1234', + 'SERVER_JARFILE' => 'test.jar', + ], + ]); + + $this->assertInstanceOf(Server::class, $result); + $this->assertCount(2, $result->variables); + $this->assertSame($server->startup, $result->startup); + $this->assertSame('1234', $result->variables[0]->server_value); + $this->assertSame('test.jar', $result->variables[1]->server_value); + } +} diff --git a/tests/Integration/Services/Servers/SuspensionServiceTest.php b/tests/Integration/Services/Servers/SuspensionServiceTest.php new file mode 100644 index 000000000..92822aaa1 --- /dev/null +++ b/tests/Integration/Services/Servers/SuspensionServiceTest.php @@ -0,0 +1,80 @@ +repository = Mockery::mock(DaemonServerRepository::class); + $this->app->instance(DaemonServerRepository::class, $this->repository); + } + + public function testServerIsSuspendedAndUnsuspended() + { + $server = $this->createServerModel(['suspended' => false]); + + $this->repository->expects('setServer')->twice()->andReturnSelf(); + $this->repository->expects('suspend')->with(false)->andReturnUndefined(); + + $this->getService()->toggle($server, SuspensionService::ACTION_SUSPEND); + + $server->refresh(); + $this->assertTrue($server->suspended); + + $this->repository->expects('suspend')->with(true)->andReturnUndefined(); + + $this->getService()->toggle($server, SuspensionService::ACTION_UNSUSPEND); + + $server->refresh(); + $this->assertFalse($server->suspended); + } + + public function testNoActionIsTakenIfSuspensionStatusIsUnchanged() + { + $server = $this->createServerModel(['suspended' => false]); + + $this->getService()->toggle($server, SuspensionService::ACTION_UNSUSPEND); + + $server->refresh(); + $this->assertFalse($server->suspended); + + $server->update(['suspended' => true]); + $this->getService()->toggle($server, SuspensionService::ACTION_SUSPEND); + + $server->refresh(); + $this->assertTrue($server->suspended); + } + + public function testExceptionIsThrownIfInvalidActionsArePassed() + { + $server = $this->createServerModel(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected one of: "suspend", "unsuspend". Got: "foo"'); + + $this->getService()->toggle($server, 'foo'); + } + + /** + * @return \Pterodactyl\Services\Servers\SuspensionService + */ + private function getService() + { + return $this->app->make(SuspensionService::class); + } +} diff --git a/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php b/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php index 9cf838f79..9d32323dd 100644 --- a/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php +++ b/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php @@ -86,7 +86,7 @@ class ServerConfigurationStructureServiceTest extends TestCase ], $response['container']); $this->assertSame($model->uuid, $response['uuid']); - $this->assertSame((bool) $model->suspended, $response['suspended']); + $this->assertSame($model->suspended, $response['suspended']); $this->assertSame(['environment_array'], $response['environment']); $this->assertSame($model->startup, $response['invocation']); } diff --git a/tests/Unit/Services/Servers/StartupModificationServiceTest.php b/tests/Unit/Services/Servers/StartupModificationServiceTest.php deleted file mode 100644 index f87daf43c..000000000 --- a/tests/Unit/Services/Servers/StartupModificationServiceTest.php +++ /dev/null @@ -1,194 +0,0 @@ -daemonServerRepository = m::mock(DaemonServerRepository::class); - $this->connection = m::mock(ConnectionInterface::class); - $this->eggRepository = m::mock(EggRepositoryInterface::class); - $this->environmentService = m::mock(EnvironmentService::class); - $this->repository = m::mock(ServerRepositoryInterface::class); - $this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class); - $this->validatorService = m::mock(VariableValidatorService::class); - } - - /** - * Test startup modification as a non-admin user. - */ - public function testStartupModifiedAsNormalUser() - { - $model = factory(Server::class)->make(); - - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->validatorService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_USER)->once()->andReturnNull(); - $this->validatorService->shouldReceive('handle')->with(123, ['test' => 'abcd1234'])->once()->andReturn( - collect([(object) ['id' => 1, 'value' => 'stored-value']]) - ); - - $this->serverVariableRepository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf(); - $this->serverVariableRepository->shouldReceive('updateOrCreate')->with([ - 'server_id' => $model->id, - 'variable_id' => 1, - ], ['variable_value' => 'stored-value'])->once()->andReturnNull(); - - $this->environmentService->shouldReceive('handle')->with($model)->once()->andReturn(['env']); - $this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andReturnSelf(); - $this->daemonServerRepository->shouldReceive('update')->with([ - 'build' => ['env|overwrite' => ['env']], - ])->once()->andReturn(new Response); - - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $response = $this->getService()->handle($model, ['egg_id' => 123, 'environment' => ['test' => 'abcd1234']]); - - $this->assertInstanceOf(Server::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test startup modification as an admin user. - */ - public function testStartupModificationAsAdminUser() - { - $model = factory(Server::class)->make([ - 'egg_id' => 123, - 'image' => 'docker:image', - ]); - - $eggModel = factory(Egg::class)->make([ - 'id' => 456, - 'nest_id' => 12345, - ]); - - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->validatorService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_ADMIN)->once()->andReturnNull(); - $this->validatorService->shouldReceive('handle')->with(456, ['test' => 'abcd1234'])->once()->andReturn( - collect([(object) ['id' => 1, 'value' => 'stored-value'], (object) ['id' => 2, 'value' => null]]) - ); - - $this->serverVariableRepository->shouldReceive('withoutFreshModel->updateOrCreate')->once()->with([ - 'server_id' => $model->id, - 'variable_id' => 1, - ], ['variable_value' => 'stored-value'])->andReturnNull(); - - $this->serverVariableRepository->shouldReceive('withoutFreshModel->updateOrCreate')->once()->with([ - 'server_id' => $model->id, - 'variable_id' => 2, - ], ['variable_value' => ''])->andReturnNull(); - - $this->eggRepository->shouldReceive('setColumns->find')->once()->with($eggModel->id)->andReturn($eggModel); - - $this->repository->shouldReceive('update')->with($model->id, m::subset([ - 'installed' => 0, - 'nest_id' => $eggModel->nest_id, - 'egg_id' => $eggModel->id, - 'image' => 'docker:image', - ]))->once()->andReturn($model); - $this->repository->shouldReceive('getDaemonServiceData')->with($model, true)->once()->andReturn([ - 'egg' => 'abcd1234', - ]); - - $this->environmentService->shouldReceive('handle')->with($model)->once()->andReturn(['env']); - - $this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andReturnSelf(); - $this->daemonServerRepository->shouldReceive('update')->with([ - 'build' => [ - 'env|overwrite' => ['env'], - 'image' => $model->image, - ], - 'service' => [ - 'egg' => 'abcd1234', - 'skip_scripts' => false, - ], - ])->once()->andReturn(new Response); - - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $service = $this->getService(); - $service->setUserLevel(User::USER_LEVEL_ADMIN); - $response = $service->handle($model, [ - 'docker_image' => 'docker:image', - 'egg_id' => $eggModel->id, - 'environment' => ['test' => 'abcd1234'], - ]); - - $this->assertInstanceOf(Server::class, $response); - $this->assertSame($model, $response); - } - - /** - * Return an instance of the service with mocked dependencies. - * - * @return \Pterodactyl\Services\Servers\StartupModificationService - */ - private function getService(): StartupModificationService - { - return new StartupModificationService( - $this->connection, - $this->daemonServerRepository, - $this->eggRepository, - $this->environmentService, - $this->repository, - $this->serverVariableRepository, - $this->validatorService - ); - } -} diff --git a/tests/Unit/Services/Servers/SuspensionServiceTest.php b/tests/Unit/Services/Servers/SuspensionServiceTest.php deleted file mode 100644 index da76ad3c9..000000000 --- a/tests/Unit/Services/Servers/SuspensionServiceTest.php +++ /dev/null @@ -1,192 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Tests\Unit\Services\Servers; - -use Exception; -use Mockery as m; -use Tests\TestCase; -use GuzzleHttp\Psr7\Response; -use Pterodactyl\Models\Server; -use Psr\Log\LoggerInterface as Writer; -use GuzzleHttp\Exception\RequestException; -use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Services\Servers\SuspensionService; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; - -class SuspensionServiceTest extends TestCase -{ - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - protected $daemonServerRepository; - - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $database; - - /** - * @var \GuzzleHttp\Exception\RequestException - */ - protected $exception; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Models\Server - */ - protected $server; - - /** - * @var \Pterodactyl\Services\Servers\SuspensionService - */ - protected $service; - - /** - * @var \Psr\Log\LoggerInterface - */ - protected $writer; - - /** - * Setup tests. - */ - public function setUp(): void - { - parent::setUp(); - - $this->daemonServerRepository = m::mock(DaemonServerRepositoryInterface::class); - $this->database = m::mock(ConnectionInterface::class); - $this->exception = m::mock(RequestException::class)->makePartial(); - $this->repository = m::mock(ServerRepositoryInterface::class); - $this->writer = m::mock(Writer::class); - - $this->server = factory(Server::class)->make(['suspended' => 0, 'node_id' => 1]); - - $this->service = new SuspensionService( - $this->database, - $this->daemonServerRepository, - $this->repository, - $this->writer - ); - } - - /** - * Test that the function accepts an integer in place of the server model. - * - * @expectedException \Exception - */ - public function testFunctionShouldAcceptAnIntegerInPlaceOfAServerModel() - { - $this->repository->shouldReceive('find')->with($this->server->id)->once()->andThrow(new Exception()); - - $this->service->toggle($this->server->id); - } - - /** - * Test that no action being passed suspends a server. - */ - public function testServerShouldBeSuspendedWhenNoActionIsPassed() - { - $this->server->suspended = 0; - - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with($this->server->id, ['suspended' => true])->once()->andReturnNull(); - - $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf() - ->shouldReceive('suspend')->withNoArgs()->once()->andReturn(new Response); - $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $this->assertTrue($this->service->toggle($this->server)); - } - - /** - * Test that server is unsuspended if action=unsuspend. - */ - public function testServerShouldBeUnsuspendedWhenUnsuspendActionIsPassed() - { - $this->server->suspended = 1; - - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with($this->server->id, ['suspended' => false])->once()->andReturnNull(); - - $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf() - ->shouldReceive('unsuspend')->withNoArgs()->once()->andReturn(new Response); - $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $this->assertTrue($this->service->toggle($this->server, 'unsuspend')); - } - - /** - * Test that nothing happens if a server is already unsuspended and action=unsuspend. - */ - public function testNoActionShouldHappenIfServerIsAlreadyUnsuspendedAndActionIsUnsuspend() - { - $this->server->suspended = 0; - - $this->assertTrue($this->service->toggle($this->server, 'unsuspend')); - } - - /** - * Test that nothing happens if a server is already suspended and action=suspend. - */ - public function testNoActionShouldHappenIfServerIsAlreadySuspendedAndActionIsSuspend() - { - $this->server->suspended = 1; - - $this->assertTrue($this->service->toggle($this->server, 'suspend')); - } - - /** - * Test that an exception thrown by Guzzle is caught and transformed to a displayable exception. - */ - public function testExceptionThrownByGuzzleShouldBeCaughtAndTransformedToDisplayable() - { - $this->server->suspended = 0; - - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with($this->server->id, ['suspended' => true])->once()->andReturnNull(); - - $this->daemonServerRepository->shouldReceive('setServer')->with($this->server) - ->once()->andThrow($this->exception); - - $this->exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('getStatusCode')->withNoArgs()->once()->andReturn(400); - - $this->writer->shouldReceive('warning')->with($this->exception)->once()->andReturnNull(); - - try { - $this->service->toggle($this->server); - } catch (Exception $exception) { - $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals( - trans('admin/server.exceptions.daemon_exception', ['code' => 400]), - $exception->getMessage() - ); - } - } - - /** - * Test that if action is not suspend or unsuspend an exception is thrown. - * - * @expectedException \InvalidArgumentException - */ - public function testExceptionShouldBeThrownIfActionIsNotValid() - { - $this->service->toggle($this->server, 'random'); - } -}