Skip to content

Commit 7a643be

Browse files
committed
Add test coverage for startup modification
1 parent d087beb commit 7a643be

File tree

3 files changed

+147
-18
lines changed

3 files changed

+147
-18
lines changed

app/Services/Servers/StartupModificationService.php

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,21 @@ public function handle(Server $server, array $data): Server
8989
*/
9090
protected function updateAdministrativeSettings(array $data, Server &$server)
9191
{
92-
if (
93-
is_digit(Arr::get($data, 'egg_id'))
94-
&& $data['egg_id'] != $server->egg_id
95-
&& is_null(Arr::get($data, 'nest_id'))
96-
) {
92+
$eggId = Arr::get($data, 'egg_id');
93+
94+
if (is_digit($eggId) && $server->egg_id !== (int)$eggId) {
9795
/** @var \Pterodactyl\Models\Egg $egg */
98-
$egg = Egg::query()->select(['nest_id'])->findOrFail($data['egg_id']);
96+
$egg = Egg::query()->findOrFail($data['egg_id']);
9997

100-
$data['nest_id'] = $egg->nest_id;
98+
$server = $server->forceFill([
99+
'egg_id' => $egg->id,
100+
'nest_id' => $egg->nest_id,
101+
]);
101102
}
102103

103104
$server->forceFill([
104105
'installed' => 0,
105106
'startup' => $data['startup'] ?? $server->startup,
106-
'nest_id' => $data['nest_id'] ?? $server->nest_id,
107-
'egg_id' => $data['egg_id'] ?? $server->egg_id,
108107
'skip_scripts' => $data['skip_scripts'] ?? isset($data['skip_scripts']),
109108
'image' => $data['docker_image'] ?? $server->image,
110109
])->save();

tests/Integration/Services/Servers/StartupModificationServiceTest.php

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
namespace Pterodactyl\Tests\Integration\Services\Servers;
44

55
use Exception;
6+
use Ramsey\Uuid\Uuid;
7+
use Pterodactyl\Models\Egg;
8+
use Pterodactyl\Models\User;
9+
use Pterodactyl\Models\Nest;
610
use Pterodactyl\Models\Server;
711
use Pterodactyl\Models\ServerVariable;
812
use Illuminate\Validation\ValidationException;
913
use Pterodactyl\Tests\Integration\IntegrationTestCase;
14+
use Illuminate\Database\Eloquent\ModelNotFoundException;
1015
use Pterodactyl\Services\Servers\StartupModificationService;
1116

1217
class StartupModificationServiceTest extends IntegrationTestCase
@@ -46,20 +51,121 @@ public function testNonAdminCanModifyServerVariables()
4651

4752
ServerVariable::query()->where('variable_id', $server->variables[1]->id)->delete();
4853

49-
/** @var \Pterodactyl\Models\Server $result */
50-
$result = $this->app->make(StartupModificationService::class)->handle($server, [
51-
'egg_id' => $server->egg_id + 1,
52-
'startup' => 'random gibberish',
53-
'environment' => [
54-
'BUNGEE_VERSION' => '1234',
55-
'SERVER_JARFILE' => 'test.jar',
56-
],
57-
]);
54+
$result = $this->getService()
55+
->handle($server, [
56+
'egg_id' => $server->egg_id + 1,
57+
'startup' => 'random gibberish',
58+
'environment' => [
59+
'BUNGEE_VERSION' => '1234',
60+
'SERVER_JARFILE' => 'test.jar',
61+
],
62+
]);
5863

5964
$this->assertInstanceOf(Server::class, $result);
6065
$this->assertCount(2, $result->variables);
6166
$this->assertSame($server->startup, $result->startup);
6267
$this->assertSame('1234', $result->variables[0]->server_value);
6368
$this->assertSame('test.jar', $result->variables[1]->server_value);
6469
}
70+
71+
/**
72+
* Test that modifying an egg as an admin properly updates the data for the server.
73+
*/
74+
public function testServerIsProperlyModifiedAsAdminUser()
75+
{
76+
/** @var \Pterodactyl\Models\Egg $nextEgg */
77+
$nextEgg = Nest::query()->findOrFail(2)->eggs()->firstOrFail();
78+
79+
$server = $this->createServerModel(['egg_id' => 1]);
80+
81+
$this->assertNotSame($nextEgg->id, $server->egg_id);
82+
$this->assertNotSame($nextEgg->nest_id, $server->nest_id);
83+
84+
$response = $this->getService()
85+
->setUserLevel(User::USER_LEVEL_ADMIN)
86+
->handle($server, [
87+
'egg_id' => $nextEgg->id,
88+
'startup' => 'sample startup',
89+
'skip_scripts' => true,
90+
'docker_image' => 'docker/hodor',
91+
]);
92+
93+
$this->assertInstanceOf(Server::class, $response);
94+
$this->assertSame($nextEgg->id, $response->egg_id);
95+
$this->assertSame($nextEgg->nest_id, $response->nest_id);
96+
$this->assertSame('sample startup', $response->startup);
97+
$this->assertSame('docker/hodor', $response->image);
98+
$this->assertTrue($response->skip_scripts);
99+
}
100+
101+
/**
102+
* Test that hidden variables can be updated by an admin but are not affected by a
103+
* regular user who attempts to pass them through.
104+
*/
105+
public function testEnvironmentVariablesCanBeUpdatedByAdmin()
106+
{
107+
$server = $this->createServerModel();
108+
$server->loadMissing(['egg', 'variables']);
109+
110+
$clone = $this->cloneEggAndVariables($server->egg);
111+
// This makes the BUNGEE_VERSION variable not user editable.
112+
$clone->variables()->first()->update([
113+
'user_editable' => false,
114+
]);
115+
116+
$server->fill(['egg_id' => $clone->id])->saveOrFail();
117+
$server->refresh();
118+
119+
ServerVariable::query()->updateOrCreate([
120+
'server_id' => $server->id,
121+
'variable_id' => $server->variables[0]->id,
122+
], ['variable_value' => 'EXIST']);
123+
124+
$response = $this->getService()->handle($server, [
125+
'environment' => [
126+
'BUNGEE_VERSION' => '1234',
127+
'SERVER_JARFILE' => 'test.jar',
128+
],
129+
]);
130+
131+
$this->assertCount(2, $response->variables);
132+
$this->assertSame('EXIST', $response->variables[0]->server_value);
133+
$this->assertSame('test.jar', $response->variables[1]->server_value);
134+
135+
$response = $this->getService()
136+
->setUserLevel(User::USER_LEVEL_ADMIN)
137+
->handle($server, [
138+
'environment' => [
139+
'BUNGEE_VERSION' => '1234',
140+
'SERVER_JARFILE' => 'test.jar',
141+
],
142+
]);
143+
144+
$this->assertCount(2, $response->variables);
145+
$this->assertSame('1234', $response->variables[0]->server_value);
146+
$this->assertSame('test.jar', $response->variables[1]->server_value);
147+
}
148+
149+
/**
150+
* Test that passing an invalid egg ID into the function throws an exception
151+
* rather than silently failing or skipping.
152+
*/
153+
public function testInvalidEggIdTriggersException()
154+
{
155+
$server = $this->createServerModel();
156+
157+
$this->expectException(ModelNotFoundException::class);
158+
159+
$this->getService()
160+
->setUserLevel(User::USER_LEVEL_ADMIN)
161+
->handle($server, ['egg_id' => 123456789]);
162+
}
163+
164+
/**
165+
* @return \Pterodactyl\Services\Servers\StartupModificationService
166+
*/
167+
private function getService()
168+
{
169+
return $this->app->make(StartupModificationService::class);
170+
}
65171
}

