Merge branch 'matthewpi/transfer-improvements' of https://github.com/Pterodactyl/Panel into matthewpi/transfer-improvements

This commit is contained in:
Dane Everitt 2020-12-24 10:10:41 -08:00
commit 25e53d9f22
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
49 changed files with 88 additions and 4473 deletions

View file

@ -19,8 +19,8 @@ HASHIDS_SALT=
HASHIDS_LENGTH=8
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_HOST=smtp.example.com
MAIL_PORT=25
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls

View file

@ -25,11 +25,13 @@ class Kernel extends ConsoleKernel
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
$schedule->command('p:schedule:process')->everyMinute()->withoutOverlapping();
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be removed
// from the UI view for the server.
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
$pruneAge = config('backups.prune_age', 360); // Defaults to 6 hours (time is in minuteS)
if ($pruneAge > 0) {
$schedule->command('p:maintenance:prune-backups', [
'--since-minutes' => '30',
'--since-minutes' => $pruneAge,
])->everyThirtyMinutes();
}
// Every day cleanup any internal backups of service files.
$schedule->command('p:maintenance:clean-service-backups')->daily();

View file

@ -13,6 +13,7 @@ use Pterodactyl\Repositories\Wings\DaemonFileRepository;
use Pterodactyl\Transformers\Daemon\FileObjectTransformer;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\PullFileRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\ListFilesRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest;
@ -143,10 +144,7 @@ class FileController extends ClientApiController
*/
public function write(WriteFileContentRequest $request, Server $server): JsonResponse
{
$this->fileRepository->setServer($server)->putContent(
$this->encode($request->get('file')),
$request->getContent()
);
$this->fileRepository->setServer($server)->putContent($request->get('file'), $request->getContent());
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
@ -284,16 +282,18 @@ class FileController extends ClientApiController
}
/**
* Encodes a given file name & path in a format that should work for a good majority
* of file names without too much confusing logic.
* Requests that a file be downloaded from a remote location by Wings.
*
* @param string $path
* @return string
* @param $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
private function encode(string $path): string
public function pull(PullFileRequest $request, Server $server): JsonResponse
{
return Collection::make(explode('/', rawurldecode($path)))->map(function ($value) {
return rawurlencode($value);
})->join('/');
$this->fileRepository->setServer($server)->pull($request->input('url'), $request->input('directory'));
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
}

View file

@ -52,7 +52,7 @@ class BackupRemoteUploadController extends Controller
public function __invoke(Request $request, string $backup)
{
// Get the size query parameter.
$size = (int)$request->query('size');
$size = (int) $request->query('size');
if (empty($size)) {
throw new BadRequestHttpException('A non-empty "size" query parameter must be provided.');
}

View file

@ -0,0 +1,29 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files;
use Pterodactyl\Models\Permission;
use Pterodactyl\Contracts\Http\ClientPermissionsRequest;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class PullFileRequest extends ClientApiRequest implements ClientPermissionsRequest
{
/**
* @return string
*/
public function permission(): string
{
return Permission::ACTION_FILE_CREATE;
}
/**
* @return string[]
*/
public function rules(): array
{
return [
'url' => 'required|string|url',
'directory' => 'sometimes|nullable|string',
];
}
}

View file

@ -2,6 +2,7 @@
namespace Pterodactyl\Repositories\Wings;
use Illuminate\Support\Arr;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Backup;
use Pterodactyl\Models\Server;
@ -48,7 +49,7 @@ class DaemonBackupRepository extends DaemonRepository
'json' => [
'adapter' => $this->adapter ?? config('backups.default'),
'uuid' => $backup->uuid,
'ignored_files' => $backup->ignored_files,
'ignore' => implode('\n', $backup->ignored_files),
],
]
);

View file

@ -37,7 +37,7 @@ class DaemonFileRepository extends DaemonRepository
throw new DaemonConnectionException($exception);
}
$length = (int) $response->getHeader('Content-Length')[0] ?? 0;
$length = (int)$response->getHeader('Content-Length')[0] ?? 0;
if ($notLargerThan && $length > $notLargerThan) {
throw new FileSizeTooLargeException;
@ -297,4 +297,29 @@ class DaemonFileRepository extends DaemonRepository
throw new DaemonConnectionException($exception);
}
}
/**
* Pulls a file from the given URL and saves it to the disk.
*
* @param string $url
* @param string|null $directory
* @return \Psr\Http\Message\ResponseInterface
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function pull(string $url, ?string $directory): ResponseInterface
{
Assert::isInstanceOf($this->server, Server::class);
try {
return $this->getHttpClient()->post(
sprintf('/api/servers/%s/files/pull', $this->server->uuid),
[
'json' => ['url' => $url, 'directory' => $directory ?? '/'],
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
}
}

View file

@ -117,9 +117,9 @@ 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) {
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) {
if (! $override || $server->backup_limit <= 0) {
throw new TooManyBackupsException($server->backup_limit);
}

View file

@ -12,6 +12,10 @@ return [
// uses to upload backups to S3 storage. Value is in minutes, so this would default to an hour.
'presigned_url_lifespan' => env('BACKUP_PRESIGNED_URL_LIFESPAN', 60),
// The time to wait before automatically failing a backup, time is in minutes and defaults
// to 6 hours. To disable this feature, set the value to `0`.
'prune_age' => env('BACKUP_PRUNE_AGE', 360),
'disks' => [
// There is no configuration for the local disk for Wings. That configuration
// is determined by the Daemon configuration, and not the Panel.

View file

@ -28,7 +28,7 @@
"name": "Sponge Version",
"description": "The version of SpongeVanilla to download and use.",
"env_variable": "SPONGE_VERSION",
"default_value": "1.11.2-6.1.0-BETA-21",
"default_value": "1.12.2-7.3.0",
"user_viewable": true,
"user_editable": false,
"rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/"

View file

@ -127,7 +127,7 @@ export default ({ database, className }: Props) => {
<Can action={'database.view_password'}>
<div css={tw`mt-6`}>
<Label>Password</Label>
<CopyOnClick text={database.password?.valueOf}><Input type={'text'} readOnly value={database.password}/></CopyOnClick>
<CopyOnClick text={database.password}><Input type={'text'} readOnly value={database.password}/></CopyOnClick>
</div>
</Can>
<div css={tw`mt-6`}>

View file

@ -66,6 +66,7 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ
Route::post('/delete', 'Servers\FileController@delete');
Route::post('/create-folder', 'Servers\FileController@create');
Route::post('/chmod', 'Servers\FileController@chmod');
Route::post('/pull', 'Servers\FileController@pull');
Route::get('/upload', 'Servers\FileUploadController');
});

View file

@ -1,59 +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\Commands;
use Tests\TestCase;
use Illuminate\Console\Command;
use Illuminate\Contracts\Foundation\Application;
use Symfony\Component\Console\Tester\CommandTester;
abstract class CommandTestCase extends TestCase
{
/**
* @var bool
*/
protected $commandIsInteractive = true;
/**
* Set a command to be non-interactive for testing purposes.
*
* @return $this
*/
public function withoutInteraction()
{
$this->commandIsInteractive = false;
return $this;
}
/**
* Return the display from running a command.
*
* @param \Illuminate\Console\Command $command
* @param array $args
* @param array $inputs
* @param array $opts
* @return string
*/
protected function runCommand(Command $command, array $args = [], array $inputs = [], array $opts = [])
{
if (! $command->getLaravel() instanceof Application) {
$command->setLaravel($this->app);
}
$response = new CommandTester($command);
$response->setInputs($inputs);
$opts = array_merge($opts, ['interactive' => $this->commandIsInteractive]);
$response->execute($args, $opts);
return $response->getDisplay();
}
}

View file

