Add ability to update an Egg by re-uploading a file.

This commit is contained in:
Dane Everitt 2017-10-08 23:50:52 -05:00
parent e2cb789b2b
commit e01d7497f5
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
17 changed files with 449 additions and 23 deletions

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Exceptions\Service\Egg;
use Pterodactyl\Exceptions\DisplayException;
class BadJsonFormatException extends DisplayException
{
}

View file

@ -7,7 +7,7 @@
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
namespace Pterodactyl\Exceptions\Service\Pack; namespace Pterodactyl\Exceptions\Service;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;

View file

@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Response;
use Pterodactyl\Services\Eggs\Sharing\EggExporterService; use Pterodactyl\Services\Eggs\Sharing\EggExporterService;
use Pterodactyl\Services\Eggs\Sharing\EggImporterService; use Pterodactyl\Services\Eggs\Sharing\EggImporterService;
use Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest; use Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest;
use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService;
class EggShareController extends Controller class EggShareController extends Controller
{ {
@ -35,21 +36,29 @@ class EggShareController extends Controller
*/ */
protected $importerService; protected $importerService;
/**
* @var \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService
*/
protected $updateImporterService;
/** /**
* OptionShareController constructor. * OptionShareController constructor.
* *
* @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Services\Eggs\Sharing\EggExporterService $exporterService * @param \Pterodactyl\Services\Eggs\Sharing\EggExporterService $exporterService
* @param \Pterodactyl\Services\Eggs\Sharing\EggImporterService $importerService * @param \Pterodactyl\Services\Eggs\Sharing\EggImporterService $importerService
* @param \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService $updateImporterService
*/ */
public function __construct( public function __construct(
AlertsMessageBag $alert, AlertsMessageBag $alert,
EggExporterService $exporterService, EggExporterService $exporterService,
EggImporterService $importerService EggImporterService $importerService,
EggUpdateImporterService $updateImporterService
) { ) {
$this->alert = $alert; $this->alert = $alert;
$this->exporterService = $exporterService; $this->exporterService = $exporterService;
$this->importerService = $importerService; $this->importerService = $importerService;
$this->updateImporterService = $updateImporterService;
} }
/** /**
@ -76,7 +85,8 @@ class EggShareController extends Controller
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
*/ */
public function import(EggImportFormRequest $request): RedirectResponse public function import(EggImportFormRequest $request): RedirectResponse
{ {
@ -85,4 +95,24 @@ class EggShareController extends Controller
return redirect()->route('admin.nests.egg.view', ['egg' => $egg->id]); return redirect()->route('admin.nests.egg.view', ['egg' => $egg->id]);
} }
/**
* Update an existing Egg using a new imported file.
*
* @param \Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest $request
* @param int $egg
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
*/
public function update(EggImportFormRequest $request, int $egg): RedirectResponse
{
$this->updateImporterService->handle($egg, $request->file('import_file'));
$this->alert->success(trans('admin/nests.eggs.notices.updated_via_import'))->flash();
return redirect()->route('admin.nests.egg.view', ['egg' => $egg]);
}
} }

View file

@ -156,7 +156,7 @@ class PackController extends Controller
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException
* @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException
* @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException

View file

@ -18,9 +18,14 @@ class EggImportFormRequest extends AdminFormRequest
*/ */
public function rules() public function rules()
{ {
return [ $rules = [
'import_file' => 'bail|required|file|max:1000|mimetypes:application/json,text/plain', 'import_file' => 'bail|required|file|max:1000|mimetypes:application/json,text/plain',
'import_to_nest' => 'bail|required|integer|exists:nests,id',
]; ];
if ($this->method() !== 'PUT') {
$rules['import_to_nest'] = 'bail|required|integer|exists:nests,id';
}
return $rules;
} }
} }

View file

