Add some additional test coverage and clean up modification service and suspension service
This commit is contained in:
parent
83efb2d7b6
commit
d087bebc93
12 changed files with 214 additions and 503 deletions
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Tests\Integration\Services\Servers;
|
||||
|
||||
use Exception;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\ServerVariable;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Pterodactyl\Tests\Integration\IntegrationTestCase;
|
||||
use Pterodactyl\Services\Servers\StartupModificationService;
|
||||
|
||||
class StartupModificationServiceTest extends IntegrationTestCase
|
||||
{
|
||||
/**
|
||||
* Test that a non-admin request to modify the server startup parameters does
|
||||
* not perform any egg or nest updates. This also attempts to pass through an
|
||||
* egg_id variable which should have no impact if the request is coming from
|
||||
* a non-admin entity.
|
||||
*/
|
||||
public function testNonAdminCanModifyServerVariables()
|
||||
{
|
||||
// Theoretically lines up with the Bungeecord Minecraft egg.
|
||||
$server = $this->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);
|
||||
}
|
||||
}
|
80
tests/Integration/Services/Servers/SuspensionServiceTest.php
Normal file
80
tests/Integration/Services/Servers/SuspensionServiceTest.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Tests\Integration\Services\Servers;
|
||||
|
||||
use Mockery;
|
||||
use InvalidArgumentException;
|
||||
use Pterodactyl\Services\Servers\SuspensionService;
|
||||
use Pterodactyl\Tests\Integration\IntegrationTestCase;
|
||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
||||
|
||||
class SuspensionServiceTest extends IntegrationTestCase
|
||||
{
|
||||
/** @var \Mockery\MockInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* Setup test instance.
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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);
|
||||
}
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Servers;
|
||||
|
||||
use Mockery as m;
|
||||
use Tests\TestCase;
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Pterodactyl\Models\User;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Services\Servers\EnvironmentService;
|
||||
use Pterodactyl\Services\Servers\VariableValidatorService;
|
||||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
||||
use Pterodactyl\Services\Servers\StartupModificationService;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepository;
|
||||
|
||||
class StartupModificationServiceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $daemonServerRepository;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $eggRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Servers\EnvironmentService|\Mockery\Mock
|
||||
*/
|
||||
private $environmentService;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $serverVariableRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Servers\VariableValidatorService|\Mockery\Mock
|
||||
*/
|
||||
private $validatorService;
|
||||
|
||||
/**
|
||||
* Setup tests.
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* 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');
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue