diff --git a/CHANGELOG.md b/CHANGELOG.md index 936c2b6a0..cfc6c0f9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v1.1.3 +### Fixed +* Server bulk power actions command will no longer attempt to run commands against installing or suspended servers. + ## v1.1.2 ### Fixed * Fixes an exception thrown while trying to validate IP access for the client API. diff --git a/README.md b/README.md index ace35378b..418135fa3 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f | [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. | | [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. | | [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | -| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. | +| [**XCORE**](https://xcore-server.de/) | XCORE offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. | | [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. | | [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims to cheap services on quality servers. Premium i9-9900K processors will run your game like a dream. | | [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. | diff --git a/app/Console/Commands/Server/BulkPowerActionCommand.php b/app/Console/Commands/Server/BulkPowerActionCommand.php index 383879902..32d9868b5 100644 --- a/app/Console/Commands/Server/BulkPowerActionCommand.php +++ b/app/Console/Commands/Server/BulkPowerActionCommand.php @@ -2,30 +2,15 @@ namespace Pterodactyl\Console\Commands\Server; +use Pterodactyl\Models\Server; use Illuminate\Console\Command; -use GuzzleHttp\Exception\RequestException; use Illuminate\Validation\ValidationException; use Illuminate\Validation\Factory as ValidatorFactory; use Pterodactyl\Repositories\Wings\DaemonPowerRepository; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class BulkPowerActionCommand extends Command { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonPowerRepository - */ - private $powerRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - - /** - * @var \Illuminate\Validation\Factory - */ - private $validator; - /** * @var string */ @@ -39,37 +24,20 @@ class BulkPowerActionCommand extends Command */ protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.'; - /** - * BulkPowerActionCommand constructor. - * - * @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Illuminate\Validation\Factory $validator - */ - public function __construct( - DaemonPowerRepository $powerRepository, - ServerRepositoryInterface $repository, - ValidatorFactory $validator - ) { - parent::__construct(); - - $this->repository = $repository; - $this->validator = $validator; - $this->powerRepository = $powerRepository; - } - /** * Handle the bulk power request. * + * @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository + * @param \Illuminate\Validation\Factory $validator * @throws \Illuminate\Validation\ValidationException */ - public function handle() + public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator) { $action = $this->argument('action'); $nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes')); $servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers')); - $validator = $this->validator->make([ + $validator = $validator->make([ 'action' => $action, 'nodes' => $nodes, 'servers' => $servers, @@ -89,23 +57,18 @@ class BulkPowerActionCommand extends Command throw new ValidationException($validator); } - $count = $this->repository->getServersForPowerActionCount($servers, $nodes); + $count = $this->getQueryBuilder($servers, $nodes)->count(); if (! $this->confirm(trans('command/messages.server.power.confirm', ['action' => $action, 'count' => $count])) && $this->input->isInteractive()) { return; } $bar = $this->output->createProgressBar($count); - $servers = $this->repository->getServersForPowerAction($servers, $nodes); - - $servers->each(function ($server) use ($action, &$bar) { + $this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) { $bar->clear(); try { - $this->powerRepository - ->setNode($server->node) - ->setServer($server) - ->send($action); - } catch (RequestException $exception) { + $powerRepository->setServer($server)->send($action); + } catch (DaemonConnectionException $exception) { $this->output->error(trans('command/messages.server.power.action_failed', [ 'name' => $server->name, 'id' => $server->id, @@ -120,4 +83,28 @@ class BulkPowerActionCommand extends Command $this->line(''); } + + /** + * Returns the query builder instance that will return the servers that should be affected. + * + * @param array $servers + * @param array $nodes + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function getQueryBuilder(array $servers, array $nodes) + { + $instance = Server::query() + ->where('suspended', false) + ->where('installed', Server::STATUS_INSTALLED); + + if (! empty($nodes) && ! empty($servers)) { + $instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes); + } else if (empty($nodes) && ! empty($servers)) { + $instance->whereIn('id', $servers); + } else if (! empty($nodes) && empty($servers)) { + $instance->whereIn('node_id', $nodes); + } + + return $instance->with('node'); + } } diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php index d7e3dfc5d..55cf79a39 100644 --- a/app/Contracts/Repository/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -95,25 +95,6 @@ interface ServerRepositoryInterface extends RepositoryInterface */ public function getByUuid(string $uuid): Server; - /** - * Return all of the servers that should have a power action performed against them. - * - * @param int[] $servers - * @param int[] $nodes - * @param bool $returnCount - * @return int|\Illuminate\Support\LazyCollection - */ - public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false); - - /** - * Return the total number of servers that will be affected by the query. - * - * @param int[] $servers - * @param int[] $nodes - * @return int - */ - public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int; - /** * Check if a given UUID and UUID-Short string are unique to a server. * diff --git a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php index f52b71297..3bbff3d48 100644 --- a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php +++ b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Client\Account; -use Pterodactyl\Models\User; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException; @@ -32,8 +31,8 @@ class UpdatePasswordRequest extends ClientApiRequest */ public function rules(): array { - $rules = User::getRulesForUpdate($this->user()); - - return ['password' => array_merge($rules['password'], ['confirmed'])]; + return [ + 'password' => ['required', 'string', 'confirmed', 'min:8'], + ]; } } diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 8784a37cb..ee3c3e16d 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -69,7 +69,7 @@ class RunTaskJob extends Job implements ShouldQueue $commandRepository->setServer($server)->send($this->task->payload); break; case 'backup': - $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null); + $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); break; default: throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index fdfd82fc2..5c16664b6 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -194,45 +194,6 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt } } - /** - * Return all of the servers that should have a power action performed against them. - * - * @param int[] $servers - * @param int[] $nodes - * @param bool $returnCount - * @return int|\Illuminate\Support\LazyCollection - */ - public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false) - { - $instance = $this->getBuilder(); - - if (! empty($nodes) && ! empty($servers)) { - $instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes); - } else if (empty($nodes) && ! empty($servers)) { - $instance->whereIn('id', $servers); - } else if (! empty($nodes) && empty($servers)) { - $instance->whereIn('node_id', $nodes); - } - - if ($returnCount) { - return $instance->count(); - } - - return $instance->with('node')->cursor(); - } - - /** - * Return the total number of servers that will be affected by the query. - * - * @param int[] $servers - * @param int[] $nodes - * @return int - */ - public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int - { - return $this->getServersForPowerAction($servers, $nodes, true); - } - /** * Check if a given UUID and UUID-Short string are unique to a server. * diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php index ca8c3decd..5c34961a4 100644 --- a/app/Services/Backups/InitiateBackupService.php +++ b/app/Services/Backups/InitiateBackupService.php @@ -13,6 +13,7 @@ use Pterodactyl\Repositories\Eloquent\BackupRepository; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; +use Pterodactyl\Services\Backups\DeleteBackupService; class InitiateBackupService { @@ -41,24 +42,32 @@ class InitiateBackupService */ private $backupManager; + /** + * @var \Pterodactyl\Services\Backups\DeleteBackupService + */ + private $deleteBackupService; + /** * InitiateBackupService constructor. * * @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository + * @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService * @param \Pterodactyl\Extensions\Backups\BackupManager $backupManager */ public function __construct( BackupRepository $repository, ConnectionInterface $connection, DaemonBackupRepository $daemonBackupRepository, + DeleteBackupService $deleteBackupService, BackupManager $backupManager ) { $this->repository = $repository; $this->connection = $connection; $this->daemonBackupRepository = $daemonBackupRepository; $this->backupManager = $backupManager; + $this->deleteBackupService = $deleteBackupService; } /** @@ -96,13 +105,8 @@ class InitiateBackupService * @throws \Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException * @throws \Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException */ - public function handle(Server $server, string $name = null): Backup + public function handle(Server $server, string $name = null, bool $override = false): Backup { - // Do not allow the user to continue if this server is already at its limit. - if (! $server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) { - throw new TooManyBackupsException($server->backup_limit); - } - $previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10); if ($previous->count() >= 2) { throw new TooManyRequestsHttpException( @@ -111,6 +115,18 @@ class InitiateBackupService ); } + // Check if the server has reached or exceeded it's backup limit + if (!$server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) { + // Do not allow the user to continue if this server is already at its limit and can't override. + if (!$override || $server->backup_limit <= 0) { + throw new TooManyBackupsException($server->backup_limit); + } + + // Remove oldest backup + $oldestBackup = $server->backups()->where('is_successful', true)->orderByDesc('created_at')->first(); + $this->deleteBackupService->handle($oldestBackup); + } + return $this->connection->transaction(function () use ($server, $name) { /** @var \Pterodactyl\Models\Backup $backup */ $backup = $this->repository->create([ diff --git a/config/database.php b/config/database.php index 5a4e8bc77..e69e19ed0 100644 --- a/config/database.php +++ b/config/database.php @@ -45,7 +45,7 @@ return [ 'collation' => 'utf8mb4_unicode_ci', 'prefix' => env('DB_PREFIX', ''), 'strict' => env('DB_STRICT_MODE', false), - 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE'))) + 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))) ], /* @@ -68,7 +68,7 @@ return [ 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => false, - 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE'))) + 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))) ], ], diff --git a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php index 67461ecc8..f46481b47 100644 --- a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php +++ b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php @@ -83,10 +83,10 @@ class MergePermissionsTableIntoSubusers extends Migration ->map(function ($value) { return self::$permissionsMap[$value] ?? null; })->filter(function ($value) { - return !is_null($value) && $value !== Permission::ACTION_WEBSOCKET_CONNECT; + return ! is_null($value) && $value !== Permission::ACTION_WEBSOCKET_CONNECT; }) // All subusers get this permission, so make sure it gets pushed into the array. - ->merge([ Permission::ACTION_WEBSOCKET_CONNECT ]) + ->merge([Permission::ACTION_WEBSOCKET_CONNECT]) ->unique() ->values() ->toJson(); @@ -103,12 +103,12 @@ class MergePermissionsTableIntoSubusers extends Migration */ public function down() { - $flipped = array_flip(self::$permissionsMap); + $flipped = array_flip(array_filter(self::$permissionsMap)); foreach (DB::select('SELECT id, permissions FROM subusers') as $datum) { $values = []; foreach (json_decode($datum->permissions, true) as $permission) { - if (!empty($v = $flipped[$permission])) { + if (! empty($v = $flipped[$permission])) { $values[] = $datum->id; $values[] = $v; } diff --git a/package.json b/package.json index 2f947b532..e62c7ea55 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "xterm-addon-fit": "^0.4.0", "xterm-addon-search": "^0.7.0", "xterm-addon-search-bar": "^0.2.0", + "xterm-addon-web-links": "^0.4.0", "yup": "^0.29.1" }, "devDependencies": { diff --git a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx index 7dec924a5..1c91ef4d9 100644 --- a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx @@ -77,7 +77,7 @@ export default () => {
{ const fitAddon = new FitAddon(); const searchAddon = new SearchAddon(); const searchBar = new SearchBarAddon({ searchAddon }); + const webLinksAddon = new WebLinksAddon(); const { connected, instance } = ServerContext.useStoreState(state => state.socket); const [ canSendCommands ] = usePermissions([ 'control.console' ]); const serverId = ServerContext.useStoreState(state => state.server.data!.id); @@ -115,6 +117,7 @@ export default () => { terminal.loadAddon(fitAddon); terminal.loadAddon(searchAddon); terminal.loadAddon(searchBar); + terminal.loadAddon(webLinksAddon); fitAddon.fit(); // Add support for capturing keys diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx index 2ce843365..d70faffba 100644 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -24,7 +24,7 @@ const schema = object().shape({ .matches(/^[A-Za-z0-9_\-.]{3,48}$/, 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.'), connectionsFrom: string() .required('A connection value must be provided.') - .matches(/^([1-9]{1,3}|%)(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?$/, 'A valid connection address must be provided.'), + .matches(/^([0-9]{1,3}|%)(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?$/, 'A valid connection address must be provided.'), }); export default () => { diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 3eeaa981e..952a7b746 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -131,7 +131,7 @@ export default ({ database, className }: Props) => {
- + { : setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}> -

Accept Minecraft® EULA

+

Accept Minecraft® EULA

By pressing {'"I Accept"'} below you are indicating your agreement to the  { rel={'noreferrer noopener'} href="https://account.mojang.com/documents/minecraft_eula" > - Mojang EULA + Minecraft® EULA .

diff --git a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx index e1fca0cb0..08f33b161 100644 --- a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx +++ b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx @@ -91,7 +91,7 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => { diff --git a/resources/views/admin/nodes/view/index.blade.php b/resources/views/admin/nodes/view/index.blade.php index 993f233dc..13d62e9ee 100644 --- a/resources/views/admin/nodes/view/index.blade.php +++ b/resources/views/admin/nodes/view/index.blade.php @@ -51,7 +51,7 @@ - Total CPU Cores + Total CPU Threads diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php index 75b152090..6534bd508 100644 --- a/tests/Integration/Api/Client/AccountControllerTest.php +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -140,6 +140,29 @@ class AccountControllerTest extends ClientApiIntegrationTestCase $response->assertJsonPath('errors.0.detail', 'The password provided was invalid for this account.'); } + /** + * Test that a validation error is returned to the user if no password is provided or if + * the password is below the minimum password length. + */ + public function testErrorIsReturnedForInvalidRequestData() + { + $user = factory(User::class)->create(); + + $this->actingAs($user)->putJson('/api/client/account/password', [ + 'current_password' => 'password', + ]) + ->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY) + ->assertJsonPath('errors.0.meta.rule', 'required'); + + $this->actingAs($user)->putJson('/api/client/account/password', [ + 'current_password' => 'password', + 'password' => 'pass', + 'password_confirmation' => 'pass', + ]) + ->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY) + ->assertJsonPath('errors.0.meta.rule', 'min'); + } + /** * Test that a validation error is returned if the password passed in the request * does not have a confirmation, or the confirmation is not the same as the password. diff --git a/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php b/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php deleted file mode 100644 index d1ba90cf4..000000000 --- a/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php +++ /dev/null @@ -1,161 +0,0 @@ -powerRepository = m::mock(DaemonPowerRepository::class); - $this->repository = m::mock(ServerRepositoryInterface::class); - } - - /** - * Test that an action can be sent to all servers. - */ - public function testSendAction() - { - /** @var \Pterodactyl\Models\Server[] $servers */ - $servers = factory(Server::class)->times(2)->make(); - - foreach ($servers as &$server) { - $server->setRelation('node', factory(Node::class)->make()); - } - - $this->repository->expects('getServersForPowerActionCount')->with([], [])->andReturn(2); - $this->repository->expects('getServersForPowerAction')->with([], [])->andReturn($servers); - - for ($i = 0; $i < count($servers); $i++) { - $this->powerRepository->expects('setNode->setServer->send')->with('kill')->andReturnNull(); - } - - $display = $this->runCommand($this->getCommand(), ['action' => 'kill'], ['yes']); - - $this->assertNotEmpty($display); - $this->assertStringContainsString('2/2', $display); - $this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 2]), $display); - } - - /** - * Test filtering servers and nodes. - */ - public function testSendWithFilters() - { - $server = factory(Server::class)->make(); - $server->setRelation('node', $node = factory(Node::class)->make()); - - $this->repository->expects('getServersForPowerActionCount') - ->with([1, 2], [3, 4]) - ->andReturn(1); - - $this->repository->expects('getServersForPowerAction') - ->with([1, 2], [3, 4]) - ->andReturn(Collection::make([$server])); - - $this->powerRepository->expects('setNode')->with($node)->andReturnSelf(); - $this->powerRepository->expects('setServer')->with($server)->andReturnSelf(); - $this->powerRepository->expects('send')->with('kill')->andReturn(new Response); - - $display = $this->runCommand($this->getCommand(), [ - 'action' => 'kill', - '--servers' => '1,2', - '--nodes' => '3,4', - ], ['yes']); - - $this->assertNotEmpty($display); - $this->assertStringContainsString('1/1', $display); - $this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 1]), $display); - } - - /** - * Test that sending empty options returns the expected results. - */ - public function testSendWithEmptyOptions() - { - $server = factory(Server::class)->make(); - $server->setRelation('node', factory(Node::class)->make()); - - $this->repository->expects('getServersForPowerActionCount') - ->with([], []) - ->andReturn(1); - - $this->repository->expects('getServersForPowerAction')->with([], [])->andReturn(Collection::make([$server])); - $this->powerRepository->expects('setNode->setServer->send')->with('kill')->andReturnNull(); - - $display = $this->runCommand($this->getCommand(), [ - 'action' => 'kill', - '--servers' => '', - '--nodes' => '', - ], ['yes']); - - $this->assertNotEmpty($display); - $this->assertStringContainsString('1/1', $display); - $this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 1]), $display); - } - - /** - * Test that validation occurs correctly. - * - * @param array $data - * - * @dataProvider validationFailureDataProvider - */ - public function testValidationErrors(array $data) - { - $this->expectException(ValidationException::class); - $this->runCommand($this->getCommand(), $data); - } - - /** - * Provide invalid data for the command. - * - * @return array - */ - public function validationFailureDataProvider(): array - { - return [ - [['action' => 'hodor']], - [['action' => 'hodor', '--servers' => 'hodor']], - [['action' => 'kill', '--servers' => 'hodor']], - [['action' => 'kill', '--servers' => '1,2,3', '--nodes' => 'hodor']], - [['action' => 'kill', '--servers' => '1,2,3', '--nodes' => '1,2,test']], - ]; - } - - /** - * Return an instance of the command with mocked dependencies. - * - * @return \Pterodactyl\Console\Commands\Server\BulkPowerActionCommand - */ - private function getCommand(): BulkPowerActionCommand - { - return new BulkPowerActionCommand($this->powerRepository, $this->repository, $this->app->make(Factory::class)); - } -} diff --git a/yarn.lock b/yarn.lock index fff07b1d4..28348823b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7621,6 +7621,11 @@ xterm-addon-search@^0.7.0: resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.7.0.tgz#c929d3e5cbb335e82bff72f158ea82936d9cd4ef" integrity sha512-6060evmJJ+tZcjnx33FXaeEHLpuXEa7l9UzUsYfMlCKbu88AbE+5LJocTKCHYd71cwCwb9pjmv/G1o9Rf9Zbcg== +xterm-addon-web-links@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0.tgz#265cbf8221b9b315d0a748e1323bee331cd5da03" + integrity sha512-xv8GeiINmx0zENO9hf5k+5bnkaE8mRzF+OBAr9WeFq2eLaQSudioQSiT34M1ofKbzcdjSsKiZm19Rw3i4eXamg== + xterm@^4.9.0: version "4.9.0" resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0.tgz#7a4c097a433d565339b5533b468bbc60c6c87969"