@ -1,275 +0,0 @@
<?php
namespace Tests\Unit\Commands\Environment;
use Mockery as m;
use Tests\Unit\Commands\CommandTestCase;
use Illuminate\Contracts\Config\Repository;
use Pterodactyl\Console\Commands\Environment\EmailSettingsCommand;
class EmailSettingsCommandTest extends CommandTestCase
{
/**
* @var \Pterodactyl\Console\Commands\Environment\EmailSettingsCommand|\Mockery\Mock
*/
protected $command;
/**
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
protected $config;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->config = m::mock(Repository::class);
$this->command = m::mock(EmailSettingsCommand::class . '[call, writeToEnvironment]', [$this->config]);
$this->command->setLaravel($this->app);
}
/**
* Test selection of the SMTP driver with no options passed.
*/
public function testSmtpDriverSelection()
{
// TODO(dane): fix this
$this->markTestSkipped('Skipped, GitHub actions cannot run successfully.');
// $data = [
// 'MAIL_DRIVER' => 'smtp',
// 'MAIL_HOST' => 'mail.test.com',
// 'MAIL_PORT' => '567',
// 'MAIL_USERNAME' => 'username',
// 'MAIL_PASSWORD' => 'password',
// 'MAIL_FROM' => 'mail@from.com',
// 'MAIL_FROM_NAME' => 'MailName',
// 'MAIL_ENCRYPTION' => 'tls',
// ];
//
// $this->setupCoreFunctions($data);
// $display = $this->runCommand($this->command, [], array_values($data));
//
// $this->assertNotEmpty($display);
// $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Test that the command can run when all variables are passed in as options.
*/
public function testSmtpDriverSelectionWithOptionsPassed()
{
$data = [
'MAIL_DRIVER' => 'smtp',
'MAIL_HOST' => 'mail.test.com',
'MAIL_PORT' => '567',
'MAIL_USERNAME' => 'username',
'MAIL_PASSWORD' => 'password',
'MAIL_FROM' => 'mail@from.com',
'MAIL_FROM_NAME' => 'MailName',
'MAIL_ENCRYPTION' => 'tls',
];
$this->setupCoreFunctions($data);
$display = $this->withoutInteraction()->runCommand($this->command, [
'--driver' => $data['MAIL_DRIVER'],
'--email' => $data['MAIL_FROM'],
'--from' => $data['MAIL_FROM_NAME'],
'--encryption' => $data['MAIL_ENCRYPTION'],
'--host' => $data['MAIL_HOST'],
'--port' => $data['MAIL_PORT'],
'--username' => $data['MAIL_USERNAME'],
'--password' => $data['MAIL_PASSWORD'],
]);
$this->assertNotEmpty($display);
$this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Test selection of PHP mail() as the driver.
*/
public function testPHPMailDriverSelection()
{
$data = [
'MAIL_DRIVER' => 'mail',
'MAIL_FROM' => 'mail@from.com',
'MAIL_FROM_NAME' => 'MailName',
'MAIL_ENCRYPTION' => 'tls',
];
$this->setupCoreFunctions($data);
// The driver flag is passed because there seems to be some issue with the command tester
// when using a choice() method when two keys start with the same letters.
//
// In this case, mail and mailgun.
unset($data['MAIL_DRIVER']);
$display = $this->runCommand($this->command, ['--driver' => 'mail'], array_values($data));
$this->assertNotEmpty($display);
$this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Test selection of the Mailgun driver with no options passed.
*/
public function testMailgunDriverSelection()
{
$data = [
'MAIL_DRIVER' => 'mailgun',
'MAILGUN_DOMAIN' => 'domain.com',
'MAILGUN_SECRET' => '123456',
'MAIL_FROM' => 'mail@from.com',
'MAIL_FROM_NAME' => 'MailName',
'MAIL_ENCRYPTION' => 'tls',
];
$this->setupCoreFunctions($data);
$display = $this->runCommand($this->command, [], array_values($data));
$this->assertNotEmpty($display);
$this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Test mailgun driver selection when variables are passed as options.
*/
public function testMailgunDriverSelectionWithOptionsPassed()
{
$data = [
'MAIL_DRIVER' => 'mailgun',
'MAILGUN_DOMAIN' => 'domain.com',
'MAILGUN_SECRET' => '123456',
'MAIL_FROM' => 'mail@from.com',
'MAIL_FROM_NAME' => 'MailName',
'MAIL_ENCRYPTION' => 'tls',
];
$this->setupCoreFunctions($data);
$display = $this->withoutInteraction()->runCommand($this->command, [
'--driver' => $data['MAIL_DRIVER'],
'--email' => $data['MAIL_FROM'],
'--from' => $data['MAIL_FROM_NAME'],
'--encryption' => $data['MAIL_ENCRYPTION'],
'--host' => $data['MAILGUN_DOMAIN'],
'--password' => $data['MAILGUN_SECRET'],
]);
$this->assertNotEmpty($display);
$this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Test selection of the Mandrill driver with no options passed.
*/
public function testMandrillDriverSelection()
{
$data = [
'MAIL_DRIVER' => 'mandrill',
'MANDRILL_SECRET' => '123456',
'MAIL_FROM' => 'mail@from.com',
'MAIL_FROM_NAME' => 'MailName',
'MAIL_ENCRYPTION' => 'tls',
];
$this->setupCoreFunctions($data);
$display = $this->runCommand($this->command, [], array_values($data));
$this->assertNotEmpty($display);
$this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Test mandrill driver selection when variables are passed as options.
*/
public function testMandrillDriverSelectionWithOptionsPassed()
{
$data = [
'MAIL_DRIVER' => 'mandrill',
'MANDRILL_SECRET' => '123456',
'MAIL_FROM' => 'mail@from.com',
'MAIL_FROM_NAME' => 'MailName',
'MAIL_ENCRYPTION' => 'tls',
];
$this->setupCoreFunctions($data);
$display = $this->withoutInteraction()->runCommand($this->command, [
'--driver' => $data['MAIL_DRIVER'],
'--email' => $data['MAIL_FROM'],
'--from' => $data['MAIL_FROM_NAME'],
'--encryption' => $data['MAIL_ENCRYPTION'],
'--password' => $data['MANDRILL_SECRET'],
]);
$this->assertNotEmpty($display);
$this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Test selection of the Postmark driver with no options passed.
*/
public function testPostmarkDriverSelection()
{
$data = [
'MAIL_DRIVER' => 'smtp',
'MAIL_HOST' => 'smtp.postmarkapp.com',
'MAIL_PORT' => '587',
'MAIL_USERNAME' => '123456',
'MAIL_PASSWORD' => '123456',
'MAIL_FROM' => 'mail@from.com',
'MAIL_FROM_NAME' => 'MailName',
'MAIL_ENCRYPTION' => 'tls',
];
$this->setupCoreFunctions($data);
$display = $this->runCommand($this->command, [], [
'postmark', '123456', $data['MAIL_FROM'], $data['MAIL_FROM_NAME'], $data['MAIL_ENCRYPTION'],
]);
$this->assertNotEmpty($display);
$this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Test postmark driver selection when variables are passed as options.
*/
public function testPostmarkDriverSelectionWithOptionsPassed()
{
$data = [
'MAIL_DRIVER' => 'smtp',
'MAIL_HOST' => 'smtp.postmarkapp.com',
'MAIL_PORT' => '587',
'MAIL_USERNAME' => '123456',
'MAIL_PASSWORD' => '123456',
'MAIL_FROM' => 'mail@from.com',
'MAIL_FROM_NAME' => 'MailName',
'MAIL_ENCRYPTION' => 'tls',
];
$this->setupCoreFunctions($data);
$display = $this->withoutInteraction()->runCommand($this->command, [
'--driver' => 'postmark',
'--email' => $data['MAIL_FROM'],
'--from' => $data['MAIL_FROM_NAME'],
'--encryption' => $data['MAIL_ENCRYPTION'],
'--username' => $data['MAIL_USERNAME'],
]);
$this->assertNotEmpty($display);
$this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
* Setup the core functions that are repeated across all of these tests.
*
* @param array $data
*/
private function setupCoreFunctions(array $data)
{
$this->config->shouldReceive('get')->withAnyArgs()->zeroOrMoreTimes()->andReturnNull();
$this->command->shouldReceive('writeToEnvironment')->with($data)->once()->andReturnNull();
}
}

View file

@ -1,128 +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\Commands\Location;
use Mockery as m;
use Pterodactyl\Models\Location;
use Tests\Unit\Commands\CommandTestCase;
use Pterodactyl\Services\Locations\LocationDeletionService;
use Pterodactyl\Console\Commands\Location\DeleteLocationCommand;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
class DeleteLocationCommandTest extends CommandTestCase
{
/**
* @var \Pterodactyl\Console\Commands\Location\DeleteLocationCommand
*/
protected $command;
/**
* @var \Pterodactyl\Services\Locations\LocationDeletionService|\Mockery\Mock
*/
protected $deletionService;
/**
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->deletionService = m::mock(LocationDeletionService::class);
$this->repository = m::mock(LocationRepositoryInterface::class);
$this->command = new DeleteLocationCommand($this->deletionService, $this->repository);
$this->command->setLaravel($this->app);
}
/**
* Test that a location can be deleted.
*/
public function testLocationIsDeleted()
{
$locations = collect([
$location1 = factory(Location::class)->make(),
$location2 = factory(Location::class)->make(),
]);
$this->repository->shouldReceive('all')->withNoArgs()->once()->andReturn($locations);
$this->deletionService->shouldReceive('handle')->with($location2->id)->once()->andReturnNull();
$display = $this->runCommand($this->command, [], [$location2->short]);
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.location.deleted'), $display);
}
/**
* Test that a location is deleted if passed in as an option.
*/
public function testLocationIsDeletedIfPassedInOption()
{
$locations = collect([
$location1 = factory(Location::class)->make(),
$location2 = factory(Location::class)->make(),
]);
$this->repository->shouldReceive('all')->withNoArgs()->once()->andReturn($locations);
$this->deletionService->shouldReceive('handle')->with($location2->id)->once()->andReturnNull();
$display = $this->withoutInteraction()->runCommand($this->command, [
'--short' => $location2->short,
]);
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.location.deleted'), $display);
}
/**
* Test that prompt shows back up if the user enters the wrong parameters.
*/
public function testInteractiveEnvironmentAllowsReAttemptingSearch()
{
$locations = collect([
$location1 = factory(Location::class)->make(),
$location2 = factory(Location::class)->make(),
]);
$this->repository->shouldReceive('all')->withNoArgs()->once()->andReturn($locations);
$this->deletionService->shouldReceive('handle')->with($location2->id)->once()->andReturnNull();
$display = $this->runCommand($this->command, [], ['123_not_exist', 'another_not_exist', $location2->short]);
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.location.no_location_found'), $display);
$this->assertStringContainsString(trans('command/messages.location.deleted'), $display);
}
/**
* Test that no re-attempt is performed in a non-interactive environment.
*/
public function testNonInteractiveEnvironmentThrowsErrorIfNoLocationIsFound()
{
$locations = collect([
$location1 = factory(Location::class)->make(),
$location2 = factory(Location::class)->make(),
]);
$this->repository->shouldReceive('all')->withNoArgs()->once()->andReturn($locations);
$this->deletionService->shouldNotReceive('handle');
$display = $this->withoutInteraction()->runCommand($this->command, ['--short' => 'randomTestString']);
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.location.no_location_found'), $display);
}
}

View file

@ -1,87 +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\Commands\Location;
use Mockery as m;
use Pterodactyl\Models\Location;
use Tests\Unit\Commands\CommandTestCase;
use Pterodactyl\Services\Locations\LocationCreationService;
use Pterodactyl\Console\Commands\Location\MakeLocationCommand;
class MakeLocationCommandTest extends CommandTestCase
{
/**
* @var \Pterodactyl\Console\Commands\Location\MakeLocationCommand
*/
protected $command;
/**
* @var \Pterodactyl\Services\Locations\LocationCreationService|\Mockery\Mock
*/
protected $creationService;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->creationService = m::mock(LocationCreationService::class);
$this->command = new MakeLocationCommand($this->creationService);
$this->command->setLaravel($this->app);
}
/**
* Test that a location can be created when no options are passed.
*/
public function testLocationIsCreatedWithNoOptionsPassed()
{
$location = factory(Location::class)->make();
$this->creationService->shouldReceive('handle')->with([
'short' => $location->short,
'long' => $location->long,
])->once()->andReturn($location);
$display = $this->runCommand($this->command, [], [$location->short, $location->long]);
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.location.created', [
'name' => $location->short,
'id' => $location->id,
]), $display);
}
/**
* Test that a location is created when options are passed.
*/
public function testLocationIsCreatedWhenOptionsArePassed()
{
$location = factory(Location::class)->make();
$this->creationService->shouldReceive('handle')->with([
'short' => $location->short,
'long' => $location->long,
])->once()->andReturn($location);
$display = $this->withoutInteraction()->runCommand($this->command, [
'--short' => $location->short,
'--long' => $location->long,
]);
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.location.created', [
'name' => $location->short,
'id' => $location->id,
]), $display);
}
}

View file

@ -1,87 +0,0 @@
<?php
namespace Tests\Unit\Commands\Maintenance;
use SplFileInfo;
use Mockery as m;
use Carbon\Carbon;
use Tests\Unit\Commands\CommandTestCase;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Contracts\Filesystem\Filesystem;
use Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
class CleanServiceBackupFilesCommandTest extends CommandTestCase
{
/**
* @var \Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand
*/
protected $command;
/**
* @var \Illuminate\Contracts\Filesystem\Filesystem|\Mockery\Mock
*/
protected $disk;
/**
* @var \Illuminate\Contracts\Filesystem\Factory|\Mockery\Mock
*/
protected $filesystem;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
Carbon::setTestNow(Carbon::now());
$this->disk = m::mock(Filesystem::class);
$this->filesystem = m::mock(Factory::class);
$this->filesystem->shouldReceive('disk')->withNoArgs()->once()->andReturn($this->disk);
}
/**
* Test that a file is deleted if it is > 5min old.
*/
public function testCommandCleansFilesMoreThan5MinutesOld()
{
$file = new SplFileInfo('testfile.txt');
$this->disk->shouldReceive('files')->with('services/.bak')->once()->andReturn([$file]);
$this->disk->shouldReceive('lastModified')->with($file->getPath())->once()->andReturn(Carbon::now()->subDays(100)->getTimestamp());
$this->disk->shouldReceive('delete')->with($file->getPath())->once()->andReturnNull();
$display = $this->runCommand($this->getCommand());
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.maintenance.deleting_service_backup', ['file' => 'testfile.txt']), $display);
}
/**
* Test that a file isn't deleted if it is < 5min old.
*/
public function testCommandDoesNotCleanFileLessThan5MinutesOld()
{
$file = new SplFileInfo('testfile.txt');
$this->disk->shouldReceive('files')->with('services/.bak')->once()->andReturn([$file]);
$this->disk->shouldReceive('lastModified')->with($file->getPath())->once()->andReturn(Carbon::now()->getTimestamp());
$display = $this->runCommand($this->getCommand());
$this->assertEmpty($display);
}
/**
* Return an instance of the command for testing.
*
* @return \Pterodactyl\Console\Commands\Maintenance\CleanServiceBackupFilesCommand
*/
private function getCommand(): CleanServiceBackupFilesCommand
{
$command = new CleanServiceBackupFilesCommand($this->filesystem);
$command->setLaravel($this->app);
return $command;
}
}

View file

@ -1,83 +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\Commands\User;
use Mockery as m;
use Pterodactyl\Models\User;
use Tests\Unit\Commands\CommandTestCase;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Console\Commands\User\DisableTwoFactorCommand;
class DisableTwoFactorCommandTest extends CommandTestCase
{
/**
* @var \Pterodactyl\Console\Commands\User\DisableTwoFactorCommand
*/
protected $command;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(UserRepositoryInterface::class);
$this->command = new DisableTwoFactorCommand($this->repository);
$this->command->setLaravel($this->app);
}
/**
* Test 2-factor auth is disabled when no option is passed.
*/
public function testTwoFactorIsDisabledWhenNoOptionIsPassed()
{
$user = factory(User::class)->make();
$this->repository->shouldReceive('setColumns')->with(['id', 'email'])->once()->andReturnSelf()
->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user);
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($user->id, [
'use_totp' => false,
'totp_secret' => null,
])->once()->andReturnNull();
$display = $this->runCommand($this->command, [], [$user->email]);
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display);
}
/**
* Test 2-factor auth is disabled when user is passed in option.
*/
public function testTwoFactorIsDisabledWhenOptionIsPassed()
{
$user = factory(User::class)->make();
$this->repository->shouldReceive('setColumns')->with(['id', 'email'])->once()->andReturnSelf()
->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user);
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($user->id, [
'use_totp' => false,
'totp_secret' => null,
])->once()->andReturnNull();
$display = $this->withoutInteraction()->runCommand($this->command, ['--email' => $user->email]);
$this->assertNotEmpty($display);
$this->assertStringContainsString(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display);
}
}

View file

@ -1,132 +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\Commands\User;
use Mockery as m;
use Pterodactyl\Models\User;
use Tests\Unit\Commands\CommandTestCase;
use Pterodactyl\Services\Users\UserCreationService;
use Pterodactyl\Console\Commands\User\MakeUserCommand;
class MakeUserCommandTest extends CommandTestCase
{
/**
* @var \Pterodactyl\Console\Commands\User\MakeUserCommand
*/
protected $command;
/**
* @var \Pterodactyl\Services\Users\UserCreationService
*/
protected $creationService;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->creationService = m::mock(UserCreationService::class);
$this->command = new MakeUserCommand($this->creationService);
$this->command->setLaravel($this->app);
}
/**
* Test that the command executes if no options are passed.
*/
public function testCommandWithNoPassedOptions()
{
// TODO(dane): fix this
$this->markTestSkipped('Skipped, GitHub actions cannot run successfully.');
// $user = factory(User::class)->make(['root_admin' => true]);
//
// $this->creationService->shouldReceive('handle')->with([
// 'email' => $user->email,
// 'username' => $user->username,
// 'name_first' => $user->name_first,
// 'name_last' => $user->name_last,
// 'password' => 'Password123',
// 'root_admin' => $user->root_admin,
// ])->once()->andReturn($user);
//
// $display = $this->runCommand($this->command, [], [
// 'yes', $user->email, $user->username, $user->name_first, $user->name_last, 'Password123',
// ]);
//
// $this->assertNotEmpty($display);
// $this->assertStringContainsString(trans('command/messages.user.ask_password_help'), $display);
// $this->assertStringContainsString($user->uuid, $display);
// $this->assertStringContainsString($user->email, $display);
// $this->assertStringContainsString($user->username, $display);
// $this->assertStringContainsString($user->name, $display);
// $this->assertStringContainsString('Yes', $display);
}
/**
* Test that the --no-password flag works as intended.
*/
public function testCommandWithNoPasswordOption()
{
$user = factory(User::class)->make(['root_admin' => true]);
$this->creationService->shouldReceive('handle')->with([
'email' => $user->email,
'username' => $user->username,
'name_first' => $user->name_first,
'name_last' => $user->name_last,
'password' => null,
'root_admin' => $user->root_admin,
])->once()->andReturn($user);
$display = $this->runCommand($this->command, ['--no-password' => true], [
'yes', $user->email, $user->username, $user->name_first, $user->name_last,
]);
$this->assertNotEmpty($display);
$this->assertStringNotContainsString(trans('command/messages.user.ask_password_help'), $display);
}
/**
* Test command when arguments are passed as flags.
*/
public function testCommandWithOptionsPassed()
{
$user = factory(User::class)->make(['root_admin' => false]);
$this->creationService->shouldReceive('handle')->with([
'email' => $user->email,
'username' => $user->username,
'name_first' => $user->name_first,
'name_last' => $user->name_last,
'password' => 'Password123',
'root_admin' => $user->root_admin,
])->once()->andReturn($user);
$display = $this->withoutInteraction()->runCommand($this->command, [
'--email' => $user->email,
'--username' => $user->username,
'--name-first' => $user->name_first,
'--name-last' => $user->name_last,
'--password' => 'Password123',
'--admin' => 0,
]);
$this->assertNotEmpty($display);
$this->assertStringNotContainsString(trans('command/messages.user.ask_password_help'), $display);
$this->assertStringContainsString($user->uuid, $display);
$this->assertStringContainsString($user->email, $display);
$this->assertStringContainsString($user->username, $display);
$this->assertStringContainsString($user->name, $display);
$this->assertStringContainsString('No', $display);
}
}