@ -15,7 +15,8 @@ use Illuminate\Http\UploadedFile;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
class EggImporterService class EggImporterService
@ -69,7 +70,8 @@ class EggImporterService
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
*/ */
public function handle(UploadedFile $file, int $nest): Egg public function handle(UploadedFile $file, int $nest): Egg
{ {
@ -78,6 +80,11 @@ class EggImporterService
} }
$parsed = json_decode($file->openFile()->fread($file->getSize())); $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(),
]));
}
if (object_get($parsed, 'meta.version') !== 'PTDL_v1') { if (object_get($parsed, 'meta.version') !== 'PTDL_v1') {
throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided'));

View file

@ -0,0 +1,113 @@
<?php
namespace Pterodactyl\Services\Eggs\Sharing;
use Illuminate\Http\UploadedFile;
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;
class EggUpdateImporterService
{
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $connection;
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface
*/
protected $variableRepository;
/**
* EggUpdateImporterService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $variableRepository
*/
public function __construct(
ConnectionInterface $connection,
EggRepositoryInterface $repository,
EggVariableRepositoryInterface $variableRepository
) {
$this->connection = $connection;
$this->repository = $repository;
$this->variableRepository = $variableRepository;
}
/**
* Update an existing Egg using an uploaded JSON file.
*
* @param int $egg
* @param \Illuminate\Http\UploadedFile $file
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
*/
public function handle(int $egg, UploadedFile $file)
{
if (! $file->isValid() || ! $file->isFile()) {
throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error'));
}
$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(),
]));
}
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, [
'author' => object_get($parsed, 'author'),
'name' => object_get($parsed, 'name'),
'description' => object_get($parsed, 'description'),
'docker_image' => 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->withoutFresh()->updateOrCreate([
'egg_id' => $egg,
'env_variable' => $variable->env_variable,
], collect($variable)->except(['egg_id', 'env_variable'])->toArray());
});
$imported = collect($parsed->variables)->pluck('env_variable')->toArray();
$existing = $this->variableRepository->withColumns(['id', 'env_variable'])->findWhere([['egg_id', '=', $egg]]);
// 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],
['env_variable', '=', $variable->env_variable],
]);
}
});
$this->connection->commit();
}
}

View file