tests/Traits/Integration/CreatesTestModels.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Tests\Traits\Integration;
44

5+
use Ramsey\Uuid\Uuid;
56
use Pterodactyl\Models\Egg;
67
use Pterodactyl\Models\Nest;
78
use Pterodactyl\Models\Node;
@@ -74,4 +75,27 @@ public function createServerModel(array $attributes = []): Server
7475
'location', 'user', 'node', 'allocation', 'nest', 'egg',
7576
])->findOrFail($server->id);
7677
}
78+
79+
/**
80+
* Clones a given egg allowing us to make modifications that don't affect other
81+
* tests that rely on the egg existing in the correct state.
82+
*
83+
* @param \Pterodactyl\Models\Egg $egg
84+
* @return \Pterodactyl\Models\Egg
85+
*/
86+
protected function cloneEggAndVariables(Egg $egg): Egg
87+
{
88+
$model = $egg->replicate(['id', 'uuid']);
89+
$model->uuid = Uuid::uuid4()->toString();
90+
$model->push();
91+
92+
/** @var \Pterodactyl\Models\Egg $model */
93+
$model = $model->fresh();
94+
95+
foreach ($egg->variables as $variable) {
96+
$variable->replicate(['id', 'egg_id'])->forceFill(['egg_id' => $model->id])->push();
97+
}
98+
99+
return $model->fresh();
100+
}
77101
}

0 commit comments

Comments
 (0)