diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index b73500bb8..baaf1a154 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -46,10 +46,10 @@ class EggTransformer extends BaseTransformer 'description' => $model->description, 'docker_image' => $model->docker_image, 'config' => [ - 'files' => json_decode($model->config_files), - 'startup' => json_decode($model->config_startup), + 'files' => json_decode($model->config_files, true), + 'startup' => json_decode($model->config_startup, true), 'stop' => $model->config_stop, - 'logs' => json_decode($model->config_logs), + 'logs' => json_decode($model->config_logs, true), 'extends' => $model->config_from, ], 'startup' => $model->startup, diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php index 9517af61d..80154a682 100644 --- a/app/Transformers/Api/Application/NestTransformer.php +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -4,6 +4,7 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; +use Pterodactyl\Models\Server; use Pterodactyl\Services\Acl\Api\AdminAcl; class NestTransformer extends BaseTransformer @@ -49,6 +50,8 @@ class NestTransformer extends BaseTransformer * * @param \Pterodactyl\Models\Nest $model * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + * + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEggs(Nest $model) { @@ -60,4 +63,23 @@ class NestTransformer extends BaseTransformer return $this->collection($model->getRelation('eggs'), $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); } + + /** + * Include the servers relationship on the given Nest model. + * + * @param \Pterodactyl\Models\Nest $model + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + * + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + */ + public function includeServers(Nest $model) + { + if (! $this->authorize(AdminAcl::RESOURCE_SERVERS)) { + return $this->null(); + } + + $model->loadMissing('servers'); + + return $this->collection($model->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME); + } } diff --git a/tests/Integration/Api/Application/Nests/EggControllerTest.php b/tests/Integration/Api/Application/Nests/EggControllerTest.php new file mode 100644 index 000000000..4ec2fc5f4 --- /dev/null +++ b/tests/Integration/Api/Application/Nests/EggControllerTest.php @@ -0,0 +1,149 @@ +repository = $this->app->make(EggRepositoryInterface::class); + } + + /** + * Test that all of the eggs belonging to a given nest can be returned. + */ + public function testListAllEggsInNest() + { + $eggs = $this->repository->findWhere([['nest_id', '=', 1]]); + + $response = $this->getJson('/api/application/nests/' . $eggs->first()->nest_id . '/eggs'); + $response->assertStatus(Response::HTTP_OK); + $response->assertJsonCount(count($eggs), 'data'); + $response->assertJsonStructure([ + 'object', + 'data' => [ + [ + 'object', + 'attributes' => [ + 'id', 'uuid', 'nest', 'author', 'description', 'docker_image', 'startup', 'created_at', 'updated_at', + 'script' => ['privileged', 'install', 'entry', 'container', 'extends'], + 'config' => [ + 'files' => [], + 'startup' => ['done', 'userInteraction' => []], + 'stop', + 'logs' => ['custom', 'location'], + 'extends', + ], + ], + ], + ], + ]); + + foreach (array_get($response->json(), 'data') as $datum) { + $egg = $eggs->where('id', '=', $datum['attributes']['id'])->first(); + + $expected = json_encode(Arr::sortRecursive($datum['attributes'])); + $actual = json_encode(Arr::sortRecursive($this->getTransformer(EggTransformer::class)->transform($egg))); + + $this->assertSame($expected, $actual, + 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL . "[{$expected}]" . PHP_EOL . PHP_EOL . 'within' . PHP_EOL . PHP_EOL . "[{$actual}]." + ); + } + } + + /** + * Test that a single egg can be returned. + */ + public function testReturnSingleEgg() + { + $egg = $this->repository->find(1); + + $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id); + $response->assertStatus(Response::HTTP_OK); + $response->assertJsonStructure([ + 'object', + 'attributes' => [ + 'id', 'uuid', 'nest', 'author', 'description', 'docker_image', 'startup', 'script' => [], 'config' => [], 'created_at', 'updated_at', + ], + ]); + + $response->assertJson([ + 'object' => 'egg', + 'attributes' => $this->getTransformer(EggTransformer::class)->transform($egg), + ], true); + } + + /** + * Test that a single egg and all of the defined relationships can be returned. + */ + public function testReturnSingleEggWithRelationships() + { + $egg = $this->repository->find(1); + + $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/' . $egg->id . '?include=servers,variables,nest'); + $response->assertStatus(Response::HTTP_OK); + $response->assertJsonStructure([ + 'object', + 'attributes' => [ + 'relationships' => [ + 'nest' => ['object', 'attributes'], + 'servers' => ['object', 'data' => []], + 'variables' => ['object', 'data' => []], + ], + ], + ]); + } + + /** + * Test that a missing egg returns a 404 error. + */ + public function testGetMissingEgg() + { + $egg = $this->repository->find(1); + + $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); + $this->assertNotFoundJson($response); + } + + /** + * Test that an authentication error occurs if a key does not have permission + * to access a resource. + */ + public function testErrorReturnedIfNoPermission() + { + $egg = $this->repository->find(1); + $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); + + $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs'); + $this->assertAccessDeniedJson($response); + } + + /** + * Test that a nests's existence is not exposed unless an API key has permission + * to access the resource. + */ + public function testResourceIsNotExposedWithoutPermissions() + { + $egg = $this->repository->find(1); + $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); + + $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs/nil'); + $this->assertAccessDeniedJson($response); + } +} diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php new file mode 100644 index 000000000..f0f7b280a --- /dev/null +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -0,0 +1,143 @@ +repository = $this->app->make(NestRepositoryInterface::class); + } + + /** + * Test that the expected nests are returned in the request. + */ + public function testNestResponse() + { + /** @var \Pterodactyl\Models\Nest[] $nests */ + $nests = $this->repository->all(); + + $response = $this->getJson('/api/application/nests'); + $response->assertStatus(Response::HTTP_OK); + $response->assertJsonCount(count($nests), 'data'); + $response->assertJsonStructure([ + 'object', + 'data' => [['object', 'attributes' => ['id', 'uuid', 'author', 'name', 'description', 'created_at', 'updated_at']]], + 'meta' => ['pagination' => ['total', 'count', 'per_page', 'current_page', 'total_pages']], + ]); + + $response->assertJson([ + 'object' => 'list', + 'data' => [], + 'meta' => [ + 'pagination' => [ + 'total' => 4, + 'count' => 4, + 'per_page' => 50, + 'current_page' => 1, + 'total_pages' => 1, + ], + ], + ]); + + foreach ($nests as $nest) { + $response->assertJsonFragment([ + 'object' => 'nest', + 'attributes' => $this->getTransformer(NestTransformer::class)->transform($nest), + ]); + } + } + + /** + * Test that getting a single nest returns the expected result. + */ + public function testSingleNestResponse() + { + $nest = $this->repository->find(1); + + $response = $this->getJson('/api/application/nests/' . $nest->id); + $response->assertStatus(Response::HTTP_OK); + $response->assertJsonStructure([ + 'object', + 'attributes' => ['id', 'uuid', 'author', 'name', 'description', 'created_at', 'updated_at'], + ]); + + $response->assertJson([ + 'object' => 'nest', + 'attributes' => $this->getTransformer(NestTransformer::class)->transform($nest), + ]); + } + + /** + * Test that including eggs in the response works as expected. + */ + public function testSingleNestWithEggsIncluded() + { + $nest = $this->repository->find(1); + $nest->loadMissing('eggs'); + + $response = $this->getJson('/api/application/nests/' . $nest->id . '?include=servers,eggs'); + $response->assertStatus(Response::HTTP_OK); + $response->assertJsonStructure([ + 'object', + 'attributes' => [ + 'relationships' => [ + 'eggs' => ['object', 'data' => []], + 'servers' => ['object', 'data' => []], + ], + ], + ]); + + $response->assertJsonCount(count($nest->getRelation('eggs')), 'attributes.relationships.eggs.data'); + } + + /** + * Test that a missing nest returns a 404 error. + */ + public function testGetMissingNest() + { + $response = $this->getJson('/api/application/nests/nil'); + $this->assertNotFoundJson($response); + } + + /** + * Test that an authentication error occurs if a key does not have permission + * to access a resource. + */ + public function testErrorReturnedIfNoPermission() + { + $nest = $this->repository->find(1); + $this->createNewDefaultApiKey($this->getApiUser(), ['r_nests' => 0]); + + $response = $this->getJson('/api/application/nests/' . $nest->id); + $this->assertAccessDeniedJson($response); + } + + /** + * Test that a nest's existence is not exposed unless an API key has permission + * to access the resource. + */ + public function testResourceIsNotExposedWithoutPermissions() + { + $nest = $this->repository->find(1); + $this->createNewDefaultApiKey($this->getApiUser(), ['r_nests' => 0]); + + $response = $this->getJson('/api/application/nests/' . $nest->id); + $this->assertAccessDeniedJson($response); + } +} diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index 3c2a1ad4d..bfaa2e3f9 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -23,6 +23,9 @@ abstract class IntegrationTestCase extends TestCase Model::unsetEventDispatcher(); } + /** + * @return array + */ protected function connectionsToTransact() { return ['testing'];