View file

@ -1,143 +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\Http\Controllers\Admin;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\DatabaseHost;
use Prologue\Alerts\AlertsMessageBag;
use Illuminate\Pagination\LengthAwarePaginator;
use Tests\Assertions\ControllerAssertionsTrait;
use Pterodactyl\Http\Controllers\Admin\DatabaseController;
use Pterodactyl\Services\Databases\Hosts\HostUpdateService;
use Pterodactyl\Services\Databases\Hosts\HostCreationService;
use Pterodactyl\Services\Databases\Hosts\HostDeletionService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
class DatabaseControllerTest extends TestCase
{
use ControllerAssertionsTrait;
/**
* @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock
*/
private $alert;
/**
* @var \Pterodactyl\Services\Databases\Hosts\HostCreationService|\Mockery\Mock
*/
private $creationService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface|\Mockery\Mock
*/
private $databaseRepository;
/**
* @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService|\Mockery\Mock
*/
private $deletionService;
/**
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface|\Mockery\Mock
*/
private $locationRepository;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* @var \Pterodactyl\Services\Databases\Hosts\HostUpdateService|\Mockery\Mock
*/
private $updateService;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->alert = m::mock(AlertsMessageBag::class);
$this->creationService = m::mock(HostCreationService::class);
$this->databaseRepository = m::mock(DatabaseRepositoryInterface::class);
$this->deletionService = m::mock(HostDeletionService::class);
$this->locationRepository = m::mock(LocationRepositoryInterface::class);
$this->repository = m::mock(DatabaseHostRepositoryInterface::class);
$this->updateService = m::mock(HostUpdateService::class);
}
/**
* Test the index controller.
*/
public function testIndexController()
{
$this->locationRepository->shouldReceive('getAllWithNodes')->withNoArgs()->once()->andReturn(collect(['getAllWithNodes']));
$this->repository->shouldReceive('getWithViewDetails')->withNoArgs()->once()->andReturn(collect(['getWithViewDetails']));
$response = $this->getController()->index();
$this->assertIsViewResponse($response);
$this->assertViewNameEquals('admin.databases.index', $response);
$this->assertViewHasKey('locations', $response);
$this->assertViewHasKey('hosts', $response);
$this->assertViewKeyEquals('locations', collect(['getAllWithNodes']), $response);
$this->assertViewKeyEquals('hosts', collect(['getWithViewDetails']), $response);
}
/**
* Test the view controller for displaying a specific database host.
*/
public function testViewController()
{
$model = factory(DatabaseHost::class)->make();
$paginator = new LengthAwarePaginator([], 1, 1);
$this->locationRepository->shouldReceive('getAllWithNodes')->withNoArgs()->once()->andReturn(collect(['getAllWithNodes']));
$this->repository->shouldReceive('find')->with(1)->once()->andReturn($model);
$this->databaseRepository->shouldReceive('getDatabasesForHost')
->once()
->with(1)
->andReturn($paginator);
$response = $this->getController()->view(1);
$this->assertIsViewResponse($response);
$this->assertViewNameEquals('admin.databases.view', $response);
$this->assertViewHasKey('locations', $response);
$this->assertViewHasKey('host', $response);
$this->assertViewHasKey('databases', $response);
$this->assertViewKeyEquals('locations', collect(['getAllWithNodes']), $response);
$this->assertViewKeyEquals('host', $model, $response);
$this->assertViewKeyEquals('databases', $paginator, $response);
}
/**
* Return an instance of the DatabaseController with mock dependencies.
*
* @return \Pterodactyl\Http\Controllers\Admin\DatabaseController
*/
private function getController(): DatabaseController
{
return new DatabaseController(
$this->alert,
$this->repository,
$this->databaseRepository,
$this->creationService,
$this->deletionService,
$this->updateService,
$this->locationRepository
);
}
}

View file

@ -1,82 +0,0 @@
<?php
namespace Tests\Unit\Http\Controllers;
use Mockery as m;
use Prologue\Alerts\AlertsMessageBag;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Pterodactyl\Http\Controllers\Admin\Settings\MailController;
use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface;
class MailControllerTest extends ControllerTestCase
{
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
private $alert;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
private $configRepository;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
private $encrypter;
/**
* @var \Illuminate\Contracts\Console\Kernel
*/
private $kernel;
/**
* @var \Pterodactyl\Contracts\Repository\SettingsRepositoryInterface
*/
private $settingsRepositoryInterface;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->alert = m::mock(AlertsMessageBag::class);
$this->configRepository = m::mock(ConfigRepository::class);
$this->encrypter = m::mock(Encrypter::class);
$this->kernel = m::mock(Kernel::class);
$this->settingsRepositoryInterface = m::mock(SettingsRepositoryInterface::class);
}
/**
* Test the mail controller for viewing mail settings page.
*/
public function testIndex()
{
$this->configRepository->shouldReceive('get');
$response = $this->getController()->index();
$this->assertIsViewResponse($response);
$this->assertViewNameEquals('admin.settings.mail', $response);
}
/**
* Prepare a MailController using our mocks.
*
* @return MailController
*/
public function getController()
{
return new MailController(
$this->alert,
$this->configRepository,
$this->encrypter,
$this->kernel,
$this->settingsRepositoryInterface
);
}
}

View file

@ -1,99 +0,0 @@
<?php
namespace Tests\Unit\Http\Controllers;
use Mockery as m;
use Tests\TestCase;
use Tests\Traits\Http\RequestMockHelpers;
use Tests\Assertions\ControllerAssertionsTrait;
abstract class ControllerTestCase extends TestCase
{
use ControllerAssertionsTrait, RequestMockHelpers;
/**
* @var \Pterodactyl\Http\Controllers\Controller|\Mockery\Mock
*/
private $controller;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->buildRequestMock();
}
/**
* Set an instance of the controller.
*
* @param \Pterodactyl\Http\Controllers\Controller|\Mockery\Mock $controller
*/
public function setControllerInstance($controller)
{
$this->controller = $controller;
}
/**
* Return an instance of the controller.
*
* @return \Mockery\Mock|\Pterodactyl\Http\Controllers\Controller
*/
public function getControllerInstance()
{
return $this->controller;
}
/**
* Helper function to mock injectJavascript requests.
*
* @param array|null $args
* @param bool $subset
*/
protected function mockInjectJavascript(array $args = null, bool $subset = false)
{
$controller = $this->getControllerInstance();
$controller->shouldReceive('setRequest')->with($this->request)->once()->andReturnSelf();
if (is_null($args)) {
$controller->shouldReceive('injectJavascript')->withAnyArgs()->once()->andReturnNull();
} else {
$with = $subset ? m::subset($args) : $args;
$controller->shouldReceive('injectJavascript')->with($with)->once()->andReturnNull();
}
}
/**
* Mocks a request input call.
*
* @param string $param
* @param mixed $return
*/
protected function mockRequestInput(string $param, $return = null)
{
$this->request->shouldReceive('input')->withArgs(function ($k) use ($param) {
return $k === $param;
})->andReturn($return);
}
/**
* Build and return a mocked controller instance to use for testing.
*
* @param string $class
* @param array $args
* @return \Mockery\Mock|\Pterodactyl\Http\Controllers\Controller
*/
protected function buildMockedController(string $class, array $args = [])
{
$controller = m::mock($class, $args)->makePartial();
if (is_null($this->getControllerInstance())) {
$this->setControllerInstance($controller);
}
return $this->getControllerInstance();
}
}

View file

@ -1,60 +0,0 @@
<?php
namespace Tests\Unit\Services\Allocations;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Allocation;
use Pterodactyl\Services\Allocations\AllocationDeletionService;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException;
class AllocationDeletionServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface|\Mockery\Mock
*/
private $repository;
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(AllocationRepositoryInterface::class);
}
/**
* Test that an allocation is deleted.
*/
public function testAllocationIsDeleted()
{
$model = factory(Allocation::class)->make(['id' => 123]);
$this->repository->expects('delete')->with($model->id)->andReturns(1);
$response = $this->getService()->handle($model);
$this->assertEquals(1, $response);
}
/**
* Test that an exception gets thrown if an allocation is currently assigned to a server.
*/
public function testExceptionThrownIfAssignedToServer()
{
$this->expectException(ServerUsingAllocationException::class);
$model = factory(Allocation::class)->make(['server_id' => 123]);
$this->getService()->handle($model);
}
/**
* Return an instance of the service with mocked injections.
*
* @return \Pterodactyl\Services\Allocations\AllocationDeletionService
*/
private function getService(): AllocationDeletionService
{
return new AllocationDeletionService($this->repository);
}
}

View file

@ -1,291 +0,0 @@
<?php
namespace Tests\Unit\Services\Allocations;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Node;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Services\Allocations\AssignmentService;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException;
use Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException;
use Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException;
use Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException;
class AssignmentServiceTest extends TestCase
{
/**
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
*/
protected $connection;
/**
* @var \Pterodactyl\Models\Node
*/
protected $node;
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->node = factory(Node::class)->make();
$this->connection = m::mock(ConnectionInterface::class);
$this->repository = m::mock(AllocationRepositoryInterface::class);
}
/**
* Test a non-CIDR notated IP address without a port range.
*/
public function testIndividualIpAddressWithoutRange()
{
$data = [
'allocation_ip' => '192.168.1.1',
'allocation_ports' => ['2222'],
];
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull();
$this->repository->shouldReceive('insertIgnore')->with([
[
'node_id' => $this->node->id,
'ip' => '192.168.1.1',
'port' => 2222,
'ip_alias' => null,
'server_id' => null,
],
])->once()->andReturn(true);
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle($this->node, $data);
}
/**
* Test a non-CIDR IP address with a port range provided.
*/
public function testIndividualIpAddressWithRange()
{
$data = [
'allocation_ip' => '192.168.1.1',
'allocation_ports' => ['1025-1027'],
];
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull();
$this->repository->shouldReceive('insertIgnore')->once()->with([
[
'node_id' => $this->node->id,
'ip' => '192.168.1.1',
'port' => 1025,
'ip_alias' => null,
'server_id' => null,
],
[
'node_id' => $this->node->id,
'ip' => '192.168.1.1',
'port' => 1026,
'ip_alias' => null,
'server_id' => null,
],
[
'node_id' => $this->node->id,
'ip' => '192.168.1.1',
'port' => 1027,
'ip_alias' => null,
'server_id' => null,
],
])->andReturn(true);
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle($this->node, $data);
}
/**
* Test a non-CIDR IP address with a single port and an alias.
*/
public function testIndividualIPAddressWithAlias()
{
$data = [
'allocation_ip' => '192.168.1.1',
'allocation_ports' => ['2222'],
'allocation_alias' => 'my.alias.net',
];
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull();
$this->repository->shouldReceive('insertIgnore')->once()->with([
[
'node_id' => $this->node->id,
'ip' => '192.168.1.1',
'port' => 2222,
'ip_alias' => 'my.alias.net',
'server_id' => null,
],
])->andReturn(true);
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle($this->node, $data);
}
/**
* Test that a domain name can be passed in place of an IP address.
*/
public function testDomainNamePassedInPlaceOfIPAddress()
{
$data = [
'allocation_ip' => 'unit-test-static.pterodactyl.io',
'allocation_ports' => ['2222'],
];
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull();
$this->repository->shouldReceive('insertIgnore')->once()->with([
[
'node_id' => $this->node->id,
'ip' => '127.0.0.1',
'port' => 2222,
'ip_alias' => null,
'server_id' => null,
],
])->andReturn(true);
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle($this->node, $data);
}
/**
* Test that a CIDR IP address without a range works properly.
*/
public function testCIDRNotatedIPAddressWithoutRange()
{
$data = [
'allocation_ip' => '192.168.1.100/31',
'allocation_ports' => ['2222'],
];
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull();
$this->repository->shouldReceive('insertIgnore')->once()->with([
[
'node_id' => $this->node->id,
'ip' => '192.168.1.100',
'port' => 2222,
'ip_alias' => null,
'server_id' => null,
],
])->andReturn(true);
$this->repository->shouldReceive('insertIgnore')->once()->with([
[
'node_id' => $this->node->id,
'ip' => '192.168.1.101',
'port' => 2222,
'ip_alias' => null,
'server_id' => null,
],
])->andReturn(true);
$this->connection->shouldReceive('commit')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle($this->node, $data);
}
/**
* Test that a CIDR IP address with a range works properly.
*/
public function testCIDRNotatedIPAddressOutsideRangeLimit()
{
$this->expectException(CidrOutOfRangeException::class);
$this->expectExceptionMessage('CIDR notation only allows masks between /25 and /32.');
$data = [
'allocation_ip' => '192.168.1.100/20',
'allocation_ports' => ['2222'],
];
$this->getService()->handle($this->node, $data);
}
/**
* Test that an exception is thrown if there are too many ports.
*/
public function testAllocationWithPortsExceedingLimit()
{
$this->expectException(TooManyPortsInRangeException::class);
$this->expectExceptionMessage('Adding more than 1000 ports in a single range at once is not supported.');
$data = [
'allocation_ip' => '192.168.1.1',
'allocation_ports' => ['5000-7000'],
];
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle($this->node, $data);
}
/**
* Test that an exception is thrown if an invalid port is provided.
*/
public function testInvalidPortProvided()
{
$this->expectException(InvalidPortMappingException::class);
$this->expectExceptionMessage('The mapping provided for test123 was invalid and could not be processed.');
$data = [
'allocation_ip' => '192.168.1.1',
'allocation_ports' => ['test123'],
];
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle($this->node, $data);
}
/**
* Test that ports outside of defined limits throw an error.
*
* @param array $ports
*
* @dataProvider invalidPortsDataProvider
*/
public function testPortRangeOutsideOfRangeLimits(array $ports)
{
$this->expectException(PortOutOfRangeException::class);
$this->expectExceptionMessage('Ports in an allocation must be greater than 1024 and less than or equal to 65535.');
$data = ['allocation_ip' => '192.168.1.1', 'allocation_ports' => $ports];
$this->connection->shouldReceive('beginTransaction')->once()->withNoArgs()->andReturnNull();
$this->getService()->handle($this->node, $data);
}
/**
* Provide ports and ranges of ports that exceed the viable port limits for the software.
*
* @return array
*/
public function invalidPortsDataProvider(): array
{
return [
[['65536']],
[['1024']],
[['1000']],
[['0']],
[['65530-65540']],
[['65540-65560']],
[[PHP_INT_MAX]],
];
}
/**
* Returns an instance of the service with mocked dependencies for testing.
*
* @return \Pterodactyl\Services\Allocations\AssignmentService
*/
private function getService(): AssignmentService
{
return new AssignmentService($this->repository, $this->connection);
}
}

View file

@ -1,92 +0,0 @@
<?php
namespace Tests\Unit\Services\Databases;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Database;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Extensions\DynamicDatabaseConnection;
use Pterodactyl\Services\Databases\DatabasePasswordService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
class DatabasePasswordServiceTest extends TestCase
{
/**
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
*/
private $connection;
/**
* @var \Pterodactyl\Extensions\DynamicDatabaseConnection|\Mockery\Mock
*/
private $dynamic;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->connection = m::mock(ConnectionInterface::class);
$this->dynamic = m::mock(DynamicDatabaseConnection::class);
$this->encrypter = m::mock(Encrypter::class);
$this->repository = m::mock(DatabaseRepositoryInterface::class);
}
/**
* Test that a password can be updated.
*/
public function testPasswordIsChanged()
{
/** @var \Pterodactyl\Models\Database $model */
$model = factory(Database::class)->make(['max_connections' => 0]);
$this->connection->expects('transaction')->with(m::on(function ($closure) {
return is_null($closure());
}));
$this->dynamic->expects('set')->with('dynamic', $model->database_host_id)->andReturnNull();
$this->encrypter->expects('encrypt')->with(m::on(function ($string) {
preg_match_all('/[!@+=.^-]/', $string, $matches, PREG_SET_ORDER);
$this->assertTrue(count($matches) >= 2 && count($matches) <= 6, "Failed asserting that [{$string}] contains 2 to 6 special characters.");
$this->assertTrue(strlen($string) === 24, "Failed asserting that [{$string}] is 24 characters in length.");
return true;
}))->andReturn('enc123');
$this->repository->expects('withoutFreshModel')->withNoArgs()->andReturnSelf();
$this->repository->expects('update')->with($model->id, ['password' => 'enc123'])->andReturn(true);
$this->repository->expects('dropUser')->with($model->username, $model->remote)->andReturn(true);
$this->repository->expects('createUser')->with($model->username, $model->remote, m::any(), 0)->andReturn(true);
$this->repository->expects('assignUserToDatabase')->with($model->database, $model->username, $model->remote)->andReturn(true);
$this->repository->expects('flush')->withNoArgs()->andReturn(true);
$response = $this->getService()->handle($model);
$this->assertNotEmpty($response);
}
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Databases\DatabasePasswordService
*/
private function getService(): DatabasePasswordService
{
return new DatabasePasswordService($this->connection, $this->repository, $this->dynamic, $this->encrypter);
}
}

View file

@ -1,103 +0,0 @@
<?php
namespace Tests\Unit\Services\Databases\Hosts;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\DatabaseHost;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Extensions\DynamicDatabaseConnection;
use Pterodactyl\Services\Databases\Hosts\HostCreationService;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
class HostCreationServiceTest extends TestCase
{
/**
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
*/
private $connection;
/**
* @var \Illuminate\Database\DatabaseManager|\Mockery\Mock
*/
private $databaseManager;
/**
* @var \Pterodactyl\Extensions\DynamicDatabaseConnection|\Mockery\Mock
*/
private $dynamic;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->connection = m::mock(ConnectionInterface::class);
$this->databaseManager = m::mock(DatabaseManager::class);
$this->dynamic = m::mock(DynamicDatabaseConnection::class);
$this->encrypter = m::mock(Encrypter::class);
$this->repository = m::mock(DatabaseHostRepositoryInterface::class);
}
/**
* Test that a database host can be created.
*/
public function testDatabaseHostIsCreated()
{
$model = factory(DatabaseHost::class)->make();
$this->connection->expects('transaction')->with(m::on(function ($closure) {
return ! is_null($closure());
}))->andReturn($model);
$this->encrypter->expects('encrypt')->with('test123')->andReturn('enc123');
$this->repository->expects('create')->with(m::subset([
'password' => 'enc123',
'username' => $model->username,
'node_id' => $model->node_id,
]))->andReturn($model);
$this->dynamic->expects('set')->with('dynamic', $model)->andReturnNull();
$this->databaseManager->expects('connection')->with('dynamic')->andReturnSelf();
$this->databaseManager->expects('select')->with('SELECT 1 FROM dual')->andReturnNull();
$response = $this->getService()->handle([
'password' => 'test123',
'username' => $model->username,
'node_id' => $model->node_id,
]);
$this->assertNotEmpty($response);
$this->assertSame($model, $response);
}
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Databases\Hosts\HostCreationService
*/
private function getService(): HostCreationService
{
return new HostCreationService(
$this->connection,
$this->databaseManager,
$this->repository,
$this->dynamic,
$this->encrypter
);
}
}

View file

@ -1,85 +0,0 @@
<?php
namespace Tests\Unit\Services\Databases\Hosts;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Services\Databases\Hosts\HostDeletionService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
class HostDeletionServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface|\Mockery\Mock
*/
private $databaseRepository;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->databaseRepository = m::mock(DatabaseRepositoryInterface::class);
$this->repository = m::mock(DatabaseHostRepositoryInterface::class);
}
/**
* Test that a host can be deleted.
*/
public function testHostIsDeleted()
{
$this->databaseRepository->shouldReceive('findCountWhere')->with([['database_host_id', '=', 1234]])->once()->andReturn(0);
$this->repository->shouldReceive('delete')->with(1234)->once()->andReturn(1);
$response = $this->getService()->handle(1234);
$this->assertNotEmpty($response);
$this->assertSame(1, $response);
}
/**
* Test that an exception is thrown if a host with databases is deleted.
*
* @dataProvider databaseCountDataProvider
*/
public function testExceptionIsThrownIfDeletingHostWithDatabases(int $count)
{
$this->databaseRepository->shouldReceive('findCountWhere')->with([['database_host_id', '=', 1234]])->once()->andReturn($count);
try {
$this->getService()->handle(1234);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(HasActiveServersException::class, $exception);
$this->assertEquals(trans('exceptions.databases.delete_has_databases'), $exception->getMessage());
}
}
/**
* Data provider to ensure exceptions are thrown for any value > 0.
*
* @return array
*/
public function databaseCountDataProvider(): array
{
return [[1], [2], [10]];
}
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Databases\Hosts\HostDeletionService
*/
private function getService(): HostDeletionService
{
return new HostDeletionService($this->databaseRepository, $this->repository);
}
}

View file

@ -1,114 +0,0 @@
<?php
namespace Tests\Unit\Services\Databases\Hosts;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\DatabaseHost;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Extensions\DynamicDatabaseConnection;
use Pterodactyl\Services\Databases\Hosts\HostUpdateService;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
class HostUpdateServiceTest extends TestCase
{
/**
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
*/
private $connection;
/**
* @var \Illuminate\Database\DatabaseManager|\Mockery\Mock
*/
private $databaseManager;
/**
* @var \Pterodactyl\Extensions\DynamicDatabaseConnection|\Mockery\Mock
*/
private $dynamic;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->connection = m::mock(ConnectionInterface::class);
$this->databaseManager = m::mock(DatabaseManager::class);
$this->dynamic = m::mock(DynamicDatabaseConnection::class);
$this->encrypter = m::mock(Encrypter::class);
$this->repository = m::mock(DatabaseHostRepositoryInterface::class);
}
/**
* Test that a password is encrypted before storage if provided.
*/
public function testPasswordIsEncryptedWhenProvided()
{
$model = factory(DatabaseHost::class)->make();
$this->connection->expects('transaction')->with(m::on(function ($closure) {
return ! is_null($closure());
}))->andReturn($model);
$this->encrypter->expects('encrypt')->with('test123')->andReturn('enc123');
$this->repository->expects('update')->with(1234, ['password' => 'enc123'])->andReturn($model);
$this->dynamic->expects('set')->with('dynamic', $model)->andReturnNull();
$this->databaseManager->expects('connection')->with('dynamic')->andReturnSelf();
$this->databaseManager->expects('select')->with('SELECT 1 FROM dual')->andReturnNull();
$response = $this->getService()->handle(1234, ['password' => 'test123']);
$this->assertNotEmpty($response);
$this->assertSame($model, $response);
}
/**
* Test that updates still occur when no password is provided.
*/
public function testUpdateOccursWhenNoPasswordIsProvided()
{
$model = factory(DatabaseHost::class)->make();
$this->connection->expects('transaction')->with(m::on(function ($closure) {
return ! is_null($closure());
}))->andReturn($model);
$this->repository->expects('update')->with(1234, ['username' => 'test'])->andReturn($model);
$this->dynamic->expects('set')->with('dynamic', $model)->andReturnNull();
$this->databaseManager->expects('connection')->with('dynamic')->andReturnSelf();
$this->databaseManager->expects('select')->with('SELECT 1 FROM dual')->andReturnNull();
$response = $this->getService()->handle(1234, ['password' => '', 'username' => 'test']);
$this->assertNotEmpty($response);
$this->assertSame($model, $response);
}
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Databases\Hosts\HostUpdateService
*/
private function getService(): HostUpdateService
{
return new HostUpdateService(
$this->connection,
$this->databaseManager,
$this->repository,
$this->dynamic,
$this->encrypter
);
}
}

View file

@ -1,145 +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\Services\Options;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Egg;
use Tests\Traits\MocksUuids;
use Illuminate\Contracts\Config\Repository;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Services\Eggs\EggCreationService;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException;
class EggCreationServiceTest extends TestCase
{
use MocksUuids;
/**
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
protected $config;
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Eggs\EggCreationService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->config = m::mock(Repository::class);
$this->repository = m::mock(EggRepositoryInterface::class);
$this->service = new EggCreationService($this->config, $this->repository);
}
/**
* Test that a new model is created when not using the config from attribute.
*/
public function testCreateNewModelWithoutUsingConfigFrom()
{
$model = factory(Egg::class)->make();
$this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com');
$this->repository->shouldReceive('create')->with([
'uuid' => $this->getKnownUuid(),
'author' => 'test@example.com',
'config_from' => null,
'name' => $model->name,
], true, true)->once()->andReturn($model);
$response = $this->service->handle(['name' => $model->name]);
$this->assertNotEmpty($response);
$this->assertNull(object_get($response, 'config_from'));
$this->assertEquals($model->name, $response->name);
}
/**
* Test that a new model is created when using the config from attribute.
*/
public function testCreateNewModelUsingConfigFrom()
{
$model = factory(Egg::class)->make();
$this->repository->shouldReceive('findCountWhere')->with([
['nest_id', '=', $model->nest_id],
['id', '=', 12345],
])->once()->andReturn(1);
$this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com');
$this->repository->shouldReceive('create')->with([
'nest_id' => $model->nest_id,
'config_from' => 12345,
'uuid' => $this->getKnownUuid(),
'author' => 'test@example.com',
], true, true)->once()->andReturn($model);
$response = $this->service->handle([
'nest_id' => $model->nest_id,
'config_from' => 12345,
]);
$this->assertNotEmpty($response);
$this->assertEquals($response, $model);
}
/**
* Test that certain data, such as the UUID or author takes priority over data
* that is passed into the function.
*/
public function testDataProvidedByHandlerTakesPriorityOverPassedData()
{
$model = factory(Egg::class)->make();
$this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com');
$this->repository->shouldReceive('create')->with([
'uuid' => $this->getKnownUuid(),
'author' => 'test@example.com',
'config_from' => null,
'name' => $model->name,
], true, true)->once()->andReturn($model);
$response = $this->service->handle(['name' => $model->name, 'uuid' => 'should-be-ignored', 'author' => 'should-be-ignored']);
$this->assertNotEmpty($response);
$this->assertNull(object_get($response, 'config_from'));
$this->assertEquals($model->name, $response->name);
}
/**
* Test that an exception is thrown if no parent configuration can be located.
*/
public function testExceptionIsThrownIfNoParentConfigurationIsFound()
{
$this->repository->shouldReceive('findCountWhere')->with([
['nest_id', '=', null],
['id', '=', 1],
])->once()->andReturn(0);
try {
$this->service->handle(['config_from' => 1]);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception);
$this->assertEquals(trans('exceptions.nest.egg.must_be_child'), $exception->getMessage());
}
}
}

View file

@ -1,93 +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\Services\Options;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Services\Eggs\EggDeletionService;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Exceptions\Service\Egg\HasChildrenException;
use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class EggDeletionServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
*/
protected $serverRepository;
/**
* @var \Pterodactyl\Services\Eggs\EggDeletionService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(EggRepositoryInterface::class);
$this->serverRepository = m::mock(ServerRepositoryInterface::class);
$this->service = new EggDeletionService($this->serverRepository, $this->repository);
}
/**
* Test that Egg is deleted if no servers are found.
*/
public function testEggIsDeletedIfNoServersAreFound()
{
$this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(0);
$this->repository->shouldReceive('findCountWhere')->with([['config_from', '=', 1]])->once()->andReturn(0);
$this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1);
$this->assertEquals(1, $this->service->handle(1));
}
/**
* Test that Egg is not deleted if servers are found.
*/
public function testExceptionIsThrownIfServersAreFound()
{
$this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(1);
try {
$this->service->handle(1);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(HasActiveServersException::class, $exception);
$this->assertEquals(trans('exceptions.nest.egg.delete_has_servers'), $exception->getMessage());
}
}
/**
* Test that an exception is thrown if children Eggs exist.
*/
public function testExceptionIsThrownIfChildrenArePresent()
{
$this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(0);
$this->repository->shouldReceive('findCountWhere')->with([['config_from', '=', 1]])->once()->andReturn(1);
try {
$this->service->handle(1);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(HasChildrenException::class, $exception);
$this->assertEquals(trans('exceptions.nest.egg.has_children'), $exception->getMessage());
}
}
}