@ -13,8 +13,8 @@ use Ramsey\Uuid\Uuid;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface; use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException;
class PackCreationService class PackCreationService
@ -65,7 +65,7 @@ class PackCreationService
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
*/ */
public function handle(array $data, UploadedFile $file = null) public function handle(array $data, UploadedFile $file = null)
{ {

View file

@ -11,8 +11,8 @@ namespace Pterodactyl\Services\Packs;
use ZipArchive; use ZipArchive;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException;
use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException;
use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException;
@ -58,7 +58,7 @@ class TemplateUploadService
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException
* @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException
@ -94,7 +94,7 @@ class TemplateUploadService
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException
* @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException

View file

@ -16,6 +16,7 @@ return [
'eggs' => [ 'eggs' => [
'notices' => [ 'notices' => [
'imported' => 'Successfully imported this Egg and its associated variables.', 'imported' => 'Successfully imported this Egg and its associated variables.',
'updated_via_import' => 'This Egg has been updated using the file provided.',
'deleted' => 'Successfully deleted the requested egg from the Panel.', 'deleted' => 'Successfully deleted the requested egg from the Panel.',
'updated' => 'Egg configuration has been updated successfully.', 'updated' => 'Egg configuration has been updated successfully.',
'script_updated' => 'Egg install script has been updated and will run whenever servers are installed.', 'script_updated' => 'Egg install script has been updated and will run whenever servers are installed.',

View file

@ -31,6 +31,7 @@ return [
'reserved_name' => 'The environment variable :name is protected and cannot be assigned to a variable.', 'reserved_name' => 'The environment variable :name is protected and cannot be assigned to a variable.',
], ],
'importer' => [ 'importer' => [
'json_error' => 'There was an error while attempting to parse the JSON file: :error.',
'file_error' => 'The JSON file provided was not valid.', 'file_error' => 'The JSON file provided was not valid.',
'invalid_json_provided' => 'The JSON file provided is not in a format that can be recognized.', 'invalid_json_provided' => 'The JSON file provided is not in a format that can be recognized.',
], ],

View file

@ -30,14 +30,39 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="col-xs-12">
<div class="callout callout-info">
<strong>Notice:</strong> Editing an Egg or any of the Process Management fields <em>requires</em> that each Daemon be rebooted in order to apply the changes.
</div>
</div>
</div> </div>
<form action="{{ route('admin.nests.egg.view', $egg->id) }}" method="POST"> <form action="{{ route('admin.nests.egg.view', $egg->id) }}" enctype="multipart/form-data" method="POST">
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="callout callout-info"> <div class="box box-danger">
<strong>Notice:</strong> Editing an Egg or any of the Process Management fields <em>requires</em> that each Daemon be rebooted in order to apply the changes. <div class="box-body">
<div class="row">
<div class="col-xs-8">
<div class="form-group no-margin-bottom">
<label for="pName" class="control-label">Egg File</label>
<div>
<input type="file" name="import_file" class="form-control" style="border: 0;margin-left:-10px;" />
<p class="text-muted small no-margin-bottom">If you would like to replace settings for this Egg by uploading a new JSON file, simply select it here and press "Update Egg". This will not change any existing startup strings or Docker images for existing servers.</p>
</div>
</div>
</div>
<div class="col-xs-4">
{!! csrf_field() !!}
<button type="submit" name="_method" value="PUT" class="btn btn-sm btn-danger pull-right">Update Egg</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div>
</form>
<form action="{{ route('admin.nests.egg.view', $egg->id) }}" method="POST">
<div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">

View file

@ -163,6 +163,8 @@ Route::group(['prefix' => 'nests'], function () {
Route::post('/egg/new', 'Nests\EggController@store'); Route::post('/egg/new', 'Nests\EggController@store');
Route::post('/egg/{egg}/variables', 'Nests\EggVariableController@store'); Route::post('/egg/{egg}/variables', 'Nests\EggVariableController@store');
Route::put('/egg/{egg}', 'Nests\EggShareController@update');
Route::patch('/view/{nest}', 'Nests\NestController@update'); Route::patch('/view/{nest}', 'Nests\NestController@update');
Route::patch('/egg/{egg}', 'Nests\EggController@update'); Route::patch('/egg/{egg}', 'Nests\EggController@update');
Route::patch('/egg/{egg}/scripts', 'Nests\EggScriptController@update'); Route::patch('/egg/{egg}/scripts', 'Nests\EggScriptController@update');

View file

@ -21,7 +21,8 @@ use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Services\Eggs\Sharing\EggImporterService; use Pterodactyl\Services\Eggs\Sharing\EggImporterService;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
class EggImporterServiceTest extends TestCase class EggImporterServiceTest extends TestCase
@ -90,7 +91,7 @@ class EggImporterServiceTest extends TestCase
$this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([
'meta' => ['version' => 'PTDL_v1'], 'meta' => ['version' => 'PTDL_v1'],
'name' => $egg->name, 'name' => $egg->name,
'tag' => $egg->tag, 'author' => $egg->author,
'variables' => [ 'variables' => [
$variable = factory(EggVariable::class)->make(), $variable = factory(EggVariable::class)->make(),
], ],
@ -165,4 +166,24 @@ class EggImporterServiceTest extends TestCase
$this->assertEquals(trans('exceptions.nest.importer.invalid_json_provided'), $exception->getMessage()); $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()
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$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($this->file, 1234);
} 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

@ -0,0 +1,212 @@
<?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()
{
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();
$variable = factory(EggVariable::class)->make();
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$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('withoutFresh->updateOrCreate')->with([
'egg_id' => $egg->id,
'env_variable' => $variable->env_variable,
], collect($variable)->except(['egg_id', 'env_variable'])->toArray())->once()->andReturnNull();
$this->variableRepository->shouldReceive('withColumns')->with(['id', 'env_variable'])->once()->andReturnSelf()
->shouldReceive('findWhere')->with([['egg_id', '=', $egg->id]])->once()->andReturn([$variable]);
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->service->handle($egg->id, $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();
$variable1 = factory(EggVariable::class)->make();
$variable2 = factory(EggVariable::class)->make();
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$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('withoutFresh->updateOrCreate')->with([
'egg_id' => $egg->id,
'env_variable' => $variable1->env_variable,
], collect($variable1)->except(['egg_id', 'env_variable'])->toArray())->once()->andReturnNull();
$this->variableRepository->shouldReceive('withColumns')->with(['id', 'env_variable'])->once()->andReturnSelf()
->shouldReceive('findWhere')->with([['egg_id', '=', $egg->id]])->once()->andReturn([$variable1, $variable2]);
$this->variableRepository->shouldReceive('deleteWhere')->with([
['egg_id', '=', $egg->id],
['env_variable', '=', $variable2->env_variable],
])->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->service->handle($egg->id, $this->file);
$this->assertTrue(true);
}
/**
* Test that an exception is thrown if the file is invalid.
*/
public function testExceptionIsThrownIfFileIsInvalid()
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(false);
try {
$this->service->handle(1234, $this->file);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(InvalidFileUploadException::class, $exception);
$this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage());
}
}
/**
* Test that an exception is thrown if the file is not a file.
*/
public function testExceptionIsThrownIfFileIsNotAFile()
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(false);
try {
$this->service->handle(1234, $this->file);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(InvalidFileUploadException::class, $exception);
$this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage());
}
}
/**
* Test that an exception is thrown if the JSON metadata is invalid.
*/
public function testExceptionIsThrownIfJsonMetaDataIsInvalid()
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$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(1234, $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()
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$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(1234, $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

@ -19,7 +19,7 @@ use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Services\Packs\PackCreationService; use Pterodactyl\Services\Packs\PackCreationService;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface; use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException;
class PackCreationServiceTest extends TestCase class PackCreationServiceTest extends TestCase

View file

@ -16,8 +16,8 @@ use Pterodactyl\Models\Pack;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Pterodactyl\Services\Packs\PackCreationService; use Pterodactyl\Services\Packs\PackCreationService;
use Pterodactyl\Services\Packs\TemplateUploadService; use Pterodactyl\Services\Packs\TemplateUploadService;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException;
use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException;
use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException;