From cca0010a00c76ba60aa959a509103bb8e6e5944f Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 14:40:19 -0400 Subject: [PATCH] Update egg import/update logic to all use the same pathwaus --- app/Observers/EggVariableObserver.php | 22 +++ app/Providers/AppServiceProvider.php | 10 -- app/Providers/EventServiceProvider.php | 21 +++ app/Services/Eggs/EggParserService.php | 89 +++++++++++ .../Eggs/Sharing/EggImporterService.php | 139 ++++-------------- .../Eggs/Sharing/EggUpdateImporterService.php | 106 ++++--------- database/Seeders/EggSeeder.php | 117 +++++---------- .../Application/Nests/EggControllerTest.php | 29 +--- .../Servers/ServerCreationServiceTest.php | 14 +- .../StartupModificationServiceTest.php | 4 +- .../Servers/VariableValidatorServiceTest.php | 25 +++- .../Traits/Integration/CreatesTestModels.php | 29 ++-- 12 files changed, 271 insertions(+), 334 deletions(-) create mode 100644 app/Observers/EggVariableObserver.php create mode 100644 app/Services/Eggs/EggParserService.php diff --git a/app/Observers/EggVariableObserver.php b/app/Observers/EggVariableObserver.php new file mode 100644 index 000000000..a18718f47 --- /dev/null +++ b/app/Observers/EggVariableObserver.php @@ -0,0 +1,22 @@ +field_type) { + unset($variable->field_type); + } + } + + public function updating(EggVariable $variable): void + { + if ($variable->field_type) { + unset($variable->field_type); + } + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 4ccc78641..51fa6e7fd 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,17 +5,11 @@ namespace Pterodactyl\Providers; use View; use Cache; use Illuminate\Support\Str; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; -use Pterodactyl\Observers\UserObserver; use Pterodactyl\Extensions\Themes\Theme; -use Pterodactyl\Observers\ServerObserver; -use Pterodactyl\Observers\SubuserObserver; class AppServiceProvider extends ServiceProvider { @@ -26,10 +20,6 @@ class AppServiceProvider extends ServiceProvider { Schema::defaultStringLength(191); - User::observe(UserObserver::class); - Server::observe(ServerObserver::class); - Subuser::observe(SubuserObserver::class); - View::share('appVersion', $this->versionData()['version'] ?? 'undefined'); View::share('appIsGit', $this->versionData()['is_git'] ?? false); diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 5be9601d6..bf8dfc43d 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,6 +2,14 @@ namespace Pterodactyl\Providers; +use Pterodactyl\Models\User; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; +use Pterodactyl\Models\EggVariable; +use Pterodactyl\Observers\UserObserver; +use Pterodactyl\Observers\ServerObserver; +use Pterodactyl\Observers\SubuserObserver; +use Pterodactyl\Observers\EggVariableObserver; use Pterodactyl\Events\Server\Installed as ServerInstalledEvent; use Pterodactyl\Notifications\ServerInstalled as ServerInstalledNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -18,4 +26,17 @@ class EventServiceProvider extends ServiceProvider ServerInstalledNotification::class, ], ]; + + /** + * Boots the service provider and registers model event listeners. + */ + public function boot() + { + parent::boot(); + + User::observe(UserObserver::class); + Server::observe(ServerObserver::class); + Subuser::observe(SubuserObserver::class); + EggVariable::observe(EggVariableObserver::class); + } } diff --git a/app/Services/Eggs/EggParserService.php b/app/Services/Eggs/EggParserService.php new file mode 100644 index 000000000..ff84fdfd8 --- /dev/null +++ b/app/Services/Eggs/EggParserService.php @@ -0,0 +1,89 @@ +getError() !== UPLOAD_ERR_OK || !$file->isFile()) { + throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.'); + } + + /** @var array $parsed */ + $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); + if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { + throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.'); + } + + return $this->convertToV2($parsed); + } + + /** + * Fills the provided model with the parsed JSON data. + */ + public function fillFromParsed(Egg $model, array $parsed): Egg + { + return $model->forceFill([ + 'name' => Arr::get($parsed, 'name'), + 'description' => Arr::get($parsed, 'description'), + 'features' => Arr::get($parsed, 'features'), + 'docker_images' => Arr::get($parsed, 'docker_images'), + 'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist')) + ->filter(fn ($value) => !empty($value)), + 'update_url' => Arr::get($parsed, 'meta.update_url'), + 'config_files' => Arr::get($parsed, 'config.files'), + 'config_startup' => Arr::get($parsed, 'config.startup'), + 'config_logs' => Arr::get($parsed, 'config.logs'), + 'config_stop' => Arr::get($parsed, 'config.stop'), + 'startup' => Arr::get($parsed, 'startup'), + 'script_install' => Arr::get($parsed, 'scripts.installation.script'), + 'script_entry' => Arr::get($parsed, 'scripts.installation.entrypoint'), + 'script_container' => Arr::get($parsed, 'scripts.installation.container'), + ]); + } + + /** + * Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles + * the "docker_images" field potentially not being present, and not being in the + * expected "key => value" format. + */ + protected function convertToV2(array $parsed): array + { + if (Arr::get($parsed, 'meta.version') === Egg::EXPORT_VERSION) { + return $parsed; + } + + // Maintain backwards compatability for eggs that are still using the old single image + // string format. New eggs can provide an array of Docker images that can be used. + if (!isset($parsed['images'])) { + $images = [Arr::get($parsed, 'image') ?? 'nil']; + } else { + $images = $parsed['images']; + } + + unset($parsed['images'], $parsed['image']); + + $parsed['docker_images'] = []; + foreach ($images as $image) { + $parsed['docker_images'][$image] = $image; + } + + $parsed['variables'] = array_map(function ($value) { + return array_merge($value, ['field_type' => 'text']); + }, $parsed['variables']); + + return $parsed; + } +} diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 74f71e1bd..746ce26be 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -5,141 +5,52 @@ namespace Pterodactyl\Services\Eggs\Sharing; use Ramsey\Uuid\Uuid; use Illuminate\Support\Arr; use Pterodactyl\Models\Egg; +use Pterodactyl\Models\Nest; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Collection; +use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Services\Eggs\EggParserService; class EggImporterService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; + protected ConnectionInterface $connection; - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - protected $eggVariableRepository; + protected EggParserService $parser; - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - protected $nestRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * EggImporterService constructor. - */ - public function __construct( - ConnectionInterface $connection, - EggRepositoryInterface $repository, - EggVariableRepositoryInterface $eggVariableRepository, - NestRepositoryInterface $nestRepository - ) { + public function __construct(ConnectionInterface $connection, EggParserService $parser) + { $this->connection = $connection; - $this->eggVariableRepository = $eggVariableRepository; - $this->repository = $repository; - $this->nestRepository = $nestRepository; + $this->parser = $parser; } /** * Take an uploaded JSON file and parse it into a new egg. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException - * @throws \JsonException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable */ public function handle(UploadedFile $file, int $nest): Egg { - if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { - throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); - } + $parsed = $this->parser->handle($file); - /** @var array $parsed */ - $parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR); - if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); - } + /** @var \Pterodactyl\Models\Nest $nest */ + $nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nest); - if ($parsed['meta']['version'] !== Egg::EXPORT_VERSION) { - $parsed = $this->convertV1ToV2($parsed); - } + return $this->connection->transaction(function () use ($nest, $parsed) { + $egg = (new Egg())->forceFill([ + 'uuid' => Uuid::uuid4()->toString(), + 'nest_id' => $nest->id, + 'author' => Arr::get($parsed, 'author'), + 'copy_script_from' => null, + ]); - $nest = $this->nestRepository->getWithEggs($nest); - $this->connection->beginTransaction(); + $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg->save(); - /** @var \Pterodactyl\Models\Egg $egg */ - $egg = $this->repository->create([ - 'uuid' => Uuid::uuid4()->toString(), - 'nest_id' => $nest->id, - 'author' => Arr::get($parsed, 'author'), - 'name' => Arr::get($parsed, 'name'), - 'description' => Arr::get($parsed, 'description'), - 'features' => Arr::get($parsed, 'features'), - 'docker_images' => Arr::get($parsed, 'docker_images'), - 'file_denylist' => Collection::make(Arr::get($parsed, 'file_denylist'))->filter(function ($value) { - return !empty($value); - }), - 'update_url' => Arr::get($parsed, 'meta.update_url'), - 'config_files' => Arr::get($parsed, 'config.files'), - 'config_startup' => Arr::get($parsed, 'config.startup'), - 'config_logs' => Arr::get($parsed, 'config.logs'), - 'config_stop' => Arr::get($parsed, 'config.stop'), - 'startup' => Arr::get($parsed, 'startup'), - 'script_install' => Arr::get($parsed, 'scripts.installation.script'), - 'script_entry' => Arr::get($parsed, 'scripts.installation.entrypoint'), - 'script_container' => Arr::get($parsed, 'scripts.installation.container'), - 'copy_script_from' => null, - ], true, true); + foreach ($parsed['variables'] ?? [] as $variable) { + EggVariable::query()->forceCreate(array_merge($variable, ['egg_id' => $egg->id])); + } - Collection::make($parsed['variables'] ?? [])->each(function (array $variable) use ($egg) { - unset($variable['field_type']); - - $this->eggVariableRepository->create(array_merge($variable, [ - 'egg_id' => $egg->id, - ])); + return $egg; }); - - $this->connection->commit(); - - return $egg; - } - - /** - * Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles - * the "docker_images" field potentially not being present, and not being in the - * expected "key => value" format. - */ - protected function convertV1ToV2(array $parsed): array - { - // Maintain backwards compatability for eggs that are still using the old single image - // string format. New eggs can provide an array of Docker images that can be used. - if (!isset($parsed['images'])) { - $images = [Arr::get($parsed, 'image') ?? 'nil']; - } else { - $images = $parsed['images']; - } - - unset($parsed['images'], $parsed['image']); - - $parsed['docker_images'] = []; - foreach ($images as $image) { - $parsed['docker_images'][$image] = $image; - } - - $parsed['variables'] = array_map(function ($value) { - return array_merge($value, ['field_type' => 'text']); - }, $parsed['variables']); - - return $parsed; } } diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index 205314314..7d2811642 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -4,105 +4,53 @@ namespace Pterodactyl\Services\Eggs\Sharing; use Pterodactyl\Models\Egg; use Illuminate\Http\UploadedFile; +use Illuminate\Support\Collection; +use Pterodactyl\Models\EggVariable; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; -use Pterodactyl\Exceptions\Service\InvalidFileUploadException; -use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Services\Eggs\EggParserService; class EggUpdateImporterService { - /** - * @var \Illuminate\Database\ConnectionInterface - */ - protected $connection; + protected ConnectionInterface $connection; - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface - */ - protected $variableRepository; + protected EggParserService $parser; /** * EggUpdateImporterService constructor. */ - public function __construct( - ConnectionInterface $connection, - EggRepositoryInterface $repository, - EggVariableRepositoryInterface $variableRepository - ) { + public function __construct(ConnectionInterface $connection, EggParserService $parser) + { $this->connection = $connection; - $this->repository = $repository; - $this->variableRepository = $variableRepository; + $this->parser = $parser; } /** * Update an existing Egg using an uploaded JSON file. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException - * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable */ - public function handle(Egg $egg, UploadedFile $file) + public function handle(Egg $egg, UploadedFile $file): Egg { - if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) { - throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage())); - } + $parsed = $this->parser->handle($file); - $parsed = json_decode($file->openFile()->fread($file->getSize())); - if (json_last_error() !== 0) { - throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()])); - } + return $this->connection->transaction(function () use ($egg, $parsed) { + $egg = $this->parser->fillFromParsed($egg, $parsed); + $egg->save(); - if (object_get($parsed, 'meta.version') !== 'PTDL_v1') { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); - } - - $this->connection->beginTransaction(); - $this->repository->update($egg->id, [ - 'author' => object_get($parsed, 'author'), - 'name' => object_get($parsed, 'name'), - 'description' => object_get($parsed, 'description'), - 'features' => object_get($parsed, 'features'), - // Maintain backwards compatibility for eggs that are still using the old single image - // string format. New eggs can provide an array of Docker images that can be used. - 'docker_images' => object_get($parsed, 'images') ?? [object_get($parsed, 'image')], - 'config_files' => object_get($parsed, 'config.files'), - 'config_startup' => object_get($parsed, 'config.startup'), - 'config_logs' => object_get($parsed, 'config.logs'), - 'config_stop' => object_get($parsed, 'config.stop'), - 'startup' => object_get($parsed, 'startup'), - 'script_install' => object_get($parsed, 'scripts.installation.script'), - 'script_entry' => object_get($parsed, 'scripts.installation.entrypoint'), - 'script_container' => object_get($parsed, 'scripts.installation.container'), - ], true, true); - - // Update Existing Variables - collect($parsed->variables)->each(function ($variable) use ($egg) { - $this->variableRepository->withoutFreshModel()->updateOrCreate([ - 'egg_id' => $egg->id, - 'env_variable' => $variable->env_variable, - ], collect($variable)->except(['egg_id', 'env_variable'])->toArray()); - }); - - $imported = collect($parsed->variables)->pluck('env_variable')->toArray(); - $existing = $this->variableRepository->setColumns(['id', 'env_variable'])->findWhere([['egg_id', '=', $egg->id]]); - - // Delete variables not present in the import. - collect($existing)->each(function ($variable) use ($egg, $imported) { - if (!in_array($variable->env_variable, $imported)) { - $this->variableRepository->deleteWhere([ - ['egg_id', '=', $egg->id], - ['env_variable', '=', $variable->env_variable], - ]); + // Update existing variables or create new ones. + foreach ($parsed['variables'] ?? [] as $variable) { + EggVariable::unguarded(function () use ($egg, $variable) { + $egg->variables()->updateOrCreate([ + 'env_variable' => $variable['env_variable'], + ], Collection::make($variable)->except('egg_id', 'env_variable')->toArray()); + }); } - }); - $this->connection->commit(); + $imported = array_map(fn ($value) => $value['env_variable'], $parsed['variables'] ?? []); + + $egg->variables()->whereNotIn('env_variable', $imported)->delete(); + + return $egg->refresh(); + }); } } diff --git a/database/Seeders/EggSeeder.php b/database/Seeders/EggSeeder.php index 4c7697cf0..234e7b5a4 100644 --- a/database/Seeders/EggSeeder.php +++ b/database/Seeders/EggSeeder.php @@ -2,59 +2,38 @@ namespace Database\Seeders; +use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Illuminate\Database\Seeder; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Collection; -use Illuminate\Filesystem\Filesystem; use Pterodactyl\Services\Eggs\Sharing\EggImporterService; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; -use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService; class EggSeeder extends Seeder { - /** - * @var \Illuminate\Filesystem\Filesystem - */ - private $filesystem; + protected EggImporterService $importerService; + + protected EggUpdateImporterService $updateImporterService; /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService + * @var string[] */ - private $importerService; - - /** - * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface - */ - private $nestRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService - */ - private $updateImporterService; + public static array $import = [ + 'Minecraft', + 'Source Engine', + 'Voice Servers', + 'Rust', + ]; /** * EggSeeder constructor. */ public function __construct( EggImporterService $importerService, - EggRepositoryInterface $repository, - EggUpdateImporterService $updateImporterService, - Filesystem $filesystem, - NestRepositoryInterface $nestRepository + EggUpdateImporterService $updateImporterService ) { - $this->filesystem = $filesystem; $this->importerService = $importerService; - $this->repository = $repository; $this->updateImporterService = $updateImporterService; - $this->nestRepository = $nestRepository; } /** @@ -62,72 +41,44 @@ class EggSeeder extends Seeder */ public function run() { - $this->getEggsToImport()->each(function ($nest) { - $this->parseEggFiles($this->findMatchingNest($nest)); - }); - } - - /** - * Return a list of eggs to import. - */ - protected function getEggsToImport(): Collection - { - return collect([ - 'Minecraft', - 'Source Engine', - 'Voice Servers', - 'Rust', - ]); - } - - /** - * Find the nest that these eggs should be attached to. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - private function findMatchingNest(string $nestName): Nest - { - return $this->nestRepository->findFirstWhere([ - ['author', '=', 'support@pterodactyl.io'], - ['name', '=', $nestName], - ]); + foreach (static::$import as $nest) { + /* @noinspection PhpParamsInspection */ + $this->parseEggFiles( + Nest::query()->where('author', 'support@pterodactyl.io')->where('name', $nest)->firstOrFail() + ); + } } /** * Loop through the list of egg files and import them. */ - private function parseEggFiles(Nest $nest) + protected function parseEggFiles(Nest $nest) { - $files = $this->filesystem->allFiles(database_path('Seeders/eggs/' . kebab_case($nest->name))); + $files = new \DirectoryIterator(database_path('Seeders/eggs/' . kebab_case($nest->name))); $this->command->alert('Updating Eggs for Nest: ' . $nest->name); - Collection::make($files)->each(function ($file) use ($nest) { - /* @var \Symfony\Component\Finder\SplFileInfo $file */ - $decoded = json_decode($file->getContents()); - if (json_last_error() !== JSON_ERROR_NONE) { - $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); - - return; + /** @var \DirectoryIterator $file */ + foreach ($files as $file) { + if (!$file->isFile() || !$file->isReadable()) { + continue; } + $decoded = json_decode(file_get_contents($file->getRealPath()), true, 512, JSON_THROW_ON_ERROR); $file = new UploadedFile($file->getPathname(), $file->getFilename(), 'application/json'); - try { - $egg = $this->repository->setColumns('id')->findFirstWhere([ - ['author', '=', $decoded->author], - ['name', '=', $decoded->name], - ['nest_id', '=', $nest->id], - ]); + $egg = $nest->eggs() + ->where('author', $decoded['author']) + ->where('name', $decoded['name']) + ->first(); + if ($egg instanceof Egg) { $this->updateImporterService->handle($egg, $file); - - $this->command->info('Updated ' . $decoded->name); - } catch (RecordNotFoundException $exception) { + $this->command->info('Updated ' . $decoded['name']); + } else { $this->importerService->handle($file, $nest->id); - - $this->command->comment('Created ' . $decoded->name); + $this->command->comment('Created ' . $decoded['name']); } - }); + } $this->command->line(''); } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Nests/EggControllerTest.php index 0482e91eb..7fc523658 100644 --- a/tests/Integration/Api/Application/Nests/EggControllerTest.php +++ b/tests/Integration/Api/Application/Nests/EggControllerTest.php @@ -3,34 +3,19 @@ namespace Pterodactyl\Tests\Integration\Api\Application\Nests; use Illuminate\Support\Arr; +use Pterodactyl\Models\Egg; use Illuminate\Http\Response; -use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase; class EggControllerTest extends ApplicationApiIntegrationTestCase { - /** - * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface - */ - private $repository; - - /** - * Setup tests. - */ - public function setUp(): void - { - parent::setUp(); - - $this->repository = $this->app->make(EggRepositoryInterface::class); - } - /** * Test that all the eggs belonging to a given nest can be returned. */ public function testListAllEggsInNest() { - $eggs = $this->repository->findWhere([['nest_id', '=', 1]]); + $eggs = Egg::query()->where('nest_id', 1)->get(); $response = $this->getJson('/api/application/nests/' . $eggs->first()->nest_id . '/eggs'); $response->assertStatus(Response::HTTP_OK); @@ -74,7 +59,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testReturnSingleEgg() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id); $response->assertStatus(Response::HTTP_OK); @@ -96,7 +81,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testReturnSingleEggWithRelationships() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id . '?include=servers,variables,nest'); $response->assertStatus(Response::HTTP_OK); @@ -117,7 +102,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetMissingEgg() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); $this->assertNotFoundJson($response); @@ -129,7 +114,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs'); @@ -142,7 +127,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testResourceIsNotExposedWithoutPermissions() { - $egg = $this->repository->find(1); + $egg = Egg::query()->findOrFail(1); $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); diff --git a/tests/Integration/Services/Servers/ServerCreationServiceTest.php b/tests/Integration/Services/Servers/ServerCreationServiceTest.php index 329e3be1d..c194c21a5 100644 --- a/tests/Integration/Services/Servers/ServerCreationServiceTest.php +++ b/tests/Integration/Services/Servers/ServerCreationServiceTest.php @@ -25,7 +25,9 @@ class ServerCreationServiceTest extends IntegrationTestCase use WithFaker; /** @var \Mockery\MockInterface */ - private $daemonServerRepository; + protected $daemonServerRepository; + + protected Egg $bungeecord; /** * Stub the calls to Wings so that we don't actually hit those API endpoints. @@ -34,6 +36,12 @@ class ServerCreationServiceTest extends IntegrationTestCase { parent::setUp(); + /* @noinspection PhpFieldAssignmentTypeMismatchInspection */ + $this->bungeecord = Egg::query() + ->where('author', 'support@pterodactyl.io') + ->where('name', 'Bungeecord') + ->firstOrFail(); + $this->daemonServerRepository = Mockery::mock(DaemonServerRepository::class); $this->swap(DaemonServerRepository::class, $this->daemonServerRepository); } @@ -67,7 +75,7 @@ class ServerCreationServiceTest extends IntegrationTestCase $allocations[0]->port, ]); - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->bungeecord); // We want to make sure that the validator service runs as an admin, and not as a regular // user when saving variables. $egg->variables()->first()->update([ @@ -178,7 +186,7 @@ class ServerCreationServiceTest extends IntegrationTestCase 'cpu' => 0, 'startup' => 'java server2.jar', 'image' => 'java:8', - 'egg_id' => 1, + 'egg_id' => $this->bungeecord->id, 'environment' => [ 'BUNGEE_VERSION' => '123', 'SERVER_JARFILE' => 'server2.jar', diff --git a/tests/Integration/Services/Servers/StartupModificationServiceTest.php b/tests/Integration/Services/Servers/StartupModificationServiceTest.php index 8f61e1a18..31a1b85a2 100644 --- a/tests/Integration/Services/Servers/StartupModificationServiceTest.php +++ b/tests/Integration/Services/Servers/StartupModificationServiceTest.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Tests\Integration\Services\Servers; use Exception; -use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; @@ -23,8 +22,7 @@ class StartupModificationServiceTest extends IntegrationTestCase */ public function testNonAdminCanModifyServerVariables() { - // Theoretically lines up with the Bungeecord Minecraft egg. - $server = $this->createServerModel(['egg_id' => 1]); + $server = $this->createServerModel(); try { $this->app->make(StartupModificationService::class)->handle($server, [ diff --git a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php index 01f6f7f02..86a725746 100644 --- a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php +++ b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php @@ -11,13 +11,25 @@ use Pterodactyl\Services\Servers\VariableValidatorService; class VariableValidatorServiceTest extends IntegrationTestCase { + protected Egg $egg; + + public function setUp(): void + { + parent::setUp(); + + /* @noinspection PhpFieldAssignmentTypeMismatchInspection */ + $this->egg = Egg::query() + ->where('author', 'support@pterodactyl.io') + ->where('name', 'Bungeecord') + ->firstOrFail(); + } + /** * Test that enviornment variables for a server are validated as expected. */ public function testEnvironmentVariablesCanBeValidated() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); try { $this->getService()->handle($egg->id, [ @@ -54,8 +66,7 @@ class VariableValidatorServiceTest extends IntegrationTestCase */ public function testNormalUserCannotValidateNonUserEditableVariables() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->first()->update([ 'user_editable' => false, ]); @@ -74,8 +85,7 @@ class VariableValidatorServiceTest extends IntegrationTestCase public function testEnvironmentVariablesCanBeUpdatedAsAdmin() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->first()->update([ 'user_editable' => false, ]); @@ -107,8 +117,7 @@ class VariableValidatorServiceTest extends IntegrationTestCase public function testNullableEnvironmentVariablesCanBeUsedCorrectly() { - /** @noinspection PhpParamsInspection */ - $egg = $this->cloneEggAndVariables(Egg::query()->findOrFail(1)); + $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->where('env_variable', '!=', 'BUNGEE_VERSION')->delete(); $egg->variables()->update(['rules' => 'nullable|string']); diff --git a/tests/Traits/Integration/CreatesTestModels.php b/tests/Traits/Integration/CreatesTestModels.php index bafa9baa8..0885ccc5a 100644 --- a/tests/Traits/Integration/CreatesTestModels.php +++ b/tests/Traits/Integration/CreatesTestModels.php @@ -4,7 +4,6 @@ namespace Pterodactyl\Tests\Traits\Integration; use Ramsey\Uuid\Uuid; use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Nest; use Pterodactyl\Models\Node; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; @@ -52,20 +51,17 @@ trait CreatesTestModels $attributes['allocation_id'] = $allocation->id; } - if (!isset($attributes['nest_id'])) { - /** @var \Pterodactyl\Models\Nest $nest */ - $nest = Nest::with('eggs')->first(); - $attributes['nest_id'] = $nest->id; + if (empty($attributes['egg_id'])) { + $egg = !empty($attributes['nest_id']) + ? Egg::query()->where('nest_id', $attributes['nest_id'])->firstOrFail() + : $this->getBungeecordEgg(); - if (!isset($attributes['egg_id'])) { - $attributes['egg_id'] = $nest->getRelation('eggs')->first()->id; - } + $attributes['egg_id'] = $egg->id; + $attributes['nest_id'] = $egg->nest_id; } - if (!isset($attributes['egg_id'])) { - /** @var \Pterodactyl\Models\Egg $egg */ - $egg = Egg::where('nest_id', $attributes['nest_id'])->first(); - $attributes['egg_id'] = $egg->id; + if (empty($attributes['nest_id'])) { + $attributes['nest_id'] = Egg::query()->findOrFail($attributes['egg_id'])->nest_id; } unset($attributes['user_id'], $attributes['location_id']); @@ -99,4 +95,13 @@ trait CreatesTestModels return $model->fresh(); } + + /** + * Most every test just assumes it is using Bungeecord — this is the critical + * egg model for all tests unless specified otherwise. + */ + private function getBungeecordEgg() + { + return Egg::query()->where('author', 'support@pterodactyl.io')->where('name', 'Bungeecord')->firstOrFail(); + } }