View file

@ -1,91 +0,0 @@
<?php
namespace Tests\Unit\Services\Services\Options;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Egg;
use Pterodactyl\Services\Eggs\EggUpdateService;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException;
class EggUpdateServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Models\Egg
*/
protected $model;
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Eggs\EggUpdateService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->model = factory(Egg::class)->make(['id' => 123]);
$this->repository = m::mock(EggRepositoryInterface::class);
$this->service = new EggUpdateService($this->repository);
}
/**
* Test that an Egg is updated when no config_from attribute is passed.
*/
public function testEggIsUpdatedWhenNoConfigFromIsProvided()
{
$this->repository->shouldReceive('withoutFreshModel->update')
->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull();
$this->service->handle($this->model, ['test_field' => 'field_value']);
$this->assertTrue(true);
}
/**
* Test that Egg is updated when a valid config_from attribute is passed.
*/
public function testOptionIsUpdatedWhenValidConfigFromIsPassed()
{
$this->repository->shouldReceive('findCountWhere')->with([
['nest_id', '=', $this->model->nest_id],
['id', '=', 1],
])->once()->andReturn(1);
$this->repository->shouldReceive('withoutFreshModel->update')
->with($this->model->id, ['config_from' => 1])->once()->andReturnNull();
$this->service->handle($this->model, ['config_from' => 1]);
$this->assertTrue(true);
}
/**
* Test that an exception is thrown if an invalid config_from attribute is passed.
*/
public function testExceptionIsThrownIfInvalidParentConfigIsPassed()
{
$this->repository->shouldReceive('findCountWhere')->with([
['nest_id', '=', $this->model->nest_id],
['id', '=', 1],
])->once()->andReturn(0);
try {
$this->service->handle($this->model, ['config_from' => 1]);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception);
$this->assertEquals(trans('exceptions.nest.egg.must_be_child'), $exception->getMessage());
}
}
}

View file

@ -1,87 +0,0 @@
<?php
namespace Tests\Unit\Services\Eggs\Scripts;
use Exception;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Egg;
use Pterodactyl\Services\Eggs\Scripts\InstallScriptService;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException;
class InstallScriptServiceTest extends TestCase
{
/**
* @var array
*/
protected $data = [
'script_install' => 'test-script',
'script_is_privileged' => true,
'script_entry' => '/bin/bash',
'script_container' => 'ubuntu',
'copy_script_from' => null,
];
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(EggRepositoryInterface::class);
}
/**
* Test that passing a new copy_script_from attribute works properly.
*/
public function testUpdateWithValidCopyScriptFromAttribute()
{
$model = factory(Egg::class)->make(['id' => 123, 'nest_id' => 456]);
$this->data['copy_script_from'] = 1;
$this->repository->shouldReceive('isCopyableScript')->with(1, $model->nest_id)->once()->andReturn(true);
$this->repository->expects('withoutFreshModel->update')->with($model->id, $this->data)->andReturnNull();
$this->getService()->handle($model, $this->data);
}
/**
* Test that an exception gets raised when the script is not copyable.
*/
public function testUpdateWithInvalidCopyScriptFromAttribute()
{
$this->data['copy_script_from'] = 1;
$this->expectException(InvalidCopyFromException::class);
$this->expectExceptionMessage(trans('exceptions.nest.egg.invalid_copy_id'));
$model = factory(Egg::class)->make(['id' => 123, 'nest_id' => 456]);
$this->repository->expects('isCopyableScript')->with(1, $model->nest_id)->andReturn(false);
$this->getService()->handle($model, $this->data);
}
/**
* Test standard functionality.
*/
public function testUpdateWithoutNewCopyScriptFromAttribute()
{
$model = factory(Egg::class)->make(['id' => 123, 'nest_id' => 456]);
$this->repository->expects('withoutFreshModel->update')->with($model->id, $this->data)->andReturnNull();
$this->getService()->handle($model, $this->data);
}
private function getService()
{
return new InstallScriptService($this->repository);
}
}

View file

@ -1,69 +0,0 @@
<?php
namespace Tests\Unit\Services\Eggs\Sharing;
use Mockery as m;
use Carbon\Carbon;
use Tests\TestCase;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\EggVariable;
use Tests\Assertions\NestedObjectAssertionsTrait;
use Pterodactyl\Services\Eggs\Sharing\EggExporterService;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
class EggExporterServiceTest extends TestCase
{
use NestedObjectAssertionsTrait;
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
Carbon::setTestNow(Carbon::now());
$this->repository = m::mock(EggRepositoryInterface::class);
}
/**
* Test that a JSON structure is returned.
*/
public function testJsonStructureIsExported()
{
$egg = factory(Egg::class)->make([
'id' => 123,
'nest_id' => 456,
]);
$egg->variables = collect([$variable = factory(EggVariable::class)->make()]);
$this->repository->shouldReceive('getWithExportAttributes')->with($egg->id)->once()->andReturn($egg);
$service = new EggExporterService($this->repository);
$response = $service->handle($egg->id);
$this->assertNotEmpty($response);
$data = json_decode($response);
$this->assertEquals(JSON_ERROR_NONE, json_last_error());
$this->assertObjectHasNestedAttribute('meta.version', $data);
$this->assertObjectNestedValueEquals('meta.version', 'PTDL_v1', $data);
$this->assertObjectHasNestedAttribute('author', $data);
$this->assertObjectNestedValueEquals('author', $egg->author, $data);
$this->assertObjectHasNestedAttribute('exported_at', $data);
$this->assertObjectNestedValueEquals('exported_at', Carbon::now()->toIso8601String(), $data);
$this->assertObjectHasNestedAttribute('scripts.installation.script', $data);
$this->assertObjectHasNestedAttribute('scripts.installation.container', $data);
$this->assertObjectHasNestedAttribute('scripts.installation.entrypoint', $data);
$this->assertObjectHasAttribute('variables', $data);
$this->assertArrayHasKey('0', $data->variables);
$this->assertObjectHasAttribute('name', $data->variables[0]);
$this->assertObjectNestedValueEquals('name', $variable->name, $data->variables[0]);
}
}

View file

@ -1,187 +0,0 @@
<?php
namespace Tests\Unit\Services\Eggs\Sharing;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Nest;
use Tests\Traits\MocksUuids;
use Illuminate\Http\UploadedFile;
use Pterodactyl\Models\EggVariable;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Services\Eggs\Sharing\EggImporterService;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
class EggImporterServiceTest extends TestCase
{
use MocksUuids;
/**
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
*/
protected $connection;
/**
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock
*/
protected $eggVariableRepository;
/**
* @var \Illuminate\Http\UploadedFile|\Mockery\Mock
*/
protected $file;
/**
* @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock
*/
protected $nestRepository;
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->file = m::mock(UploadedFile::class);
$this->connection = m::mock(ConnectionInterface::class);
$this->eggVariableRepository = m::mock(EggVariableRepositoryInterface::class);
$this->nestRepository = m::mock(NestRepositoryInterface::class);
$this->repository = m::mock(EggRepositoryInterface::class);
$this->service = new EggImporterService(
$this->connection, $this->repository, $this->eggVariableRepository, $this->nestRepository
);
}
/**
* Test that a service option can be successfully imported.
*/
public function testEggConfigurationIsImported()
{
$egg = factory(Egg::class)->make(['id' => 123]);
$nest = factory(Nest::class)->make(['id' => 456]);
$this->file->expects('getError')->andReturn(UPLOAD_ERR_OK);
$this->file->expects('isFile')->andReturn(true);
$this->file->expects('getSize')->andReturn(100);
$this->file->expects('openFile->fread')->with(100)->once()->andReturn(json_encode([
'meta' => ['version' => 'PTDL_v1'],
'name' => $egg->name,
'author' => $egg->author,
'variables' => [
$variable = factory(EggVariable::class)->make(),
],
]));
$this->nestRepository->shouldReceive('getWithEggs')->with($nest->id)->once()->andReturn($nest);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('create')->with(m::subset([
'uuid' => $this->getKnownUuid(),
'nest_id' => $nest->id,
'name' => $egg->name,
]), true, true)->once()->andReturn($egg);
$this->eggVariableRepository->shouldReceive('create')->with(m::subset([
'egg_id' => $egg->id,
'env_variable' => $variable->env_variable,
]))->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$response = $this->service->handle($this->file, $nest->id);
$this->assertNotEmpty($response);
$this->assertInstanceOf(Egg::class, $response);
$this->assertSame($egg, $response);
}
/**
* Test that an exception is thrown if the file is invalid.
*/
public function testExceptionIsThrownIfFileIsInvalid()
{
$this->expectException(InvalidFileUploadException::class);
$this->expectExceptionMessage(
'The selected file ["test.txt"] was not in a valid format to import. (is_file: true is_valid: true err_code: 4 err: UPLOAD_ERR_NO_FILE)'
);
$this->file->expects('getFilename')->andReturns('test.txt');
$this->file->expects('isFile')->andReturns(true);
$this->file->expects('isValid')->andReturns(true);
$this->file->expects('getError')->twice()->andReturns(UPLOAD_ERR_NO_FILE);
$this->file->expects('getErrorMessage')->andReturns('UPLOAD_ERR_NO_FILE');
$this->service->handle($this->file, 1234);
}
/**
* Test that an exception is thrown if the file is not a file.
*/
public function testExceptionIsThrownIfFileIsNotAFile()
{
$this->expectException(InvalidFileUploadException::class);
$this->expectExceptionMessage(
'The selected file ["test.txt"] was not in a valid format to import. (is_file: false is_valid: true err_code: 4 err: UPLOAD_ERR_NO_FILE)'
);
$this->file->expects('getFilename')->andReturns('test.txt');
$this->file->expects('isFile')->andReturns(false);
$this->file->expects('isValid')->andReturns(true);
$this->file->expects('getError')->twice()->andReturns(UPLOAD_ERR_NO_FILE);
$this->file->expects('getErrorMessage')->andReturns('UPLOAD_ERR_NO_FILE');
$this->service->handle($this->file, 1234);
}
/**
* Test that an exception is thrown if the JSON metadata is invalid.
*/
public function testExceptionIsThrownIfJsonMetaDataIsInvalid()
{
$this->expectException(InvalidFileUploadException::class);
$this->expectExceptionMessage(trans('exceptions.nest.importer.invalid_json_provided'));
$this->file->expects('getError')->andReturn(UPLOAD_ERR_OK);
$this->file->expects('isFile')->andReturn(true);
$this->file->expects('getSize')->andReturn(100);
$this->file->expects('openFile->fread')->with(100)->andReturn(json_encode([
'meta' => ['version' => 'hodor'],
]));
$this->service->handle($this->file, 1234);
}
/**
* Test that an exception is thrown if bad JSON is provided.
*/
public function testExceptionIsThrownIfBadJsonIsProvided()
{
$this->expectException(BadJsonFormatException::class);
$this->expectExceptionMessage(trans('exceptions.nest.importer.json_error', [
'error' => 'Syntax error',
]));
$this->file->expects('getError')->andReturn(UPLOAD_ERR_OK);
$this->file->expects('isFile')->andReturn(true);
$this->file->expects('getSize')->andReturn(100);
$this->file->expects('openFile->fread')->with(100)->andReturn('}');
$this->service->handle($this->file, 1234);
}
}

View file

@ -1,219 +0,0 @@
<?php
namespace Tests\Unit\Services\Eggs\Sharing;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Egg;
use Illuminate\Http\UploadedFile;
use Pterodactyl\Models\EggVariable;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
class EggUpdateImporterServiceTest extends TestCase
{
/**
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
*/
protected $connection;
/**
* @var \Illuminate\Http\UploadedFile|\Mockery\Mock
*/
protected $file;
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService
*/
protected $service;
/**
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock
*/
protected $variableRepository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->connection = m::mock(ConnectionInterface::class);
$this->file = m::mock(UploadedFile::class);
$this->repository = m::mock(EggRepositoryInterface::class);
$this->variableRepository = m::mock(EggVariableRepositoryInterface::class);
$this->service = new EggUpdateImporterService($this->connection, $this->repository, $this->variableRepository);
}
/**
* Test that an egg update is handled correctly using an uploaded file.
*/
public function testEggIsUpdated()
{
$egg = factory(Egg::class)->make(['id' => 123]);
$variable = factory(EggVariable::class)->make();
$this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK);
$this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100);
$this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([
'meta' => ['version' => 'PTDL_v1'],
'name' => $egg->name,
'author' => 'newauthor@example.com',
'variables' => [$variable->toArray()],
]));
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('update')->with($egg->id, m::subset([
'author' => 'newauthor@example.com',
'name' => $egg->name,
]), true, true)->once()->andReturn($egg);
$this->variableRepository->shouldReceive('withoutFreshModel->updateOrCreate')->with([
'egg_id' => $egg->id,
'env_variable' => $variable->env_variable,
], collect($variable)->except(['egg_id', 'env_variable'])->toArray())->once()->andReturnNull();
$this->variableRepository->shouldReceive('setColumns')->with(['id', 'env_variable'])->once()->andReturnSelf()
->shouldReceive('findWhere')->with([['egg_id', '=', $egg->id]])->once()->andReturn(collect([$variable]));
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->service->handle($egg, $this->file);
$this->assertTrue(true);
}
/**
* Test that an imported file with less variables than currently existing deletes
* the un-needed variables from the database.
*/
public function testVariablesMissingFromImportAreDeleted()
{
$egg = factory(Egg::class)->make(['id' => 123]);
$variable1 = factory(EggVariable::class)->make();
$variable2 = factory(EggVariable::class)->make();
$this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK);
$this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100);
$this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([
'meta' => ['version' => 'PTDL_v1'],
'name' => $egg->name,
'author' => 'newauthor@example.com',
'variables' => [$variable1->toArray()],
]));
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('update')->with($egg->id, m::subset([
'author' => 'newauthor@example.com',
'name' => $egg->name,
]), true, true)->once()->andReturn($egg);
$this->variableRepository->shouldReceive('withoutFreshModel->updateOrCreate')->with([
'egg_id' => $egg->id,
'env_variable' => $variable1->env_variable,
], collect($variable1)->except(['egg_id', 'env_variable'])->toArray())->once()->andReturnNull();
$this->variableRepository->shouldReceive('setColumns')->with(['id', 'env_variable'])->once()->andReturnSelf()
->shouldReceive('findWhere')->with([['egg_id', '=', $egg->id]])->once()->andReturn(collect([$variable1, $variable2]));
$this->variableRepository->shouldReceive('deleteWhere')->with([
['egg_id', '=', $egg->id],
['env_variable', '=', $variable2->env_variable],
])->once()->andReturn(1);
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->service->handle($egg, $this->file);
$this->assertTrue(true);
}
/**
* Test that an exception is thrown if the file is invalid.
*/
public function testExceptionIsThrownIfFileIsInvalid()
{
$egg = factory(Egg::class)->make(['id' => 123]);
$this->expectException(InvalidFileUploadException::class);
$this->expectExceptionMessageMatches('/^The selected file \["test\.txt"\] was not in a valid format to import\./');
$file = new UploadedFile('test.txt', 'original.txt', 'application/json', UPLOAD_ERR_NO_FILE, true);
$this->service->handle($egg, $file);
}
/**
* Test that an exception is thrown if the file is not a file.
*/
public function testExceptionIsThrownIfFileIsNotAFile()
{
$egg = factory(Egg::class)->make(['id' => 123]);
$this->expectException(InvalidFileUploadException::class);
$this->expectExceptionMessageMatches('/^The selected file \["test\.txt"\] was not in a valid format to import\./');
$file = m::mock(
new UploadedFile('test.txt', 'original.txt', 'application/json', UPLOAD_ERR_INI_SIZE, true)
)->makePartial();
$file->expects('isFile')->andReturnFalse();
$this->service->handle($egg, $file);
}
/**
* Test that an exception is thrown if the JSON metadata is invalid.
*/
public function testExceptionIsThrownIfJsonMetaDataIsInvalid()
{
$egg = factory(Egg::class)->make(['id' => 123]);
$this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK);
$this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100);
$this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([
'meta' => ['version' => 'hodor'],
]));
try {
$this->service->handle($egg, $this->file);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(InvalidFileUploadException::class, $exception);
$this->assertEquals(trans('exceptions.nest.importer.invalid_json_provided'), $exception->getMessage());
}
}
/**
* Test that an exception is thrown if bad JSON is provided.
*/
public function testExceptionIsThrownIfBadJsonIsProvided()
{
$egg = factory(Egg::class)->make(['id' => 123]);
$this->file->shouldReceive('getError')->withNoArgs()->once()->andReturn(UPLOAD_ERR_OK);
$this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100);
$this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn('}');
try {
$this->service->handle($egg, $this->file);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(BadJsonFormatException::class, $exception);
$this->assertEquals(trans('exceptions.nest.importer.json_error', [
'error' => json_last_error_msg(),
]), $exception->getMessage());
}
}
}

View file

@ -1,185 +0,0 @@
<?php
namespace Tests\Unit\Services\Eggs\Variables;
use Mockery as m;
use Tests\TestCase;
use BadMethodCallException;
use Pterodactyl\Models\EggVariable;
use Illuminate\Contracts\Validation\Factory;
use Pterodactyl\Services\Eggs\Variables\VariableCreationService;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
use Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException;
use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException;
class VariableCreationServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* @var \Illuminate\Contracts\Validation\Factory|\Mockery\Mock
*/
private $validator;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(EggVariableRepositoryInterface::class);
$this->validator = m::mock(Factory::class);
}
/**
* Test basic functionality, data should be stored in the database.
*/
public function testVariableIsCreatedAndStored()
{
$data = ['env_variable' => 'TEST_VAR_123', 'default_value' => 'test'];
$this->repository->shouldReceive('create')->with(m::subset([
'egg_id' => 1,
'default_value' => 'test',
'user_viewable' => false,
'user_editable' => false,
'env_variable' => 'TEST_VAR_123',
]))->once()->andReturn(new EggVariable);
$this->assertInstanceOf(EggVariable::class, $this->getService()->handle(1, $data));
}
/**
* Test that the option key in the data array is properly parsed.
*/
public function testOptionsPassedInArrayKeyAreParsedProperly()
{
$data = ['env_variable' => 'TEST_VAR_123', 'options' => ['user_viewable', 'user_editable']];
$this->repository->shouldReceive('create')->with(m::subset([
'default_value' => '',
'user_viewable' => true,
'user_editable' => true,
'env_variable' => 'TEST_VAR_123',
]))->once()->andReturn(new EggVariable);
$this->assertInstanceOf(EggVariable::class, $this->getService()->handle(1, $data));
}
/**
* Test that an empty (null) value passed in the option key is handled
* properly as an array. Also tests the same case against the default_value.
*
* @see https://github.com/Pterodactyl/Panel/issues/841
* @see https://github.com/Pterodactyl/Panel/issues/943
*/
public function testNullOptionValueIsPassedAsArray()
{
$data = ['env_variable' => 'TEST_VAR_123', 'options' => null, 'default_value' => null];
$this->repository->shouldReceive('create')->with(m::subset([
'default_value' => '',
'user_viewable' => false,
'user_editable' => false,
]))->once()->andReturn(new EggVariable);
$this->assertInstanceOf(EggVariable::class, $this->getService()->handle(1, $data));
}
/**
* Test that all of the reserved variables defined in the model trigger an exception.
*
* @param string $variable
*
* @dataProvider reservedNamesProvider
*/
public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames(string $variable)
{
$this->expectException(ReservedVariableNameException::class);
$this->getService()->handle(1, ['env_variable' => $variable]);
}
/**
* Test that the egg ID applied in the function takes higher priority than an
* ID passed into the handler.
*/
public function testEggIdPassedInDataIsNotApplied()
{
$data = ['egg_id' => 123456, 'env_variable' => 'TEST_VAR_123'];
$this->repository->shouldReceive('create')->with(m::subset([
'egg_id' => 1,
]))->once()->andReturn(new EggVariable);
$this->assertInstanceOf(EggVariable::class, $this->getService()->handle(1, $data));
}
/**
* Test that validation errors due to invalid rules are caught and handled properly.
*/
public function testInvalidValidationRulesResultInException()
{
$this->expectException(BadValidationRuleException::class);
$this->expectExceptionMessage('The validation rule "hodor_door" is not a valid rule for this application.');
$data = ['env_variable' => 'TEST_VAR_123', 'rules' => 'string|hodorDoor'];
$this->validator->shouldReceive('make')->once()
->with(['__TEST' => 'test'], ['__TEST' => 'string|hodorDoor'])
->andReturnSelf();
$this->validator->shouldReceive('fails')->once()
->withNoArgs()
->andThrow(new BadMethodCallException('Method [validateHodorDoor] does not exist.'));
$this->getService()->handle(1, $data);
}
/**
* Test that an exception not stemming from a bad rule is not caught.
*/
public function testExceptionNotCausedByBadRuleIsNotCaught()
{
$this->expectException(BadMethodCallException::class);
$this->expectExceptionMessage('Received something, but no expectations were specified.');
$data = ['env_variable' => 'TEST_VAR_123', 'rules' => 'string'];
$this->validator->shouldReceive('make')->once()
->with(['__TEST' => 'test'], ['__TEST' => 'string'])
->andReturnSelf();
$this->validator->shouldReceive('fails')->once()
->withNoArgs()
->andThrow(new BadMethodCallException('Received something, but no expectations were specified.'));
$this->getService()->handle(1, $data);
}
/**
* Provides the data to be used in the tests.
*
* @return array
*/
public function reservedNamesProvider()
{
$data = [];
$exploded = explode(',', EggVariable::RESERVED_ENV_NAMES);
foreach ($exploded as $e) {
$data[] = [$e];
}
return $data;
}
/**
* Return an instance of the service with mocked dependencies for testing.
*
* @return \Pterodactyl\Services\Eggs\Variables\VariableCreationService
*/
private function getService(): VariableCreationService
{
return new VariableCreationService($this->repository, $this->validator);
}
}

View file

@ -1,241 +0,0 @@
<?php
namespace Tests\Unit\Services\Eggs\Variables;
use Exception;
use Mockery as m;
use Tests\TestCase;
use BadMethodCallException;
use Pterodactyl\Models\EggVariable;
use Illuminate\Contracts\Validation\Factory;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Services\Eggs\Variables\VariableUpdateService;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
use Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException;
use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException;
class VariableUpdateServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Models\EggVariable|\Mockery\Mock
*/
private $model;
/**
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* @var \Illuminate\Contracts\Validation\Factory|\Mockery\Mock
*/
private $validator;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->model = factory(EggVariable::class)->make();
$this->repository = m::mock(EggVariableRepositoryInterface::class);
$this->validator = m::mock(Factory::class);
}
/**
* Test the function when no env_variable key is passed into the function.
*/
public function testVariableIsUpdatedWhenNoEnvironmentVariableIsPassed()
{
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($this->model->id, m::subset([
'user_viewable' => false,
'user_editable' => false,
]))->once()->andReturn(true);
$this->assertTrue($this->getService()->handle($this->model, []));
}
/**
* Test that a null value passed in for the default is converted to a string.
*
* @see https://github.com/Pterodactyl/Panel/issues/934
*/
public function testNullDefaultValue()
{
$this->repository->shouldReceive('withoutFreshModel->update')->with($this->model->id, m::subset([
'user_viewable' => false,
'user_editable' => false,
'default_value' => '',
]))->once()->andReturn(true);
$this->assertTrue($this->getService()->handle($this->model, ['default_value' => null]));
}
/**
* Test the function when a valid env_variable key is passed into the function.
*/
public function testVariableIsUpdatedWhenValidEnvironmentVariableIsPassed()
{
$this->repository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf()
->shouldReceive('findCountWhere')->with([
['env_variable', '=', 'TEST_VAR_123'],
['egg_id', '=', $this->model->option_id],
['id', '!=', $this->model->id],
])->once()->andReturn(0);
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($this->model->id, m::subset([
'user_viewable' => false,
'user_editable' => false,
'env_variable' => 'TEST_VAR_123',
]))->once()->andReturn(true);
$this->assertTrue($this->getService()->handle($this->model, ['env_variable' => 'TEST_VAR_123']));
}
/**
* Test that an empty (null) value passed in the option key is handled
* properly as an array. Also tests that a null description is handled.
*
* @see https://github.com/Pterodactyl/Panel/issues/841
*/
public function testNullOptionValueIsPassedAsArray()
{
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($this->model->id, m::subset([
'user_viewable' => false,
'user_editable' => false,
'description' => '',
]))->once()->andReturn(true);
$this->assertTrue($this->getService()->handle($this->model, ['options' => null, 'description' => null]));
}
/**
* Test that data passed into the handler is overwritten inside the handler.
*/
public function testDataPassedIntoHandlerTakesLowerPriorityThanDataSet()
{
$this->repository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf()
->shouldReceive('findCountWhere')->with([
['env_variable', '=', 'TEST_VAR_123'],
['egg_id', '=', $this->model->option_id],
['id', '!=', $this->model->id],
])->once()->andReturn(0);
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($this->model->id, m::subset([
'user_viewable' => false,
'user_editable' => false,
'env_variable' => 'TEST_VAR_123',
]))->once()->andReturn(true);
$this->assertTrue($this->getService()->handle($this->model, ['user_viewable' => 123456, 'env_variable' => 'TEST_VAR_123']));
}
/**
* Test that a non-unique environment variable triggers an exception.
*/
public function testExceptionIsThrownIfEnvironmentVariableIsNotUnique()
{
$this->repository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf()
->shouldReceive('findCountWhere')->with([
['env_variable', '=', 'TEST_VAR_123'],
['egg_id', '=', $this->model->option_id],
['id', '!=', $this->model->id],
])->once()->andReturn(1);
try {
$this->getService()->handle($this->model, ['env_variable' => 'TEST_VAR_123']);
} catch (Exception $exception) {
$this->assertInstanceOf(DisplayException::class, $exception);
$this->assertEquals(trans('exceptions.service.variables.env_not_unique', [
'name' => 'TEST_VAR_123',
]), $exception->getMessage());
}
}
/**
* Test that all of the reserved variables defined in the model trigger an exception.
*
* @dataProvider reservedNamesProvider
*/
public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames(string $variable)
{
$this->expectException(ReservedVariableNameException::class);
$this->getService()->handle($this->model, ['env_variable' => $variable]);
}
/**
* Test that validation errors due to invalid rules are caught and handled properly.
*/
public function testInvalidValidationRulesResultInException()
{
$this->expectException(BadValidationRuleException::class);
$this->expectExceptionMessage('The validation rule "hodor_door" is not a valid rule for this application.');
$data = ['env_variable' => 'TEST_VAR_123', 'rules' => 'string|hodorDoor'];
$this->repository->shouldReceive('setColumns->findCountWhere')->once()->andReturn(0);
$this->validator->shouldReceive('make')->once()
->with(['__TEST' => 'test'], ['__TEST' => 'string|hodorDoor'])
->andReturnSelf();
$this->validator->shouldReceive('fails')->once()
->withNoArgs()
->andThrow(new BadMethodCallException('Method [validateHodorDoor] does not exist.'));
$this->getService()->handle($this->model, $data);
}
/**
* Test that an exception not stemming from a bad rule is not caught.
*/
public function testExceptionNotCausedByBadRuleIsNotCaught()
{
$this->expectException(BadMethodCallException::class);
$this->expectExceptionMessage('Received something, but no expectations were specified.');
$data = ['rules' => 'string'];
$this->validator->shouldReceive('make')->once()
->with(['__TEST' => 'test'], ['__TEST' => 'string'])
->andReturnSelf();
$this->validator->shouldReceive('fails')->once()
->withNoArgs()
->andThrow(new BadMethodCallException('Received something, but no expectations were specified.'));
$this->getService()->handle($this->model, $data);
}
/**
* Provides the data to be used in the tests.
*
* @return array
*/
public function reservedNamesProvider()
{
$data = [];
$exploded = explode(',', EggVariable::RESERVED_ENV_NAMES);
foreach ($exploded as $e) {
$data[] = [$e];
}
return $data;
}
/**
* Return an instance of the service with mocked dependencies for testing.
*
* @return \Pterodactyl\Services\Eggs\Variables\VariableUpdateService
*/
private function getService(): VariableUpdateService
{
return new VariableUpdateService($this->repository, $this->validator);
}
}

View file

@ -1,56 +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\Locations;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Location;
use Pterodactyl\Services\Locations\LocationCreationService;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
class LocationCreationServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Locations\LocationCreationService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(LocationRepositoryInterface::class);
$this->service = new LocationCreationService($this->repository);
}
/**
* Test that a location is created.
*/
public function testLocationIsCreated()
{
$location = factory(Location::class)->make();
$this->repository->shouldReceive('create')->with(['test_data' => 'test_value'])->once()->andReturn($location);
$response = $this->service->handle(['test_data' => 'test_value']);
$this->assertNotEmpty($response);
$this->assertInstanceOf(Location::class, $response);
$this->assertEquals($location, $response);
}
}

View file

@ -1,76 +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\Locations;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Services\Locations\LocationDeletionService;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Exceptions\Service\Location\HasActiveNodesException;
class LocationDeletionServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
*/
protected $nodeRepository;
/**
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Locations\LocationDeletionService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->nodeRepository = m::mock(NodeRepositoryInterface::class);
$this->repository = m::mock(LocationRepositoryInterface::class);
$this->service = new LocationDeletionService($this->repository, $this->nodeRepository);
}
/**
* Test that a location is deleted.
*/
public function testLocationIsDeleted()
{
$this->nodeRepository->shouldReceive('findCountWhere')->with([['location_id', '=', 123]])->once()->andReturn(0);
$this->repository->shouldReceive('delete')->with(123)->once()->andReturn(1);
$response = $this->service->handle(123);
$this->assertEquals(1, $response);
}
/**
* Test that an exception is thrown if nodes are attached to a location.
*/
public function testExceptionIsThrownIfNodesAreAttached()
{
$this->nodeRepository->shouldReceive('findCountWhere')->with([['location_id', '=', 123]])->once()->andReturn(1);
try {
$this->service->handle(123);
} catch (DisplayException $exception) {
$this->assertInstanceOf(HasActiveNodesException::class, $exception);
$this->assertEquals(trans('exceptions.locations.has_nodes'), $exception->getMessage());
}
}
}

View file

@ -1,67 +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\Locations;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Location;
use Pterodactyl\Services\Locations\LocationUpdateService;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
class LocationUpdateServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Locations\LocationUpdateService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(LocationRepositoryInterface::class);
$this->service = new LocationUpdateService($this->repository);
}
/**
* Test location is updated.
*/
public function testLocationIsUpdated()
{
$model = factory(Location::class)->make(['id' => 123]);
$this->repository->shouldReceive('update')->with(123, ['test_data' => 'test_value'])->once()->andReturn($model);
$response = $this->service->handle($model->id, ['test_data' => 'test_value']);
$this->assertNotEmpty($response);
$this->assertInstanceOf(Location::class, $response);
}
/**
* Test that a model can be passed in place of an ID.
*/
public function testModelCanBePassedToFunction()
{
$model = factory(Location::class)->make(['id' => 123]);
$this->repository->shouldReceive('update')->with(123, ['test_data' => 'test_value'])->once()->andReturn($model);
$response = $this->service->handle($model, ['test_data' => 'test_value']);
$this->assertNotEmpty($response);
$this->assertInstanceOf(Location::class, $response);
}
}

View file

@ -1,95 +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\Services;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Nest;
use Tests\Traits\MocksUuids;
use Illuminate\Contracts\Config\Repository;
use Pterodactyl\Services\Nests\NestCreationService;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
class NestCreationServiceTest extends TestCase
{
use MocksUuids;
/**
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
private $config;
/**
* @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->config = m::mock(Repository::class);
$this->repository = m::mock(NestRepositoryInterface::class);
}
/**
* Test that a new service can be created using the correct data.
*/
public function testCreateNewService()
{
$model = factory(Nest::class)->make();
$this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('testauthor@example.com');
$this->repository->shouldReceive('create')->with([
'uuid' => $this->getKnownUuid(),
'author' => 'testauthor@example.com',
'name' => $model->name,
'description' => $model->description,
], true, true)->once()->andReturn($model);
$response = $this->getService()->handle(['name' => $model->name, 'description' => $model->description]);
$this->assertInstanceOf(Nest::class, $response);
$this->assertEquals($model, $response);
}
/**
* Test creation of a new nest with a defined author. This is used by seeder
* scripts which need to set a specific author for nests in order for other
* functionality to work correctly.
*/
public function testCreateServiceWithDefinedAuthor()
{
$model = factory(Nest::class)->make();
$this->repository->shouldReceive('create')->with([
'uuid' => $this->getKnownUuid(),
'author' => 'support@pterodactyl.io',
'name' => $model->name,
'description' => $model->description,
], true, true)->once()->andReturn($model);
$response = $this->getService()->handle(['name' => $model->name, 'description' => $model->description], 'support@pterodactyl.io');
$this->assertInstanceOf(Nest::class, $response);
$this->assertEquals($model, $response);
}
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Nests\NestCreationService
*/
private function getService(): NestCreationService
{
return new NestCreationService($this->config, $this->repository);
}
}

View file

@ -1,91 +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\Services;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Services\Nests\NestDeletionService;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class NestDeletionServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
*/
protected $serverRepository;
/**
* @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Nests\NestDeletionService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->serverRepository = m::mock(ServerRepositoryInterface::class);
$this->repository = m::mock(NestRepositoryInterface::class);
$this->service = new NestDeletionService($this->serverRepository, $this->repository);
}
/**
* Test that a service is deleted when there are no servers attached to a service.
*/
public function testServiceIsDeleted()
{
$this->serverRepository->shouldReceive('findCountWhere')->with([['nest_id', '=', 1]])->once()->andReturn(0);
$this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1);
$this->assertEquals(1, $this->service->handle(1));
}
/**
* Test that an exception is thrown when there are servers attached to a service.
*
* @dataProvider serverCountProvider
*
* @param int $count
*/
public function testExceptionIsThrownIfServersAreAttached(int $count)
{
$this->serverRepository->shouldReceive('findCountWhere')->with([['nest_id', '=', 1]])->once()->andReturn($count);
try {
$this->service->handle(1);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(HasActiveServersException::class, $exception);
$this->assertEquals(trans('exceptions.nest.delete_has_servers'), $exception->getMessage());
}
}
/**
* Provide assorted server counts to ensure that an exception is always thrown when more than 0 servers are found.
*
* @return array
*/
public function serverCountProvider()
{
return [
[1], [2], [5], [10],
];
}
}

View file

@ -1,62 +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\Services;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Services\Nests\NestUpdateService;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
class NestUpdateServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Nests\NestUpdateService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(NestRepositoryInterface::class);
$this->service = new NestUpdateService($this->repository);
}
/**
* Test that the author key is removed from the data array before updating the record.
*/
public function testAuthorArrayKeyIsRemovedIfPassed()
{
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with(1, ['otherfield' => 'value'])->once()->andReturnNull();
$this->service->handle(1, ['author' => 'author1', 'otherfield' => 'value']);
}
/**
* Test that the function continues to work when no author key is passed.
*/
public function testServiceIsUpdatedWhenNoAuthorKeyIsPassed()
{
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with(1, ['otherfield' => 'value'])->once()->andReturnNull();
$this->service->handle(1, ['otherfield' => 'value']);
}
}

View file

@ -1,79 +0,0 @@
<?php
namespace Tests\Unit\Services\Nodes;
use Mockery as m;
use Tests\TestCase;
use Ramsey\Uuid\Uuid;
use phpmock\phpunit\PHPMock;
use Pterodactyl\Models\Node;
use Ramsey\Uuid\UuidFactory;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Services\Nodes\NodeCreationService;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
class NodeCreationServiceTest extends TestCase
{
use PHPMock;
/**
* @var \Mockery\MockInterface
*/
private $repository;
/**
* @var \Mockery\MockInterface
*/
private $encrypter;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
/* @noinspection PhpParamsInspection */
Uuid::setFactory(
m::mock(UuidFactory::class . '[uuid4]', [
'uuid4' => Uuid::fromString('00000000-0000-0000-0000-000000000000'),
])
);
$this->repository = m::mock(NodeRepositoryInterface::class);
$this->encrypter = m::mock(Encrypter::class);
}
/**
* Test that a node is created and a daemon secret token is created.
*/
public function testNodeIsCreatedAndDaemonSecretIsGenerated()
{
/** @var \Pterodactyl\Models\Node $node */
$node = factory(Node::class)->make();
$this->encrypter->expects('encrypt')->with(m::on(function ($value) {
return strlen($value) === Node::DAEMON_TOKEN_LENGTH;
}))->andReturns('encrypted_value');
$this->repository->expects('create')->with(m::on(function ($value) {
$this->assertTrue(is_array($value));
$this->assertSame('NodeName', $value['name']);
$this->assertSame('00000000-0000-0000-0000-000000000000', $value['uuid']);
$this->assertSame('encrypted_value', $value['daemon_token']);
$this->assertTrue(strlen($value['daemon_token_id']) === Node::DAEMON_TOKEN_ID_LENGTH);
return true;
}), true, true)->andReturn($node);
$this->assertSame($node, $this->getService()->handle(['name' => 'NodeName']));
}
/**
* @return \Pterodactyl\Services\Nodes\NodeCreationService
*/
private function getService()
{
return new NodeCreationService($this->encrypter, $this->repository);
}
}

View file

@ -1,94 +0,0 @@
<?php
namespace Tests\Unit\Services\Nodes;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Node;
use Pterodactyl\Exceptions\DisplayException;
use Illuminate\Contracts\Translation\Translator;
use Pterodactyl\Services\Nodes\NodeDeletionService;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class NodeDeletionServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $serverRepository;
/**
* @var \Illuminate\Contracts\Translation\Translator
*/
protected $translator;
/**
* @var \Pterodactyl\Services\Nodes\NodeDeletionService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(NodeRepositoryInterface::class);
$this->serverRepository = m::mock(ServerRepositoryInterface::class);
$this->translator = m::mock(Translator::class);
$this->service = new NodeDeletionService(
$this->repository,
$this->serverRepository,
$this->translator
);
}
/**
* Test that a node is deleted if there are no servers attached to it.
*/
public function testNodeIsDeletedIfNoServersAreAttached()
{
$this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf()
->shouldReceive('findCountWhere')->with([['node_id', '=', 1]])->once()->andReturn(0);
$this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1);
$this->assertEquals(1, $this->service->handle(1));
}
/**
* Test that an exception is thrown if servers are attached to the node.
*/
public function testExceptionIsThrownIfServersAreAttachedToNode()
{
$this->expectException(DisplayException::class);
$this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf()
->shouldReceive('findCountWhere')->with([['node_id', '=', 1]])->once()->andReturn(1);
$this->translator->shouldReceive('trans')->with('exceptions.node.servers_attached')->once()->andReturnNull();
$this->repository->shouldNotReceive('delete');
$this->service->handle(1);
}
/**
* Test that a model can be passed into the handle function rather than an ID.
*/
public function testModelCanBePassedToFunctionInPlaceOfNodeId()
{
$node = factory(Node::class)->make(['id' => 123]);
$this->serverRepository->shouldReceive('setColumns')->with('id')->once()->andReturnSelf()
->shouldReceive('findCountWhere')->with([['node_id', '=', $node->id]])->once()->andReturn(0);
$this->repository->shouldReceive('delete')->with($node->id)->once()->andReturn(1);
$this->assertEquals(1, $this->service->handle($node));
}
}

View file

@ -1,241 +0,0 @@
<?php
namespace Tests\Unit\Services\Nodes;
use Exception;
use Mockery as m;
use Tests\TestCase;
use GuzzleHttp\Psr7\Request;
use phpmock\phpunit\PHPMock;
use Pterodactyl\Models\Node;
use Tests\Traits\MocksRequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Services\Nodes\NodeUpdateService;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException;
class NodeUpdateServiceTest extends TestCase
{
use PHPMock, MocksRequestException;
/**
* @var \Mockery\MockInterface
*/
private $connection;
/**
* @var \Mockery\MockInterface
*/
private $configurationRepository;
/**
* @var \Mockery\MockInterface
*/
private $encrypter;
/**
* @var \Mockery\MockInterface
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->connection = m::mock(ConnectionInterface::class);
$this->encrypter = m::mock(Encrypter::class);
$this->configurationRepository = m::mock(DaemonConfigurationRepository::class);
$this->repository = m::mock(NodeRepository::class);
}
/**
* Test that the daemon secret is reset when `reset_secret` is passed in the data.
*/
public function testNodeIsUpdatedAndDaemonSecretIsReset()
{
/** @var \Pterodactyl\Models\Node $model */
$model = factory(Node::class)->make([
'fqdn' => 'https://example.com',
]);
/** @var \Pterodactyl\Models\Node $updatedModel */
$updatedModel = factory(Node::class)->make([
'name' => 'New Name',
'fqdn' => 'https://example2.com',
]);
$this->connection->expects('transaction')->with(m::on(function ($closure) use ($updatedModel) {
$response = $closure();
$this->assertIsArray($response);
$this->assertTrue(count($response) === 2);
$this->assertSame($updatedModel, $response[0]);
$this->assertFalse($response[1]);
return true;
}))->andReturns([$updatedModel, false]);
$this->encrypter->expects('encrypt')->with(m::on(function ($value) {
return strlen($value) === Node::DAEMON_TOKEN_LENGTH;
}))->andReturns('encrypted_value');
$this->repository->expects('withFreshModel->update')->with($model->id, m::on(function ($value) {
$this->assertTrue(is_array($value));
$this->assertSame('New Name', $value['name']);
$this->assertSame('encrypted_value', $value['daemon_token']);
$this->assertTrue(strlen($value['daemon_token_id']) === Node::DAEMON_TOKEN_ID_LENGTH);
return true;
}), true, true)->andReturns($updatedModel);
$this->configurationRepository->expects('setNode')->with(m::on(function ($value) use ($model, $updatedModel) {
$this->assertInstanceOf(Node::class, $value);
$this->assertSame($model->uuid, $value->uuid);
// Yes, this is correct. Always use the updated model's FQDN when making requests to
// the Daemon so that any changes to that are properly propagated down to the daemon.
//
// @see https://github.com/pterodactyl/panel/issues/1931
$this->assertSame($updatedModel->fqdn, $value->fqdn);
return true;
}))->andReturnSelf();
$this->configurationRepository->expects('update')->with($updatedModel);
$this->getService()->handle($model, [
'name' => $updatedModel->name,
], true);
}
/**
* Test that daemon secret is not modified when no variable is passed in data.
*/
public function testNodeIsUpdatedAndDaemonSecretIsNotChanged()
{
/** @var \Pterodactyl\Models\Node $model */
$model = factory(Node::class)->make(['fqdn' => 'https://example.com']);
/** @var \Pterodactyl\Models\Node $updatedModel */
$updatedModel = factory(Node::class)->make(['name' => 'New Name', 'fqdn' => $model->fqdn]);
$this->connection->expects('transaction')->with(m::on(function ($closure) use ($updatedModel) {
$response = $closure();
$this->assertIsArray($response);
$this->assertTrue(count($response) === 2);
$this->assertSame($updatedModel, $response[0]);
$this->assertFalse($response[1]);
return true;
}))->andReturns([$updatedModel, false]);
$this->repository->expects('withFreshModel->update')->with($model->id, m::on(function ($value) {
$this->assertTrue(is_array($value));
$this->assertSame('New Name', $value['name']);
$this->assertArrayNotHasKey('daemon_token', $value);
$this->assertArrayNotHasKey('daemon_token_id', $value);
return true;
}), true, true)->andReturns($updatedModel);
$this->configurationRepository->expects('setNode->update')->with($updatedModel);
$this->getService()->handle($model, ['name' => $updatedModel->name]);
}
/**
* Test that an exception caused by a connection error is handled.
*/
public function testExceptionRelatedToConnection()
{
$this->configureExceptionMock(DaemonConnectionException::class);
$this->expectException(ConfigurationNotPersistedException::class);
/** @var \Pterodactyl\Models\Node $model */
$model = factory(Node::class)->make(['fqdn' => 'https://example.com']);
/** @var \Pterodactyl\Models\Node $updatedModel */
$updatedModel = factory(Node::class)->make(['name' => 'New Name', 'fqdn' => $model->fqdn]);
$this->connection->expects('transaction')->with(m::on(function ($closure) use ($updatedModel) {
$response = $closure();
$this->assertIsArray($response);
$this->assertTrue(count($response) === 2);
$this->assertSame($updatedModel, $response[0]);
$this->assertTrue($response[1]);
return true;
}))->andReturn([$updatedModel, true]);
$this->repository->expects('withFreshModel->update')->with($model->id, m::on(function ($value) {
$this->assertTrue(is_array($value));
$this->assertSame('New Name', $value['name']);
$this->assertArrayNotHasKey('daemon_token', $value);
$this->assertArrayNotHasKey('daemon_token_id', $value);
return true;
}), true, true)->andReturns($updatedModel);
$this->configurationRepository->expects('setNode->update')->with($updatedModel)->andThrow(
new DaemonConnectionException(
new ConnectException('', new Request('GET', 'Test'), new Exception)
)
);
$this->getService()->handle($model, ['name' => $updatedModel->name]);
}
/**
* Test that an exception not caused by a daemon connection error is handled.
*/
public function testExceptionNotRelatedToConnection()
{
/** @var \Pterodactyl\Models\Node $model */
$model = factory(Node::class)->make(['fqdn' => 'https://example.com']);
/** @var \Pterodactyl\Models\Node $updatedModel */
$updatedModel = factory(Node::class)->make(['name' => 'New Name', 'fqdn' => $model->fqdn]);
$this->connection->expects('transaction')->with(m::on(function ($closure) use ($updatedModel) {
try {
$closure();
} catch (Exception $exception) {
$this->assertInstanceOf(Exception::class, $exception);
$this->assertSame('Foo', $exception->getMessage());
return true;
}
return false;
}));
$this->repository->expects('withFreshModel->update')->andReturns($updatedModel);
$this->configurationRepository->expects('setNode->update')->andThrow(
new Exception('Foo')
);
$this->getService()->handle($model, ['name' => $updatedModel->name]);
}
/**
* Return an instance of the service with mocked injections.
*
* @return \Pterodactyl\Services\Nodes\NodeUpdateService
*/
private function getService(): NodeUpdateService
{
return new NodeUpdateService(
$this->connection, $this->encrypter, $this->configurationRepository, $this->repository
);
}
}

View file

@ -1,173 +0,0 @@
<?php
namespace Tests\Unit\Services\Servers;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Location;
use Illuminate\Support\Collection;
use Pterodactyl\Models\EggVariable;
use Pterodactyl\Services\Servers\EnvironmentService;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class EnvironmentServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
config()->set('pterodactyl.environment_variables', []);
}
/**
* Test that set environment key stores the key into a retrievable array.
*/
public function testSettingEnvironmentKeyPersistsItInArray()
{
$service = $this->getService();
$service->setEnvironmentKey('TEST_KEY', function () {
return true;
});
$this->assertNotEmpty($service->getEnvironmentKeys());
$this->assertArrayHasKey('TEST_KEY', $service->getEnvironmentKeys());
}
/**
* Test that environment defaults are returned by the process function.
*/
public function testProcessShouldReturnDefaultEnvironmentVariablesForAServer()
{
$model = $this->getServerModel([
'TEST_VARIABLE' => factory(EggVariable::class)->make([
'id' => 987,
'env_variable' => 'TEST_VARIABLE',
'default_value' => 'Test Variable',
]),
]);
$response = $this->getService()->handle($model);
$this->assertNotEmpty($response);
$this->assertCount(4, $response);
$this->assertArrayHasKey('TEST_VARIABLE', $response);
$this->assertSame('Test Variable', $response['TEST_VARIABLE']);
}
/**
* Test that variables included at run-time are also included.
*/
public function testProcessShouldReturnKeySetAtRuntime()
{
$model = $this->getServerModel([]);
$service = $this->getService();
$service->setEnvironmentKey('TEST_VARIABLE', function ($server) {
return $server->uuidShort;
});
$response = $service->handle($model);
$this->assertNotEmpty($response);
$this->assertArrayHasKey('TEST_VARIABLE', $response);
$this->assertSame($model->uuidShort, $response['TEST_VARIABLE']);
}
/**
* Test that duplicate variables provided in config override the defaults.
*/
public function testProcessShouldAllowOverwritingVariablesWithConfigurationFile()
{
config()->set('pterodactyl.environment_variables', [
'P_SERVER_UUID' => 'name',
]);
$model = $this->getServerModel([]);
$response = $this->getService()->handle($model);
$this->assertNotEmpty($response);
$this->assertSame(3, count($response));
$this->assertArrayHasKey('P_SERVER_UUID', $response);
$this->assertSame($model->name, $response['P_SERVER_UUID']);
}
/**
* Test that config based environment variables can be done using closures.
*/
public function testVariablesSetInConfigurationAllowForClosures()
{
config()->set('pterodactyl.environment_variables', [
'P_SERVER_UUID' => function ($server) {
return $server->id * 2;
},
]);
$model = $this->getServerModel([]);
$response = $this->getService()->handle($model);
$this->assertNotEmpty($response);
$this->assertSame(3, count($response));
$this->assertArrayHasKey('P_SERVER_UUID', $response);
$this->assertSame($model->id * 2, $response['P_SERVER_UUID']);
}
/**
* Test that duplicate variables provided at run-time override the defaults and those
* that are defined in the configuration file.
*/
public function testProcessShouldAllowOverwritingDefaultVariablesWithRuntimeProvided()
{
config()->set('pterodactyl.environment_variables', [
'P_SERVER_UUID' => 'overwritten-config',
]);
$model = $this->getServerModel([]);
$service = $this->getService();
$service->setEnvironmentKey('P_SERVER_UUID', function ($model) {
return 'overwritten';
});
$response = $service->handle($model);
$this->assertNotEmpty($response);
$this->assertSame(3, count($response));
$this->assertArrayHasKey('P_SERVER_UUID', $response);
$this->assertSame('overwritten', $response['P_SERVER_UUID']);
}
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Servers\EnvironmentService
*/
private function getService(): EnvironmentService
{
return new EnvironmentService;
}
/**
* Return a server model with a location relationship to be used in the tests.
*
* @param array $variables
* @return \Pterodactyl\Models\Server
*/
private function getServerModel(array $variables): Server
{
/** @var \Pterodactyl\Models\Server $server */
$server = factory(Server::class)->make([
'id' => 123,
'location' => factory(Location::class)->make(),
]);
$server->setRelation('variables', Collection::make($variables));
return $server;
}
}

View file

@ -1,103 +0,0 @@
<?php
namespace Tests\Unit\Services\Servers;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Allocation;
use Pterodactyl\Services\Servers\EnvironmentService;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
class ServerConfigurationStructureServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Services\Servers\EnvironmentService|\Mockery\Mock
*/
private $environment;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->environment = m::mock(EnvironmentService::class);
$this->repository = m::mock(ServerRepositoryInterface::class);
}
/**
* Test that a configuration is returned in the proper format when passed a
* server model that is missing required relationships.
*/
public function testCorrectStructureIsReturned()
{
/** @var \Pterodactyl\Models\Server $model */
$model = factory(Server::class)->make();
$model->setRelation('allocation', factory(Allocation::class)->make());
$model->setRelation('allocations', collect(factory(Allocation::class)->times(2)->make()));
$model->setRelation('egg', factory(Egg::class)->make());
$this->environment->expects('handle')->with($model)->andReturn(['environment_array']);
$response = $this->getService()->handle($model);
$this->assertNotEmpty($response);
$this->assertArrayNotHasKey('user', $response);
$this->assertArrayNotHasKey('keys', $response);
$this->assertArrayHasKey('uuid', $response);
$this->assertArrayHasKey('suspended', $response);
$this->assertArrayHasKey('environment', $response);
$this->assertArrayHasKey('invocation', $response);
$this->assertArrayHasKey('skip_egg_scripts', $response);
$this->assertArrayHasKey('build', $response);
$this->assertArrayHasKey('container', $response);
$this->assertArrayHasKey('allocations', $response);
$this->assertSame([
'default' => [
'ip' => $model->allocation->ip,
'port' => $model->allocation->port,
],
'mappings' => $model->getAllocationMappings(),
], $response['allocations']);
$this->assertSame([
'memory_limit' => $model->memory,
'swap' => $model->swap,
'io_weight' => $model->io,
'cpu_limit' => $model->cpu,
'threads' => $model->threads,
'disk_space' => $model->disk,
], $response['build']);
$this->assertSame([
'image' => $model->image,
'oom_disabled' => $model->oom_disabled,
'requires_rebuild' => false,
], $response['container']);
$this->assertSame($model->uuid, $response['uuid']);
$this->assertSame($model->suspended, $response['suspended']);
$this->assertSame(['environment_array'], $response['environment']);
$this->assertSame($model->startup, $response['invocation']);
}
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Servers\ServerConfigurationStructureService
*/
private function getService(): ServerConfigurationStructureService
{
return new ServerConfigurationStructureService($this->environment);
}
}

View file

@ -1,73 +0,0 @@
<?php
namespace Tests\Unit\Services\Servers;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Allocation;
use Pterodactyl\Models\EggVariable;
use Pterodactyl\Services\Servers\StartupCommandService;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class StartupCommandViewServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(ServerRepositoryInterface::class);
}
/**
* Test that the correct startup string is returned.
*/
public function testServiceResponse()
{
$server = factory(Server::class)->make([
'id' => 123,
'startup' => 'example {{SERVER_MEMORY}} {{SERVER_IP}} {{SERVER_PORT}} {{TEST_VARIABLE}} {{TEST_VARIABLE_HIDDEN}} {{UNKNOWN}}',
]);
$variables = collect([
factory(EggVariable::class)->make([
'env_variable' => 'TEST_VARIABLE',
'server_value' => 'Test Value',
'user_viewable' => 1,
]),
factory(EggVariable::class)->make([
'env_variable' => 'TEST_VARIABLE_HIDDEN',
'server_value' => 'Hidden Value',
'user_viewable' => 0,
]),
]);
$server->setRelation('variables', $variables);
$server->setRelation('allocation', $allocation = factory(Allocation::class)->make());
$response = $this->getService()->handle($server);
$this->assertSame(
sprintf('example %s %s %s %s %s {{UNKNOWN}}', $server->memory, $allocation->ip, $allocation->port, 'Test Value', '[hidden]'),
$response
);
}
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Servers\StartupCommandService
*/
private function getService(): StartupCommandService
{
return new StartupCommandService;
}
}